diff options
author | Josh Rahm <joshuarahm@gmail.com> | 2022-08-30 23:29:44 -0600 |
---|---|---|
committer | Josh Rahm <joshuarahm@gmail.com> | 2022-08-30 23:29:44 -0600 |
commit | 442d4e54c30b8e193e3f6e4d32b43e96815bccd7 (patch) | |
tree | b52e341e7db3d2428d8762a7ecf9b58dd84ff6c4 | |
parent | 8436383af96dc7afa3596fc22c012d68e76f47f8 (diff) | |
parent | f4274d0f62625683486d3912dcd6e8e45877c6a4 (diff) | |
download | rneovim-442d4e54c30b8e193e3f6e4d32b43e96815bccd7.tar.gz rneovim-442d4e54c30b8e193e3f6e4d32b43e96815bccd7.tar.bz2 rneovim-442d4e54c30b8e193e3f6e4d32b43e96815bccd7.zip |
Merge remote-tracking branch 'upstream/master' into usermarks
490 files changed, 60513 insertions, 43361 deletions
diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index 11f5f17bba..23c7862b99 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -49,7 +49,11 @@ ee031eb5256bb83e0d6add2bae6fd943a4186ffe 271bb32855853b011fceaf0ad2f829bce66b2a19 aefdc6783cb77f09786542c90901a9e7120bea42 aa4f9c5341f5280f16cce0630ea54b84eef717b3 +0adc66171a355a12494d87ebb767d509540c7ef9 # typos d238b8f6003d34cae7f65ff7585b48a2cd9449fb 4547137aaff32b20172870a549d3a28a3c7adf1c + +# generated docs +ea333badd24f691c753d8048f911d1db349bc2cd diff --git a/.gitattributes b/.gitattributes index e09a918303..1770c9b164 100755 --- a/.gitattributes +++ b/.gitattributes @@ -1,6 +1,16 @@ -*.h linguist-language=C +*.h.in linguist-language=C +*.c.in linguist-language=C +*CMakeLists.txt linguist-language=CMake + +runtime/doc/* linguist-documentation + +src/xdiff/** linguist-vendored +src/cjson/** linguist-vendored +src/unicode/** linguist-vendored + src/nvim/testdir/test42.in diff -.github/ export-ignore + +.github/ export-ignore .travis.yml export-ignore codecov.yml export-ignore -.builds/ export-ignore +.builds/ export-ignore diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d670db5fac..e22d99067a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,11 +18,6 @@ concurrency: jobs: lint: - # This job tests two things: it lints the code but also builds neovim using - # system dependencies instead of bundled dependencies. This is to make sure - # we are able to build neovim without pigeonholing ourselves into specifics - # of the bundled dependencies. - if: (github.event_name == 'pull_request' && github.base_ref == 'master') || (github.event_name == 'push' && github.ref == 'refs/heads/master') runs-on: ubuntu-20.04 timeout-minutes: 10 @@ -42,7 +37,6 @@ jobs: autoconf \ automake \ build-essential \ - ccache \ cmake \ flake8 \ gettext \ @@ -66,7 +60,6 @@ jobs: ninja-build \ pkg-config - - name: Cache uncrustify id: cache-uncrustify uses: actions/cache@v3 @@ -97,19 +90,11 @@ jobs: with: path: | ${{ env.CACHE_NVIM_DEPS_DIR }} - ~/.ccache key: lint-${{ hashFiles('cmake/*', '**/CMakeLists.txt', '!cmake.deps/**CMakeLists.txt') }}-${{ github.base_ref }} - name: Build third-party deps run: ./ci/before_script.sh - - name: Build nvim - run: ./ci/run_tests.sh build_nvim - - - if: "!cancelled()" - name: lintcfull - run: make lintcfull - - if: "!cancelled()" name: lintstylua uses: JohnnyMorganz/stylua-action@1.0.0 @@ -118,12 +103,6 @@ jobs: args: --check runtime/ - if: "!cancelled()" - name: uncrustify - run: | - ${{ env.CACHE_UNCRUSTIFY }} -c ./src/uncrustify.cfg -q --replace --no-backup $(find ./src/nvim -name "*.[ch]") - git diff --color --exit-code - - - if: "!cancelled()" name: lintlua run: make lintlua @@ -136,6 +115,91 @@ jobs: run: make lintsh - if: "!cancelled()" + name: uncrustify + run: | + ${{ env.CACHE_UNCRUSTIFY }} -c ./src/uncrustify.cfg -q --replace --no-backup $(find ./src/nvim -name "*.[ch]") + + - if: "!cancelled()" + name: suggester / uncrustify + uses: reviewdog/action-suggester@v1 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + tool_name: uncrustify + cleanup: false + + - if: "!cancelled()" + name: check uncrustify + run: | + git diff --color --exit-code + + - name: Cache dependencies + run: ./ci/before_cache.sh + + lintc: + # This job tests two things: it lints the code but also builds neovim using + # system dependencies instead of bundled dependencies. This is to make sure + # we are able to build neovim without pigeonholing ourselves into specifics + # of the bundled dependencies. + + if: (github.event_name == 'pull_request' && github.base_ref == 'master') || (github.event_name == 'push' && github.ref == 'refs/heads/master') + runs-on: ubuntu-20.04 + timeout-minutes: 10 + env: + CC: gcc + steps: + - uses: actions/checkout@v3 + + - name: Setup common environment variables + run: ./.github/workflows/env.sh lint + + - name: Install apt packages + run: | + sudo add-apt-repository ppa:neovim-ppa/stable + sudo apt-get update + sudo apt-get install -y \ + autoconf \ + automake \ + build-essential \ + 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 + + - 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 }} + + - name: Build third-party deps + run: ./ci/before_script.sh + + - name: Build nvim + run: ./ci/run_tests.sh build_nvim + + - if: "!cancelled()" + name: lintc + run: make lintc + + - if: "!cancelled()" name: check-single-includes run: make check-single-includes @@ -190,7 +254,7 @@ jobs: if: matrix.os == 'linux' run: | sudo apt-get update - sudo apt-get install -y autoconf automake build-essential ccache 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 + 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 - name: Install minimum required version of cmake if: matrix.cmake == 'minimum_required' @@ -222,7 +286,7 @@ jobs: if: matrix.os == 'osx' run: | brew update --quiet - brew install automake ccache cpanminus ninja + brew install automake cpanminus ninja - name: Setup interpreter packages run: ./ci/install.sh @@ -232,7 +296,6 @@ jobs: with: path: | ${{ env.CACHE_NVIM_DEPS_DIR }} - ~/.ccache key: ${{ matrix.runner }}-${{ matrix.flavor }}-${{ matrix.cc }}-${{ hashFiles('cmake/*', 'cmake.deps/**', '**/CMakeLists.txt') }}-${{ github.base_ref }} - name: Build third-party deps diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000000..b31382af37 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,42 @@ +name: "CodeQL" + +on: + schedule: + - cron: '42 0 * * 0' + workflow_dispatch: +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'cpp', 'python' ] + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Setup common environment variables + 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 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + + - if: matrix.language == 'cpp' + run: make + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/env.sh b/.github/workflows/env.sh index e7c9d19f3a..061588da1a 100755 --- a/.github/workflows/env.sh +++ b/.github/workflows/env.sh @@ -8,7 +8,6 @@ $HOME/.local/bin EOF cat <<EOF >> "$GITHUB_ENV" -CACHE_ENABLE=true CI_BUILD_DIR=$GITHUB_WORKSPACE BUILD_DIR=$GITHUB_WORKSPACE/build DEPS_BUILD_DIR=$HOME/nvim-deps @@ -20,10 +19,6 @@ 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 -CCACHE_BASEDIR=$GITHUB_WORKSPACE -CCACHE_COMPRESS=1 -CCACHE_SLOPPINESS=time_macros,file_macro -CCACHE_DIR=$HOME/.ccache EOF DEPS_CMAKE_FLAGS= diff --git a/.github/workflows/release-winget.yml b/.github/workflows/release-winget.yml deleted file mode 100644 index c3ca5fe752..0000000000 --- a/.github/workflows/release-winget.yml +++ /dev/null @@ -1,12 +0,0 @@ -name: Publish to WinGet -on: - release: - types: [released] -jobs: - publish: - runs-on: windows-latest # action can only be run on windows - steps: - - uses: vedantmgoyal2009/winget-releaser@latest - with: - identifier: Neovim.Neovim - token: ${{ secrets.WINGET_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cab57add52..d6933e9330 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,10 +13,10 @@ on: - v[0-9]+.[0-9]+.[0-9]+ # Build on the oldest supported images, so we have broader compatibility -# Upgrade to gcc-11 to prevent it from using its builtins (#14150) +# Build with gcc-10 to prevent triggering #14150 (default is still gcc-9 on 20.04) jobs: linux: - runs-on: ubuntu-18.04 + runs-on: ubuntu-20.04 outputs: version: ${{ steps.build.outputs.version }} release: ${{ steps.build.outputs.release }} @@ -27,7 +27,7 @@ jobs: - name: Install dependencies run: | sudo apt-get update - sudo apt-get install -y autoconf automake build-essential cmake gcc-11 gettext libtool-bin locales ninja-build pkg-config unzip + sudo apt-get install -y autoconf automake build-essential cmake gettext libtool-bin locales ninja-build pkg-config unzip - if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.tag_name != 'nightly') run: printf 'NVIM_BUILD_TYPE=Release\n' >> $GITHUB_ENV - if: github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.event.inputs.tag_name == 'nightly') @@ -35,7 +35,7 @@ jobs: - name: Build release id: build run: | - CC=gcc-11 make CMAKE_BUILD_TYPE=${NVIM_BUILD_TYPE} CMAKE_EXTRA_FLAGS="-DCMAKE_INSTALL_PREFIX:PATH=" + 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)" make DESTDIR="$GITHUB_WORKSPACE/build/release/nvim-linux64" install @@ -50,7 +50,7 @@ jobs: retention-days: 1 appimage: - runs-on: ubuntu-18.04 + runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v3 with: @@ -58,11 +58,11 @@ jobs: - name: Install dependencies run: | sudo apt-get update - sudo apt-get install -y autoconf automake build-essential cmake gcc-11 gettext libtool-bin locales ninja-build pkg-config unzip + sudo apt-get install -y autoconf automake build-essential cmake gettext libtool-bin locales ninja-build pkg-config unzip - if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.tag_name != 'nightly') - run: CC=gcc-11 make appimage-latest + run: CC=gcc-10 make appimage-latest - if: github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.event.inputs.tag_name == 'nightly') - run: CC=gcc-11 make appimage-nightly + run: CC=gcc-10 make appimage-nightly - uses: actions/upload-artifact@v3 with: name: appimage @@ -150,7 +150,7 @@ jobs: publish: needs: [linux, appimage, macOS, windows] - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest env: GH_REPO: ${{ github.repository }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -227,3 +227,16 @@ 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 # run after publish job is finished + # publish to winget only on stable releases + if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.tag_name != 'nightly') + runs-on: windows-latest # action can only be run on windows + steps: + - uses: vedantmgoyal2009/winget-releaser@latest + with: + identifier: Neovim.Neovim + # the latter one is a fallback value, reference: + # https://docs.github.com/en/actions/using-jobs/using-concurrency#example-using-a-fallback-value + release-tag: ${{ github.event.inputs.tag_name || github.ref }} + token: ${{ secrets.WINGET_TOKEN }} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 17622fa33a..582d5fbdd4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -219,10 +219,10 @@ You can lint a single file (but this will _not_ exclude legacy errors): ### Style - You can format files by using: -``` - make format -``` -This will format changed Lua and C files with all appropriate flags set. + ``` + make format + ``` + This will format changed Lua and C files with all appropriate flags set. - Style rules are (mostly) defined by `src/uncrustify.cfg` which tries to match the [style-guide]. To use the Nvim `gq` command with `uncrustify`: ``` @@ -245,15 +245,61 @@ This will format changed Lua and C files with all appropriate flags set. ``` git config blame.ignoreRevsFile .git-blame-ignore-revs ``` -- Use **[universal-ctags](https://github.com/universal-ctags/ctags).** - ("Exuberant ctags", the typical `ctags` binary provided by your distro, is - unmaintained and won't recognize many function signatures in Neovim source.) + +- Recommendation is to use **[clangd]**. + Can use the maintained config in [nvim-lspconfig/clangd]. - Explore the source code [on the web](https://sourcegraph.com/github.com/neovim/neovim). -- If using [lua-language-server][], symlink `contrib/luarc.json` into the +- If using [lua-language-server], symlink `contrib/luarc.json` into the project root: $ ln -s contrib/luarc.json .luarc.json +### Includes + +For managing includes in C files, use [include-what-you-use]. + +- [Install include-what-you-use][include-what-you-use-install] +- Run with: + ``` + make CMAKE_EXTRA_FLAGS=-DCMAKE_C_INCLUDE_WHAT_YOU_USE=include-what-you-use | tee iwyu.txt + ``` + +See [#549][549] for more details. + +Documenting +----------- + +Many parts of the `:help` documentation are autogenerated from C or Lua docstrings using the `./scripts/gen_vimdoc.py` script. +You can filter the regeneration based on the target (api, lua, or lsp), or the file you changed, that need a doc refresh using `./scripts/gen_vimdoc.py -t <target>`. + +## Lua docstrings + +Lua documentation uses a subset of [EmmyLua] annotations. A rough outline of a function documentation is + +```lua +--- {Brief} +--- +--- {Long explanation} +--- +---@param arg1 type {description} +---@param arg2 type {description} +{...} +--- +---@return type {description} +``` + +If possible, always add type information (`table`, `string`, `number`, ...). Multiple valid types are separated by a bar (`string|table`). Indicate optional parameters via `type|nil`. + +If a function in your Lua module should not be documented (e.g. internal function or local function), you should set the doc comment to: + +``` +---@private +``` + +Mark functions that are deprecated as +``` +---@deprecated +``` Reviewing --------- @@ -271,30 +317,36 @@ commits in the feature branch which aren't in the `master` branch; `-p` shows each commit's diff. To show the whole surrounding function of a change as context, use the `-W` argument as well. +[549]: https://github.com/neovim/neovim/issues/549 +[1820]: https://github.com/neovim/neovim/pull/1820 +[3174]: https://github.com/neovim/neovim/issues/3174 +[ASan]: http://clang.llvm.org/docs/AddressSanitizer.html +[Clang report]: https://neovim.io/doc/reports/clang/ +[GitHub Actions]: https://github.com/neovim/neovim/actions +[clangd]: https://clangd.llvm.org +[Merge a Vim patch]: https://github.com/neovim/neovim/wiki/Merging-patches-from-upstream-Vim +[complexity:low]: https://github.com/neovim/neovim/issues?q=is%3Aopen+is%3Aissue+label%3Acomplexity%3Alow +[conventional_commits]: https://www.conventionalcommits.org +[EmmyLua]: https://github.com/sumneko/lua-language-server/wiki/Annotations [gcc-warnings]: https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html +[gh]: https://cli.github.com/ [git-bisect]: http://git-scm.com/book/en/v2/Git-Tools-Debugging-with-Git [git-feature-branch]: https://www.atlassian.com/git/tutorials/comparing-workflows [git-history-filtering]: https://www.atlassian.com/git/tutorials/git-log/filtering-the-commit-history [git-history-rewriting]: http://git-scm.com/book/en/v2/Git-Tools-Rewriting-History [git-rebasing]: http://git-scm.com/book/en/v2/Git-Branching-Rebasing [github-issues]: https://github.com/neovim/neovim/issues -[1820]: https://github.com/neovim/neovim/pull/1820 -[gh]: https://cli.github.com/ -[conventional_commits]: https://www.conventionalcommits.org -[style-guide]: https://neovim.io/doc/user/dev_style.html#dev-style -[ASan]: http://clang.llvm.org/docs/AddressSanitizer.html -[run-tests]: https://github.com/neovim/neovim/blob/master/test/README.md#running-tests -[wiki-faq]: https://github.com/neovim/neovim/wiki/FAQ -[review-checklist]: https://github.com/neovim/neovim/wiki/Code-review-checklist -[3174]: https://github.com/neovim/neovim/issues/3174 -[sourcehut]: https://builds.sr.ht/~jmk -[GitHub Actions]: https://github.com/neovim/neovim/actions -[Merge a Vim patch]: https://github.com/neovim/neovim/wiki/Merging-patches-from-upstream-Vim -[Clang report]: https://neovim.io/doc/reports/clang/ -[complexity:low]: https://github.com/neovim/neovim/issues?q=is%3Aopen+is%3Aissue+label%3Acomplexity%3Alow +[include-what-you-use-install]: https://github.com/include-what-you-use/include-what-you-use#how-to-install +[include-what-you-use]: https://github.com/include-what-you-use/include-what-you-use#using-with-cmake +[lua-language-server]: https://github.com/sumneko/lua-language-server/ [master error list]: https://raw.githubusercontent.com/neovim/doc/gh-pages/reports/clint/errors.json -[wiki-contribute-help]: https://github.com/neovim/neovim/wiki/contribute-%3Ahelp +[nvim-lspconfig/clangd]: https://github.com/neovim/nvim-lspconfig/blob/master/doc/server_configurations.md#clangd [pr-draft]: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request [pr-ready]: https://docs.github.com/en/github/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/changing-the-stage-of-a-pull-request +[review-checklist]: https://github.com/neovim/neovim/wiki/Code-review-checklist +[run-tests]: https://github.com/neovim/neovim/blob/master/test/README.md#running-tests +[sourcehut]: https://builds.sr.ht/~jmk +[style-guide]: https://neovim.io/doc/user/dev_style.html#dev-style [uncrustify]: http://uncrustify.sourceforge.net/ -[lua-language-server]: https://github.com/sumneko/lua-language-server/ +[wiki-contribute-help]: https://github.com/neovim/neovim/wiki/contribute-%3Ahelp +[wiki-faq]: https://github.com/neovim/neovim/wiki/FAQ diff --git a/ci/before_cache.sh b/ci/before_cache.sh index bec6c37bbe..9bc9bb45e9 100755 --- a/ci/before_cache.sh +++ b/ci/before_cache.sh @@ -12,11 +12,6 @@ mkdir -p "${HOME}/.cache" echo "before_cache.sh: cache size" du -chd 1 "${HOME}/.cache" | sort -rh | head -20 -echo "before_cache.sh: ccache stats" -ccache -s 2>/dev/null || true -# Do not keep ccache stats (uploaded to cache otherwise; reset initially anyway). -find "${HOME}/.ccache" -name stats -delete - # Update the third-party dependency cache only if the build was successful. if ended_successfully && [ -d "${DEPS_BUILD_DIR}" ]; then # Do not cache downloads. They should not be needed with up-to-date deps. diff --git a/ci/before_script.sh b/ci/before_script.sh index f7216338d4..08e0cb9103 100755 --- a/ci/before_script.sh +++ b/ci/before_script.sh @@ -16,11 +16,6 @@ if [[ -n "${LLVM_SYMBOLIZER}" ]] && [[ ! $(type -P "${LLVM_SYMBOLIZER}") ]]; the exit 1 fi -echo "before_script.sh: ccache stats (will be cleared)" -ccache -s -# Reset ccache stats for real results in before_cache. -ccache --zero-stats - # Compile dependencies. build_deps diff --git a/ci/build.ps1 b/ci/build.ps1 index 6c042f9116..6709a9507a 100644 --- a/ci/build.ps1 +++ b/ci/build.ps1 @@ -101,6 +101,8 @@ function Test { # 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'; $_ }
diff --git a/ci/common/build.sh b/ci/common/build.sh index f083796a28..6e7ea2c8f8 100644 --- a/ci/common/build.sh +++ b/ci/common/build.sh @@ -24,9 +24,7 @@ build_deps() { mkdir -p "${DEPS_BUILD_DIR}" # Use cached dependencies if $CACHE_MARKER exists. - if test "${CACHE_ENABLE}" = "false" ; then - export CCACHE_RECACHE=1 - elif test -f "${CACHE_MARKER}" ; then + if test -f "${CACHE_MARKER}"; then echo "Using third-party dependencies from cache (last update: $(_stat "${CACHE_MARKER}"))." cp -a "${CACHE_NVIM_DEPS_DIR}"/. "${DEPS_BUILD_DIR}" fi diff --git a/cmake.deps/CMakeLists.txt b/cmake.deps/CMakeLists.txt index 8724ab4916..27659b2a56 100644 --- a/cmake.deps/CMakeLists.txt +++ b/cmake.deps/CMakeLists.txt @@ -8,6 +8,11 @@ include(CheckCCompilerFlag) # Point CMake at any custom modules we may ship list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake" "${PROJECT_SOURCE_DIR}/../cmake") +get_property(isMultiConfig GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(NOT isMultiConfig) + set(BUILD_TYPE_STRING -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}) +endif() + # In Windows/MSVC CMAKE_BUILD_TYPE changes the paths/linking of the build # recipes (libuv, msgpack), make sure it is set if(NOT CMAKE_BUILD_TYPE) @@ -63,11 +68,9 @@ endif() option(USE_EXISTING_SRC_DIR "Skip download of deps sources in case of existing source directory." OFF) -if(WIN32) - find_package(Git) - if(NOT Git_FOUND) - message(FATAL_ERROR "Git is required to apply patches for Windows.") - endif() +find_package(Git) +if(NOT Git_FOUND) + message(FATAL_ERROR "Git is required to apply patches.") endif() if(UNIX) @@ -159,8 +162,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/a7d0265480c662964988f83d4e245bf139eb7cc0.tar.gz) -set(LUAJIT_SHA256 7d7f58ca5c02b453ed4ddd2298e741053cbd6cd3d96e79460d06ec6684244c59) +set(LUAJIT_URL https://github.com/LuaJIT/LuaJIT/archive/633f265f67f322cbe2c5fd11d3e46d968ac220f7.tar.gz) +set(LUAJIT_SHA256 2681f0a6f624a64a8dfb70a5a377d494daf38960442c547d9c468674c1afa3c2) set(LUA_URL https://www.lua.org/ftp/lua-5.1.5.tar.gz) set(LUA_SHA256 2640fc56a795f29d28ef15e13c34a47e223960b0240e8cb0a82d9b0738695333) diff --git a/cmake.deps/cmake/BuildGettext.cmake b/cmake.deps/cmake/BuildGettext.cmake index 6128ecfa69..f36c00c559 100644 --- a/cmake.deps/cmake/BuildGettext.cmake +++ b/cmake.deps/cmake/BuildGettext.cmake @@ -1,5 +1,4 @@ if(MSVC) - ExternalProject_Add(gettext PREFIX ${DEPS_BUILD_DIR} URL ${GETTEXT_URL} @@ -19,14 +18,13 @@ if(MSVC) -DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR} # Pass toolchain -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN} - -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} + ${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 ${CMAKE_BUILD_TYPE} INSTALL_COMMAND ${CMAKE_COMMAND} --build . --target install --config ${CMAKE_BUILD_TYPE}) - 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 5ff33e0cd3..434bfd6979 100644 --- a/cmake.deps/cmake/BuildLibiconv.cmake +++ b/cmake.deps/cmake/BuildLibiconv.cmake @@ -1,5 +1,4 @@ if(MSVC) - ExternalProject_Add(libiconv PREFIX ${DEPS_BUILD_DIR} URL ${LIBICONV_URL} @@ -19,12 +18,11 @@ if(MSVC) -DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR} # Pass toolchain -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN} - -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} + ${BUILD_TYPE_STRING} -DCMAKE_GENERATOR=${CMAKE_GENERATOR} -DCMAKE_GENERATOR_PLATFORM=${CMAKE_GENERATOR_PLATFORM} BUILD_COMMAND ${CMAKE_COMMAND} --build . --config ${CMAKE_BUILD_TYPE} INSTALL_COMMAND ${CMAKE_COMMAND} --build . --target install --config ${CMAKE_BUILD_TYPE}) - 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 d44e09d734..66bac57d23 100644 --- a/cmake.deps/cmake/BuildLibtermkey.cmake +++ b/cmake.deps/cmake/BuildLibtermkey.cmake @@ -18,7 +18,7 @@ ExternalProject_Add(libtermkey -DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR} # Pass toolchain -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN} - -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} + ${BUILD_TYPE_STRING} # Hack to avoid -rdynamic in Mingw -DCMAKE_SHARED_LIBRARY_LINK_C_FLAGS="" -DCMAKE_GENERATOR=${CMAKE_GENERATOR} diff --git a/cmake.deps/cmake/BuildLibuv.cmake b/cmake.deps/cmake/BuildLibuv.cmake index ba5de38714..aee3b9a43f 100644 --- a/cmake.deps/cmake/BuildLibuv.cmake +++ b/cmake.deps/cmake/BuildLibuv.cmake @@ -1,77 +1,26 @@ -# BuildLibuv(TARGET targetname CONFIGURE_COMMAND ... BUILD_COMMAND ... INSTALL_COMMAND ...) -# Reusable function to build libuv, wraps ExternalProject_Add. -# Failing to pass a command argument will result in no command being run -function(BuildLibuv) - cmake_parse_arguments(_libuv - "BUILD_IN_SOURCE" - "TARGET" - "PATCH_COMMAND;CONFIGURE_COMMAND;BUILD_COMMAND;INSTALL_COMMAND" - ${ARGN}) - - if(NOT _libuv_CONFIGURE_COMMAND AND NOT _libuv_BUILD_COMMAND - AND NOT _libuv_INSTALL_COMMAND) - message(FATAL_ERROR "Must pass at least one of CONFIGURE_COMMAND, BUILD_COMMAND, INSTALL_COMMAND") - endif() - if(NOT _libuv_TARGET) - set(_libuv_TARGET "libuv") - endif() - - ExternalProject_Add(${_libuv_TARGET} - PREFIX ${DEPS_BUILD_DIR} - URL ${LIBUV_URL} - DOWNLOAD_DIR ${DEPS_DOWNLOAD_DIR}/libuv - DOWNLOAD_COMMAND ${CMAKE_COMMAND} - -DPREFIX=${DEPS_BUILD_DIR} - -DDOWNLOAD_DIR=${DEPS_DOWNLOAD_DIR}/libuv - -DURL=${LIBUV_URL} - -DEXPECTED_SHA256=${LIBUV_SHA256} - -DTARGET=${_libuv_TARGET} - -DUSE_EXISTING_SRC_DIR=${USE_EXISTING_SRC_DIR} - -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/DownloadAndExtractFile.cmake - PATCH_COMMAND "${_libuv_PATCH_COMMAND}" - BUILD_IN_SOURCE ${_libuv_BUILD_IN_SOURCE} - CONFIGURE_COMMAND "${_libuv_CONFIGURE_COMMAND}" - BUILD_COMMAND "${_libuv_BUILD_COMMAND}" - INSTALL_COMMAND "${_libuv_INSTALL_COMMAND}") -endfunction() - -set(UNIX_CFGCMD sh ${DEPS_BUILD_DIR}/src/libuv/autogen.sh && - ${DEPS_BUILD_DIR}/src/libuv/configure --with-pic --disable-shared - --prefix=${DEPS_INSTALL_DIR} --libdir=${DEPS_INSTALL_DIR}/lib - CC=${DEPS_C_COMPILER}) - -if(UNIX) - BuildLibuv( - CONFIGURE_COMMAND ${UNIX_CFGCMD} MAKE=${MAKE_PRG} - INSTALL_COMMAND ${MAKE_PRG} V=1 install) - -elseif(WIN32) - - set(UV_OUTPUT_DIR ${DEPS_BUILD_DIR}/src/libuv/${CMAKE_BUILD_TYPE}) - if(MSVC) - set(BUILD_SHARED ON) - elseif(MINGW) - set(BUILD_SHARED OFF) - else() - message(FATAL_ERROR "Trying to build libuv in an unsupported system ${CMAKE_SYSTEM_NAME}/${CMAKE_C_COMPILER_ID}") - endif() - BuildLibUv(BUILD_IN_SOURCE - CONFIGURE_COMMAND ${CMAKE_COMMAND} -E copy - ${CMAKE_CURRENT_SOURCE_DIR}/cmake/LibuvCMakeLists.txt - ${DEPS_BUILD_DIR}/src/libuv/CMakeLists.txt - COMMAND ${CMAKE_COMMAND} ${DEPS_BUILD_DIR}/src/libuv/CMakeLists.txt - -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} - -DCMAKE_GENERATOR=${CMAKE_GENERATOR} - -DCMAKE_GENERATOR_PLATFORM=${CMAKE_GENERATOR_PLATFORM} - -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} - -DBUILD_SHARED_LIBS=${BUILD_SHARED} - -DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR} - PATCH_COMMAND ${LIBUV_PATCH_COMMAND} - BUILD_COMMAND ${CMAKE_COMMAND} --build . --config ${CMAKE_BUILD_TYPE} - INSTALL_COMMAND ${CMAKE_COMMAND} --build . --target install --config ${CMAKE_BUILD_TYPE}) - -else() - message(FATAL_ERROR "Trying to build libuv in an unsupported system ${CMAKE_SYSTEM_NAME}/${CMAKE_C_COMPILER_ID}") -endif() +ExternalProject_Add(libuv + PREFIX ${DEPS_BUILD_DIR} + URL ${LIBUV_URL} + CMAKE_ARGS + -DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR} + -DCMAKE_INSTALL_LIBDIR=lib + -DBUILD_TESTING=OFF + -DCMAKE_POSITION_INDEPENDENT_CODE=ON + -DLIBUV_BUILD_SHARED=OFF + CMAKE_CACHE_ARGS + -DCMAKE_OSX_ARCHITECTURES:STRING=${CMAKE_OSX_ARCHITECTURES} + DOWNLOAD_DIR ${DEPS_DOWNLOAD_DIR}/libuv + DOWNLOAD_COMMAND ${CMAKE_COMMAND} + -DPREFIX=${DEPS_BUILD_DIR} + -DDOWNLOAD_DIR=${DEPS_DOWNLOAD_DIR}/libuv + -DURL=${LIBUV_URL} + -DEXPECTED_SHA256=${LIBUV_SHA256} + -DTARGET=libuv + -DUSE_EXISTING_SRC_DIR=${USE_EXISTING_SRC_DIR} + -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/DownloadAndExtractFile.cmake + PATCH_COMMAND + ${GIT_EXECUTABLE} -C ${DEPS_BUILD_DIR}/src/libuv init + COMMAND ${GIT_EXECUTABLE} -C ${DEPS_BUILD_DIR}/src/libuv apply --ignore-whitespace + ${CMAKE_CURRENT_SOURCE_DIR}/patches/libuv-disable-shared.patch) list(APPEND THIRD_PARTY_DEPS libuv) diff --git a/cmake.deps/cmake/BuildLibvterm.cmake b/cmake.deps/cmake/BuildLibvterm.cmake index 2c300dda7c..a905733abc 100644 --- a/cmake.deps/cmake/BuildLibvterm.cmake +++ b/cmake.deps/cmake/BuildLibvterm.cmake @@ -48,8 +48,13 @@ if(WIN32) -DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR} -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} -DCMAKE_GENERATOR_PLATFORM=${CMAKE_GENERATOR_PLATFORM} - "-DCMAKE_C_FLAGS:STRING=${CMAKE_C_COMPILER_ARG1} -fPIC" -DCMAKE_GENERATOR=${CMAKE_GENERATOR}) + + if(MSVC) + list(APPEND LIBVTERM_CONFIGURE_COMMAND "-DCMAKE_C_FLAGS:STRING=${CMAKE_C_COMPILER_ARG1}") + else() + list(APPEND LIBVTERM_CONFIGURE_COMMAND "-DCMAKE_C_FLAGS:STRING=${CMAKE_C_COMPILER_ARG1} -fPIC") + endif() set(LIBVTERM_BUILD_COMMAND ${CMAKE_COMMAND} --build . --config ${CMAKE_BUILD_TYPE}) set(LIBVTERM_INSTALL_COMMAND ${CMAKE_COMMAND} --build . --target install --config ${CMAKE_BUILD_TYPE}) else() diff --git a/cmake.deps/cmake/BuildLuarocks.cmake b/cmake.deps/cmake/BuildLuarocks.cmake index 6933d263f2..73f3331176 100644 --- a/cmake.deps/cmake/BuildLuarocks.cmake +++ b/cmake.deps/cmake/BuildLuarocks.cmake @@ -192,35 +192,21 @@ if(USE_BUNDLED_BUSTED) # luv set(LUV_DEPS luacheck) if(USE_BUNDLED_LUV) - list(APPEND LUV_DEPS luv-static lua-compat-5.3) - set(LUV_ARGS "CFLAGS=-O0 -g3 -fPIC") - if(USE_BUNDLED_LIBUV) - list(APPEND LUV_ARGS LIBUV_DIR=${HOSTDEPS_INSTALL_DIR}) - # workaround for bug introduced in - # https://github.com/luarocks/luarocks/commit/83126ba324846b754ffc5e0345341f01262b3f86 - if(MSVC) - list(APPEND LUV_ARGS LIBUV_LIBDIR=${HOSTDEPS_INSTALL_DIR}/lib) - endif() - endif() - SET(LUV_PRIVATE_ARGS LUA_COMPAT53_INCDIR=${DEPS_BUILD_DIR}/src/lua-compat-5.3/c-api) - add_custom_command(OUTPUT ${ROCKS_DIR}/luv - COMMAND ${LUAROCKS_BINARY} - ARGS make ${LUAROCKS_BUILDARGS} ${LUV_ARGS} ${LUV_PRIVATE_ARGS} - WORKING_DIRECTORY ${DEPS_BUILD_DIR}/src/luv - DEPENDS ${LUV_DEPS}) + set(NVIM_CLIENT_DEPS luacheck luv-static lua-compat-5.3) else() add_custom_command(OUTPUT ${ROCKS_DIR}/luv COMMAND ${LUAROCKS_BINARY} ARGS build luv ${LUV_VERSION} ${LUAROCKS_BUILDARGS} - DEPENDS ${LUV_DEPS}) + DEPENDS luacheck) + add_custom_target(luv DEPENDS ${ROCKS_DIR}/luv) + set(NVIM_CLIENT_DEPS luv) endif() - add_custom_target(luv DEPENDS ${ROCKS_DIR}/luv) # nvim-client: https://github.com/neovim/lua-client add_custom_command(OUTPUT ${ROCKS_DIR}/nvim-client COMMAND ${LUAROCKS_BINARY} ARGS build nvim-client 0.2.4-1 ${LUAROCKS_BUILDARGS} - DEPENDS luv) + DEPENDS ${NVIM_CLIENT_DEPS}) add_custom_target(nvim-client DEPENDS ${ROCKS_DIR}/nvim-client) list(APPEND THIRD_PARTY_DEPS busted luacheck nvim-client) diff --git a/cmake.deps/cmake/BuildLuv.cmake b/cmake.deps/cmake/BuildLuv.cmake index 6e9a333dc8..f960b24992 100644 --- a/cmake.deps/cmake/BuildLuv.cmake +++ b/cmake.deps/cmake/BuildLuv.cmake @@ -56,15 +56,10 @@ 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") -# Replace luv default rockspec with the alternate one under the "rockspecs" -# directory -set(LUV_PATCH_COMMAND - ${CMAKE_COMMAND} -E copy_directory ${LUV_SRC_DIR}/rockspecs ${LUV_SRC_DIR}) - set(LUV_CONFIGURE_COMMAND_COMMON ${CMAKE_COMMAND} ${LUV_SRC_DIR} -DCMAKE_GENERATOR=${CMAKE_GENERATOR} - -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} + ${BUILD_TYPE_STRING} -DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR} -DCMAKE_OSX_ARCHITECTURES=${CMAKE_OSX_ARCHITECTURES_ALT_SEP} -DLUA_BUILD_TYPE=System @@ -90,7 +85,8 @@ endif() if(USE_BUNDLED_LIBUV) set(LUV_CONFIGURE_COMMAND_COMMON ${LUV_CONFIGURE_COMMAND_COMMON} - -DCMAKE_PREFIX_PATH=${DEPS_INSTALL_DIR}) + -DCMAKE_PREFIX_PATH=${DEPS_INSTALL_DIR} + -DLIBUV_LIBRARIES=uv_a) endif() if(MSVC) @@ -127,6 +123,8 @@ BuildLuv(PATCH_COMMAND ${LUV_PATCH_COMMAND} list(APPEND THIRD_PARTY_DEPS luv-static) if(USE_BUNDLED_LUAJIT) add_dependencies(luv-static luajit) +elseif(USE_BUNDLED_LUA) + add_dependencies(luv-static lua) endif() if(USE_BUNDLED_LIBUV) add_dependencies(luv-static libuv) diff --git a/cmake.deps/cmake/BuildMsgpack.cmake b/cmake.deps/cmake/BuildMsgpack.cmake index 10bf1c8e37..ea3fa84d7b 100644 --- a/cmake.deps/cmake/BuildMsgpack.cmake +++ b/cmake.deps/cmake/BuildMsgpack.cmake @@ -36,7 +36,6 @@ set(MSGPACK_CONFIGURE_COMMAND ${CMAKE_COMMAND} ${DEPS_BUILD_DIR}/src/msgpack -DMSGPACK_BUILD_EXAMPLES=OFF -DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR} -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} - -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_OSX_ARCHITECTURES=${CMAKE_OSX_ARCHITECTURES_ALT_SEP} "-DCMAKE_C_FLAGS:STRING=${CMAKE_C_COMPILER_ARG1} -fPIC" -DCMAKE_GENERATOR=${CMAKE_GENERATOR}) @@ -52,8 +51,8 @@ if(MSVC) -DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR} -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} -DCMAKE_GENERATOR_PLATFORM=${CMAKE_GENERATOR_PLATFORM} + ${BUILD_TYPE_STRING} "-DCMAKE_C_FLAGS:STRING=${CMAKE_C_COMPILER_ARG1}" - -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} # Make sure we use the same generator, otherwise we may # accidentally end up using different MSVC runtimes -DCMAKE_GENERATOR=${CMAKE_GENERATOR}) diff --git a/cmake.deps/cmake/BuildTreesitter.cmake b/cmake.deps/cmake/BuildTreesitter.cmake index 01fdb837e2..6d98f6179c 100644 --- a/cmake.deps/cmake/BuildTreesitter.cmake +++ b/cmake.deps/cmake/BuildTreesitter.cmake @@ -43,7 +43,7 @@ if(MSVC) -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} -DCMAKE_GENERATOR=${CMAKE_GENERATOR} -DCMAKE_GENERATOR_PLATFORM=${CMAKE_GENERATOR_PLATFORM} - -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} + ${BUILD_TYPE_STRING} -DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR} BUILD_COMMAND ${CMAKE_COMMAND} --build . --config ${CMAKE_BUILD_TYPE} INSTALL_COMMAND ${CMAKE_COMMAND} --build . --target install --config ${CMAKE_BUILD_TYPE} diff --git a/cmake.deps/cmake/BuildTreesitterParsers.cmake b/cmake.deps/cmake/BuildTreesitterParsers.cmake index 11ffb792de..1ff86e89b9 100644 --- a/cmake.deps/cmake/BuildTreesitterParsers.cmake +++ b/cmake.deps/cmake/BuildTreesitterParsers.cmake @@ -17,7 +17,7 @@ CMAKE_ARGS -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} -DCMAKE_GENERATOR=${CMAKE_GENERATOR} -DCMAKE_GENERATOR_PLATFORM=${CMAKE_GENERATOR_PLATFORM} - -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} + ${BUILD_TYPE_STRING} -DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR} -DCMAKE_OSX_ARCHITECTURES=${CMAKE_OSX_ARCHITECTURES_ALT_SEP} # Pass toolchain diff --git a/cmake.deps/cmake/BuildUnibilium.cmake b/cmake.deps/cmake/BuildUnibilium.cmake index 2f940bdfd3..fd5d68f9a8 100644 --- a/cmake.deps/cmake/BuildUnibilium.cmake +++ b/cmake.deps/cmake/BuildUnibilium.cmake @@ -19,7 +19,7 @@ if(WIN32) # Pass toolchain -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN} -DCMAKE_GENERATOR_PLATFORM=${CMAKE_GENERATOR_PLATFORM} - -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} + ${BUILD_TYPE_STRING} -DCMAKE_GENERATOR=${CMAKE_GENERATOR} BUILD_COMMAND ${CMAKE_COMMAND} --build . --config ${CMAKE_BUILD_TYPE} INSTALL_COMMAND ${CMAKE_COMMAND} --build . --target install --config ${CMAKE_BUILD_TYPE}) diff --git a/cmake.deps/cmake/GettextCMakeLists.txt b/cmake.deps/cmake/GettextCMakeLists.txt index c3f78716d0..d9f251897d 100644 --- a/cmake.deps/cmake/GettextCMakeLists.txt +++ b/cmake.deps/cmake/GettextCMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 2.8.12) +cmake_minimum_required(VERSION 3.10) project(gettext C) # Adds PREFIX to each item in LIST diff --git a/cmake.deps/cmake/LibiconvCMakeLists.txt b/cmake.deps/cmake/LibiconvCMakeLists.txt index 8ad3cc9352..b4ba6b5d7e 100644 --- a/cmake.deps/cmake/LibiconvCMakeLists.txt +++ b/cmake.deps/cmake/LibiconvCMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 2.8.12) +cmake_minimum_required(VERSION 3.10) project(libiconv C) include_directories( diff --git a/cmake.deps/cmake/LibuvCMakeLists.txt b/cmake.deps/cmake/LibuvCMakeLists.txt deleted file mode 100644 index 0432319834..0000000000 --- a/cmake.deps/cmake/LibuvCMakeLists.txt +++ /dev/null @@ -1,33 +0,0 @@ -cmake_minimum_required(VERSION 2.8.12) -project(libuv LANGUAGES C) - -file(GLOB UV_SOURCES_COMMON src/*.c) -file(GLOB UV_SOURCES_WIN src/win/*.c) - -add_library(uv ${UV_SOURCES_COMMON} ${UV_SOURCES_WIN}) -target_compile_definitions(uv PRIVATE WIN32_LEAN_AND_MEAN "_WIN32_WINNT=0x0600") -target_link_libraries(uv iphlpapi psapi shell32 userenv ws2_32) -target_include_directories(uv PUBLIC ./include PRIVATE ./src) -if(BUILD_SHARED_LIBS) - set_target_properties(uv PROPERTIES DEFINE_SYMBOL BUILDING_UV_SHARED) -endif() - -install(FILES - include/uv.h - DESTINATION include) - -install(FILES - include/uv/errno.h - include/uv/threadpool.h - include/uv/tree.h - include/uv/version.h - include/uv/win.h - DESTINATION include/uv) - -include(GNUInstallDirs) -install(TARGETS uv - PUBLIC_HEADER - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) diff --git a/cmake.deps/cmake/LibvtermCMakeLists.txt b/cmake.deps/cmake/LibvtermCMakeLists.txt index 16c4d542c4..ff1d2d6b79 100644 --- a/cmake.deps/cmake/LibvtermCMakeLists.txt +++ b/cmake.deps/cmake/LibvtermCMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 2.8.12) +cmake_minimum_required(VERSION 3.10) project(libvterm LANGUAGES C) include(GNUInstallDirs) diff --git a/cmake.deps/cmake/TreesitterCMakeLists.txt b/cmake.deps/cmake/TreesitterCMakeLists.txt index 9e3ba3eeda..e20b47dd74 100644 --- a/cmake.deps/cmake/TreesitterCMakeLists.txt +++ b/cmake.deps/cmake/TreesitterCMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 2.8.12) +cmake_minimum_required(VERSION 3.10) project(tree-sitter LANGUAGES C) file(GLOB SRC_FILES ${PROJECT_SOURCE_DIR}/lib/src/*.c) diff --git a/cmake.deps/cmake/TreesitterParserCMakeLists.txt b/cmake.deps/cmake/TreesitterParserCMakeLists.txt index 2808a9ee14..54bc35fb8a 100644 --- a/cmake.deps/cmake/TreesitterParserCMakeLists.txt +++ b/cmake.deps/cmake/TreesitterParserCMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 2.8.12) +cmake_minimum_required(VERSION 3.10) # some parsers have c++ scanner, problem? project(parser C) # CXX diff --git a/cmake.deps/cmake/UnibiliumCMakeLists.txt b/cmake.deps/cmake/UnibiliumCMakeLists.txt index 08a8599352..9112b416fa 100644 --- a/cmake.deps/cmake/UnibiliumCMakeLists.txt +++ b/cmake.deps/cmake/UnibiliumCMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 2.8.12) +cmake_minimum_required(VERSION 3.10) project(unibilium LANGUAGES C) file(GLOB SRC_FILES ${PROJECT_SOURCE_DIR}/*.c) diff --git a/cmake.deps/cmake/libtermkeyCMakeLists.txt b/cmake.deps/cmake/libtermkeyCMakeLists.txt index af54c1daec..6c02b7549d 100644 --- a/cmake.deps/cmake/libtermkeyCMakeLists.txt +++ b/cmake.deps/cmake/libtermkeyCMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 2.8.12) +cmake_minimum_required(VERSION 3.10) project(libtermkey) add_definitions(-D _CRT_SECURE_NO_WARNINGS) diff --git a/cmake.deps/patches/libuv-disable-shared.patch b/cmake.deps/patches/libuv-disable-shared.patch new file mode 100644 index 0000000000..0e5722f4ba --- /dev/null +++ b/cmake.deps/patches/libuv-disable-shared.patch @@ -0,0 +1,117 @@ +From 326a1845f924432332071d03d156b7df4af7c46f Mon Sep 17 00:00:00 2001 +From: Tim Tavlintsev <ttavlintsev@enttec.com> +Date: Thu, 21 Jul 2022 16:42:21 +1000 +Subject: [PATCH] Add CMake option LIBUV_BUILD_SHARED to enable/disable shared + library build Fix #3637 + +--- + CMakeLists.txt | 66 +++++++++++++++++++++++++++++--------------------- + 1 file changed, 38 insertions(+), 28 deletions(-) + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 2c42c3ff..a8e19980 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -28,6 +28,8 @@ cmake_dependent_option(LIBUV_BUILD_BENCH + "Build the benchmarks when building unit tests and we are the root project" ON + "LIBUV_BUILD_TESTS" OFF) + ++option(LIBUV_BUILD_SHARED "Build shared lib" ON) ++ + # Qemu Build + option(QEMU "build for qemu" OFF) + if(QEMU) +@@ -390,25 +392,27 @@ if(APPLE OR CMAKE_SYSTEM_NAME MATCHES "DragonFly|FreeBSD|Linux|NetBSD|OpenBSD") + list(APPEND uv_test_libraries util) + endif() + +-add_library(uv SHARED ${uv_sources}) +-target_compile_definitions(uv +- INTERFACE +- USING_UV_SHARED=1 +- PRIVATE +- BUILDING_UV_SHARED=1 +- ${uv_defines}) +-target_compile_options(uv PRIVATE ${uv_cflags}) +-target_include_directories(uv +- PUBLIC +- $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include> +- $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}> +- PRIVATE +- $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/src>) +-if(CMAKE_SYSTEM_NAME STREQUAL "OS390") +- target_include_directories(uv PUBLIC $<BUILD_INTERFACE:${ZOSLIB_DIR}/include>) +- set_target_properties(uv PROPERTIES LINKER_LANGUAGE CXX) ++if(LIBUV_BUILD_SHARED) ++ add_library(uv SHARED ${uv_sources}) ++ target_compile_definitions(uv ++ INTERFACE ++ USING_UV_SHARED=1 ++ PRIVATE ++ BUILDING_UV_SHARED=1 ++ ${uv_defines}) ++ target_compile_options(uv PRIVATE ${uv_cflags}) ++ target_include_directories(uv ++ PUBLIC ++ $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include> ++ $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}> ++ PRIVATE ++ $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/src>) ++ if(CMAKE_SYSTEM_NAME STREQUAL "OS390") ++ target_include_directories(uv PUBLIC $<BUILD_INTERFACE:${ZOSLIB_DIR}/include>) ++ set_target_properties(uv PROPERTIES LINKER_LANGUAGE CXX) ++ endif() ++ target_link_libraries(uv ${uv_libraries}) + endif() +-target_link_libraries(uv ${uv_libraries}) + + add_library(uv_a STATIC ${uv_sources}) + target_compile_definitions(uv_a PRIVATE ${uv_defines}) +@@ -669,28 +673,34 @@ string(REPLACE ";" " " LIBS "${LIBS}") + file(STRINGS configure.ac configure_ac REGEX ^AC_INIT) + string(REGEX MATCH "([0-9]+)[.][0-9]+[.][0-9]+" PACKAGE_VERSION "${configure_ac}") + set(UV_VERSION_MAJOR "${CMAKE_MATCH_1}") +-# The version in the filename is mirroring the behaviour of autotools. +-set_target_properties(uv PROPERTIES +- VERSION ${UV_VERSION_MAJOR}.0.0 +- SOVERSION ${UV_VERSION_MAJOR}) ++ + set(includedir ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_INCLUDEDIR}) + set(libdir ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}) + set(prefix ${CMAKE_INSTALL_PREFIX}) +-configure_file(libuv.pc.in libuv.pc @ONLY) + configure_file(libuv-static.pc.in libuv-static.pc @ONLY) + + install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) + install(FILES LICENSE DESTINATION ${CMAKE_INSTALL_DOCDIR}) +-install(FILES ${PROJECT_BINARY_DIR}/libuv.pc ${PROJECT_BINARY_DIR}/libuv-static.pc ++install(FILES ${PROJECT_BINARY_DIR}/libuv-static.pc + DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) +-install(TARGETS uv EXPORT libuvConfig +- RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} +- LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} +- ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) + install(TARGETS uv_a EXPORT libuvConfig + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) + install(EXPORT libuvConfig DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/libuv) + ++if(LIBUV_BUILD_SHARED) ++ # The version in the filename is mirroring the behaviour of autotools. ++ set_target_properties(uv PROPERTIES ++ VERSION ${UV_VERSION_MAJOR}.0.0 ++ SOVERSION ${UV_VERSION_MAJOR}) ++ configure_file(libuv.pc.in libuv.pc @ONLY) ++ install(FILES ${PROJECT_BINARY_DIR}/libuv.pc ++ DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) ++ install(TARGETS uv EXPORT libuvConfig ++ RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} ++ LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ++ ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) ++endif() ++ + if(MSVC) + set(CMAKE_DEBUG_POSTFIX d) + endif() +-- +2.37.0 + diff --git a/cmake/FindLibUV.cmake b/cmake/FindLibUV.cmake index 63babfea67..d6c4e9cbef 100644 --- a/cmake/FindLibUV.cmake +++ b/cmake/FindLibUV.cmake @@ -13,7 +13,7 @@ endif() find_path(LIBUV_INCLUDE_DIR uv.h HINTS ${PC_LIBUV_INCLUDEDIR} ${PC_LIBUV_INCLUDE_DIRS}) -list(APPEND LIBUV_NAMES uv) +list(APPEND LIBUV_NAMES uv_a uv) find_library(LIBUV_LIBRARY NAMES ${LIBUV_NAMES} HINTS ${PC_LIBUV_LIBDIR} ${PC_LIBUV_LIBRARY_DIRS}) diff --git a/runtime/autoload/dist/ft.vim b/runtime/autoload/dist/ft.vim index a2f485dd67..77140d62b1 100644 --- a/runtime/autoload/dist/ft.vim +++ b/runtime/autoload/dist/ft.vim @@ -348,7 +348,7 @@ func dist#ft#FTidl() setf idl endfunc -" Distinguish between "default" and Cproto prototype file. */ +" 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 @@ -358,7 +358,14 @@ func dist#ft#ProtoCheck(default) if getline(2) =~ '.;$' setf cpp else - exe 'setf ' . a:default + " 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 diff --git a/runtime/autoload/python.vim b/runtime/autoload/python.vim index 7e7bca6fb6..1eaad09ef5 100644 --- a/runtime/autoload/python.vim +++ b/runtime/autoload/python.vim @@ -3,13 +3,35 @@ let s:keepcpo= &cpo set cpo&vim +" need to inspect some old g:pyindent_* variables to be backward compatible +let g:python_indent = extend(get(g:, 'python_indent', {}), #{ + \ closed_paren_align_last_line: v:true, + \ open_paren: get(g:, 'pyindent_open_paren', 'shiftwidth() * 2'), + \ nested_paren: get(g:, 'pyindent_nested_paren', 'shiftwidth()'), + \ continue: get(g:, 'pyindent_continue', 'shiftwidth() * 2'), + "\ searchpair() can be slow, limit the time to 150 msec or what is put in + "\ g:python_indent.searchpair_timeout + \ searchpair_timeout: get(g:, 'pyindent_searchpair_timeout', 150), + "\ Identing inside parentheses can be very slow, regardless of the searchpair() + "\ timeout, so let the user disable this feature if he doesn't need it + \ disable_parentheses_indenting: get(g:, 'pyindent_disable_parentheses_indenting', v:false), + \ }, 'keep') + +let s:maxoff = 50 " maximum number of lines to look backwards for () + +function s:SearchBracket(fromlnum, flags) + return searchpairpos('[[({]', '', '[])}]', a:flags, + \ {-> synstack('.', col('.')) + \ ->map({_, id -> id->synIDattr('name')}) + \ ->match('\%(Comment\|Todo\|String\)$') >= 0}, + \ [0, a:fromlnum - s:maxoff]->max(), g:python_indent.searchpair_timeout) +endfunction + " See if the specified line is already user-dedented from the expected value. function s:Dedented(lnum, expected) return indent(a:lnum) <= a:expected - shiftwidth() endfunction -let s:maxoff = 50 " maximum number of lines to look backwards for () - " Some other filetypes which embed Python have slightly different indent " rules (e.g. bitbake). Those filetypes can pass an extra funcref to this " function which is evaluated below. @@ -22,7 +44,7 @@ function python#GetIndent(lnum, ...) if a:lnum > 1 && getline(a:lnum - 2) =~ '\\$' return indent(a:lnum - 1) endif - return indent(a:lnum - 1) + (exists("g:pyindent_continue") ? eval(g:pyindent_continue) : (shiftwidth() * 2)) + return indent(a:lnum - 1) + get(g:, 'pyindent_continue', g:python_indent.continue)->eval() endif " If the start of the line is in a string don't change the indent. @@ -39,30 +61,34 @@ function python#GetIndent(lnum, ...) return 0 endif - call cursor(plnum, 1) - - " Identing inside parentheses can be very slow, regardless of the searchpair() - " timeout, so let the user disable this feature if he doesn't need it - let disable_parentheses_indenting = get(g:, "pyindent_disable_parentheses_indenting", 0) - - if disable_parentheses_indenting == 1 + if g:python_indent.disable_parentheses_indenting == 1 let plindent = indent(plnum) let plnumstart = plnum else - " searchpair() can be slow sometimes, limit the time to 150 msec or what is - " put in g:pyindent_searchpair_timeout - let searchpair_stopline = 0 - let searchpair_timeout = get(g:, 'pyindent_searchpair_timeout', 150) + " Indent inside parens. + " Align with the open paren unless it is at the end of the line. + " E.g. + " open_paren_not_at_EOL(100, + " (200, + " 300), + " 400) + " open_paren_at_EOL( + " 100, 200, 300, 400) + call cursor(a:lnum, 1) + let [parlnum, parcol] = s:SearchBracket(a:lnum, 'nbW') + if parlnum > 0 + if parcol != col([parlnum, '$']) - 1 + return parcol + elseif getline(a:lnum) =~ '^\s*[])}]' && !g:python_indent.closed_paren_align_last_line + return indent(parlnum) + endif + endif + + call cursor(plnum, 1) " If the previous line is inside parenthesis, use the indent of the starting " line. - " Trick: use the non-existing "dummy" variable to break out of the loop when - " going too far back. - let parlnum = searchpair('(\|{\|\[', '', ')\|}\|\]', 'nbW', - \ "line('.') < " . (plnum - s:maxoff) . " ? dummy :" - \ . " synIDattr(synID(line('.'), col('.'), 1), 'name')" - \ . " =~ '\\(Comment\\|Todo\\|String\\)$'", - \ searchpair_stopline, searchpair_timeout) + let [parlnum, _] = s:SearchBracket(plnum, 'nbW') if parlnum > 0 if a:0 > 0 && ExtraFunc(parlnum) " We may have found the opening brace of a bitbake Python task, e.g. 'python do_task {' @@ -85,11 +111,7 @@ function python#GetIndent(lnum, ...) " + b " + c) call cursor(a:lnum, 1) - let p = searchpair('(\|{\|\[', '', ')\|}\|\]', 'bW', - \ "line('.') < " . (a:lnum - s:maxoff) . " ? dummy :" - \ . " synIDattr(synID(line('.'), col('.'), 1), 'name')" - \ . " =~ '\\(Comment\\|Todo\\|String\\)$'", - \ searchpair_stopline, searchpair_timeout) + let [p, _] = s:SearchBracket(a:lnum, 'bW') if p > 0 if a:0 > 0 && ExtraFunc(p) " Currently only used by bitbake @@ -109,15 +131,13 @@ function python#GetIndent(lnum, ...) else if p == plnum " When the start is inside parenthesis, only indent one 'shiftwidth'. - let pp = searchpair('(\|{\|\[', '', ')\|}\|\]', 'bW', - \ "line('.') < " . (a:lnum - s:maxoff) . " ? dummy :" - \ . " synIDattr(synID(line('.'), col('.'), 1), 'name')" - \ . " =~ '\\(Comment\\|Todo\\|String\\)$'", - \ searchpair_stopline, searchpair_timeout) + let [pp, _] = s:SearchBracket(a:lnum, 'bW') if pp > 0 - return indent(plnum) + (exists("g:pyindent_nested_paren") ? eval(g:pyindent_nested_paren) : shiftwidth()) + return indent(plnum) + \ + get(g:, 'pyindent_nested_paren', g:python_indent.nested_paren)->eval() endif - return indent(plnum) + (exists("g:pyindent_open_paren") ? eval(g:pyindent_open_paren) : (shiftwidth() * 2)) + return indent(plnum) + \ + get(g:, 'pyindent_open_paren', g:python_indent.open_paren)->eval() endif if plnumstart == p return indent(plnum) @@ -136,12 +156,16 @@ function python#GetIndent(lnum, ...) " If the last character in the line is a comment, do a binary search for " the start of the comment. synID() is slow, a linear search would take " too long on a long line. - if synIDattr(synID(plnum, pline_len, 1), "name") =~ "\\(Comment\\|Todo\\)$" + if synstack(plnum, pline_len) + \ ->map({_, id -> id->synIDattr('name')}) + \ ->match('\%(Comment\|Todo\)$') >= 0 let min = 1 let max = pline_len while min < max let col = (min + max) / 2 - if synIDattr(synID(plnum, col, 1), "name") =~ "\\(Comment\\|Todo\\)$" + if synstack(plnum, col) + \ ->map({_, id -> id->synIDattr('name')}) + \ ->match('\%(Comment\|Todo\)$') >= 0 let max = col else let min = col + 1 diff --git a/runtime/colors/blue.vim b/runtime/colors/blue.vim index 20f87bede8..2fbce09b67 100644 --- a/runtime/colors/blue.vim +++ b/runtime/colors/blue.vim @@ -4,7 +4,7 @@ " Maintainer: Original maintainer Steven Vertigan <steven@vertigan.wattle.id.au> " Website: https://github.com/vim/colorschemes " License: Same as Vim -" Last Updated: 2022-07-26 15:49:58 +" Last Updated: Tue 23 Aug 2022 16:50:34 MSK " Generated by Colortemplate v2.2.0 @@ -13,12 +13,12 @@ set background=dark hi clear let g:colors_name = 'blue' -let s:t_Co = exists('&t_Co') && !empty(&t_Co) && &t_Co >= 0 ? &t_Co : -1 +let s:t_Co = exists('&t_Co') && !has('gui_running') ? (&t_Co ? &t_Co : 0) : -1 if (has('termguicolors') && &termguicolors) || has('gui_running') - let g:terminal_ansi_colors = ['#000000', '#870000', '#006400', '#878700', '#000087', '#870087', '#008787', '#bcbcbc', '#878787', '#d70000', '#00ff00', '#ffdf00', '#5fafff', '#d787d7', '#5fffff', '#ffffff'] + let g:terminal_ansi_colors = ['#000000', '#cd0000', '#00cd00', '#cdcd00', '#0000ee', '#cd00cd', '#00cdcd', '#e5e5e5', '#7f7f7f', '#ff0000', '#00ff00', '#ffff00', '#5c5cff', '#ff00ff', '#00ffff', '#ffffff'] endif -hi Normal guifg=#ffdf00 guibg=#000087 gui=NONE cterm=NONE +hi Normal guifg=#ffd700 guibg=#000087 gui=NONE cterm=NONE hi CursorLine guifg=NONE guibg=#005faf gui=NONE cterm=NONE hi Pmenu guifg=#ffffff guibg=#008787 gui=NONE cterm=NONE hi PmenuSel guifg=#008787 guibg=#ffffff gui=NONE cterm=NONE @@ -27,9 +27,9 @@ hi ColorColumn guifg=NONE guibg=#870087 gui=NONE cterm=NONE hi Conceal guifg=NONE guibg=NONE gui=NONE ctermfg=NONE ctermbg=NONE cterm=NONE hi Cursor guifg=#000000 guibg=#00ff00 gui=NONE cterm=NONE hi CursorColumn guifg=NONE guibg=#005faf gui=NONE cterm=NONE -hi CursorIM guifg=#000000 guibg=#ffdf00 gui=NONE cterm=NONE -hi CursorLineNr guifg=#ffdf00 guibg=#005faf gui=bold cterm=NONE -hi EndOfBuffer guifg=#ffdf00 guibg=#000087 gui=NONE cterm=NONE +hi CursorIM guifg=#000000 guibg=#ffd700 gui=NONE cterm=NONE +hi CursorLineNr guifg=#ffd700 guibg=#005faf gui=bold cterm=NONE +hi EndOfBuffer guifg=#ffd700 guibg=#000087 gui=NONE cterm=NONE hi Error guifg=#ff7f50 guibg=#000087 gui=reverse cterm=reverse hi ErrorMsg guifg=#ffffff guibg=#d70000 gui=NONE cterm=NONE hi FoldColumn guifg=#008787 guibg=NONE gui=NONE cterm=NONE @@ -43,7 +43,7 @@ hi NonText guifg=#d787d7 guibg=NONE gui=NONE cterm=NONE hi PmenuSbar guifg=NONE guibg=NONE gui=NONE ctermfg=NONE ctermbg=NONE cterm=NONE hi PmenuThumb guifg=NONE guibg=#ffffff gui=NONE cterm=NONE hi Question guifg=#00ff00 guibg=NONE gui=NONE cterm=NONE -hi Search guifg=#ffdf00 guibg=#000000 gui=reverse cterm=reverse +hi Search guifg=#ffd700 guibg=#000000 gui=reverse cterm=reverse hi SignColumn guifg=#008787 guibg=NONE gui=NONE cterm=NONE hi SpecialKey guifg=#5fffff guibg=NONE gui=NONE cterm=NONE hi SpellBad guifg=#d70000 guibg=NONE guisp=#d70000 gui=undercurl cterm=underline @@ -58,7 +58,7 @@ hi VertSplit guifg=#008787 guibg=NONE gui=NONE cterm=NONE hi Visual guifg=#ffffff guibg=#008787 gui=NONE cterm=NONE hi VisualNOS guifg=#008787 guibg=#ffffff gui=NONE cterm=NONE hi WarningMsg guifg=#d70000 guibg=NONE gui=NONE cterm=NONE -hi WildMenu guifg=#000087 guibg=#ffdf00 gui=NONE cterm=NONE +hi WildMenu guifg=#000087 guibg=#ffd700 gui=NONE cterm=NONE hi debugBreakpoint guifg=#00ff00 guibg=#000087 gui=reverse cterm=reverse hi debugPC guifg=#5fffff guibg=#000087 gui=reverse cterm=reverse hi Directory guifg=#5fffff guibg=NONE gui=NONE cterm=NONE @@ -73,7 +73,7 @@ hi Statement guifg=#ffffff guibg=NONE gui=NONE cterm=NONE hi Todo guifg=NONE guibg=NONE gui=reverse ctermfg=NONE ctermbg=NONE cterm=reverse hi Type guifg=#ffa500 guibg=NONE gui=bold cterm=NONE hi Underlined guifg=NONE guibg=NONE gui=underline ctermfg=NONE ctermbg=NONE cterm=underline -hi Label guifg=#ffdf00 guibg=NONE gui=NONE cterm=NONE +hi Label guifg=#ffd700 guibg=NONE gui=NONE cterm=NONE hi! link Terminal Normal hi! link Debug Special hi! link diffAdded String @@ -526,6 +526,22 @@ if s:t_Co >= 0 endif " Background: dark +" Color: x_black #000000 16 black +" Color: x_darkred #cd0000 160 darkred +" Color: x_darkgreen #00cd00 40 darkgreen +" Color: x_darkyellow #cdcd00 184 darkyellow +" Color: x_darkblue #0000ee 21 darkblue +" Color: x_darkmagenta #cd00cd 164 darkmagenta +" Color: x_darkcyan #00cdcd 44 darkcyan +" Color: x_gray #e5e5e5 254 gray +" Color: x_darkgray #7f7f7f 244 darkgray +" Color: x_red #ff0000 196 red +" Color: x_green #00ff00 46 green +" Color: x_yellow #ffff00 226 yellow +" Color: x_blue #5c5cff 63 blue +" Color: x_magenta #ff00ff 201 magenta +" Color: x_cyan #00ffff 51 cyan +" Color: x_white #ffffff 231 white " Color: black #000000 16 black " Color: darkred #870000 88 darkred " Color: darkyellow #878700 100 darkyellow @@ -537,7 +553,7 @@ endif " Color: darkgray #878787 102 darkgray " Color: red #d70000 160 red " Color: green #00ff00 46 green -" Color: yellow #ffdf00 220 yellow +" Color: yellow #ffd700 220 yellow " Color: blue #005faf 25 blue " Color: magenta #d787d7 176 magenta " Color: cyan #5fffff 87 cyan @@ -549,8 +565,8 @@ endif " Color: coral #ff7f50 209 red " Color: olivedrab #6b8e23 64 green " Color: slateblue #6a5acd 62 darkmagenta -" Term colors: black darkred darkgreen darkyellow darkblue darkmagenta darkcyan gray -" Term colors: darkgray red green yellow xtermblue magenta cyan white +" Term colors: x_black x_darkred x_darkgreen x_darkyellow x_darkblue x_darkmagenta x_darkcyan x_gray +" Term colors: x_darkgray x_red x_green x_yellow x_blue x_magenta x_cyan x_white " Color: bgDiffA #5F875F 65 darkgreen " Color: bgDiffC #5F87AF 67 blue " Color: bgDiffD #AF5FAF 133 magenta diff --git a/runtime/colors/darkblue.vim b/runtime/colors/darkblue.vim index 3d24c9235a..9e96a4638c 100644 --- a/runtime/colors/darkblue.vim +++ b/runtime/colors/darkblue.vim @@ -4,7 +4,7 @@ " Maintainer: Original author Bohdan Vlasyuk <bohdan@vstu.edu.ua> " Website: https://github.com/vim/colorschemes " License: Same as Vim -" Last Updated: 2022-07-26 15:49:59 +" Last Updated: Tue 23 Aug 2022 16:50:35 MSK " Generated by Colortemplate v2.2.0 @@ -13,7 +13,7 @@ set background=dark hi clear let g:colors_name = 'darkblue' -let s:t_Co = exists('&t_Co') && !empty(&t_Co) && &t_Co >= 0 ? &t_Co : -1 +let s:t_Co = exists('&t_Co') && !has('gui_running') ? (&t_Co ? &t_Co : 0) : -1 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'] diff --git a/runtime/colors/delek.vim b/runtime/colors/delek.vim index c15d96ef33..e21adf2795 100644 --- a/runtime/colors/delek.vim +++ b/runtime/colors/delek.vim @@ -4,7 +4,7 @@ " Maintainer: Original maintainer David Schweikert <david@schweikert.ch> " Website: https://github.com/vim/colorschemes " License: Same as Vim -" Last Updated: 2022-07-26 15:50:00 +" Last Updated: Tue 23 Aug 2022 16:50:36 MSK " Generated by Colortemplate v2.2.0 @@ -13,7 +13,7 @@ set background=light hi clear let g:colors_name = 'delek' -let s:t_Co = exists('&t_Co') && !empty(&t_Co) && &t_Co >= 0 ? &t_Co : -1 +let s:t_Co = exists('&t_Co') && !has('gui_running') ? (&t_Co ? &t_Co : 0) : -1 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'] diff --git a/runtime/colors/desert.vim b/runtime/colors/desert.vim index 93bc73edec..510c2ee263 100644 --- a/runtime/colors/desert.vim +++ b/runtime/colors/desert.vim @@ -4,7 +4,7 @@ " Maintainer: Original maintainer Hans Fugal <hans@fugal.net> " Website: https://github.com/vim/colorschemes " License: Same as Vim -" Last Updated: 2022-07-26 15:50:01 +" Last Updated: Tue 23 Aug 2022 16:50:37 MSK " Generated by Colortemplate v2.2.0 @@ -13,7 +13,7 @@ set background=dark hi clear let g:colors_name = 'desert' -let s:t_Co = exists('&t_Co') && !empty(&t_Co) && &t_Co >= 0 ? &t_Co : -1 +let s:t_Co = exists('&t_Co') && !has('gui_running') ? (&t_Co ? &t_Co : 0) : -1 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'] diff --git a/runtime/colors/elflord.vim b/runtime/colors/elflord.vim index f6e66ab06e..dc0327b5fe 100644 --- a/runtime/colors/elflord.vim +++ b/runtime/colors/elflord.vim @@ -3,7 +3,7 @@ " Maintainer: original maintainer Ron Aaron <ron@ronware.org> " Website: https://www.github.com/vim/colorschemes " License: Same as Vim -" Last Updated: 2022-07-26 15:50:02 +" Last Updated: Tue 23 Aug 2022 16:50:37 MSK " Generated by Colortemplate v2.2.0 @@ -12,7 +12,7 @@ set background=dark hi clear let g:colors_name = 'elflord' -let s:t_Co = exists('&t_Co') && !empty(&t_Co) && &t_Co >= 0 ? &t_Co : -1 +let s:t_Co = exists('&t_Co') && !has('gui_running') ? (&t_Co ? &t_Co : 0) : -1 hi! link Terminal Normal hi! link Boolean Constant diff --git a/runtime/colors/evening.vim b/runtime/colors/evening.vim index bc39e87b9a..978444d79f 100644 --- a/runtime/colors/evening.vim +++ b/runtime/colors/evening.vim @@ -4,7 +4,7 @@ " Maintainer: Original maintainer Steven Vertigan <steven@vertigan.wattle.id.au> " Website: https://github.com/vim/colorschemes " License: Same as Vim -" Last Updated: 2022-07-26 15:50:03 +" Last Updated: Tue 23 Aug 2022 16:50:38 MSK " Generated by Colortemplate v2.2.0 @@ -13,7 +13,7 @@ set background=dark hi clear let g:colors_name = 'evening' -let s:t_Co = exists('&t_Co') && !empty(&t_Co) && &t_Co >= 0 ? &t_Co : -1 +let s:t_Co = exists('&t_Co') && !has('gui_running') ? (&t_Co ? &t_Co : 0) : -1 if (has('termguicolors') && &termguicolors) || has('gui_running') let g:terminal_ansi_colors = ['#000000', '#ffa500', '#2e8b57', '#ffff00', '#006faf', '#8b008b', '#008b8b', '#bebebe', '#4d4d4d', '#ff5f5f', '#00ff00', '#ffff60', '#0087ff', '#ff80ff', '#00ffff', '#ffffff'] diff --git a/runtime/colors/habamax.vim b/runtime/colors/habamax.vim index 469d1846d6..b356260102 100644 --- a/runtime/colors/habamax.vim +++ b/runtime/colors/habamax.vim @@ -4,7 +4,7 @@ " Maintainer: Maxim Kim <habamax@gmail.com> " Website: https://github.com/vim/colorschemes " License: Same as Vim -" Last Updated: 2022-07-26 15:50:04 +" Last Updated: Tue 23 Aug 2022 16:50:38 MSK " Generated by Colortemplate v2.2.0 @@ -13,10 +13,10 @@ set background=dark hi clear let g:colors_name = 'habamax' -let s:t_Co = exists('&t_Co') && !empty(&t_Co) && &t_Co >= 0 ? &t_Co : -1 +let s:t_Co = exists('&t_Co') && !has('gui_running') ? (&t_Co ? &t_Co : 0) : -1 if (has('termguicolors') && &termguicolors) || has('gui_running') - let g:terminal_ansi_colors = ['#1c1c1c', '#d75f5f', '#87af87', '#afaf87', '#5f87af', '#af87af', '#5f8787', '#9e9e9e', '#767676', '#df875f', '#afd7af', '#dfdf87', '#87afd7', '#dfafdf', '#87afaf', '#bcbcbc'] + let g:terminal_ansi_colors = ['#1c1c1c', '#d75f5f', '#87af87', '#afaf87', '#5f87af', '#af87af', '#5f8787', '#9e9e9e', '#767676', '#d7875f', '#afd7af', '#d7d787', '#87afd7', '#d7afd7', '#87afaf', '#bcbcbc'] endif hi! link Terminal Normal hi! link StatuslineTerm Statusline @@ -58,14 +58,14 @@ hi! link elixirInclude Statement hi! link elixirAtom PreProc hi! link elixirDocTest String hi ALEErrorSign guifg=#d75f5f guibg=NONE gui=NONE cterm=NONE -hi ALEInfoSign guifg=#dfdf87 guibg=NONE gui=NONE cterm=NONE +hi ALEInfoSign guifg=#d7d787 guibg=NONE gui=NONE cterm=NONE hi ALEWarningSign guifg=#af87af guibg=NONE gui=NONE cterm=NONE hi ALEError guifg=#1c1c1c guibg=#d75f5f gui=NONE cterm=NONE hi ALEVirtualTextError guifg=#1c1c1c guibg=#d75f5f gui=NONE cterm=NONE hi ALEWarning guifg=#1c1c1c guibg=#af87af gui=NONE cterm=NONE hi ALEVirtualTextWarning guifg=#1c1c1c guibg=#af87af gui=NONE cterm=NONE -hi ALEInfo guifg=#dfdf87 guibg=NONE gui=NONE cterm=NONE -hi ALEVirtualTextInfo guifg=#dfdf87 guibg=NONE gui=NONE cterm=NONE +hi ALEInfo guifg=#d7d787 guibg=NONE gui=NONE cterm=NONE +hi ALEVirtualTextInfo guifg=#d7d787 guibg=NONE gui=NONE cterm=NONE hi Normal guifg=#bcbcbc guibg=#1c1c1c gui=NONE cterm=NONE hi Statusline guifg=#1c1c1c guibg=#9e9e9e gui=NONE cterm=NONE hi StatuslineNC guifg=#1c1c1c guibg=#767676 gui=NONE cterm=NONE @@ -93,18 +93,18 @@ hi PmenuSel guifg=#1c1c1c guibg=#afaf87 gui=NONE cterm=NONE hi SignColumn guifg=NONE guibg=NONE gui=NONE ctermfg=NONE ctermbg=NONE cterm=NONE hi Error guifg=#d75f5f guibg=#1c1c1c gui=reverse cterm=reverse hi ErrorMsg guifg=#d75f5f guibg=#1c1c1c gui=reverse cterm=reverse -hi ModeMsg guifg=#1c1c1c guibg=#dfdf87 gui=NONE cterm=NONE +hi ModeMsg guifg=#1c1c1c guibg=#d7d787 gui=NONE cterm=NONE hi MoreMsg guifg=#87af87 guibg=NONE gui=NONE cterm=NONE hi Question guifg=#afaf87 guibg=NONE gui=NONE cterm=NONE -hi WarningMsg guifg=#df875f guibg=NONE gui=NONE cterm=NONE -hi Todo guifg=#dfdf87 guibg=#1c1c1c gui=reverse cterm=reverse +hi WarningMsg guifg=#d7875f guibg=NONE gui=NONE cterm=NONE +hi Todo guifg=#d7d787 guibg=#1c1c1c gui=reverse cterm=reverse hi MatchParen guifg=#5f8787 guibg=#1c1c1c gui=reverse cterm=reverse hi Search guifg=#1c1c1c guibg=#87af87 gui=NONE cterm=NONE hi IncSearch guifg=#1c1c1c guibg=#ffaf5f gui=NONE cterm=NONE hi CurSearch guifg=#1c1c1c guibg=#afaf87 gui=NONE cterm=NONE -hi WildMenu guifg=#1c1c1c guibg=#dfdf87 gui=NONE cterm=NONE +hi WildMenu guifg=#1c1c1c guibg=#d7d787 gui=NONE cterm=NONE hi debugPC guifg=#1c1c1c guibg=#5f87af gui=NONE cterm=NONE -hi debugBreakpoint guifg=#1c1c1c guibg=#df875f gui=NONE cterm=NONE +hi debugBreakpoint guifg=#1c1c1c guibg=#d7875f gui=NONE cterm=NONE hi Cursor guifg=#1c1c1c guibg=#ffaf5f gui=NONE cterm=NONE hi lCursor guifg=#1c1c1c guibg=#5fff00 gui=NONE cterm=NONE hi CursorLine guifg=NONE guibg=#303030 gui=NONE cterm=NONE @@ -114,9 +114,9 @@ hi ColorColumn guifg=NONE guibg=#262626 gui=NONE cterm=NONE hi SpellBad guifg=NONE guibg=NONE guisp=#d75f5f gui=undercurl ctermfg=NONE ctermbg=NONE cterm=underline hi SpellCap guifg=NONE guibg=NONE guisp=#5f87af gui=undercurl ctermfg=NONE ctermbg=NONE cterm=underline hi SpellLocal guifg=NONE guibg=NONE guisp=#87af87 gui=undercurl ctermfg=NONE ctermbg=NONE cterm=underline -hi SpellRare guifg=NONE guibg=NONE guisp=#dfafdf gui=undercurl ctermfg=NONE ctermbg=NONE cterm=underline +hi SpellRare guifg=NONE guibg=NONE guisp=#d7afd7 gui=undercurl ctermfg=NONE ctermbg=NONE cterm=underline hi Comment guifg=#767676 guibg=NONE gui=NONE cterm=NONE -hi Constant guifg=#df875f guibg=NONE gui=NONE cterm=NONE +hi Constant guifg=#d7875f guibg=NONE gui=NONE cterm=NONE hi String guifg=#87af87 guibg=NONE gui=NONE cterm=NONE hi Character guifg=#afd7af guibg=NONE gui=NONE cterm=NONE hi Identifier guifg=#87afaf guibg=NONE gui=NONE cterm=NONE @@ -125,7 +125,7 @@ hi PreProc guifg=#afaf87 guibg=NONE gui=NONE cterm=NONE hi Type guifg=#87afd7 guibg=NONE gui=NONE cterm=NONE hi Special guifg=#5f8787 guibg=NONE gui=NONE cterm=NONE hi Underlined guifg=NONE guibg=NONE gui=underline ctermfg=NONE ctermbg=NONE cterm=underline -hi Title guifg=#dfdf87 guibg=NONE gui=bold cterm=bold +hi Title guifg=#d7d787 guibg=NONE gui=bold cterm=bold hi Directory guifg=#87afaf guibg=NONE gui=bold cterm=bold hi Conceal guifg=#767676 guibg=NONE gui=NONE cterm=NONE hi Ignore guifg=NONE guibg=NONE gui=NONE ctermfg=NONE ctermbg=NONE cterm=NONE @@ -135,7 +135,7 @@ hi DiffDelete guifg=#af875f guibg=NONE gui=NONE cterm=NONE hi diffAdded guifg=#87af87 guibg=NONE gui=NONE cterm=NONE hi diffRemoved guifg=#d75f5f guibg=NONE gui=NONE cterm=NONE hi diffSubname guifg=#af87af guibg=NONE gui=NONE cterm=NONE -hi DiffText guifg=#000000 guibg=#dfdfdf gui=NONE cterm=NONE +hi DiffText guifg=#000000 guibg=#d7d7d7 gui=NONE cterm=NONE hi DiffChange guifg=#000000 guibg=#afafaf gui=NONE cterm=NONE if s:t_Co >= 256 @@ -254,7 +254,7 @@ if s:t_Co >= 256 hi diffAdded ctermfg=108 ctermbg=NONE cterm=NONE hi diffRemoved ctermfg=167 ctermbg=NONE cterm=NONE hi diffSubname ctermfg=139 ctermbg=NONE cterm=NONE - hi DiffText ctermfg=16 ctermbg=254 cterm=NONE + hi DiffText ctermfg=16 ctermbg=188 cterm=NONE hi DiffChange ctermfg=16 ctermbg=145 cterm=NONE unlet s:t_Co finish @@ -489,15 +489,15 @@ endif " Color: color00 #1C1C1C 234 black " Color: color08 #767676 243 darkgray " Color: color01 #D75F5F 167 darkred -" Color: color09 #DF875F 173 red +" Color: color09 #D7875F 173 red " Color: color02 #87AF87 108 darkgreen " Color: color10 #AFD7AF 151 green " Color: color03 #AFAF87 144 darkyellow -" Color: color11 #DFDF87 186 yellow +" Color: color11 #D7D787 186 yellow " Color: color04 #5F87AF 67 blue " Color: color12 #87AFD7 110 blue " Color: color05 #AF87AF 139 darkmagenta -" Color: color13 #DFAFDF 182 magenta +" Color: color13 #D7AFD7 182 magenta " Color: color06 #5F8787 66 darkcyan " Color: color14 #87AFAF 109 cyan " Color: color07 #9E9E9E 247 gray @@ -506,12 +506,12 @@ endif " Color: colorB #262626 235 darkgrey " Color: colorNonT #585858 240 darkgrey " Color: colorC #FFAF5F 215 red -" Color: colorlC #5FFF00 ~ +" Color: colorlC #5FFF00 82 green " Color: colorV #1F3F5F 109 cyan " Color: diffAdd #87AF87 108 darkgreen " Color: diffDelete #af875f 137 darkyellow " Color: diffChange #AFAFAF 145 darkgray -" Color: diffText #DFDFDF 254 lightgrey +" Color: diffText #D7D7D7 188 lightgrey " Color: black #000000 16 black " Color: white #FFFFFF 231 white " Term colors: color00 color01 color02 color03 color04 color05 color06 color07 diff --git a/runtime/colors/industry.vim b/runtime/colors/industry.vim index d6678b2bb2..c627d8c826 100644 --- a/runtime/colors/industry.vim +++ b/runtime/colors/industry.vim @@ -4,7 +4,7 @@ " Maintainer: Original maintainer Shian Lee. " Website: https://github.com/vim/colorschemes " License: Same as Vim -" Last Updated: 2022-07-26 15:50:05 +" Last Updated: Tue 23 Aug 2022 16:50:39 MSK " Generated by Colortemplate v2.2.0 @@ -13,7 +13,7 @@ set background=dark hi clear let g:colors_name = 'industry' -let s:t_Co = exists('&t_Co') && !empty(&t_Co) && &t_Co >= 0 ? &t_Co : -1 +let s:t_Co = exists('&t_Co') && !has('gui_running') ? (&t_Co ? &t_Co : 0) : -1 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'] diff --git a/runtime/colors/koehler.vim b/runtime/colors/koehler.vim index 87f1893ad7..cc4eff6cdf 100644 --- a/runtime/colors/koehler.vim +++ b/runtime/colors/koehler.vim @@ -3,7 +3,7 @@ " Maintainer: original maintainer Ron Aaron <ron@ronware.org> " Website: https://www.github.com/vim/colorschemes " License: Same as Vim -" Last Updated: 2022-07-26 15:50:06 +" Last Updated: Tue 23 Aug 2022 16:50:39 MSK " Generated by Colortemplate v2.2.0 @@ -12,7 +12,7 @@ set background=dark hi clear let g:colors_name = 'koehler' -let s:t_Co = exists('&t_Co') && !empty(&t_Co) && &t_Co >= 0 ? &t_Co : -1 +let s:t_Co = exists('&t_Co') && !has('gui_running') ? (&t_Co ? &t_Co : 0) : -1 hi! link Terminal Normal hi! link Boolean Constant diff --git a/runtime/colors/lunaperche.vim b/runtime/colors/lunaperche.vim new file mode 100644 index 0000000000..2f2d4d949b --- /dev/null +++ b/runtime/colors/lunaperche.vim @@ -0,0 +1,911 @@ +" Name: Perchè il sole a Milano? Portofino? Dimmi la luna perchè? +" Description: White(perchè il sole)/Black(la luna perchè?) background colorscheme. +" Author: Maxim Kim <habamax@gmail.com> +" Maintainer: Maxim Kim <habamax@gmail.com> +" Website: https://www.github.com/vim/colorschemes +" License: Vim License (see `:help license`) +" Last Updated: Tue 23 Aug 2022 16:50:40 MSK + +" Generated by Colortemplate v2.2.0 + +hi clear +let g:colors_name = 'lunaperche' + +let s:t_Co = exists('&t_Co') && !has('gui_running') ? (&t_Co ? &t_Co : 0) : -1 + +hi! link helpVim Title +hi! link helpHeader Title +hi! link helpHyperTextJump Underlined +hi! link fugitiveSymbolicRef PreProc +hi! link fugitiveHeading Statement +hi! link fugitiveStagedHeading Statement +hi! link fugitiveUnstagedHeading Statement +hi! link fugitiveUntrackedHeading Statement +hi! link fugitiveStagedModifier PreProc +hi! link fugitiveUnstagedModifier PreProc +hi! link fugitiveHash Constant +hi! link diffFile PreProc +hi! link markdownHeadingDelimiter Special +hi! link rstSectionDelimiter PreProc +hi! link rstDirective Special +hi! link rstHyperlinkReference Special +hi! link rstFieldName Special +hi! link rstDelimiter Special +hi! link rstInterpretedText Special +hi! link colortemplateKey Statement +hi! link xmlTagName Statement +hi! link javaScriptFunction Statement +hi! link javaScriptIdentifier Statement +hi! link sqlKeyword Statement +hi! link yamlBlockMappingKey Statement +hi! link rubyMacro Statement +hi! link rubyDefine Statement +hi! link vimGroup Normal +hi! link vimVar Normal +hi! link vimOper Normal +hi! link vimSep Normal +hi! link vimParenSep Normal +hi! link vimOption Normal +hi! link vimCommentString Comment +hi! link pythonInclude Statement +hi! link elixirOperator Statement +hi! link elixirKeyword Statement +hi! link elixirBlockDefinition Statement +hi! link elixirDefine Statement +hi! link elixirPrivateDefine Statement +hi! link elixirGuard Statement +hi! link elixirPrivateGuard Statement +hi! link elixirModuleDefine Statement +hi! link elixirProtocolDefine Statement +hi! link elixirImplDefine Statement +hi! link elixirRecordDefine Statement +hi! link elixirPrivateRecordDefine Statement +hi! link elixirMacroDefine Statement +hi! link elixirPrivateMacroDefine Statement +hi! link elixirDelegateDefine Statement +hi! link elixirOverridableDefine Statement +hi! link elixirExceptionDefine Statement +hi! link elixirCallbackDefine Statement +hi! link elixirStructDefine Statement +hi! link elixirExUnitMacro Statement +hi! link elixirInclude Statement +hi! link elixirVariable Special +hi! link elixirAtom Constant +hi! link elixirDocTest String +hi! link shQuote Constant +hi! link shNoQuote Normal +hi! link shTestOpr Normal +hi! link shOperator Normal +hi! link shSetOption Normal +hi! link shOption Normal +hi! link shCommandSub Normal +hi! link shDerefPattern shQuote +hi! link shDerefOp Special +hi! link Terminal Normal +hi! link StatuslineTerm Statusline +hi! link StatuslineTermNC StatuslineNC +hi! link LineNrAbove LineNr +hi! link LineNrBelow LineNr +if &background ==# 'dark' + if (has('termguicolors') && &termguicolors) || has('gui_running') + let g:terminal_ansi_colors = ['#000000', '#af5f5f', '#5faf5f', '#af875f', '#5f87af', '#d787af', '#5fafaf', '#c6c6c6', '#767676', '#ff5f5f', '#5fd75f', '#ffd787', '#87afd7', '#ffafd7', '#5fd7d7', '#ffffff'] + endif + hi Normal guifg=#c6c6c6 guibg=#000000 gui=NONE cterm=NONE + hi Statusline guifg=#000000 guibg=#c6c6c6 gui=bold cterm=bold + hi StatuslineNC guifg=#000000 guibg=#767676 gui=NONE cterm=NONE + hi VertSplit guifg=#767676 guibg=#767676 gui=NONE cterm=NONE + hi TabLine guifg=#000000 guibg=#c6c6c6 gui=NONE cterm=NONE + hi TabLineFill guifg=NONE guibg=#767676 gui=NONE cterm=NONE + hi TabLineSel guifg=#ffffff guibg=#000000 gui=bold cterm=bold + hi ToolbarLine guifg=NONE guibg=NONE gui=NONE ctermfg=NONE ctermbg=NONE cterm=NONE + hi ToolbarButton guifg=#000000 guibg=#ffffff gui=NONE cterm=NONE + hi QuickFixLine guifg=#000000 guibg=#87afd7 gui=NONE cterm=NONE + hi CursorLineNr guifg=#ffffff guibg=NONE gui=bold cterm=bold + hi LineNr guifg=#585858 guibg=NONE gui=NONE cterm=NONE + hi NonText guifg=#585858 guibg=NONE gui=NONE cterm=NONE + hi FoldColumn guifg=#585858 guibg=NONE gui=NONE cterm=NONE + hi EndOfBuffer guifg=#585858 guibg=NONE gui=NONE cterm=NONE + hi SpecialKey guifg=#585858 guibg=NONE gui=NONE cterm=NONE + hi Pmenu guifg=NONE guibg=#1c1c1c gui=NONE cterm=NONE + hi PmenuSel guifg=NONE guibg=#005f00 gui=NONE cterm=NONE + hi PmenuThumb guifg=NONE guibg=#c6c6c6 gui=NONE cterm=NONE + hi PmenuSbar guifg=NONE guibg=NONE gui=NONE ctermfg=NONE ctermbg=NONE cterm=NONE + hi SignColumn guifg=NONE guibg=NONE gui=NONE ctermfg=NONE ctermbg=NONE cterm=NONE + hi Error guifg=#ffffff guibg=#ff5f5f gui=NONE cterm=NONE + hi ErrorMsg guifg=#ffffff guibg=#ff5f5f gui=NONE cterm=NONE + hi ModeMsg guifg=#ffd787 guibg=NONE gui=reverse cterm=reverse + hi MoreMsg guifg=#5fd75f guibg=NONE gui=NONE cterm=NONE + hi Question guifg=#ffafd7 guibg=NONE gui=NONE cterm=NONE + hi WarningMsg guifg=#ff5f5f guibg=NONE gui=NONE cterm=NONE + hi Todo guifg=#5fd7d7 guibg=#000000 gui=reverse cterm=reverse + hi Search guifg=#000000 guibg=#ffd787 gui=NONE cterm=NONE + hi IncSearch guifg=#000000 guibg=#5fd75f gui=NONE cterm=NONE + hi CurSearch guifg=#000000 guibg=#5fd75f gui=NONE cterm=NONE + hi WildMenu guifg=#000000 guibg=#ffd787 gui=bold cterm=bold + hi debugPC guifg=#5f87af guibg=NONE gui=reverse cterm=reverse + hi debugBreakpoint guifg=#5fafaf guibg=NONE gui=reverse cterm=reverse + hi Cursor guifg=#ffffff guibg=#000000 gui=reverse cterm=reverse + hi lCursor guifg=#ff5fff guibg=#000000 gui=reverse cterm=reverse + hi Visual guifg=#ffffff guibg=#005f87 gui=NONE cterm=NONE + hi MatchParen guifg=#c5e7c5 guibg=#000000 gui=reverse cterm=reverse + hi VisualNOS guifg=#000000 guibg=#5fafaf gui=NONE cterm=NONE + hi CursorLine guifg=NONE guibg=#262626 gui=NONE cterm=NONE + hi CursorColumn guifg=NONE guibg=#262626 gui=NONE cterm=NONE + hi Folded guifg=#767676 guibg=#1c1c1c gui=NONE cterm=NONE + hi ColorColumn guifg=NONE guibg=#1c1c1c gui=NONE cterm=NONE + hi SpellBad guifg=NONE guibg=NONE guisp=#ff5f5f gui=undercurl ctermfg=NONE ctermbg=NONE cterm=NONE + hi SpellCap guifg=NONE guibg=NONE guisp=#5fafaf gui=undercurl ctermfg=NONE ctermbg=NONE cterm=NONE + hi SpellLocal guifg=NONE guibg=NONE guisp=#5faf5f gui=undercurl ctermfg=NONE ctermbg=NONE cterm=NONE + hi SpellRare guifg=NONE guibg=NONE guisp=#ffafd7 gui=undercurl ctermfg=NONE ctermbg=NONE cterm=NONE + hi Comment guifg=#87afd7 guibg=NONE gui=NONE cterm=NONE + hi Constant guifg=#ffd787 guibg=NONE gui=NONE cterm=NONE + hi Identifier guifg=NONE guibg=NONE gui=NONE ctermfg=NONE ctermbg=NONE cterm=NONE + hi Statement guifg=#eeeeee guibg=NONE gui=bold cterm=bold + hi Type guifg=#5fd75f guibg=NONE gui=bold cterm=bold + hi PreProc guifg=#af875f guibg=NONE gui=NONE cterm=NONE + hi Special guifg=#5fafaf guibg=NONE gui=NONE cterm=NONE + hi Underlined guifg=NONE guibg=NONE gui=underline ctermfg=NONE ctermbg=NONE cterm=underline + hi Title guifg=NONE guibg=NONE gui=bold ctermfg=NONE ctermbg=NONE cterm=bold + hi Directory guifg=#5fd7d7 guibg=NONE gui=bold cterm=bold + hi Conceal guifg=NONE guibg=NONE gui=NONE ctermfg=NONE ctermbg=NONE cterm=NONE + hi Ignore guifg=NONE guibg=NONE gui=NONE ctermfg=NONE ctermbg=NONE cterm=NONE + hi DiffAdd guifg=#000000 guibg=#af87af gui=NONE cterm=NONE + hi DiffChange guifg=#000000 guibg=#d0d0d0 gui=NONE cterm=NONE + hi DiffText guifg=#000000 guibg=#5fd7d7 gui=NONE cterm=NONE + hi DiffDelete guifg=#d78787 guibg=NONE gui=NONE cterm=NONE + hi diffAdded guifg=#5fd75f guibg=NONE gui=NONE cterm=NONE + hi diffRemoved guifg=#d78787 guibg=NONE gui=NONE cterm=NONE + hi diffSubname guifg=#ffafd7 guibg=NONE gui=NONE cterm=NONE + hi dirType guifg=#d787af guibg=NONE gui=NONE cterm=NONE + hi dirPermissionUser guifg=#5faf5f guibg=NONE gui=NONE cterm=NONE + hi dirPermissionGroup guifg=#af875f guibg=NONE gui=NONE cterm=NONE + hi dirPermissionOther guifg=#5fafaf guibg=NONE gui=NONE cterm=NONE + hi dirOwner guifg=#767676 guibg=NONE gui=NONE cterm=NONE + hi dirGroup guifg=#767676 guibg=NONE gui=NONE cterm=NONE + hi dirTime guifg=#767676 guibg=NONE gui=NONE cterm=NONE + hi dirSize guifg=#ffd787 guibg=NONE gui=NONE cterm=NONE + hi dirSizeMod guifg=#d787af guibg=NONE gui=NONE cterm=NONE + hi FilterMenuDirectorySubtle guifg=#878787 guibg=NONE gui=NONE cterm=NONE + hi dirFilterMenuBookmarkPath guifg=#878787 guibg=NONE gui=NONE cterm=NONE + hi dirFilterMenuHistoryPath guifg=#878787 guibg=NONE gui=NONE cterm=NONE + hi FilterMenuLineNr guifg=#878787 guibg=NONE gui=NONE cterm=NONE + hi CocMenuSel guifg=NONE guibg=#005f00 gui=NONE cterm=NONE + hi CocSearch guifg=#ffd787 guibg=NONE gui=NONE cterm=NONE +else + " Light background + if (has('termguicolors') && &termguicolors) || has('gui_running') + let g:terminal_ansi_colors = ['#000000', '#870000', '#008700', '#875f00', '#005faf', '#870087', '#005f5f', '#808080', '#767676', '#d70000', '#87d787', '#d7d787', '#0087d7', '#af00af', '#00afaf', '#ffffff'] + endif + hi Normal guifg=#000000 guibg=#ffffff gui=NONE cterm=NONE + hi Statusline guifg=#ffffff guibg=#000000 gui=bold cterm=bold + hi StatuslineNC guifg=#ffffff guibg=#767676 gui=NONE cterm=NONE + hi VertSplit guifg=#767676 guibg=#767676 gui=NONE cterm=NONE + hi TabLine guifg=#000000 guibg=#bcbcbc gui=NONE cterm=NONE + hi TabLineFill guifg=NONE guibg=#767676 gui=NONE cterm=NONE + hi TabLineSel guifg=#ffffff guibg=#000000 gui=bold,reverse cterm=bold,reverse + hi ToolbarLine guifg=NONE guibg=NONE gui=NONE ctermfg=NONE ctermbg=NONE cterm=NONE + hi ToolbarButton guifg=#ffffff guibg=#000000 gui=NONE cterm=NONE + hi QuickFixLine guifg=#ffffff guibg=#0087d7 gui=NONE cterm=NONE + hi CursorLineNr guifg=#000000 guibg=NONE gui=bold cterm=bold + hi LineNr guifg=#9e9e9e guibg=NONE gui=NONE cterm=NONE + hi NonText guifg=#9e9e9e guibg=NONE gui=NONE cterm=NONE + hi FoldColumn guifg=#9e9e9e guibg=NONE gui=NONE cterm=NONE + hi EndOfBuffer guifg=#9e9e9e guibg=NONE gui=NONE cterm=NONE + hi SpecialKey guifg=#9e9e9e guibg=NONE gui=NONE cterm=NONE + hi Pmenu guifg=NONE guibg=#eeeeee gui=NONE cterm=NONE + hi PmenuSel guifg=NONE guibg=#afd7af gui=NONE cterm=NONE + hi PmenuThumb guifg=NONE guibg=#767676 gui=NONE cterm=NONE + hi PmenuSbar guifg=NONE guibg=NONE gui=NONE ctermfg=NONE ctermbg=NONE cterm=NONE + hi SignColumn guifg=NONE guibg=NONE gui=NONE ctermfg=NONE ctermbg=NONE cterm=NONE + hi Error guifg=#ffffff guibg=#d70000 gui=NONE cterm=NONE + hi ErrorMsg guifg=#ffffff guibg=#d70000 gui=NONE cterm=NONE + hi ModeMsg guifg=#d7d787 guibg=#000000 gui=reverse cterm=reverse + hi MoreMsg guifg=#008700 guibg=NONE gui=bold cterm=bold + hi Question guifg=#870087 guibg=NONE gui=bold cterm=bold + hi WarningMsg guifg=#d70000 guibg=NONE gui=bold cterm=bold + hi Todo guifg=#005f5f guibg=#ffffff gui=reverse cterm=reverse + hi Search guifg=#000000 guibg=#d7d787 gui=NONE cterm=NONE + hi IncSearch guifg=#000000 guibg=#87d787 gui=NONE cterm=NONE + hi CurSearch guifg=#000000 guibg=#87d787 gui=NONE cterm=NONE + hi WildMenu guifg=#000000 guibg=#d7d787 gui=bold cterm=bold + hi debugPC guifg=#005faf guibg=NONE gui=reverse cterm=reverse + hi debugBreakpoint guifg=#005f5f guibg=NONE gui=reverse cterm=reverse + hi Cursor guifg=#000000 guibg=#ffffff gui=reverse cterm=reverse + hi lCursor guifg=#ff00ff guibg=#000000 gui=reverse cterm=reverse + hi Visual guifg=#ffffff guibg=#5f87af gui=NONE cterm=NONE + hi MatchParen guifg=NONE guibg=#c5e7c5 gui=NONE cterm=NONE + hi VisualNOS guifg=#ffffff guibg=#00afaf gui=NONE cterm=NONE + hi CursorLine guifg=NONE guibg=#e4e4e4 gui=NONE cterm=NONE + hi CursorColumn guifg=NONE guibg=#e4e4e4 gui=NONE cterm=NONE + hi Folded guifg=#767676 guibg=#eeeeee gui=NONE cterm=NONE + hi ColorColumn guifg=NONE guibg=#eeeeee gui=NONE cterm=NONE + hi SpellBad guifg=NONE guibg=NONE guisp=#870000 gui=undercurl ctermfg=NONE ctermbg=NONE cterm=NONE + hi SpellCap guifg=NONE guibg=NONE guisp=#005f5f gui=undercurl ctermfg=NONE ctermbg=NONE cterm=NONE + hi SpellLocal guifg=NONE guibg=NONE guisp=#008700 gui=undercurl ctermfg=NONE ctermbg=NONE cterm=NONE + hi SpellRare guifg=NONE guibg=NONE guisp=#af00af gui=undercurl ctermfg=NONE ctermbg=NONE cterm=NONE + hi Comment guifg=#005faf guibg=NONE gui=NONE cterm=NONE + hi Constant guifg=#870000 guibg=NONE gui=NONE cterm=NONE + hi Identifier guifg=NONE guibg=NONE gui=NONE ctermfg=NONE ctermbg=NONE cterm=NONE + hi Statement guifg=#000000 guibg=NONE gui=bold cterm=bold + hi Type guifg=#008700 guibg=NONE gui=bold cterm=bold + hi PreProc guifg=#875f00 guibg=NONE gui=NONE cterm=NONE + hi Special guifg=#005f5f guibg=NONE gui=NONE cterm=NONE + hi Underlined guifg=NONE guibg=NONE gui=underline ctermfg=NONE ctermbg=NONE cterm=underline + hi Title guifg=NONE guibg=NONE gui=bold ctermfg=NONE ctermbg=NONE cterm=bold + hi Directory guifg=#005faf guibg=NONE gui=bold cterm=bold + hi Conceal guifg=NONE guibg=NONE gui=NONE ctermfg=NONE ctermbg=NONE cterm=NONE + hi Ignore guifg=NONE guibg=NONE gui=NONE ctermfg=NONE ctermbg=NONE cterm=NONE + hi DiffAdd guifg=#000000 guibg=#d7afd7 gui=NONE cterm=NONE + hi DiffChange guifg=#000000 guibg=#d0d0d0 gui=NONE cterm=NONE + hi DiffText guifg=#000000 guibg=#5fd7d7 gui=NONE cterm=NONE + hi DiffDelete guifg=#870000 guibg=NONE gui=NONE cterm=NONE + hi diffAdded guifg=#008700 guibg=NONE gui=NONE cterm=NONE + hi diffRemoved guifg=#d70000 guibg=NONE gui=NONE cterm=NONE + hi diffSubname guifg=#870087 guibg=NONE gui=NONE cterm=NONE + hi dirType guifg=#005f5f guibg=NONE gui=NONE cterm=NONE + hi dirPermissionUser guifg=#875f00 guibg=NONE gui=NONE cterm=NONE + hi dirPermissionGroup guifg=#008700 guibg=NONE gui=NONE cterm=NONE + hi dirPermissionOther guifg=#870087 guibg=NONE gui=NONE cterm=NONE + hi dirOwner guifg=#808080 guibg=NONE gui=NONE cterm=NONE + hi dirGroup guifg=#808080 guibg=NONE gui=NONE cterm=NONE + hi dirTime guifg=#808080 guibg=NONE gui=NONE cterm=NONE + hi dirSize guifg=#870000 guibg=NONE gui=NONE cterm=NONE + hi dirSizeMod guifg=#005f5f guibg=NONE gui=NONE cterm=NONE + hi dirLink guifg=#008700 guibg=NONE gui=bold cterm=bold + hi dirFilterMenuBookmarkPath guifg=#626262 guibg=NONE gui=NONE cterm=NONE + hi dirFilterMenuHistoryPath guifg=#626262 guibg=NONE gui=NONE cterm=NONE + hi FilterMenuDirectorySubtle guifg=#626262 guibg=NONE gui=NONE cterm=NONE + hi FilterMenuLineNr guifg=#626262 guibg=NONE gui=NONE cterm=NONE + hi CocMenuSel guifg=NONE guibg=#afd7af gui=NONE cterm=NONE + hi CocSearch guifg=#870000 guibg=NONE gui=NONE cterm=NONE +endif + +if s:t_Co >= 256 + hi! link helpVim Title + hi! link helpHeader Title + hi! link helpHyperTextJump Underlined + hi! link fugitiveSymbolicRef PreProc + hi! link fugitiveHeading Statement + hi! link fugitiveStagedHeading Statement + hi! link fugitiveUnstagedHeading Statement + hi! link fugitiveUntrackedHeading Statement + hi! link fugitiveStagedModifier PreProc + hi! link fugitiveUnstagedModifier PreProc + hi! link fugitiveHash Constant + hi! link diffFile PreProc + hi! link markdownHeadingDelimiter Special + hi! link rstSectionDelimiter PreProc + hi! link rstDirective Special + hi! link rstHyperlinkReference Special + hi! link rstFieldName Special + hi! link rstDelimiter Special + hi! link rstInterpretedText Special + hi! link colortemplateKey Statement + hi! link xmlTagName Statement + hi! link javaScriptFunction Statement + hi! link javaScriptIdentifier Statement + hi! link sqlKeyword Statement + hi! link yamlBlockMappingKey Statement + hi! link rubyMacro Statement + hi! link rubyDefine Statement + hi! link vimGroup Normal + hi! link vimVar Normal + hi! link vimOper Normal + hi! link vimSep Normal + hi! link vimParenSep Normal + hi! link vimOption Normal + hi! link vimCommentString Comment + hi! link pythonInclude Statement + hi! link elixirOperator Statement + hi! link elixirKeyword Statement + hi! link elixirBlockDefinition Statement + hi! link elixirDefine Statement + hi! link elixirPrivateDefine Statement + hi! link elixirGuard Statement + hi! link elixirPrivateGuard Statement + hi! link elixirModuleDefine Statement + hi! link elixirProtocolDefine Statement + hi! link elixirImplDefine Statement + hi! link elixirRecordDefine Statement + hi! link elixirPrivateRecordDefine Statement + hi! link elixirMacroDefine Statement + hi! link elixirPrivateMacroDefine Statement + hi! link elixirDelegateDefine Statement + hi! link elixirOverridableDefine Statement + hi! link elixirExceptionDefine Statement + hi! link elixirCallbackDefine Statement + hi! link elixirStructDefine Statement + hi! link elixirExUnitMacro Statement + hi! link elixirInclude Statement + hi! link elixirVariable Special + hi! link elixirAtom Constant + hi! link elixirDocTest String + hi! link shQuote Constant + hi! link shNoQuote Normal + hi! link shTestOpr Normal + hi! link shOperator Normal + hi! link shSetOption Normal + hi! link shOption Normal + hi! link shCommandSub Normal + hi! link shDerefPattern shQuote + hi! link shDerefOp Special + hi! link Terminal Normal + hi! link StatuslineTerm Statusline + hi! link StatuslineTermNC StatuslineNC + hi! link LineNrAbove LineNr + hi! link LineNrBelow LineNr + if &background ==# 'dark' + hi Normal ctermfg=251 ctermbg=16 cterm=NONE + hi Statusline ctermfg=16 ctermbg=251 cterm=bold + hi StatuslineNC ctermfg=16 ctermbg=243 cterm=NONE + hi VertSplit ctermfg=243 ctermbg=243 cterm=NONE + hi TabLine ctermfg=16 ctermbg=251 cterm=NONE + hi TabLineFill ctermfg=NONE ctermbg=243 cterm=NONE + hi TabLineSel ctermfg=231 ctermbg=16 cterm=bold + hi ToolbarLine ctermfg=NONE ctermbg=NONE cterm=NONE + hi ToolbarButton ctermfg=16 ctermbg=231 cterm=NONE + hi QuickFixLine ctermfg=16 ctermbg=110 cterm=NONE + hi CursorLineNr ctermfg=231 ctermbg=NONE cterm=bold + hi LineNr ctermfg=240 ctermbg=NONE cterm=NONE + hi NonText ctermfg=240 ctermbg=NONE cterm=NONE + hi FoldColumn ctermfg=240 ctermbg=NONE cterm=NONE + hi EndOfBuffer ctermfg=240 ctermbg=NONE cterm=NONE + hi SpecialKey ctermfg=240 ctermbg=NONE cterm=NONE + hi Pmenu ctermfg=NONE ctermbg=234 cterm=NONE + hi PmenuSel ctermfg=NONE ctermbg=22 cterm=NONE + hi PmenuThumb ctermfg=NONE ctermbg=251 cterm=NONE + hi PmenuSbar ctermfg=NONE ctermbg=NONE cterm=NONE + hi SignColumn ctermfg=NONE ctermbg=NONE cterm=NONE + hi Error ctermfg=231 ctermbg=203 cterm=NONE + hi ErrorMsg ctermfg=231 ctermbg=203 cterm=NONE + hi ModeMsg ctermfg=222 ctermbg=NONE cterm=reverse + hi MoreMsg ctermfg=77 ctermbg=NONE cterm=NONE + hi Question ctermfg=218 ctermbg=NONE cterm=NONE + hi WarningMsg ctermfg=203 ctermbg=NONE cterm=NONE + hi Todo ctermfg=116 ctermbg=16 cterm=reverse + hi Search ctermfg=16 ctermbg=222 cterm=NONE + hi IncSearch ctermfg=16 ctermbg=77 cterm=NONE + hi CurSearch ctermfg=16 ctermbg=77 cterm=NONE + hi WildMenu ctermfg=16 ctermbg=222 cterm=bold + hi debugPC ctermfg=67 ctermbg=NONE cterm=reverse + hi debugBreakpoint ctermfg=73 ctermbg=NONE cterm=reverse + hi Visual ctermfg=231 ctermbg=24 cterm=NONE + hi MatchParen ctermfg=30 ctermbg=16 cterm=reverse + hi VisualNOS ctermfg=16 ctermbg=73 cterm=NONE + hi CursorLine ctermfg=NONE ctermbg=235 cterm=NONE + hi CursorColumn ctermfg=NONE ctermbg=235 cterm=NONE + hi Folded ctermfg=243 ctermbg=234 cterm=NONE + hi ColorColumn ctermfg=NONE ctermbg=234 cterm=NONE + hi SpellBad ctermfg=203 ctermbg=NONE cterm=underline + hi SpellCap ctermfg=73 ctermbg=NONE cterm=underline + hi SpellLocal ctermfg=77 ctermbg=NONE cterm=underline + hi SpellRare ctermfg=218 ctermbg=NONE cterm=underline + hi Comment ctermfg=110 ctermbg=NONE cterm=NONE + hi Constant ctermfg=222 ctermbg=NONE cterm=NONE + hi Identifier ctermfg=NONE ctermbg=NONE cterm=NONE + hi Statement ctermfg=255 ctermbg=NONE cterm=bold + hi Type ctermfg=77 ctermbg=NONE cterm=bold + hi PreProc ctermfg=137 ctermbg=NONE cterm=NONE + hi Special ctermfg=73 ctermbg=NONE cterm=NONE + hi Underlined ctermfg=NONE ctermbg=NONE cterm=underline + hi Title ctermfg=NONE ctermbg=NONE cterm=bold + hi Directory ctermfg=116 ctermbg=NONE cterm=bold + hi Conceal ctermfg=NONE ctermbg=NONE cterm=NONE + hi Ignore ctermfg=NONE ctermbg=NONE cterm=NONE + hi DiffAdd ctermfg=16 ctermbg=139 cterm=NONE + hi DiffChange ctermfg=16 ctermbg=252 cterm=NONE + hi DiffText ctermfg=16 ctermbg=80 cterm=NONE + hi DiffDelete ctermfg=174 ctermbg=NONE cterm=NONE + hi diffAdded ctermfg=77 ctermbg=NONE cterm=NONE + hi diffRemoved ctermfg=174 ctermbg=NONE cterm=NONE + hi diffSubname ctermfg=218 ctermbg=NONE cterm=NONE + hi dirType ctermfg=175 ctermbg=NONE cterm=NONE + hi dirPermissionUser ctermfg=71 ctermbg=NONE cterm=NONE + hi dirPermissionGroup ctermfg=137 ctermbg=NONE cterm=NONE + hi dirPermissionOther ctermfg=73 ctermbg=NONE cterm=NONE + hi dirOwner ctermfg=243 ctermbg=NONE cterm=NONE + hi dirGroup ctermfg=243 ctermbg=NONE cterm=NONE + hi dirTime ctermfg=243 ctermbg=NONE cterm=NONE + hi dirSize ctermfg=222 ctermbg=NONE cterm=NONE + hi dirSizeMod ctermfg=175 ctermbg=NONE cterm=NONE + hi FilterMenuDirectorySubtle ctermfg=102 ctermbg=NONE cterm=NONE + hi dirFilterMenuBookmarkPath ctermfg=102 ctermbg=NONE cterm=NONE + hi dirFilterMenuHistoryPath ctermfg=102 ctermbg=NONE cterm=NONE + hi FilterMenuLineNr ctermfg=102 ctermbg=NONE cterm=NONE + hi CocMenuSel ctermfg=NONE ctermbg=22 cterm=NONE + hi CocSearch ctermfg=222 ctermbg=NONE cterm=NONE + else + " Light background + hi Normal ctermfg=16 ctermbg=231 cterm=NONE + hi Statusline ctermfg=231 ctermbg=16 cterm=bold + hi StatuslineNC ctermfg=231 ctermbg=243 cterm=NONE + hi VertSplit ctermfg=243 ctermbg=243 cterm=NONE + hi TabLine ctermfg=16 ctermbg=250 cterm=NONE + hi TabLineFill ctermfg=NONE ctermbg=243 cterm=NONE + hi TabLineSel ctermfg=231 ctermbg=16 cterm=bold,reverse + hi ToolbarLine ctermfg=NONE ctermbg=NONE cterm=NONE + hi ToolbarButton ctermfg=231 ctermbg=16 cterm=NONE + hi QuickFixLine ctermfg=231 ctermbg=32 cterm=NONE + hi CursorLineNr ctermfg=16 ctermbg=NONE cterm=bold + hi LineNr ctermfg=247 ctermbg=NONE cterm=NONE + hi NonText ctermfg=247 ctermbg=NONE cterm=NONE + hi FoldColumn ctermfg=247 ctermbg=NONE cterm=NONE + hi EndOfBuffer ctermfg=247 ctermbg=NONE cterm=NONE + hi SpecialKey ctermfg=247 ctermbg=NONE cterm=NONE + hi Pmenu ctermfg=NONE ctermbg=255 cterm=NONE + hi PmenuSel ctermfg=NONE ctermbg=151 cterm=NONE + hi PmenuThumb ctermfg=NONE ctermbg=243 cterm=NONE + hi PmenuSbar ctermfg=NONE ctermbg=NONE cterm=NONE + hi SignColumn ctermfg=NONE ctermbg=NONE cterm=NONE + hi Error ctermfg=231 ctermbg=160 cterm=NONE + hi ErrorMsg ctermfg=231 ctermbg=160 cterm=NONE + hi ModeMsg ctermfg=186 ctermbg=16 cterm=reverse + hi MoreMsg ctermfg=28 ctermbg=NONE cterm=bold + hi Question ctermfg=90 ctermbg=NONE cterm=bold + hi WarningMsg ctermfg=160 ctermbg=NONE cterm=bold + hi Todo ctermfg=23 ctermbg=231 cterm=reverse + hi Search ctermfg=16 ctermbg=186 cterm=NONE + hi IncSearch ctermfg=16 ctermbg=114 cterm=NONE + hi CurSearch ctermfg=16 ctermbg=114 cterm=NONE + hi WildMenu ctermfg=16 ctermbg=186 cterm=bold + hi debugPC ctermfg=25 ctermbg=NONE cterm=reverse + hi debugBreakpoint ctermfg=23 ctermbg=NONE cterm=reverse + hi Visual ctermfg=231 ctermbg=67 cterm=NONE + hi MatchParen ctermfg=30 ctermbg=231 cterm=reverse + hi VisualNOS ctermfg=231 ctermbg=37 cterm=NONE + hi CursorLine ctermfg=NONE ctermbg=254 cterm=NONE + hi CursorColumn ctermfg=NONE ctermbg=254 cterm=NONE + hi Folded ctermfg=243 ctermbg=255 cterm=NONE + hi ColorColumn ctermfg=NONE ctermbg=255 cterm=NONE + hi SpellBad ctermfg=88 ctermbg=NONE cterm=underline + hi SpellCap ctermfg=23 ctermbg=NONE cterm=underline + hi SpellLocal ctermfg=28 ctermbg=NONE cterm=underline + hi SpellRare ctermfg=133 ctermbg=NONE cterm=underline + hi Comment ctermfg=25 ctermbg=NONE cterm=NONE + hi Constant ctermfg=88 ctermbg=NONE cterm=NONE + hi Identifier ctermfg=NONE ctermbg=NONE cterm=NONE + hi Statement ctermfg=16 ctermbg=NONE cterm=bold + hi Type ctermfg=28 ctermbg=NONE cterm=bold + hi PreProc ctermfg=94 ctermbg=NONE cterm=NONE + hi Special ctermfg=23 ctermbg=NONE cterm=NONE + hi Underlined ctermfg=NONE ctermbg=NONE cterm=underline + hi Title ctermfg=NONE ctermbg=NONE cterm=bold + hi Directory ctermfg=25 ctermbg=NONE cterm=bold + hi Conceal ctermfg=NONE ctermbg=NONE cterm=NONE + hi Ignore ctermfg=NONE ctermbg=NONE cterm=NONE + hi DiffAdd ctermfg=16 ctermbg=182 cterm=NONE + hi DiffChange ctermfg=16 ctermbg=252 cterm=NONE + hi DiffText ctermfg=16 ctermbg=80 cterm=NONE + hi DiffDelete ctermfg=88 ctermbg=NONE cterm=NONE + hi diffAdded ctermfg=28 ctermbg=NONE cterm=NONE + hi diffRemoved ctermfg=160 ctermbg=NONE cterm=NONE + hi diffSubname ctermfg=90 ctermbg=NONE cterm=NONE + hi dirType ctermfg=23 ctermbg=NONE cterm=NONE + hi dirPermissionUser ctermfg=94 ctermbg=NONE cterm=NONE + hi dirPermissionGroup ctermfg=28 ctermbg=NONE cterm=NONE + hi dirPermissionOther ctermfg=90 ctermbg=NONE cterm=NONE + hi dirOwner ctermfg=244 ctermbg=NONE cterm=NONE + hi dirGroup ctermfg=244 ctermbg=NONE cterm=NONE + hi dirTime ctermfg=244 ctermbg=NONE cterm=NONE + hi dirSize ctermfg=88 ctermbg=NONE cterm=NONE + hi dirSizeMod ctermfg=23 ctermbg=NONE cterm=NONE + hi dirLink ctermfg=28 ctermbg=NONE cterm=bold + hi dirFilterMenuBookmarkPath ctermfg=241 ctermbg=NONE cterm=NONE + hi dirFilterMenuHistoryPath ctermfg=241 ctermbg=NONE cterm=NONE + hi FilterMenuDirectorySubtle ctermfg=241 ctermbg=NONE cterm=NONE + hi FilterMenuLineNr ctermfg=241 ctermbg=NONE cterm=NONE + hi CocMenuSel ctermfg=NONE ctermbg=151 cterm=NONE + hi CocSearch ctermfg=88 ctermbg=NONE cterm=NONE + endif + unlet s:t_Co + finish +endif + +if s:t_Co >= 16 + if &background ==# 'dark' + hi Normal ctermfg=grey ctermbg=black cterm=NONE + hi Statusline ctermfg=black ctermbg=grey cterm=bold + hi StatuslineNC ctermfg=black ctermbg=darkgrey cterm=NONE + hi VertSplit ctermfg=darkgrey ctermbg=darkgrey cterm=NONE + hi TabLine ctermfg=black ctermbg=grey cterm=NONE + hi TabLineFill ctermfg=NONE ctermbg=darkgrey cterm=NONE + hi TabLineSel ctermfg=white ctermbg=black cterm=bold + hi ToolbarLine ctermfg=NONE ctermbg=NONE cterm=NONE + hi ToolbarButton ctermfg=black ctermbg=white cterm=NONE + hi QuickFixLine ctermfg=black ctermbg=blue cterm=NONE + hi CursorLineNr ctermfg=white ctermbg=NONE cterm=bold + hi LineNr ctermfg=grey ctermbg=NONE cterm=NONE + hi NonText ctermfg=grey ctermbg=NONE cterm=NONE + hi FoldColumn ctermfg=grey ctermbg=NONE cterm=NONE + hi EndOfBuffer ctermfg=grey ctermbg=NONE cterm=NONE + hi SpecialKey ctermfg=grey ctermbg=NONE cterm=NONE + hi Pmenu ctermfg=black ctermbg=darkgrey cterm=NONE + hi PmenuSel ctermfg=black ctermbg=darkgreen cterm=NONE + hi PmenuThumb ctermfg=NONE ctermbg=grey cterm=NONE + hi PmenuSbar ctermfg=NONE ctermbg=NONE cterm=NONE + hi SignColumn ctermfg=NONE ctermbg=NONE cterm=NONE + hi Error ctermfg=white ctermbg=red cterm=NONE + hi ErrorMsg ctermfg=white ctermbg=red cterm=NONE + hi ModeMsg ctermfg=yellow ctermbg=NONE cterm=reverse + hi MoreMsg ctermfg=green ctermbg=NONE cterm=NONE + hi Question ctermfg=magenta ctermbg=NONE cterm=NONE + hi WarningMsg ctermfg=red ctermbg=NONE cterm=NONE + hi Todo ctermfg=cyan ctermbg=black cterm=reverse + hi Search ctermfg=black ctermbg=yellow cterm=NONE + hi IncSearch ctermfg=black ctermbg=green cterm=NONE + hi CurSearch ctermfg=black ctermbg=green cterm=NONE + hi WildMenu ctermfg=black ctermbg=yellow cterm=bold + hi debugPC ctermfg=darkblue ctermbg=NONE cterm=reverse + hi debugBreakpoint ctermfg=darkcyan ctermbg=NONE cterm=reverse + hi Visual ctermfg=white ctermbg=darkblue cterm=NONE + hi MatchParen ctermfg=darkcyan ctermbg=black cterm=reverse + hi VisualNOS ctermfg=black ctermbg=darkcyan cterm=NONE + hi CursorLine ctermfg=NONE ctermbg=NONE cterm=underline + hi CursorColumn ctermfg=black ctermbg=yellow cterm=NONE + hi Folded ctermfg=black ctermbg=darkyellow cterm=NONE + hi ColorColumn ctermfg=black ctermbg=darkyellow cterm=NONE + hi SpellBad ctermfg=red ctermbg=NONE cterm=underline + hi SpellCap ctermfg=darkcyan ctermbg=NONE cterm=underline + hi SpellLocal ctermfg=green ctermbg=NONE cterm=underline + hi SpellRare ctermfg=magenta ctermbg=NONE cterm=underline + hi Comment ctermfg=blue ctermbg=NONE cterm=NONE + hi Constant ctermfg=yellow ctermbg=NONE cterm=NONE + hi Identifier ctermfg=NONE ctermbg=NONE cterm=NONE + hi Statement ctermfg=grey ctermbg=NONE cterm=bold + hi Type ctermfg=green ctermbg=NONE cterm=bold + hi PreProc ctermfg=darkyellow ctermbg=NONE cterm=NONE + hi Special ctermfg=darkcyan ctermbg=NONE cterm=NONE + hi Underlined ctermfg=NONE ctermbg=NONE cterm=underline + hi Title ctermfg=NONE ctermbg=NONE cterm=bold + hi Directory ctermfg=cyan ctermbg=NONE cterm=bold + hi Conceal ctermfg=NONE ctermbg=NONE cterm=NONE + hi Ignore ctermfg=NONE ctermbg=NONE cterm=NONE + hi DiffAdd ctermfg=black ctermbg=darkmagenta cterm=NONE + hi DiffChange ctermfg=black ctermbg=lightgray cterm=NONE + hi DiffText ctermfg=black ctermbg=cyan cterm=NONE + hi DiffDelete ctermfg=darkred ctermbg=NONE cterm=NONE + hi diffAdded ctermfg=green ctermbg=NONE cterm=NONE + hi diffRemoved ctermfg=darkred ctermbg=NONE cterm=NONE + hi diffSubname ctermfg=magenta ctermbg=NONE cterm=NONE + else + " Light background + hi Normal ctermfg=black ctermbg=white cterm=NONE + hi Statusline ctermfg=white ctermbg=black cterm=bold + hi StatuslineNC ctermfg=white ctermbg=darkgrey cterm=NONE + hi VertSplit ctermfg=darkgrey ctermbg=darkgrey cterm=NONE + hi TabLine ctermfg=black ctermbg=lightgrey cterm=NONE + hi TabLineFill ctermfg=NONE ctermbg=darkgrey cterm=NONE + hi TabLineSel ctermfg=white ctermbg=black cterm=bold,reverse + hi ToolbarLine ctermfg=NONE ctermbg=NONE cterm=NONE + hi ToolbarButton ctermfg=white ctermbg=black cterm=NONE + hi QuickFixLine ctermfg=white ctermbg=blue cterm=NONE + hi CursorLineNr ctermfg=black ctermbg=NONE cterm=bold + hi LineNr ctermfg=darkgrey ctermbg=NONE cterm=NONE + hi NonText ctermfg=darkgrey ctermbg=NONE cterm=NONE + hi FoldColumn ctermfg=darkgrey ctermbg=NONE cterm=NONE + hi EndOfBuffer ctermfg=darkgrey ctermbg=NONE cterm=NONE + hi SpecialKey ctermfg=darkgrey ctermbg=NONE cterm=NONE + hi Pmenu ctermfg=black ctermbg=grey cterm=NONE + hi PmenuSel ctermfg=black ctermbg=darkgreen cterm=NONE + hi PmenuThumb ctermfg=NONE ctermbg=darkgrey cterm=NONE + hi PmenuSbar ctermfg=NONE ctermbg=NONE cterm=NONE + hi SignColumn ctermfg=NONE ctermbg=NONE cterm=NONE + hi Error ctermfg=white ctermbg=red cterm=NONE + hi ErrorMsg ctermfg=white ctermbg=red cterm=NONE + hi ModeMsg ctermfg=yellow ctermbg=black cterm=reverse + hi MoreMsg ctermfg=darkgreen ctermbg=NONE cterm=bold + hi Question ctermfg=darkmagenta ctermbg=NONE cterm=bold + hi WarningMsg ctermfg=red ctermbg=NONE cterm=bold + hi Todo ctermfg=darkcyan ctermbg=white cterm=reverse + hi Search ctermfg=black ctermbg=yellow cterm=NONE + hi IncSearch ctermfg=black ctermbg=green cterm=NONE + hi CurSearch ctermfg=black ctermbg=green cterm=NONE + hi WildMenu ctermfg=black ctermbg=yellow cterm=bold + hi debugPC ctermfg=darkblue ctermbg=NONE cterm=reverse + hi debugBreakpoint ctermfg=darkcyan ctermbg=NONE cterm=reverse + hi Visual ctermfg=white ctermbg=darkblue cterm=NONE + hi MatchParen ctermfg=darkcyan ctermbg=white cterm=reverse + hi VisualNOS ctermfg=black ctermbg=cyan cterm=NONE + hi CursorLine ctermfg=NONE ctermbg=NONE cterm=underline + hi CursorColumn ctermfg=black ctermbg=yellow cterm=NONE + hi Folded ctermfg=black ctermbg=darkyellow cterm=NONE + hi ColorColumn ctermfg=black ctermbg=darkyellow cterm=NONE + hi SpellBad ctermfg=darkred ctermbg=NONE cterm=underline + hi SpellCap ctermfg=darkcyan ctermbg=NONE cterm=underline + hi SpellLocal ctermfg=darkgreen ctermbg=NONE cterm=underline + hi SpellRare ctermfg=magenta ctermbg=NONE cterm=underline + hi Comment ctermfg=darkblue ctermbg=NONE cterm=NONE + hi Constant ctermfg=darkred ctermbg=NONE cterm=NONE + hi Identifier ctermfg=NONE ctermbg=NONE cterm=NONE + hi Statement ctermfg=black ctermbg=NONE cterm=bold + hi Type ctermfg=darkgreen ctermbg=NONE cterm=bold + hi PreProc ctermfg=darkyellow ctermbg=NONE cterm=NONE + hi Special ctermfg=darkcyan ctermbg=NONE cterm=NONE + hi Underlined ctermfg=NONE ctermbg=NONE cterm=underline + hi Title ctermfg=NONE ctermbg=NONE cterm=bold + hi Directory ctermfg=darkblue ctermbg=NONE cterm=bold + hi Conceal ctermfg=NONE ctermbg=NONE cterm=NONE + hi Ignore ctermfg=NONE ctermbg=NONE cterm=NONE + hi DiffAdd ctermfg=black ctermbg=darkmagenta cterm=NONE + hi DiffChange ctermfg=black ctermbg=lightgray cterm=NONE + hi DiffText ctermfg=black ctermbg=cyan cterm=NONE + hi DiffDelete ctermfg=darkred ctermbg=NONE cterm=NONE + hi diffAdded ctermfg=darkgreen ctermbg=NONE cterm=NONE + hi diffRemoved ctermfg=red ctermbg=NONE cterm=NONE + hi diffSubname ctermfg=darkmagenta ctermbg=NONE cterm=NONE + endif + unlet s:t_Co + finish +endif + +if s:t_Co >= 8 + if &background ==# 'dark' + hi Normal ctermfg=grey ctermbg=black cterm=NONE + hi Statusline ctermfg=grey ctermbg=black cterm=bold,reverse + hi StatuslineNC ctermfg=black ctermbg=grey cterm=NONE + hi VertSplit ctermfg=grey ctermbg=grey cterm=NONE + hi TabLine ctermfg=grey ctermbg=black cterm=reverse + hi TabLineFill ctermfg=NONE ctermbg=grey cterm=NONE + hi TabLineSel ctermfg=grey ctermbg=black cterm=NONE + hi ToolbarLine ctermfg=NONE ctermbg=NONE cterm=NONE + hi ToolbarButton ctermfg=grey ctermbg=black cterm=bold,reverse + hi QuickFixLine ctermfg=grey ctermbg=darkblue cterm=bold + hi CursorLineNr ctermfg=black ctermbg=NONE cterm=bold + hi LineNr ctermfg=darkyellow ctermbg=NONE cterm=NONE + hi NonText ctermfg=black ctermbg=NONE cterm=NONE + hi FoldColumn ctermfg=black ctermbg=NONE cterm=NONE + hi EndOfBuffer ctermfg=black ctermbg=NONE cterm=NONE + hi SpecialKey ctermfg=black ctermbg=NONE cterm=NONE + hi Pmenu ctermfg=black ctermbg=grey cterm=NONE + hi PmenuThumb ctermfg=NONE ctermbg=darkgreen cterm=NONE + hi PmenuSbar ctermfg=NONE ctermbg=NONE cterm=NONE + hi PmenuSel ctermfg=black ctermbg=darkgreen cterm=NONE + hi SignColumn ctermfg=NONE ctermbg=NONE cterm=NONE + hi Error ctermfg=grey ctermbg=darkred cterm=NONE + hi ErrorMsg ctermfg=grey ctermbg=darkred cterm=NONE + hi ModeMsg ctermfg=darkyellow ctermbg=black cterm=reverse + hi MoreMsg ctermfg=darkgreen ctermbg=NONE cterm=NONE + hi Question ctermfg=darkmagenta ctermbg=NONE cterm=NONE + hi WarningMsg ctermfg=darkred ctermbg=NONE cterm=NONE + hi Todo ctermfg=darkcyan ctermbg=black cterm=reverse + hi Search ctermfg=darkyellow ctermbg=black cterm=reverse + hi IncSearch ctermfg=darkgreen ctermbg=black cterm=reverse + hi CurSearch ctermfg=darkgreen ctermbg=black cterm=reverse + hi WildMenu ctermfg=black ctermbg=darkyellow cterm=bold + hi debugPC ctermfg=darkblue ctermbg=NONE cterm=reverse + hi debugBreakpoint ctermfg=darkcyan ctermbg=NONE cterm=reverse + hi Visual ctermfg=black ctermbg=darkblue cterm=NONE + hi MatchParen ctermfg=darkcyan ctermbg=black cterm=reverse + hi VisualNOS ctermfg=black ctermbg=darkcyan cterm=NONE + hi CursorLine ctermfg=NONE ctermbg=NONE cterm=underline + hi CursorColumn ctermfg=black ctermbg=darkyellow cterm=NONE + hi Folded ctermfg=black ctermbg=darkyellow cterm=NONE + hi ColorColumn ctermfg=black ctermbg=darkyellow cterm=NONE + hi SpellBad ctermfg=darkred ctermbg=NONE cterm=reverse,underline + hi SpellCap ctermfg=darkcyan ctermbg=NONE cterm=reverse,underline + hi SpellLocal ctermfg=darkgreen ctermbg=black cterm=reverse,underline + hi SpellRare ctermfg=darkmagenta ctermbg=NONE cterm=reverse,underline + hi Comment ctermfg=darkblue ctermbg=NONE cterm=NONE + hi Constant ctermfg=darkred ctermbg=NONE cterm=NONE + hi Identifier ctermfg=NONE ctermbg=NONE cterm=NONE + hi Statement ctermfg=black ctermbg=NONE cterm=bold + hi Type ctermfg=darkgreen ctermbg=NONE cterm=bold + hi PreProc ctermfg=darkyellow ctermbg=NONE cterm=NONE + hi Special ctermfg=darkcyan ctermbg=NONE cterm=NONE + hi Underlined ctermfg=NONE ctermbg=NONE cterm=underline + hi Title ctermfg=NONE ctermbg=NONE cterm=bold + hi Directory ctermfg=darkcyan ctermbg=NONE cterm=bold + hi Conceal ctermfg=NONE ctermbg=NONE cterm=NONE + hi Ignore ctermfg=NONE ctermbg=NONE cterm=NONE + hi DiffAdd ctermfg=black ctermbg=darkmagenta cterm=NONE + hi DiffChange ctermfg=black ctermbg=darkcyan cterm=NONE + hi DiffText ctermfg=black ctermbg=grey cterm=NONE + hi DiffDelete ctermfg=darkred ctermbg=NONE cterm=NONE + else + " Light background + hi Normal ctermfg=black ctermbg=grey cterm=NONE + hi Statusline ctermfg=grey ctermbg=black cterm=bold + hi StatuslineNC ctermfg=grey ctermbg=darkgrey cterm=NONE + hi VertSplit ctermfg=black ctermbg=black cterm=NONE + hi TabLine ctermfg=black ctermbg=grey cterm=reverse + hi TabLineFill ctermfg=NONE ctermbg=darkgrey cterm=NONE + hi TabLineSel ctermfg=black ctermbg=grey cterm=NONE + hi ToolbarLine ctermfg=NONE ctermbg=NONE cterm=NONE + hi ToolbarButton ctermfg=grey ctermbg=black cterm=bold + hi QuickFixLine ctermfg=grey ctermbg=darkblue cterm=bold + hi CursorLineNr ctermfg=black ctermbg=NONE cterm=bold + hi LineNr ctermfg=darkyellow ctermbg=NONE cterm=NONE + hi NonText ctermfg=black ctermbg=NONE cterm=NONE + hi FoldColumn ctermfg=black ctermbg=NONE cterm=NONE + hi EndOfBuffer ctermfg=black ctermbg=NONE cterm=NONE + hi SpecialKey ctermfg=black ctermbg=NONE cterm=NONE + hi Pmenu ctermfg=grey ctermbg=black cterm=NONE + hi PmenuThumb ctermfg=NONE ctermbg=darkgreen cterm=NONE + hi PmenuSbar ctermfg=NONE ctermbg=NONE cterm=NONE + hi PmenuSel ctermfg=black ctermbg=darkgreen cterm=NONE + hi SignColumn ctermfg=NONE ctermbg=NONE cterm=NONE + hi Error ctermfg=grey ctermbg=darkred cterm=NONE + hi ErrorMsg ctermfg=grey ctermbg=darkred cterm=NONE + hi ModeMsg ctermfg=darkyellow ctermbg=black cterm=reverse + hi MoreMsg ctermfg=darkgreen ctermbg=NONE cterm=NONE + hi Question ctermfg=darkmagenta ctermbg=NONE cterm=NONE + hi WarningMsg ctermfg=darkred ctermbg=NONE cterm=NONE + hi Todo ctermfg=darkcyan ctermbg=black cterm=reverse + hi Search ctermfg=black ctermbg=darkyellow cterm=NONE + hi IncSearch ctermfg=black ctermbg=darkgreen cterm=NONE + hi CurSearch ctermfg=black ctermbg=darkgreen cterm=NONE + hi WildMenu ctermfg=black ctermbg=darkyellow cterm=bold + hi debugPC ctermfg=darkblue ctermbg=NONE cterm=reverse + hi debugBreakpoint ctermfg=darkcyan ctermbg=NONE cterm=reverse + hi Visual ctermfg=grey ctermbg=darkblue cterm=NONE + hi MatchParen ctermfg=darkcyan ctermbg=grey cterm=reverse + hi VisualNOS ctermfg=black ctermbg=darkcyan cterm=NONE + hi CursorLine ctermfg=NONE ctermbg=NONE cterm=underline + hi CursorColumn ctermfg=black ctermbg=darkyellow cterm=NONE + hi Folded ctermfg=black ctermbg=darkyellow cterm=NONE + hi ColorColumn ctermfg=black ctermbg=darkyellow cterm=NONE + hi SpellBad ctermfg=darkred ctermbg=NONE cterm=reverse,underline + hi SpellCap ctermfg=darkcyan ctermbg=NONE cterm=reverse,underline + hi SpellLocal ctermfg=darkgreen ctermbg=black cterm=reverse,underline + hi SpellRare ctermfg=darkmagenta ctermbg=NONE cterm=reverse,underline + hi Comment ctermfg=darkblue ctermbg=NONE cterm=NONE + hi Constant ctermfg=darkred ctermbg=NONE cterm=NONE + hi Identifier ctermfg=NONE ctermbg=NONE cterm=NONE + hi Statement ctermfg=black ctermbg=NONE cterm=bold + hi Type ctermfg=darkgreen ctermbg=NONE cterm=bold + hi PreProc ctermfg=darkyellow ctermbg=NONE cterm=NONE + hi Special ctermfg=darkcyan ctermbg=NONE cterm=NONE + hi Underlined ctermfg=NONE ctermbg=NONE cterm=underline + hi Title ctermfg=black ctermbg=NONE cterm=bold + hi Directory ctermfg=darkcyan ctermbg=NONE cterm=bold + hi Conceal ctermfg=NONE ctermbg=NONE cterm=NONE + hi Ignore ctermfg=NONE ctermbg=NONE cterm=NONE + hi DiffAdd ctermfg=black ctermbg=darkmagenta cterm=NONE + hi DiffChange ctermfg=black ctermbg=darkcyan cterm=NONE + hi DiffText ctermfg=grey ctermbg=black cterm=NONE + hi DiffDelete ctermfg=darkred ctermbg=NONE cterm=NONE + endif + unlet s:t_Co + finish +endif + +if s:t_Co >= 0 + hi Normal term=NONE + hi ColorColumn term=reverse + hi Conceal term=NONE + hi Cursor term=reverse + hi CursorColumn term=NONE + hi CursorLine term=underline + hi CursorLineNr term=bold + hi DiffAdd term=reverse + hi DiffChange term=NONE + hi DiffDelete term=reverse + hi DiffText term=reverse + hi Directory term=NONE + hi EndOfBuffer term=NONE + hi ErrorMsg term=bold,reverse + hi FoldColumn term=NONE + hi Folded term=NONE + hi IncSearch term=bold,reverse,underline + hi LineNr term=NONE + hi MatchParen term=bold,underline + hi ModeMsg term=bold + hi MoreMsg term=NONE + hi NonText term=NONE + hi Pmenu term=reverse + hi PmenuSbar term=reverse + hi PmenuSel term=bold + hi PmenuThumb term=NONE + hi Question term=standout + hi Search term=reverse + hi SignColumn term=reverse + hi SpecialKey term=bold + hi SpellBad term=underline + hi SpellCap term=underline + hi SpellLocal term=underline + hi SpellRare term=underline + hi StatusLine term=bold,reverse + hi StatusLineNC term=bold,underline + hi TabLine term=bold,underline + hi TabLineFill term=NONE + hi Terminal term=NONE + hi TabLineSel term=bold,reverse + hi Title term=NONE + hi VertSplit term=NONE + hi Visual term=reverse + hi VisualNOS term=NONE + hi WarningMsg term=standout + hi WildMenu term=bold + hi CursorIM term=NONE + hi ToolbarLine term=reverse + hi ToolbarButton term=bold,reverse + hi CurSearch term=reverse + hi CursorLineFold term=underline + hi CursorLineSign term=underline + hi Comment term=bold + hi Constant term=NONE + hi Error term=bold,reverse + hi Identifier term=NONE + hi Ignore term=NONE + hi PreProc term=NONE + hi Special term=NONE + hi Statement term=NONE + hi Todo term=bold,reverse + hi Type term=NONE + hi Underlined term=underline + unlet s:t_Co + finish +endif + +" Background: any +" Background: dark +" Color: color00 #000000 16 black +" Color: color08 #767676 243 darkgrey +" Color: color01 #AF5F5F 131 darkred +" Color: color09 #FF5F5F 203 red +" Color: color02 #5FAF5F 71 darkgreen +" Color: color10 #5FD75F 77 green +" Color: color03 #AF875F 137 darkyellow +" Color: color11 #FFD787 222 yellow +" Color: color04 #5F87AF 67 darkblue +" Color: color12 #87AFD7 110 blue +" Color: color05 #D787AF 175 darkmagenta +" Color: color13 #FFAFD7 218 magenta +" Color: color06 #5FAFAF 73 darkcyan +" Color: color14 #5FD7D7 116 cyan +" Color: color07 #C6C6C6 251 grey +" Color: color15 #FFFFFF 231 white +" Color: colorDimWhite #EEEEEE 255 grey +" Color: colorLine #262626 235 darkgrey +" Color: colorB #1C1C1C 234 darkgrey +" Color: colorNonT #585858 240 grey +" Color: colorTab #585858 240 grey +" Color: colorC #FFFFFF 231 white +" Color: colorlC #FF5FFF 207 magenta +" Color: colorV #005F87 24 darkblue +" Color: colorMP #C5E7C5 30 darkcyan +" Color: colorPMenuSel #005F00 22 darkgreen +" Color: colorDim #878787 102 grey +" Color: diffAdd #AF87AF 139 darkmagenta +" Color: diffDelete #D78787 174 darkred +" Color: diffChange #D0D0D0 252 lightgray +" Color: diffText #5FD7D7 80 cyan +" Color: fgDiff #000000 16 black +" Term colors: color00 color01 color02 color03 color04 color05 color06 color07 +" Term colors: color08 color09 color10 color11 color12 color13 color14 color15 +" Background: light +" Color: color00 #000000 16 black +" Color: color08 #767676 243 darkgrey +" Color: color01 #870000 88 darkred +" Color: color09 #D70000 160 red +" Color: color02 #008700 28 darkgreen +" Color: color10 #87D787 114 green +" Color: color03 #875F00 94 darkyellow +" Color: color11 #D7D787 186 yellow +" Color: color04 #005FAF 25 darkblue +" Color: color12 #0087D7 32 blue +" Color: color05 #870087 90 darkmagenta +" Color: color13 #AF00AF 133 magenta +" Color: color06 #005F5F 23 darkcyan +" Color: color14 #00AFAF 37 cyan +" Color: color07 #808080 244 grey +" Color: color15 #FFFFFF 231 white +" Color: colorLine #E4E4E4 254 grey +" Color: colorB #EEEEEE 255 grey +" Color: colorNonT #9E9E9E 247 darkgrey +" Color: colorTab #BCBCBC 250 lightgrey +" Color: colorC #000000 16 black +" Color: colorlC #FF00FF 201 magenta +" Color: colorV #5F87AF 67 darkblue +" Color: colorMP #C5E7C5 30 darkcyan +" Color: colorPMenuSel #AFD7AF 151 darkgreen +" Color: colorDim #626262 241 darkgrey +" Color: diffAdd #D7AFD7 182 darkmagenta +" Color: diffDelete #870000 88 darkred +" Color: diffChange #D0D0D0 252 lightgray +" Color: diffText #5FD7D7 80 cyan +" Color: fgDiff #000000 16 black +" Term colors: color00 color01 color02 color03 color04 color05 color06 color07 +" Term colors: color08 color09 color10 color11 color12 color13 color14 color15 +" Background: any +" vim: et ts=2 sw=2 diff --git a/runtime/colors/morning.vim b/runtime/colors/morning.vim index d32f1026f0..6ffecef422 100644 --- a/runtime/colors/morning.vim +++ b/runtime/colors/morning.vim @@ -4,7 +4,7 @@ " Maintainer: Original maintainer Bram Moolenaar <Bram@vim.org> " Website: https://github.com/vim/colorschemes " License: Same as Vim -" Last Updated: 2022-07-26 15:50:07 +" Last Updated: Tue 23 Aug 2022 16:50:41 MSK " Generated by Colortemplate v2.2.0 @@ -13,7 +13,7 @@ set background=light hi clear let g:colors_name = 'morning' -let s:t_Co = exists('&t_Co') && !empty(&t_Co) && &t_Co >= 0 ? &t_Co : -1 +let s:t_Co = exists('&t_Co') && !has('gui_running') ? (&t_Co ? &t_Co : 0) : -1 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'] diff --git a/runtime/colors/murphy.vim b/runtime/colors/murphy.vim index e9f31c2c8b..7b5defd3d0 100644 --- a/runtime/colors/murphy.vim +++ b/runtime/colors/murphy.vim @@ -4,7 +4,7 @@ " Maintainer: Original maintainer Ron Aaron <ron@ronware.org>. " Website: https://github.com/vim/colorschemes " License: Same as Vim -" Last Updated: 2022-07-26 15:50:08 +" Last Updated: Tue 23 Aug 2022 16:50:41 MSK " Generated by Colortemplate v2.2.0 @@ -13,7 +13,7 @@ set background=dark hi clear let g:colors_name = 'murphy' -let s:t_Co = exists('&t_Co') && !empty(&t_Co) && &t_Co >= 0 ? &t_Co : -1 +let s:t_Co = exists('&t_Co') && !has('gui_running') ? (&t_Co ? &t_Co : 0) : -1 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'] diff --git a/runtime/colors/pablo.vim b/runtime/colors/pablo.vim index ee689af25e..eaf6deb22b 100644 --- a/runtime/colors/pablo.vim +++ b/runtime/colors/pablo.vim @@ -3,7 +3,7 @@ " Maintainer: Original maintainerRon Aaron <ron@ronware.org> " Website: https://github.com/vim/colorschemes " License: Same as Vim -" Last Updated: 2022-07-26 15:50:09 +" Last Updated: Tue 23 Aug 2022 16:50:42 MSK " Generated by Colortemplate v2.2.0 @@ -12,7 +12,7 @@ set background=dark hi clear let g:colors_name = 'pablo' -let s:t_Co = exists('&t_Co') && !empty(&t_Co) && &t_Co >= 0 ? &t_Co : -1 +let s:t_Co = exists('&t_Co') && !has('gui_running') ? (&t_Co ? &t_Co : 0) : -1 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'] diff --git a/runtime/colors/peachpuff.vim b/runtime/colors/peachpuff.vim index 2a925b6592..ad47c31e19 100644 --- a/runtime/colors/peachpuff.vim +++ b/runtime/colors/peachpuff.vim @@ -4,7 +4,7 @@ " Maintainer: Original maintainer David Ne\v{c}as (Yeti) <yeti@physics.muni.cz> " Website: https://github.com/vim/colorschemes " License: Same as Vim -" Last Updated: 2022-07-26 15:50:10 +" Last Updated: Tue 23 Aug 2022 16:50:42 MSK " Generated by Colortemplate v2.2.0 @@ -13,7 +13,7 @@ set background=light hi clear let g:colors_name = 'peachpuff' -let s:t_Co = exists('&t_Co') && !empty(&t_Co) && &t_Co >= 0 ? &t_Co : -1 +let s:t_Co = exists('&t_Co') && !has('gui_running') ? (&t_Co ? &t_Co : 0) : -1 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'] diff --git a/runtime/colors/quiet.vim b/runtime/colors/quiet.vim index 2ebe5e628e..9f265726cc 100644 --- a/runtime/colors/quiet.vim +++ b/runtime/colors/quiet.vim @@ -4,15 +4,16 @@ " Maintainer: neutaaaaan <neutaaaaan-gh@protonmail.com> " Website: https://github.com/vim/colorschemes " License: Vim License (see `:help license`)` -" Last Updated: 2022-08-01 15:13:21 +" Last Updated: Tue 23 Aug 2022 16:50:43 MSK " Generated by Colortemplate v2.2.0 hi clear let g:colors_name = 'quiet' -let s:t_Co = exists('&t_Co') && !empty(&t_Co) && &t_Co >= 0 ? &t_Co : -1 +let s:t_Co = exists('&t_Co') && !has('gui_running') ? (&t_Co ? &t_Co : 0) : -1 +hi! link Terminal Normal hi! link StatusLineTerm StatusLine hi! link StatusLineTermNC StatusLineNC hi! link Boolean Constant @@ -48,7 +49,6 @@ if &background ==# 'dark' let g:terminal_ansi_colors = ['#080808', '#d7005f', '#00af5f', '#d78700', '#0087d7', '#d787d7', '#00afaf', '#dadada', '#707070', '#ff005f', '#00d75f', '#ffaf00', '#5fafff', '#ff87ff', '#00d7d7', '#ffffff'] endif hi Normal guifg=#dadada guibg=#080808 gui=NONE cterm=NONE - hi Terminal guifg=#dadada guibg=#080808 gui=NONE cterm=NONE hi ColorColumn guifg=NONE guibg=#1c1c1c gui=NONE cterm=NONE hi Conceal guifg=NONE guibg=NONE gui=NONE ctermfg=NONE ctermbg=NONE cterm=NONE hi Cursor guifg=NONE guibg=NONE gui=reverse ctermfg=NONE ctermbg=NONE cterm=reverse @@ -62,39 +62,39 @@ if &background ==# 'dark' hi Directory guifg=#dadada guibg=#080808 gui=NONE cterm=NONE hi EndOfBuffer guifg=#dadada guibg=#080808 gui=NONE cterm=NONE hi ErrorMsg guifg=#dadada guibg=#080808 gui=reverse cterm=reverse - hi FoldColumn guifg=#707070 guibg=#080808 gui=NONE cterm=NONE + hi FoldColumn guifg=#707070 guibg=NONE gui=NONE cterm=NONE hi Folded guifg=#707070 guibg=#080808 gui=NONE cterm=NONE hi IncSearch guifg=#ffaf00 guibg=#080808 gui=reverse cterm=reverse - hi LineNr guifg=#444444 guibg=#080808 gui=NONE cterm=NONE - hi MatchParen guifg=#ff00af guibg=#080808 gui=bold cterm=bold - hi ModeMsg guifg=#dadada guibg=#080808 gui=bold cterm=bold - hi MoreMsg guifg=#dadada guibg=#080808 gui=NONE cterm=NONE + hi LineNr guifg=#444444 guibg=NONE gui=NONE cterm=NONE + hi MatchParen guifg=#ff00af guibg=NONE gui=bold cterm=bold + hi ModeMsg guifg=#dadada guibg=NONE gui=bold cterm=bold + hi MoreMsg guifg=#dadada guibg=NONE gui=NONE cterm=NONE hi NonText guifg=#707070 guibg=NONE gui=NONE cterm=NONE hi Pmenu guifg=#080808 guibg=#87afd7 gui=NONE cterm=NONE hi PmenuSbar guifg=#dadada guibg=#707070 gui=NONE cterm=NONE hi PmenuSel guifg=#080808 guibg=#d787d7 gui=NONE cterm=NONE hi PmenuThumb guifg=#dadada guibg=#d787d7 gui=NONE cterm=NONE - hi Question guifg=#dadada guibg=#080808 gui=NONE cterm=NONE + hi Question guifg=#dadada guibg=NONE gui=NONE cterm=NONE hi QuickFixLine guifg=#d787d7 guibg=#080808 gui=reverse cterm=reverse hi Search guifg=#00afff guibg=#080808 gui=reverse cterm=reverse - hi SignColumn guifg=#dadada guibg=#080808 gui=NONE cterm=NONE - hi SpecialKey guifg=#dadada guibg=#080808 gui=NONE cterm=NONE - hi SpellBad guifg=#d7005f guibg=#080808 guisp=#d7005f gui=undercurl cterm=underline - hi SpellCap guifg=#0087d7 guibg=#080808 guisp=#0087d7 gui=undercurl cterm=underline - hi SpellLocal guifg=#d787d7 guibg=#080808 guisp=#d787d7 gui=undercurl cterm=underline - hi SpellRare guifg=#00afaf guibg=#080808 guisp=#00afaf gui=undercurl cterm=underline + hi SignColumn guifg=#dadada guibg=NONE gui=NONE cterm=NONE + hi SpecialKey guifg=#dadada guibg=NONE gui=NONE cterm=NONE + hi SpellBad guifg=#d7005f guibg=NONE guisp=#d7005f gui=undercurl cterm=underline + hi SpellCap guifg=#0087d7 guibg=NONE guisp=#0087d7 gui=undercurl cterm=underline + hi SpellLocal guifg=#d787d7 guibg=NONE guisp=#d787d7 gui=undercurl cterm=underline + hi SpellRare guifg=#00afaf guibg=NONE guisp=#00afaf gui=undercurl cterm=underline hi StatusLine guifg=#080808 guibg=#dadada gui=bold cterm=bold hi StatusLineNC guifg=#707070 guibg=#080808 gui=underline cterm=underline hi TabLine guifg=#707070 guibg=#080808 gui=underline cterm=underline hi TabLineFill guifg=#dadada guibg=NONE gui=NONE cterm=NONE hi TabLineSel guifg=#080808 guibg=#dadada gui=bold cterm=bold - hi Title guifg=#dadada guibg=#080808 gui=NONE cterm=NONE + hi Title guifg=#dadada guibg=NONE gui=NONE cterm=NONE hi VertSplit guifg=#707070 guibg=#080808 gui=NONE cterm=NONE hi Visual guifg=NONE guibg=NONE gui=reverse ctermfg=NONE ctermbg=NONE cterm=reverse hi VisualNOS guifg=NONE guibg=#303030 gui=NONE cterm=NONE - hi WarningMsg guifg=#dadada guibg=#080808 gui=NONE cterm=NONE + hi WarningMsg guifg=#dadada guibg=NONE gui=NONE cterm=NONE hi WildMenu guifg=#00afff guibg=#080808 gui=bold cterm=bold - hi Comment guifg=#707070 guibg=#080808 gui=bold cterm=bold + hi Comment guifg=#707070 guibg=NONE gui=bold cterm=bold hi Constant guifg=#dadada guibg=NONE gui=NONE cterm=NONE hi Error guifg=#ff005f guibg=#080808 gui=bold,reverse cterm=bold,reverse hi Identifier guifg=#dadada guibg=NONE gui=NONE cterm=NONE @@ -114,7 +114,6 @@ else let g:terminal_ansi_colors = ['#080808', '#af0000', '#005f00', '#af5f00', '#005faf', '#870087', '#008787', '#d7d7d7', '#626262', '#d70000', '#008700', '#d78700', '#0087d7', '#af00af', '#00afaf', '#ffffff'] endif hi Normal guifg=#080808 guibg=#d7d7d7 gui=NONE cterm=NONE - hi Terminal guifg=#080808 guibg=#d7d7d7 gui=NONE cterm=NONE hi ColorColumn guifg=NONE guibg=#e4e4e4 gui=NONE cterm=NONE hi Conceal guifg=NONE guibg=NONE gui=NONE ctermfg=NONE ctermbg=NONE cterm=NONE hi Cursor guifg=NONE guibg=NONE gui=reverse ctermfg=NONE ctermbg=NONE cterm=reverse @@ -125,26 +124,26 @@ else hi DiffChange guifg=#afafd7 guibg=#080808 gui=reverse cterm=reverse hi DiffDelete guifg=#d78787 guibg=#080808 gui=reverse cterm=reverse hi DiffText guifg=#d787d7 guibg=#080808 gui=reverse cterm=reverse - hi Directory guifg=#080808 guibg=#d7d7d7 gui=NONE cterm=NONE - hi EndOfBuffer guifg=#080808 guibg=#d7d7d7 gui=NONE cterm=NONE + hi Directory guifg=#080808 guibg=NONE gui=NONE cterm=NONE + hi EndOfBuffer guifg=#080808 guibg=NONE gui=NONE cterm=NONE hi ErrorMsg guifg=#080808 guibg=#d7d7d7 gui=reverse cterm=reverse - hi FoldColumn guifg=#626262 guibg=#d7d7d7 gui=NONE cterm=NONE + hi FoldColumn guifg=#626262 guibg=NONE gui=NONE cterm=NONE hi Folded guifg=#626262 guibg=#d7d7d7 gui=NONE cterm=NONE hi IncSearch guifg=#ffaf00 guibg=#080808 gui=reverse cterm=reverse - hi LineNr guifg=#a8a8a8 guibg=#d7d7d7 gui=NONE cterm=NONE + hi LineNr guifg=#a8a8a8 guibg=NONE gui=NONE cterm=NONE hi MatchParen guifg=#ff00af guibg=#d7d7d7 gui=bold cterm=bold - hi ModeMsg guifg=#080808 guibg=#d7d7d7 gui=bold cterm=bold - hi MoreMsg guifg=#080808 guibg=#d7d7d7 gui=NONE cterm=NONE + hi ModeMsg guifg=#080808 guibg=NONE gui=bold cterm=bold + hi MoreMsg guifg=#080808 guibg=NONE gui=NONE cterm=NONE hi NonText guifg=#626262 guibg=NONE gui=NONE cterm=NONE hi Pmenu guifg=#080808 guibg=#afafd7 gui=NONE cterm=NONE hi PmenuSbar guifg=#080808 guibg=#626262 gui=NONE cterm=NONE hi PmenuSel guifg=#080808 guibg=#d787d7 gui=NONE cterm=NONE hi PmenuThumb guifg=#080808 guibg=#d787d7 gui=NONE cterm=NONE - hi Question guifg=#080808 guibg=#d7d7d7 gui=NONE cterm=NONE + hi Question guifg=#080808 guibg=NONE gui=NONE cterm=NONE hi QuickFixLine guifg=#d787d7 guibg=#080808 gui=reverse cterm=reverse hi Search guifg=#00afff guibg=#080808 gui=reverse cterm=reverse - hi SignColumn guifg=#080808 guibg=#d7d7d7 gui=NONE cterm=NONE - hi SpecialKey guifg=#080808 guibg=#d7d7d7 gui=NONE cterm=NONE + hi SignColumn guifg=#080808 guibg=NONE gui=NONE cterm=NONE + hi SpecialKey guifg=#080808 guibg=NONE gui=NONE cterm=NONE hi SpellBad guifg=#af0000 guibg=#d7d7d7 guisp=#af0000 gui=undercurl cterm=underline hi SpellCap guifg=#005faf guibg=#d7d7d7 guisp=#005faf gui=undercurl cterm=underline hi SpellLocal guifg=#870087 guibg=#d7d7d7 guisp=#870087 gui=undercurl cterm=underline @@ -154,13 +153,13 @@ else hi TabLine guifg=#080808 guibg=#a8a8a8 gui=NONE cterm=NONE hi TabLineFill guifg=#080808 guibg=#d7d7d7 gui=NONE cterm=NONE hi TabLineSel guifg=#eeeeee guibg=#080808 gui=bold cterm=bold - hi Title guifg=#080808 guibg=#d7d7d7 gui=NONE cterm=NONE + hi Title guifg=#080808 guibg=NONE gui=NONE cterm=NONE hi VertSplit guifg=#626262 guibg=#d7d7d7 gui=NONE cterm=NONE hi Visual guifg=NONE guibg=NONE gui=reverse ctermfg=NONE ctermbg=NONE cterm=reverse hi VisualNOS guifg=NONE guibg=#eeeeee gui=NONE cterm=NONE - hi WarningMsg guifg=#080808 guibg=#d7d7d7 gui=NONE cterm=NONE + hi WarningMsg guifg=#080808 guibg=NONE gui=NONE cterm=NONE hi WildMenu guifg=#080808 guibg=#eeeeee gui=bold cterm=bold - hi Comment guifg=#080808 guibg=#d7d7d7 gui=bold cterm=bold + hi Comment guifg=#080808 guibg=NONE gui=bold cterm=bold hi Constant guifg=#080808 guibg=NONE gui=NONE cterm=NONE hi Error guifg=#ff005f guibg=#080808 gui=bold,reverse cterm=bold,reverse hi Identifier guifg=#080808 guibg=NONE gui=NONE cterm=NONE @@ -179,7 +178,6 @@ endif if s:t_Co >= 256 if &background ==# 'dark' hi Normal ctermfg=253 ctermbg=232 cterm=NONE - hi Terminal ctermfg=253 ctermbg=232 cterm=NONE hi ColorColumn ctermfg=NONE ctermbg=234 cterm=NONE hi Conceal ctermfg=NONE ctermbg=NONE cterm=NONE hi Cursor ctermfg=NONE ctermbg=NONE cterm=reverse @@ -193,39 +191,39 @@ if s:t_Co >= 256 hi Directory ctermfg=253 ctermbg=232 cterm=NONE hi EndOfBuffer ctermfg=253 ctermbg=232 cterm=NONE hi ErrorMsg ctermfg=253 ctermbg=232 cterm=reverse - hi FoldColumn ctermfg=242 ctermbg=232 cterm=NONE + hi FoldColumn ctermfg=242 ctermbg=NONE cterm=NONE hi Folded ctermfg=242 ctermbg=232 cterm=NONE hi IncSearch ctermfg=214 ctermbg=232 cterm=reverse - hi LineNr ctermfg=238 ctermbg=232 cterm=NONE - hi MatchParen ctermfg=199 ctermbg=232 cterm=bold - hi ModeMsg ctermfg=253 ctermbg=232 cterm=bold - hi MoreMsg ctermfg=253 ctermbg=232 cterm=NONE + hi LineNr ctermfg=238 ctermbg=NONE cterm=NONE + hi MatchParen ctermfg=199 ctermbg=NONE cterm=bold + hi ModeMsg ctermfg=253 ctermbg=NONE cterm=bold + hi MoreMsg ctermfg=253 ctermbg=NONE cterm=NONE hi NonText ctermfg=242 ctermbg=NONE cterm=NONE hi Pmenu ctermfg=232 ctermbg=110 cterm=NONE hi PmenuSbar ctermfg=253 ctermbg=242 cterm=NONE hi PmenuSel ctermfg=232 ctermbg=176 cterm=NONE hi PmenuThumb ctermfg=253 ctermbg=176 cterm=NONE - hi Question ctermfg=253 ctermbg=232 cterm=NONE + hi Question ctermfg=253 ctermbg=NONE cterm=NONE hi QuickFixLine ctermfg=176 ctermbg=232 cterm=reverse hi Search ctermfg=39 ctermbg=232 cterm=reverse - hi SignColumn ctermfg=253 ctermbg=232 cterm=NONE - hi SpecialKey ctermfg=253 ctermbg=232 cterm=NONE - hi SpellBad ctermfg=161 ctermbg=232 cterm=underline - hi SpellCap ctermfg=32 ctermbg=232 cterm=underline - hi SpellLocal ctermfg=176 ctermbg=232 cterm=underline - hi SpellRare ctermfg=37 ctermbg=232 cterm=underline + hi SignColumn ctermfg=253 ctermbg=NONE cterm=NONE + hi SpecialKey ctermfg=253 ctermbg=NONE cterm=NONE + hi SpellBad ctermfg=161 ctermbg=NONE cterm=underline + hi SpellCap ctermfg=32 ctermbg=NONE cterm=underline + hi SpellLocal ctermfg=176 ctermbg=NONE cterm=underline + hi SpellRare ctermfg=37 ctermbg=NONE cterm=underline hi StatusLine ctermfg=232 ctermbg=253 cterm=bold hi StatusLineNC ctermfg=242 ctermbg=232 cterm=underline hi TabLine ctermfg=242 ctermbg=232 cterm=underline hi TabLineFill ctermfg=253 ctermbg=NONE cterm=NONE hi TabLineSel ctermfg=232 ctermbg=253 cterm=bold - hi Title ctermfg=253 ctermbg=232 cterm=NONE + hi Title ctermfg=253 ctermbg=NONE cterm=NONE hi VertSplit ctermfg=242 ctermbg=232 cterm=NONE hi Visual ctermfg=NONE ctermbg=NONE cterm=reverse hi VisualNOS ctermfg=NONE ctermbg=236 cterm=NONE - hi WarningMsg ctermfg=253 ctermbg=232 cterm=NONE + hi WarningMsg ctermfg=253 ctermbg=NONE cterm=NONE hi WildMenu ctermfg=39 ctermbg=232 cterm=bold - hi Comment ctermfg=242 ctermbg=232 cterm=bold + hi Comment ctermfg=242 ctermbg=NONE cterm=bold hi Constant ctermfg=253 ctermbg=NONE cterm=NONE hi Error ctermfg=197 ctermbg=232 cterm=bold,reverse hi Identifier ctermfg=253 ctermbg=NONE cterm=NONE @@ -242,7 +240,6 @@ if s:t_Co >= 256 else " Light background hi Normal ctermfg=232 ctermbg=188 cterm=NONE - hi Terminal ctermfg=232 ctermbg=188 cterm=NONE hi ColorColumn ctermfg=NONE ctermbg=254 cterm=NONE hi Conceal ctermfg=NONE ctermbg=NONE cterm=NONE hi Cursor ctermfg=NONE ctermbg=NONE cterm=reverse @@ -253,26 +250,26 @@ if s:t_Co >= 256 hi DiffChange ctermfg=146 ctermbg=232 cterm=reverse hi DiffDelete ctermfg=174 ctermbg=232 cterm=reverse hi DiffText ctermfg=176 ctermbg=232 cterm=reverse - hi Directory ctermfg=232 ctermbg=188 cterm=NONE - hi EndOfBuffer ctermfg=232 ctermbg=188 cterm=NONE + hi Directory ctermfg=232 ctermbg=NONE cterm=NONE + hi EndOfBuffer ctermfg=232 ctermbg=NONE cterm=NONE hi ErrorMsg ctermfg=232 ctermbg=188 cterm=reverse - hi FoldColumn ctermfg=241 ctermbg=188 cterm=NONE + hi FoldColumn ctermfg=241 ctermbg=NONE cterm=NONE hi Folded ctermfg=241 ctermbg=188 cterm=NONE hi IncSearch ctermfg=214 ctermbg=232 cterm=reverse - hi LineNr ctermfg=248 ctermbg=188 cterm=NONE + hi LineNr ctermfg=248 ctermbg=NONE cterm=NONE hi MatchParen ctermfg=199 ctermbg=188 cterm=bold - hi ModeMsg ctermfg=232 ctermbg=188 cterm=bold - hi MoreMsg ctermfg=232 ctermbg=188 cterm=NONE + hi ModeMsg ctermfg=232 ctermbg=NONE cterm=bold + hi MoreMsg ctermfg=232 ctermbg=NONE cterm=NONE hi NonText ctermfg=241 ctermbg=NONE cterm=NONE hi Pmenu ctermfg=232 ctermbg=146 cterm=NONE hi PmenuSbar ctermfg=232 ctermbg=241 cterm=NONE hi PmenuSel ctermfg=232 ctermbg=176 cterm=NONE hi PmenuThumb ctermfg=232 ctermbg=176 cterm=NONE - hi Question ctermfg=232 ctermbg=188 cterm=NONE + hi Question ctermfg=232 ctermbg=NONE cterm=NONE hi QuickFixLine ctermfg=176 ctermbg=232 cterm=reverse hi Search ctermfg=39 ctermbg=232 cterm=reverse - hi SignColumn ctermfg=232 ctermbg=188 cterm=NONE - hi SpecialKey ctermfg=232 ctermbg=188 cterm=NONE + hi SignColumn ctermfg=232 ctermbg=NONE cterm=NONE + hi SpecialKey ctermfg=232 ctermbg=NONE cterm=NONE hi SpellBad ctermfg=124 ctermbg=188 cterm=underline hi SpellCap ctermfg=25 ctermbg=188 cterm=underline hi SpellLocal ctermfg=90 ctermbg=188 cterm=underline @@ -282,13 +279,13 @@ if s:t_Co >= 256 hi TabLine ctermfg=232 ctermbg=248 cterm=NONE hi TabLineFill ctermfg=232 ctermbg=188 cterm=NONE hi TabLineSel ctermfg=255 ctermbg=232 cterm=bold - hi Title ctermfg=232 ctermbg=188 cterm=NONE + hi Title ctermfg=232 ctermbg=NONE cterm=NONE hi VertSplit ctermfg=241 ctermbg=188 cterm=NONE hi Visual ctermfg=NONE ctermbg=NONE cterm=reverse hi VisualNOS ctermfg=NONE ctermbg=255 cterm=NONE - hi WarningMsg ctermfg=232 ctermbg=188 cterm=NONE + hi WarningMsg ctermfg=232 ctermbg=NONE cterm=NONE hi WildMenu ctermfg=232 ctermbg=255 cterm=bold - hi Comment ctermfg=232 ctermbg=188 cterm=bold + hi Comment ctermfg=232 ctermbg=NONE cterm=bold hi Constant ctermfg=232 ctermbg=NONE cterm=NONE hi Error ctermfg=197 ctermbg=232 cterm=bold,reverse hi Identifier ctermfg=232 ctermbg=NONE cterm=NONE @@ -310,23 +307,22 @@ endif if s:t_Co >= 16 if &background ==# 'dark' hi Normal ctermfg=NONE ctermbg=NONE cterm=NONE - hi Terminal ctermfg=NONE ctermbg=NONE cterm=NONE hi ColorColumn ctermfg=NONE ctermbg=NONE cterm=reverse hi Conceal ctermfg=NONE ctermbg=NONE cterm=NONE hi Cursor ctermfg=NONE ctermbg=NONE cterm=reverse hi CursorColumn ctermfg=NONE ctermbg=NONE cterm=NONE hi CursorLine ctermfg=NONE ctermbg=NONE cterm=NONE hi CursorLineNr ctermfg=NONE ctermbg=NONE cterm=bold - hi DiffAdd ctermfg=2 ctermbg=0 cterm=reverse - hi DiffChange ctermfg=4 ctermbg=0 cterm=reverse - hi DiffDelete ctermfg=1 ctermbg=0 cterm=reverse - hi DiffText ctermfg=5 ctermbg=0 cterm=reverse + hi DiffAdd ctermfg=darkgreen ctermbg=black cterm=reverse + hi DiffChange ctermfg=darkblue ctermbg=black cterm=reverse + hi DiffDelete ctermfg=darkred ctermbg=black cterm=reverse + hi DiffText ctermfg=darkmagenta ctermbg=black cterm=reverse hi Directory ctermfg=NONE ctermbg=NONE cterm=NONE hi EndOfBuffer ctermfg=NONE ctermbg=NONE cterm=NONE hi ErrorMsg ctermfg=NONE ctermbg=NONE cterm=bold,reverse hi FoldColumn ctermfg=NONE ctermbg=NONE cterm=NONE hi Folded ctermfg=NONE ctermbg=NONE cterm=NONE - hi IncSearch ctermfg=3 ctermbg=0 cterm=bold,reverse,underline + hi IncSearch ctermfg=darkyellow ctermbg=black cterm=bold,reverse,underline hi LineNr ctermfg=NONE ctermbg=NONE cterm=NONE hi MatchParen ctermfg=NONE ctermbg=NONE cterm=bold,underline hi ModeMsg ctermfg=NONE ctermbg=NONE cterm=bold @@ -337,14 +333,14 @@ if s:t_Co >= 16 hi PmenuSel ctermfg=NONE ctermbg=NONE cterm=bold hi PmenuThumb ctermfg=NONE ctermbg=NONE cterm=NONE hi Question ctermfg=NONE ctermbg=NONE cterm=standout - hi QuickFixLine ctermfg=5 ctermbg=0 cterm=reverse - hi Search ctermfg=6 ctermbg=0 cterm=reverse + hi QuickFixLine ctermfg=darkmagenta ctermbg=black cterm=reverse + hi Search ctermfg=darkcyan ctermbg=black cterm=reverse hi SignColumn ctermfg=NONE ctermbg=NONE cterm=reverse hi SpecialKey ctermfg=NONE ctermbg=NONE cterm=bold - hi SpellBad ctermfg=1 ctermbg=NONE cterm=underline - hi SpellCap ctermfg=4 ctermbg=NONE cterm=underline - hi SpellLocal ctermfg=5 ctermbg=NONE cterm=underline - hi SpellRare ctermfg=6 ctermbg=NONE cterm=underline + hi SpellBad ctermfg=darkred ctermbg=NONE cterm=underline + hi SpellCap ctermfg=darkblue ctermbg=NONE cterm=underline + hi SpellLocal ctermfg=darkmagenta ctermbg=NONE cterm=underline + hi SpellRare ctermfg=darkcyan ctermbg=NONE cterm=underline hi StatusLine ctermfg=NONE ctermbg=NONE cterm=bold,reverse hi StatusLineNC ctermfg=NONE ctermbg=NONE cterm=bold,underline hi TabLine ctermfg=NONE ctermbg=NONE cterm=bold,underline @@ -373,23 +369,22 @@ if s:t_Co >= 16 else " Light background hi Normal ctermfg=NONE ctermbg=NONE cterm=NONE - hi Terminal ctermfg=NONE ctermbg=NONE cterm=NONE hi ColorColumn ctermfg=NONE ctermbg=NONE cterm=reverse hi Conceal ctermfg=NONE ctermbg=NONE cterm=NONE hi Cursor ctermfg=NONE ctermbg=NONE cterm=reverse hi CursorColumn ctermfg=NONE ctermbg=NONE cterm=NONE hi CursorLine ctermfg=NONE ctermbg=NONE cterm=NONE hi CursorLineNr ctermfg=NONE ctermbg=NONE cterm=bold - hi DiffAdd ctermfg=2 ctermbg=0 cterm=reverse - hi DiffChange ctermfg=4 ctermbg=0 cterm=reverse - hi DiffDelete ctermfg=1 ctermbg=0 cterm=reverse - hi DiffText ctermfg=5 ctermbg=0 cterm=reverse + hi DiffAdd ctermfg=darkgreen ctermbg=black cterm=reverse + hi DiffChange ctermfg=darkblue ctermbg=black cterm=reverse + hi DiffDelete ctermfg=darkred ctermbg=black cterm=reverse + hi DiffText ctermfg=darkmagenta ctermbg=black cterm=reverse hi Directory ctermfg=NONE ctermbg=NONE cterm=NONE hi EndOfBuffer ctermfg=NONE ctermbg=NONE cterm=NONE hi ErrorMsg ctermfg=NONE ctermbg=NONE cterm=bold,reverse hi FoldColumn ctermfg=NONE ctermbg=NONE cterm=NONE hi Folded ctermfg=NONE ctermbg=NONE cterm=NONE - hi IncSearch ctermfg=3 ctermbg=0 cterm=bold,reverse,underline + hi IncSearch ctermfg=darkyellow ctermbg=black cterm=bold,reverse,underline hi LineNr ctermfg=NONE ctermbg=NONE cterm=NONE hi MatchParen ctermfg=NONE ctermbg=NONE cterm=bold,underline hi ModeMsg ctermfg=NONE ctermbg=NONE cterm=bold @@ -400,14 +395,14 @@ if s:t_Co >= 16 hi PmenuSel ctermfg=NONE ctermbg=NONE cterm=bold hi PmenuThumb ctermfg=NONE ctermbg=NONE cterm=NONE hi Question ctermfg=NONE ctermbg=NONE cterm=standout - hi QuickFixLine ctermfg=5 ctermbg=0 cterm=reverse - hi Search ctermfg=6 ctermbg=0 cterm=reverse + hi QuickFixLine ctermfg=darkmagenta ctermbg=black cterm=reverse + hi Search ctermfg=darkcyan ctermbg=black cterm=reverse hi SignColumn ctermfg=NONE ctermbg=NONE cterm=reverse hi SpecialKey ctermfg=NONE ctermbg=NONE cterm=bold - hi SpellBad ctermfg=1 ctermbg=NONE cterm=underline - hi SpellCap ctermfg=4 ctermbg=NONE cterm=underline - hi SpellLocal ctermfg=5 ctermbg=NONE cterm=underline - hi SpellRare ctermfg=6 ctermbg=NONE cterm=underline + hi SpellBad ctermfg=darkred ctermbg=NONE cterm=underline + hi SpellCap ctermfg=darkblue ctermbg=NONE cterm=underline + hi SpellLocal ctermfg=darkmagenta ctermbg=NONE cterm=underline + hi SpellRare ctermfg=darkcyan ctermbg=NONE cterm=underline hi StatusLine ctermfg=NONE ctermbg=NONE cterm=bold,reverse hi StatusLineNC ctermfg=NONE ctermbg=NONE cterm=bold,underline hi TabLine ctermfg=NONE ctermbg=NONE cterm=bold,underline @@ -441,23 +436,22 @@ endif if s:t_Co >= 8 if &background ==# 'dark' hi Normal ctermfg=NONE ctermbg=NONE cterm=NONE - hi Terminal ctermfg=NONE ctermbg=NONE cterm=NONE hi ColorColumn ctermfg=NONE ctermbg=NONE cterm=reverse hi Conceal ctermfg=NONE ctermbg=NONE cterm=NONE hi Cursor ctermfg=NONE ctermbg=NONE cterm=reverse hi CursorColumn ctermfg=NONE ctermbg=NONE cterm=NONE hi CursorLine ctermfg=NONE ctermbg=NONE cterm=NONE hi CursorLineNr ctermfg=NONE ctermbg=NONE cterm=bold - hi DiffAdd ctermfg=2 ctermbg=0 cterm=reverse - hi DiffChange ctermfg=4 ctermbg=0 cterm=reverse - hi DiffDelete ctermfg=1 ctermbg=0 cterm=reverse - hi DiffText ctermfg=5 ctermbg=0 cterm=reverse + hi DiffAdd ctermfg=darkgreen ctermbg=black cterm=reverse + hi DiffChange ctermfg=darkblue ctermbg=black cterm=reverse + hi DiffDelete ctermfg=darkred ctermbg=black cterm=reverse + hi DiffText ctermfg=darkmagenta ctermbg=black cterm=reverse hi Directory ctermfg=NONE ctermbg=NONE cterm=NONE hi EndOfBuffer ctermfg=NONE ctermbg=NONE cterm=NONE hi ErrorMsg ctermfg=NONE ctermbg=NONE cterm=bold,reverse hi FoldColumn ctermfg=NONE ctermbg=NONE cterm=NONE hi Folded ctermfg=NONE ctermbg=NONE cterm=NONE - hi IncSearch ctermfg=3 ctermbg=0 cterm=bold,reverse,underline + hi IncSearch ctermfg=darkyellow ctermbg=black cterm=bold,reverse,underline hi LineNr ctermfg=NONE ctermbg=NONE cterm=NONE hi MatchParen ctermfg=NONE ctermbg=NONE cterm=bold,underline hi ModeMsg ctermfg=NONE ctermbg=NONE cterm=bold @@ -468,14 +462,14 @@ if s:t_Co >= 8 hi PmenuSel ctermfg=NONE ctermbg=NONE cterm=bold hi PmenuThumb ctermfg=NONE ctermbg=NONE cterm=NONE hi Question ctermfg=NONE ctermbg=NONE cterm=standout - hi QuickFixLine ctermfg=5 ctermbg=0 cterm=reverse - hi Search ctermfg=6 ctermbg=0 cterm=reverse + hi QuickFixLine ctermfg=darkmagenta ctermbg=black cterm=reverse + hi Search ctermfg=darkcyan ctermbg=black cterm=reverse hi SignColumn ctermfg=NONE ctermbg=NONE cterm=reverse hi SpecialKey ctermfg=NONE ctermbg=NONE cterm=bold - hi SpellBad ctermfg=1 ctermbg=NONE cterm=underline - hi SpellCap ctermfg=4 ctermbg=NONE cterm=underline - hi SpellLocal ctermfg=5 ctermbg=NONE cterm=underline - hi SpellRare ctermfg=6 ctermbg=NONE cterm=underline + hi SpellBad ctermfg=darkred ctermbg=NONE cterm=underline + hi SpellCap ctermfg=darkblue ctermbg=NONE cterm=underline + hi SpellLocal ctermfg=darkmagenta ctermbg=NONE cterm=underline + hi SpellRare ctermfg=darkcyan ctermbg=NONE cterm=underline hi StatusLine ctermfg=NONE ctermbg=NONE cterm=bold,reverse hi StatusLineNC ctermfg=NONE ctermbg=NONE cterm=bold,underline hi TabLine ctermfg=NONE ctermbg=NONE cterm=bold,underline @@ -504,23 +498,22 @@ if s:t_Co >= 8 else " Light background hi Normal ctermfg=NONE ctermbg=NONE cterm=NONE - hi Terminal ctermfg=NONE ctermbg=NONE cterm=NONE hi ColorColumn ctermfg=NONE ctermbg=NONE cterm=reverse hi Conceal ctermfg=NONE ctermbg=NONE cterm=NONE hi Cursor ctermfg=NONE ctermbg=NONE cterm=reverse hi CursorColumn ctermfg=NONE ctermbg=NONE cterm=NONE hi CursorLine ctermfg=NONE ctermbg=NONE cterm=NONE hi CursorLineNr ctermfg=NONE ctermbg=NONE cterm=bold - hi DiffAdd ctermfg=2 ctermbg=0 cterm=reverse - hi DiffChange ctermfg=4 ctermbg=0 cterm=reverse - hi DiffDelete ctermfg=1 ctermbg=0 cterm=reverse - hi DiffText ctermfg=5 ctermbg=0 cterm=reverse + hi DiffAdd ctermfg=darkgreen ctermbg=black cterm=reverse + hi DiffChange ctermfg=darkblue ctermbg=black cterm=reverse + hi DiffDelete ctermfg=darkred ctermbg=black cterm=reverse + hi DiffText ctermfg=darkmagenta ctermbg=black cterm=reverse hi Directory ctermfg=NONE ctermbg=NONE cterm=NONE hi EndOfBuffer ctermfg=NONE ctermbg=NONE cterm=NONE hi ErrorMsg ctermfg=NONE ctermbg=NONE cterm=bold,reverse hi FoldColumn ctermfg=NONE ctermbg=NONE cterm=NONE hi Folded ctermfg=NONE ctermbg=NONE cterm=NONE - hi IncSearch ctermfg=3 ctermbg=0 cterm=bold,reverse,underline + hi IncSearch ctermfg=darkyellow ctermbg=black cterm=bold,reverse,underline hi LineNr ctermfg=NONE ctermbg=NONE cterm=NONE hi MatchParen ctermfg=NONE ctermbg=NONE cterm=bold,underline hi ModeMsg ctermfg=NONE ctermbg=NONE cterm=bold @@ -531,14 +524,14 @@ if s:t_Co >= 8 hi PmenuSel ctermfg=NONE ctermbg=NONE cterm=bold hi PmenuThumb ctermfg=NONE ctermbg=NONE cterm=NONE hi Question ctermfg=NONE ctermbg=NONE cterm=standout - hi QuickFixLine ctermfg=5 ctermbg=0 cterm=reverse - hi Search ctermfg=6 ctermbg=0 cterm=reverse + hi QuickFixLine ctermfg=darkmagenta ctermbg=black cterm=reverse + hi Search ctermfg=darkcyan ctermbg=black cterm=reverse hi SignColumn ctermfg=NONE ctermbg=NONE cterm=reverse hi SpecialKey ctermfg=NONE ctermbg=NONE cterm=bold - hi SpellBad ctermfg=1 ctermbg=NONE cterm=underline - hi SpellCap ctermfg=4 ctermbg=NONE cterm=underline - hi SpellLocal ctermfg=5 ctermbg=NONE cterm=underline - hi SpellRare ctermfg=6 ctermbg=NONE cterm=underline + hi SpellBad ctermfg=darkred ctermbg=NONE cterm=underline + hi SpellCap ctermfg=darkblue ctermbg=NONE cterm=underline + hi SpellLocal ctermfg=darkmagenta ctermbg=NONE cterm=underline + hi SpellRare ctermfg=darkcyan ctermbg=NONE cterm=underline hi StatusLine ctermfg=NONE ctermbg=NONE cterm=bold,reverse hi StatusLineNC ctermfg=NONE ctermbg=NONE cterm=bold,underline hi TabLine ctermfg=NONE ctermbg=NONE cterm=bold,underline @@ -638,69 +631,69 @@ if s:t_Co >= 0 endif " Background: dark -" Color: dark0 #080808 ~ 0 -" Color: dark1 #d7005f ~ 1 -" Color: dark2 #00af5f ~ 2 -" Color: dark3 #d78700 ~ 3 -" Color: dark4 #0087d7 ~ 4 -" Color: dark5 #d787d7 ~ 5 -" Color: dark6 #00afaf ~ 6 -" Color: dark7 #dadada ~ 7 -" Color: dark8 #707070 ~ 8 -" Color: dark9 #ff005f ~ 9 -" Color: dark10 #00d75f ~ 10 -" Color: dark11 #ffaf00 ~ 11 -" Color: dark12 #5fafff ~ 12 -" Color: dark13 #ff87ff ~ 13 -" Color: dark14 #00d7d7 ~ 14 -" Color: dark15 #ffffff ~ 15 -" Color: diffred #d75f5f ~ -" Color: diffgreen #00af00 ~ -" Color: diffblue #87afd7 ~ -" Color: diffpink #d787d7 ~ -" Color: uipink #ff00af ~ -" Color: uilime #afff00 ~ -" Color: uiteal #00ffaf ~ -" Color: uiblue #00afff ~ -" Color: uipurple #af00ff ~ -" Color: uiamber #ffaf00 ~ -" Color: uiblack #303030 ~ -" Color: yasogrey #1c1c1c ~ -" Color: linenrblack #444444 ~ -" Color: errorred #ff005f ~ +" Color: dark0 #080808 232 black +" Color: dark1 #d7005f 161 darkred +" Color: dark2 #00af5f 35 darkgreen +" Color: dark3 #d78700 172 darkyellow +" Color: dark4 #0087d7 32 darkblue +" Color: dark5 #d787d7 176 darkmagenta +" Color: dark6 #00afaf 37 darkcyan +" Color: dark7 #dadada 253 grey +" Color: dark8 #707070 242 darkgrey +" Color: dark9 #ff005f 197 red +" Color: dark10 #00d75f 41 green +" Color: dark11 #ffaf00 214 yellow +" Color: dark12 #5fafff 75 blue +" Color: dark13 #ff87ff 213 magenta +" Color: dark14 #00d7d7 44 cyan +" Color: dark15 #ffffff 231 white +" Color: diffred #d75f5f 167 darkred +" Color: diffgreen #00af00 34 darkgreen +" Color: diffblue #87afd7 110 darkblue +" Color: diffpink #d787d7 176 darkmagenta +" Color: uipink #ff00af 199 magenta +" Color: uilime #afff00 154 green +" Color: uiteal #00ffaf 49 green +" Color: uiblue #00afff 39 blue +" Color: uipurple #af00ff 129 darkmagenta +" Color: uiamber #ffaf00 214 darkyellow +" Color: uiblack #303030 236 darkgrey +" Color: yasogrey #1c1c1c 234 black +" Color: linenrblack #444444 238 darkgrey +" Color: errorred #ff005f 197 red " Term colors: dark0 dark1 dark2 dark3 dark4 dark5 dark6 dark7 " Term colors: dark8 dark9 dark10 dark11 dark12 dark13 dark14 dark15 " Background: light -" Color: brightwhite #eeeeee ~ -" Color: light0 #080808 ~ 0 -" Color: light1 #af0000 ~ 1 -" Color: light2 #005f00 ~ 2 -" Color: light3 #af5f00 ~ 3 -" Color: light4 #005faf ~ 4 -" Color: light5 #870087 ~ 5 -" Color: light6 #008787 ~ 6 -" Color: light7 #d7d7d7 ~ 7 -" Color: light8 #626262 ~ 8 -" Color: light9 #d70000 ~ 9 -" Color: light10 #008700 ~ 10 -" Color: light11 #d78700 ~ 11 -" Color: light12 #0087d7 ~ 12 -" Color: light13 #af00af ~ 13 -" Color: light14 #00afaf ~ 14 -" Color: light15 #ffffff ~ 15 -" Color: diffred #d78787 ~ -" Color: diffgreen #87d787 ~ -" Color: diffblue #afafd7 ~ -" Color: diffpink #d787d7 ~ -" Color: uipink #ff00af ~ -" Color: uilime #afff00 ~ -" Color: uiteal #00ffaf ~ -" Color: uiblue #00afff ~ -" Color: uipurple #af00ff ~ -" Color: uiamber #ffaf00 ~ -" Color: invisigrey #a8a8a8 ~ -" Color: yasogrey #e4e4e4 ~ -" Color: errorred #ff005f ~ +" Color: brightwhite #eeeeee 255 grey +" Color: light0 #080808 232 black +" Color: light1 #af0000 124 darkred +" Color: light2 #005f00 22 darkgreen +" Color: light3 #af5f00 130 darkyellow +" Color: light4 #005faf 25 darkblue +" Color: light5 #870087 90 darkmagenta +" Color: light6 #008787 30 darkcyan +" Color: light7 #d7d7d7 188 grey +" Color: light8 #626262 241 darkgrey +" Color: light9 #d70000 160 red +" Color: light10 #008700 28 green +" Color: light11 #d78700 172 yellow +" Color: light12 #0087d7 32 blue +" Color: light13 #af00af 127 magenta +" Color: light14 #00afaf 37 cyan +" Color: light15 #ffffff 231 white +" Color: diffred #d78787 174 red +" Color: diffgreen #87d787 114 green +" Color: diffblue #afafd7 146 blue +" Color: diffpink #d787d7 176 magenta +" Color: uipink #ff00af 199 magenta +" Color: uilime #afff00 154 green +" Color: uiteal #00ffaf 49 cyan +" Color: uiblue #00afff 39 blue +" Color: uipurple #af00ff 129 darkmagenta +" Color: uiamber #ffaf00 214 yellow +" Color: invisigrey #a8a8a8 248 darkgrey +" Color: yasogrey #e4e4e4 254 grey +" Color: errorred #ff005f 197 red " Term colors: light0 light1 light2 light3 light4 light5 light6 light7 " Term colors: light8 light9 light10 light11 light12 light13 light14 light15 " Background: any diff --git a/runtime/colors/ron.vim b/runtime/colors/ron.vim index eb5c8f1770..f622a6f991 100644 --- a/runtime/colors/ron.vim +++ b/runtime/colors/ron.vim @@ -3,7 +3,7 @@ " Maintainer: original maintainer Ron Aaron <ron@ronware.org> " Website: https://www.github.com/vim/colorschemes " License: Same as Vim -" Last Updated: 2022-07-26 15:50:11 +" Last Updated: Tue 23 Aug 2022 16:50:45 MSK " Generated by Colortemplate v2.2.0 @@ -12,7 +12,7 @@ set background=dark hi clear let g:colors_name = 'ron' -let s:t_Co = exists('&t_Co') && !empty(&t_Co) && &t_Co >= 0 ? &t_Co : -1 +let s:t_Co = exists('&t_Co') && !has('gui_running') ? (&t_Co ? &t_Co : 0) : -1 hi! link Terminal Normal hi! link Boolean Constant diff --git a/runtime/colors/shine.vim b/runtime/colors/shine.vim index de24a88136..331ca9487c 100644 --- a/runtime/colors/shine.vim +++ b/runtime/colors/shine.vim @@ -4,7 +4,7 @@ " Maintainer: Original maintainer is Yasuhiro Matsumoto <mattn@mail.goo.ne.jp> " Website: https://github.com/vim/colorschemes " License: Same as Vim -" Last Updated: 2022-07-26 15:50:12 +" Last Updated: Tue 23 Aug 2022 16:50:46 MSK " Generated by Colortemplate v2.2.0 @@ -13,7 +13,7 @@ set background=light hi clear let g:colors_name = 'shine' -let s:t_Co = exists('&t_Co') && !empty(&t_Co) && &t_Co >= 0 ? &t_Co : -1 +let s:t_Co = exists('&t_Co') && !has('gui_running') ? (&t_Co ? &t_Co : 0) : -1 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'] diff --git a/runtime/colors/slate.vim b/runtime/colors/slate.vim index 63e7d0d857..ecdaca1e2a 100644 --- a/runtime/colors/slate.vim +++ b/runtime/colors/slate.vim @@ -4,7 +4,7 @@ " Maintainer: Original maintainer Ralph Amissah <ralph@amissah.com> " Website: https://github.com/vim/colorschemes " License: Same as Vim -" Last Updated: 2022-07-26 15:50:14 +" Last Updated: Tue 23 Aug 2022 16:50:46 MSK " Generated by Colortemplate v2.2.0 @@ -13,10 +13,10 @@ set background=dark hi clear let g:colors_name = 'slate' -let s:t_Co = exists('&t_Co') && !empty(&t_Co) && &t_Co >= 0 ? &t_Co : -1 +let s:t_Co = exists('&t_Co') && !has('gui_running') ? (&t_Co ? &t_Co : 0) : -1 if (has('termguicolors') && &termguicolors) || has('gui_running') - let g:terminal_ansi_colors = ['#000000', '#ff0000', '#5f8700', '#ffff00', '#87d7ff', '#d7d787', '#ffd7af', '#666666', '#333333', '#ffafaf', '#00875f', '#ffd700', '#5f87d7', '#afaf87', '#ff8787', '#ffffff'] + let g:terminal_ansi_colors = ['#000000', '#cd0000', '#00cd00', '#cdcd00', '#0000ee', '#cd00cd', '#00cdcd', '#e5e5e5', '#7f7f7f', '#ff0000', '#00ff00', '#ffff00', '#5c5cff', '#ff00ff', '#00ffff', '#ffffff'] endif hi! link Terminal Normal hi! link LineNrAbove LineNr @@ -53,18 +53,18 @@ hi Visual guifg=#d7d787 guibg=#5f8700 gui=NONE cterm=NONE hi SignColumn guifg=NONE guibg=#262626 gui=NONE cterm=NONE hi VisualNOS guifg=#d7d787 guibg=#5f8700 gui=NONE cterm=NONE hi LineNr guifg=#666666 guibg=NONE gui=NONE cterm=NONE -hi Underlined guifg=#5f87d7 guibg=NONE gui=underline cterm=underline hi Error guifg=#ff0000 guibg=#ffffff gui=reverse cterm=reverse hi ErrorMsg guifg=#ff0000 guibg=#000000 gui=reverse cterm=reverse hi ModeMsg guifg=#262626 guibg=#ffd700 gui=NONE cterm=NONE hi WarningMsg guifg=#ff8787 guibg=NONE gui=NONE cterm=NONE hi MoreMsg guifg=#00875f guibg=NONE gui=NONE cterm=NONE hi Question guifg=#ffd700 guibg=NONE gui=NONE cterm=NONE -hi Todo guifg=#ff0000 guibg=#ffff00 gui=NONE cterm=NONE hi MatchParen guifg=#000000 guibg=#ffd700 gui=NONE cterm=NONE hi Search guifg=#000000 guibg=#d7875f gui=NONE cterm=NONE hi IncSearch guifg=#000000 guibg=#00ff00 gui=NONE cterm=NONE +hi Todo guifg=#ff0000 guibg=#ffff00 gui=NONE cterm=NONE hi WildMenu guifg=#262626 guibg=#d7d787 gui=NONE cterm=NONE +hi Underlined guifg=#5f87d7 guibg=NONE gui=underline cterm=underline hi Cursor guifg=#333333 guibg=#d7d787 gui=NONE cterm=NONE hi lCursor guifg=#262626 guibg=#ffafaf gui=NONE cterm=NONE hi SpellBad guifg=#ff0000 guibg=NONE guisp=#ff0000 gui=undercurl cterm=underline @@ -128,18 +128,18 @@ if s:t_Co >= 256 hi SignColumn ctermfg=NONE ctermbg=235 cterm=NONE hi VisualNOS ctermfg=186 ctermbg=64 cterm=NONE hi LineNr ctermfg=241 ctermbg=NONE cterm=NONE - hi Underlined ctermfg=68 ctermbg=NONE cterm=underline hi Error ctermfg=196 ctermbg=231 cterm=reverse hi ErrorMsg ctermfg=196 ctermbg=16 cterm=reverse hi ModeMsg ctermfg=235 ctermbg=220 cterm=NONE hi WarningMsg ctermfg=210 ctermbg=NONE cterm=NONE hi MoreMsg ctermfg=29 ctermbg=NONE cterm=NONE hi Question ctermfg=220 ctermbg=NONE cterm=NONE - hi Todo ctermfg=196 ctermbg=226 cterm=NONE hi MatchParen ctermfg=16 ctermbg=220 cterm=NONE hi Search ctermfg=16 ctermbg=173 cterm=NONE hi IncSearch ctermfg=16 ctermbg=46 cterm=NONE + hi Todo ctermfg=196 ctermbg=226 cterm=NONE hi WildMenu ctermfg=235 ctermbg=186 cterm=NONE + hi Underlined ctermfg=68 ctermbg=NONE cterm=underline hi SpellBad ctermfg=196 ctermbg=NONE cterm=underline hi SpellCap ctermfg=226 ctermbg=NONE cterm=underline hi SpellLocal ctermfg=217 ctermbg=NONE cterm=underline @@ -169,12 +169,12 @@ if s:t_Co >= 256 endif if s:t_Co >= 16 - hi Normal ctermfg=white ctermbg=black cterm=NONE - hi EndOfBuffer ctermfg=blue ctermbg=NONE cterm=NONE - hi StatusLine ctermfg=black ctermbg=grey cterm=NONE - hi StatusLineNC ctermfg=darkgrey ctermbg=grey cterm=NONE - hi StatusLineTerm ctermfg=black ctermbg=grey cterm=NONE - hi StatusLineTermNC ctermfg=darkgrey ctermbg=grey cterm=NONE + hi Normal ctermfg=grey ctermbg=black cterm=NONE + hi EndOfBuffer ctermfg=darkblue ctermbg=NONE cterm=bold + hi StatusLine ctermfg=white ctermbg=black cterm=bold,reverse + hi StatusLineNC ctermfg=black ctermbg=grey cterm=NONE + hi StatusLineTerm ctermfg=white ctermbg=black cterm=bold,reverse + hi StatusLineTermNC ctermfg=black ctermbg=grey cterm=NONE hi VertSplit ctermfg=darkgrey ctermbg=grey cterm=NONE hi PmenuSel ctermfg=black ctermbg=darkyellow cterm=NONE hi Pmenu ctermfg=NONE ctermbg=darkgrey cterm=NONE @@ -198,38 +198,38 @@ if s:t_Co >= 16 hi SignColumn ctermfg=NONE ctermbg=black cterm=NONE hi VisualNOS ctermfg=darkmagenta ctermbg=darkgreen cterm=NONE hi LineNr ctermfg=grey ctermbg=NONE cterm=NONE - hi Underlined ctermfg=blue ctermbg=NONE cterm=underline hi Error ctermfg=darkred ctermbg=white cterm=reverse hi ErrorMsg ctermfg=darkred ctermbg=black cterm=reverse hi ModeMsg ctermfg=black ctermbg=yellow cterm=NONE hi WarningMsg ctermfg=cyan ctermbg=NONE cterm=NONE hi MoreMsg ctermfg=green ctermbg=NONE cterm=NONE hi Question ctermfg=yellow ctermbg=NONE cterm=NONE - hi Todo ctermfg=darkred ctermbg=darkyellow cterm=NONE hi MatchParen ctermfg=black ctermbg=yellow cterm=NONE hi Search ctermfg=black ctermbg=darkmagenta cterm=NONE hi IncSearch ctermfg=black ctermbg=darkgreen cterm=NONE + hi Todo ctermfg=black ctermbg=yellow cterm=NONE hi WildMenu ctermfg=black ctermbg=darkyellow cterm=NONE + hi Underlined ctermfg=blue ctermbg=NONE cterm=underline hi SpellBad ctermfg=darkred ctermbg=NONE cterm=underline hi SpellCap ctermfg=darkyellow ctermbg=NONE cterm=underline hi SpellLocal ctermfg=red ctermbg=NONE cterm=underline hi SpellRare ctermfg=darkcyan ctermbg=NONE cterm=underline - hi Comment ctermfg=darkgrey ctermbg=NONE cterm=NONE - hi String ctermfg=cyan ctermbg=NONE cterm=NONE - hi Identifier ctermfg=darkred ctermbg=NONE cterm=NONE - hi Function ctermfg=yellow ctermbg=NONE cterm=NONE - hi Special ctermfg=darkyellow ctermbg=NONE cterm=NONE hi Statement ctermfg=blue ctermbg=NONE cterm=bold - hi Constant ctermfg=red ctermbg=NONE cterm=NONE - hi PreProc ctermfg=darkmagenta ctermbg=NONE cterm=NONE + hi Identifier ctermfg=red ctermbg=NONE cterm=bold + hi Comment ctermfg=darkgray ctermbg=NONE cterm=NONE + hi Operator ctermfg=red ctermbg=NONE cterm=NONE + hi PreProc ctermfg=red ctermbg=NONE cterm=NONE + hi Structure ctermfg=green ctermbg=NONE cterm=NONE + hi Function ctermfg=yellow ctermbg=NONE cterm=NONE hi Type ctermfg=blue ctermbg=NONE cterm=bold - hi Operator ctermfg=darkmagenta ctermbg=NONE cterm=NONE hi Define ctermfg=yellow ctermbg=NONE cterm=bold - hi Structure ctermfg=darkgreen ctermbg=NONE cterm=NONE - hi Directory ctermfg=green ctermbg=NONE cterm=bold + hi Constant ctermfg=darkyellow ctermbg=NONE cterm=NONE + hi String ctermfg=darkcyan ctermbg=NONE cterm=NONE + hi Special ctermfg=darkyellow ctermbg=NONE cterm=NONE + hi Directory ctermfg=darkgreen ctermbg=NONE cterm=bold hi Conceal ctermfg=grey ctermbg=NONE cterm=NONE hi Ignore ctermfg=NONE ctermbg=NONE cterm=NONE - hi Title ctermfg=yellow ctermbg=NONE cterm=bold + hi Title ctermfg=darkyellow ctermbg=NONE cterm=bold hi DiffAdd ctermfg=white ctermbg=darkgreen cterm=NONE hi DiffChange ctermfg=white ctermbg=blue cterm=NONE hi DiffText ctermfg=black ctermbg=grey cterm=NONE @@ -274,27 +274,28 @@ if s:t_Co >= 8 hi WarningMsg ctermfg=darkcyan ctermbg=NONE cterm=NONE hi MoreMsg ctermfg=darkgreen ctermbg=NONE cterm=NONE hi Question ctermfg=darkyellow ctermbg=NONE cterm=NONE - hi Todo ctermfg=darkred ctermbg=darkyellow cterm=NONE hi MatchParen ctermfg=black ctermbg=darkyellow cterm=NONE hi Search ctermfg=black ctermbg=darkmagenta cterm=NONE hi IncSearch ctermfg=black ctermbg=darkgreen cterm=NONE + hi Todo ctermfg=black ctermbg=yellow cterm=NONE hi WildMenu ctermfg=black ctermbg=darkyellow cterm=NONE + hi Underlined ctermfg=blue ctermbg=NONE cterm=underline hi SpellBad ctermfg=darkred ctermbg=darkyellow cterm=reverse hi SpellCap ctermfg=darkyellow ctermbg=NONE cterm=reverse hi SpellLocal ctermfg=darkmagenta ctermbg=darkyellow cterm=reverse hi SpellRare ctermfg=darkcyan ctermbg=NONE cterm=reverse - hi Comment ctermfg=grey ctermbg=NONE cterm=bold - hi String ctermfg=darkcyan ctermbg=NONE cterm=NONE - hi Identifier ctermfg=darkred ctermbg=NONE cterm=NONE - hi Function ctermfg=darkyellow ctermbg=NONE cterm=NONE - hi Special ctermfg=darkyellow ctermbg=NONE cterm=NONE hi Statement ctermfg=darkblue ctermbg=NONE cterm=bold - hi Constant ctermfg=darkred ctermbg=NONE cterm=bold - hi PreProc ctermfg=darkmagenta ctermbg=NONE cterm=NONE + hi Identifier ctermfg=darkred ctermbg=NONE cterm=bold + hi Comment ctermfg=darkgray ctermbg=NONE cterm=bold + hi Operator ctermfg=darkred ctermbg=NONE cterm=bold + hi PreProc ctermfg=darkred ctermbg=NONE cterm=bold + hi Structure ctermfg=darkgreen ctermbg=NONE cterm=bold + hi Function ctermfg=darkyellow ctermbg=NONE cterm=bold hi Type ctermfg=darkblue ctermbg=NONE cterm=bold - hi Operator ctermfg=darkmagenta ctermbg=NONE cterm=NONE hi Define ctermfg=darkyellow ctermbg=NONE cterm=bold - hi Structure ctermfg=darkgreen ctermbg=NONE cterm=NONE + hi Constant ctermfg=darkyellow ctermbg=NONE cterm=NONE + hi String ctermfg=darkcyan ctermbg=NONE cterm=NONE + hi Special ctermfg=darkyellow ctermbg=NONE cterm=NONE hi Directory ctermfg=darkgreen ctermbg=NONE cterm=bold hi Conceal ctermfg=grey ctermbg=NONE cterm=NONE hi Ignore ctermfg=NONE ctermbg=NONE cterm=NONE @@ -376,29 +377,45 @@ if s:t_Co >= 0 endif " Background: dark -" Color: foreground #FFFFFF 231 white -" Color: background #262626 235 black -" Color: color00 #000000 16 black -" Color: color08 #333333 236 darkgrey -" Color: color01 #FF0000 196 darkred -" Color: color09 #FFAFAF 217 red -" Color: color02 #5F8700 64 darkgreen -" Color: color10 #00875F 29 green -" Color: color03 #ffff00 226 darkyellow -" Color: color11 #FFD700 220 yellow -" Color: color04 #87d7FF 117 darkblue -" Color: color12 #5F87D7 68 blue -" Color: color05 #d7d787 186 darkmagenta -" Color: color13 #AFAF87 144 magenta -" Color: color06 #FFD7AF 223 darkcyan -" Color: color14 #FF8787 210 cyan -" Color: color07 #666666 241 grey -" Color: color15 #FFFFFF 231 white -" Color: color16 #D7875F 173 darkmagenta -" Color: color17 #00FF00 46 darkgreen -" Color: Pmenu #4A4A4A 239 darkgrey -" Term colors: color00 color01 color02 color03 color04 color05 color06 color07 -" Term colors: color08 color09 color10 color11 color12 color13 color14 color15 +" Color: x_black #000000 16 black +" Color: x_darkred #cd0000 160 darkred +" Color: x_darkgreen #00cd00 40 darkgreen +" Color: x_darkyellow #cdcd00 184 darkyellow +" Color: x_darkblue #0000ee 21 darkblue +" Color: x_darkmagenta #cd00cd 164 darkmagenta +" Color: x_darkcyan #00cdcd 44 darkcyan +" Color: x_gray #e5e5e5 254 gray +" Color: x_darkgray #7f7f7f 244 darkgray +" Color: x_red #ff0000 196 red +" Color: x_green #00ff00 46 green +" Color: x_yellow #ffff00 226 yellow +" Color: x_blue #5c5cff 63 blue +" Color: x_magenta #ff00ff 201 magenta +" Color: x_cyan #00ffff 51 cyan +" Color: x_white #ffffff 231 white +" Color: foreground #FFFFFF 231 white +" Color: background #262626 235 black +" Color: color00 #000000 16 black +" Color: color08 #333333 236 darkgrey +" Color: color01 #FF0000 196 darkred +" Color: color09 #FFAFAF 217 red +" Color: color02 #5F8700 64 darkgreen +" Color: color10 #00875F 29 green +" Color: color03 #ffff00 226 darkyellow +" Color: color11 #FFD700 220 yellow +" Color: color04 #87d7FF 117 darkblue +" Color: color12 #5F87D7 68 blue +" Color: color05 #d7d787 186 darkmagenta +" Color: color13 #AFAF87 144 magenta +" Color: color06 #FFD7AF 223 darkcyan +" Color: color14 #FF8787 210 cyan +" Color: color07 #666666 241 grey +" Color: color15 #FFFFFF 231 white +" Color: color16 #D7875F 173 darkmagenta +" Color: color17 #00FF00 46 darkgreen +" Color: Pmenu #4A4A4A 239 darkgrey +" Term colors: x_black x_darkred x_darkgreen x_darkyellow x_darkblue x_darkmagenta x_darkcyan x_gray +" Term colors: x_darkgray x_red x_green x_yellow x_blue x_magenta x_cyan x_white " Color: bgDiffA #5F875F 65 darkgreen " Color: bgDiffC #5F87AF 67 blue " Color: bgDiffD #AF5FAF 133 magenta diff --git a/runtime/colors/torte.vim b/runtime/colors/torte.vim index 9a9124f3c1..dbce11c921 100644 --- a/runtime/colors/torte.vim +++ b/runtime/colors/torte.vim @@ -4,7 +4,7 @@ " Maintainer: Original maintainer Thorsten Maerz <info@netztorte.de> " Website: https://github.com/vim/colorschemes " License: Same as Vim -" Last Updated: 2022-07-26 15:50:15 +" Last Updated: Tue 23 Aug 2022 16:50:47 MSK " Generated by Colortemplate v2.2.0 @@ -13,7 +13,7 @@ set background=dark hi clear let g:colors_name = 'torte' -let s:t_Co = exists('&t_Co') && !empty(&t_Co) && &t_Co >= 0 ? &t_Co : -1 +let s:t_Co = exists('&t_Co') && !has('gui_running') ? (&t_Co ? &t_Co : 0) : -1 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'] diff --git a/runtime/colors/zellner.vim b/runtime/colors/zellner.vim index 0d38cefc08..db500c52f2 100644 --- a/runtime/colors/zellner.vim +++ b/runtime/colors/zellner.vim @@ -4,7 +4,7 @@ " Maintainer: Original maintainer Ron Aaron <ron@ronware.org> " Website: https://github.com/vim/colorschemes " License: Same as Vim -" Last Updated: 2022-07-26 15:50:16 +" Last Updated: Tue 23 Aug 2022 16:50:48 MSK " Generated by Colortemplate v2.2.0 @@ -13,7 +13,7 @@ set background=light hi clear let g:colors_name = 'zellner' -let s:t_Co = exists('&t_Co') && !empty(&t_Co) && &t_Co >= 0 ? &t_Co : -1 +let s:t_Co = exists('&t_Co') && !has('gui_running') ? (&t_Co ? &t_Co : 0) : -1 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'] diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index be42b7c14e..89baf84c86 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -1,10 +1,10 @@ -*api.txt* Nvim +*api.txt* Nvim - NVIM REFERENCE MANUAL by Thiago de Arruda + NVIM REFERENCE MANUAL by Thiago de Arruda -Nvim API *API* *api* +Nvim API *API* *api* Nvim exposes a powerful API that can be used by plugins and external processes via |RPC|, |Lua| and VimL (|eval-api|). @@ -14,9 +14,9 @@ Applications can also embed libnvim to work with the C API directly. Type |gO| to see the table of contents. ============================================================================== -API Usage *api-rpc* *RPC* *rpc* +API Usage *api-rpc* *RPC* *rpc* - *msgpack-rpc* + *msgpack-rpc* RPC is the typical way to control Nvim programmatically. Nvim implements the MessagePack-RPC protocol: https://github.com/msgpack-rpc/msgpack-rpc/blob/master/spec.md @@ -32,7 +32,7 @@ other Nvim instances. API clients can: The RPC API is like a more powerful version of Vim's "clientserver" feature. -CONNECTING *rpc-connecting* +CONNECTING *rpc-connecting* See |channel-intro| for various ways to open a channel. Channel-opening functions take an `rpc` key in the options dictionary. RPC channels can also @@ -85,21 +85,21 @@ and |rpcnotify()|: call jobstop(nvim) ============================================================================== -API Definitions *api-definitions* +API Definitions *api-definitions* - *api-types* + *api-types* The Nvim C API defines custom types for all function parameters. Some are just typedefs around C99 standard types, others are Nvim-defined data structures. Basic types ~ - API Type C type + API Type C type ------------------------------------------------------------------------ Nil - Boolean bool - Integer (signed 64-bit integer) int64_t - Float (IEEE 754 double precision) double - String {char* data, size_t size} struct + Boolean bool + Integer (signed 64-bit integer) int64_t + Float (IEEE 754 double precision) double + String {char* data, size_t size} struct Array Dictionary (msgpack: map) Object @@ -116,14 +116,14 @@ Special types (msgpack EXT) ~ in the |api-metadata| `types` key are stable: they will not change and are thus forward-compatible. - EXT Type C type Data + EXT Type C type Data ------------------------------------------------------------------------ - Buffer enum value kObjectTypeBuffer |bufnr()| - Window enum value kObjectTypeWindow |window-ID| - Tabpage enum value kObjectTypeTabpage internal handle + Buffer enum value kObjectTypeBuffer |bufnr()| + Window enum value kObjectTypeWindow |window-ID| + Tabpage enum value kObjectTypeTabpage internal handle - *api-indexing* + *api-indexing* Most of the API uses 0-based indices, and ranges are end-exclusive. For the end of a range, -1 denotes the last line/column. @@ -144,7 +144,7 @@ indices, end-inclusive): |nvim_buf_get_extmarks()| |nvim_buf_set_extmark()| - *api-fast* + *api-fast* Most API functions are "deferred": they are queued on the main loop and processed sequentially with normal input. So if the editor is waiting for user input in a "modal" fashion (e.g. the |hit-enter-prompt|), the request @@ -153,7 +153,7 @@ will block. Non-deferred (fast) functions such as |nvim_get_mode()| and queue). Lua code can use |vim.in_fast_event()| to detect a fast context. ============================================================================== -API metadata *api-metadata* +API metadata *api-metadata* The Nvim C API is automatically exposed to RPC by the build system, which parses headers in src/nvim/api/* and generates dispatch-functions mapping RPC @@ -162,19 +162,19 @@ and return values. Nvim exposes its API metadata as a Dictionary with these items: -version Nvim version, API level/compatibility -version.api_level API version integer *api-level* -version.api_compatible API is backwards-compatible with this level -version.api_prerelease Declares the API as unstable/unreleased > - (version.api_prerelease && fn.since == version.api_level) -functions API function signatures, containing |api-types| info +version Nvim version, API level/compatibility +version.api_level API version integer *api-level* +version.api_compatible API is backwards-compatible with this level +version.api_prerelease Declares the API as unstable/unreleased > + (version.api_prerelease && fn.since == version.api_level) +functions API function signatures, containing |api-types| info describing the return value and parameters. -ui_events |UI| event signatures -ui_options Supported |ui-option|s -{fn}.since API level where function {fn} was introduced -{fn}.deprecated_since API level where function {fn} was deprecated -types Custom handle types defined by Nvim -error_types Possible error types returned by API functions +ui_events |UI| event signatures +ui_options Supported |ui-option|s +{fn}.since API level where function {fn} was introduced +{fn}.deprecated_since API level where function {fn} was deprecated +types Custom handle types defined by Nvim +error_types Possible error types returned by API functions About the `functions` map: @@ -188,7 +188,7 @@ About the `functions` map: - Global functions have the "method=false" flag and are prefixed with just `nvim_`, e.g. `nvim_list_bufs`. - *api-mapping* + *api-mapping* External programs (clients) can use the metadata to discover the API, using any of these approaches: @@ -231,7 +231,7 @@ As Nvim evolves the API may change in compliance with this CONTRACT: - Deprecated functions will not be removed until Nvim version 2.0 ============================================================================== -Global events *api-global-events* +Global events *api-global-events* When a client invokes an API request as an async notification, it is not possible for Nvim to send an error response. Instead, in case of error, the @@ -244,7 +244,7 @@ nvim_error_event[{type}, {message}] a string with the error message. ============================================================================== -Buffer update events *api-buffer-updates* +Buffer update events *api-buffer-updates* API clients can "attach" to Nvim buffers to subscribe to buffer update events. This is similar to |TextChanged| but more powerful and granular. @@ -254,89 +254,90 @@ Call |nvim_buf_attach()| to receive these events on the channel: *nvim_buf_lines_event* nvim_buf_lines_event[{buf}, {changedtick}, {firstline}, {lastline}, {linedata}, {more}] - When the buffer text between {firstline} and {lastline} (end-exclusive, - zero-indexed) were changed to the new text in the {linedata} list. The - granularity is a line, i.e. if a single character is changed in the editor, - the entire line is sent. + When the buffer text between {firstline} and {lastline} (end-exclusive, + zero-indexed) were changed to the new text in the {linedata} list. The + granularity is a line, i.e. if a single character is changed in the + editor, the entire line is sent. - When {changedtick} is |v:null| this means the screen lines (display) changed - but not the buffer contents. {linedata} contains the changed screen lines. - This happens when 'inccommand' shows a buffer preview. + When {changedtick} is |v:null| this means the screen lines (display) + changed but not the buffer contents. {linedata} contains the changed + screen lines. This happens when 'inccommand' shows a buffer preview. - Properties:~ - {buf} API buffer handle (buffer number) + Properties:~ + {buf} API buffer handle (buffer number) - {changedtick} value of |b:changedtick| for the buffer. If you send an API - command back to nvim you can check the value of |b:changedtick| as part of - your request to ensure that no other changes have been made. + {changedtick} value of |b:changedtick| for the buffer. If you send an + API command back to nvim you can check the value of |b:changedtick| as + part of your request to ensure that no other changes have been made. - {firstline} integer line number of the first line that was replaced. - Zero-indexed: if line 1 was replaced then {firstline} will be 0, not 1. - {firstline} is always less than or equal to the number of lines that were - in the buffer before the lines were replaced. + {firstline} integer line number of the first line that was replaced. + Zero-indexed: if line 1 was replaced then {firstline} will be 0, not + 1. {firstline} is always less than or equal to the number of lines + that were in the buffer before the lines were replaced. - {lastline} integer line number of the first line that was not replaced - (i.e. the range {firstline}, {lastline} is end-exclusive). - Zero-indexed: if line numbers 2 to 5 were replaced, this will be 5 instead - of 6. {lastline} is always be less than or equal to the number of lines - that were in the buffer before the lines were replaced. {lastline} will be - -1 if the event is part of the initial update after attaching. + {lastline} integer line number of the first line that was not replaced + (i.e. the range {firstline}, {lastline} is end-exclusive). + Zero-indexed: if line numbers 2 to 5 were replaced, this will be 5 + instead of 6. {lastline} is always be less than or equal to the number + of lines that were in the buffer before the lines were replaced. + {lastline} will be -1 if the event is part of the initial update after + attaching. - {linedata} list of strings containing the contents of the new buffer - lines. Newline characters are omitted; empty lines are sent as empty - strings. + {linedata} list of strings containing the contents of the new buffer + lines. Newline characters are omitted; empty lines are sent as empty + strings. - {more} boolean, true for a "multipart" change notification: the current - change was chunked into multiple |nvim_buf_lines_event| notifications - (e.g. because it was too big). + {more} boolean, true for a "multipart" change notification: the + current change was chunked into multiple |nvim_buf_lines_event| + notifications (e.g. because it was too big). nvim_buf_changedtick_event[{buf}, {changedtick}] *nvim_buf_changedtick_event* - When |b:changedtick| was incremented but no text was changed. Relevant for - undo/redo. + When |b:changedtick| was incremented but no text was changed. Relevant for + undo/redo. - Properties:~ - {buf} API buffer handle (buffer number) - {changedtick} new value of |b:changedtick| for the buffer + Properties:~ + {buf} API buffer handle (buffer number) + {changedtick} new value of |b:changedtick| for the buffer nvim_buf_detach_event[{buf}] *nvim_buf_detach_event* - When buffer is detached (i.e. updates are disabled). Triggered explicitly by - |nvim_buf_detach()| or implicitly in these cases: - - Buffer was |abandon|ed and 'hidden' is not set. - - Buffer was reloaded, e.g. with |:edit| or an external change triggered - |:checktime| or 'autoread'. - - Generally: whenever the buffer contents are unloaded from memory. + When buffer is detached (i.e. updates are disabled). Triggered explicitly by + |nvim_buf_detach()| or implicitly in these cases: + - Buffer was |abandon|ed and 'hidden' is not set. + - Buffer was reloaded, e.g. with |:edit| or an external change triggered + |:checktime| or 'autoread'. + - Generally: whenever the buffer contents are unloaded from memory. - Properties:~ - {buf} API buffer handle (buffer number) + Properties:~ + {buf} API buffer handle (buffer number) EXAMPLE ~ Calling |nvim_buf_attach()| with send_buffer=true on an empty buffer, emits: > - nvim_buf_lines_event[{buf}, {changedtick}, 0, -1, [""], v:false] + nvim_buf_lines_event[{buf}, {changedtick}, 0, -1, [""], v:false] User adds two lines to the buffer, emits: > - nvim_buf_lines_event[{buf}, {changedtick}, 0, 0, ["line1", "line2"], v:false] + nvim_buf_lines_event[{buf}, {changedtick}, 0, 0, ["line1", "line2"], v:false] User moves to a line containing the text "Hello world" and inserts "!", emits: > - nvim_buf_lines_event[{buf}, {changedtick}, {linenr}, {linenr} + 1, - ["Hello world!"], v:false] + nvim_buf_lines_event[{buf}, {changedtick}, {linenr}, {linenr} + 1, + ["Hello world!"], v:false] User moves to line 3 and deletes 20 lines using "20dd", emits: > - nvim_buf_lines_event[{buf}, {changedtick}, 2, 22, [], v:false] + nvim_buf_lines_event[{buf}, {changedtick}, 2, 22, [], v:false] User selects lines 3-5 using |linewise-visual| mode and then types "p" to paste a block of 6 lines, emits: > - nvim_buf_lines_event[{buf}, {changedtick}, 2, 5, - ['pasted line 1', 'pasted line 2', 'pasted line 3', 'pasted line 4', - 'pasted line 5', 'pasted line 6'], - v:false - ] + nvim_buf_lines_event[{buf}, {changedtick}, 2, 5, + ['pasted line 1', 'pasted line 2', 'pasted line 3', 'pasted line 4', + 'pasted line 5', 'pasted line 6'], + v:false + ] User reloads the buffer with ":edit", emits: > - nvim_buf_detach_event[{buf}] + nvim_buf_detach_event[{buf}] < LUA ~ @@ -369,7 +370,7 @@ callbacks attached with the same |nvim_buf_attach()| call. ============================================================================== -Buffer highlighting *api-highlights* +Buffer highlighting *api-highlights* Nvim allows plugins to add position-based highlights to buffers. This is similar to |matchaddpos()| but with some key differences. The added highlights @@ -413,7 +414,7 @@ Example using the API from Vimscript: > ============================================================================== -Floating windows *api-floatwin* +Floating windows *api-floatwin* Floating windows ("floats") are displayed on top of normal windows. This is useful to implement simple widgets, such as tooltips displayed next to the @@ -457,7 +458,7 @@ Example: create a float with scratch buffer: > > ============================================================================== -Extended marks *api-extended-marks* *extmarks* +Extended marks *api-extended-marks* *extmarks* Extended marks (extmarks) represent buffer annotations that track text changes in the buffer. They can represent cursors, folds, misspelled words, anything @@ -537,1025 +538,961 @@ created for extmark changes. Global Functions *api-global* nvim__get_runtime({pat}, {all}, {*opts}) *nvim__get_runtime()* - Find files in runtime directories + Find files in runtime directories - Attributes: ~ - |api-fast| + Attributes: ~ + |api-fast| - Parameters: ~ - {pat} pattern of files to search for - {all} whether to return all matches or only the first - {opts} is_lua: only search lua subdirs + Parameters: ~ + {pat} pattern of files to search for + {all} whether to return all matches or only the first + {opts} is_lua: only search lua subdirs - Return: ~ - list of absolute paths to the found files + Return: ~ + list of absolute paths to the found files nvim__id({obj}) *nvim__id()* - Returns object given as argument. + Returns object given as argument. - This API function is used for testing. One should not rely on - its presence in plugins. + This API function is used for testing. One should not rely on its presence + in plugins. - Parameters: ~ - {obj} Object to return. + Parameters: ~ + {obj} Object to return. - Return: ~ - its argument. + Return: ~ + its argument. nvim__id_array({arr}) *nvim__id_array()* - Returns array given as argument. + Returns array given as argument. - This API function is used for testing. One should not rely on - its presence in plugins. + This API function is used for testing. One should not rely on its presence + in plugins. - Parameters: ~ - {arr} Array to return. + Parameters: ~ + {arr} Array to return. - Return: ~ - its argument. + Return: ~ + its argument. nvim__id_dictionary({dct}) *nvim__id_dictionary()* - Returns dictionary given as argument. + Returns dictionary given as argument. - This API function is used for testing. One should not rely on - its presence in plugins. + This API function is used for testing. One should not rely on its presence + in plugins. - Parameters: ~ - {dct} Dictionary to return. + Parameters: ~ + {dct} Dictionary to return. - Return: ~ - its argument. + Return: ~ + its argument. nvim__id_float({flt}) *nvim__id_float()* - Returns floating-point value given as argument. + Returns floating-point value given as argument. - This API function is used for testing. One should not rely on - its presence in plugins. + This API function is used for testing. One should not rely on its presence + in plugins. - Parameters: ~ - {flt} Value to return. + Parameters: ~ + {flt} Value to return. - Return: ~ - its argument. + Return: ~ + its argument. nvim__inspect_cell({grid}, {row}, {col}) *nvim__inspect_cell()* - NB: if your UI doesn't use hlstate, this will not return - hlstate first time. - -nvim__set_hl_ns({ns_id}) *nvim__set_hl_ns()* - Set active namespace for highlights. - - NB: this function can be called from async contexts, but the - semantics are not yet well-defined. To start with - |nvim_set_decoration_provider| on_win and on_line callbacks - are explicitly allowed to change the namespace during a redraw - cycle. - - Attributes: ~ - |api-fast| - - Parameters: ~ - {ns_id} the namespace to activate + NB: if your UI doesn't use hlstate, this will not return hlstate first + time. nvim__stats() *nvim__stats()* - Gets internal stats. + Gets internal stats. - Return: ~ - Map of various internal stats. + Return: ~ + Map of various internal stats. nvim_call_atomic({calls}) *nvim_call_atomic()* - Calls many API methods atomically. - - This has two main usages: - 1. To perform several requests from an async context - atomically, i.e. without interleaving redraws, RPC requests - from other clients, or user interactions (however API - methods may trigger autocommands or event processing which - have such side effects, e.g. |:sleep| may wake timers). - 2. To minimize RPC overhead (roundtrips) of a sequence of many - requests. - - Attributes: ~ - |RPC| only - - Parameters: ~ - {calls} an array of calls, where each call is described - by an array with two elements: the request name, - and an array of arguments. - - Return: ~ - Array of two elements. The first is an array of return - values. The second is NIL if all calls succeeded. If a - call resulted in an error, it is a three-element array - with the zero-based index of the call which resulted in an - error, the error type and the error message. If an error - occurred, the values from all preceding calls will still - be returned. + Calls many API methods atomically. + + This has two main usages: + 1. To perform several requests from an async context atomically, i.e. + without interleaving redraws, RPC requests from other clients, or user + interactions (however API methods may trigger autocommands or event + processing which have such side effects, e.g. |:sleep| may wake + timers). + 2. To minimize RPC overhead (roundtrips) of a sequence of many requests. + + Attributes: ~ + |RPC| only + + Parameters: ~ + {calls} an array of calls, where each call is described by an array + with two elements: the request name, and an array of + arguments. + + Return: ~ + Array of two elements. The first is an array of return values. The + second is NIL if all calls succeeded. If a call resulted in an error, + it is a three-element array with the zero-based index of the call + which resulted in an error, the error type and the error message. If + an error occurred, the values from all preceding calls will still be + returned. nvim_chan_send({chan}, {data}) *nvim_chan_send()* - Send data to channel `id`. For a job, it writes it to the - stdin of the process. For the stdio channel |channel-stdio|, - it writes to Nvim's stdout. For an internal terminal instance - (|nvim_open_term()|) it writes directly to terminal output. - See |channel-bytes| for more information. + Send data to channel `id`. For a job, it writes it to the stdin of the + process. For the stdio channel |channel-stdio|, it writes to Nvim's + stdout. For an internal terminal instance (|nvim_open_term()|) it writes + directly to terminal output. See |channel-bytes| for more information. - This function writes raw data, not RPC messages. If the - channel was created with `rpc=true` then the channel expects - RPC messages, use |vim.rpcnotify()| and |vim.rpcrequest()| - instead. + This function writes raw data, not RPC messages. If the channel was + created with `rpc=true` then the channel expects RPC messages, use + |vim.rpcnotify()| and |vim.rpcrequest()| instead. - Attributes: ~ - |RPC| only - |vim.api| only + Attributes: ~ + |RPC| only + |vim.api| only - Parameters: ~ - {chan} id of the channel - {data} data to write. 8-bit clean: can contain NUL bytes. + Parameters: ~ + {chan} id of the channel + {data} data to write. 8-bit clean: can contain NUL bytes. nvim_create_buf({listed}, {scratch}) *nvim_create_buf()* - Creates a new, empty, unnamed buffer. + Creates a new, empty, unnamed buffer. - Parameters: ~ - {listed} Sets 'buflisted' - {scratch} Creates a "throwaway" |scratch-buffer| for - temporary work (always 'nomodified'). Also sets - 'nomodeline' on the buffer. + Parameters: ~ + {listed} Sets 'buflisted' + {scratch} Creates a "throwaway" |scratch-buffer| for temporary work + (always 'nomodified'). Also sets 'nomodeline' on the + buffer. - Return: ~ - Buffer handle, or 0 on error + Return: ~ + Buffer handle, or 0 on error - See also: ~ - buf_open_scratch + See also: ~ + buf_open_scratch nvim_del_current_line() *nvim_del_current_line()* - Deletes the current line. + Deletes the current line. - Attributes: ~ - not allowed when |textlock| is active + Attributes: ~ + not allowed when |textlock| is active nvim_del_keymap({mode}, {lhs}) *nvim_del_keymap()* - Unmaps a global |mapping| for the given mode. + Unmaps a global |mapping| for the given mode. - To unmap a buffer-local mapping, use |nvim_buf_del_keymap()|. + To unmap a buffer-local mapping, use |nvim_buf_del_keymap()|. - See also: ~ - |nvim_set_keymap()| + See also: ~ + |nvim_set_keymap()| nvim_del_mark({name}) *nvim_del_mark()* - Deletes an uppercase/file named mark. See |mark-motions|. + Deletes an uppercase/file named mark. See |mark-motions|. - Note: - fails with error if a lowercase or buffer local named mark - is used. + Note: + fails with error if a lowercase or buffer local named mark is used. - Parameters: ~ - {name} Mark name + Parameters: ~ + {name} Mark name - Return: ~ - true if the mark was deleted, else false. + Return: ~ + true if the mark was deleted, else false. - See also: ~ - |nvim_buf_del_mark()| - |nvim_get_mark()| + See also: ~ + |nvim_buf_del_mark()| + |nvim_get_mark()| nvim_del_var({name}) *nvim_del_var()* - Removes a global (g:) variable. + Removes a global (g:) variable. - Parameters: ~ - {name} Variable name + Parameters: ~ + {name} Variable name nvim_echo({chunks}, {history}, {opts}) *nvim_echo()* - Echo a message. + Echo a message. - Parameters: ~ - {chunks} A list of [text, hl_group] arrays, each - representing a text 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. + Parameters: ~ + {chunks} A list of [text, hl_group] arrays, each representing a text + 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. nvim_err_write({str}) *nvim_err_write()* - Writes a message to the Vim error buffer. Does not append - "\n", the message is buffered (won't display) until a linefeed - is written. + Writes a message to the Vim error buffer. Does not append "\n", the + message is buffered (won't display) until a linefeed is written. - Parameters: ~ - {str} Message + Parameters: ~ + {str} Message nvim_err_writeln({str}) *nvim_err_writeln()* - Writes a message to the Vim error buffer. Appends "\n", so the - buffer is flushed (and displayed). + Writes a message to the Vim error buffer. Appends "\n", so the buffer is + flushed (and displayed). - Parameters: ~ - {str} Message + Parameters: ~ + {str} Message - See also: ~ - nvim_err_write() + See also: ~ + nvim_err_write() nvim_eval_statusline({str}, {*opts}) *nvim_eval_statusline()* - Evaluates statusline string. - - Attributes: ~ - |api-fast| - - Parameters: ~ - {str} Statusline string (see 'statusline'). - {opts} Optional parameters. - • winid: (number) |window-ID| of the window to use - as context for statusline. - • maxwidth: (number) Maximum width of statusline. - • fillchar: (string) Character to fill blank - spaces in the statusline (see 'fillchars'). - Treated as single-width even if it isn't. - • highlights: (boolean) Return highlight - information. - • use_winbar: (boolean) Evaluate winbar instead of - statusline. - • use_tabline: (boolean) Evaluate tabline instead - of statusline. When |TRUE|, {winid} is ignored. - Mutually exclusive with {use_winbar}. - - Return: ~ - Dictionary containing statusline information, with these - keys: - • str: (string) Characters that will be displayed on the - statusline. - • width: (number) Display width of the statusline. - • highlights: Array containing highlight information of - the statusline. Only included when the "highlights" key - in {opts} is |TRUE|. Each element of the array is a - |Dictionary| with these keys: - • start: (number) Byte index (0-based) of first - character that uses the highlight. - • group: (string) Name of highlight group. + Evaluates statusline string. + + Attributes: ~ + |api-fast| + + Parameters: ~ + {str} Statusline string (see 'statusline'). + {opts} Optional parameters. + • winid: (number) |window-ID| of the window to use as context + for statusline. + • maxwidth: (number) Maximum width of statusline. + • fillchar: (string) Character to fill blank spaces in the + statusline (see 'fillchars'). Treated as single-width even + if it isn't. + • highlights: (boolean) Return highlight information. + • use_winbar: (boolean) Evaluate winbar instead of statusline. + • use_tabline: (boolean) Evaluate tabline instead of + statusline. When true, {winid} is ignored. Mutually + exclusive with {use_winbar}. + + Return: ~ + Dictionary containing statusline information, with these keys: + • str: (string) Characters that will be displayed on the statusline. + • width: (number) Display width of the statusline. + • highlights: Array containing highlight information of the + statusline. Only included when the "highlights" key in {opts} is + true. Each element of the array is a |Dictionary| with these keys: + • start: (number) Byte index (0-based) of first character that uses + the highlight. + • group: (string) Name of highlight group. nvim_exec_lua({code}, {args}) *nvim_exec_lua()* - Execute Lua code. Parameters (if any) are available as `...` - inside the chunk. The chunk can return a value. + Execute Lua code. Parameters (if any) are available as `...` inside the + chunk. The chunk can return a value. - Only statements are executed. To evaluate an expression, - prefix it with `return`: return my_function(...) + Only statements are executed. To evaluate an expression, prefix it with + `return`: return my_function(...) - Attributes: ~ - |RPC| only + Attributes: ~ + |RPC| only - Parameters: ~ - {code} Lua code to execute - {args} Arguments to the code + Parameters: ~ + {code} Lua code to execute + {args} Arguments to the code - Return: ~ - Return value of Lua code if present or NIL. + Return: ~ + Return value of Lua code if present or NIL. nvim_feedkeys({keys}, {mode}, {escape_ks}) *nvim_feedkeys()* - Sends input-keys to Nvim, subject to various quirks controlled - by `mode` flags. This is a blocking call, unlike - |nvim_input()|. + Sends input-keys to Nvim, subject to various quirks controlled by `mode` + flags. This is a blocking call, unlike |nvim_input()|. - On execution error: does not fail, but updates v:errmsg. + On execution error: does not fail, but updates v:errmsg. - To input sequences like <C-o> use |nvim_replace_termcodes()| - (typically with escape_ks=false) to replace |keycodes|, then - pass the result to nvim_feedkeys(). + To input sequences like <C-o> use |nvim_replace_termcodes()| (typically + with escape_ks=false) to replace |keycodes|, then pass the result to + nvim_feedkeys(). - Example: > - :let key = nvim_replace_termcodes("<C-o>", v:true, v:false, v:true) - :call nvim_feedkeys(key, 'n', v:false) + Example: > + :let key = nvim_replace_termcodes("<C-o>", v:true, v:false, v:true) + :call nvim_feedkeys(key, 'n', v:false) < - Parameters: ~ - {keys} to be typed - {mode} behavior flags, see |feedkeys()| - {escape_ks} If true, escape K_SPECIAL bytes in `keys` - This should be false if you already used - |nvim_replace_termcodes()|, and true - otherwise. + Parameters: ~ + {keys} to be typed + {mode} behavior flags, see |feedkeys()| + {escape_ks} If true, escape K_SPECIAL bytes in `keys` This should be + false if you already used |nvim_replace_termcodes()|, and + true otherwise. - See also: ~ - feedkeys() - vim_strsave_escape_ks + See also: ~ + feedkeys() + vim_strsave_escape_ks nvim_get_api_info() *nvim_get_api_info()* - Returns a 2-tuple (Array), where item 0 is the current channel - id and item 1 is the |api-metadata| map (Dictionary). + Returns a 2-tuple (Array), where item 0 is the current channel id and item + 1 is the |api-metadata| map (Dictionary). - Return: ~ - 2-tuple [{channel-id}, {api-metadata}] + Return: ~ + 2-tuple [{channel-id}, {api-metadata}] - Attributes: ~ - |api-fast| - |RPC| only + Attributes: ~ + |api-fast| + |RPC| only nvim_get_chan_info({chan}) *nvim_get_chan_info()* - Gets information about a channel. - - Return: ~ - Dictionary describing a channel, with these keys: - • "id" Channel id. - • "argv" (optional) Job arguments list. - • "stream" Stream underlying the channel. - • "stdio" stdin and stdout of this Nvim instance - • "stderr" stderr of this Nvim instance - • "socket" TCP/IP socket or named pipe - • "job" Job with communication over its stdio. - - • "mode" How data received on the channel is interpreted. - • "bytes" Send and receive raw bytes. - • "terminal" |terminal| instance interprets ASCII - sequences. - • "rpc" |RPC| communication on the channel is active. - - • "pty" (optional) Name of pseudoterminal. On a POSIX - system this is a device path like "/dev/pts/1". If the - name is unknown, the key will still be present if a pty - is used (e.g. for conpty on Windows). - • "buffer" (optional) Buffer with connected |terminal| - instance. - • "client" (optional) Info about the peer (client on the - other end of the RPC channel), if provided by it via - |nvim_set_client_info()|. + Gets information about a channel. + + Return: ~ + Dictionary describing a channel, with these keys: + • "id" Channel id. + • "argv" (optional) Job arguments list. + • "stream" Stream underlying the channel. + • "stdio" stdin and stdout of this Nvim instance + • "stderr" stderr of this Nvim instance + • "socket" TCP/IP socket or named pipe + • "job" Job with communication over its stdio. + + • "mode" How data received on the channel is interpreted. + • "bytes" Send and receive raw bytes. + • "terminal" |terminal| instance interprets ASCII sequences. + • "rpc" |RPC| communication on the channel is active. + + • "pty" (optional) Name of pseudoterminal. On a POSIX system this is a + device path like "/dev/pts/1". If the name is unknown, the key will + still be present if a pty is used (e.g. for conpty on Windows). + • "buffer" (optional) Buffer with connected |terminal| instance. + • "client" (optional) Info about the peer (client on the other end of + the RPC channel), if provided by it via |nvim_set_client_info()|. 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. + Returns the 24-bit RGB value of a |nvim_get_color_map()| color name or + "#rrggbb" hexadecimal string. - Example: > - :echo nvim_get_color_by_name("Pink") - :echo nvim_get_color_by_name("#cbcbcb") + Example: > + :echo nvim_get_color_by_name("Pink") + :echo nvim_get_color_by_name("#cbcbcb") < - Parameters: ~ - {name} Color name or "#rrggbb" string + Parameters: ~ + {name} Color name or "#rrggbb" string - Return: ~ - 24-bit RGB value, or -1 for invalid argument. + Return: ~ + 24-bit RGB value, or -1 for invalid argument. nvim_get_color_map() *nvim_get_color_map()* - Returns a map of color names and RGB values. + Returns a map of color names and RGB values. - Keys are color names (e.g. "Aqua") and values are 24-bit RGB - color values (e.g. 65535). + Keys are color names (e.g. "Aqua") and values are 24-bit RGB color values + (e.g. 65535). - Return: ~ - Map of color names and RGB values. + Return: ~ + Map of color names and RGB values. nvim_get_context({*opts}) *nvim_get_context()* - Gets a map of the current editor state. + Gets a map of the current editor state. - Parameters: ~ - {opts} Optional parameters. - • types: List of |context-types| ("regs", "jumps", - "bufs", "gvars", …) to gather, or empty for - "all". + Parameters: ~ + {opts} Optional parameters. + • types: List of |context-types| ("regs", "jumps", "bufs", + "gvars", …) to gather, or empty for "all". - Return: ~ - map of global |context|. + Return: ~ + map of global |context|. nvim_get_current_buf() *nvim_get_current_buf()* - Gets the current buffer. + Gets the current buffer. - Return: ~ - Buffer handle + Return: ~ + Buffer handle nvim_get_current_line() *nvim_get_current_line()* - Gets the current line. + Gets the current line. - Return: ~ - Current line string + Return: ~ + Current line string nvim_get_current_tabpage() *nvim_get_current_tabpage()* - Gets the current tabpage. + Gets the current tabpage. - Return: ~ - Tabpage handle + Return: ~ + Tabpage handle nvim_get_current_win() *nvim_get_current_win()* - Gets the current window. + Gets the current window. - Return: ~ - Window handle + Return: ~ + Window handle nvim_get_hl_by_id({hl_id}, {rgb}) *nvim_get_hl_by_id()* - Gets a highlight definition by id. |hlID()| + Gets a highlight definition by id. |hlID()| - Parameters: ~ - {hl_id} Highlight id as returned by |hlID()| - {rgb} Export RGB colors + Parameters: ~ + {hl_id} Highlight id as returned by |hlID()| + {rgb} Export RGB colors - Return: ~ - Highlight definition map + Return: ~ + Highlight definition map - See also: ~ - nvim_get_hl_by_name + See also: ~ + nvim_get_hl_by_name nvim_get_hl_by_name({name}, {rgb}) *nvim_get_hl_by_name()* - Gets a highlight definition by name. + Gets a highlight definition by name. - Parameters: ~ - {name} Highlight group name - {rgb} Export RGB colors + Parameters: ~ + {name} Highlight group name + {rgb} Export RGB colors - Return: ~ - Highlight definition map + Return: ~ + Highlight definition map - See also: ~ - nvim_get_hl_by_id + See also: ~ + nvim_get_hl_by_id nvim_get_hl_id_by_name({name}) *nvim_get_hl_id_by_name()* - Gets a highlight group by name + Gets a highlight group by name - similar to |hlID()|, but allocates a new ID if not present. + similar to |hlID()|, but allocates a new ID if not present. nvim_get_keymap({mode}) *nvim_get_keymap()* - Gets a list of global (non-buffer-local) |mapping| - definitions. + Gets a list of global (non-buffer-local) |mapping| definitions. - Parameters: ~ - {mode} Mode short-name ("n", "i", "v", ...) + Parameters: ~ + {mode} Mode short-name ("n", "i", "v", ...) - Return: ~ - Array of |maparg()|-like dictionaries describing mappings. - The "buffer" key is always zero. + Return: ~ + Array of |maparg()|-like dictionaries describing mappings. The + "buffer" key is always zero. nvim_get_mark({name}, {opts}) *nvim_get_mark()* - Return a tuple (row, col, buffer, buffername) representing the - position of the uppercase/file named mark. See |mark-motions|. + Return a tuple (row, col, buffer, buffername) representing the position of + the uppercase/file named mark. See |mark-motions|. - Marks are (1,0)-indexed. |api-indexing| + Marks are (1,0)-indexed. |api-indexing| - Note: - fails with error if a lowercase or buffer local named mark - is used. + Note: + fails with error if a lowercase or buffer local named mark is used. - Parameters: ~ - {name} Mark name - {opts} Optional parameters. Reserved for future use. + Parameters: ~ + {name} Mark name + {opts} Optional parameters. Reserved for future use. - Return: ~ - 4-tuple (row, col, buffer, buffername), (0, 0, 0, '') if - the mark is not set. + Return: ~ + 4-tuple (row, col, buffer, buffername), (0, 0, 0, '') if the mark is + not set. - See also: ~ - |nvim_buf_set_mark()| - |nvim_del_mark()| + See also: ~ + |nvim_buf_set_mark()| + |nvim_del_mark()| nvim_get_mode() *nvim_get_mode()* - Gets the current mode. |mode()| "blocking" is true if Nvim is - waiting for input. + Gets the current mode. |mode()| "blocking" is true if Nvim is waiting for + input. - Return: ~ - Dictionary { "mode": String, "blocking": Boolean } + Return: ~ + Dictionary { "mode": String, "blocking": Boolean } - Attributes: ~ - |api-fast| + Attributes: ~ + |api-fast| nvim_get_proc({pid}) *nvim_get_proc()* - Gets info describing process `pid`. + Gets info describing process `pid`. - Return: ~ - Map of process properties, or NIL if process not found. + Return: ~ + Map of process properties, or NIL if process not found. nvim_get_proc_children({pid}) *nvim_get_proc_children()* - Gets the immediate children of process `pid`. + Gets the immediate children of process `pid`. - Return: ~ - Array of child process ids, empty if process not found. + Return: ~ + Array of child process ids, empty if process not found. nvim_get_runtime_file({name}, {all}) *nvim_get_runtime_file()* - Find files in runtime directories + Find files in runtime directories - 'name' can contain wildcards. For example - nvim_get_runtime_file("colors/*.vim", true) will return all - color scheme files. Always use forward slashes (/) in the - search pattern for subdirectories regardless of platform. + 'name' can contain wildcards. For example + nvim_get_runtime_file("colors/*.vim", true) will return all color scheme + files. Always use forward slashes (/) in the search pattern for + subdirectories regardless of platform. - It is not an error to not find any files. An empty array is - returned then. + It is not an error to not find any files. An empty array is returned then. - Attributes: ~ - |api-fast| + Attributes: ~ + |api-fast| - Parameters: ~ - {name} pattern of files to search for - {all} whether to return all matches or only the first + Parameters: ~ + {name} pattern of files to search for + {all} whether to return all matches or only the first - Return: ~ - list of absolute paths to the found files + Return: ~ + list of absolute paths to the found files nvim_get_var({name}) *nvim_get_var()* - Gets a global (g:) variable. + Gets a global (g:) variable. - Parameters: ~ - {name} Variable name + Parameters: ~ + {name} Variable name - Return: ~ - Variable value + Return: ~ + Variable value nvim_get_vvar({name}) *nvim_get_vvar()* - Gets a v: variable. + Gets a v: variable. - Parameters: ~ - {name} Variable name + Parameters: ~ + {name} Variable name - Return: ~ - Variable value + Return: ~ + Variable value nvim_input({keys}) *nvim_input()* - Queues raw user-input. Unlike |nvim_feedkeys()|, this uses a - low-level input buffer and the call is non-blocking (input is - processed asynchronously by the eventloop). + Queues raw user-input. Unlike |nvim_feedkeys()|, this uses a low-level + input buffer and the call is non-blocking (input is processed + asynchronously by the eventloop). - On execution error: does not fail, but updates v:errmsg. + On execution error: does not fail, but updates v:errmsg. - Note: - |keycodes| like <CR> are translated, so "<" is special. To - input a literal "<", send <LT>. + Note: + |keycodes| like <CR> are translated, so "<" is special. To input a + literal "<", send <LT>. - Note: - For mouse events use |nvim_input_mouse()|. The pseudokey - form "<LeftMouse><col,row>" is deprecated since - |api-level| 6. + Note: + For mouse events use |nvim_input_mouse()|. The pseudokey form + "<LeftMouse><col,row>" is deprecated since |api-level| 6. - Attributes: ~ - |api-fast| + Attributes: ~ + |api-fast| - Parameters: ~ - {keys} to be typed + Parameters: ~ + {keys} to be typed - Return: ~ - Number of bytes actually written (can be fewer than - requested if the buffer becomes full). + Return: ~ + Number of bytes actually written (can be fewer than requested if the + buffer becomes full). *nvim_input_mouse()* nvim_input_mouse({button}, {action}, {modifier}, {grid}, {row}, {col}) - Send mouse event from GUI. - - Non-blocking: does not wait on any result, but queues the - event to be processed soon by the event loop. - - Note: - Currently this doesn't support "scripting" multiple mouse - events by calling it multiple times in a loop: the - intermediate mouse positions will be ignored. It should be - used to implement real-time mouse input in a GUI. The - deprecated pseudokey form ("<LeftMouse><col,row>") of - |nvim_input()| has the same limitation. - - Attributes: ~ - |api-fast| - - Parameters: ~ - {button} Mouse button: one of "left", "right", - "middle", "wheel". - {action} For ordinary buttons, one of "press", "drag", - "release". For the wheel, one of "up", "down", - "left", "right". - {modifier} String of modifiers each represented by a - single char. The same specifiers are used as - for a key press, except that the "-" separator - is optional, so "C-A-", "c-a" and "CA" can all - be used to specify Ctrl+Alt+click. - {grid} Grid number if the client uses |ui-multigrid|, - else 0. - {row} Mouse row-position (zero-based, like redraw - events) - {col} Mouse column-position (zero-based, like redraw - events) + Send mouse event from GUI. + + Non-blocking: does not wait on any result, but queues the event to be + processed soon by the event loop. + + Note: + Currently this doesn't support "scripting" multiple mouse events by + calling it multiple times in a loop: the intermediate mouse positions + will be ignored. It should be used to implement real-time mouse input + in a GUI. The deprecated pseudokey form ("<LeftMouse><col,row>") of + |nvim_input()| has the same limitation. + + Attributes: ~ + |api-fast| + + Parameters: ~ + {button} Mouse button: one of "left", "right", "middle", "wheel". + {action} For ordinary buttons, one of "press", "drag", "release". + For the wheel, one of "up", "down", "left", "right". + {modifier} String of modifiers each represented by a single char. The + same specifiers are used as for a key press, except that + the "-" separator is optional, so "C-A-", "c-a" and "CA" + can all be used to specify Ctrl+Alt+click. + {grid} Grid number if the client uses |ui-multigrid|, else 0. + {row} Mouse row-position (zero-based, like redraw events) + {col} Mouse column-position (zero-based, like redraw events) nvim_list_bufs() *nvim_list_bufs()* - Gets the current list of buffer handles + Gets the current list of buffer handles - Includes unlisted (unloaded/deleted) buffers, like `:ls!`. Use - |nvim_buf_is_loaded()| to check if a buffer is loaded. + Includes unlisted (unloaded/deleted) buffers, like `:ls!`. Use + |nvim_buf_is_loaded()| to check if a buffer is loaded. - Return: ~ - List of buffer handles + Return: ~ + List of buffer handles nvim_list_chans() *nvim_list_chans()* - Get information about all open channels. + Get information about all open channels. - Return: ~ - Array of Dictionaries, each describing a channel with the - format specified at |nvim_get_chan_info()|. + Return: ~ + Array of Dictionaries, each describing a channel with the format + specified at |nvim_get_chan_info()|. nvim_list_runtime_paths() *nvim_list_runtime_paths()* - Gets the paths contained in 'runtimepath'. + Gets the paths contained in 'runtimepath'. - Return: ~ - List of paths + Return: ~ + List of paths nvim_list_tabpages() *nvim_list_tabpages()* - Gets the current list of tabpage handles. + Gets the current list of tabpage handles. - Return: ~ - List of tabpage handles + Return: ~ + List of tabpage handles nvim_list_uis() *nvim_list_uis()* - Gets a list of dictionaries representing attached UIs. + Gets a list of dictionaries representing attached UIs. - Return: ~ - Array of UI dictionaries, each with these keys: - • "height" Requested height of the UI - • "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) + Return: ~ + Array of UI dictionaries, each with these keys: + • "height" Requested height of the UI + • "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) nvim_list_wins() *nvim_list_wins()* - Gets the current list of window handles. + Gets the current list of window handles. - Return: ~ - List of window handles + Return: ~ + List of window handles nvim_load_context({dict}) *nvim_load_context()* - Sets the current editor state from the given |context| map. + Sets the current editor state from the given |context| map. - Parameters: ~ - {dict} |Context| map. + Parameters: ~ + {dict} |Context| map. nvim_notify({msg}, {log_level}, {opts}) *nvim_notify()* - Notify the user with a message + Notify the user with a message - Relays the call to vim.notify . By default forwards your - message in the echo area but can be overridden to trigger - desktop notifications. + Relays the call to vim.notify . By default forwards your message in the + echo area but can be overridden to trigger desktop notifications. - Parameters: ~ - {msg} Message to display to the user - {log_level} The log level - {opts} Reserved for future use. + Parameters: ~ + {msg} Message to display to the user + {log_level} The log level + {opts} Reserved for future use. nvim_open_term({buffer}, {opts}) *nvim_open_term()* - Open a terminal instance in a buffer - - By default (and currently the only option) the terminal will - not be connected to an external process. Instead, input send - on the channel will be echoed directly by the terminal. This - is useful to display ANSI terminal sequences returned as part - of a rpc message, or similar. - - Note: to directly initiate the terminal using the right size, - display the buffer in a configured window before calling this. - For instance, for a floating display, first create an empty - buffer using |nvim_create_buf()|, then display it using - |nvim_open_win()|, and then call this function. Then - |nvim_chan_send()| can be called immediately to process - sequences in a virtual terminal having the intended size. - - Parameters: ~ - {buffer} the buffer to use (expected to be empty) - {opts} Optional parameters. - • on_input: lua callback for input sent, i e - keypresses in terminal mode. Note: keypresses - are sent raw as they would be to the pty - master end. For instance, a carriage return is - sent as a "\r", not as a "\n". |textlock| - applies. It is possible to call - |nvim_chan_send| directly in the callback - however. ["input", term, bufnr, data] - - Return: ~ - Channel id, or 0 on error + Open a terminal instance in a buffer + + By default (and currently the only option) the terminal will not be + connected to an external process. Instead, input send on the channel will + be echoed directly by the terminal. This is useful to display ANSI + terminal sequences returned as part of a rpc message, or similar. + + Note: to directly initiate the terminal using the right size, display the + buffer in a configured window before calling this. For instance, for a + floating display, first create an empty buffer using |nvim_create_buf()|, + then display it using |nvim_open_win()|, and then call this function. Then + |nvim_chan_send()| can be called immediately to process sequences in a + virtual terminal having the intended size. + + Parameters: ~ + {buffer} the buffer to use (expected to be empty) + {opts} Optional parameters. + • on_input: lua callback for input sent, i e keypresses in + terminal mode. Note: keypresses are sent raw as they would + be to the pty master end. For instance, a carriage return + is sent as a "\r", not as a "\n". |textlock| applies. It + is possible to call |nvim_chan_send| directly in the + callback however. ["input", term, bufnr, data] + + Return: ~ + Channel id, or 0 on error nvim_out_write({str}) *nvim_out_write()* - Writes a message to the Vim output buffer. Does not append - "\n", the message is buffered (won't display) until a linefeed - is written. + Writes a message to the Vim output buffer. Does not append "\n", the + message is buffered (won't display) until a linefeed is written. - Parameters: ~ - {str} Message + Parameters: ~ + {str} Message nvim_paste({data}, {crlf}, {phase}) *nvim_paste()* - Pastes at cursor, in any mode. + Pastes at cursor, in any mode. - Invokes the `vim.paste` handler, which handles each mode - appropriately. Sets redo/undo. Faster than |nvim_input()|. - Lines break at LF ("\n"). + Invokes the `vim.paste` handler, which handles each mode appropriately. + Sets redo/undo. Faster than |nvim_input()|. Lines break at LF ("\n"). - Errors ('nomodifiable', `vim.paste()` failure, …) are - reflected in `err` but do not affect the return value (which - is strictly decided by `vim.paste()`). On error, subsequent - calls are ignored ("drained") until the next paste is - initiated (phase 1 or -1). + Errors ('nomodifiable', `vim.paste()` failure, …) are reflected in `err` + but do not affect the return value (which is strictly decided by + `vim.paste()`). On error, subsequent calls are ignored ("drained") until + the next paste is initiated (phase 1 or -1). - Attributes: ~ - not allowed when |textlock| is active + Attributes: ~ + not allowed when |textlock| is active - Parameters: ~ - {data} Multiline input. May be binary (containing NUL - bytes). - {crlf} Also break lines at CR and CRLF. - {phase} -1: paste in a single call (i.e. without - streaming). To "stream" a paste, call `nvim_paste` sequentially with these `phase` values: - • 1: starts the paste (exactly once) - • 2: continues the paste (zero or more times) - • 3: ends the paste (exactly once) + Parameters: ~ + {data} Multiline input. May be binary (containing NUL bytes). + {crlf} Also break lines at CR and CRLF. + {phase} -1: paste in a single call (i.e. without streaming). To + "stream" a paste, call `nvim_paste` sequentially with these `phase` values: + • 1: starts the paste (exactly once) + • 2: continues the paste (zero or more times) + • 3: ends the paste (exactly once) - Return: ~ + Return: ~ - • true: Client may continue pasting. - • false: Client must cancel the paste. + • true: Client may continue pasting. + • false: Client must cancel the paste. nvim_put({lines}, {type}, {after}, {follow}) *nvim_put()* - Puts text at cursor, in any mode. - - Compare |:put| and |p| which are always linewise. - - Attributes: ~ - not allowed when |textlock| is active - - Parameters: ~ - {lines} |readfile()|-style list of lines. - |channel-lines| - {type} Edit behavior: any |getregtype()| result, or: - • "b" |blockwise-visual| mode (may include - width, e.g. "b3") - • "c" |charwise| mode - • "l" |linewise| mode - • "" guess by contents, see |setreg()| - {after} If true insert after cursor (like |p|), or - before (like |P|). - {follow} If true place cursor at end of inserted text. + Puts text at cursor, in any mode. + + Compare |:put| and |p| which are always linewise. + + Attributes: ~ + not allowed when |textlock| is active + + Parameters: ~ + {lines} |readfile()|-style list of lines. |channel-lines| + {type} Edit behavior: any |getregtype()| result, or: + • "b" |blockwise-visual| mode (may include width, e.g. "b3") + • "c" |charwise| mode + • "l" |linewise| mode + • "" guess by contents, see |setreg()| + {after} If true insert after cursor (like |p|), or before (like + |P|). + {follow} If true place cursor at end of inserted text. *nvim_replace_termcodes()* nvim_replace_termcodes({str}, {from_part}, {do_lt}, {special}) - Replaces terminal codes and |keycodes| (<CR>, <Esc>, ...) in a - string with the internal representation. + Replaces terminal codes and |keycodes| (<CR>, <Esc>, ...) in a string with + the internal representation. - Parameters: ~ - {str} String to be converted. - {from_part} Legacy Vim parameter. Usually true. - {do_lt} Also translate <lt>. Ignored if `special` is - false. - {special} Replace |keycodes|, e.g. <CR> becomes a "\r" - char. + Parameters: ~ + {str} String to be converted. + {from_part} Legacy Vim parameter. Usually true. + {do_lt} Also translate <lt>. Ignored if `special` is false. + {special} Replace |keycodes|, e.g. <CR> becomes a "\r" char. - See also: ~ - replace_termcodes - cpoptions + See also: ~ + replace_termcodes + cpoptions *nvim_select_popupmenu_item()* nvim_select_popupmenu_item({item}, {insert}, {finish}, {opts}) - Selects an item in the completion popupmenu. - - 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. - - 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`. - {opts} Optional parameters. Reserved for future use. + Selects an item in the completion popupmenu. + + 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. + + 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`. + {opts} Optional parameters. Reserved for future use. *nvim_set_client_info()* nvim_set_client_info({name}, {version}, {type}, {methods}, {attributes}) - Self-identifies the client. - - The client/plugin/application should call this after - connecting, to provide hints about its identity and purpose, - for debugging and orchestration. - - Can be called more than once; the caller should merge old info - if appropriate. Example: library first identifies the channel, - then a plugin using that library later identifies itself. - - Note: - "Something is better than nothing". You don't need to - include all the fields. - - Attributes: ~ - |RPC| only - - Parameters: ~ - {name} Short name for the connected client - {version} Dictionary describing the version, with - these (optional) keys: - • "major" major version (defaults to 0 if - not set, for no release yet) - • "minor" minor version - • "patch" patch number - • "prerelease" string describing a - prerelease, like "dev" or "beta1" - • "commit" hash or similar identifier of - commit - {type} Must be one of the following values. Client - libraries should default to "remote" unless - overridden by the user. - • "remote" remote client connected to Nvim. - • "ui" gui frontend - • "embedder" application using Nvim as a - component (for example, IDE/editor - implementing a vim mode). - • "host" plugin host, typically started by - nvim - • "plugin" single plugin, started by nvim - {methods} Builtin methods in the client. For a host, - this does not include plugin methods which - will be discovered later. The key should be - the method name, the values are dicts with - these (optional) keys (more keys may be - added in future versions of Nvim, thus - unknown keys are ignored. Clients must only - use keys defined in this or later versions - of Nvim): - • "async" if true, send as a notification. - If false or unspecified, use a blocking - request - • "nargs" Number of arguments. Could be a - single integer or an array of two - integers, minimum and maximum inclusive. - {attributes} Arbitrary string:string map of informal - client properties. Suggested keys: - • "website": Client homepage URL (e.g. - GitHub repository) - • "license": License description ("Apache - 2", "GPLv3", "MIT", …) - • "logo": URI or path to image, preferably - small logo or icon. .png or .svg format is - preferred. + Self-identifies the client. + + The client/plugin/application should call this after connecting, to + provide hints about its identity and purpose, for debugging and + orchestration. + + Can be called more than once; the caller should merge old info if + appropriate. Example: library first identifies the channel, then a plugin + using that library later identifies itself. + + Note: + "Something is better than nothing". You don't need to include all the + fields. + + Attributes: ~ + |RPC| only + + Parameters: ~ + {name} Short name for the connected client + {version} Dictionary describing the version, with these (optional) + keys: + • "major" major version (defaults to 0 if not set, for + no release yet) + • "minor" minor version + • "patch" patch number + • "prerelease" string describing a prerelease, like + "dev" or "beta1" + • "commit" hash or similar identifier of commit + {type} Must be one of the following values. Client libraries + should default to "remote" unless overridden by the + user. + • "remote" remote client connected to Nvim. + • "ui" gui frontend + • "embedder" application using Nvim as a component (for + example, IDE/editor implementing a vim mode). + • "host" plugin host, typically started by nvim + • "plugin" single plugin, started by nvim + {methods} Builtin methods in the client. For a host, this does not + include plugin methods which will be discovered later. + The key should be the method name, the values are dicts + with these (optional) keys (more keys may be added in + future versions of Nvim, thus unknown keys are ignored. + Clients must only use keys defined in this or later + versions of Nvim): + • "async" if true, send as a notification. If false or + unspecified, use a blocking request + • "nargs" Number of arguments. Could be a single integer + or an array of two integers, minimum and maximum + inclusive. + {attributes} Arbitrary string:string map of informal client + properties. Suggested keys: + • "website": Client homepage URL (e.g. GitHub + repository) + • "license": License description ("Apache 2", "GPLv3", + "MIT", …) + • "logo": URI or path to image, preferably small logo or + icon. .png or .svg format is preferred. nvim_set_current_buf({buffer}) *nvim_set_current_buf()* - Sets the current buffer. + Sets the current buffer. - Attributes: ~ - not allowed when |textlock| is active + Attributes: ~ + not allowed when |textlock| is active - Parameters: ~ - {buffer} Buffer handle + Parameters: ~ + {buffer} Buffer handle nvim_set_current_dir({dir}) *nvim_set_current_dir()* - Changes the global working directory. + Changes the global working directory. - Parameters: ~ - {dir} Directory path + Parameters: ~ + {dir} Directory path nvim_set_current_line({line}) *nvim_set_current_line()* - Sets the current line. + Sets the current line. - Attributes: ~ - not allowed when |textlock| is active + Attributes: ~ + not allowed when |textlock| is active - Parameters: ~ - {line} Line contents + Parameters: ~ + {line} Line contents nvim_set_current_tabpage({tabpage}) *nvim_set_current_tabpage()* - Sets the current tabpage. + Sets the current tabpage. - Attributes: ~ - not allowed when |textlock| is active + Attributes: ~ + not allowed when |textlock| is active - Parameters: ~ - {tabpage} Tabpage handle + Parameters: ~ + {tabpage} Tabpage handle nvim_set_current_win({window}) *nvim_set_current_win()* - Sets the current window. + Sets the current window. - Attributes: ~ - not allowed when |textlock| is active + Attributes: ~ + not allowed when |textlock| is active - Parameters: ~ - {window} Window handle + Parameters: ~ + {window} Window handle nvim_set_hl({ns_id}, {name}, {*val}) *nvim_set_hl()* - Sets a highlight group. - - Note: - Unlike the `:highlight` command which can update a - highlight group, this function completely replaces the - definition. For example: `nvim_set_hl(0, 'Visual', {})` - will clear the highlight group 'Visual'. - - Note: - The fg and bg keys also accept the string values `"fg"` or - `"bg"` which act as aliases to the corresponding - foreground and background values of the Normal group. If - the Normal group has not been defined, using these values - results in an error. - - Parameters: ~ - {ns_id} Namespace id for this highlight - |nvim_create_namespace()|. Use 0 to set a - highlight group globally |:highlight|. - {name} Highlight group name, e.g. "ErrorMsg" - {val} Highlight definition map, accepts the following - keys: - • fg (or foreground): color name or "#RRGGBB", - see note. - • bg (or background): color name or "#RRGGBB", - see note. - • sp (or special): color name or "#RRGGBB" - • blend: integer between 0 and 100 - • bold: boolean - • standout: boolean - • underline: boolean - • undercurl: boolean - • underdouble: boolean - • underdotted: boolean - • underdashed: boolean - • strikethrough: boolean - • italic: boolean - • reverse: boolean - • nocombine: boolean - • link: name of another highlight group to link - to, see |:hi-link|. - • default: Don't override existing definition - |:hi-default| - • ctermfg: Sets foreground of cterm color - |highlight-ctermfg| - • ctermbg: Sets background of cterm color - |highlight-ctermbg| - • cterm: cterm attribute map, like - |highlight-args|. If not set, cterm attributes - will match those from the attribute map - documented above. + Sets a highlight group. + + Note: + Unlike the `:highlight` command which can update a highlight group, + this function completely replaces the definition. For example: + `nvim_set_hl(0, 'Visual', {})` will clear the highlight group + 'Visual'. + + Note: + The fg and bg keys also accept the string values `"fg"` or `"bg"` + which act as aliases to the corresponding foreground and background + values of the Normal group. If the Normal group has not been defined, + using these values results in an error. + + Parameters: ~ + {ns_id} Namespace id for this highlight |nvim_create_namespace()|. + Use 0 to set a highlight group globally |:highlight|. + {name} Highlight group name, e.g. "ErrorMsg" + {val} Highlight definition map, accepts the following keys: + • fg (or foreground): color name or "#RRGGBB", see note. + • bg (or background): color name or "#RRGGBB", see note. + • sp (or special): color name or "#RRGGBB" + • blend: integer between 0 and 100 + • bold: boolean + • standout: boolean + • underline: boolean + • undercurl: boolean + • underdouble: boolean + • underdotted: boolean + • underdashed: boolean + • strikethrough: boolean + • italic: boolean + • reverse: boolean + • nocombine: boolean + • link: name of another highlight group to link to, see + |:hi-link|. + • default: Don't override existing definition |:hi-default| + • ctermfg: Sets foreground of cterm color |highlight-ctermfg| + • ctermbg: Sets background of cterm color |highlight-ctermbg| + • cterm: cterm attribute map, like |highlight-args|. If not + set, cterm attributes will match those from the attribute + map documented above. + +nvim_set_hl_ns({ns_id}) *nvim_set_hl_ns()* + Set active namespace for highlights. This can be set for a single window, + see |nvim_win_set_hl_ns|. + + Parameters: ~ + {ns_id} the namespace to use + +nvim_set_hl_ns_fast({ns_id}) *nvim_set_hl_ns_fast()* + Set active namespace for highlights while redrawing. + + This function meant to be called while redrawing, primarily from + |nvim_set_decoration_provider| on_win and on_line callbacks, which are + allowed to change the namespace during a redraw cycle. + + Attributes: ~ + |api-fast| + + Parameters: ~ + {ns_id} the namespace to activate nvim_set_keymap({mode}, {lhs}, {rhs}, {*opts}) *nvim_set_keymap()* - Sets a global |mapping| for the given mode. + Sets a global |mapping| for the given mode. - To set a buffer-local mapping, use |nvim_buf_set_keymap()|. + To set a buffer-local mapping, use |nvim_buf_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. + Unlike |:map|, leading/trailing whitespace is accepted as part of the + {lhs} or {rhs}. Empty {rhs} is |<Nop>|. |keycodes| are replaced as usual. - Example: > - call nvim_set_keymap('n', ' <NL>', '', {'nowait': v:true}) + Example: > + call nvim_set_keymap('n', ' <NL>', '', {'nowait': v:true}) < - is equivalent to: > - nmap <nowait> <Space><NL> <Nop> + is equivalent to: > + nmap <nowait> <Space><NL> <Nop> < - Parameters: ~ - {mode} Mode short-name (map command prefix: "n", "i", - "v", "x", …) or "!" for |:map!|, or empty string - for |:map|. - {lhs} Left-hand-side |{lhs}| of the mapping. - {rhs} Right-hand-side |{rhs}| of the mapping. - {opts} Optional parameters map: keys are - |:map-arguments|, values are booleans (default - false). Accepts all |:map-arguments| as keys - excluding |<buffer>| but including |noremap| and - "desc". Unknown key is an error. "desc" can be - used to give a description to the mapping. When - called from Lua, also accepts a "callback" key - that takes a Lua function to call when the mapping - is executed. When "expr" is true, - "replace_keycodes" (boolean) can be used to - replace keycodes in the resulting string (see - |nvim_replace_termcodes()|), and a Lua callback - returning `nil` is equivalent to returning an - empty string. + Parameters: ~ + {mode} Mode short-name (map command prefix: "n", "i", "v", "x", …) or + "!" for |:map!|, or empty string for |:map|. + {lhs} Left-hand-side |{lhs}| of the mapping. + {rhs} Right-hand-side |{rhs}| of the mapping. + {opts} Optional parameters map: keys are |:map-arguments|, values are + booleans (default false). Accepts all |:map-arguments| as keys + excluding |<buffer>| but including |noremap| and "desc". + Unknown key is an error. "desc" can be used to give a + description to the mapping. When called from Lua, also accepts + a "callback" key that takes a Lua function to call when the + mapping is executed. When "expr" is true, "replace_keycodes" + (boolean) can be used to replace keycodes in the resulting + string (see |nvim_replace_termcodes()|), and a Lua callback + returning `nil` is equivalent to returning an empty string. nvim_set_var({name}, {value}) *nvim_set_var()* - Sets a global (g:) variable. + Sets a global (g:) variable. - Parameters: ~ - {name} Variable name - {value} Variable value + Parameters: ~ + {name} Variable name + {value} Variable value nvim_set_vvar({name}, {value}) *nvim_set_vvar()* - Sets a v: variable, if it is not readonly. + Sets a v: variable, if it is not readonly. - Parameters: ~ - {name} Variable name - {value} Variable value + Parameters: ~ + {name} Variable name + {value} Variable value nvim_strwidth({text}) *nvim_strwidth()* - Calculates the number of display cells occupied by `text`. - Control characters including <Tab> count as one cell. + Calculates the number of display cells occupied by `text`. Control + characters including <Tab> count as one cell. - Parameters: ~ - {text} Some text + Parameters: ~ + {text} Some text - Return: ~ - Number of cells + Return: ~ + Number of cells nvim_subscribe({event}) *nvim_subscribe()* - Subscribes to event broadcasts. + Subscribes to event broadcasts. - Attributes: ~ - |RPC| only + Attributes: ~ + |RPC| only - Parameters: ~ - {event} Event type string + Parameters: ~ + {event} Event type string nvim_unsubscribe({event}) *nvim_unsubscribe()* - Unsubscribes to event broadcasts. + Unsubscribes to event broadcasts. - Attributes: ~ - |RPC| only + Attributes: ~ + |RPC| only - Parameters: ~ - {event} Event type string + Parameters: ~ + {event} Event type string ============================================================================== @@ -1563,178 +1500,157 @@ Vimscript Functions *api-vimscript* *nvim_call_dict_function()* nvim_call_dict_function({dict}, {fn}, {args}) - Calls a VimL |Dictionary-function| with the given arguments. + Calls a VimL |Dictionary-function| with the given arguments. - On execution error: fails with VimL error, updates v:errmsg. + On execution error: fails with VimL error, updates v:errmsg. - Parameters: ~ - {dict} Dictionary, or String evaluating to a VimL |self| - dict - {fn} Name of the function defined on the VimL dict - {args} Function arguments packed in an Array + Parameters: ~ + {dict} Dictionary, or String evaluating to a VimL |self| dict + {fn} Name of the function defined on the VimL dict + {args} Function arguments packed in an Array - Return: ~ - Result of the function call + Return: ~ + Result of the function call nvim_call_function({fn}, {args}) *nvim_call_function()* - Calls a VimL function with the given arguments. + Calls a VimL function with the given arguments. - On execution error: fails with VimL error, updates v:errmsg. + On execution error: fails with VimL error, updates v:errmsg. - Parameters: ~ - {fn} Function to call - {args} Function arguments packed in an Array + Parameters: ~ + {fn} Function to call + {args} Function arguments packed in an Array - Return: ~ - Result of the function call + Return: ~ + Result of the function call nvim_command({command}) *nvim_command()* - Executes an Ex command. + Executes an Ex command. - On execution error: fails with VimL error, updates v:errmsg. + On execution error: fails with VimL error, updates v:errmsg. - Prefer using |nvim_cmd()| or |nvim_exec()| over this. To - evaluate multiple lines of Vim script or an Ex command - directly, use |nvim_exec()|. To construct an Ex command using - a structured format and then execute it, use |nvim_cmd()|. To - modify an Ex command before evaluating it, use - |nvim_parse_cmd()| in conjunction with |nvim_cmd()|. + Prefer using |nvim_cmd()| or |nvim_exec()| over this. To evaluate multiple + lines of Vim script or an Ex command directly, use |nvim_exec()|. To + construct an Ex command using a structured format and then execute it, use + |nvim_cmd()|. To modify an Ex command before evaluating it, use + |nvim_parse_cmd()| in conjunction with |nvim_cmd()|. - Parameters: ~ - {command} Ex command string + Parameters: ~ + {command} Ex command string nvim_eval({expr}) *nvim_eval()* - Evaluates a VimL |expression|. Dictionaries and Lists are - recursively expanded. + Evaluates a VimL |expression|. Dictionaries and Lists are recursively + expanded. - On execution error: fails with VimL error, updates v:errmsg. + On execution error: fails with VimL error, updates v:errmsg. - Parameters: ~ - {expr} VimL expression string + Parameters: ~ + {expr} VimL expression string - Return: ~ - Evaluation result or expanded object + Return: ~ + Evaluation result or expanded object nvim_exec({src}, {output}) *nvim_exec()* - Executes Vimscript (multiline block of Ex commands), like - anonymous |:source|. + Executes Vimscript (multiline block of Ex commands), like anonymous + |:source|. - Unlike |nvim_command()| this function supports heredocs, - script-scope (s:), etc. + Unlike |nvim_command()| this function supports heredocs, script-scope + (s:), etc. - On execution error: fails with VimL error, updates v:errmsg. + On execution error: fails with VimL error, updates v:errmsg. - Parameters: ~ - {src} Vimscript code - {output} Capture and return all (non-error, non-shell - |:!|) output + Parameters: ~ + {src} Vimscript code + {output} Capture and return all (non-error, non-shell |:!|) output - Return: ~ - Output (non-error, non-shell |:!|) if `output` is true, - else empty string. + Return: ~ + Output (non-error, non-shell |:!|) if `output` is true, else empty + string. - See also: ~ - |execute()| - |nvim_command()| - |nvim_cmd()| + See also: ~ + |execute()| + |nvim_command()| + |nvim_cmd()| *nvim_parse_expression()* nvim_parse_expression({expr}, {flags}, {highlight}) - Parse a VimL expression. - - Attributes: ~ - |api-fast| - - Parameters: ~ - {expr} Expression to parse. Always treated as a - single line. - {flags} Flags: - • "m" if multiple expressions in a row are - allowed (only the first one will be - parsed), - • "E" if EOC tokens are not allowed - (determines whether they will stop parsing - process or be recognized as an - operator/space, though also yielding an - error). - • "l" when needing to start parsing with - lvalues for ":let" or ":for". Common flag - sets: - • "m" to parse like for ":echo". - • "E" to parse like for "<C-r>=". - • empty string for ":call". - • "lm" to parse for ":let". - {highlight} If true, return value will also include - "highlight" key containing array of 4-tuples - (arrays) (Integer, Integer, Integer, String), - where first three numbers define the - highlighted region and represent line, - starting column and ending column (latter - exclusive: one should highlight region - [start_col, end_col)). - - Return: ~ - - • AST: top-level dictionary with these keys: - • "error": Dictionary with error, present only if parser - saw some error. Contains the following keys: - • "message": String, error message in printf format, - translated. Must contain exactly one "%.*s". - • "arg": String, error message argument. - - • "len": Amount of bytes successfully parsed. With flags - equal to "" that should be equal to the length of expr - string. (“Successfully parsed” here means - “participated in AST creation”, not “till the first - error”.) - • "ast": AST, either nil or a dictionary with these - keys: - • "type": node type, one of the value names from - ExprASTNodeType stringified without "kExprNode" - prefix. - • "start": a pair [line, column] describing where node - is "started" where "line" is always 0 (will not be 0 - if you will be using nvim_parse_viml() on e.g. - ":let", but that is not present yet). Both elements - are Integers. - • "len": “length” of the node. This and "start" are - there for debugging purposes primary (debugging - parser and providing debug information). - • "children": a list of nodes described in top/"ast". - There always is zero, one or two children, key will - not be present if node has no children. Maximum - number of children may be found in node_maxchildren - array. - - • Local values (present only for certain nodes): - • "scope": a single Integer, specifies scope for - "Option" and "PlainIdentifier" nodes. For "Option" it - is one of ExprOptScope values, for "PlainIdentifier" - it is one of ExprVarScope values. - • "ident": identifier (without scope, if any), present - for "Option", "PlainIdentifier", "PlainKey" and - "Environment" nodes. - • "name": Integer, register name (one character) or -1. - Only present for "Register" nodes. - • "cmp_type": String, comparison type, one of the value - names from ExprComparisonType, stringified without - "kExprCmp" prefix. Only present for "Comparison" - nodes. - • "ccs_strategy": String, case comparison strategy, one - of the value names from ExprCaseCompareStrategy, - stringified without "kCCStrategy" prefix. Only present - for "Comparison" nodes. - • "augmentation": String, augmentation type for - "Assignment" nodes. Is either an empty string, "Add", - "Subtract" or "Concat" for "=", "+=", "-=" or ".=" - respectively. - • "invert": Boolean, true if result of comparison needs - to be inverted. Only present for "Comparison" nodes. - • "ivalue": Integer, integer value for "Integer" nodes. - • "fvalue": Float, floating-point value for "Float" - nodes. - • "svalue": String, value for "SingleQuotedString" and - "DoubleQuotedString" nodes. + Parse a VimL expression. + + Attributes: ~ + |api-fast| + + Parameters: ~ + {expr} Expression to parse. Always treated as a single line. + {flags} Flags: + • "m" if multiple expressions in a row are allowed (only + the first one will be parsed), + • "E" if EOC tokens are not allowed (determines whether + they will stop parsing process or be recognized as an + operator/space, though also yielding an error). + • "l" when needing to start parsing with lvalues for + ":let" or ":for". Common flag sets: + • "m" to parse like for ":echo". + • "E" to parse like for "<C-r>=". + • empty string for ":call". + • "lm" to parse for ":let". + {highlight} If true, return value will also include "highlight" key + containing array of 4-tuples (arrays) (Integer, Integer, + Integer, String), where first three numbers define the + highlighted region and represent line, starting column + and ending column (latter exclusive: one should highlight + region [start_col, end_col)). + + Return: ~ + + • AST: top-level dictionary with these keys: + • "error": Dictionary with error, present only if parser saw some + error. Contains the following keys: + • "message": String, error message in printf format, translated. + Must contain exactly one "%.*s". + • "arg": String, error message argument. + + • "len": Amount of bytes successfully parsed. With flags equal to "" + that should be equal to the length of expr string. (“Successfully + parsed” here means “participated in AST creation”, not “till the + first error”.) + • "ast": AST, either nil or a dictionary with these keys: + • "type": node type, one of the value names from ExprASTNodeType + stringified without "kExprNode" prefix. + • "start": a pair [line, column] describing where node is + "started" where "line" is always 0 (will not be 0 if you will be + using nvim_parse_viml() on e.g. ":let", but that is not present + yet). Both elements are Integers. + • "len": “length” of the node. This and "start" are there for + debugging purposes primary (debugging parser and providing debug + information). + • "children": a list of nodes described in top/"ast". There always + is zero, one or two children, key will not be present if node + has no children. Maximum number of children may be found in + node_maxchildren array. + + • Local values (present only for certain nodes): + • "scope": a single Integer, specifies scope for "Option" and + "PlainIdentifier" nodes. For "Option" it is one of ExprOptScope + values, for "PlainIdentifier" it is one of ExprVarScope values. + • "ident": identifier (without scope, if any), present for "Option", + "PlainIdentifier", "PlainKey" and "Environment" nodes. + • "name": Integer, register name (one character) or -1. Only present + for "Register" nodes. + • "cmp_type": String, comparison type, one of the value names from + ExprComparisonType, stringified without "kExprCmp" prefix. Only + present for "Comparison" nodes. + • "ccs_strategy": String, case comparison strategy, one of the value + names from ExprCaseCompareStrategy, stringified without + "kCCStrategy" prefix. Only present for "Comparison" nodes. + • "augmentation": String, augmentation type for "Assignment" nodes. + Is either an empty string, "Add", "Subtract" or "Concat" for "=", + "+=", "-=" or ".=" respectively. + • "invert": Boolean, true if result of comparison needs to be + inverted. Only present for "Comparison" nodes. + • "ivalue": Integer, integer value for "Integer" nodes. + • "fvalue": Float, floating-point value for "Float" nodes. + • "svalue": String, value for "SingleQuotedString" and + "DoubleQuotedString" nodes. ============================================================================== @@ -1742,351 +1658,328 @@ Command Functions *api-command* *nvim_buf_create_user_command()* nvim_buf_create_user_command({buffer}, {name}, {command}, {*opts}) - Create a new user command |user-commands| in the given buffer. + Create a new user command |user-commands| in the given buffer. - Parameters: ~ - {buffer} Buffer handle, or 0 for current buffer. + Parameters: ~ + {buffer} Buffer handle, or 0 for current buffer. - See also: ~ - nvim_create_user_command + See also: ~ + nvim_create_user_command *nvim_buf_del_user_command()* nvim_buf_del_user_command({buffer}, {name}) - Delete a buffer-local user-defined command. + Delete a buffer-local user-defined command. - Only commands created with |:command-buffer| or - |nvim_buf_create_user_command()| can be deleted with this - function. + Only commands created with |:command-buffer| or + |nvim_buf_create_user_command()| can be deleted with this function. - Parameters: ~ - {buffer} Buffer handle, or 0 for current buffer. - {name} Name of the command to delete. + Parameters: ~ + {buffer} Buffer handle, or 0 for current buffer. + {name} Name of the command to delete. nvim_buf_get_commands({buffer}, {*opts}) *nvim_buf_get_commands()* - Gets a map of buffer-local |user-commands|. + Gets a map of buffer-local |user-commands|. - Parameters: ~ - {buffer} Buffer handle, or 0 for current buffer - {opts} Optional parameters. Currently not used. + Parameters: ~ + {buffer} Buffer handle, or 0 for current buffer + {opts} Optional parameters. Currently not used. - Return: ~ - Map of maps describing commands. + Return: ~ + Map of maps describing commands. nvim_cmd({*cmd}, {*opts}) *nvim_cmd()* - Executes an Ex command. - - Unlike |nvim_command()| this command takes a structured - Dictionary instead of a String. This allows for easier - construction and manipulation of an Ex command. This also - allows for things such as having spaces inside a command - argument, expanding filenames in a command that otherwise - doesn't expand filenames, etc. - - On execution error: fails with VimL error, updates v:errmsg. - - Parameters: ~ - {cmd} Command to execute. Must be a Dictionary that can - contain the same values as the return value of - |nvim_parse_cmd()| except "addr", "nargs" and - "nextcmd" which are ignored if provided. All - values except for "cmd" are optional. - {opts} Optional parameters. - • output: (boolean, default false) Whether to - return command output. - - Return: ~ - Command output (non-error, non-shell |:!|) if `output` is - true, else empty string. - - See also: ~ - |nvim_exec()| - |nvim_command()| + Executes an Ex command. + + Unlike |nvim_command()| this command takes a structured Dictionary instead + of a String. This allows for easier construction and manipulation of an Ex + command. This also allows for things such as having spaces inside a + command argument, expanding filenames in a command that otherwise doesn't + expand filenames, etc. + + On execution error: fails with VimL error, updates v:errmsg. + + Parameters: ~ + {cmd} Command to execute. Must be a Dictionary that can contain the + same values as the return value of |nvim_parse_cmd()| except + "addr", "nargs" and "nextcmd" which are ignored if provided. + All values except for "cmd" are optional. + {opts} Optional parameters. + • output: (boolean, default false) Whether to return command + output. + + Return: ~ + Command output (non-error, non-shell |:!|) if `output` is true, else + empty string. + + See also: ~ + |nvim_exec()| + |nvim_command()| *nvim_create_user_command()* nvim_create_user_command({name}, {command}, {*opts}) - Create a new user command |user-commands| + Create a new user command |user-commands| - {name} is the name of the new command. The name must begin - with an uppercase letter. + {name} is the name of the new command. The name must begin with an + uppercase letter. - {command} is the replacement text or Lua function to execute. + {command} is the replacement text or Lua function to execute. - Example: > - :call nvim_create_user_command('SayHello', 'echo "Hello world!"', {}) - :SayHello - Hello world! + Example: > + :call nvim_create_user_command('SayHello', 'echo "Hello world!"', {}) + :SayHello + Hello world! < - Parameters: ~ - {name} Name of the new user command. Must begin with - an uppercase letter. - {command} Replacement command to execute when this user - command is executed. When called from Lua, the - command can also be a Lua function. The - function is called with a single table argument - that contains the following keys: - • args: (string) The args passed to the - command, if any |<args>| - • fargs: (table) The args split by unescaped - whitespace (when more than one argument is - allowed), if any |<f-args>| - • bang: (boolean) "true" if the command was - executed with a ! modifier |<bang>| - • line1: (number) The starting line of the - command range |<line1>| - • line2: (number) The final line of the command - range |<line2>| - • range: (number) The number of items in the - command range: 0, 1, or 2 |<range>| - • count: (number) Any count supplied |<count>| - • reg: (string) The optional register, if - specified |<reg>| - • mods: (string) Command modifiers, if any - |<mods>| - • smods: (table) Command modifiers in a - structured format. Has the same structure as - the "mods" key of |nvim_parse_cmd()|. - {opts} Optional command attributes. See - |command-attributes| for more details. To use - boolean attributes (such as |:command-bang| or - |:command-bar|) set the value to "true". In - addition to the string options listed in - |:command-complete|, the "complete" key also - accepts a Lua function which works like the - "customlist" completion mode - |:command-completion-customlist|. Additional - parameters: - • desc: (string) Used for listing the command - when a Lua function is used for {command}. - • force: (boolean, default true) Override any - previous definition. - • preview: (function) Preview callback for - 'inccommand' |:command-preview| + Parameters: ~ + {name} Name of the new user command. Must begin with an uppercase + letter. + {command} Replacement command to execute when this user command is + executed. When called from Lua, the command can also be a + Lua function. The function is called with a single table + argument that contains the following keys: + • args: (string) The args passed to the command, if any + |<args>| + • fargs: (table) The args split by unescaped whitespace + (when more than one argument is allowed), if any + |<f-args>| + • bang: (boolean) "true" if the command was executed with a + ! modifier |<bang>| + • line1: (number) The starting line of the command range + |<line1>| + • line2: (number) The final line of the command range + |<line2>| + • range: (number) The number of items in the command range: + 0, 1, or 2 |<range>| + • count: (number) Any count supplied |<count>| + • reg: (string) The optional register, if specified |<reg>| + • mods: (string) Command modifiers, if any |<mods>| + • smods: (table) Command modifiers in a structured format. + Has the same structure as the "mods" key of + |nvim_parse_cmd()|. + {opts} Optional command attributes. See |command-attributes| for + more details. To use boolean attributes (such as + |:command-bang| or |:command-bar|) set the value to "true". + In addition to the string options listed in + |:command-complete|, the "complete" key also accepts a Lua + function which works like the "customlist" completion mode + |:command-completion-customlist|. Additional parameters: + • desc: (string) Used for listing the command when a Lua + function is used for {command}. + • force: (boolean, default true) Override any previous + definition. + • preview: (function) Preview callback for 'inccommand' + |:command-preview| nvim_del_user_command({name}) *nvim_del_user_command()* - Delete a user-defined command. + Delete a user-defined command. - Parameters: ~ - {name} Name of the command to delete. + Parameters: ~ + {name} Name of the command to delete. nvim_get_commands({*opts}) *nvim_get_commands()* - Gets a map of global (non-buffer-local) Ex commands. + Gets a map of global (non-buffer-local) Ex commands. - Currently only |user-commands| are supported, not builtin Ex - commands. + Currently only |user-commands| are supported, not builtin Ex commands. - Parameters: ~ - {opts} Optional parameters. Currently only supports - {"builtin":false} + Parameters: ~ + {opts} Optional parameters. Currently only supports {"builtin":false} - Return: ~ - Map of maps describing commands. + Return: ~ + Map of maps describing commands. nvim_parse_cmd({str}, {opts}) *nvim_parse_cmd()* - Parse command line. - - Doesn't check the validity of command arguments. - - Attributes: ~ - |api-fast| - - Parameters: ~ - {str} Command line string to parse. Cannot contain "\n". - {opts} Optional parameters. Reserved for future use. - - Return: ~ - Dictionary containing command information, with these - keys: - • cmd: (string) Command name. - • range: (array) Command <range>. Can have 0-2 elements - depending on how many items the range contains. Has no - elements if command doesn't accept a range or if no - range was specified, one element if only a single range - item was specified and two elements if both range items - were specified. - • count: (number) Any |<count>| that was supplied to the - command. -1 if command cannot take a count. - • reg: (number) The optional command |<register>|, if - specified. Empty string if not specified or 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. - • 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. - • magic: (dictionary) Which characters have special - meaning in the command arguments. - • file: (boolean) The command expands filenames. Which - means characters such as "%", "#" and wildcards are - expanded. - • bar: (boolean) The "|" character is treated as a - command separator and the double quote character (") - is treated as the start of a comment. - - • mods: (dictionary) |:command-modifiers|. - • filter: (dictionary) |:filter|. - • pattern: (string) Filter pattern. Empty string if - there is no filter. - • force: (boolean) Whether filter is inverted or not. - - • silent: (boolean) |:silent|. - • emsg_silent: (boolean) |:silent!|. - • unsilent: (boolean) |:unsilent|. - • sandbox: (boolean) |:sandbox|. - • noautocmd: (boolean) |:noautocmd|. - • browse: (boolean) |:browse|. - • confirm: (boolean) |:confirm|. - • hide: (boolean) |:hide|. - • keepalt: (boolean) |:keepalt|. - • keepjumps: (boolean) |:keepjumps|. - • keepmarks: (boolean) |:keepmarks|. - • keeppatterns: (boolean) |:keeppatterns|. - • lockmarks: (boolean) |:lockmarks|. - • noswapfile: (boolean) |:noswapfile|. - • tab: (integer) |:tab|. - • verbose: (integer) |:verbose|. -1 when omitted. - • vertical: (boolean) |:vertical|. - • split: (string) Split modifier string, is an empty - string when there's no split modifier. If there is a - split modifier it can be one of: - • "aboveleft": |:aboveleft|. - • "belowright": |:belowright|. - • "topleft": |:topleft|. - • "botright": |:botright|. + Parse command line. + + Doesn't check the validity of command arguments. + + Attributes: ~ + |api-fast| + + Parameters: ~ + {str} Command line string to parse. Cannot contain "\n". + {opts} Optional parameters. Reserved for future use. + + Return: ~ + Dictionary containing command information, with these keys: + • cmd: (string) Command name. + • range: (array) Command <range>. Can have 0-2 elements depending on + how many items the range contains. Has no elements if command + doesn't accept a range or if no range was specified, one element if + only a single range item was specified and two elements if both + range items were specified. + • count: (number) Any |<count>| that was supplied to the command. -1 + if command cannot take a count. + • reg: (number) The optional command |<register>|, if specified. Empty + string if not specified or 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. + • 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. + • magic: (dictionary) Which characters have special meaning in the + command arguments. + • file: (boolean) The command expands filenames. Which means + characters such as "%", "#" and wildcards are expanded. + • bar: (boolean) The "|" character is treated as a command separator + and the double quote character (") is treated as the start of a + comment. + + • mods: (dictionary) |:command-modifiers|. + • filter: (dictionary) |:filter|. + • pattern: (string) Filter pattern. Empty string if there is no + filter. + • force: (boolean) Whether filter is inverted or not. + + • silent: (boolean) |:silent|. + • emsg_silent: (boolean) |:silent!|. + • unsilent: (boolean) |:unsilent|. + • sandbox: (boolean) |:sandbox|. + • noautocmd: (boolean) |:noautocmd|. + • browse: (boolean) |:browse|. + • confirm: (boolean) |:confirm|. + • hide: (boolean) |:hide|. + • keepalt: (boolean) |:keepalt|. + • keepjumps: (boolean) |:keepjumps|. + • keepmarks: (boolean) |:keepmarks|. + • keeppatterns: (boolean) |:keeppatterns|. + • lockmarks: (boolean) |:lockmarks|. + • noswapfile: (boolean) |:noswapfile|. + • tab: (integer) |:tab|. + • verbose: (integer) |:verbose|. -1 when omitted. + • vertical: (boolean) |:vertical|. + • split: (string) Split modifier string, is an empty string when + there's no split modifier. If there is a split modifier it can be + one of: + • "aboveleft": |:aboveleft|. + • "belowright": |:belowright|. + • "topleft": |:topleft|. + • "botright": |:botright|. ============================================================================== Options Functions *api-options* nvim_buf_get_option({buffer}, {name}) *nvim_buf_get_option()* - Gets a buffer option value + Gets a buffer option value - Parameters: ~ - {buffer} Buffer handle, or 0 for current buffer - {name} Option name + Parameters: ~ + {buffer} Buffer handle, or 0 for current buffer + {name} Option name - Return: ~ - Option value + Return: ~ + Option value nvim_buf_set_option({buffer}, {name}, {value}) *nvim_buf_set_option()* - Sets a buffer option value. Passing `nil` as value deletes the - option (only works if there's a global fallback) + Sets a buffer option value. Passing `nil` as value deletes the option + (only works if there's a global fallback) - Parameters: ~ - {buffer} Buffer handle, or 0 for current buffer - {name} Option name - {value} Option value + Parameters: ~ + {buffer} Buffer handle, or 0 for current buffer + {name} Option name + {value} Option value nvim_get_all_options_info() *nvim_get_all_options_info()* - Gets the option information for all options. + Gets the option information for all options. - The dictionary has the full option names as keys and option - metadata dictionaries as detailed at |nvim_get_option_info|. + The dictionary has the full option names as keys and option metadata + dictionaries as detailed at |nvim_get_option_info|. - Return: ~ - dictionary of all options + Return: ~ + dictionary of all options nvim_get_option({name}) *nvim_get_option()* - Gets the global value of an option. + Gets the global value of an option. - Parameters: ~ - {name} Option name + Parameters: ~ + {name} Option name - Return: ~ - Option value (global) + Return: ~ + Option value (global) nvim_get_option_info({name}) *nvim_get_option_info()* - Gets the option information for one option - - Resulting dictionary has keys: - • name: Name of the option (like 'filetype') - • shortname: Shortened name of the option (like 'ft') - • type: type of option ("string", "number" or "boolean") - • default: The default value for the option - • was_set: Whether the option was set. - • last_set_sid: Last set script id (if any) - • last_set_linenr: line number where option was set - • last_set_chan: Channel where option was set (0 for local) - • scope: one of "global", "win", or "buf" - • global_local: whether win or buf option has a global value - • commalist: List of comma separated values - • flaglist: List of single char flags - - Parameters: ~ - {name} Option name - - Return: ~ - Option Information + Gets the option information for one option + + Resulting dictionary has keys: + • name: Name of the option (like 'filetype') + • shortname: Shortened name of the option (like 'ft') + • type: type of option ("string", "number" or "boolean") + • default: The default value for the option + • was_set: Whether the option was set. + • last_set_sid: Last set script id (if any) + • last_set_linenr: line number where option was set + • last_set_chan: Channel where option was set (0 for local) + • scope: one of "global", "win", or "buf" + • global_local: whether win or buf option has a global value + • commalist: List of comma separated values + • flaglist: List of single char flags + + Parameters: ~ + {name} Option name + + Return: ~ + Option Information nvim_get_option_value({name}, {*opts}) *nvim_get_option_value()* - Gets the value of an option. The behavior of this function - matches that of |:set|: the local value of an option is - returned if it exists; otherwise, the global value is - returned. Local values always correspond to the current buffer - or window, unless "buf" or "win" is set in {opts}. - - Parameters: ~ - {name} Option name - {opts} Optional parameters - • scope: One of "global" or "local". Analogous to - |:setglobal| and |:setlocal|, respectively. - • win: |window-ID|. Used for getting window local - options. - • buf: Buffer number. Used for getting buffer - local options. Implies {scope} is "local". - - Return: ~ - Option value + Gets the value of an option. The behavior of this function matches that of + |:set|: the local value of an option is returned if it exists; otherwise, + the global value is returned. Local values always correspond to the + current buffer or window, unless "buf" or "win" is set in {opts}. + + Parameters: ~ + {name} Option name + {opts} Optional parameters + • scope: One of "global" or "local". Analogous to |:setglobal| + and |:setlocal|, respectively. + • win: |window-ID|. Used for getting window local options. + • buf: Buffer number. Used for getting buffer local options. + Implies {scope} is "local". + + Return: ~ + Option value nvim_set_option({name}, {value}) *nvim_set_option()* - Sets the global value of an option. + Sets the global value of an option. - Parameters: ~ - {name} Option name - {value} New option value + Parameters: ~ + {name} Option name + {value} New option value *nvim_set_option_value()* nvim_set_option_value({name}, {value}, {*opts}) - Sets the value of an option. The behavior of this function - matches that of |:set|: for global-local options, both the - global and local value are set unless otherwise specified with - {scope}. - - Note the options {win} and {buf} cannot be used together. - - Parameters: ~ - {name} Option name - {value} New option value - {opts} Optional parameters - • scope: One of 'global' or 'local'. Analogous to - |:setglobal| and |:setlocal|, respectively. - • win: |window-ID|. Used for setting window local - option. - • buf: Buffer number. Used for setting buffer - local option. + Sets the value of an option. The behavior of this function matches that of + |:set|: for global-local options, both the global and local value are set + unless otherwise specified with {scope}. + + Note the options {win} and {buf} cannot be used together. + + Parameters: ~ + {name} Option name + {value} New option value + {opts} Optional parameters + • scope: One of 'global' or 'local'. Analogous to + |:setglobal| and |:setlocal|, respectively. + • win: |window-ID|. Used for setting window local option. + • buf: Buffer number. Used for setting buffer local option. nvim_win_get_option({window}, {name}) *nvim_win_get_option()* - Gets a window option value + Gets a window option value - Parameters: ~ - {window} Window handle, or 0 for current window - {name} Option name + Parameters: ~ + {window} Window handle, or 0 for current window + {name} Option name - Return: ~ - Option value + Return: ~ + Option value nvim_win_set_option({window}, {name}, {value}) *nvim_win_set_option()* - Sets a window option value. Passing `nil` as value deletes the - option (only works if there's a global fallback) + Sets a window option value. Passing `nil` as value deletes the option + (only works if there's a global fallback) - Parameters: ~ - {window} Window handle, or 0 for current window - {name} Option name - {value} Option value + Parameters: ~ + {window} Window handle, or 0 for current window + {name} Option name + {value} Option value ============================================================================== @@ -2097,451 +1990,427 @@ For more information on buffers, see |buffers|. Unloaded Buffers:~ -Buffers may be unloaded by the |:bunload| command or the -buffer's |'bufhidden'| option. When a buffer is unloaded its -file contents are freed from memory and vim cannot operate on -the buffer lines until it is reloaded (usually by opening the -buffer again in a new window). API methods such as -|nvim_buf_get_lines()| and |nvim_buf_line_count()| will be +Buffers may be unloaded by the |:bunload| command or the buffer's +|'bufhidden'| option. When a buffer is unloaded its file contents are +freed from memory and vim cannot operate on the buffer lines until it is +reloaded (usually by opening the buffer again in a new window). API +methods such as |nvim_buf_get_lines()| and |nvim_buf_line_count()| will be affected. -You can use |nvim_buf_is_loaded()| or |nvim_buf_line_count()| -to check whether a buffer is loaded. +You can use |nvim_buf_is_loaded()| or |nvim_buf_line_count()| to check +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): > - events = {} - vim.api.nvim_buf_attach(0, false, { - on_lines=function(...) table.insert(events, {...}) end}) + 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): > + events = {} + vim.api.nvim_buf_attach(0, false, { + on_lines=function(...) table.insert(events, {...}) end}) < - Parameters: ~ - {buffer} Buffer handle, or 0 for current buffer - {send_buffer} True if the initial notification should - contain the whole buffer: first - notification will be - `nvim_buf_lines_event`. Else the first - notification will be - `nvim_buf_changedtick_event`. Not for Lua - callbacks. - {opts} Optional parameters. - • on_lines: Lua callback invoked on change. - Return `true` to detach. Args: - • the string "lines" - • buffer handle - • b:changedtick - • first line that changed (zero-indexed) - • last line that was changed - • last line in the updated range - • byte count of previous contents - • deleted_codepoints (if `utf_sizes` is - true) - • deleted_codeunits (if `utf_sizes` is - true) - - • on_bytes: lua callback invoked on change. - This callback receives more granular - information about the change compared to - on_lines. Return `true` to detach. Args: - • the string "bytes" - • buffer handle - • b:changedtick - • start row of the changed text - (zero-indexed) - • start column of the changed text - • byte offset of the changed text (from - the start of the buffer) - • old end row of the changed text - • old end column of the changed text - • old end byte length of the changed text - • new end row of the changed text - • new end column of the changed text - • new end byte length of the changed text - - • on_changedtick: Lua callback invoked on - changedtick increment without text - change. Args: - • the string "changedtick" - • buffer handle - • b:changedtick - - • on_detach: Lua callback invoked on - detach. Args: - • the string "detach" - • buffer handle - - • on_reload: Lua callback invoked on - reload. The entire buffer content should - be considered changed. Args: - • the string "reload" - • buffer handle - - • utf_sizes: include UTF-32 and UTF-16 size - of the replaced region, as args to - `on_lines`. - • preview: also attach to command preview - (i.e. 'inccommand') events. - - Return: ~ - False if attach failed (invalid parameter, or buffer isn't - loaded); otherwise True. TODO: LUA_API_NO_EVAL - - See also: ~ - |nvim_buf_detach()| - |api-buffer-updates-lua| + Parameters: ~ + {buffer} Buffer handle, or 0 for current buffer + {send_buffer} True if the initial notification should contain the + whole buffer: first notification will be + `nvim_buf_lines_event`. Else the first notification + will be `nvim_buf_changedtick_event`. Not for Lua + callbacks. + {opts} Optional parameters. + • on_lines: Lua callback invoked on change. Return `true` to detach. Args: + • the string "lines" + • buffer handle + • b:changedtick + • first line that changed (zero-indexed) + • last line that was changed + • last line in the updated range + • byte count of previous contents + • deleted_codepoints (if `utf_sizes` is true) + • deleted_codeunits (if `utf_sizes` is true) + + • on_bytes: lua callback invoked on change. This + callback receives more granular information about the + change compared to on_lines. Return `true` to detach. Args: + • the string "bytes" + • buffer handle + • b:changedtick + • start row of the changed text (zero-indexed) + • start column of the changed text + • byte offset of the changed text (from the start of + the buffer) + • old end row of the changed text + • old end column of the changed text + • old end byte length of the changed text + • new end row of the changed text + • new end column of the changed text + • new end byte length of the changed text + + • on_changedtick: Lua callback invoked on changedtick + increment without text change. Args: + • the string "changedtick" + • buffer handle + • b:changedtick + + • on_detach: Lua callback invoked on detach. Args: + • the string "detach" + • buffer handle + + • on_reload: Lua callback invoked on reload. The entire + buffer content should be considered changed. Args: + • the string "reload" + • buffer handle + + • utf_sizes: include UTF-32 and UTF-16 size of the + replaced region, as args to `on_lines`. + • preview: also attach to command preview (i.e. + 'inccommand') events. + + Return: ~ + False if attach failed (invalid parameter, or buffer isn't loaded); + otherwise True. TODO: LUA_API_NO_EVAL + + See also: ~ + |nvim_buf_detach()| + |api-buffer-updates-lua| nvim_buf_call({buffer}, {fun}) *nvim_buf_call()* - call a function with buffer as temporary current buffer + call a function with buffer as temporary current buffer - This temporarily switches current buffer to "buffer". If the - current window already shows "buffer", the window is not - switched If a window inside the current tabpage (including a - float) already shows the buffer One of these windows will be - set as current window temporarily. Otherwise a temporary - scratch window (called the "autocmd window" for historical - reasons) will be used. + This temporarily switches current buffer to "buffer". If the current + window already shows "buffer", the window is not switched If a window + inside the current tabpage (including a float) already shows the buffer + One of these windows will be set as current window temporarily. Otherwise + a temporary scratch window (called the "autocmd window" for historical + reasons) will be used. - This is useful e.g. to call vimL functions that only work with - the current buffer/window currently, like |termopen()|. + This is useful e.g. to call vimL functions that only work with the current + buffer/window currently, like |termopen()|. - Attributes: ~ - |vim.api| only + Attributes: ~ + |vim.api| only - Parameters: ~ - {buffer} Buffer handle, or 0 for current buffer - {fun} Function to call inside the buffer (currently - lua callable only) + Parameters: ~ + {buffer} Buffer handle, or 0 for current buffer + {fun} Function to call inside the buffer (currently lua callable + only) - Return: ~ - Return value of function. NB: will deepcopy lua values - currently, use upvalues to send lua references in and out. + Return: ~ + Return value of function. NB: will deepcopy lua values currently, use + upvalues to send lua references in and out. nvim_buf_del_keymap({buffer}, {mode}, {lhs}) *nvim_buf_del_keymap()* - Unmaps a buffer-local |mapping| for the given mode. + Unmaps a buffer-local |mapping| for the given mode. - Parameters: ~ - {buffer} Buffer handle, or 0 for current buffer + Parameters: ~ + {buffer} Buffer handle, or 0 for current buffer - See also: ~ - |nvim_del_keymap()| + See also: ~ + |nvim_del_keymap()| nvim_buf_del_mark({buffer}, {name}) *nvim_buf_del_mark()* - Deletes a named mark in the buffer. See |mark-motions|. + Deletes a named mark in the buffer. See |mark-motions|. - Note: - only deletes marks set in the buffer, if the mark is not - set in the buffer it will return false. + Note: + only deletes marks set in the buffer, if the mark is not set in the + buffer it will return false. - Parameters: ~ - {buffer} Buffer to set the mark on - {name} Mark name + Parameters: ~ + {buffer} Buffer to set the mark on + {name} Mark name - Return: ~ - true if the mark was deleted, else false. + Return: ~ + true if the mark was deleted, else false. - See also: ~ - |nvim_buf_set_mark()| - |nvim_del_mark()| + See also: ~ + |nvim_buf_set_mark()| + |nvim_del_mark()| nvim_buf_del_var({buffer}, {name}) *nvim_buf_del_var()* - Removes a buffer-scoped (b:) variable + Removes a buffer-scoped (b:) variable - Parameters: ~ - {buffer} Buffer handle, or 0 for current buffer - {name} Variable name + Parameters: ~ + {buffer} Buffer handle, or 0 for current buffer + {name} Variable name nvim_buf_delete({buffer}, {opts}) *nvim_buf_delete()* - Deletes the buffer. See |:bwipeout| + Deletes the buffer. See |:bwipeout| - Attributes: ~ - not allowed when |textlock| is active + Attributes: ~ + not allowed when |textlock| is active - Parameters: ~ - {buffer} Buffer handle, or 0 for current buffer - {opts} Optional parameters. Keys: - • force: Force deletion and ignore unsaved - changes. - • unload: Unloaded only, do not delete. See - |:bunload| + Parameters: ~ + {buffer} Buffer handle, or 0 for current buffer + {opts} Optional parameters. Keys: + • force: Force deletion and ignore unsaved changes. + • unload: Unloaded only, do not delete. See |:bunload| nvim_buf_detach({buffer}) *nvim_buf_detach()* - Deactivates buffer-update events on the channel. + Deactivates buffer-update events on the channel. - Attributes: ~ - |RPC| only + Attributes: ~ + |RPC| only - Parameters: ~ - {buffer} Buffer handle, or 0 for current buffer + Parameters: ~ + {buffer} Buffer handle, or 0 for current buffer - Return: ~ - False if detach failed (because the buffer isn't loaded); - otherwise True. + Return: ~ + False if detach failed (because the buffer isn't loaded); otherwise + True. - See also: ~ - |nvim_buf_attach()| - |api-lua-detach| for detaching Lua callbacks + See also: ~ + |nvim_buf_attach()| + |api-lua-detach| for detaching Lua callbacks nvim_buf_get_changedtick({buffer}) *nvim_buf_get_changedtick()* - Gets a changed tick of a buffer + Gets a changed tick of a buffer - Parameters: ~ - {buffer} Buffer handle, or 0 for current buffer + Parameters: ~ + {buffer} Buffer handle, or 0 for current buffer - Return: ~ - `b:changedtick` value. + Return: ~ + `b:changedtick` value. nvim_buf_get_keymap({buffer}, {mode}) *nvim_buf_get_keymap()* - Gets a list of buffer-local |mapping| definitions. + Gets a list of buffer-local |mapping| definitions. - Parameters: ~ - {mode} Mode short-name ("n", "i", "v", ...) - {buffer} Buffer handle, or 0 for current buffer + Parameters: ~ + {mode} Mode short-name ("n", "i", "v", ...) + {buffer} Buffer handle, or 0 for current buffer - Return: ~ - Array of |maparg()|-like dictionaries describing mappings. - The "buffer" key holds the associated buffer handle. + Return: ~ + Array of |maparg()|-like dictionaries describing mappings. The + "buffer" key holds the associated buffer handle. *nvim_buf_get_lines()* nvim_buf_get_lines({buffer}, {start}, {end}, {strict_indexing}) - Gets a line-range from the buffer. + Gets a line-range from the buffer. - Indexing is zero-based, end-exclusive. Negative indices are - interpreted as length+1+index: -1 refers to the index past the - end. So to get the last element use start=-2 and end=-1. + Indexing is zero-based, end-exclusive. Negative indices are interpreted as + length+1+index: -1 refers to the index past the end. So to get the last + element use start=-2 and end=-1. - Out-of-bounds indices are clamped to the nearest valid value, - unless `strict_indexing` is set. + Out-of-bounds indices are clamped to the nearest valid value, unless + `strict_indexing` is set. - Parameters: ~ - {buffer} Buffer handle, or 0 for current buffer - {start} First line index - {end} Last line index, exclusive - {strict_indexing} Whether out-of-bounds should be an - error. + Parameters: ~ + {buffer} Buffer handle, or 0 for current buffer + {start} First line index + {end} Last line index, exclusive + {strict_indexing} Whether out-of-bounds should be an error. - Return: ~ - Array of lines, or empty array for unloaded buffer. + Return: ~ + Array of lines, or empty array for unloaded buffer. nvim_buf_get_mark({buffer}, {name}) *nvim_buf_get_mark()* - Returns a tuple (row,col) representing the position of the - named mark. See |mark-motions|. + Returns a tuple (row,col) representing the position of the named mark. See + |mark-motions|. - Marks are (1,0)-indexed. |api-indexing| + Marks are (1,0)-indexed. |api-indexing| - Parameters: ~ - {buffer} Buffer handle, or 0 for current buffer - {name} Mark name + Parameters: ~ + {buffer} Buffer handle, or 0 for current buffer + {name} Mark name - Return: ~ - (row, col) tuple, (0, 0) if the mark is not set, or is an - uppercase/file mark set in another buffer. + Return: ~ + (row, col) tuple, (0, 0) if the mark is not set, or is an + uppercase/file mark set in another buffer. - See also: ~ - |nvim_buf_set_mark()| - |nvim_buf_del_mark()| + See also: ~ + |nvim_buf_set_mark()| + |nvim_buf_del_mark()| nvim_buf_get_name({buffer}) *nvim_buf_get_name()* - Gets the full file name for the buffer + Gets the full file name for the buffer - Parameters: ~ - {buffer} Buffer handle, or 0 for current buffer + Parameters: ~ + {buffer} Buffer handle, or 0 for current buffer - Return: ~ - Buffer name + Return: ~ + Buffer name nvim_buf_get_offset({buffer}, {index}) *nvim_buf_get_offset()* - Returns the byte offset of a line (0-indexed). |api-indexing| + Returns the byte offset of a line (0-indexed). |api-indexing| - Line 1 (index=0) has offset 0. UTF-8 bytes are counted. EOL is - one byte. 'fileformat' and 'fileencoding' are ignored. The - line index just after the last line gives the total byte-count - of the buffer. A final EOL byte is counted if it would be - written, see 'eol'. + Line 1 (index=0) has offset 0. UTF-8 bytes are counted. EOL is one byte. + 'fileformat' and 'fileencoding' are ignored. The line index just after the + last line gives the total byte-count of the buffer. A final EOL byte is + counted if it would be written, see 'eol'. - Unlike |line2byte()|, throws error for out-of-bounds indexing. - Returns -1 for unloaded buffer. + Unlike |line2byte()|, throws error for out-of-bounds indexing. Returns -1 + for unloaded buffer. - Parameters: ~ - {buffer} Buffer handle, or 0 for current buffer - {index} Line index + Parameters: ~ + {buffer} Buffer handle, or 0 for current buffer + {index} Line index - Return: ~ - Integer byte offset, or -1 for unloaded buffer. + Return: ~ + Integer byte offset, or -1 for unloaded buffer. *nvim_buf_get_text()* nvim_buf_get_text({buffer}, {start_row}, {start_col}, {end_row}, {end_col}, {opts}) - Gets a range from the buffer. + Gets a range from the buffer. - This differs from |nvim_buf_get_lines()| in that it allows - retrieving only portions of a line. + This differs from |nvim_buf_get_lines()| in that it allows retrieving only + portions of a line. - Indexing is zero-based. Row indices are end-inclusive, and - column indices are end-exclusive. + Indexing is zero-based. Row indices are end-inclusive, and column indices + are end-exclusive. - Prefer |nvim_buf_get_lines()| when retrieving entire lines. + Prefer |nvim_buf_get_lines()| when retrieving entire lines. - Parameters: ~ - {buffer} Buffer handle, or 0 for current buffer - {start_row} First line index - {start_col} Starting column (byte offset) on first line - {end_row} Last line index, inclusive - {end_col} Ending column (byte offset) on last line, - exclusive - {opts} Optional parameters. Currently unused. + Parameters: ~ + {buffer} Buffer handle, or 0 for current buffer + {start_row} First line index + {start_col} Starting column (byte offset) on first line + {end_row} Last line index, inclusive + {end_col} Ending column (byte offset) on last line, exclusive + {opts} Optional parameters. Currently unused. - Return: ~ - Array of lines, or empty array for unloaded buffer. + Return: ~ + Array of lines, or empty array for unloaded buffer. nvim_buf_get_var({buffer}, {name}) *nvim_buf_get_var()* - Gets a buffer-scoped (b:) variable. + Gets a buffer-scoped (b:) variable. - Parameters: ~ - {buffer} Buffer handle, or 0 for current buffer - {name} Variable name + Parameters: ~ + {buffer} Buffer handle, or 0 for current buffer + {name} Variable name - Return: ~ - Variable value + Return: ~ + Variable value nvim_buf_is_loaded({buffer}) *nvim_buf_is_loaded()* - Checks if a buffer is valid and loaded. See |api-buffer| for - more info about unloaded buffers. + Checks if a buffer is valid and loaded. See |api-buffer| for more info + about unloaded buffers. - Parameters: ~ - {buffer} Buffer handle, or 0 for current buffer + Parameters: ~ + {buffer} Buffer handle, or 0 for current buffer - Return: ~ - true if the buffer is valid and loaded, false otherwise. + Return: ~ + true if the buffer is valid and loaded, false otherwise. nvim_buf_is_valid({buffer}) *nvim_buf_is_valid()* - Checks if a buffer is valid. + Checks if a buffer is valid. - Note: - Even if a buffer is valid it may have been unloaded. See - |api-buffer| for more info about unloaded buffers. + Note: + Even if a buffer is valid it may have been unloaded. See |api-buffer| + for more info about unloaded buffers. - Parameters: ~ - {buffer} Buffer handle, or 0 for current buffer + Parameters: ~ + {buffer} Buffer handle, or 0 for current buffer - Return: ~ - true if the buffer is valid, false otherwise. + Return: ~ + true if the buffer is valid, false otherwise. nvim_buf_line_count({buffer}) *nvim_buf_line_count()* - Returns the number of lines in the given buffer. + Returns the number of lines in the given buffer. - Parameters: ~ - {buffer} Buffer handle, or 0 for current buffer + Parameters: ~ + {buffer} Buffer handle, or 0 for current buffer - Return: ~ - Line count, or 0 for unloaded buffer. |api-buffer| + Return: ~ + Line count, or 0 for unloaded buffer. |api-buffer| *nvim_buf_set_keymap()* nvim_buf_set_keymap({buffer}, {mode}, {lhs}, {rhs}, {*opts}) - Sets a buffer-local |mapping| for the given mode. + Sets a buffer-local |mapping| for the given mode. - Parameters: ~ - {buffer} Buffer handle, or 0 for current buffer + Parameters: ~ + {buffer} Buffer handle, or 0 for current buffer - See also: ~ - |nvim_set_keymap()| + See also: ~ + |nvim_set_keymap()| *nvim_buf_set_lines()* nvim_buf_set_lines({buffer}, {start}, {end}, {strict_indexing}, {replacement}) - Sets (replaces) a line-range in the buffer. + Sets (replaces) a line-range in the buffer. - Indexing is zero-based, end-exclusive. Negative indices are - interpreted as length+1+index: -1 refers to the index past the - end. So to change or delete the last element use start=-2 and - end=-1. + Indexing is zero-based, end-exclusive. Negative indices are interpreted as + length+1+index: -1 refers to the index past the end. So to change or + delete the last element use start=-2 and end=-1. - To insert lines at a given index, set `start` and `end` to the - same index. To delete a range of lines, set `replacement` to - an empty array. + To insert lines at a given index, set `start` and `end` to the same index. + To delete a range of lines, set `replacement` to an empty array. - Out-of-bounds indices are clamped to the nearest valid value, - unless `strict_indexing` is set. + Out-of-bounds indices are clamped to the nearest valid value, unless + `strict_indexing` is set. - Attributes: ~ - not allowed when |textlock| is active + Attributes: ~ + not allowed when |textlock| is active - Parameters: ~ - {buffer} Buffer handle, or 0 for current buffer - {start} First line index - {end} Last line index, exclusive - {strict_indexing} Whether out-of-bounds should be an - error. - {replacement} Array of lines to use as replacement + Parameters: ~ + {buffer} Buffer handle, or 0 for current buffer + {start} First line index + {end} Last line index, exclusive + {strict_indexing} Whether out-of-bounds should be an error. + {replacement} Array of lines to use as replacement *nvim_buf_set_mark()* nvim_buf_set_mark({buffer}, {name}, {line}, {col}, {opts}) - Sets a named mark in the given buffer, all marks are allowed - file/uppercase, visual, last change, etc. See |mark-motions|. + Sets a named mark in the given buffer, all marks are allowed + file/uppercase, visual, last change, etc. See |mark-motions|. - Marks are (1,0)-indexed. |api-indexing| + Marks are (1,0)-indexed. |api-indexing| - Note: - Passing 0 as line deletes the mark + Note: + Passing 0 as line deletes the mark - Parameters: ~ - {buffer} Buffer to set the mark on - {name} Mark name - {line} Line number - {col} Column/row number - {opts} Optional parameters. Reserved for future use. + Parameters: ~ + {buffer} Buffer to set the mark on + {name} Mark name + {line} Line number + {col} Column/row number + {opts} Optional parameters. Reserved for future use. - Return: ~ - true if the mark was set, else false. + Return: ~ + true if the mark was set, else false. - See also: ~ - |nvim_buf_del_mark()| - |nvim_buf_get_mark()| + See also: ~ + |nvim_buf_del_mark()| + |nvim_buf_get_mark()| nvim_buf_set_name({buffer}, {name}) *nvim_buf_set_name()* - Sets the full file name for a buffer + Sets the full file name for a buffer - Parameters: ~ - {buffer} Buffer handle, or 0 for current buffer - {name} Buffer name + Parameters: ~ + {buffer} Buffer handle, or 0 for current buffer + {name} Buffer name *nvim_buf_set_text()* nvim_buf_set_text({buffer}, {start_row}, {start_col}, {end_row}, {end_col}, {replacement}) - Sets (replaces) a range in the buffer + Sets (replaces) a range in the buffer - This is recommended over |nvim_buf_set_lines()| when only - modifying parts of a line, as extmarks will be preserved on - non-modified parts of the touched lines. + This is recommended over |nvim_buf_set_lines()| when only modifying parts + of a line, as extmarks will be preserved on non-modified parts of the + touched lines. - Indexing is zero-based. Row indices are end-inclusive, and - column indices are end-exclusive. + Indexing is zero-based. Row indices are end-inclusive, and column indices + are end-exclusive. - To insert text at a given `(row, column)` location, use - `start_row = end_row = row` and `start_col = end_col = col`. - To delete the text in a range, use `replacement = {}`. + To insert text at a given `(row, column)` location, use `start_row = + end_row = row` and `start_col = end_col = col`. To delete the text in a + range, use `replacement = {}`. - Prefer |nvim_buf_set_lines()| if you are only adding or - deleting entire lines. + Prefer |nvim_buf_set_lines()| if you are only adding or deleting entire + lines. - Parameters: ~ - {buffer} Buffer handle, or 0 for current buffer - {start_row} First line index - {start_col} Starting column (byte offset) on first line - {end_row} Last line index, inclusive - {end_col} Ending column (byte offset) on last line, - exclusive - {replacement} Array of lines to use as replacement + Parameters: ~ + {buffer} Buffer handle, or 0 for current buffer + {start_row} First line index + {start_col} Starting column (byte offset) on first line + {end_row} Last line index, inclusive + {end_col} Ending column (byte offset) on last line, exclusive + {replacement} Array of lines to use as replacement nvim_buf_set_var({buffer}, {name}, {value}) *nvim_buf_set_var()* - Sets a buffer-scoped (b:) variable + Sets a buffer-scoped (b:) variable - Parameters: ~ - {buffer} Buffer handle, or 0 for current buffer - {name} Variable name - {value} Variable value + Parameters: ~ + {buffer} Buffer handle, or 0 for current buffer + {name} Variable name + {value} Variable value ============================================================================== @@ -2550,107 +2419,96 @@ Extmark Functions *api-extmark* *nvim_buf_add_highlight()* nvim_buf_add_highlight({buffer}, {ns_id}, {hl_group}, {line}, {col_start}, {col_end}) - Adds a highlight to buffer. - - Useful for plugins that dynamically generate highlights to a - buffer (like a semantic highlighter or linter). The function - adds a single highlight to a buffer. Unlike |matchaddpos()| - highlights follow changes to line numbering (as lines are - inserted/removed above the highlighted line), like signs and - marks do. - - Namespaces are used for batch deletion/updating of a set of - highlights. To create a namespace, use - |nvim_create_namespace()| which returns a namespace id. Pass - it in to this function as `ns_id` to add highlights to the - namespace. All highlights in the same namespace can then be - cleared with single call to |nvim_buf_clear_namespace()|. If - the highlight never will be deleted by an API call, pass - `ns_id = -1`. - - As a shorthand, `ns_id = 0` can be used to create a new - namespace for the highlight, the allocated id is then - returned. If `hl_group` is the empty string no highlight is - added, but a new `ns_id` is still returned. This is supported - for backwards compatibility, new code should use - |nvim_create_namespace()| to create a new empty namespace. - - Parameters: ~ - {buffer} Buffer handle, or 0 for current buffer - {ns_id} namespace to use or -1 for ungrouped - highlight - {hl_group} Name of the highlight group to use - {line} Line to highlight (zero-indexed) - {col_start} Start of (byte-indexed) column range to - highlight - {col_end} End of (byte-indexed) column range to - highlight, or -1 to highlight to end of line - - Return: ~ - The ns_id that was used + Adds a highlight to buffer. + + Useful for plugins that dynamically generate highlights to a buffer (like + a semantic highlighter or linter). The function adds a single highlight to + a buffer. Unlike |matchaddpos()| highlights follow changes to line + numbering (as lines are inserted/removed above the highlighted line), like + signs and marks do. + + Namespaces are used for batch deletion/updating of a set of highlights. To + create a namespace, use |nvim_create_namespace()| which returns a + namespace id. Pass it in to this function as `ns_id` to add highlights to + the namespace. All highlights in the same namespace can then be cleared + with single call to |nvim_buf_clear_namespace()|. If the highlight never + will be deleted by an API call, pass `ns_id = -1`. + + As a shorthand, `ns_id = 0` can be used to create a new namespace for the + highlight, the allocated id is then returned. If `hl_group` is the empty + string no highlight is added, but a new `ns_id` is still returned. This is + supported for backwards compatibility, new code should use + |nvim_create_namespace()| to create a new empty namespace. + + Parameters: ~ + {buffer} Buffer handle, or 0 for current buffer + {ns_id} namespace to use or -1 for ungrouped highlight + {hl_group} Name of the highlight group to use + {line} Line to highlight (zero-indexed) + {col_start} Start of (byte-indexed) column range to highlight + {col_end} End of (byte-indexed) column range to highlight, or -1 to + highlight to end of line + + Return: ~ + The ns_id that was used *nvim_buf_clear_namespace()* nvim_buf_clear_namespace({buffer}, {ns_id}, {line_start}, {line_end}) - Clears namespaced objects (highlights, extmarks, virtual text) - from a region. + Clears namespaced objects (highlights, extmarks, virtual text) from a + region. - Lines are 0-indexed. |api-indexing| To clear the namespace in - the entire buffer, specify line_start=0 and line_end=-1. + Lines are 0-indexed. |api-indexing| To clear the namespace in the entire + buffer, specify line_start=0 and line_end=-1. - Parameters: ~ - {buffer} Buffer handle, or 0 for current buffer - {ns_id} Namespace to clear, or -1 to clear all - namespaces. - {line_start} Start of range of lines to clear - {line_end} End of range of lines to clear (exclusive) - or -1 to clear to end of buffer. + Parameters: ~ + {buffer} Buffer handle, or 0 for current buffer + {ns_id} Namespace to clear, or -1 to clear all namespaces. + {line_start} Start of range of lines to clear + {line_end} End of range of lines to clear (exclusive) or -1 to + 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 - {ns_id} Namespace id from |nvim_create_namespace()| - {id} Extmark id + Parameters: ~ + {buffer} Buffer handle, or 0 for current buffer + {ns_id} Namespace id from |nvim_create_namespace()| + {id} Extmark id - Return: ~ - true if the extmark was found, else false + Return: ~ + true if the extmark was found, else false *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 - {ns_id} Namespace id from |nvim_create_namespace()| - {id} Extmark id - {opts} Optional parameters. Keys: - • details: Whether to include the details dict + Parameters: ~ + {buffer} Buffer handle, or 0 for current buffer + {ns_id} Namespace id from |nvim_create_namespace()| + {id} Extmark id + {opts} Optional parameters. Keys: + • details: Whether to include the details dict - Return: ~ - 0-indexed (row, col) tuple or empty list () if extmark id - was absent + Return: ~ + 0-indexed (row, col) tuple or empty list () if extmark id was absent *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 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: + 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], {}) < - If `end` is less than `start`, traversal works backwards. - (Useful with `limit`, to get the first marks prior to a given - position.) + If `end` is less than `start`, traversal works backwards. (Useful with + `limit`, to get the first marks prior to a given position.) - Example: + Example: > local a = vim.api local pos = a.nvim_win_get_cursor(0) @@ -2666,759 +2524,694 @@ nvim_buf_get_extmarks({buffer}, {ns_id}, {start}, {end}, {opts}) print(vim.inspect(ms)) < - Parameters: ~ - {buffer} Buffer handle, or 0 for current buffer - {ns_id} Namespace id from |nvim_create_namespace()| - {start} Start of range: a 0-indexed (row, col) or valid - extmark id (whose position defines the bound). - |api-indexing| - {end} End of range (inclusive): a 0-indexed (row, col) - or valid extmark id (whose position defines the - bound). |api-indexing| - {opts} Optional parameters. Keys: - • limit: Maximum number of marks to return - • details Whether to include the details dict - - Return: ~ - List of [extmark_id, row, col] tuples in "traversal - order". + Parameters: ~ + {buffer} Buffer handle, or 0 for current buffer + {ns_id} Namespace id from |nvim_create_namespace()| + {start} Start of range: a 0-indexed (row, col) or valid extmark id + (whose position defines the bound). |api-indexing| + {end} End of range (inclusive): a 0-indexed (row, col) or valid + extmark id (whose position defines the bound). + |api-indexing| + {opts} Optional parameters. Keys: + • limit: Maximum number of marks to return + • details Whether to include the details dict + + Return: ~ + List of [extmark_id, row, col] tuples in "traversal order". *nvim_buf_set_extmark()* nvim_buf_set_extmark({buffer}, {ns_id}, {line}, {col}, {*opts}) - 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 an existing mark by passing in - its id. The caller must then keep track of existing and unused - ids itself. (Useful over RPC, to avoid waiting for the return - value.) - - Using the optional arguments, it is possible to use this to - highlight a range of text, and also to associate virtual text - to the mark. - - Parameters: ~ - {buffer} Buffer handle, or 0 for current buffer - {ns_id} Namespace id from |nvim_create_namespace()| - {line} Line where to place the mark, 0-based. - |api-indexing| - {col} Column where to place the mark, 0-based. - |api-indexing| - {opts} Optional parameters. - • id : id of the extmark to edit. - • end_row : ending line of the mark, 0-based - inclusive. - • end_col : ending col of the mark, 0-based - exclusive. - • hl_group : name of the highlight group used to - highlight this mark. - • hl_eol : when true, for a multiline highlight - covering the EOL of a line, continue the - highlight for the rest of the screen line - (just like for diff and cursorline highlight). - • virt_text : virtual text to link to this mark. - A list of [text, highlight] tuples, each - representing a text chunk with specified - highlight. `highlight` element can either be a - a single highlight group, or an array of - multiple highlight groups that will be stacked - (highest priority last). A highlight group can - be supplied either as a string or as an - integer, the latter which can be obtained - using |nvim_get_hl_id_by_name|. - • virt_text_pos : position of virtual text. - Possible values: - • "eol": right after eol character (default) - • "overlay": display over the specified - column, without shifting the underlying - text. - • "right_align": display right aligned in the - window. - - • virt_text_win_col : position the virtual text - at a fixed window column (starting from the - first text column) - • virt_text_hide : hide the virtual text when - the background text is selected or hidden due - to horizontal scroll 'nowrap' - • hl_mode : control how highlights are combined - with the highlights of the text. Currently - only affects virt_text highlights, but might - affect `hl_group` in later versions. - • "replace": only show the virt_text color. - This is the default - • "combine": combine with background text - color - • "blend": blend with background text color. - - • virt_lines : virtual lines to add next to this - mark This should be an array over lines, where - each line in turn is an array over [text, - highlight] tuples. In general, buffer and - window options do not affect the display of - the text. In particular 'wrap' and 'linebreak' - options do not take effect, so the number of - extra screen lines will always match the size - of the array. However the 'tabstop' buffer - option is still used for hard tabs. By default - lines are placed below the buffer line - containing the mark. - • virt_lines_above: place virtual lines above - instead. - • virt_lines_leftcol: Place extmarks in the - leftmost column of the window, bypassing sign - and number columns. - • ephemeral : for use with - |nvim_set_decoration_provider| callbacks. The - mark will only be used for the current redraw - cycle, and not be permantently stored in the - buffer. - • right_gravity : boolean that indicates the - direction the extmark will be shifted in when - new text is inserted (true for right, false - for left). defaults to true. - • end_right_gravity : boolean that indicates the - direction the extmark end position (if it - exists) will be shifted in when new text is - inserted (true for right, false for left). - Defaults to false. - • priority: a priority value for the highlight - group. For example treesitter highlighting - uses a value of 100. - • strict: boolean that indicates extmark should - not be placed if the line or column value is - past the end of the buffer or end of the line - respectively. Defaults to true. - • sign_text: string of length 1-2 used to - display in the sign column. Note: ranges are - unsupported and decorations are only applied - to start_row - • sign_hl_group: name of the highlight group - used to highlight the sign column text. Note: - ranges are unsupported and decorations are - only applied to start_row - • number_hl_group: name of the highlight group - used to highlight the number column. Note: - ranges are unsupported and decorations are - only applied to start_row - • line_hl_group: name of the highlight group - used to highlight the whole line. Note: ranges - are unsupported and decorations are only - applied to start_row - • cursorline_hl_group: name of the highlight - group used to highlight the line when the - cursor is on the same line as the mark and - 'cursorline' is enabled. Note: ranges are - unsupported and decorations are only applied - to start_row - • conceal: string which should be either empty - or a single character. Enable concealing - similar to |:syn-conceal|. When a character is - supplied it is used as |:syn-cchar|. - "hl_group" is used as highlight for the cchar - if provided, otherwise it defaults to - |hl-Conceal|. - • ui_watched: boolean that indicates the mark - should be drawn by a UI. When set, the UI will - receive win_extmark events. Note: the mark is - positioned by virt_text attributes. Can be - used together with virt_text. - - Return: ~ - Id of the created/updated 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 an existing mark by passing in its id. The caller must then keep + track of existing and unused ids itself. (Useful over RPC, to avoid + waiting for the return value.) + + Using the optional arguments, it is possible to use this to highlight a + range of text, and also to associate virtual text to the mark. + + Parameters: ~ + {buffer} Buffer handle, or 0 for current buffer + {ns_id} Namespace id from |nvim_create_namespace()| + {line} Line where to place the mark, 0-based. |api-indexing| + {col} Column where to place the mark, 0-based. |api-indexing| + {opts} Optional parameters. + • id : id of the extmark to edit. + • end_row : ending line of the mark, 0-based inclusive. + • end_col : ending col of the mark, 0-based exclusive. + • hl_group : name of the highlight group used to highlight + this mark. + • hl_eol : when true, for a multiline highlight covering the + EOL of a line, continue the highlight for the rest of the + screen line (just like for diff and cursorline highlight). + • virt_text : virtual text to link to this mark. A list of + [text, highlight] tuples, each representing a text chunk + with specified highlight. `highlight` element can either + be a a single highlight group, or an array of multiple + highlight groups that will be stacked (highest priority + last). A highlight group can be supplied either as a + string or as an integer, the latter which can be obtained + using |nvim_get_hl_id_by_name|. + • virt_text_pos : position of virtual text. Possible values: + • "eol": right after eol character (default) + • "overlay": display over the specified column, without + shifting the underlying text. + • "right_align": display right aligned in the window. + + • virt_text_win_col : position the virtual text at a fixed + window column (starting from the first text column) + • virt_text_hide : hide the virtual text when the background + text is selected or hidden due to horizontal scroll + 'nowrap' + • hl_mode : control how highlights are combined with the + highlights of the text. Currently only affects virt_text + highlights, but might affect `hl_group` in later versions. + • "replace": only show the virt_text color. This is the + default + • "combine": combine with background text color + • "blend": blend with background text color. + + • virt_lines : virtual lines to add next to this mark This + should be an array over lines, where each line in turn is + an array over [text, highlight] tuples. In general, buffer + and window options do not affect the display of the text. + In particular 'wrap' and 'linebreak' options do not take + effect, so the number of extra screen lines will always + match the size of the array. However the 'tabstop' buffer + option is still used for hard tabs. By default lines are + placed below the buffer line containing the mark. + • virt_lines_above: place virtual lines above instead. + • virt_lines_leftcol: Place extmarks in the leftmost column + of the window, bypassing sign and number columns. + • ephemeral : for use with |nvim_set_decoration_provider| + callbacks. The mark will only be used for the current + redraw cycle, and not be permantently stored in the + buffer. + • right_gravity : boolean that indicates the direction the + extmark will be shifted in when new text is inserted (true + for right, false for left). defaults to true. + • end_right_gravity : boolean that indicates the direction + the extmark end position (if it exists) will be shifted in + when new text is inserted (true for right, false for + left). Defaults to false. + • priority: a priority value for the highlight group or sign + attribute. For example treesitter highlighting uses a + value of 100. + • strict: boolean that indicates extmark should not be + placed if the line or column value is past the end of the + buffer or end of the line respectively. Defaults to true. + • sign_text: string of length 1-2 used to display in the + sign column. Note: ranges are unsupported and decorations + are only applied to start_row + • sign_hl_group: name of the highlight group used to + highlight the sign column text. Note: ranges are + unsupported and decorations are only applied to start_row + • number_hl_group: name of the highlight group used to + highlight the number column. Note: ranges are unsupported + and decorations are only applied to start_row + • line_hl_group: name of the highlight group used to + highlight the whole line. Note: ranges are unsupported and + decorations are only applied to start_row + • cursorline_hl_group: name of the highlight group used to + highlight the line when the cursor is on the same line as + the mark and 'cursorline' is enabled. Note: ranges are + unsupported and decorations are only applied to start_row + • conceal: string which should be either empty or a single + character. Enable concealing similar to |:syn-conceal|. + When a character is supplied it is used as |:syn-cchar|. + "hl_group" is used as highlight for the cchar if provided, + otherwise it defaults to |hl-Conceal|. + • ui_watched: boolean that indicates the mark should be + drawn by a UI. When set, the UI will receive win_extmark + events. Note: the mark is positioned by virt_text + attributes. Can be used together with virt_text. + + Return: ~ + 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. - Namespaces are used for buffer highlights and virtual text, - see |nvim_buf_add_highlight()| and |nvim_buf_set_extmark()|. + Namespaces are used for buffer highlights and virtual text, see + |nvim_buf_add_highlight()| and |nvim_buf_set_extmark()|. - Namespaces can be named or anonymous. If `name` matches an - existing namespace, the associated id is returned. If `name` - is an empty string a new, anonymous namespace is created. + Namespaces can be named or anonymous. If `name` matches an existing + namespace, the associated id is returned. If `name` is an empty string a + new, anonymous namespace is created. - Parameters: ~ - {name} Namespace name or empty string + Parameters: ~ + {name} Namespace name or empty string - Return: ~ - Namespace id + Return: ~ + Namespace id nvim_get_namespaces() *nvim_get_namespaces()* - Gets existing, non-anonymous namespaces. + Gets existing, non-anonymous namespaces. - Return: ~ - dict that maps from names to namespace ids. + 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 - - 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. |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 redraw ). - - Note: this function should not be called often. Rather, the - callbacks themselves can be used to throttle unneeded - callbacks. the `on_start` callback can return `false` to - disable the provider until the next redraw. Similarly, return - `false` in `on_win` will skip the `on_lines` calls for that - window (but any extmarks set in `on_win` will still be used). - A plugin managing multiple sources of decoration should - ideally only set one provider, and merge the sources - internally. You can use multiple `ns_id` for the extmarks - set/modified inside the callback anyway. - - Note: doing anything other than setting extmarks is considered - experimental. Doing things like changing options are not - expliticly forbidden, but is likely to have unexpected - consequences (such as 100% CPU consumption). doing - `vim.rpcnotify` should be OK, but `vim.rpcrequest` is quite - dubious for the moment. - - Attributes: ~ - |vim.api| only - - Parameters: ~ - {ns_id} Namespace id from |nvim_create_namespace()| - {opts} Callbacks invoked during redraw: - • on_start: called first on each screen redraw - ["start", tick] - • on_buf: called for each buffer being redrawn - (before window callbacks) ["buf", bufnr, tick] - • on_win: called when starting to redraw a - specific window. ["win", winid, bufnr, topline, - botline_guess] - • on_line: called for each buffer line being - redrawn. (The interaction with fold lines is - subject to change) ["win", winid, bufnr, row] - • on_end: called at the end of a redraw cycle - ["end", tick] + 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. + |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 + redraw ). + + Note: this function should not be called often. Rather, the callbacks + themselves can be used to throttle unneeded callbacks. the `on_start` + callback can return `false` to disable the provider until the next redraw. + Similarly, return `false` in `on_win` will skip the `on_lines` calls for + that window (but any extmarks set in `on_win` will still be used). A + plugin managing multiple sources of decoration should ideally only set one + provider, and merge the sources internally. You can use multiple `ns_id` + for the extmarks set/modified inside the callback anyway. + + Note: doing anything other than setting extmarks is considered + experimental. Doing things like changing options are not expliticly + forbidden, but is likely to have unexpected consequences (such as 100% CPU + consumption). doing `vim.rpcnotify` should be OK, but `vim.rpcrequest` is + quite dubious for the moment. + + Attributes: ~ + |vim.api| only + + Parameters: ~ + {ns_id} Namespace id from |nvim_create_namespace()| + {opts} Callbacks invoked during redraw: + • on_start: called first on each screen redraw ["start", + tick] + • on_buf: called for each buffer being redrawn (before window + callbacks) ["buf", bufnr, tick] + • on_win: called when starting to redraw a specific window. + ["win", winid, bufnr, topline, botline_guess] + • on_line: called for each buffer line being redrawn. (The + interaction with fold lines is subject to change) ["win", + winid, bufnr, row] + • on_end: called at the end of a redraw cycle ["end", tick] ============================================================================== Window Functions *api-window* nvim_win_call({window}, {fun}) *nvim_win_call()* - Calls a function with window as temporary current window. + Calls a function with window as temporary current window. - Attributes: ~ - |vim.api| only + Attributes: ~ + |vim.api| only - Parameters: ~ - {window} Window handle, or 0 for current window - {fun} Function to call inside the window (currently - lua callable only) + Parameters: ~ + {window} Window handle, or 0 for current window + {fun} Function to call inside the window (currently lua callable + only) - Return: ~ - Return value of function. NB: will deepcopy lua values - currently, use upvalues to send lua references in and out. + Return: ~ + Return value of function. NB: will deepcopy lua values currently, use + upvalues to send lua references in and out. - See also: ~ - |win_execute()| - |nvim_buf_call()| + See also: ~ + |win_execute()| + |nvim_buf_call()| nvim_win_close({window}, {force}) *nvim_win_close()* - Closes the window (like |:close| with a |window-ID|). + Closes the window (like |:close| with a |window-ID|). - Attributes: ~ - not allowed when |textlock| is active + Attributes: ~ + not allowed when |textlock| is active - Parameters: ~ - {window} Window handle, or 0 for current window - {force} Behave like `:close!` The last window of a - buffer with unwritten changes can be closed. The - buffer will become hidden, even if 'hidden' is - not set. + Parameters: ~ + {window} Window handle, or 0 for current window + {force} Behave like `:close!` The last window of a buffer with + unwritten changes can be closed. The buffer will become + hidden, even if 'hidden' is not set. nvim_win_del_var({window}, {name}) *nvim_win_del_var()* - Removes a window-scoped (w:) variable + Removes a window-scoped (w:) variable - Parameters: ~ - {window} Window handle, or 0 for current window - {name} Variable name + Parameters: ~ + {window} Window handle, or 0 for current window + {name} Variable name nvim_win_get_buf({window}) *nvim_win_get_buf()* - Gets the current buffer in a window + Gets the current buffer in a window - Parameters: ~ - {window} Window handle, or 0 for current window + Parameters: ~ + {window} Window handle, or 0 for current window - Return: ~ - Buffer handle + Return: ~ + Buffer handle nvim_win_get_cursor({window}) *nvim_win_get_cursor()* - Gets the (1,0)-indexed cursor position in the window. - |api-indexing| + Gets the (1,0)-indexed cursor position in the window. |api-indexing| - Parameters: ~ - {window} Window handle, or 0 for current window + Parameters: ~ + {window} Window handle, or 0 for current window - Return: ~ - (row, col) tuple + Return: ~ + (row, col) tuple nvim_win_get_height({window}) *nvim_win_get_height()* - Gets the window height + Gets the window height - Parameters: ~ - {window} Window handle, or 0 for current window + Parameters: ~ + {window} Window handle, or 0 for current window - Return: ~ - Height as a count of rows + Return: ~ + Height as a count of rows nvim_win_get_number({window}) *nvim_win_get_number()* - Gets the window number + Gets the window number - Parameters: ~ - {window} Window handle, or 0 for current window + Parameters: ~ + {window} Window handle, or 0 for current window - Return: ~ - Window number + Return: ~ + Window number nvim_win_get_position({window}) *nvim_win_get_position()* - Gets the window position in display cells. First position is - zero. + Gets the window position in display cells. First position is zero. - Parameters: ~ - {window} Window handle, or 0 for current window + Parameters: ~ + {window} Window handle, or 0 for current window - Return: ~ - (row, col) tuple with the window position + Return: ~ + (row, col) tuple with the window position nvim_win_get_tabpage({window}) *nvim_win_get_tabpage()* - Gets the window tabpage + Gets the window tabpage - Parameters: ~ - {window} Window handle, or 0 for current window + Parameters: ~ + {window} Window handle, or 0 for current window - Return: ~ - Tabpage that contains the window + Return: ~ + Tabpage that contains the window nvim_win_get_var({window}, {name}) *nvim_win_get_var()* - Gets a window-scoped (w:) variable + Gets a window-scoped (w:) variable - Parameters: ~ - {window} Window handle, or 0 for current window - {name} Variable name + Parameters: ~ + {window} Window handle, or 0 for current window + {name} Variable name - Return: ~ - Variable value + Return: ~ + Variable value nvim_win_get_width({window}) *nvim_win_get_width()* - Gets the window width + Gets the window width - Parameters: ~ - {window} Window handle, or 0 for current window + Parameters: ~ + {window} Window handle, or 0 for current window - Return: ~ - Width as a count of columns + Return: ~ + Width as a count of columns nvim_win_hide({window}) *nvim_win_hide()* - Closes the window and hide the buffer it contains (like - |:hide| with a |window-ID|). + Closes the window and hide the buffer it contains (like |:hide| with a + |window-ID|). - Like |:hide| the buffer becomes hidden unless another window - is editing it, or 'bufhidden' is `unload`, `delete` or `wipe` - as opposed to |:close| or |nvim_win_close|, which will close - the buffer. + Like |:hide| the buffer becomes hidden unless another window is editing + it, or 'bufhidden' is `unload`, `delete` or `wipe` as opposed to |:close| + or |nvim_win_close|, which will close the buffer. - Attributes: ~ - not allowed when |textlock| is active + Attributes: ~ + not allowed when |textlock| is active - Parameters: ~ - {window} Window handle, or 0 for current window + Parameters: ~ + {window} Window handle, or 0 for current window nvim_win_is_valid({window}) *nvim_win_is_valid()* - Checks if a window is valid + Checks if a window is valid - Parameters: ~ - {window} Window handle, or 0 for current window + Parameters: ~ + {window} Window handle, or 0 for current window - Return: ~ - true if the window is valid, false otherwise + Return: ~ + true if the window is valid, false otherwise nvim_win_set_buf({window}, {buffer}) *nvim_win_set_buf()* - Sets the current buffer in a window, without side effects + Sets the current buffer in a window, without side effects - Attributes: ~ - not allowed when |textlock| is active + Attributes: ~ + not allowed when |textlock| is active - Parameters: ~ - {window} Window handle, or 0 for current window - {buffer} Buffer handle + Parameters: ~ + {window} Window handle, or 0 for current window + {buffer} Buffer handle nvim_win_set_cursor({window}, {pos}) *nvim_win_set_cursor()* - Sets the (1,0)-indexed cursor position in the window. - |api-indexing| This scrolls the window even if it is not the - current one. + Sets the (1,0)-indexed cursor position in the window. |api-indexing| This + scrolls the window even if it is not the current one. - Parameters: ~ - {window} Window handle, or 0 for current window - {pos} (row, col) tuple representing the new position + Parameters: ~ + {window} Window handle, or 0 for current window + {pos} (row, col) tuple representing the new position nvim_win_set_height({window}, {height}) *nvim_win_set_height()* - Sets the window height. + Sets the window height. + + Parameters: ~ + {window} Window handle, or 0 for current window + {height} Height as a count of rows - Parameters: ~ - {window} Window handle, or 0 for current window - {height} Height as a count of rows +nvim_win_set_hl_ns({window}, {ns_id}) *nvim_win_set_hl_ns()* + Set highlight namespace for a window. This will use highlights defined in + this namespace, but fall back to global highlights (ns=0) when missing. + + This takes predecence over the 'winhighlight' option. + + Parameters: ~ + {ns_id} the namespace to use nvim_win_set_var({window}, {name}, {value}) *nvim_win_set_var()* - Sets a window-scoped (w:) variable + Sets a window-scoped (w:) variable - Parameters: ~ - {window} Window handle, or 0 for current window - {name} Variable name - {value} Variable value + Parameters: ~ + {window} Window handle, or 0 for current window + {name} Variable name + {value} Variable value nvim_win_set_width({window}, {width}) *nvim_win_set_width()* - Sets the window width. This will only succeed if the screen is - split vertically. + Sets the window width. This will only succeed if the screen is split + vertically. - Parameters: ~ - {window} Window handle, or 0 for current window - {width} Width as a count of columns + Parameters: ~ + {window} Window handle, or 0 for current window + {width} Width as a count of columns ============================================================================== Win_Config Functions *api-win_config* nvim_open_win({buffer}, {enter}, {*config}) *nvim_open_win()* - Open a new window. - - Currently this is used to open floating and external windows. - Floats are windows that are drawn above the split layout, at - some anchor position in some other window. Floats can be drawn - internally or by external GUI with the |ui-multigrid| - extension. External windows are only supported with multigrid - GUIs, and are displayed as separate top-level windows. - - For a general overview of floats, see |api-floatwin|. - - Exactly one of `external` and `relative` must be specified. - The `width` and `height` of the new window must be specified. - - With relative=editor (row=0,col=0) refers to the top-left - corner of the screen-grid and (row=Lines-1,col=Columns-1) - refers to the bottom-right corner. Fractional values are - allowed, but the builtin implementation (used by non-multigrid - UIs) will always round down to nearest integer. - - Out-of-bounds values, and configurations that make the float - not fit inside the main editor, are allowed. The builtin - implementation truncates values so floats are fully within the - main screen grid. External GUIs 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 > - vim.api.nvim_open_win(0, false, - {relative='win', row=3, col=3, width=12, height=3}) + Open a new window. + + Currently this is used to open floating and external windows. Floats are + windows that are drawn above the split layout, at some anchor position in + some other window. Floats can be drawn internally or by external GUI with + the |ui-multigrid| extension. External windows are only supported with + multigrid GUIs, and are displayed as separate top-level windows. + + For a general overview of floats, see |api-floatwin|. + + Exactly one of `external` and `relative` must be specified. The `width` + and `height` of the new window must be specified. + + With relative=editor (row=0,col=0) refers to the top-left corner of the + screen-grid and (row=Lines-1,col=Columns-1) refers to the bottom-right + corner. Fractional values are allowed, but the builtin implementation + (used by non-multigrid UIs) will always round down to nearest integer. + + Out-of-bounds values, and configurations that make the float not fit + inside the main editor, are allowed. The builtin implementation truncates + values so floats are fully within the main screen grid. External GUIs + 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 > + 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) > - vim.api.nvim_open_win(0, false, - {relative='win', width=12, height=3, bufpos={100,10}}) + Example (Lua): buffer-relative float (travels as buffer is scrolled) > + vim.api.nvim_open_win(0, false, + {relative='win', width=12, height=3, bufpos={100,10}}) < - Attributes: ~ - not allowed when |textlock| is active - - Parameters: ~ - {buffer} Buffer to display, or 0 for current buffer - {enter} Enter the window (make it the current window) - {config} Map defining the window configuration. Keys: - • relative: Sets the window layout to - "floating", placed at (row,col) coordinates - relative to: - • "editor" The global editor grid - • "win" Window given by the `win` field, or - current window. - • "cursor" Cursor position in current window. - - • win: |window-ID| for relative="win". - • anchor: Decides which corner of the float to - place at (row,col): - • "NW" northwest (default) - • "NE" northeast - • "SW" southwest - • "SE" southeast - - • width: Window width (in character cells). - Minimum of 1. - • height: Window height (in character cells). - Minimum of 1. - • bufpos: Places float relative to buffer text - (only when relative="win"). Takes a tuple of - zero-indexed [line, column]. `row` and `col` if given are applied relative to this - position, else they default to: - • `row=1` and `col=0` if `anchor` is "NW" or - "NE" - • `row=0` and `col=0` if `anchor` is "SW" or - "SE" (thus like a tooltip near the buffer - text). - - • row: Row position in units of "screen cell - height", may be fractional. - • col: Column position in units of "screen cell - width", may be fractional. - • focusable: Enable focus by user actions - (wincmds, mouse events). Defaults to true. - Non-focusable windows can be entered by - |nvim_set_current_win()|. - • external: GUI should display the window as an - external top-level window. Currently accepts - no other positioning configuration together - with this. - • zindex: Stacking order. floats with higher `zindex` go on top on floats with lower indices. Must - be larger than zero. The following screen - elements have hard-coded z-indices: - • 100: insert completion popupmenu - • 200: message scrollback - • 250: cmdline completion popupmenu (when - wildoptions+=pum) The default value for - floats are 50. In general, values below 100 - are recommended, unless there is a good - reason to overshadow builtin elements. - - • style: Configure the appearance of the window. - Currently only takes one non-empty value: - • "minimal" Nvim will display the window with - many UI options disabled. This is useful - when displaying a temporary 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 'fillchars' to a space char, - and clearing the |EndOfBuffer| region in - 'winhighlight'. - - • border: Style of (optional) window border. - This can either be a string or an array. The - string values are - • "none": No border (default). - • "single": A single line box. - • "double": A double line box. - • "rounded": Like "single", but with rounded - corners ("╭" etc.). - • "solid": Adds padding by a single whitespace - cell. - • "shadow": A drop shadow effect by blending - with the background. - • If it is an array, it should have a length - of eight or any divisor of eight. The array - will specifify the eight chars building up - the border in a clockwise fashion starting - with the top-left corner. As an example, the - double box style could be specified as [ - "╔", "═" ,"╗", "║", "╝", "═", "╚", "║" ]. If - the number of chars are less than eight, - they will be repeated. Thus an ASCII border - could be specified as [ "/", "-", "\\", "|" - ], or all chars the same as [ "x" ]. An - empty string can be used to turn off a - specific border, for instance, [ "", "", "", - ">", "", "", "", "<" ] will only make - vertical borders but not horizontal ones. By - default, `FloatBorder` highlight is used, - which links to `WinSeparator` when not - defined. It could also be specified by - character: [ {"+", "MyCorner"}, {"x", - "MyBorder"} ]. - - • noautocmd: If true then no buffer-related - autocommand events such as |BufEnter|, - |BufLeave| or |BufWinEnter| may fire from - calling this function. - - Return: ~ - Window handle, or 0 on error + Attributes: ~ + not allowed when |textlock| is active + + Parameters: ~ + {buffer} Buffer to display, or 0 for current buffer + {enter} Enter the window (make it the current window) + {config} Map defining the window configuration. Keys: + • relative: Sets the window layout to "floating", placed at + (row,col) coordinates relative to: + • "editor" The global editor grid + • "win" Window given by the `win` field, or current + window. + • "cursor" Cursor position in current window. + + • win: |window-ID| for relative="win". + • anchor: Decides which corner of the float to place at + (row,col): + • "NW" northwest (default) + • "NE" northeast + • "SW" southwest + • "SE" southeast + + • width: Window width (in character cells). Minimum of 1. + • height: Window height (in character cells). Minimum of 1. + • bufpos: Places float relative to buffer text (only when + relative="win"). Takes a tuple of zero-indexed [line, + column]. `row` and `col` if given are applied relative to this position, else they + default to: + • `row=1` and `col=0` if `anchor` is "NW" or "NE" + • `row=0` and `col=0` if `anchor` is "SW" or "SE" (thus + like a tooltip near the buffer text). + + • row: Row position in units of "screen cell height", may be + fractional. + • col: Column position in units of "screen cell width", may + be fractional. + • focusable: Enable focus by user actions (wincmds, mouse + events). Defaults to true. Non-focusable windows can be + entered by |nvim_set_current_win()|. + • external: GUI should display the window as an external + top-level window. Currently accepts no other positioning + configuration together with this. + • zindex: Stacking order. floats with higher `zindex` go on top on floats with lower indices. Must be larger + than zero. The following screen elements have hard-coded + z-indices: + • 100: insert completion popupmenu + • 200: message scrollback + • 250: cmdline completion popupmenu (when + wildoptions+=pum) The default value for floats are 50. + In general, values below 100 are recommended, unless + there is a good reason to overshadow builtin elements. + + • style: Configure the appearance of the window. Currently + only takes one non-empty value: + • "minimal" Nvim will display the window with many UI + options disabled. This is useful when displaying a + temporary 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 'fillchars' to a space + char, and clearing the |EndOfBuffer| region in + 'winhighlight'. + + • border: Style of (optional) window border. This can either + be a string or an array. The string values are + • "none": No border (default). + • "single": A single line box. + • "double": A double line box. + • "rounded": Like "single", but with rounded corners ("╭" + etc.). + • "solid": Adds padding by a single whitespace cell. + • "shadow": A drop shadow effect by blending with the + background. + • If it is an array, it should have a length of eight or + any divisor of eight. The array will specifify the eight + chars building up the border in a clockwise fashion + starting with the top-left corner. As an example, the + double box style could be specified as [ "╔", "═" ,"╗", + "║", "╝", "═", "╚", "║" ]. If the number of chars are + less than eight, they will be repeated. Thus an ASCII + border could be specified as [ "/", "-", "\\", "|" ], or + all chars the same as [ "x" ]. An empty string can be + used to turn off a specific border, for instance, [ "", + "", "", ">", "", "", "", "<" ] will only make vertical + borders but not horizontal ones. By default, + `FloatBorder` highlight is used, which links to + `WinSeparator` when not defined. It could also be + specified by character: [ {"+", "MyCorner"}, {"x", + "MyBorder"} ]. + + • noautocmd: If true then no buffer-related autocommand + events such as |BufEnter|, |BufLeave| or |BufWinEnter| may + fire from calling this function. + + Return: ~ + Window handle, or 0 on error nvim_win_get_config({window}) *nvim_win_get_config()* - Gets window configuration. + Gets window configuration. - The returned value may be given to |nvim_open_win()|. + The returned value may be given to |nvim_open_win()|. - `relative` is empty for normal windows. + `relative` is empty for normal windows. - Parameters: ~ - {window} Window handle, or 0 for current window + Parameters: ~ + {window} Window handle, or 0 for current window - Return: ~ - Map defining the window configuration, see - |nvim_open_win()| + Return: ~ + Map defining the window configuration, see |nvim_open_win()| nvim_win_set_config({window}, {*config}) *nvim_win_set_config()* - Configures window layout. Currently only for floating and - external windows (including changing a split window to those - layouts). + Configures window layout. Currently only for floating and external windows + (including changing a split window to those layouts). - When reconfiguring a floating window, absent option keys will - not be changed. `row`/`col` and `relative` must be - reconfigured together. + When reconfiguring a floating window, absent option keys will not be + changed. `row`/`col` and `relative` must be reconfigured together. - Parameters: ~ - {window} Window handle, or 0 for current window - {config} Map defining the window configuration, see - |nvim_open_win()| + Parameters: ~ + {window} Window handle, or 0 for current window + {config} Map defining the window configuration, see |nvim_open_win()| - See also: ~ - |nvim_open_win()| + See also: ~ + |nvim_open_win()| ============================================================================== Tabpage Functions *api-tabpage* nvim_tabpage_del_var({tabpage}, {name}) *nvim_tabpage_del_var()* - Removes a tab-scoped (t:) variable + Removes a tab-scoped (t:) variable - Parameters: ~ - {tabpage} Tabpage handle, or 0 for current tabpage - {name} Variable name + Parameters: ~ + {tabpage} Tabpage handle, or 0 for current tabpage + {name} Variable name nvim_tabpage_get_number({tabpage}) *nvim_tabpage_get_number()* - Gets the tabpage number + Gets the tabpage number - Parameters: ~ - {tabpage} Tabpage handle, or 0 for current tabpage + Parameters: ~ + {tabpage} Tabpage handle, or 0 for current tabpage - Return: ~ - Tabpage number + Return: ~ + Tabpage number nvim_tabpage_get_var({tabpage}, {name}) *nvim_tabpage_get_var()* - Gets a tab-scoped (t:) variable + Gets a tab-scoped (t:) variable - Parameters: ~ - {tabpage} Tabpage handle, or 0 for current tabpage - {name} Variable name + Parameters: ~ + {tabpage} Tabpage handle, or 0 for current tabpage + {name} Variable name - Return: ~ - Variable value + Return: ~ + Variable value nvim_tabpage_get_win({tabpage}) *nvim_tabpage_get_win()* - Gets the current window in a tabpage + Gets the current window in a tabpage - Parameters: ~ - {tabpage} Tabpage handle, or 0 for current tabpage + Parameters: ~ + {tabpage} Tabpage handle, or 0 for current tabpage - Return: ~ - Window handle + Return: ~ + Window handle nvim_tabpage_is_valid({tabpage}) *nvim_tabpage_is_valid()* - Checks if a tabpage is valid + Checks if a tabpage is valid - Parameters: ~ - {tabpage} Tabpage handle, or 0 for current tabpage + Parameters: ~ + {tabpage} Tabpage handle, or 0 for current tabpage - Return: ~ - true if the tabpage is valid, false otherwise + Return: ~ + true if the tabpage is valid, false otherwise nvim_tabpage_list_wins({tabpage}) *nvim_tabpage_list_wins()* - Gets the windows in a tabpage + Gets the windows in a tabpage - Parameters: ~ - {tabpage} Tabpage handle, or 0 for current tabpage + Parameters: ~ + {tabpage} Tabpage handle, or 0 for current tabpage - Return: ~ - List of windows in `tabpage` + Return: ~ + List of windows in `tabpage` *nvim_tabpage_set_var()* nvim_tabpage_set_var({tabpage}, {name}, {value}) - Sets a tab-scoped (t:) variable + Sets a tab-scoped (t:) variable - Parameters: ~ - {tabpage} Tabpage handle, or 0 for current tabpage - {name} Variable name - {value} Variable value + Parameters: ~ + {tabpage} Tabpage handle, or 0 for current tabpage + {name} Variable name + {value} Variable value ============================================================================== Autocmd Functions *api-autocmd* nvim_clear_autocmds({*opts}) *nvim_clear_autocmds()* - Clear all autocommands that match the corresponding {opts}. To - delete a particular autocmd, see |nvim_del_autocmd|. + Clear all autocommands that match the corresponding {opts}. To delete a + particular autocmd, see |nvim_del_autocmd|. - Parameters: ~ - {opts} Parameters - • event: (string|table) Examples: - • event: "pat1" - • event: { "pat1" } - • event: { "pat1", "pat2", "pat3" } + Parameters: ~ + {opts} Parameters + • event: (string|table) Examples: + • event: "pat1" + • event: { "pat1" } + • event: { "pat1", "pat2", "pat3" } - • pattern: (string|table) - • pattern or patterns to match exactly. - • For example, if you have `*.py` as that - pattern for the autocmd, you must pass - `*.py` exactly to clear it. `test.py` will - not match the pattern. + • pattern: (string|table) + • pattern or patterns to match exactly. + • For example, if you have `*.py` as that pattern for the + autocmd, you must pass `*.py` exactly to clear it. + `test.py` will not match the pattern. - • defaults to clearing all patterns. - • NOTE: Cannot be used with {buffer} + • defaults to clearing all patterns. + • NOTE: Cannot be used with {buffer} - • buffer: (bufnr) - • clear only |autocmd-buflocal| autocommands. - • NOTE: Cannot be used with {pattern} + • buffer: (bufnr) + • clear only |autocmd-buflocal| autocommands. + • NOTE: Cannot be used with {pattern} - • group: (string|int) The augroup name or id. - • NOTE: If not passed, will only delete autocmds not in any group. + • group: (string|int) The augroup name or id. + • NOTE: If not passed, will only delete autocmds not in any group. nvim_create_augroup({name}, {*opts}) *nvim_create_augroup()* - Create or get an autocommand group |autocmd-groups|. + Create or get an autocommand group |autocmd-groups|. - To get an existing group id, do: > - local id = vim.api.nvim_create_augroup("MyGroup", { - clear = false - }) + To get an existing group id, do: > + local id = vim.api.nvim_create_augroup("MyGroup", { + clear = false + }) < - Parameters: ~ - {name} String: The name of the group - {opts} Dictionary Parameters - • clear (bool) optional: defaults to true. Clear - existing commands if the group already exists - |autocmd-groups|. + Parameters: ~ + {name} String: The name of the group + {opts} Dictionary Parameters + • clear (bool) optional: defaults to true. Clear existing + commands if the group already exists |autocmd-groups|. - Return: ~ - Integer id of the created group. + Return: ~ + Integer id of the created group. - See also: ~ - |autocmd-groups| + See also: ~ + |autocmd-groups| nvim_create_autocmd({event}, {*opts}) *nvim_create_autocmd()* - Create an |autocommand| + 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). + 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 + Example using callback: > + -- Lua function + local myluafun = function() print("This buffer enters") end - -- Vimscript function name (as a string) - local myvimfun = "g:MyVimFunction" + -- Vimscript function name (as a string) + local myvimfun = "g:MyVimFunction" - vim.api.nvim_create_autocmd({"BufEnter", "BufWinEnter"}, { - pattern = {"*.c", "*.h"}, - callback = myluafun, -- Or myvimfun - }) + vim.api.nvim_create_autocmd({"BufEnter", "BufWinEnter"}, { + pattern = {"*.c", "*.h"}, + callback = myluafun, -- Or myvimfun + }) < - 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 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 @@ -3431,282 +3224,263 @@ nvim_create_autocmd({event}, {*opts}) *nvim_create_autocmd()* }) < - Example using command: > - vim.api.nvim_create_autocmd({"BufEnter", "BufWinEnter"}, { - pattern = {"*.c", "*.h"}, - command = "echo 'Entering a C or C++ file'", - }) + Example using command: > + vim.api.nvim_create_autocmd({"BufEnter", "BufWinEnter"}, { + pattern = {"*.c", "*.h"}, + command = "echo 'Entering a C or C++ file'", + }) < - Example values for pattern: > - pattern = "*.py" - pattern = { "*.py", "*.pyi" } + Example values for pattern: > + pattern = "*.py" + pattern = { "*.py", "*.pyi" } < - Example values for event: > - "BufWritePre" - {"CursorHold", "BufWritePre", "BufWritePost"} + Example values for event: > + "BufWritePre" + {"CursorHold", "BufWritePre", "BufWritePost"} < - Parameters: ~ - {event} (string|array) The event or events to register - this autocommand - {opts} Dictionary of autocommand options: - • group (string|integer) optional: the - autocommand group name or id to match against. - • pattern (string|array) optional: pattern or - patterns to match against |autocmd-pattern|. - • buffer (integer) optional: buffer number for - buffer local autocommands |autocmd-buflocal|. - Cannot be used with {pattern}. - • desc (string) optional: description of the - autocommand. - • callback (function|string) optional: if a - string, the name of a Vimscript function to - call when this autocommand is triggered. - Otherwise, a Lua function which is called when - this autocommand is triggered. Cannot be used - with {command}. Lua callbacks can return true - to delete the autocommand; in addition, they - accept a single table argument with the - following keys: - • id: (number) the autocommand id - • event: (string) the name of the event that - triggered the autocommand |autocmd-events| - • group: (number|nil) the autocommand group id, - if it exists - • match: (string) the expanded value of - |<amatch>| - • buf: (number) the expanded value of |<abuf>| - • file: (string) the expanded value of - |<afile>| - • data: (any) arbitrary data passed to - |nvim_exec_autocmds()| - - • command (string) optional: Vim command to - execute on event. Cannot be used with - {callback} - • once (boolean) optional: defaults to false. Run - the autocommand only once |autocmd-once|. - • nested (boolean) optional: defaults to false. - Run nested autocommands |autocmd-nested|. - - Return: ~ - Integer id of the created autocommand. - - See also: ~ - |autocommand| - |nvim_del_autocmd()| + Parameters: ~ + {event} (string|array) The event or events to register this + autocommand + {opts} Dictionary of autocommand options: + • group (string|integer) optional: the autocommand group name + or id to match against. + • pattern (string|array) optional: pattern or patterns to + match against |autocmd-pattern|. + • buffer (integer) optional: buffer number for buffer local + autocommands |autocmd-buflocal|. Cannot be used with + {pattern}. + • desc (string) optional: description of the autocommand. + • callback (function|string) optional: if a string, the name + of a Vimscript function to call when this autocommand is + triggered. Otherwise, a Lua function which is called when + this autocommand is triggered. Cannot be used with + {command}. Lua callbacks can return true to delete the + autocommand; in addition, they accept a single table + argument with the following keys: + • id: (number) the autocommand id + • event: (string) the name of the event that triggered the + autocommand |autocmd-events| + • group: (number|nil) the autocommand group id, if it + exists + • match: (string) the expanded value of |<amatch>| + • buf: (number) the expanded value of |<abuf>| + • file: (string) the expanded value of |<afile>| + • data: (any) arbitrary data passed to + |nvim_exec_autocmds()| + + • command (string) optional: Vim command to execute on event. + Cannot be used with {callback} + • once (boolean) optional: defaults to false. Run the + autocommand only once |autocmd-once|. + • nested (boolean) optional: defaults to false. Run nested + autocommands |autocmd-nested|. + + Return: ~ + Integer id of the created autocommand. + + See also: ~ + |autocommand| + |nvim_del_autocmd()| nvim_del_augroup_by_id({id}) *nvim_del_augroup_by_id()* - Delete an autocommand group by id. + Delete an autocommand group by id. - To get a group id one can use |nvim_get_autocmds()|. + To get a group id one can use |nvim_get_autocmds()|. - NOTE: behavior differs from |augroup-delete|. When deleting a - group, autocommands contained in this group will also be - deleted and cleared. This group will no longer exist. + NOTE: behavior differs from |augroup-delete|. When deleting a group, + autocommands contained in this group will also be deleted and cleared. + This group will no longer exist. - Parameters: ~ - {id} Integer The id of the group. + Parameters: ~ + {id} Integer The id of the group. - See also: ~ - |nvim_del_augroup_by_name()| - |nvim_create_augroup()| + See also: ~ + |nvim_del_augroup_by_name()| + |nvim_create_augroup()| nvim_del_augroup_by_name({name}) *nvim_del_augroup_by_name()* - Delete an autocommand group by name. + Delete an autocommand group by name. - NOTE: behavior differs from |augroup-delete|. When deleting a - group, autocommands contained in this group will also be - deleted and cleared. This group will no longer exist. + NOTE: behavior differs from |augroup-delete|. When deleting a group, + autocommands contained in this group will also be deleted and cleared. + This group will no longer exist. - Parameters: ~ - {name} String The name of the group. + Parameters: ~ + {name} String The name of the group. - See also: ~ - |autocommand-groups| + See also: ~ + |autocommand-groups| nvim_del_autocmd({id}) *nvim_del_autocmd()* - Delete an autocommand by id. + Delete an autocommand by id. - NOTE: Only autocommands created via the API have an id. + NOTE: Only autocommands created via the API have an id. - Parameters: ~ - {id} Integer The id returned by nvim_create_autocmd + Parameters: ~ + {id} Integer The id returned by nvim_create_autocmd - See also: ~ - |nvim_create_autocmd()| + See also: ~ + |nvim_create_autocmd()| nvim_exec_autocmds({event}, {*opts}) *nvim_exec_autocmds()* - Execute all autocommands for {event} that match the - corresponding {opts} |autocmd-execute|. - - Parameters: ~ - {event} (String|Array) The event or events to execute - {opts} Dictionary of autocommand options: - • group (string|integer) optional: the - autocommand group name or id to match against. - |autocmd-groups|. - • pattern (string|array) optional: defaults to - "*" |autocmd-pattern|. Cannot be used with - {buffer}. - • buffer (integer) optional: buffer number - |autocmd-buflocal|. Cannot be used with - {pattern}. - • modeline (bool) optional: defaults to true. - Process the modeline after the autocommands - |<nomodeline>|. - • data (any): arbitrary data to send to the - autocommand callback. See - |nvim_create_autocmd()| for details. - - See also: ~ - |:doautocmd| + Execute all autocommands for {event} that match the corresponding {opts} + |autocmd-execute|. + + Parameters: ~ + {event} (String|Array) The event or events to execute + {opts} Dictionary of autocommand options: + • group (string|integer) optional: the autocommand group name + or id to match against. |autocmd-groups|. + • pattern (string|array) optional: defaults to "*" + |autocmd-pattern|. Cannot be used with {buffer}. + • buffer (integer) optional: buffer number + |autocmd-buflocal|. Cannot be used with {pattern}. + • modeline (bool) optional: defaults to true. Process the + modeline after the autocommands |<nomodeline>|. + • data (any): arbitrary data to send to the autocommand + callback. See |nvim_create_autocmd()| for details. + + See also: ~ + |:doautocmd| nvim_get_autocmds({*opts}) *nvim_get_autocmds()* - Get all autocommands that match the corresponding {opts}. - - These examples will get autocommands matching ALL the given - criteria: > - -- Matches all criteria - autocommands = vim.api.nvim_get_autocmds({ - group = "MyGroup", - event = {"BufEnter", "BufWinEnter"}, - pattern = {"*.c", "*.h"} - }) - - -- All commands from one group - autocommands = vim.api.nvim_get_autocmds({ - group = "MyGroup", - }) + Get all autocommands that match the corresponding {opts}. + + These examples will get autocommands matching ALL the given criteria: > + -- Matches all criteria + autocommands = vim.api.nvim_get_autocmds({ + group = "MyGroup", + event = {"BufEnter", "BufWinEnter"}, + pattern = {"*.c", "*.h"} + }) + + -- All commands from one group + autocommands = vim.api.nvim_get_autocmds({ + group = "MyGroup", + }) < - NOTE: When multiple patterns or events are provided, it will - find all the autocommands that match any combination of them. - - Parameters: ~ - {opts} Dictionary with at least one of the following: - • group (string|integer): the autocommand group - name or id to match against. - • event (string|array): event or events to match - against |autocmd-events|. - • pattern (string|array): pattern or patterns to - match against |autocmd-pattern|. - - Return: ~ - Array of autocommands matching the criteria, with each - item containing the following fields: - • id (number): the autocommand id (only when defined with - the API). - • group (integer): the autocommand group id. - • group_name (string): the autocommand group name. - • desc (string): the autocommand description. - • event (string): the autocommand event. - • command (string): the autocommand command. Note: this - will be empty if a callback is set. - • callback (function|string|nil): Lua function or name of - a Vim script function which is executed when this - autocommand is triggered. - • once (boolean): whether the autocommand is only run - once. - • pattern (string): the autocommand pattern. If the - autocommand is buffer local |autocmd-buffer-local|: - • buflocal (boolean): true if the autocommand is buffer - local. - • buffer (number): the buffer number. + NOTE: When multiple patterns or events are provided, it will find all the + autocommands that match any combination of them. + + Parameters: ~ + {opts} Dictionary with at least one of the following: + • group (string|integer): the autocommand group name or id to + match against. + • event (string|array): event or events to match against + |autocmd-events|. + • pattern (string|array): pattern or patterns to match against + |autocmd-pattern|. + + Return: ~ + Array of autocommands matching the criteria, with each item containing + the following fields: + • id (number): the autocommand id (only when defined with the API). + • group (integer): the autocommand group id. + • group_name (string): the autocommand group name. + • desc (string): the autocommand description. + • event (string): the autocommand event. + • command (string): the autocommand command. Note: this will be empty + if a callback is set. + • callback (function|string|nil): Lua function or name of a Vim script + function which is executed when this autocommand is triggered. + • once (boolean): whether the autocommand is only run once. + • pattern (string): the autocommand pattern. If the autocommand is + buffer local |autocmd-buffer-local|: + • buflocal (boolean): true if the autocommand is buffer local. + • buffer (number): the buffer number. ============================================================================== UI Functions *api-ui* nvim_ui_attach({width}, {height}, {options}) *nvim_ui_attach()* - Activates UI events on the channel. + Activates UI events on the channel. - Entry point of all UI clients. Allows |--embed| to continue - startup. Implies that the client is ready to show the UI. Adds - the client to the list of UIs. |nvim_list_uis()| + Entry point of all UI clients. Allows |--embed| to continue startup. + Implies that the client is ready to show the UI. Adds the client to the + list of UIs. |nvim_list_uis()| - Note: - If multiple UI clients are attached, the global screen - dimensions degrade to the smallest client. E.g. if client - A requests 80x40 but client B requests 200x100, the global - screen has size 80x40. + Note: + If multiple UI clients are attached, the global screen dimensions + degrade to the smallest client. E.g. if client A requests 80x40 but + client B requests 200x100, the global screen has size 80x40. - Attributes: ~ - |RPC| only + Attributes: ~ + |RPC| only - Parameters: ~ - {width} Requested screen columns - {height} Requested screen rows - {options} |ui-option| map + Parameters: ~ + {width} Requested screen columns + {height} Requested screen rows + {options} |ui-option| map nvim_ui_detach() *nvim_ui_detach()* - Deactivates UI events on the channel. + Deactivates UI events on the channel. - Removes the client from the list of UIs. |nvim_list_uis()| + Removes the client from the list of UIs. |nvim_list_uis()| - Attributes: ~ - |RPC| only + Attributes: ~ + |RPC| only *nvim_ui_pum_set_bounds()* nvim_ui_pum_set_bounds({width}, {height}, {row}, {col}) - Tells Nvim the geometry of the popumenu, to align floating - windows with an external popup menu. - - Note that this method is not to be confused with - |nvim_ui_pum_set_height()|, which sets the number of visible - items in the popup menu, while this function sets the bounding - box of the popup menu, including visual elements such as - borders and sliders. Floats need not use the same font size, - nor be anchored to exact grid corners, so one can set - floating-point numbers to the popup menu geometry. - - Attributes: ~ - |RPC| only - - Parameters: ~ - {width} Popupmenu width. - {height} Popupmenu height. - {row} Popupmenu row. - {col} Popupmenu height. + Tells Nvim the geometry of the popumenu, to align floating windows with an + external popup menu. + + Note that this method is not to be confused with + |nvim_ui_pum_set_height()|, which sets the number of visible items in the + popup menu, while this function sets the bounding box of the popup menu, + including visual elements such as borders and sliders. Floats need not use + the same font size, nor be anchored to exact grid corners, so one can set + floating-point numbers to the popup menu geometry. + + Attributes: ~ + |RPC| only + + Parameters: ~ + {width} Popupmenu width. + {height} Popupmenu height. + {row} Popupmenu row. + {col} Popupmenu height. nvim_ui_pum_set_height({height}) *nvim_ui_pum_set_height()* - Tells Nvim the number of elements displaying in the popumenu, - to decide <PageUp> and <PageDown> movement. + Tells Nvim the number of elements displaying in the popumenu, to decide + <PageUp> and <PageDown> movement. - Attributes: ~ - |RPC| only + Attributes: ~ + |RPC| only - Parameters: ~ - {height} Popupmenu height, must be greater than zero. + Parameters: ~ + {height} Popupmenu height, must be greater than zero. nvim_ui_set_option({name}, {value}) *nvim_ui_set_option()* - TODO: Documentation + TODO: Documentation - Attributes: ~ - |RPC| only + Attributes: ~ + |RPC| only nvim_ui_try_resize({width}, {height}) *nvim_ui_try_resize()* - TODO: Documentation + TODO: Documentation - Attributes: ~ - |RPC| only + Attributes: ~ + |RPC| only *nvim_ui_try_resize_grid()* nvim_ui_try_resize_grid({grid}, {width}, {height}) - Tell Nvim to resize a grid. Triggers a grid_resize event with - the requested grid size or the maximum size if it exceeds size - limits. + Tell Nvim to resize a grid. Triggers a grid_resize event with the + requested grid size or the maximum size if it exceeds size limits. - On invalid grid handle, fails with error. + On invalid grid handle, fails with error. - Attributes: ~ - |RPC| only + Attributes: ~ + |RPC| only - Parameters: ~ - {grid} The handle of the grid to be changed. - {width} The new requested width. - {height} The new requested height. + Parameters: ~ + {grid} The handle of the grid to be changed. + {width} The new requested width. + {height} The new requested height. - vim:tw=78:ts=8:ft=help:norl: + vim:tw=78:ts=8:sw=4:sts=4:et:ft=help:norl: diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt index 59e5c078a3..63226fe701 100644 --- a/runtime/doc/autocmd.txt +++ b/runtime/doc/autocmd.txt @@ -441,8 +441,8 @@ CompleteChanged *CompleteChanged* Non-recursive (event cannot trigger itself). Cannot change the text. |textlock| - The size and position of the popup are also - available by calling |pum_getpos()|. + The size and position of the popup are also + available by calling |pum_getpos()|. *CompleteDonePre* CompleteDonePre After Insert mode completion is done. Either diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt index c56ab70774..a2e15142e7 100644 --- a/runtime/doc/builtin.txt +++ b/runtime/doc/builtin.txt @@ -77,6 +77,7 @@ changenr() Number current change number chanclose({id} [, {stream}]) Number Closes a channel or one of its streams chansend({id}, {data}) Number Writes {data} to channel char2nr({expr} [, {utf8}]) Number ASCII/UTF-8 value of first char in {expr} +charclass({string}) Number character class of {string} charcol({expr}) Number column number of cursor or mark charidx({string}, {idx} [, {countcc}]) Number char index of byte {idx} in {string} @@ -119,7 +120,7 @@ dictwatcherdel({dict}, {pattern}, {callback}) did_filetype() Number |TRUE| if FileType autocommand event used diff_filler({lnum}) Number diff filler lines about {lnum} diff_hlID({lnum}, {col}) Number diff highlighting at {lnum}/{col} -digraph_get({chars}) String get the digraph of {chars} +digraph_get({chars}) String get the |digraph| of {chars} digraph_getlist([{listall}]) List get all |digraph|s digraph_set({chars}, {digraph}) Boolean register |digraph| digraph_setlist({digraphlist}) Boolean register multiple |digraph|s @@ -239,8 +240,8 @@ haslocaldir([{winnr} [, {tabnr}]]) the tab executed |:tcd| hasmapto({what} [, {mode} [, {abbr}]]) Number |TRUE| if mapping to {what} exists -histadd({history}, {item}) String add an item to a history -histdel({history} [, {item}]) String remove an item from a history +histadd({history}, {item}) Number add an item to a history +histdel({history} [, {item}]) Number remove an item from a history histget({history} [, {index}]) String get the item {index} from a history histnr({history}) Number highest index of a history hlID({name}) Number syntax ID of highlight group {name} @@ -396,8 +397,10 @@ setbufline({expr}, {lnum}, {text}) Number set line {lnum} to {text} in buffer {expr} setbufvar({buf}, {varname}, {val}) set {varname} in buffer {buf} to {val} +setcellwidths({list}) none set character cell width overrides setcharpos({expr}, {list}) Number set the {expr} position to {list} setcharsearch({dict}) Dict set character search from {dict} +setcmdline({str} [, {pos}]) Number set command-line setcmdpos({pos}) Number set cursor position in command-line setcursorcharpos({list}) Number move cursor to position in {list} setenv({name}, {val}) none set environment variable @@ -586,7 +589,7 @@ acos({expr}) *acos()* {expr} must evaluate to a |Float| or a |Number| in the range [-1, 1]. Returns NaN if {expr} is outside the range [-1, 1]. Returns - 0.0 if {expr} is not a |Float| or a |Number|. + 0.0 if {expr} is not a |Float| or a |Number|. Examples: > :echo acos(0) < 1.570796 > @@ -782,7 +785,8 @@ browsedir({title}, {initdir}) browsing is not possible, an empty string is returned. bufadd({name}) *bufadd()* - Add a buffer to the buffer list with String {name}. + Add a buffer to the buffer list with name {name} (must be a + String). If a buffer for file {name} already exists, return that buffer number. Otherwise return the buffer number of the newly created buffer. When {name} is an empty string then a new @@ -833,7 +837,8 @@ bufload({buf}) *bufload()* Ensure the buffer {buf} is loaded. When the buffer name refers to an existing file then the file is read. Otherwise the buffer will be empty. If the buffer was already loaded - then there is no change. + then there is no change. If the buffer is not related to a + file the no file is read (e.g., when 'buftype' is "nofile"). If there is an existing swap file for the file of the buffer, there will be no dialog, the buffer will be loaded anyway. The {buf} argument is used like with |bufexists()|. @@ -1025,7 +1030,7 @@ chanclose({id} [, {stream}]) *chanclose()* are closed. If the channel is a pty, this will then close the pty master, sending SIGHUP to the job process. For a socket, there is only one stream, and {stream} should be - ommited. + omitted. chansend({id}, {data}) *chansend()* Send data to channel {id}. For a job, it writes it to the @@ -1063,7 +1068,19 @@ char2nr({string} [, {utf8}]) *char2nr()* Can also be used as a |method|: > GetChar()->char2nr() -< + +charclass({string}) *charclass()* + Return the character class of the first character in {string}. + The character class is one of: + 0 blank + 1 punctuation + 2 word character + 3 emoji + other specific Unicode class + The class is used in patterns and word motions. + Returns 0 if {string} is not a |String|. + + *charcol()* charcol({expr}) Same as |col()| but returns the character index of the column position given with {expr} instead of the byte position. @@ -1122,6 +1139,9 @@ chdir({dir}) *chdir()* " ... do some work call chdir(save_dir) endif + +< Can also be used as a |method|: > + GetDir()->chdir() < cindent({lnum}) *cindent()* Get the amount of indent for line {lnum} according the C @@ -1135,8 +1155,8 @@ cindent({lnum}) *cindent()* GetLnum()->cindent() clearmatches([{win}]) *clearmatches()* - Clears all matches previously defined for the current window - by |matchadd()| and the |:match| commands. + Clears all matches previously defined for the current window + by |matchadd()| and the |:match| commands. If {win} is specified, use the window with this number or window ID instead of the current window. @@ -1512,6 +1532,18 @@ cursor({list}) Can also be used as a |method|: > GetCursorPos()->cursor() +debugbreak({pid}) *debugbreak()* + Specifically used to interrupt a program being debugged. It + will cause process {pid} to get a SIGTRAP. Behavior for other + processes is undefined. See |terminal-debugger|. + {Sends a SIGINT to a process {pid} other than MS-Windows} + + Returns |TRUE| if successfully interrupted the program. + Otherwise returns |FALSE|. + + Can also be used as a |method|: > + GetPid()->debugbreak() + deepcopy({expr} [, {noref}]) *deepcopy()* *E698* Make a copy of {expr}. For Numbers and Strings this isn't different from using {expr} directly. @@ -1953,18 +1985,6 @@ exp({expr}) *exp()* Can also be used as a |method|: > Compute()->exp() -debugbreak({pid}) *debugbreak()* - Specifically used to interrupt a program being debugged. It - will cause process {pid} to get a SIGTRAP. Behavior for other - processes is undefined. See |terminal-debugger|. - {Sends a SIGINT to a process {pid} other than MS-Windows} - - Returns |TRUE| if successfully interrupted the program. - Otherwise returns |FALSE|. - - Can also be used as a |method|: > - GetPid()->debugbreak() - expand({string} [, {nosuf} [, {list}]]) *expand()* Expand wildcards and the following special keywords in {string}. 'wildignorecase' applies. @@ -1988,6 +2008,7 @@ expand({string} [, {nosuf} [, {list}]]) *expand()* <afile> autocmd file name <abuf> autocmd buffer number (as a String!) <amatch> autocmd matched name + <cexpr> C expression under the cursor <sfile> sourced script file or function name <slnum> sourced script line number or function line number @@ -1995,6 +2016,9 @@ expand({string} [, {nosuf} [, {list}]]) *expand()* a function <SID> "<SNR>123_" where "123" is the current script ID |<SID>| + <script> sourced script file, or script file + where the current function was defined + <stack> call stack <cword> word under the cursor <cWORD> WORD under the cursor <client> the {clientid} of the last received @@ -2027,6 +2051,9 @@ expand({string} [, {nosuf} [, {list}]]) *expand()* is not defined, an empty string is used. Using "%:p" in a buffer with no name, results in the current directory, with a '/' added. + When 'verbose' is set then expanding '%', '#' and <> items + will result in an error message if the argument cannot be + expanded. When {string} does not start with '%', '#' or '<', it is expanded like a file name is expanded on the command line. @@ -2058,10 +2085,13 @@ expandcmd({string}) *expandcmd()* like with |expand()|, and environment variables, anywhere in {string}. "~user" and "~/path" are only expanded at the start. - Returns the expanded string. Example: > + 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 ~ -< Can also be used as a |method|: > + Can also be used as a |method|: > GetCommand()->expandcmd() < extend({expr1}, {expr2} [, {expr3}]) *extend()* @@ -2851,7 +2881,8 @@ getcmdcompltype() *getcmdcompltype()* Only works when the command line is being edited, thus requires use of |c_CTRL-\_e| or |c_CTRL-R_=|. See |:command-completion| for the return string. - Also see |getcmdtype()|, |setcmdpos()| and |getcmdline()|. + Also see |getcmdtype()|, |setcmdpos()|, |getcmdline()| and + |setcmdline()|. Returns an empty string when completion is not defined. getcmdline() *getcmdline()* @@ -2860,7 +2891,8 @@ getcmdline() *getcmdline()* |c_CTRL-R_=|. Example: > :cmap <F7> <C-\>eescape(getcmdline(), ' \')<CR> -< Also see |getcmdtype()|, |getcmdpos()| and |setcmdpos()|. +< Also see |getcmdtype()|, |getcmdpos()|, |setcmdpos()| and + |setcmdline()|. Returns an empty string when entering a password or using |inputsecret()|. @@ -2870,7 +2902,8 @@ getcmdpos() *getcmdpos()* Only works when editing the command line, thus requires use of |c_CTRL-\_e| or |c_CTRL-R_=| or an expression mapping. Returns 0 otherwise. - Also see |getcmdtype()|, |setcmdpos()| and |getcmdline()|. + Also see |getcmdtype()|, |setcmdpos()|, |getcmdline()| and + |setcmdline()|. getcmdscreenpos() *getcmdscreenpos()* Return the screen position of the cursor in the command line @@ -2879,7 +2912,8 @@ getcmdscreenpos() *getcmdscreenpos()* Only works when editing the command line, thus requires use of |c_CTRL-\_e| or |c_CTRL-R_=| or an expression mapping. Returns 0 otherwise. - Also see |getcmdpos()|, |setcmdpos()|. + Also see |getcmdpos()|, |setcmdpos()|, |getcmdline()| and + |setcmdline()|. getcmdtype() *getcmdtype()* Return the current command-line type. Possible return values @@ -2980,10 +3014,10 @@ getcurpos([{winid}]) current value of the buffer if it is not the current window. If {winid} is invalid a list with zeroes is returned. - This can be used to save and restore the cursor position: > - let save_cursor = getcurpos() - MoveTheCursorAround - call setpos('.', save_cursor) + This can be used to save and restore the cursor position: > + let save_cursor = getcurpos() + MoveTheCursorAround + call setpos('.', save_cursor) < Note that this only works within the window. See |winrestview()| for restoring more state. @@ -3118,7 +3152,7 @@ getjumplist([{winnr} [, {tabnr}]]) *getjumplist()* {winnr} can also be a |window-ID|. With {winnr} and {tabnr} use the window in the specified tab page. If {winnr} or {tabnr} is invalid, an empty list is - returned. + returned. The returned list contains two entries: a list with the jump locations and the last used jump position number in the list. @@ -3505,7 +3539,7 @@ gettabwinvar({tabnr}, {winnr}, {varname} [, {def}]) *gettabwinvar()* Get the value of window-local variable {varname} in window {winnr} in tab page {tabnr}. The {varname} argument is a string. When {varname} is empty a - dictionary with all window-local variables is returned. + dictionary with all window-local variables is returned. When {varname} is equal to "&" get the values of all window-local options in a |Dictionary|. Otherwise, when {varname} starts with "&" get the value of a @@ -4727,9 +4761,9 @@ maparg({name} [, {mode} [, {abbr} [, {dict}]]]) *maparg()* listing. When there is no mapping for {name}, an empty String is - returned if {dict} is FALSE, otherwise returns an empty Dict. - When the mapping for {name} is empty, then "<Nop>" is - returned. + returned if {dict} is FALSE, otherwise returns an empty Dict. + When the mapping for {name} is empty, then "<Nop>" is + returned. The {name} can have special key names, like in the ":map" command. @@ -4944,7 +4978,7 @@ matchadd({group}, {pattern} [, {priority} [, {id} [, {dict}]]]) highlighted matches. The dict can have the following members: conceal Special character to show instead of the - match (only for |hl-Conceal| highlighed + match (only for |hl-Conceal| highlighted matches, see |:syn-cchar|) window Instead of the current window use the window with this number or window ID. @@ -5903,7 +5937,7 @@ pum_getpos() *pum_getpos()* size total nr of items scrollbar |TRUE| if scrollbar is visible - The values are the same as in |v:event| during |CompleteChanged|. + The values are the same as in |v:event| during |CompleteChanged|. pumvisible() *pumvisible()* Returns non-zero when the popup menu is visible, zero @@ -6398,8 +6432,10 @@ search({pattern} [, {flags} [, {stopline} [, {timeout} [, {skip}]]]]) starts in column zero and then matches before the cursor are skipped. When the 'c' flag is present in 'cpo' the next search starts after the match. Without the 'c' flag the next - search starts one column further. This matters for - overlapping matches. + search starts one column after the start of the match. This + matters for overlapping matches. See |cpo-c|. You can also + insert "\ze" to change where the match ends, see |/\ze|. + When searching backwards and the 'z' flag is given then the search starts in column zero, thus no match in the current line will be found (unless wrapping around the end of the @@ -6653,7 +6689,7 @@ searchpair({start}, {middle}, {end} [, {flags} [, {skip} When {skip} is omitted or empty, every match is accepted. When evaluating {skip} causes an error the search is aborted and -1 returned. - {skip} can be a string, a lambda, a funcref or a partial. + {skip} can be a string, a lambda, a funcref or a partial. Anything else makes the function fail. For {stopline} and {timeout} see |search()|. @@ -6817,6 +6853,33 @@ setbufvar({buf}, {varname}, {val}) *setbufvar()* third argument: > GetValue()->setbufvar(buf, varname) + +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. + 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. + + 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([]); +< You can use the script $VIMRUNTIME/tools/emoji_list.vim to see + the effect for known emoji characters. + setcharpos({expr}, {list}) *setcharpos()* Same as |setpos()| but uses the specified column number as the character index instead of the byte index in the line. @@ -6854,6 +6917,16 @@ setcharsearch({dict}) *setcharsearch()* Can also be used as a |method|: > SavedSearch()->setcharsearch() +setcmdline({str} [, {pos}]) *setcmdline()* + Set the command line to {str} and set the cursor position to + {pos}. + If {pos} is omitted, the cursor is positioned after the text. + Returns 0 when successful, 1 when not editing the command + line. + + Can also be used as a |method|: > + GetText()->setcmdline() + setcmdpos({pos}) *setcmdpos()* Set the cursor position in the command line to byte position {pos}. The first position is 1. @@ -6866,8 +6939,8 @@ setcmdpos({pos}) *setcmdpos()* before inserting the resulting text. When the number is too big the cursor is put at the end of the line. A number smaller than one has undefined results. - Returns FALSE when successful, TRUE when not editing the - command line. + Returns 0 when successful, 1 when not editing the command + line. Can also be used as a |method|: > GetPos()->setcmdpos() @@ -7651,29 +7724,28 @@ stdpath({what}) *stdpath()* *E6100* config String User configuration directory. |init.vim| is stored here. config_dirs List Other configuration directories. - data String User data directory. The |shada-file| - is stored here. + data String User data directory. data_dirs List Other data directories. log String Logs directory (for use by plugins too). run String Run directory: temporary, local storage for sockets, named pipes, etc. state String Session state directory: storage for file - drafts, undo, shada, etc. + drafts, undo, |shada|, etc. Example: > :echo stdpath("config") str2float({string} [, {quoted}]) *str2float()* - Convert String {string} to a Float. This mostly works the - same as when using a floating point number in an expression, - see |floating-point-format|. But it's a bit more permissive. - E.g., "1e40" is accepted, while in an expression you need to - write "1.0e40". The hexadecimal form "0x123" is also - accepted, but not others, like binary or octal. - When {quoted} is present and non-zero then embedded single - quotes before the dot are ignored, thus "1'000.0" is a - thousand. + Convert String {string} to a Float. This mostly works the + same as when using a floating point number in an expression, + see |floating-point-format|. But it's a bit more permissive. + E.g., "1e40" is accepted, while in an expression you need to + write "1.0e40". The hexadecimal form "0x123" is also + accepted, but not others, like binary or octal. + When {quoted} is present and non-zero then embedded single + quotes before the dot are ignored, thus "1'000.0" is a + thousand. Text after the number is silently ignored. The decimal point is always '.', no matter what the locale is set to. A comma ends the number: "12,345.67" is converted to diff --git a/runtime/doc/cmdline.txt b/runtime/doc/cmdline.txt index 5d82f5985b..f19671e713 100644 --- a/runtime/doc/cmdline.txt +++ b/runtime/doc/cmdline.txt @@ -875,16 +875,28 @@ Note: these are typed literally, they are not special keys! match with (for FileType, Syntax and SpellFileMissing events). When the match is with a file name, it is expanded to the - full path. + full path. *:<sfile>* *<sfile>* <sfile> When executing a `:source` command, is replaced with the file name of the sourced file. *E498* - When executing a function, is replaced with: - "function {function-name}[{lnum}]" - function call nesting is indicated like this: - "function {function-name1}[{lnum}]..{function-name2}[{lnum}]" + When executing a function, is replaced with the call stack, + as with <stack> (this is for backwards compatibility, using + <stack> or <script> is preferred). Note that filename-modifiers are useless when <sfile> is - used inside a function. + not used inside a script. + *:<stack>* *<stack>* + <stack> is replaced with the call stack, using + "function {function-name}[{lnum}]" for a function line + and "script {file-name}[{lnum}]" for a script line, and + ".." in between items. E.g.: + "function {function-name1}[{lnum}]..{function-name2}[{lnum}]" + If there is no call stack you get error *E489* . + *:<script>* *<script>* + <script> When executing a `:source` command, is replaced with the file + name of the sourced file. When executing a function, is + replaced with the file name of the script where it is + defined. + If the file name cannot be determined you get error *E1274* . *:<slnum>* *<slnum>* <slnum> When executing a `:source` command, is replaced with the line number. *E842* diff --git a/runtime/doc/diagnostic.txt b/runtime/doc/diagnostic.txt index 7fb10f2a66..e1b52746be 100644 --- a/runtime/doc/diagnostic.txt +++ b/runtime/doc/diagnostic.txt @@ -38,60 +38,60 @@ optionally supplied). A good rule of thumb is that if a method is meant to modify the diagnostics for a buffer (e.g. |vim.diagnostic.set()|) then it requires a namespace. - *diagnostic-structure* + *diagnostic-structure* A diagnostic is a Lua table with the following keys. Required keys are indicated with (*): - bufnr: Buffer number - lnum(*): The starting line of the diagnostic - end_lnum: The final line of the diagnostic - col(*): The starting column of the diagnostic - end_col: The final column of the diagnostic - severity: The severity of the diagnostic |vim.diagnostic.severity| - message(*): The diagnostic text - source: The source of the diagnostic - code: The diagnostic code - user_data: Arbitrary data plugins or users can add + bufnr: Buffer number + lnum(*): The starting line of the diagnostic + end_lnum: The final line of the diagnostic + col(*): The starting column of the diagnostic + end_col: The final column of the diagnostic + severity: The severity of the diagnostic |vim.diagnostic.severity| + message(*): The diagnostic text + source: The source of the diagnostic + code: The diagnostic code + user_data: Arbitrary data plugins or users can add Diagnostics use the same indexing as the rest of the Nvim API (i.e. 0-based rows and columns). |api-indexing| - *vim.diagnostic.severity* *diagnostic-severity* + *vim.diagnostic.severity* *diagnostic-severity* The "severity" key in a diagnostic is one of the values defined in `vim.diagnostic.severity`: - vim.diagnostic.severity.ERROR - vim.diagnostic.severity.WARN - vim.diagnostic.severity.INFO - vim.diagnostic.severity.HINT + vim.diagnostic.severity.ERROR + vim.diagnostic.severity.WARN + vim.diagnostic.severity.INFO + vim.diagnostic.severity.HINT 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: > - vim.diagnostic.get(0, { severity = vim.diagnostic.severity.WARN }) + vim.diagnostic.get(0, { severity = vim.diagnostic.severity.WARN }) 2. A table with a "min" or "max" key (or both): > - vim.diagnostic.get(0, { severity = { min = vim.diagnostic.severity.WARN } }) + vim.diagnostic.get(0, { severity = { min = vim.diagnostic.severity.WARN } }) The latter form allows users to specify a range of severities. ============================================================================== -HANDLERS *diagnostic-handlers* +HANDLERS *diagnostic-handlers* Diagnostics are shown to the user with |vim.diagnostic.show()|. The display of diagnostics is managed through handlers. A handler is a table with a "show" and (optionally) a "hide" function. The "show" function has the signature > - function(namespace, bufnr, diagnostics, opts) + function(namespace, bufnr, diagnostics, opts) < and is responsible for displaying or otherwise handling the given diagnostics. The "hide" function takes care of "cleaning up" any actions taken by the "show" function and has the signature > - function(namespace, bufnr) + function(namespace, bufnr) < Handlers can be configured with |vim.diagnostic.config()| and added by creating a new key in `vim.diagnostic.handlers` (see @@ -105,31 +105,31 @@ function for a config option, the function has already been evaluated). Nvim provides these handlers by default: "virtual_text", "signs", and "underline". - *diagnostic-handlers-example* + *diagnostic-handlers-example* The example below creates a new handler that notifies the user of diagnostics with |vim.notify()|: > - -- It's good practice to namespace custom handlers to avoid collisions - vim.diagnostic.handlers["my/notify"] = { - show = function(namespace, bufnr, diagnostics, opts) - -- In our example, the opts table has a "log_level" option - local level = opts["my/notify"].log_level - - local name = vim.diagnostic.get_namespace(namespace).name - local msg = string.format("%d diagnostics in buffer %d from %s", - #diagnostics, - bufnr, - name) - vim.notify(msg, level) - end, - } - - -- Users can configure the handler - vim.diagnostic.config({ - ["my/notify"] = { - log_level = vim.log.levels.INFO - } - }) + -- It's good practice to namespace custom handlers to avoid collisions + vim.diagnostic.handlers["my/notify"] = { + show = function(namespace, bufnr, diagnostics, opts) + -- In our example, the opts table has a "log_level" option + local level = opts["my/notify"].log_level + + local name = vim.diagnostic.get_namespace(namespace).name + local msg = string.format("%d diagnostics in buffer %d from %s", + #diagnostics, + bufnr, + name) + vim.notify(msg, level) + end, + } + + -- Users can configure the handler + vim.diagnostic.config({ + ["my/notify"] = { + log_level = vim.log.levels.INFO + } + }) < In this example, there is nothing to do when diagnostics are hidden, so we omit the "hide" function. @@ -137,43 +137,43 @@ 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: > - -- Create a custom namespace. This will aggregate signs from all other - -- namespaces and only show the one with the highest severity on a - -- given line - local ns = vim.api.nvim_create_namespace("my_namespace") - - -- Get a reference to the original signs handler - local orig_signs_handler = vim.diagnostic.handlers.signs - - -- Override the built-in signs handler - vim.diagnostic.handlers.signs = { - show = function(_, bufnr, _, opts) - -- Get all diagnostics from the whole buffer rather than just the - -- diagnostics passed to the handler - local diagnostics = vim.diagnostic.get(bufnr) - - -- Find the "worst" diagnostic per line - local max_severity_per_line = {} - for _, d in pairs(diagnostics) do - local m = max_severity_per_line[d.lnum] - if not m or d.severity < m.severity then - max_severity_per_line[d.lnum] = d - end - end - - -- Pass the filtered diagnostics (with our custom namespace) to - -- the original handler - local filtered_diagnostics = vim.tbl_values(max_severity_per_line) - orig_signs_handler.show(ns, bufnr, filtered_diagnostics, opts) - end, - hide = function(_, bufnr) - orig_signs_handler.hide(ns, bufnr) - end, - } + -- Create a custom namespace. This will aggregate signs from all other + -- namespaces and only show the one with the highest severity on a + -- given line + local ns = vim.api.nvim_create_namespace("my_namespace") + + -- Get a reference to the original signs handler + local orig_signs_handler = vim.diagnostic.handlers.signs + + -- Override the built-in signs handler + vim.diagnostic.handlers.signs = { + show = function(_, bufnr, _, opts) + -- Get all diagnostics from the whole buffer rather than just the + -- diagnostics passed to the handler + local diagnostics = vim.diagnostic.get(bufnr) + + -- Find the "worst" diagnostic per line + local max_severity_per_line = {} + for _, d in pairs(diagnostics) do + local m = max_severity_per_line[d.lnum] + if not m or d.severity < m.severity then + max_severity_per_line[d.lnum] = d + end + end + + -- Pass the filtered diagnostics (with our custom namespace) to + -- the original handler + local filtered_diagnostics = vim.tbl_values(max_severity_per_line) + orig_signs_handler.show(ns, bufnr, filtered_diagnostics, opts) + end, + hide = function(_, bufnr) + orig_signs_handler.hide(ns, bufnr) + end, + } < ============================================================================== -HIGHLIGHTS *diagnostic-highlights* +HIGHLIGHTS *diagnostic-highlights* All highlights defined for diagnostics begin with `Diagnostic` followed by the type of highlight (e.g., `Sign`, `Underline`, etc.) and the severity (e.g. @@ -189,102 +189,102 @@ highlights), use the |:highlight| command: > highlight DiagnosticError guifg="BrightRed" < - *hl-DiagnosticError* + *hl-DiagnosticError* DiagnosticError - Used as the base highlight group. - Other Diagnostic highlights link to this by default (except Underline) + Used as the base highlight group. + Other Diagnostic highlights link to this by default (except Underline) - *hl-DiagnosticWarn* + *hl-DiagnosticWarn* DiagnosticWarn - Used as the base highlight group. - Other Diagnostic highlights link to this by default (except Underline) + Used as the base highlight group. + Other Diagnostic highlights link to this by default (except Underline) - *hl-DiagnosticInfo* + *hl-DiagnosticInfo* DiagnosticInfo - Used as the base highlight group. - Other Diagnostic highlights link to this by default (except Underline) + Used as the base highlight group. + Other Diagnostic highlights link to this by default (except Underline) - *hl-DiagnosticHint* + *hl-DiagnosticHint* DiagnosticHint - Used as the base highlight group. - Other Diagnostic highlights link to this by default (except Underline) + Used as the base highlight group. + Other Diagnostic highlights link to this by default (except Underline) - *hl-DiagnosticVirtualTextError* + *hl-DiagnosticVirtualTextError* DiagnosticVirtualTextError - Used for "Error" diagnostic virtual text. + Used for "Error" diagnostic virtual text. - *hl-DiagnosticVirtualTextWarn* + *hl-DiagnosticVirtualTextWarn* DiagnosticVirtualTextWarn - Used for "Warn" diagnostic virtual text. + Used for "Warn" diagnostic virtual text. - *hl-DiagnosticVirtualTextInfo* + *hl-DiagnosticVirtualTextInfo* DiagnosticVirtualTextInfo - Used for "Info" diagnostic virtual text. + Used for "Info" diagnostic virtual text. - *hl-DiagnosticVirtualTextHint* + *hl-DiagnosticVirtualTextHint* DiagnosticVirtualTextHint - Used for "Hint" diagnostic virtual text. + Used for "Hint" diagnostic virtual text. - *hl-DiagnosticUnderlineError* + *hl-DiagnosticUnderlineError* DiagnosticUnderlineError - Used to underline "Error" diagnostics. + Used to underline "Error" diagnostics. - *hl-DiagnosticUnderlineWarn* + *hl-DiagnosticUnderlineWarn* DiagnosticUnderlineWarn - Used to underline "Warn" diagnostics. + Used to underline "Warn" diagnostics. - *hl-DiagnosticUnderlineInfo* + *hl-DiagnosticUnderlineInfo* DiagnosticUnderlineInfo - Used to underline "Info" diagnostics. + Used to underline "Info" diagnostics. - *hl-DiagnosticUnderlineHint* + *hl-DiagnosticUnderlineHint* DiagnosticUnderlineHint - Used to underline "Hint" diagnostics. + Used to underline "Hint" diagnostics. - *hl-DiagnosticFloatingError* + *hl-DiagnosticFloatingError* DiagnosticFloatingError - Used to color "Error" diagnostic messages in diagnostics float. - See |vim.diagnostic.open_float()| + Used to color "Error" diagnostic messages in diagnostics float. + See |vim.diagnostic.open_float()| - *hl-DiagnosticFloatingWarn* + *hl-DiagnosticFloatingWarn* DiagnosticFloatingWarn - Used to color "Warn" diagnostic messages in diagnostics float. + Used to color "Warn" diagnostic messages in diagnostics float. - *hl-DiagnosticFloatingInfo* + *hl-DiagnosticFloatingInfo* DiagnosticFloatingInfo - Used to color "Info" diagnostic messages in diagnostics float. + Used to color "Info" diagnostic messages in diagnostics float. - *hl-DiagnosticFloatingHint* + *hl-DiagnosticFloatingHint* DiagnosticFloatingHint - Used to color "Hint" diagnostic messages in diagnostics float. + Used to color "Hint" diagnostic messages in diagnostics float. - *hl-DiagnosticSignError* + *hl-DiagnosticSignError* DiagnosticSignError - Used for "Error" signs in sign column. + Used for "Error" signs in sign column. - *hl-DiagnosticSignWarn* + *hl-DiagnosticSignWarn* DiagnosticSignWarn - Used for "Warn" signs in sign column. + Used for "Warn" signs in sign column. - *hl-DiagnosticSignInfo* + *hl-DiagnosticSignInfo* DiagnosticSignInfo - Used for "Info" signs in sign column. + Used for "Info" signs in sign column. - *hl-DiagnosticSignHint* + *hl-DiagnosticSignHint* DiagnosticSignHint - Used for "Hint" signs in sign column. + Used for "Hint" signs in sign column. ============================================================================== -SIGNS *diagnostic-signs* +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: > - sign define DiagnosticSignError text=E texthl=DiagnosticSignError linehl= numhl= - sign define DiagnosticSignWarn text=W texthl=DiagnosticSignWarn linehl= numhl= - sign define DiagnosticSignInfo text=I texthl=DiagnosticSignInfo linehl= numhl= - sign define DiagnosticSignHint text=H texthl=DiagnosticSignHint linehl= numhl= + sign define DiagnosticSignError text=E texthl=DiagnosticSignError linehl= numhl= + sign define DiagnosticSignWarn text=W texthl=DiagnosticSignWarn linehl= numhl= + sign define DiagnosticSignInfo text=I texthl=DiagnosticSignInfo linehl= numhl= + sign define DiagnosticSignHint text=H texthl=DiagnosticSignHint linehl= numhl= When the "severity_sort" option is set (see |vim.diagnostic.config()|) the priority of each sign depends on the severity of the associated diagnostic. @@ -292,459 +292,407 @@ Otherwise, all signs have the same priority (the value of the "priority" option in the "signs" table of |vim.diagnostic.config()| or 10 if unset). ============================================================================== -EVENTS *diagnostic-events* +EVENTS *diagnostic-events* - *DiagnosticChanged* -DiagnosticChanged After diagnostics have changed. + *DiagnosticChanged* +DiagnosticChanged After diagnostics have changed. Example: > - autocmd DiagnosticChanged * lua vim.diagnostic.setqflist({ open = false }) + autocmd DiagnosticChanged * lua vim.diagnostic.setqflist({ open = false }) < ============================================================================== Lua module: vim.diagnostic *diagnostic-api* config({opts}, {namespace}) *vim.diagnostic.config()* - Configure diagnostic options globally or for a specific - diagnostic namespace. + Configure diagnostic options globally or for a specific diagnostic + namespace. - Configuration can be specified globally, per-namespace, or - ephemerally (i.e. only for a single call to - |vim.diagnostic.set()| or |vim.diagnostic.show()|). Ephemeral - configuration has highest priority, followed by namespace - configuration, and finally global configuration. + Configuration can be specified globally, per-namespace, or ephemerally + (i.e. only for a single call to |vim.diagnostic.set()| or + |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 > - vim.diagnostic.config({ virtual_text = true }) + vim.diagnostic.config({ virtual_text = true }) < - and a diagnostic producer sets diagnostics with > + and a diagnostic producer sets diagnostics with > - vim.diagnostic.set(ns, 0, diagnostics, { virtual_text = false }) + vim.diagnostic.set(ns, 0, diagnostics, { virtual_text = false }) < - then virtual text will not be enabled for those diagnostics. - - Note: - Each of the configuration options below accepts one of the - following: - • `false`: Disable this feature - • `true`: Enable this feature, use default settings. - • `table`: Enable this feature with overrides. Use an - empty table to use default values. - • `function`: Function with signature (namespace, bufnr) - that returns any of the above. - - Parameters: ~ - {opts} (table|nil) When omitted or "nil", retrieve - the current configuration. Otherwise, a - configuration table with the following keys: - • underline: (default true) Use underline for - diagnostics. Options: - • severity: Only underline diagnostics - matching the given severity - |diagnostic-severity| - - • virtual_text: (default true) Use virtual - text for diagnostics. If multiple - diagnostics are set for a namespace, one - prefix per diagnostic + the last diagnostic - message are shown. Options: - • severity: Only show virtual text for - diagnostics matching the given severity - |diagnostic-severity| - • source: (boolean or string) Include the - diagnostic source in virtual text. Use - "if_many" to only show sources if there - is more than one diagnostic source in the - buffer. Otherwise, any truthy value means - to always show the diagnostic source. - • spacing: (number) Amount of empty spaces - inserted at the beginning of the virtual - text. - • prefix: (string) Prepend diagnostic - message with prefix. - • format: (function) A function that takes - a diagnostic as input and returns a - string. The return value is the text used - to display the diagnostic. Example: > - - function(diagnostic) - if diagnostic.severity == vim.diagnostic.severity.ERROR then - return string.format("E: %s", diagnostic.message) - end - return diagnostic.message - end + then virtual text will not be enabled for those diagnostics. + + Note: + Each of the configuration options below accepts one of the following: + • `false`: Disable this feature + • `true`: Enable this feature, use default settings. + • `table`: Enable this feature with overrides. Use an empty table to + use default values. + • `function`: Function with signature (namespace, bufnr) that returns + any of the above. + + Parameters: ~ + {opts} (table|nil) When omitted or "nil", retrieve the current + configuration. Otherwise, a configuration table with the + following keys: + • underline: (default true) Use underline for + diagnostics. Options: + • severity: Only underline diagnostics matching the + given severity |diagnostic-severity| + + • virtual_text: (default true) Use virtual text for + diagnostics. If multiple diagnostics are set for a + namespace, one prefix per diagnostic + the last + diagnostic message are shown. Options: + • severity: Only show virtual text for diagnostics + matching the given severity |diagnostic-severity| + • source: (boolean or string) Include the diagnostic + source in virtual text. Use "if_many" to only show + sources if there is more than one diagnostic source + in the buffer. Otherwise, any truthy value means to + always show the diagnostic source. + • spacing: (number) Amount of empty spaces inserted at + the beginning of the virtual text. + • prefix: (string) Prepend diagnostic message with + prefix. + • format: (function) A function that takes a diagnostic + as input and returns a string. The return value is + the text used to display the diagnostic. Example: > + + function(diagnostic) + if diagnostic.severity == vim.diagnostic.severity.ERROR then + return string.format("E: %s", diagnostic.message) + end + return diagnostic.message + end < - • signs: (default true) Use signs for - diagnostics. Options: - • severity: Only show signs for diagnostics - matching the given severity - |diagnostic-severity| - • priority: (number, default 10) Base - priority to use for signs. When - {severity_sort} is used, the priority of - a sign is adjusted based on its severity. - Otherwise, all signs use the same - priority. - - • float: Options for floating windows. See - |vim.diagnostic.open_float()|. - • update_in_insert: (default false) Update - diagnostics in Insert mode (if false, - diagnostics are updated on InsertLeave) - • severity_sort: (default false) Sort - diagnostics by severity. This affects the - order in which signs and virtual text are - displayed. When true, higher severities are - displayed before lower severities (e.g. - ERROR is displayed before WARN). Options: - • reverse: (boolean) Reverse sort order - {namespace} (number|nil) Update the options for the given - namespace. When omitted, update the global - diagnostic options. + • signs: (default true) Use signs for diagnostics. + Options: + • severity: Only show signs for diagnostics matching + the given severity |diagnostic-severity| + • priority: (number, default 10) Base priority to use + for signs. When {severity_sort} is used, the priority + of a sign is adjusted based on its severity. + Otherwise, all signs use the same priority. + + • float: Options for floating windows. See + |vim.diagnostic.open_float()|. + • update_in_insert: (default false) Update diagnostics in + Insert mode (if false, diagnostics are updated on + InsertLeave) + • severity_sort: (default false) Sort diagnostics by + severity. This affects the order in which signs and + virtual text are displayed. When true, higher + severities are displayed before lower severities (e.g. + ERROR is displayed before WARN). Options: + • reverse: (boolean) Reverse sort order + {namespace} (number|nil) Update the options for the given namespace. + When omitted, update the global diagnostic options. disable({bufnr}, {namespace}) *vim.diagnostic.disable()* - Disable diagnostics in the given buffer. + Disable diagnostics in the given buffer. - Parameters: ~ - {bufnr} (number|nil) Buffer number, or 0 for current - buffer. When omitted, disable diagnostics in - all buffers. - {namespace} (number|nil) Only disable diagnostics for the - given namespace. + Parameters: ~ + {bufnr} (number|nil) Buffer number, or 0 for current buffer. When + omitted, disable diagnostics in all buffers. + {namespace} (number|nil) Only disable diagnostics for the given + namespace. enable({bufnr}, {namespace}) *vim.diagnostic.enable()* - Enable diagnostics in the given buffer. + Enable diagnostics in the given buffer. - Parameters: ~ - {bufnr} (number|nil) Buffer number, or 0 for current - buffer. When omitted, enable diagnostics in - all buffers. - {namespace} (number|nil) Only enable diagnostics for the - given namespace. + Parameters: ~ + {bufnr} (number|nil) Buffer number, or 0 for current buffer. When + omitted, enable diagnostics in all buffers. + {namespace} (number|nil) Only enable diagnostics for the given + namespace. fromqflist({list}) *vim.diagnostic.fromqflist()* - Convert a list of quickfix items to a list of diagnostics. + Convert a list of quickfix items to a list of diagnostics. - Parameters: ~ - {list} (table) A list of quickfix items from - |getqflist()| or |getloclist()|. + Parameters: ~ + {list} (table) A list of quickfix items from |getqflist()| or + |getloclist()|. - Return: ~ - array of diagnostics |diagnostic-structure| + Return: ~ + array of diagnostics |diagnostic-structure| get({bufnr}, {opts}) *vim.diagnostic.get()* - Get current diagnostics. - - Parameters: ~ - {bufnr} (number|nil) Buffer number to get diagnostics - from. Use 0 for current buffer or nil for all - buffers. - {opts} (table|nil) A table with the following keys: - • 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|. + Get current diagnostics. + + Parameters: ~ + {bufnr} (number|nil) Buffer number to get diagnostics from. Use 0 for + current buffer or nil for all buffers. + {opts} (table|nil) A table with the following keys: + • 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|. get_namespace({namespace}) *vim.diagnostic.get_namespace()* - Get namespace metadata. + Get namespace metadata. - Parameters: ~ - {namespace} (number) Diagnostic namespace + Parameters: ~ + {namespace} (number) Diagnostic namespace - Return: ~ - (table) Namespace metadata + Return: ~ + (table) Namespace metadata get_namespaces() *vim.diagnostic.get_namespaces()* - Get current diagnostic namespaces. + Get current diagnostic namespaces. - Return: ~ - (table) A list of active diagnostic namespaces - |vim.diagnostic|. + Return: ~ + (table) A list of active diagnostic namespaces |vim.diagnostic|. get_next({opts}) *vim.diagnostic.get_next()* - Get the next diagnostic closest to the cursor position. + Get the next diagnostic closest to the cursor position. - Parameters: ~ - {opts} (table) See |vim.diagnostic.goto_next()| + Parameters: ~ + {opts} (table) See |vim.diagnostic.goto_next()| - Return: ~ - (table) Next diagnostic + Return: ~ + (table) Next diagnostic get_next_pos({opts}) *vim.diagnostic.get_next_pos()* - Return the position of the next diagnostic in the current - buffer. + Return the position of the next diagnostic in the current buffer. - Parameters: ~ - {opts} (table) See |vim.diagnostic.goto_next()| + Parameters: ~ + {opts} (table) See |vim.diagnostic.goto_next()| - Return: ~ - (table) Next diagnostic position as a (row, col) tuple. + Return: ~ + (table) Next diagnostic position as a (row, col) tuple. get_prev({opts}) *vim.diagnostic.get_prev()* - Get the previous diagnostic closest to the cursor position. + Get the previous diagnostic closest to the cursor position. - Parameters: ~ - {opts} (table) See |vim.diagnostic.goto_next()| + Parameters: ~ + {opts} (table) See |vim.diagnostic.goto_next()| - Return: ~ - (table) Previous diagnostic + Return: ~ + (table) Previous diagnostic get_prev_pos({opts}) *vim.diagnostic.get_prev_pos()* - Return the position of the previous diagnostic in the current - buffer. + Return the position of the previous diagnostic in the current buffer. - Parameters: ~ - {opts} (table) See |vim.diagnostic.goto_next()| + Parameters: ~ + {opts} (table) See |vim.diagnostic.goto_next()| - Return: ~ - (table) Previous diagnostic position as a (row, col) - tuple. + Return: ~ + (table) Previous diagnostic position as a (row, col) tuple. goto_next({opts}) *vim.diagnostic.goto_next()* - Move to the next diagnostic. - - Parameters: ~ - {opts} (table|nil) Configuration table with the following - keys: - • namespace: (number) Only consider diagnostics - from the given namespace. - • cursor_position: (cursor position) Cursor - position as a (row, col) tuple. See - |nvim_win_get_cursor()|. Defaults to the current - cursor position. - • wrap: (boolean, default true) Whether to loop - around file or not. Similar to 'wrapscan'. - • severity: See |diagnostic-severity|. - • float: (boolean or table, default true) If - "true", call |vim.diagnostic.open_float()| after - moving. If a table, pass the table as the {opts} - parameter to |vim.diagnostic.open_float()|. - Unless overridden, the float will show - diagnostics at the new cursor position (as if - "cursor" were passed to the "scope" option). - • win_id: (number, default 0) Window ID + Move to the next diagnostic. + + Parameters: ~ + {opts} (table|nil) Configuration table with the following keys: + • namespace: (number) Only consider diagnostics from the given + namespace. + • cursor_position: (cursor position) Cursor position as a + (row, col) tuple. See |nvim_win_get_cursor()|. Defaults to + the current cursor position. + • wrap: (boolean, default true) Whether to loop around file or + not. Similar to 'wrapscan'. + • severity: See |diagnostic-severity|. + • float: (boolean or table, default true) If "true", call + |vim.diagnostic.open_float()| after moving. If a table, pass + the table as the {opts} parameter to + |vim.diagnostic.open_float()|. Unless overridden, the float + will show diagnostics at the new cursor position (as if + "cursor" were passed to the "scope" option). + • win_id: (number, default 0) Window ID goto_prev({opts}) *vim.diagnostic.goto_prev()* - Move to the previous diagnostic in the current buffer. + Move to the previous diagnostic in the current buffer. - Parameters: ~ - {opts} (table) See |vim.diagnostic.goto_next()| + Parameters: ~ + {opts} (table) See |vim.diagnostic.goto_next()| hide({namespace}, {bufnr}) *vim.diagnostic.hide()* - Hide currently displayed diagnostics. + Hide currently displayed diagnostics. - This only clears the decorations displayed in the buffer. - Diagnostics can be redisplayed with |vim.diagnostic.show()|. - To completely remove diagnostics, use - |vim.diagnostic.reset()|. + This only clears the decorations displayed in the buffer. Diagnostics can + be redisplayed with |vim.diagnostic.show()|. To completely remove + diagnostics, use |vim.diagnostic.reset()|. - To hide diagnostics and prevent them from re-displaying, use - |vim.diagnostic.disable()|. + To hide diagnostics and prevent them from re-displaying, use + |vim.diagnostic.disable()|. - Parameters: ~ - {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. + Parameters: ~ + {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. *vim.diagnostic.match()* match({str}, {pat}, {groups}, {severity_map}, {defaults}) - Parse a diagnostic from a string. + Parse a diagnostic from a string. - For example, consider a line of output from a linter: > + For example, consider a line of output from a linter: > - WARNING filename:27:3: Variable 'foo' does not exist + 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: > - 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: ~ - {str} (string) String to parse diagnostics from. - {pat} (string) Lua pattern with capture groups. - {groups} (table) List of fields in a - |diagnostic-structure| to associate with - captures from {pat}. - {severity_map} (table) A table mapping the severity field - from {groups} with an item from - |vim.diagnostic.severity|. - {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}. + Parameters: ~ + {str} (string) String to parse diagnostics from. + {pat} (string) Lua pattern with capture groups. + {groups} (table) List of fields in a |diagnostic-structure| to + associate with captures from {pat}. + {severity_map} (table) A table mapping the severity field from + {groups} with an item from |vim.diagnostic.severity|. + {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}. open_float({opts}, {...}) *vim.diagnostic.open_float()* - Show diagnostics in a floating window. - - Parameters: ~ - {opts} (table|nil) Configuration table with the same keys - as |vim.lsp.util.open_floating_preview()| in - addition to the following: - • bufnr: (number) Buffer number to show - diagnostics from. Defaults to the current - buffer. - • namespace: (number) Limit diagnostics to the - given namespace - • scope: (string, default "line") Show diagnostics - from the whole buffer ("buffer"), the current - cursor line ("line"), or the current cursor - position ("cursor"). Shorthand versions are also - accepted ("c" for "cursor", "l" for "line", "b" - for "buffer"). - • pos: (number or table) If {scope} is "line" or - "cursor", use this position rather than the - cursor position. If a number, interpreted as a - line number; otherwise, a (row, col) tuple. - • severity_sort: (default false) Sort diagnostics - by severity. Overrides the setting from - |vim.diagnostic.config()|. - • severity: See |diagnostic-severity|. Overrides - the setting from |vim.diagnostic.config()|. - • header: (string or table) String to use as the - header for the floating window. If a table, it - is interpreted as a [text, hl_group] tuple. - Overrides the setting from - |vim.diagnostic.config()|. - • source: (boolean or string) Include the - diagnostic source in the message. Use "if_many" - to only show sources if there is more than one - source of diagnostics in the buffer. Otherwise, - any truthy value means to always show the - diagnostic source. Overrides the setting from - |vim.diagnostic.config()|. - • format: (function) A function that takes a - diagnostic as input and returns a string. The - return value is the text used to display the - diagnostic. Overrides the setting from - |vim.diagnostic.config()|. - • prefix: (function, string, or table) Prefix each - diagnostic in the floating window. If a - function, it must have the signature - (diagnostic, i, total) -> (string, string), - where {i} is the index of the diagnostic being - evaluated and {total} is the total number of - diagnostics displayed in the window. The - function should return a string which is - prepended to each diagnostic in the window as - well as an (optional) highlight group which will - be used to highlight the prefix. If {prefix} is - a table, it is interpreted as a [text, hl_group] - tuple as in |nvim_echo()|; otherwise, if - {prefix} is a string, it is prepended to each - diagnostic in the window with no highlight. - Overrides the setting from - |vim.diagnostic.config()|. - - Return: ~ - tuple ({float_bufnr}, {win_id}) + Show diagnostics in a floating window. + + Parameters: ~ + {opts} (table|nil) Configuration table with the same keys as + |vim.lsp.util.open_floating_preview()| in addition to the + following: + • bufnr: (number) Buffer number to show diagnostics from. + Defaults to the current buffer. + • namespace: (number) Limit diagnostics to the given namespace + • scope: (string, default "line") Show diagnostics from the + whole buffer ("buffer"), the current cursor line ("line"), + or the current cursor position ("cursor"). Shorthand + versions are also accepted ("c" for "cursor", "l" for + "line", "b" for "buffer"). + • pos: (number or table) If {scope} is "line" or "cursor", use + this position rather than the cursor position. If a number, + interpreted as a line number; otherwise, a (row, col) tuple. + • severity_sort: (default false) Sort diagnostics by severity. + Overrides the setting from |vim.diagnostic.config()|. + • severity: See |diagnostic-severity|. Overrides the setting + from |vim.diagnostic.config()|. + • header: (string or table) String to use as the header for + the floating window. If a table, it is interpreted as a + [text, hl_group] tuple. Overrides the setting from + |vim.diagnostic.config()|. + • source: (boolean or string) Include the diagnostic source in + the message. Use "if_many" to only show sources if there is + more than one source of diagnostics in the buffer. + Otherwise, any truthy value means to always show the + diagnostic source. Overrides the setting from + |vim.diagnostic.config()|. + • format: (function) A function that takes a diagnostic as + input and returns a string. The return value is the text + used to display the diagnostic. Overrides the setting from + |vim.diagnostic.config()|. + • prefix: (function, string, or table) Prefix each diagnostic + in the floating window. If a function, it must have the + signature (diagnostic, i, total) -> (string, string), where + {i} is the index of the diagnostic being evaluated and + {total} is the total number of diagnostics displayed in the + window. The function should return a string which is + prepended to each diagnostic in the window as well as an + (optional) highlight group which will be used to highlight + the prefix. If {prefix} is a table, it is interpreted as a + [text, hl_group] tuple as in |nvim_echo()|; otherwise, if + {prefix} is a string, it is prepended to each diagnostic in + the window with no highlight. Overrides the setting from + |vim.diagnostic.config()|. + + Return: ~ + tuple ({float_bufnr}, {win_id}) reset({namespace}, {bufnr}) *vim.diagnostic.reset()* - Remove all diagnostics from the given namespace. - - Unlike |vim.diagnostic.hide()|, this function removes all - saved diagnostics. They cannot be redisplayed using - |vim.diagnostic.show()|. To simply remove diagnostic - decorations in a way that they can be re-displayed, use - |vim.diagnostic.hide()|. - - Parameters: ~ - {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. + Remove all diagnostics from the given namespace. + + Unlike |vim.diagnostic.hide()|, this function removes all saved + diagnostics. They cannot be redisplayed using |vim.diagnostic.show()|. To + simply remove diagnostic decorations in a way that they can be + re-displayed, use |vim.diagnostic.hide()|. + + Parameters: ~ + {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. set({namespace}, {bufnr}, {diagnostics}, {opts}) *vim.diagnostic.set()* - Set diagnostics for the given namespace and buffer. + Set diagnostics for the given namespace and buffer. - Parameters: ~ - {namespace} (number) The diagnostic namespace - {bufnr} (number) Buffer number - {diagnostics} (table) A list of diagnostic items - |diagnostic-structure| - {opts} (table|nil) Display options to pass to - |vim.diagnostic.show()| + Parameters: ~ + {namespace} (number) The diagnostic namespace + {bufnr} (number) Buffer number + {diagnostics} (table) A list of diagnostic items + |diagnostic-structure| + {opts} (table|nil) Display options to pass to + |vim.diagnostic.show()| setloclist({opts}) *vim.diagnostic.setloclist()* - Add buffer diagnostics to the location list. - - Parameters: ~ - {opts} (table|nil) Configuration table with the following - keys: - • namespace: (number) Only add diagnostics from - the given namespace. - • winnr: (number, default 0) Window number to set - location list for. - • open: (boolean, default true) Open the location - list after setting. - • title: (string) Title of the location list. - Defaults to "Diagnostics". - • severity: See |diagnostic-severity|. + Add buffer diagnostics to the location list. + + Parameters: ~ + {opts} (table|nil) Configuration table with the following keys: + • namespace: (number) Only add diagnostics from the given + namespace. + • winnr: (number, default 0) Window number to set location + list for. + • open: (boolean, default true) Open the location list after + setting. + • title: (string) Title of the location list. Defaults to + "Diagnostics". + • severity: See |diagnostic-severity|. setqflist({opts}) *vim.diagnostic.setqflist()* - Add all diagnostics to the quickfix list. - - Parameters: ~ - {opts} (table|nil) Configuration table with the following - keys: - • namespace: (number) Only add diagnostics from - the given namespace. - • open: (boolean, default true) Open quickfix list - after setting. - • title: (string) Title of quickfix list. Defaults - to "Diagnostics". - • severity: See |diagnostic-severity|. + Add all diagnostics to the quickfix list. + + Parameters: ~ + {opts} (table|nil) Configuration table with the following keys: + • namespace: (number) Only add diagnostics from the given + namespace. + • open: (boolean, default true) Open quickfix list after + setting. + • title: (string) Title of quickfix list. Defaults to + "Diagnostics". + • severity: See |diagnostic-severity|. *vim.diagnostic.show()* 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. - {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, use the saved diagnostics for - the given namespace and buffer. This can be - used to display a list of diagnostics - without saving them or to display only a - subset of diagnostics. May not be used when - {namespace} or {bufnr} is nil. - {opts} (table|nil) Display options. See - |vim.diagnostic.config()|. + Display diagnostics for the given namespace and buffer. + + Parameters: ~ + {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, + use the saved diagnostics for the given namespace and + buffer. This can be used to display a list of + diagnostics without saving them or to display only a + subset of diagnostics. May not be used when {namespace} + or {bufnr} is nil. + {opts} (table|nil) Display options. See + |vim.diagnostic.config()|. toqflist({diagnostics}) *vim.diagnostic.toqflist()* - Convert a list of diagnostics to a list of quickfix items that - can be passed to |setqflist()| or |setloclist()|. + Convert a list of diagnostics to a list of quickfix items that can be + passed to |setqflist()| or |setloclist()|. - Parameters: ~ - {diagnostics} (table) List of diagnostics - |diagnostic-structure|. + Parameters: ~ + {diagnostics} (table) List of diagnostics |diagnostic-structure|. - Return: ~ - array of quickfix list items |setqflist-what| + Return: ~ + array of quickfix list items |setqflist-what| - vim:tw=78:ts=8:ft=help:norl: + vim:tw=78:ts=8:sw=4:sts=4:et:ft=help:norl: diff --git a/runtime/doc/editing.txt b/runtime/doc/editing.txt index c19d9f482b..dcb0bf8a2e 100644 --- a/runtime/doc/editing.txt +++ b/runtime/doc/editing.txt @@ -613,12 +613,19 @@ list of the current window. And after the last one: :+2argadd y a b c x y There is no check for duplicates, it is possible to - add a file to the argument list twice. - The currently edited file is not changed. + add a file to the argument list twice. You can use + |:argdedupe| to fix it afterwards: > + :argadd *.txt | argdedupe +< The currently edited file is not changed. Note: you can also use this method: > :args ## x < This will add the "x" item and sort the new list. +:argded[upe] *:argded* *:argdedupe* + Remove duplicate filenames from the argument list. + If your current file is a duplicate, your current file + will change to the original file index. + :argd[elete] {pattern} .. *:argd* *:argdelete* *E480* *E610* Delete files from the argument list that match the {pattern}s. {pattern} is used like a file pattern, diff --git a/runtime/doc/help.txt b/runtime/doc/help.txt index b97c9a2e3f..04e31e0680 100644 --- a/runtime/doc/help.txt +++ b/runtime/doc/help.txt @@ -25,6 +25,7 @@ Get specific help: It is possible to go directly to whatever you want help Option ' :help 'textwidth' Regular expression / :help /[ See |help-summary| for more contexts and an explanation. + See |notation| for an explanation of the help syntax. Search for help: Type ":help word", then hit CTRL-D to see matching help entries for "word". @@ -185,6 +186,8 @@ Other ~ |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 *standard-plugin-list* Standard plugins ~ diff --git a/runtime/doc/indent.txt b/runtime/doc/indent.txt index 1a1d8e30b0..c5411b5f16 100644 --- a/runtime/doc/indent.txt +++ b/runtime/doc/indent.txt @@ -979,25 +979,38 @@ indentation: > PYTHON *ft-python-indent* The amount of indent can be set for the following situations. The examples -given are the defaults. Note that the variables are set to an expression, so -that you can change the value of 'shiftwidth' later. +given are the defaults. Note that the dictionary values are set to an +expression, so that you can change the value of 'shiftwidth' later. Indent after an open paren: > - let g:pyindent_open_paren = 'shiftwidth() * 2' + let g:python_indent.open_paren = 'shiftwidth() * 2' Indent after a nested paren: > - let g:pyindent_nested_paren = 'shiftwidth()' + let g:python_indent.nested_paren = 'shiftwidth()' Indent for a continuation line: > - let g:pyindent_continue = 'shiftwidth() * 2' + let g:python_indent.continue = 'shiftwidth() * 2' + +By default, the closing paren on a multiline construct lines up under the first +non-whitespace character of the previous line. +If you prefer that it's lined up under the first character of the line that +starts the multiline construct, reset this key: > + let g:python_indent.closed_paren_align_last_line = v:false The method uses |searchpair()| to look back for unclosed parentheses. This can sometimes be slow, thus it timeouts after 150 msec. If you notice the indenting isn't correct, you can set a larger timeout in msec: > - let g:pyindent_searchpair_timeout = 500 + let g:python_indent.searchpair_timeout = 500 If looking back for unclosed parenthesis is still too slow, especially during a copy-paste operation, or if you don't need indenting inside multi-line parentheses, you can completely disable this feature: > - let g:pyindent_disable_parentheses_indenting = 1 + let g:python_indent.disable_parentheses_indenting = 1 + +For backward compatibility, these variables are also supported: > + g:pyindent_open_paren + g:pyindent_nested_paren + g:pyindent_continue + g:pyindent_searchpair_timeout + g:pyindent_disable_parentheses_indenting R *ft-r-indent* diff --git a/runtime/doc/index.txt b/runtime/doc/index.txt index 7d8a89887a..7f3ef20762 100644 --- a/runtime/doc/index.txt +++ b/runtime/doc/index.txt @@ -1143,6 +1143,7 @@ tag command action ~ be remapped |:args| :ar[gs] print the argument list |:argadd| :arga[dd] add items to the argument list +|:argdedupe| :argded[upe] remove duplicates from the argument list |:argdelete| :argd[elete] delete items from the argument list |:argedit| :arge[dit] add item to the argument list and edit it |:argdo| :argdo do a command on all items in the argument list diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt index 11f96db8c9..3393a1a1fd 100644 --- a/runtime/doc/lsp.txt +++ b/runtime/doc/lsp.txt @@ -10,7 +10,7 @@ Nvim supports the Language Server Protocol (LSP), which means it acts as a client to LSP servers and includes a Lua framework `vim.lsp` for building enhanced LSP tools. - https://microsoft.github.io/language-server-protocol/ + https://microsoft.github.io/language-server-protocol/ LSP facilitates features like go-to-definition, find-references, hover, completion, rename, format, refactor, etc., using semantic whole-project @@ -34,11 +34,11 @@ Follow these steps to get LSP features: 2. Configure the LSP client per language server. A minimal example: > - vim.lsp.start({ - name = 'my-server-name', - cmd = {'name-of-language-server-executable'}, - root_dir = vim.fs.dirname(vim.fs.find({'setup.py', 'pyproject.toml'}, { upward = true })[1]), - }) + vim.lsp.start({ + name = 'my-server-name', + cmd = {'name-of-language-server-executable'}, + root_dir = vim.fs.dirname(vim.fs.find({'setup.py', 'pyproject.toml'}, { upward = true })[1]), + }) < See |vim.lsp.start| for details. @@ -59,6 +59,9 @@ language server supports the functionality. - |tagfunc| is set to |vim.lsp.tagfunc|. This enables features like go-to-definition, |:tjump|, and keymaps like |CTRL-]|, |CTRL-W_]|, |CTRL-W_}| to utilize the language server. +- |formatexpr| is set to |vim.lsp.formatexpr| if both |formatprg| and + |formatexpr| are empty. This allows to format lines via |gq| if the language + server supports it. 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 @@ -98,7 +101,7 @@ To learn what capabilities are available you can run the following command in a buffer with a started LSP client: > - :lua =vim.lsp.get_active_clients()[1].server_capabilities + :lua =vim.lsp.get_active_clients()[1].server_capabilities < Full list of features provided by default can be found in |lsp-buf|. @@ -123,13 +126,14 @@ FAQ *lsp-faq* "after/ftplugin/python.vim". - Q: How do I run a request synchronously (e.g. for formatting on file save)? - A: Use the `_sync` variant of the function provided by |lsp-buf|, if it - exists. + A: Check if the function has an `async` parameter and set the value to + false. E.g. code formatting: > " Auto-format *.rs (rust) files prior to saving them - autocmd BufWritePre *.rs lua vim.lsp.buf.formatting_sync(nil, 1000) + " (async = false is the default for format) + autocmd BufWritePre *.rs lua vim.lsp.buf.format({ async = false }) < *lsp-vs-treesitter* @@ -160,7 +164,7 @@ 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: > - :lua print(vim.inspect(vim.tbl_keys(vim.lsp.handlers))) + :lua print(vim.inspect(vim.tbl_keys(vim.lsp.handlers))) < *lsp-method* @@ -201,84 +205,83 @@ For |lsp-request|, each |lsp-handler| has this signature: > function(err, result, ctx, config) < - Parameters: ~ - {err} (table|nil) - When the language server is unable to complete a - request, a table with information about the error - is sent. Otherwise, it is `nil`. See |lsp-response|. - {result} (Result | Params | nil) - When the language server is able to successfully - complete a request, this contains the `result` key - of the response. See |lsp-response|. - {ctx} (table) - Context describes additional calling state - associated with the handler. It consists of the - following key, value pairs: - - {method} (string) - The |lsp-method| name. - {client_id} (number) - The ID of the |vim.lsp.client|. - {bufnr} (Buffer) - Buffer handle, or 0 for current. - - {params} (table|nil) - The parameters used in the original request - which resulted in this handler - call. - {config} (table) - Configuration for the handler. - - Each handler can define its own configuration - table that allows users to customize the behavior - of a particular handler. - - To configure a particular |lsp-handler|, see: - |lsp-handler-configuration| - - - Returns: ~ - The |lsp-handler| can respond by returning two values: `result, err` - Where `err` must be shaped like an RPC error: - `{ code, message, data? }` - - You can use |vim.lsp.rpc_response_error()| to create this object. + Parameters: ~ + {err} (table|nil) + When the language server is unable to complete a + request, a table with information about the error is + sent. Otherwise, it is `nil`. See |lsp-response|. + {result} (Result | Params | nil) + When the language server is able to successfully + complete a request, this contains the `result` key of + the response. See |lsp-response|. + {ctx} (table) + Context describes additional calling state associated + with the handler. It consists of the following key, + value pairs: + + {method} (string) + The |lsp-method| name. + {client_id} (number) + The ID of the |vim.lsp.client|. + {bufnr} (Buffer) + Buffer handle, or 0 for current. + {params} (table|nil) + The parameters used in the original + request which resulted in this handler + call. + {config} (table) + Configuration for the handler. + + Each handler can define its own configuration table + that allows users to customize the behavior of a + particular handler. + + To configure a particular |lsp-handler|, see: + |lsp-handler-configuration| + + + Returns: ~ + The |lsp-handler| can respond by returning two values: `result, err` + Where `err` must be shaped like an RPC error: + `{ code, message, data? }` + + You can use |vim.lsp.rpc_response_error()| to create this object. For |lsp-notification|, each |lsp-handler| has this signature: > function(err, result, ctx, config) < - Parameters: ~ - {err} (nil) - This is always `nil`. - See |lsp-notification| - {result} (Result) - This contains the `params` key of the notification. - See |lsp-notification| - {ctx} (table) - Context describes additional calling state - associated with the handler. It consists of the - following key, value pairs: - - {method} (string) - The |lsp-method| name. - {client_id} (number) - The ID of the |vim.lsp.client|. - {config} (table) - Configuration for the handler. - - Each handler can define its own configuration - table that allows users to customize the behavior - of a particular handler. - - For an example, see: - |vim.lsp.diagnostic.on_publish_diagnostics()| - - To configure a particular |lsp-handler|, see: - |lsp-handler-configuration| - - Returns: ~ - The |lsp-handler|'s return value will be ignored. + Parameters: ~ + {err} (nil) + This is always `nil`. + See |lsp-notification| + {result} (Result) + This contains the `params` key of the notification. + See |lsp-notification| + {ctx} (table) + Context describes additional calling state associated + with the handler. It consists of the following key, + value pairs: + + {method} (string) + The |lsp-method| name. + {client_id} (number) + The ID of the |vim.lsp.client|. + {config} (table) + Configuration for the handler. + + Each handler can define its own configuration table + that allows users to customize the behavior of a + particular handler. + + For an example, see: + |vim.lsp.diagnostic.on_publish_diagnostics()| + + To configure a particular |lsp-handler|, see: + |lsp-handler-configuration| + + Returns: ~ + The |lsp-handler|'s return value will be ignored. *lsp-handler-configuration* @@ -289,49 +292,50 @@ To configure the behavior of a builtin |lsp-handler|, the convenient method 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.handlers["textDocument/publishDiagnostics"] = vim.lsp.with( - vim.lsp.diagnostic.on_publish_diagnostics, { - -- Disable signs - signs = false, - } - ) + vim.lsp.handlers["textDocument/publishDiagnostics"] = vim.lsp.with( + vim.lsp.diagnostic.on_publish_diagnostics, { + -- Disable signs + signs = false, + } + ) < To enable signs, use |vim.lsp.with()| again to create and assign a new |lsp-handler| to |vim.lsp.handlers| for the associated method: > - vim.lsp.handlers["textDocument/publishDiagnostics"] = vim.lsp.with( - vim.lsp.diagnostic.on_publish_diagnostics, { - -- Enable signs - signs = true, - } - ) + vim.lsp.handlers["textDocument/publishDiagnostics"] = vim.lsp.with( + vim.lsp.diagnostic.on_publish_diagnostics, { + -- Enable signs + signs = true, + } + ) < To configure a handler on a per-server basis, you can use the {handlers} key for |vim.lsp.start_client()| > - vim.lsp.start_client { - ..., -- Other configuration omitted. - handlers = { - ["textDocument/publishDiagnostics"] = vim.lsp.with( - vim.lsp.diagnostic.on_publish_diagnostics, { - -- Disable virtual_text - virtual_text = false, - } - }, - } + vim.lsp.start_client { + ..., -- Other configuration omitted. + handlers = { + ["textDocument/publishDiagnostics"] = vim.lsp.with( + vim.lsp.diagnostic.on_publish_diagnostics, { + -- Disable virtual_text + virtual_text = false, + } + ), + }, + } < or if using 'nvim-lspconfig', you can use the {handlers} key of `setup()`: > - require('lspconfig').rust_analyzer.setup { - handlers = { - ["textDocument/publishDiagnostics"] = vim.lsp.with( - vim.lsp.diagnostic.on_publish_diagnostics, { - -- Disable virtual_text - virtual_text = false - } - ), - } - } + require('lspconfig').rust_analyzer.setup { + handlers = { + ["textDocument/publishDiagnostics"] = vim.lsp.with( + vim.lsp.diagnostic.on_publish_diagnostics, { + -- Disable virtual_text + virtual_text = false + } + ), + } + } < Some handlers do not have an explicitly named handler function (such as |on_publish_diagnostics()|). To override these, first create a reference @@ -354,31 +358,31 @@ Handlers can be set by: To override the handler for the `"textDocument/definition"` method: > - vim.lsp.handlers["textDocument/definition"] = my_custom_default_definition + 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: > - vim.lsp.start_client { - ..., -- Other configuration omitted. - handlers = { + vim.lsp.start_client { + ..., -- Other configuration omitted. + handlers = { ["textDocument/definition"] = my_custom_server_definition - }, - } + }, + } - The {handler} parameter for |vim.lsp.buf_request()|. This will set the |lsp-handler| ONLY for the current request. For example: > - vim.lsp.buf_request( - 0, - "textDocument/definition", - definition_params, - my_request_custom_definition - ) + vim.lsp.buf_request( + 0, + "textDocument/definition", + definition_params, + my_request_custom_definition + ) < In summary, the |lsp-handler| will be chosen based on the current |lsp-method| in the following order: @@ -400,8 +404,8 @@ https://github.com/microsoft/language-server-protocol/raw/gh-pages/_specificatio For example `vim.lsp.protocol.ErrorCodes` allows reverse lookup by number or name: > - vim.lsp.protocol.TextDocumentSyncKind.Full == 1 - vim.lsp.protocol.TextDocumentSyncKind[1] == "Full" + vim.lsp.protocol.TextDocumentSyncKind.Full == 1 + vim.lsp.protocol.TextDocumentSyncKind[1] == "Full" < *lsp-response* @@ -412,7 +416,7 @@ For the format of the response message, see: For the format of the notification message, see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#notificationMessage - *on-list-handler* + *lsp-on-list-handler* `on_list` receives a table with: @@ -421,22 +425,22 @@ 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.: +> + local function on_list(options) + vim.fn.setqflist({}, ' ', options) + vim.api.nvim_command('cfirst') + end - local function on_list(options) - vim.fn.setqflist({}, ' ', options) - vim.api.nvim_command('cfirst') - end - - vim.lsp.buf.definition{on_list=on_list} - vim.lsp.buf.references(nil, {on_list=on_list}) - + vim.lsp.buf.definition{on_list=on_list} + vim.lsp.buf.references(nil, {on_list=on_list}) +< If you prefer loclist do something like this: - - local function on_list(options) - vim.fn.setloclist(0, {}, ' ', options) - vim.api.nvim_command('lopen') - end - +> + local function on_list(options) + vim.fn.setloclist(0, {}, ' ', options) + vim.api.nvim_command('lopen') + end +< ================================================================================ LSP HIGHLIGHT *lsp-highlight* @@ -461,11 +465,11 @@ Highlight groups related to |lsp-codelens| functionality. *hl-LspCodeLens* LspCodeLens - Used to color the virtual text of the codelens. See - |nvim_buf_set_extmark()|. + Used to color the virtual text of the codelens. See + |nvim_buf_set_extmark()|. LspCodeLensSeparator *hl-LspCodeLensSeparator* - Used to color the separator between two or more code lens. + Used to color the separator between two or more code lenses. *lsp-highlight-signature* @@ -473,8 +477,8 @@ Highlight groups related to |vim.lsp.handlers.signature_help()|. *hl-LspSignatureActiveParameter* LspSignatureActiveParameter - Used to highlight the active parameter in the signature help. See - |vim.lsp.handlers.signature_help()|. + Used to highlight the active parameter in the signature help. See + |vim.lsp.handlers.signature_help()|. ============================================================================== EVENTS *lsp-events* @@ -513,317 +517,268 @@ callback in the "data" table. Example: > In addition, the following |User| |autocommands| are provided: LspProgressUpdate *LspProgressUpdate* - Upon receipt of a progress notification from the server. See - |vim.lsp.util.get_progress_messages()|. + Upon receipt of a progress notification from the server. See + |vim.lsp.util.get_progress_messages()|. LspRequest *LspRequest* - After a change to the active set of pending LSP requests. See {requests} - in |vim.lsp.client|. + After a change to the active set of pending LSP requests. See {requests} + in |vim.lsp.client|. Example: > - autocmd User LspProgressUpdate redrawstatus - autocmd User LspRequest redrawstatus + autocmd User LspProgressUpdate redrawstatus + autocmd User LspRequest redrawstatus < ============================================================================== Lua module: vim.lsp *lsp-core* buf_attach_client({bufnr}, {client_id}) *vim.lsp.buf_attach_client()* - Implements the `textDocument/did…` notifications required to - track a buffer for any language server. + Implements the `textDocument/did…` notifications required to track a + buffer for any language server. - Without calling this, the server won't be notified of changes - to a buffer. + Without calling this, the server won't be notified of changes to a buffer. - Parameters: ~ - {bufnr} (number) Buffer handle, or 0 for current - {client_id} (number) Client id + Parameters: ~ + {bufnr} (number) Buffer handle, or 0 for current + {client_id} (number) Client id buf_detach_client({bufnr}, {client_id}) *vim.lsp.buf_detach_client()* - Detaches client from the specified buffer. Note: While the - server is notified that the text document (buffer) was closed, - it is still able to send notifications should it ignore this - notification. + Detaches client from the specified buffer. Note: While the server is + notified that the text document (buffer) was closed, it is still able to + send notifications should it ignore this notification. - Parameters: ~ - {bufnr} (number) Buffer handle, or 0 for current - {client_id} (number) Client id + Parameters: ~ + {bufnr} (number) Buffer handle, or 0 for current + {client_id} (number) Client id buf_is_attached({bufnr}, {client_id}) *vim.lsp.buf_is_attached()* - Checks if a buffer is attached for a particular client. + Checks if a buffer is attached for a particular client. - Parameters: ~ - {bufnr} (number) Buffer handle, or 0 for current - {client_id} (number) the client id + Parameters: ~ + {bufnr} (number) Buffer handle, or 0 for current + {client_id} (number) the client id 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 + Send a notification to a server - Return: ~ - true if any client returns true; false otherwise + Parameters: ~ + {bufnr} [number] (optional): The number of the buffer + {method} [string]: Name of the request method + {params} [string]: Arguments to send to the server - *vim.lsp.buf_request()* -buf_request({bufnr}, {method}, {params}, {handler}) - Sends an async request for all active clients attached to the - buffer. - - Parameters: ~ - {bufnr} (number) Buffer handle, or 0 for current. - {method} (string) LSP method name - {params} (optional, table) Parameters to send to the - server - {handler} (optional, function) See |lsp-handler| If nil, - follows resolution strategy defined in - |lsp-handler-configuration| - - Return: ~ - 2-tuple: - • Map of client-id:request-id pairs for all successful - requests. - • Function which can be used to cancel all the requests. - You could instead iterate all clients and call their - `cancel_request()` methods. + Return: ~ + true if any client returns true; false otherwise *vim.lsp.buf_request_all()* buf_request_all({bufnr}, {method}, {params}, {callback}) - Sends an async request for all active clients attached to the - buffer. Executes the callback on the combined result. - Parameters are the same as |vim.lsp.buf_request()| but the - return result and callback are different. - - Parameters: ~ - {bufnr} (number) Buffer handle, or 0 for current. - {method} (string) LSP method name - {params} (optional, table) Parameters to send to the - server - {callback} (function) The callback to call when all - requests are finished. - - Return: ~ - (function) A function that will cancel all requests which - is the same as the one returned from `buf_request`. + Sends an async request for all active clients attached to the buffer. + Executes the callback on the combined result. Parameters are the same as + |vim.lsp.buf_request()| but the return result and callback are different. + + Parameters: ~ + {bufnr} (number) Buffer handle, or 0 for current. + {method} (string) LSP method name + {params} (optional, table) Parameters to send to the server + {callback} (function) The callback to call when all requests are + finished. + + Return: ~ + (function) A function that will cancel all requests which is the same + as the one returned from `buf_request`. *vim.lsp.buf_request_sync()* buf_request_sync({bufnr}, {method}, {params}, {timeout_ms}) - Sends a request to all server and waits for the response of - all of them. - - Calls |vim.lsp.buf_request_all()| but blocks Nvim while - awaiting the result. Parameters are the same as - |vim.lsp.buf_request()| but the return result is different. - Wait maximum of {timeout_ms} (default 1000) 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. - - Return: ~ - Map of client_id:request_result. On timeout, cancel or - error, returns `(nil, err)` where `err` is a string - describing the failure reason. + Sends a request to all server and waits for the response of all of them. + + Calls |vim.lsp.buf_request_all()| but blocks Nvim while awaiting the + result. Parameters are the same as |vim.lsp.buf_request()| but the return + result is different. Wait maximum of {timeout_ms} (default 1000) 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. + + Return: ~ + Map of client_id:request_result. On timeout, cancel or error, returns + `(nil, err)` where `err` is a string describing the failure reason. client() *vim.lsp.client* - LSP client object. You can get an active client object via - |vim.lsp.get_client_by_id()| or - |vim.lsp.get_active_clients()|. - - • Methods: - • request(method, params, [handler], bufnr) Sends a request - to the server. This is a thin wrapper around - {client.rpc.request} with some additional checking. If - {handler} is not specified, If one is not found there, - then an error will occur. Returns: {status}, - {[client_id]}. {status} is a boolean indicating if the - notification was successful. If it is `false`, then it - will always be `false` (the client has shutdown). If - {status} is `true`, the function returns {request_id} as - the second result. You can use this with - `client.cancel_request(request_id)` to cancel the request. - • request_sync(method, params, timeout_ms, bufnr) Sends a - request to the server and synchronously waits for the - response. This is a wrapper around {client.request} - 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 string describing the failure reason. If the request - was unsuccessful returns `nil`. - • notify(method, params) Sends a notification to an LSP - server. Returns: a boolean to indicate if the notification - was successful. If it is false, then it will always be - false (the client has shutdown). - • cancel_request(id) Cancels a request with a given request - id. Returns: same as `notify()`. - • stop([force]) Stops a client, optionally with force. By - default, it will just ask the server to shutdown without - force. If you request to stop a client which has - previously been requested to shutdown, it will - automatically escalate and force shutdown. - • is_stopped() Checks whether a client is stopped. Returns: - true if the client is fully stopped. - • on_attach(client, bufnr) Runs the on_attach function from - the client's config if it was defined. Useful for - buffer-local setup. - - • Members - • {id} (number): The id allocated to the client. - • {name} (string): If a name is specified on creation, that - will be used. Otherwise it is just the client id. This is - used for logs and messages. - • {rpc} (table): RPC client object, for low level - interaction with the client. See |vim.lsp.rpc.start()|. - • {offset_encoding} (string): The encoding used for - communicating with the server. You can modify this in the - `config`'s `on_init` method before text is sent to the - server. - • {handlers} (table): The handlers used by the client as - described in |lsp-handler|. - • {requests} (table): The current pending requests in flight - to the server. Entries are key-value pairs with the key - being the request ID while the value is a table with - `type`, `bufnr`, and `method` key-value pairs. `type` is - either "pending" for an active request, or "cancel" for a - cancel request. - • {config} (table): copy of the table that was passed by the - user to |vim.lsp.start_client()|. - • {server_capabilities} (table): Response from the server - sent on `initialize` describing the server's capabilities. + LSP client object. You can get an active client object via + |vim.lsp.get_client_by_id()| or |vim.lsp.get_active_clients()|. + + • Methods: + • request(method, params, [handler], bufnr) Sends a request to the + server. This is a thin wrapper around {client.rpc.request} with some + additional checking. If {handler} is not specified, If one is not + found there, then an error will occur. Returns: {status}, + {[client_id]}. {status} is a boolean indicating if the notification + was successful. If it is `false`, then it will always be `false` (the + client has shutdown). If {status} is `true`, the function returns + {request_id} as the second result. You can use this with + `client.cancel_request(request_id)` to cancel the request. + • request_sync(method, params, timeout_ms, bufnr) Sends a request to the + server and synchronously waits for the response. This is a wrapper + around {client.request} 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 string + describing the failure reason. If the request was unsuccessful returns + `nil`. + • notify(method, params) Sends a notification to an LSP server. Returns: + a boolean to indicate if the notification was successful. If it is + false, then it will always be false (the client has shutdown). + • cancel_request(id) Cancels a request with a given request id. Returns: + same as `notify()`. + • stop([force]) Stops a client, optionally with force. By default, it + will just ask the server to shutdown without force. If you request to + stop a client which has previously been requested to shutdown, it will + automatically escalate and force shutdown. + • is_stopped() Checks whether a client is stopped. Returns: true if the + client is fully stopped. + • on_attach(client, bufnr) Runs the on_attach function from the client's + config if it was defined. Useful for buffer-local setup. + + • Members + • {id} (number): The id allocated to the client. + • {name} (string): If a name is specified on creation, that will be + used. Otherwise it is just the client id. This is used for logs and + messages. + • {rpc} (table): RPC client object, for low level interaction with the + client. See |vim.lsp.rpc.start()|. + • {offset_encoding} (string): The encoding used for communicating with + the server. You can modify this in the `config`'s `on_init` method + before text is sent to the server. + • {handlers} (table): The handlers used by the client as described in + |lsp-handler|. + • {requests} (table): The current pending requests in flight to the + server. Entries are key-value pairs with the key being the request ID + while the value is a table with `type`, `bufnr`, and `method` + key-value pairs. `type` is either "pending" for an active request, or + "cancel" for a cancel request. + • {config} (table): copy of the table that was passed by the user to + |vim.lsp.start_client()|. + • {server_capabilities} (table): Response from the server sent on + `initialize` describing the server's capabilities. client_is_stopped({client_id}) *vim.lsp.client_is_stopped()* - Checks whether a client is stopped. + Checks whether a client is stopped. - Parameters: ~ - {client_id} (Number) + Parameters: ~ + {client_id} (Number) - Return: ~ - true if client is stopped, false otherwise. + Return: ~ + true if client is stopped, false otherwise. *vim.lsp.for_each_buffer_client()* for_each_buffer_client({bufnr}, {fn}) - Invokes a function for each LSP client attached to a buffer. - - Parameters: ~ - {bufnr} (number) Buffer number - {fn} (function) Function to run on each client - attached to buffer {bufnr}. The function takes - the client, client ID, and buffer number as - arguments. Example: > - - vim.lsp.for_each_buffer_client(0, function(client, client_id, bufnr) - print(vim.inspect(client)) - end) + Invokes a function for each LSP client attached to a buffer. + + Parameters: ~ + {bufnr} (number) Buffer number + {fn} (function) Function to run on each client attached to buffer + {bufnr}. The function takes the client, client ID, and buffer + number as arguments. Example: > + + vim.lsp.for_each_buffer_client(0, function(client, client_id, bufnr) + print(vim.inspect(client)) + end) < formatexpr({opts}) *vim.lsp.formatexpr()* - Provides an interface between the built-in client and a - `formatexpr` function. - - Currently only supports a single client. This can be set via - `setlocal formatexpr=v:lua.vim.lsp.formatexpr()` but will - typically or in `on_attach` via - `vim.api.nvim_buf_set_option(bufnr, 'formatexpr', - 'v:lua.vim.lsp.formatexpr(#{timeout_ms:250})')`. - - Parameters: ~ - {opts} (table) options for customizing the formatting - expression which takes the following optional - keys: - • timeout_ms (default 500ms). The timeout period - for the formatting request. + Provides an interface between the built-in client and a `formatexpr` + function. + + Currently only supports a single client. This can be set via `setlocal + formatexpr=v:lua.vim.lsp.formatexpr()` but will typically or in + `on_attach` via `vim.api.nvim_buf_set_option(bufnr, 'formatexpr', + 'v:lua.vim.lsp.formatexpr(#{timeout_ms:250})')`. + + Parameters: ~ + {opts} (table) options for customizing the formatting expression + which takes the following optional keys: + • timeout_ms (default 500ms). The timeout period for the + formatting request. get_active_clients({filter}) *vim.lsp.get_active_clients()* - Get active clients. - - Parameters: ~ - {filter} (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 - - Return: ~ - (table) List of |vim.lsp.client| objects + Get active clients. + + Parameters: ~ + {filter} (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 + + Return: ~ + (table) List of |vim.lsp.client| objects *vim.lsp.get_buffers_by_client_id()* get_buffers_by_client_id({client_id}) - Returns list of buffers attached to client_id. + Returns list of buffers attached to client_id. - Parameters: ~ - {client_id} (number) client id + Parameters: ~ + {client_id} (number) client id - Return: ~ - list of buffer ids + Return: ~ + list of buffer ids get_client_by_id({client_id}) *vim.lsp.get_client_by_id()* - Gets a client by id, or nil if the id is invalid. The returned - client may not yet be fully initialized. + Gets a client by id, or nil if the id is invalid. The returned client may + not yet be fully initialized. - Parameters: ~ - {client_id} (number) client id + Parameters: ~ + {client_id} (number) client id - Return: ~ - |vim.lsp.client| object, or nil + Return: ~ + |vim.lsp.client| object, or nil get_log_path() *vim.lsp.get_log_path()* - Gets the path of the logfile used by the LSP client. + Gets the path of the logfile used by the LSP client. - Return: ~ - (String) Path to logfile. + Return: ~ + (String) Path to logfile. omnifunc({findstart}, {base}) *vim.lsp.omnifunc()* - Implements 'omnifunc' compatible LSP completion. + Implements 'omnifunc' compatible LSP completion. - Parameters: ~ - {findstart} 0 or 1, decides behavior - {base} If findstart=0, text to match against + Parameters: ~ + {findstart} 0 or 1, decides behavior + {base} If findstart=0, text to match against - Return: ~ - (number) Decided by {findstart}: - • findstart=0: column where the completion starts, or -2 - or -3 - • findstart=1: list of matches (actually just calls - |complete()|) + Return: ~ + (number) Decided by {findstart}: + • findstart=0: column where the completion starts, or -2 or -3 + • findstart=1: list of matches (actually just calls |complete()|) - See also: ~ - |complete-functions| - |complete-items| - |CompleteDone| + See also: ~ + |complete-functions| + |complete-items| + |CompleteDone| set_log_level({level}) *vim.lsp.set_log_level()* - Sets the global log level for LSP logging. + Sets the global log level for LSP logging. - Levels by name: "TRACE", "DEBUG", "INFO", "WARN", "ERROR", - "OFF" + Levels by name: "TRACE", "DEBUG", "INFO", "WARN", "ERROR", "OFF" - Level numbers begin with "TRACE" at 0 + Level numbers begin with "TRACE" at 0 - Use `lsp.log_levels` for reverse lookup. + Use `lsp.log_levels` for reverse lookup. - Parameters: ~ - {level} [number|string] the case insensitive level name - or number + Parameters: ~ + {level} [number|string] the case insensitive level name or number - See also: ~ - |vim.lsp.log_levels| + See also: ~ + |vim.lsp.log_levels| start({config}, {opts}) *vim.lsp.start()* - Create a new LSP client and start a language server or reuses - an already running client if one is found matching `name` and - `root_dir`. Attaches the current buffer to the client. + Create a new LSP client and start a language server or reuses an already + running client if one is found matching `name` and `root_dir`. Attaches + the current buffer to the client. - Example: + Example: > vim.lsp.start({ @@ -833,248 +788,215 @@ start({config}, {opts}) *vim.lsp.start()* }) < - See |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. - - Language servers use this information to discover metadata - like the dependencies of your project and they tend to index - the contents within the project folder. - - To ensure a language server is only started for languages it - can handle, make sure to call |vim.lsp.start| within a - |FileType| autocmd. Either use |:au|, |nvim_create_autocmd()| - or put the call in a `ftplugin/<filetype_name>.lua` (See - |ftplugin-name|) - - Parameters: ~ - {config} (table) Same configuration as documented in - |lsp.start_client()| - {opts} nil|table Optional keyword arguments: - • reuse_client (fun(client: client, config: - table): boolean) Predicate used to decide if a - client should be re-used. Used on all running - clients. The default implementation re-uses a - client if name and root_dir matches. - - Return: ~ - (number) client_id + See |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. + + Language servers use this information to discover metadata like the + dependencies of your project and they tend to index the contents within + the project folder. + + To ensure a language server is only started for languages it can handle, + make sure to call |vim.lsp.start| within a |FileType| autocmd. Either use + |:au|, |nvim_create_autocmd()| or put the call in a + `ftplugin/<filetype_name>.lua` (See |ftplugin-name|) + + Parameters: ~ + {config} (table) Same configuration as documented in + |lsp.start_client()| + {opts} nil|table Optional keyword arguments: + • reuse_client (fun(client: client, config: table): boolean) + Predicate used to decide if a client should be re-used. + Used on all running clients. The default implementation + re-uses a client if name and root_dir matches. + + Return: ~ + (number|nil) client_id 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} (required, string or list treated - like |jobstart()|) Base command that - initiates the LSP client. - {cmd_cwd} (string, default=|getcwd()|) - Directory to launch the `cmd` - process. Not related to `root_dir`. - {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"; } + 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"; } < - {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, default 500): - Milliseconds to wait for server to - exit cleanly after sending the - 'shutdown' request before sending - kill -15. If set to false, nvim - exits immediately after sending the - 'shutdown' request to the server. - {root_dir} (string) Directory where the LSP - server will base its - workspaceFolders, rootUri, and - rootPath on initialization. - - Return: ~ - Client id. |vim.lsp.get_client_by_id()| Note: client may - not be fully initialized. Use `on_init` to do any actions - once the client has been initialized. + {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 + initialized. Use `on_init` to do any actions once the client has been + initialized. stop_client({client_id}, {force}) *vim.lsp.stop_client()* - Stops a client(s). + 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: > 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. + 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 + Parameters: ~ + {client_id} client id or |vim.lsp.client| object, or list thereof + {force} (boolean) (optional) shutdown forcefully tagfunc({...}) *vim.lsp.tagfunc()* - Provides an interface between the built-in client and - 'tagfunc'. + Provides an interface between the built-in client and 'tagfunc'. - When used with normal mode commands (e.g. |CTRL-]|) this will - invoke the "textDocument/definition" LSP method to find the - tag under the cursor. Otherwise, uses "workspace/symbol". If - no results are returned from any LSP servers, falls back to - using built-in tags. + When used with normal mode commands (e.g. |CTRL-]|) this will invoke the + "textDocument/definition" LSP method to find the tag under the cursor. + Otherwise, uses "workspace/symbol". If no results are returned from any + LSP servers, falls back to using built-in tags. - Parameters: ~ - {pattern} Pattern used to find a workspace symbol - {flags} See |tag-function| + Parameters: ~ + {pattern} Pattern used to find a workspace symbol + {flags} See |tag-function| - Return: ~ - A list of matching tags + Return: ~ + A list of matching tags with({handler}, {override_config}) *vim.lsp.with()* - Function to manage overriding defaults for LSP handlers. + Function to manage overriding defaults for LSP handlers. - Parameters: ~ - {handler} (function) See |lsp-handler| - {override_config} (table) Table containing the keys to - override behavior of the {handler} + Parameters: ~ + {handler} (function) See |lsp-handler| + {override_config} (table) Table containing the keys to override + behavior of the {handler} ============================================================================== @@ -1082,471 +1004,432 @@ Lua module: vim.lsp.buf *lsp-buf* *vim.lsp.buf.add_workspace_folder()* add_workspace_folder({workspace_folder}) - Add the folder at path to the workspace folders. If {path} is - not provided, the user will be prompted for a path using - |input()|. + Add the folder at path to the workspace folders. If {path} is not + provided, the user will be prompted for a path using |input()|. clear_references() *vim.lsp.buf.clear_references()* - Removes document highlights from current buffer. + Removes document highlights from current buffer. code_action({options}) *vim.lsp.buf.code_action()* - Selects a code action available at the current cursor - position. - - Parameters: ~ - {options} (table|nil) Optional table which holds the - following optional fields: - • context: (table|nil) Corresponds to `CodeActionContext` of the LSP specification: - • diagnostics (table|nil): LSP`Diagnostic[]` . Inferred from the current position if not - provided. - • only (table|nil): List of LSP - `CodeActionKind`s used to filter the code - actions. Most language servers support - values like `refactor` or `quickfix`. - - • filter: (function|nil) Predicate taking an - `CodeAction` and returning a boolean. - • apply: (boolean|nil) When set to `true`, and - there is just one remaining action (after - filtering), the action is applied without - user query. - • range: (table|nil) Range for which code - actions should be requested. If in visual - mode this defaults to the active selection. - Table must contain `start` and `end` keys - with {row, col} tuples using mark-like - indexing. See |api-indexing| - - See also: ~ - https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_codeAction + Selects a code action available at the current cursor position. + + Parameters: ~ + {options} (table|nil) Optional table which holds the following + optional fields: + • context: (table|nil) Corresponds to `CodeActionContext` of the LSP specification: + • diagnostics (table|nil): LSP`Diagnostic[]` . Inferred from the current position if not provided. + • only (table|nil): List of LSP `CodeActionKind`s used to + filter the code actions. Most language servers support + values like `refactor` or `quickfix`. + + • filter: (function|nil) Predicate taking an `CodeAction` + and returning a boolean. + • apply: (boolean|nil) When set to `true`, and there is + just one remaining action (after filtering), the action + is applied without user query. + • range: (table|nil) Range for which code actions should be + requested. If in visual mode this defaults to the active + selection. Table must contain `start` and `end` keys with + {row, col} tuples using mark-like indexing. See + |api-indexing| + + See also: ~ + https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_codeAction completion({context}) *vim.lsp.buf.completion()* - Retrieves the completion items at the current cursor position. - Can only be called in Insert mode. + Retrieves the completion items at the current cursor position. Can only be + called in Insert mode. - Parameters: ~ - {context} (context support not yet implemented) - Additional information about the context in - which a completion was triggered (how it was - triggered, and by which trigger character, if - applicable) + Parameters: ~ + {context} (context support not yet implemented) Additional + information about the context in which a completion was + triggered (how it was triggered, and by which trigger + character, if applicable) - See also: ~ - |vim.lsp.protocol.constants.CompletionTriggerKind| + See also: ~ + |vim.lsp.protocol.constants.CompletionTriggerKind| declaration({options}) *vim.lsp.buf.declaration()* - Jumps to the declaration of the symbol under the cursor. - Note: - Many servers do not implement this method. Generally, see - |vim.lsp.buf.definition()| instead. - - Parameters: ~ - {options} (table|nil) additional options - • reuse_win: (boolean) Jump to existing window - if buffer is already open. - • on_list: (function) handler for list results. - See |on-list-handler| + Jumps to the declaration of the symbol under the cursor. + Note: + Many servers do not implement this method. Generally, see + |vim.lsp.buf.definition()| instead. + + Parameters: ~ + {options} (table|nil) additional options + • reuse_win: (boolean) Jump to existing window if buffer is + already open. + • on_list: (function) handler for list results. See + |lsp-on-list-handler| definition({options}) *vim.lsp.buf.definition()* - Jumps to the definition of the symbol under the cursor. + Jumps to the definition of the symbol under the cursor. - Parameters: ~ - {options} (table|nil) additional options - • reuse_win: (boolean) Jump to existing window - if buffer is already open. - • on_list: (function) handler for list results. - See |on-list-handler| + Parameters: ~ + {options} (table|nil) additional options + • reuse_win: (boolean) Jump to existing window if buffer is + already open. + • on_list: (function) handler for list results. See + |lsp-on-list-handler| 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.: + 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() < - Note: Usage of |vim.lsp.buf.document_highlight()| requires the - following highlight groups to be defined or you won't be able - to see the actual highlights. |LspReferenceText| - |LspReferenceRead| |LspReferenceWrite| + Note: Usage of |vim.lsp.buf.document_highlight()| requires the following + highlight groups to be defined or you won't be able to see the actual + highlights. |LspReferenceText| |LspReferenceRead| |LspReferenceWrite| document_symbol({options}) *vim.lsp.buf.document_symbol()* - Lists all symbols in the current buffer in the quickfix - window. + Lists all symbols in the current buffer in the quickfix window. - Parameters: ~ - {options} (table|nil) additional options - • on_list: (function) handler for list results. - See |on-list-handler| + Parameters: ~ + {options} (table|nil) additional options + • on_list: (function) handler for list results. See + |lsp-on-list-handler| execute_command({command_params}) *vim.lsp.buf.execute_command()* - Executes an LSP server command. + Executes an LSP server command. - Parameters: ~ - {command_params} (table) A valid `ExecuteCommandParams` - object + Parameters: ~ + {command_params} (table) A valid `ExecuteCommandParams` object - See also: ~ - https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_executeCommand + See also: ~ + https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_executeCommand format({options}) *vim.lsp.buf.format()* - Formats a buffer using the attached (and optionally filtered) - language server clients. - - Parameters: ~ - {options} table|nil Optional table which holds the - following optional fields: - • formatting_options (table|nil): Can be used - to specify FormattingOptions. Some - unspecified options will be automatically - derived from the current Neovim options. See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#formattingOptions - • timeout_ms (integer|nil, default 1000): Time - in milliseconds to block for formatting - requests. No effect if async=true - • bufnr (number|nil): Restrict formatting to - the clients attached to the given buffer, - defaults to the current 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: • > - - -- Never request typescript-language-server for formatting - vim.lsp.buf.format { - filter = function(client) return client.name ~= "tsserver" end - } + Formats a buffer using the attached (and optionally filtered) language + server clients. + + Parameters: ~ + {options} table|nil Optional table which holds the following optional + fields: + • formatting_options (table|nil): Can be used to specify + FormattingOptions. Some unspecified options will be + automatically derived from the current Neovim options. + See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#formattingOptions + • timeout_ms (integer|nil, default 1000): Time in + milliseconds to block for formatting requests. No effect + if async=true + • bufnr (number|nil): Restrict formatting to the clients + attached to the given buffer, defaults to the current + 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: • > + + -- 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 asynchronous can lead to - unexpected changes. - • id (number|nil): Restrict formatting to the - client with ID (client.id) matching this - field. - • name (string|nil): Restrict formatting to the - client with name (client.name) matching this - field. + • async boolean|nil If true the method won't block. + Defaults to false. Editing the buffer while formatting + asynchronous can lead to unexpected changes. + • id (number|nil): Restrict formatting to the client with + ID (client.id) matching this field. + • name (string|nil): Restrict formatting to the client with + name (client.name) matching this field. formatting({options}) *vim.lsp.buf.formatting()* - Formats the current buffer. + Formats the current buffer. - Parameters: ~ - {options} (table|nil) Can be used to specify - FormattingOptions. Some unspecified options - will be automatically derived from the current - Neovim options. + Parameters: ~ + {options} (table|nil) Can be used to specify FormattingOptions. Some + unspecified options will be automatically derived from the + current Neovim options. - See also: ~ - https://microsoft.github.io/language-server-protocol/specification#textDocument_formatting + See also: ~ + https://microsoft.github.io/language-server-protocol/specification#textDocument_formatting *vim.lsp.buf.formatting_seq_sync()* formatting_seq_sync({options}, {timeout_ms}, {order}) - Formats the current buffer by sequentially requesting - formatting from attached clients. + Formats the current buffer by sequentially requesting formatting from + attached clients. - Useful when multiple clients with formatting capability are - attached. + Useful when multiple clients with formatting capability are attached. - Since it's synchronous, can be used for running on save, to - make sure buffer is formatted prior to being saved. - {timeout_ms} is passed on to the |vim.lsp.client| `request_sync` method. Example: > + Since it's synchronous, can be used for running on save, to make sure + buffer is formatted prior to being saved. {timeout_ms} is passed on to the + |vim.lsp.client| `request_sync` method. Example: > - vim.api.nvim_command[[autocmd BufWritePre <buffer> lua vim.lsp.buf.formatting_seq_sync()]] + vim.api.nvim_command[[autocmd BufWritePre <buffer> lua vim.lsp.buf.formatting_seq_sync()]] < - Parameters: ~ - {options} (table|nil) `FormattingOptions` entries - {timeout_ms} (number|nil) Request timeout - {order} (table|nil) List of client names. Formatting - is requested from clients in the following - order: first all clients that are not in the - `order` list, then the remaining clients in - the order as they occur in the `order` list. + Parameters: ~ + {options} (table|nil) `FormattingOptions` entries + {timeout_ms} (number|nil) Request timeout + {order} (table|nil) List of client names. Formatting is + requested from clients in the following order: first all + clients that are not in the `order` list, then the + remaining clients in the order as they occur in the + `order` list. *vim.lsp.buf.formatting_sync()* formatting_sync({options}, {timeout_ms}) - Performs |vim.lsp.buf.formatting()| synchronously. + Performs |vim.lsp.buf.formatting()| synchronously. - Useful for running on save, to make sure buffer is formatted - prior to being saved. {timeout_ms} is passed on to - |vim.lsp.buf_request_sync()|. Example: + Useful for running on save, to make sure buffer is formatted prior to + being saved. {timeout_ms} is passed on to |vim.lsp.buf_request_sync()|. + Example: > autocmd BufWritePre <buffer> lua vim.lsp.buf.formatting_sync() < - Parameters: ~ - {options} (table|nil) with valid `FormattingOptions` - entries - {timeout_ms} (number) Request timeout + Parameters: ~ + {options} (table|nil) with valid `FormattingOptions` entries + {timeout_ms} (number) Request timeout - See also: ~ - |vim.lsp.buf.formatting_seq_sync| + See also: ~ + |vim.lsp.buf.formatting_seq_sync| hover() *vim.lsp.buf.hover()* - Displays hover information about the symbol under the cursor - in a floating window. Calling the function twice will jump - into the floating window. + Displays hover information about the symbol under the cursor in a floating + window. Calling the function twice will jump into the floating window. implementation({options}) *vim.lsp.buf.implementation()* - Lists all the implementations for the symbol under the cursor - in the quickfix window. + Lists all the implementations for the symbol under the cursor in the + quickfix window. - Parameters: ~ - {options} (table|nil) additional options - • on_list: (function) handler for list results. - See |on-list-handler| + Parameters: ~ + {options} (table|nil) additional options + • on_list: (function) handler for list results. See + |lsp-on-list-handler| incoming_calls() *vim.lsp.buf.incoming_calls()* - Lists all the call sites of the symbol under the cursor in the - |quickfix| window. If the symbol can resolve to multiple - items, the user can pick one in the |inputlist|. + Lists all the call sites of the symbol under the cursor in the |quickfix| + window. If the symbol can resolve to multiple items, the user can pick one + in the |inputlist|. list_workspace_folders() *vim.lsp.buf.list_workspace_folders()* - List workspace folders. + List workspace folders. outgoing_calls() *vim.lsp.buf.outgoing_calls()* - Lists all the items that are called by the symbol under the - cursor in the |quickfix| window. If the symbol can resolve to - multiple items, the user can pick one in the |inputlist|. + Lists all the items that are called by the symbol under the cursor in the + |quickfix| window. If the symbol can resolve to multiple items, the user + can pick one in the |inputlist|. *vim.lsp.buf.range_code_action()* range_code_action({context}, {start_pos}, {end_pos}) - Performs |vim.lsp.buf.code_action()| for a given range. - - Parameters: ~ - {context} (table|nil) `CodeActionContext` of the LSP specification: - • diagnostics: (table|nil) LSP`Diagnostic[]` . Inferred from the current position if not - provided. - • only: (table|nil) List of LSP - `CodeActionKind`s used to filter the code - actions. Most language servers support - values like `refactor` or `quickfix`. - {start_pos} ({number, number}, optional) mark-indexed - position. Defaults to the start of the last - visual selection. - {end_pos} ({number, number}, optional) mark-indexed - position. Defaults to the end of the last - visual selection. + Performs |vim.lsp.buf.code_action()| for a given range. + + Parameters: ~ + {context} (table|nil) `CodeActionContext` of the LSP specification: + • diagnostics: (table|nil) LSP`Diagnostic[]` . Inferred from the current position if not provided. + • only: (table|nil) List of LSP `CodeActionKind`s used to + filter the code actions. Most language servers support + values like `refactor` or `quickfix`. + {start_pos} ({number, number}, optional) mark-indexed position. + Defaults to the start of the last visual selection. + {end_pos} ({number, number}, optional) mark-indexed position. + Defaults to the end of the last visual selection. *vim.lsp.buf.range_formatting()* range_formatting({options}, {start_pos}, {end_pos}) - Formats a given range. + Formats a given range. - Parameters: ~ - {options} Table with valid `FormattingOptions` entries. - {start_pos} ({number, number}, optional) mark-indexed - position. Defaults to the start of the last - visual selection. - {end_pos} ({number, number}, optional) mark-indexed - position. Defaults to the end of the last - visual selection. + Parameters: ~ + {options} Table with valid `FormattingOptions` entries. + {start_pos} ({number, number}, optional) mark-indexed position. + Defaults to the start of the last visual selection. + {end_pos} ({number, number}, optional) mark-indexed position. + Defaults to the end of the last visual selection. references({context}, {options}) *vim.lsp.buf.references()* - Lists all the references to the symbol under the cursor in the - quickfix window. + Lists all the references to the symbol under the cursor in the quickfix + window. - Parameters: ~ - {context} (table) Context for the request - {options} (table|nil) additional options - • on_list: (function) handler for list results. - See |on-list-handler| + Parameters: ~ + {context} (table) Context for the request + {options} (table|nil) additional options + • on_list: (function) handler for list results. See + |lsp-on-list-handler| - See also: ~ - https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_references + See also: ~ + https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_references *vim.lsp.buf.remove_workspace_folder()* remove_workspace_folder({workspace_folder}) - Remove the folder at path from the workspace folders. If - {path} is not provided, the user will be prompted for a path - using |input()|. + Remove the folder at path from the workspace folders. If {path} is not + provided, the user will be prompted for a path using |input()|. rename({new_name}, {options}) *vim.lsp.buf.rename()* - Renames all references to the symbol under the cursor. - - Parameters: ~ - {new_name} (string|nil) If not provided, the user will be - prompted for a new name using - |vim.ui.input()|. - {options} (table|nil) additional options - • filter (function|nil): Predicate used to - filter clients. Receives a client as - argument and must return a boolean. Clients - matching the predicate are included. - • name (string|nil): Restrict clients used for - rename to ones where client.name matches - this field. + Renames all references to the symbol under the cursor. + + Parameters: ~ + {new_name} (string|nil) If not provided, the user will be prompted + for a new name using |vim.ui.input()|. + {options} (table|nil) additional options + • filter (function|nil): Predicate used to filter clients. + Receives a client as argument and must return a boolean. + Clients matching the predicate are included. + • name (string|nil): Restrict clients used for rename to + ones where client.name matches this field. server_ready() *vim.lsp.buf.server_ready()* - Checks whether the language servers attached to the current - buffer are ready. + Checks whether the language servers attached to the current buffer are + ready. - Return: ~ - `true` if server responds. + Return: ~ + `true` if server responds. signature_help() *vim.lsp.buf.signature_help()* - Displays signature information about the symbol under the - cursor in a floating window. + Displays signature information about the symbol under the cursor in a + floating window. type_definition({options}) *vim.lsp.buf.type_definition()* - Jumps to the definition of the type of the symbol under the - cursor. + Jumps to the definition of the type of the symbol under the cursor. - Parameters: ~ - {options} (table|nil) additional options - • reuse_win: (boolean) Jump to existing window - if buffer is already open. - • on_list: (function) handler for list results. - See |on-list-handler| + Parameters: ~ + {options} (table|nil) additional options + • reuse_win: (boolean) Jump to existing window if buffer is + already open. + • on_list: (function) handler for list results. See + |lsp-on-list-handler| workspace_symbol({query}, {options}) *vim.lsp.buf.workspace_symbol()* - Lists all symbols in the current workspace in the quickfix - window. + Lists all symbols in the current workspace in the quickfix window. - The list is filtered against {query}; if the argument is - omitted from the call, the user is prompted to enter a string - on the command line. An empty string means no filtering is - done. + The list is filtered against {query}; if the argument is omitted from the + call, the user is prompted to enter a string on the command line. An empty + string means no filtering is done. - Parameters: ~ - {query} (string, optional) - {options} (table|nil) additional options - • on_list: (function) handler for list results. - See |on-list-handler| + Parameters: ~ + {query} (string, optional) + {options} (table|nil) additional options + • on_list: (function) handler for list results. See + |lsp-on-list-handler| ============================================================================== Lua module: vim.lsp.diagnostic *lsp-diagnostic* get_namespace({client_id}) *vim.lsp.diagnostic.get_namespace()* - Get the diagnostic namespace associated with an LSP client - |vim.diagnostic|. + Get the diagnostic namespace associated with an LSP client + |vim.diagnostic|. - Parameters: ~ - {client_id} (number) The id of the LSP client + Parameters: ~ + {client_id} (number) The id of the LSP client *vim.lsp.diagnostic.on_publish_diagnostics()* 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()|: > - - vim.lsp.handlers["textDocument/publishDiagnostics"] = vim.lsp.with( - vim.lsp.diagnostic.on_publish_diagnostics, { - -- Enable underline, use default values - underline = true, - -- Enable virtual text, override spacing to 4 - virtual_text = { - spacing = 4, - }, - -- Use a function to dynamically turn signs off - -- and on, using buffer local variables - signs = function(namespace, bufnr) - return vim.b[bufnr].show_signs == true - end, - -- Disable a feature - update_in_insert = false, - } - ) + |lsp-handler| for the method "textDocument/publishDiagnostics" + + See |vim.diagnostic.config()| for configuration options. Handler-specific + configuration can be set using |vim.lsp.with()|: > + + vim.lsp.handlers["textDocument/publishDiagnostics"] = vim.lsp.with( + vim.lsp.diagnostic.on_publish_diagnostics, { + -- Enable underline, use default values + underline = true, + -- Enable virtual text, override spacing to 4 + virtual_text = { + spacing = 4, + }, + -- Use a function to dynamically turn signs off + -- and on, using buffer local variables + signs = function(namespace, bufnr) + return vim.b[bufnr].show_signs == true + end, + -- Disable a feature + update_in_insert = false, + } + ) < - Parameters: ~ - {config} (table) Configuration table (see - |vim.diagnostic.config()|). + Parameters: ~ + {config} (table) Configuration table (see |vim.diagnostic.config()|). ============================================================================== Lua module: vim.lsp.codelens *lsp-codelens* display({lenses}, {bufnr}, {client_id}) *vim.lsp.codelens.display()* - Display the lenses using virtual text + Display the lenses using virtual text - Parameters: ~ - {lenses} (table) of lenses to display (`CodeLens[] | - null`) - {bufnr} (number) - {client_id} (number) + Parameters: ~ + {lenses} (table) of lenses to display (`CodeLens[] | null`) + {bufnr} (number) + {client_id} (number) get({bufnr}) *vim.lsp.codelens.get()* - Return all lenses for the given buffer + Return all lenses for the given buffer - Parameters: ~ - {bufnr} (number) Buffer number. 0 can be used for the - current buffer. + Parameters: ~ + {bufnr} (number) Buffer number. 0 can be used for the current buffer. - Return: ~ - (table) (`CodeLens[]`) + Return: ~ + (table) (`CodeLens[]`) *vim.lsp.codelens.on_codelens()* on_codelens({err}, {result}, {ctx}, {_}) - |lsp-handler| for the method `textDocument/codeLens` + |lsp-handler| for the method `textDocument/codeLens` refresh() *vim.lsp.codelens.refresh()* - Refresh the codelens for the current buffer + Refresh the codelens for the current buffer - It is recommended to trigger this using an autocmd or via - keymap. + It is recommended to trigger this using an autocmd or via keymap. > autocmd BufEnter,CursorHold,InsertLeave <buffer> lua vim.lsp.codelens.refresh() < run() *vim.lsp.codelens.run()* - Run the code lens in the current line + Run the code lens in the current line save({lenses}, {bufnr}, {client_id}) *vim.lsp.codelens.save()* - Store lenses for a specific buffer and client + Store lenses for a specific buffer and client - Parameters: ~ - {lenses} (table) of lenses to store (`CodeLens[] | - null`) - {bufnr} (number) - {client_id} (number) + Parameters: ~ + {lenses} (table) of lenses to store (`CodeLens[] | null`) + {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" > + + vim.lsp.handlers["textDocument/hover"] = vim.lsp.with( + vim.lsp.handlers.hover, { + -- Use a sharp border with `FloatBorder` highlights + border = "single" + } + ) < - Parameters: ~ - {config} (table) Configuration table. - • border: (default=nil) - • Add borders to the floating window - • See |nvim_open_win()| + Parameters: ~ + {config} (table) Configuration table. + • border: (default=nil) + • Add borders to the floating window + • See |nvim_open_win()| *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" - } - ) + |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" + } + ) < - Parameters: ~ - {config} (table) Configuration table. - • border: (default=nil) - • Add borders to the floating window - • See |vim.api.nvim_open_win()| + Parameters: ~ + {config} (table) Configuration table. + • border: (default=nil) + • Add borders to the floating window + • See |vim.api.nvim_open_win()| ============================================================================== @@ -1554,591 +1437,553 @@ Lua module: vim.lsp.util *lsp-util* *vim.lsp.util.apply_text_document_edit()* apply_text_document_edit({text_document_edit}, {index}, {offset_encoding}) - Applies a `TextDocumentEdit`, which is a list of changes to a - single document. + Applies a `TextDocumentEdit`, which is a list of changes to a single + document. - Parameters: ~ - {text_document_edit} table: a `TextDocumentEdit` object - {index} number: Optional index of the edit, - if from a list of edits (or nil, if - not from a list) + Parameters: ~ + {text_document_edit} table: a `TextDocumentEdit` object + {index} number: Optional index of the edit, if from a + list of edits (or nil, if not from a list) - See also: ~ - https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentEdit + See also: ~ + https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentEdit *vim.lsp.util.apply_text_edits()* apply_text_edits({text_edits}, {bufnr}, {offset_encoding}) - Applies a list of text edits to a buffer. + Applies a list of text edits to a buffer. - Parameters: ~ - {text_edits} (table) list of `TextEdit` objects - {bufnr} (number) Buffer id - {offset_encoding} (string) utf-8|utf-16|utf-32 + Parameters: ~ + {text_edits} (table) list of `TextEdit` objects + {bufnr} (number) Buffer id + {offset_encoding} (string) utf-8|utf-16|utf-32 - See also: ~ - https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textEdit + See also: ~ + https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textEdit *vim.lsp.util.apply_workspace_edit()* apply_workspace_edit({workspace_edit}, {offset_encoding}) - Applies a `WorkspaceEdit`. + Applies a `WorkspaceEdit`. - Parameters: ~ - {workspace_edit} (table) `WorkspaceEdit` - {offset_encoding} (string) utf-8|utf-16|utf-32 (required) + Parameters: ~ + {workspace_edit} (table) `WorkspaceEdit` + {offset_encoding} (string) utf-8|utf-16|utf-32 (required) buf_clear_references({bufnr}) *vim.lsp.util.buf_clear_references()* - Removes document highlights from a buffer. + Removes document highlights from a buffer. - Parameters: ~ - {bufnr} (number) Buffer id + Parameters: ~ + {bufnr} (number) Buffer id *vim.lsp.util.buf_highlight_references()* buf_highlight_references({bufnr}, {references}, {offset_encoding}) - Shows a list of document highlights for a certain buffer. + Shows a list of document highlights for a certain buffer. - Parameters: ~ - {bufnr} (number) Buffer id - {references} (table) List of `DocumentHighlight` - objects to highlight - {offset_encoding} (string) One of "utf-8", "utf-16", - "utf-32". + Parameters: ~ + {bufnr} (number) Buffer id + {references} (table) List of `DocumentHighlight` objects to + highlight + {offset_encoding} (string) One of "utf-8", "utf-16", "utf-32". - See also: ~ - https://microsoft.github.io/language-server-protocol/specifications/specification-3-17/#documentHighlight + See also: ~ + https://microsoft.github.io/language-server-protocol/specifications/specification-3-17/#documentHighlight *vim.lsp.util.character_offset()* character_offset({buf}, {row}, {col}, {offset_encoding}) - Returns the UTF-32 and UTF-16 offsets for a position in a - certain buffer. + Returns the UTF-32 and UTF-16 offsets for a position in a certain buffer. - Parameters: ~ - {buf} (number) buffer number (0 for current) - {row} 0-indexed line - {col} 0-indexed byte offset in line - {offset_encoding} (string) utf-8|utf-16|utf-32|nil - defaults to `offset_encoding` of first - client of `buf` + Parameters: ~ + {buf} (number) buffer number (0 for current) + {row} 0-indexed line + {col} 0-indexed byte offset in line + {offset_encoding} (string) utf-8|utf-16|utf-32|nil defaults to + `offset_encoding` of first client of `buf` - Return: ~ - (number, number) `offset_encoding` index of the character - in line {row} column {col} in buffer {buf} + Return: ~ + (number, number) `offset_encoding` index of the character in line + {row} column {col} in buffer {buf} *vim.lsp.util.convert_input_to_markdown_lines()* convert_input_to_markdown_lines({input}, {contents}) - Converts any of `MarkedString` | `MarkedString[]` | - `MarkupContent` into a list of lines containing valid - markdown. Useful to populate the hover window for - `textDocument/hover`, for parsing the result of - `textDocument/signatureHelp`, and potentially others. + Converts any of `MarkedString` | `MarkedString[]` | `MarkupContent` into a + list of lines containing valid markdown. Useful to populate the hover + window for `textDocument/hover`, for parsing the result of + `textDocument/signatureHelp`, and potentially others. - Parameters: ~ - {input} (`MarkedString` | `MarkedString[]` | - `MarkupContent`) - {contents} (table, optional, default `{}`) List of - strings to extend with converted lines + Parameters: ~ + {input} (`MarkedString` | `MarkedString[]` | `MarkupContent`) + {contents} (table, optional, default `{}`) List of strings to extend + with converted lines - Return: ~ - {contents}, extended with lines of converted markdown. + Return: ~ + {contents}, extended with lines of converted markdown. - See also: ~ - https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_hover + See also: ~ + https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_hover *vim.lsp.util.convert_signature_help_to_markdown_lines()* convert_signature_help_to_markdown_lines({signature_help}, {ft}, {triggers}) - Converts `textDocument/SignatureHelp` response to markdown - lines. + Converts `textDocument/SignatureHelp` response to markdown lines. - Parameters: ~ - {signature_help} Response of `textDocument/SignatureHelp` - {ft} optional filetype that will be use as - the `lang` for the label markdown code - block - {triggers} optional list of trigger characters from - the lsp server. used to better determine - parameter offsets + Parameters: ~ + {signature_help} Response of `textDocument/SignatureHelp` + {ft} optional filetype that will be use as the `lang` for + the label markdown code block + {triggers} optional list of trigger characters from the lsp + server. used to better determine parameter offsets - Return: ~ - list of lines of converted markdown. + Return: ~ + list of lines of converted markdown. - See also: ~ - https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_signatureHelp + See also: ~ + https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_signatureHelp *vim.lsp.util.extract_completion_items()* extract_completion_items({result}) - Can be used to extract the completion items from a `textDocument/completion` request, which may return one of `CompletionItem[]` , `CompletionList` or null. + Can be used to extract the completion items from a `textDocument/completion` request, which may return one of `CompletionItem[]` , `CompletionList` or null. - Parameters: ~ - {result} (table) The result of a - `textDocument/completion` request + Parameters: ~ + {result} (table) The result of a `textDocument/completion` request - Return: ~ - (table) List of completion items + Return: ~ + (table) List of completion items - See also: ~ - https://microsoft.github.io/language-server-protocol/specification#textDocument_completion + See also: ~ + https://microsoft.github.io/language-server-protocol/specification#textDocument_completion get_effective_tabstop({bufnr}) *vim.lsp.util.get_effective_tabstop()* - Returns indentation size. + Returns indentation size. - Parameters: ~ - {bufnr} (number|nil): Buffer handle, defaults to current + Parameters: ~ + {bufnr} (number|nil): Buffer handle, defaults to current - Return: ~ - (number) indentation size + Return: ~ + (number) indentation size - See also: ~ - |shiftwidth| + See also: ~ + |shiftwidth| *vim.lsp.util.jump_to_location()* jump_to_location({location}, {offset_encoding}, {reuse_win}) - Jumps to a location. + Jumps to a location. - Parameters: ~ - {location} (table) (`Location`|`LocationLink`) - {offset_encoding} (string) utf-8|utf-16|utf-32 (required) - {reuse_win} (boolean) Jump to existing window if - buffer is already opened. + Parameters: ~ + {location} (table) (`Location`|`LocationLink`) + {offset_encoding} (string) utf-8|utf-16|utf-32 (required) + {reuse_win} (boolean) Jump to existing window if buffer is + already opened. - Return: ~ - `true` if the jump succeeded + Return: ~ + `true` if the jump succeeded *vim.lsp.util.locations_to_items()* locations_to_items({locations}, {offset_encoding}) - Returns the items with the byte position calculated correctly - and in sorted order, for display in quickfix and location - lists. + Returns the items with the byte position calculated correctly and in + sorted order, for display in quickfix and location lists. - The result can be passed to the {list} argument of - |setqflist()| or |setloclist()|. + The result can be passed to the {list} argument of |setqflist()| or + |setloclist()|. - Parameters: ~ - {locations} (table) list of `Location`s or - `LocationLink`s - {offset_encoding} (string) offset_encoding for locations - utf-8|utf-16|utf-32 + Parameters: ~ + {locations} (table) list of `Location`s or `LocationLink`s + {offset_encoding} (string) offset_encoding for locations + utf-8|utf-16|utf-32 - Return: ~ - (table) list of items + Return: ~ + (table) list of items lookup_section({settings}, {section}) *vim.lsp.util.lookup_section()* - Helper function to return nested values in language server - settings + Helper function to return nested values in language server settings - Parameters: ~ - {settings} a table of language server settings - {section} a string indicating the field of the settings - table + Parameters: ~ + {settings} a table of language server settings + {section} a string indicating the field of the settings table - Return: ~ - (table or string) The value of settings accessed via - section + Return: ~ + (table or string) The value of settings accessed via section *vim.lsp.util.make_floating_popup_options()* make_floating_popup_options({width}, {height}, {opts}) - Creates a table with sensible default options for a floating - window. The table can be passed to |nvim_open_win()|. - - Parameters: ~ - {width} (number) window width (in character cells) - {height} (number) window height (in character cells) - {opts} (table, optional) - • offset_x (number) offset to add to `col` - • offset_y (number) offset to add to `row` - • border (string or table) override `border` - • focusable (string or table) override - `focusable` - • zindex (string or table) override `zindex`, - defaults to 50 - - Return: ~ - (table) Options + Creates a table with sensible default options for a floating window. The + table can be passed to |nvim_open_win()|. + + Parameters: ~ + {width} (number) window width (in character cells) + {height} (number) window height (in character cells) + {opts} (table, optional) + • offset_x (number) offset to add to `col` + • offset_y (number) offset to add to `row` + • border (string or table) override `border` + • focusable (string or table) override `focusable` + • zindex (string or table) override `zindex`, defaults to 50 + + Return: ~ + (table) Options *vim.lsp.util.make_formatting_params()* make_formatting_params({options}) - Creates a `DocumentFormattingParams` object for the current - buffer and cursor position. + Creates a `DocumentFormattingParams` object for the current buffer and + cursor position. - Parameters: ~ - {options} (table|nil) with valid `FormattingOptions` - entries + Parameters: ~ + {options} (table|nil) with valid `FormattingOptions` entries - Return: ~ - `DocumentFormattingParams` object + Return: ~ + `DocumentFormattingParams` object - See also: ~ - https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_formatting + See also: ~ + https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_formatting *vim.lsp.util.make_given_range_params()* make_given_range_params({start_pos}, {end_pos}, {bufnr}, {offset_encoding}) - Using the given range in the current buffer, creates an object - that is similar to |vim.lsp.util.make_range_params()|. - - Parameters: ~ - {start_pos} number[]|nil {row, col} mark-indexed - position. Defaults to the start of the - last visual selection. - {end_pos} number[]|nil {row, col} mark-indexed - position. Defaults to the end of the - last visual selection. - {bufnr} (number|nil) buffer handle or 0 for - current, defaults to current - {offset_encoding} "utf-8"|"utf-16"|"utf-32"|nil defaults - to `offset_encoding` of first client of - `bufnr` - - Return: ~ - { textDocument = { uri = `current_file_uri` }, range = { - start = `start_position`, end = `end_position` } } + Using the given range in the current buffer, creates an object that is + similar to |vim.lsp.util.make_range_params()|. + + Parameters: ~ + {start_pos} number[]|nil {row, col} mark-indexed position. + Defaults to the start of the last visual selection. + {end_pos} number[]|nil {row, col} mark-indexed position. + Defaults to the end of the last visual selection. + {bufnr} (number|nil) buffer handle or 0 for current, + defaults to current + {offset_encoding} "utf-8"|"utf-16"|"utf-32"|nil defaults to + `offset_encoding` of first client of `bufnr` + + Return: ~ + { textDocument = { uri = `current_file_uri` }, range = { start = + `start_position`, end = `end_position` } } *vim.lsp.util.make_position_params()* make_position_params({window}, {offset_encoding}) - Creates a `TextDocumentPositionParams` object for the current - buffer and cursor position. + Creates a `TextDocumentPositionParams` object for the current buffer and + cursor position. - 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` of first - client of buffer of `window` + 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` of first client of buffer of + `window` - Return: ~ - `TextDocumentPositionParams` object + Return: ~ + `TextDocumentPositionParams` object - See also: ~ - https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentPositionParams + See also: ~ + https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentPositionParams *vim.lsp.util.make_range_params()* make_range_params({window}, {offset_encoding}) - Using the current position in the current buffer, creates an - object that can be used as a building block for several LSP - requests, such as `textDocument/codeAction`, - `textDocument/colorPresentation`, - `textDocument/rangeFormatting`. - - Parameters: ~ - {window} number|nil: window handle or 0 for - current, defaults to current - {offset_encoding} "utf-8"|"utf-16"|"utf-32"|nil defaults - to `offset_encoding` of first client of - buffer of `window` - - Return: ~ - { textDocument = { uri = `current_file_uri` }, range = { - start = `current_position`, end = `current_position` } } + Using the current position in the current buffer, creates an object that + can be used as a building block for several LSP requests, such as + `textDocument/codeAction`, `textDocument/colorPresentation`, + `textDocument/rangeFormatting`. + + Parameters: ~ + {window} number|nil: window handle or 0 for current, + defaults to current + {offset_encoding} "utf-8"|"utf-16"|"utf-32"|nil defaults to + `offset_encoding` of first client of buffer of + `window` + + Return: ~ + { textDocument = { uri = `current_file_uri` }, range = { start = + `current_position`, end = `current_position` } } *vim.lsp.util.make_text_document_params()* make_text_document_params({bufnr}) - Creates a `TextDocumentIdentifier` object for the current - buffer. + Creates a `TextDocumentIdentifier` object for the current buffer. - Parameters: ~ - {bufnr} number|nil: Buffer handle, defaults to current + Parameters: ~ + {bufnr} number|nil: Buffer handle, defaults to current - Return: ~ - `TextDocumentIdentifier` + Return: ~ + `TextDocumentIdentifier` - See also: ~ - https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentIdentifier + See also: ~ + https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentIdentifier *vim.lsp.util.make_workspace_params()* make_workspace_params({added}, {removed}) - Create the workspace params + Create the workspace params - Parameters: ~ - {added} - {removed} + Parameters: ~ + {added} + {removed} *vim.lsp.util.open_floating_preview()* open_floating_preview({contents}, {syntax}, {opts}) - Shows contents in a floating window. - - Parameters: ~ - {contents} (table) of lines to show in window - {syntax} (string) of syntax to set for opened buffer - {opts} (table) with optional fields (additional keys - are passed on to |vim.api.nvim_open_win()|) - • height: (number) height of floating window - • width: (number) width of floating window - • wrap: (boolean, default true) wrap long - lines - • wrap_at: (number) character to wrap at for - computing height when wrap is enabled - • max_width: (number) maximal width of - floating window - • max_height: (number) maximal height of - floating window - • pad_top: (number) number of lines to pad - contents at top - • pad_bottom: (number) number of lines to pad - contents at bottom - • focus_id: (string) if a popup with this id - is opened, then focus it - • close_events: (table) list of events that - closes the floating window - • focusable: (boolean, default true) Make - float focusable - • focus: (boolean, default true) If `true`, - and if {focusable} is also `true`, focus an - existing floating window with the same - {focus_id} - - Return: ~ - bufnr,winnr buffer and window number of the newly created - floating preview window + Shows contents in a floating window. + + Parameters: ~ + {contents} (table) of lines to show in window + {syntax} (string) of syntax to set for opened buffer + {opts} (table) with optional fields (additional keys are passed + on to |vim.api.nvim_open_win()|) + • height: (number) height of floating window + • width: (number) width of floating window + • wrap: (boolean, default true) wrap long lines + • wrap_at: (number) character to wrap at for computing + height when wrap is enabled + • max_width: (number) maximal width of floating window + • max_height: (number) maximal height of floating window + • pad_top: (number) number of lines to pad contents at top + • pad_bottom: (number) number of lines to pad contents at + bottom + • focus_id: (string) if a popup with this id is opened, + then focus it + • close_events: (table) list of events that closes the + floating window + • focusable: (boolean, default true) Make float focusable + • focus: (boolean, default true) If `true`, and if + {focusable} is also `true`, focus an existing floating + window with the same {focus_id} + + Return: ~ + bufnr,winnr buffer and window number of the newly created floating + preview window parse_snippet({input}) *vim.lsp.util.parse_snippet()* - Parses snippets in a completion entry. + Parses snippets in a completion entry. - Parameters: ~ - {input} (string) unparsed snippet + Parameters: ~ + {input} (string) unparsed snippet - Return: ~ - (string) parsed snippet + Return: ~ + (string) parsed snippet preview_location({location}, {opts}) *vim.lsp.util.preview_location()* - Previews a location in a floating window + Previews a location in a floating window - behavior depends on type of location: - • for Location, range is shown (e.g., function definition) - • for LocationLink, targetRange is shown (e.g., body of - function definition) + behavior depends on type of location: + • for Location, range is shown (e.g., function definition) + • for LocationLink, targetRange is shown (e.g., body of function + definition) - Parameters: ~ - {location} a single `Location` or `LocationLink` + Parameters: ~ + {location} a single `Location` or `LocationLink` - Return: ~ - (bufnr,winnr) buffer and window number of floating window - or nil + Return: ~ + (bufnr,winnr) buffer and window number of floating window or nil rename({old_fname}, {new_fname}, {opts}) *vim.lsp.util.rename()* - Rename old_fname to new_fname + Rename old_fname to new_fname - Parameters: ~ - {opts} (table) + Parameters: ~ + {opts} (table) set_lines({lines}, {A}, {B}, {new_lines}) *vim.lsp.util.set_lines()* - Replaces text in a range with new text. + Replaces text in a range with new text. - CAUTION: Changes in-place! + CAUTION: Changes in-place! - Parameters: ~ - {lines} (table) Original list of strings - {A} (table) Start position; a 2-tuple of {line, - col} numbers - {B} (table) End position; a 2-tuple of {line, - col} numbers - {new_lines} A list of strings to replace the original + Parameters: ~ + {lines} (table) Original list of strings + {A} (table) Start position; a 2-tuple of {line, col} numbers + {B} (table) End position; a 2-tuple of {line, col} numbers + {new_lines} A list of strings to replace the original - Return: ~ - (table) The modified {lines} object + Return: ~ + (table) The modified {lines} object *vim.lsp.util.stylize_markdown()* stylize_markdown({bufnr}, {contents}, {opts}) - Converts markdown into syntax highlighted regions by stripping - the code blocks and converting them into highlighted code. - This will by default insert a blank line separator after those - code block regions to improve readability. - - This method configures the given buffer and returns the lines - to set. - - If you want to open a popup with fancy markdown, use - `open_floating_preview` instead - - Parameters: ~ - {contents} (table) of lines to show in window - {opts} dictionary with optional fields - • height of floating window - • width of floating window - • wrap_at character to wrap at for computing - height - • max_width maximal width of floating window - • max_height maximal height of floating window - • pad_top number of lines to pad contents at - top - • pad_bottom number of lines to pad contents - at bottom - • separator insert separator after code block - - Return: ~ - width,height size of float + Converts markdown into syntax highlighted regions by stripping the code + blocks and converting them into highlighted code. This will by default + insert a blank line separator after those code block regions to improve + readability. + + This method configures the given buffer and returns the lines to set. + + If you want to open a popup with fancy markdown, use + `open_floating_preview` instead + + Parameters: ~ + {contents} (table) of lines to show in window + {opts} dictionary with optional fields + • height of floating window + • width of floating window + • wrap_at character to wrap at for computing height + • max_width maximal width of floating window + • max_height maximal height of floating window + • pad_top number of lines to pad contents at top + • pad_bottom number of lines to pad contents at bottom + • separator insert separator after code block + + Return: ~ + width,height size of float symbols_to_items({symbols}, {bufnr}) *vim.lsp.util.symbols_to_items()* - Converts symbols to quickfix list items. + Converts symbols to quickfix list items. - Parameters: ~ - {symbols} DocumentSymbol[] or SymbolInformation[] + Parameters: ~ + {symbols} DocumentSymbol[] or SymbolInformation[] *vim.lsp.util.text_document_completion_list_to_complete_items()* text_document_completion_list_to_complete_items({result}, {prefix}) - Turns the result of a `textDocument/completion` request into - vim-compatible |complete-items|. + Turns the result of a `textDocument/completion` request into + vim-compatible |complete-items|. - Parameters: ~ - {result} The result of a `textDocument/completion` call, - e.g. from |vim.lsp.buf.completion()|, which may - be one of `CompletionItem[]`, `CompletionList` - or `null` - {prefix} (string) the prefix to filter the completion - items + Parameters: ~ + {result} The result of a `textDocument/completion` call, e.g. from + |vim.lsp.buf.completion()|, which may be one of + `CompletionItem[]`, `CompletionList` or `null` + {prefix} (string) the prefix to filter the completion items - Return: ~ - { matches = complete-items table, incomplete = bool } + Return: ~ + { matches = complete-items table, incomplete = bool } - See also: ~ - |complete-items| + See also: ~ + |complete-items| trim_empty_lines({lines}) *vim.lsp.util.trim_empty_lines()* - Removes empty lines from the beginning and end. + Removes empty lines from the beginning and end. - Parameters: ~ - {lines} (table) list of lines to trim + Parameters: ~ + {lines} (table) list of lines to trim - Return: ~ - (table) trimmed list of lines + Return: ~ + (table) trimmed list of lines *vim.lsp.util.try_trim_markdown_code_blocks()* try_trim_markdown_code_blocks({lines}) - Accepts markdown lines and tries to reduce them to a filetype - if they comprise just a single code block. + Accepts markdown lines and tries to reduce them to a filetype if they + comprise just a single code block. - CAUTION: Modifies the input in-place! + CAUTION: Modifies the input in-place! - Parameters: ~ - {lines} (table) list of lines + Parameters: ~ + {lines} (table) list of lines - Return: ~ - (string) filetype or 'markdown' if it was unchanged. + Return: ~ + (string) filetype or 'markdown' if it was unchanged. ============================================================================== Lua module: vim.lsp.log *lsp-log* get_filename() *vim.lsp.log.get_filename()* - Returns the log filename. + Returns the log filename. - Return: ~ - (string) log filename + Return: ~ + (string) log filename get_level() *vim.lsp.log.get_level()* - Gets the current log level. + Gets the current log level. - Return: ~ - (string) current log level + Return: ~ + (string) current log level set_format_func({handle}) *vim.lsp.log.set_format_func()* - Sets formatting function used to format logs + Sets formatting function used to format logs - Parameters: ~ - {handle} (function) function to apply to logging - arguments, pass vim.inspect for multi-line - formatting + Parameters: ~ + {handle} (function) function to apply to logging arguments, pass + vim.inspect for multi-line formatting set_level({level}) *vim.lsp.log.set_level()* - Sets the current log level. + Sets the current log level. - Parameters: ~ - {level} (string or number) One of `vim.lsp.log.levels` + Parameters: ~ + {level} (string or number) One of `vim.lsp.log.levels` should_log({level}) *vim.lsp.log.should_log()* - Checks whether the level is sufficient for logging. + Checks whether the level is sufficient for logging. - Parameters: ~ - {level} (number) log level + Parameters: ~ + {level} (number) log level - Return: ~ - (bool) true if would log, false if not + Return: ~ + (bool) true if would log, false if not ============================================================================== Lua module: vim.lsp.rpc *lsp-rpc* +connect({host}, {port}) *vim.lsp.rpc.connect()* + Create a LSP RPC client factory that connects via TCP to the given host + and port + + Parameters: ~ + {host} (string) + {port} (number) + + Return: ~ + (function) + format_rpc_error({err}) *vim.lsp.rpc.format_rpc_error()* - Constructs an error message from an LSP error object. + Constructs an error message from an LSP error object. - Parameters: ~ - {err} (table) The error object + Parameters: ~ + {err} (table) The error object - Return: ~ - (string) The formatted error message + Return: ~ + (string) The formatted error message notify({method}, {params}) *vim.lsp.rpc.notify()* - Sends a notification to the LSP server. + Sends a notification to the LSP server. - Parameters: ~ - {method} (string) The invoked LSP method - {params} (table): Parameters for the invoked LSP method + Parameters: ~ + {method} (string) The invoked LSP method + {params} (table|nil): Parameters for the invoked LSP method - Return: ~ - (bool) `true` if notification could be sent, `false` if - not + Return: ~ + (bool) `true` if notification could be sent, `false` if not *vim.lsp.rpc.request()* request({method}, {params}, {callback}, {notify_reply_callback}) - Sends a request to the LSP server and runs {callback} upon - response. - - Parameters: ~ - {method} (string) The invoked LSP method - {params} (table) Parameters for the - invoked LSP method - {callback} (function) Callback to invoke - {notify_reply_callback} (function|nil) Callback to invoke - as soon as a request is no longer - pending - - Return: ~ - (bool, number) `(true, message_id)` if request could be - sent, `false` if not + Sends a request to the LSP server and runs {callback} upon response. + + Parameters: ~ + {method} (string) The invoked LSP method + {params} (table|nil) Parameters for the invoked LSP + method + {callback} (function) Callback to invoke + {notify_reply_callback} (function|nil) Callback to invoke as soon as + a request is no longer pending + + Return: ~ + (bool, number) `(true, message_id)` if request could be sent, `false` + if not *vim.lsp.rpc.rpc_response_error()* rpc_response_error({code}, {message}, {data}) - Creates an RPC response object/table. + Creates an RPC response object/table. - Parameters: ~ - {code} RPC error code defined in - `vim.lsp.protocol.ErrorCodes` - {message} (optional) arbitrary message to send to server - {data} (optional) arbitrary data to send to server + Parameters: ~ + {code} (number) RPC error code defined in + `vim.lsp.protocol.ErrorCodes` + {message} (string|nil) arbitrary message to send to server + {data} any|nil arbitrary data to send to server *vim.lsp.rpc.start()* start({cmd}, {cmd_args}, {dispatchers}, {extra_spawn_params}) - Starts an LSP server process and create an LSP RPC client - object to interact with it. Communication with the server is - currently limited to stdio. - - Parameters: ~ - {cmd} (string) Command to start the LSP - server. - {cmd_args} (table) List of additional string - arguments to pass to {cmd}. - {dispatchers} (table, optional) Dispatchers for - LSP message types. Valid dispatcher - names are: - • `"notification"` - • `"server_request"` - • `"on_error"` - • `"on_exit"` - {extra_spawn_params} (table, optional) Additional context - for the LSP server process. May - contain: - • {cwd} (string) Working directory - for the LSP server process - • {env} (table) Additional - environment variables for LSP - server process - - Return: ~ - Client RPC object. - Methods: - • `notify()` |vim.lsp.rpc.notify()| - • `request()` |vim.lsp.rpc.request()| - - Members: - • {pid} (number) The LSP server's PID. - • {handle} A handle for low-level interaction with the LSP - server process |vim.loop|. + Starts an LSP server process and create an LSP RPC client object to + interact with it. Communication with the server is currently limited to + stdio. + + Parameters: ~ + {cmd} (string) Command to start the LSP server. + {cmd_args} (table) List of additional string arguments to + pass to {cmd}. + {dispatchers} (table|nil) Dispatchers for LSP message types. + Valid dispatcher names are: + • `"notification"` + • `"server_request"` + • `"on_error"` + • `"on_exit"` + {extra_spawn_params} (table|nil) Additional context for the LSP + server process. May contain: + • {cwd} (string) Working directory for the LSP + server process + • {env} (table) Additional environment variables + for LSP server process + + Return: ~ + Client RPC object. + Methods: + • `notify()` |vim.lsp.rpc.notify()| + • `request()` |vim.lsp.rpc.request()| + • `is_closing()` returns a boolean indicating if the RPC is closing. + • `terminate()` terminates the RPC client. ============================================================================== @@ -2146,23 +1991,20 @@ Lua module: vim.lsp.sync *lsp-sync* *vim.lsp.sync.compute_diff()* compute_diff({___MissingCloseParenHere___}) - Returns the range table for the difference between prev and - curr lines + Returns the range table for the difference between prev and curr lines - Parameters: ~ - {prev_lines} (table) list of lines - {curr_lines} (table) list of lines - {firstline} (number) line to begin search for first - difference - {lastline} (number) line to begin search in - old_lines for last difference - {new_lastline} (number) line to begin search in - new_lines for last difference - {offset_encoding} (string) encoding requested by language - server + Parameters: ~ + {prev_lines} (table) list of lines + {curr_lines} (table) list of lines + {firstline} (number) line to begin search for first difference + {lastline} (number) line to begin search in old_lines for last + difference + {new_lastline} (number) line to begin search in new_lines for last + difference + {offset_encoding} (string) encoding requested by language server - Return: ~ - (table) TextDocumentContentChangeEvent see https://microsoft.github.io/language-server-protocol/specifications/specification-3-17/#textDocumentContentChangeEvent + Return: ~ + (table) TextDocumentContentChangeEvent see https://microsoft.github.io/language-server-protocol/specifications/specification-3-17/#textDocumentContentChangeEvent ============================================================================== @@ -2170,19 +2012,18 @@ Lua module: vim.lsp.protocol *lsp-protocol* *vim.lsp.protocol.make_client_capabilities()* make_client_capabilities() - Gets a new ClientCapabilities object describing the LSP client - capabilities. + Gets a new ClientCapabilities object describing the LSP client + capabilities. *vim.lsp.protocol.resolve_capabilities()* resolve_capabilities({server_capabilities}) - Creates a normalized object describing LSP server - capabilities. + Creates a normalized object describing LSP server capabilities. - Parameters: ~ - {server_capabilities} (table) Table of capabilities - supported by the server + Parameters: ~ + {server_capabilities} (table) Table of capabilities supported by the + server - Return: ~ - (table) Normalized table of capabilities + Return: ~ + (table) Normalized table of capabilities - vim:tw=78:ts=8:ft=help:norl: + 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 4062a35735..499b3f9a6f 100644 --- a/runtime/doc/lua.txt +++ b/runtime/doc/lua.txt @@ -37,7 +37,7 @@ separator when searching. For a module `foo.bar`, each directory is searched for `lua/foo/bar.lua`, then `lua/foo/bar/init.lua`. If no files are found, the directories are searched again for a shared library with a name matching `lua/foo/bar.?`, where `?` is a list of suffixes (such as `so` or `dll`) derived from -the initial value of `package.cpath`. If still no files are found, Nvim falls +the initial value of |package.cpath|. If still no files are found, Nvim falls back to Lua's default search mechanism. The first script found is run and `require()` returns the value returned by the script if any, else `true`. @@ -46,7 +46,7 @@ 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. -For example, if 'runtimepath' is `foo,bar` and `package.cpath` was +For example, if 'runtimepath' is `foo,bar` and |package.cpath| was `./?.so;./?.dll` at startup, `require('mod')` searches these paths in order and loads the first module found: @@ -59,27 +59,27 @@ and loads the first module found: bar/lua/mod.so bar/lua/mod.dll -Nvim automatically adjusts `package.path` and `package.cpath` according to the +Nvim automatically adjusts |package.path| and |package.cpath| according to the effective 'runtimepath' value. Adjustment happens whenever 'runtimepath' is -changed. `package.path` is adjusted by simply appending `/lua/?.lua` and +changed. |package.path| is adjusted by simply appending `/lua/?.lua` and `/lua/?/init.lua` to each directory from 'runtimepath' (`/` is actually the first character of `package.config`). -Similarly to `package.path`, modified directories from 'runtimepath' are also -added to `package.cpath`. In this case, instead of appending `/lua/?.lua` and +Similarly to |package.path|, modified directories from 'runtimepath' are also +added to |package.cpath|. In this case, instead of appending `/lua/?.lua` and `/lua/?/init.lua` to each runtimepath, all unique `?`-containing suffixes of -the existing `package.cpath` are used. Example: +the existing |package.cpath| are used. Example: 1. Given that - 'runtimepath' contains `/foo/bar,/xxx;yyy/baz,/abc`; - initial (defined at compile-time or derived from - `$LUA_CPATH`/`$LUA_INIT`) `package.cpath` contains + `$LUA_CPATH`/`$LUA_INIT`) |package.cpath| contains `./?.so;/def/ghi/a?d/j/g.elf;/def/?.so`. 2. It finds `?`-containing suffixes `/?.so`, `/a?d/j/g.elf` and `/?.so`, in order: parts of the path starting from the first path component containing question mark and preceding path separator. 3. The suffix of `/def/?.so`, namely `/?.so` is not unique, as it’s the same - as the suffix of the first path from `package.path` (i.e. `./?.so`). Which + as the suffix of the first path from |package.path| (i.e. `./?.so`). Which leaves `/?.so` and `/a?d/j/g.elf`, in this order. 4. 'runtimepath' has three paths: `/foo/bar`, `/xxx;yyy/baz` and `/abc`. The second one contains a semicolon which is a paths separator so it is out, @@ -93,7 +93,7 @@ the existing `package.cpath` are used. Example: - `/abc/lua/?.so` - `/abc/lua/a?d/j/g.elf` -6. New paths are prepended to the original `package.cpath`. +6. New paths are prepended to the original |package.cpath|. The result will look like this: @@ -108,16 +108,16 @@ Note: remembered and removed at the next update, while all paths derived from the new 'runtimepath' are prepended as described above. This allows removing paths when path is removed from 'runtimepath', adding paths when they are - added and reordering `package.path`/`package.cpath` content if 'runtimepath' + added and reordering |package.path|/|package.cpath| content if 'runtimepath' was reordered. - Although adjustments happen automatically, Nvim does not track current - values of `package.path` or `package.cpath`. If you happen to delete some + values of |package.path| or |package.cpath|. If you happen to delete some paths from there you can set 'runtimepath' to trigger an update: > let &runtimepath = &runtimepath - Skipping paths from 'runtimepath' which contain semicolons applies both to - `package.path` and `package.cpath`. Given that there are some badly written + |package.path| and |package.cpath|. Given that there are some badly written plugins using shell, which will not work with paths containing semicolons, it is better to not have them in 'runtimepath' at all. @@ -182,7 +182,7 @@ Lua Patterns *lua-patterns* For performance reasons, Lua does not support regular expressions natively. Instead, the Lua `string` standard library allows manipulations using a -restricted set of "patterns", see https://www.lua.org/manual/5.1/manual.html#5.4.1 +restricted set of "patterns", see |luaref-patterns|. Examples (`string.match` extracts the first match): > @@ -292,68 +292,64 @@ arguments separated by " " (space) instead of "\t" (tab). *:lua* :lua {chunk} - Executes Lua chunk {chunk}. - If {chunk} starts with "=" the rest of the chunk is - evaluated as an expression and printed. `:lua =expr` - is equivalent to `:lua print(vim.inspect(expr))` - Examples: > - :lua vim.api.nvim_command('echo "Hello, Nvim!"') -< To see the Lua version: > - :lua print(_VERSION) -< To see the LuaJIT version: > - :lua =jit.version + Executes Lua chunk {chunk}. If {chunk} starts with "=" the rest of the + chunk is evaluated as an expression and printed. `:lua =expr` is + equivalent to `:lua print(vim.inspect(expr))` + + Examples: > + :lua vim.api.nvim_command('echo "Hello, Nvim!"') +< To see the Lua version: > + :lua print(_VERSION) +< To see the LuaJIT version: > + :lua =jit.version < *:lua-heredoc* :lua << [endmarker] {script} {endmarker} - Executes Lua script {script} from within Vimscript. - {endmarker} must NOT be preceded by whitespace. You - can omit [endmarker] after the "<<" and use a dot "." - after {script} (similar to |:append|, |:insert|). - - Example: - > - function! CurrentLineInfo() - lua << EOF - local linenr = vim.api.nvim_win_get_cursor(0)[1] - local curline = vim.api.nvim_buf_get_lines( - 0, linenr - 1, linenr, false)[1] - print(string.format("Current line [%d] has %d bytes", - linenr, #curline)) - EOF - endfunction + Executes Lua script {script} from within Vimscript. {endmarker} must NOT + be preceded by whitespace. You can omit [endmarker] after the "<<" and use + a dot "." after {script} (similar to |:append|, |:insert|). + + Example: > + function! CurrentLineInfo() + lua << EOF + local linenr = vim.api.nvim_win_get_cursor(0)[1] + local curline = vim.api.nvim_buf_get_lines( + 0, linenr - 1, linenr, false)[1] + print(string.format("Current line [%d] has %d bytes", + linenr, #curline)) + EOF + endfunction < - Note that the `local` variables will disappear when - the block finishes. But not globals. + Note that the `local` variables will disappear when the block finishes. + But not globals. *:luado* -:[range]luado {body} Executes Lua chunk "function(line, linenr) {body} end" - for each buffer line in [range], where `line` is the - current line text (without <EOL>), and `linenr` is the - current line number. If the function returns a string - that becomes the text of the corresponding buffer - line. Default [range] is the whole file: "1,$". - - Examples: - > - :luado return string.format("%s\t%d", line:reverse(), #line) - - :lua require"lpeg" - :lua -- balanced parenthesis grammar: - :lua bp = lpeg.P{ "(" * ((1 - lpeg.S"()") + lpeg.V(1))^0 * ")" } - :luado if bp:match(line) then return "-->\t" .. line end +:[range]luado {body} + Executes Lua chunk "function(line, linenr) {body} end" for each buffer + line in [range], where `line` is the current line text (without <EOL>), + and `linenr` is the current line number. If the function returns a string + that becomes the text of the corresponding buffer line. Default [range] is + the whole file: "1,$". + + Examples: > + :luado return string.format("%s\t%d", line:reverse(), #line) + + :lua require"lpeg" + :lua -- balanced parenthesis grammar: + :lua bp = lpeg.P{ "(" * ((1 - lpeg.S"()") + lpeg.V(1))^0 * ")" } + :luado if bp:match(line) then return "-->\t" .. line end < *:luafile* :luafile {file} - Execute Lua script in {file}. - The whole argument is used as the filename (like - |:edit|), spaces do not need to be escaped. - Alternatively you can |:source| Lua files. - - Examples: > - :luafile script.lua - :luafile % + Execute Lua script in {file}. + The whole argument is used as the filename (like |:edit|), spaces do not + need to be escaped. Alternatively you can |:source| Lua files. + + Examples: > + :luafile script.lua + :luafile % < ============================================================================== @@ -516,8 +512,8 @@ management. Try this command to see available functions: > :lua print(vim.inspect(vim.loop)) < -Reference: https://github.com/luvit/luv/blob/master/docs.md -Examples: https://github.com/luvit/luv/tree/master/examples +Internally, `vim.loop` wraps the "luv" Lua bindings for the LibUV library; +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 @@ -634,53 +630,53 @@ 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 > - au TextYankPost * silent! lua vim.highlight.on_yank() + 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 > - au TextYankPost * silent! lua vim.highlight.on_yank {higroup="IncSearch", timeout=150} + au TextYankPost * silent! lua vim.highlight.on_yank {higroup="IncSearch", timeout=150} < If you want to exclude visual selections from highlighting on yank, use > - au TextYankPost * silent! lua vim.highlight.on_yank {on_visual=false} + au TextYankPost * silent! lua vim.highlight.on_yank {on_visual=false} < vim.highlight.on_yank({opts}) *vim.highlight.on_yank()* - Highlights the yanked text. The fields of the optional dict {opts} - control the highlight: - - {higroup} highlight group for yanked region (default |hl-IncSearch|) - - {timeout} time in ms before highlight is cleared (default `150`) - - {on_macro} highlight when executing macro (default `false`) - - {on_visual} highlight when yanking visual selection (default `true`) - - {event} event structure (default |v:event|) + Highlights the yanked text. The fields of the optional dict {opts} + control the highlight: + - {higroup} highlight group for yanked region (default |hl-IncSearch|) + - {timeout} time in ms before highlight is cleared (default `150`) + - {on_macro} highlight when executing macro (default `false`) + - {on_visual} highlight when yanking visual selection (default `true`) + - {event} event structure (default |v:event|) vim.highlight.range({bufnr}, {ns}, {hlgroup}, {start}, {finish}, {opts}) *vim.highlight.range()* - Apply highlight group to range of text. - - Parameters: ~ - {bufnr} buffer number - {ns} namespace for highlights - {hlgroup} highlight group name - {start} starting position (tuple {line,col}) - {finish} finish position (tuple {line,col}) - {opts} optional parameters: - • `regtype`: type of range (characterwise, linewise, - or blockwise, see |setreg|), default `'v'` - • `inclusive`: range includes end position, - default `false` - • `priority`: priority of highlight, default - `vim.highlight.user` (see below) + Apply highlight group to range of text. + + Parameters: ~ + {bufnr} buffer number + {ns} namespace for highlights + {hlgroup} highlight group name + {start} starting position (tuple {line,col}) + {finish} finish position (tuple {line,col}) + {opts} optional parameters: + • `regtype`: type of range (characterwise, linewise, + or blockwise, see |setreg|), default `'v'` + • `inclusive`: range includes end position, + default `false` + • `priority`: priority of highlight, default + `vim.highlight.user` (see below) vim.highlight.priorities *vim.highlight.priorities* - Table with default priorities used for highlighting: - • `syntax`: `50`, used for standard syntax highlighting - • `treesitter`: `100`, used for tree-sitter-based highlighting - • `diagnostics`: `150`, used for code analysis such as diagnostics - • `user`: `200`, used for user-triggered highlights such as LSP - document symbols or `on_yank` autocommands + Table with default priorities used for highlighting: + • `syntax`: `50`, used for standard syntax highlighting + • `treesitter`: `100`, used for tree-sitter-based highlighting + • `diagnostics`: `150`, used for code analysis such as diagnostics + • `user`: `200`, used for user-triggered highlights such as LSP document + symbols or `on_yank` autocommands ------------------------------------------------------------------------------ VIM.REGEX *lua-regex* @@ -689,94 +685,89 @@ Vim regexes can be used directly from lua. Currently they only allow matching within a single line. vim.regex({re}) *vim.regex()* - Parse the Vim regex {re} and return a regex object. Regexes are - "magic" and case-sensitive by default, regardless of 'magic' and - 'ignorecase'. They can be controlled with flags, see |/magic| and - |/ignorecase|. + Parse the Vim regex {re} and return a regex object. Regexes are "magic" + and case-sensitive by default, regardless of 'magic' and 'ignorecase'. + They can be controlled with flags, see |/magic| and |/ignorecase|. Methods on the regex object: regex:match_str({str}) *regex:match_str()* - Match the string against the regex. If the string should match the - regex precisely, surround the regex with `^` and `$`. - If the was a match, the byte indices for the beginning and end of - the match is returned. When there is no match, `nil` is returned. - As any integer is truth-y, `regex:match()` can be directly used - as a condition in an if-statement. + Match the string against the regex. If the string should match the regex + precisely, surround the regex with `^` and `$`. If the was a match, the + byte indices for the beginning and end of the match is returned. When + there is no match, `nil` is returned. As any integer is truth-y, + `regex:match()` can be directly used as a condition in an if-statement. regex:match_line({bufnr}, {line_idx} [, {start}, {end}]) *regex:match_line()* - Match line {line_idx} (zero-based) in buffer {bufnr}. If {start} and - {end} are supplied, match only this byte index range. Otherwise see - |regex:match_str()|. If {start} is used, then the returned byte - indices will be relative {start}. + Match line {line_idx} (zero-based) in buffer {bufnr}. If {start} and {end} + are supplied, match only this byte index range. Otherwise see + |regex:match_str()|. If {start} is used, then the returned byte indices + will be relative {start}. ------------------------------------------------------------------------------ VIM.DIFF *lua-diff* 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: > - - vim.diff('a\n', 'b\nc\n') - --> - @@ -1 +1,2 @@ - -a - +b - +c - - vim.diff('a\n', 'b\nc\n', {result_type = 'indices'}) - --> - { - {1, 1, 1, 2} - } + Run diff on strings {a} and {b}. Any indices returned by this function, + either directly or via callback arguments, are 1-based. + + Examples: > + + vim.diff('a\n', 'b\nc\n') + --> + @@ -1 +1,2 @@ + -a + +b + +c + + vim.diff('a\n', 'b\nc\n', {result_type = 'indices'}) + --> + { + {1, 1, 1, 2} + } < - Parameters: ~ - {a} First string to compare - {b} Second string to compare - {opts} Optional parameters: - • `on_hunk` (callback): - Invoked for each hunk in the diff. Return a - negative number to cancel the callback for any - remaining hunks. - Args: - • `start_a` (integer): Start line of hunk in {a}. - • `count_a` (integer): Hunk size in {a}. - • `start_b` (integer): Start line of hunk in {b}. - • `count_b` (integer): Hunk size in {b}. - • `result_type` (string): Form of the returned diff: - • "unified": (default) String in unified format. - • "indices": Array of hunk locations. - Note: This option is ignored if `on_hunk` is - used. - • `algorithm` (string): - Diff algorithm to use. Values: - • "myers" the default algorithm - • "minimal" spend extra time to generate the - smallest possible diff - • "patience" patience diff algorithm - • "histogram" histogram diff algorithm - • `ctxlen` (integer): Context length - • `interhunkctxlen` (integer): - Inter hunk context length - • `ignore_whitespace` (boolean): - Ignore whitespace - • `ignore_whitespace_change` (boolean): - Ignore whitespace change - • `ignore_whitespace_change_at_eol` (boolean) - Ignore whitespace change at end-of-line. - • `ignore_cr_at_eol` (boolean) - Ignore carriage return at end-of-line - • `ignore_blank_lines` (boolean) - Ignore blank lines - • `indent_heuristic` (boolean): - Use the indent heuristic for the internal - diff library. - - Return: ~ - See {opts.result_type}. nil if {opts.on_hunk} is given. + Parameters: ~ + {a} First string to compare + {b} Second string to compare + {opts} Optional parameters: + • `on_hunk` (callback): + Invoked for each hunk in the diff. Return a negative number + to cancel the callback for any remaining hunks. + Args: + • `start_a` (integer): Start line of hunk in {a}. + • `count_a` (integer): Hunk size in {a}. + • `start_b` (integer): Start line of hunk in {b}. + • `count_b` (integer): Hunk size in {b}. + • `result_type` (string): Form of the returned diff: + • "unified": (default) String in unified format. + • "indices": Array of hunk locations. + Note: This option is ignored if `on_hunk` is used. + • `algorithm` (string): + Diff algorithm to use. Values: + • "myers" the default algorithm + • "minimal" spend extra time to generate the + smallest possible diff + • "patience" patience diff algorithm + • "histogram" histogram diff algorithm + • `ctxlen` (integer): Context length + • `interhunkctxlen` (integer): + Inter hunk context length + • `ignore_whitespace` (boolean): + Ignore whitespace + • `ignore_whitespace_change` (boolean): + Ignore whitespace change + • `ignore_whitespace_change_at_eol` (boolean) + Ignore whitespace change at end-of-line. + • `ignore_cr_at_eol` (boolean) + Ignore carriage return at end-of-line + • `ignore_blank_lines` (boolean) + Ignore blank lines + • `indent_heuristic` (boolean): + Use the indent heuristic for the internal + diff library. + + Return: ~ + See {opts.result_type}. nil if {opts.on_hunk} is given. ------------------------------------------------------------------------------ VIM.MPACK *lua-mpack* @@ -785,115 +776,130 @@ The *vim.mpack* module provides encoding and decoding of Lua objects to and from msgpack-encoded strings. Supports |vim.NIL| and |vim.empty_dict()|. vim.mpack.encode({obj}) *vim.mpack.encode* - Encodes (or "packs") Lua object {obj} as msgpack in a Lua string. + Encodes (or "packs") Lua object {obj} as msgpack in a Lua string. vim.mpack.decode({str}) *vim.mpack.decode* - Decodes (or "unpacks") the msgpack-encoded {str} to a Lua object. + Decodes (or "unpacks") the msgpack-encoded {str} to a Lua object. ------------------------------------------------------------------------------ VIM.SPELL *lua-spell* vim.spell.check({str}) *vim.spell.check()* - Check {str} for spelling errors. Similar to the Vimscript function - |spellbadword()|. + Check {str} for spelling errors. Similar to the Vimscript function + |spellbadword()|. - Note: The behaviour of this function is dependent on: 'spelllang', - 'spellfile', 'spellcapcheck' and 'spelloptions' which can all be - local to the buffer. Consider calling this with |nvim_buf_call()|. + Note: The behaviour of this function is dependent on: 'spelllang', + 'spellfile', 'spellcapcheck' and 'spelloptions' which can all be local to + the buffer. Consider calling this with |nvim_buf_call()|. - Example: > + Example: > - vim.spell.check("the quik brown fox") - --> - { - {'quik', 'bad', 4} - } + vim.spell.check("the quik brown fox") + --> + { + {'quik', 'bad', 4} + } < - Parameters: ~ - {str} String to spell check. - - Return: ~ - List of tuples with three items: - - The badly spelled word. - - The type of the spelling error: - "bad" spelling mistake - "rare" rare word - "local" word only valid in another region - "caps" word should start with Capital - - The position in {str} where the word begins. + Parameters: ~ + {str} String to spell check. + + Return: ~ + List of tuples with three items: + - The badly spelled word. + - The type of the spelling error: + "bad" spelling mistake + "rare" rare word + "local" word only valid in another region + "caps" word should start with Capital + - The position in {str} where the word begins. ------------------------------------------------------------------------------ VIM *lua-builtin* vim.api.{func}({...}) *vim.api* - Invokes Nvim |API| function {func} with arguments {...}. - Example: call the "nvim_get_current_line()" API function: > - print(tostring(vim.api.nvim_get_current_line())) + Invokes Nvim |API| function {func} with arguments {...}. + Example: call the "nvim_get_current_line()" API function: > + print(tostring(vim.api.nvim_get_current_line())) vim.version() *vim.version* - Gets the version of the current Nvim build. + Gets the version of the current Nvim build. vim.in_fast_event() *vim.in_fast_event()* - Returns true if the code is executing as part of a "fast" event - handler, where most of the API is disabled. These are low-level events - (e.g. |lua-loop-callbacks|) which can be invoked whenever Nvim polls - for input. When this is `false` most API functions are callable (but - may be subject to other restrictions such as |textlock|). + Returns true if the code is executing as part of a "fast" event handler, + where most of the API is disabled. These are low-level events (e.g. + |lua-loop-callbacks|) which can be invoked whenever Nvim polls for input. + When this is `false` most API functions are callable (but may be subject + to other restrictions such as |textlock|). vim.NIL *vim.NIL* - Special value representing NIL in |RPC| and |v:null| in Vimscript - conversion, and similar cases. Lua `nil` cannot be used as part of - a Lua table representing a Dictionary or Array, because it is - treated as missing: `{"foo", nil}` is the same as `{"foo"}`. + Special value representing NIL in |RPC| and |v:null| in Vimscript + conversion, and similar cases. Lua `nil` cannot be used as part of a Lua + table representing a Dictionary or Array, because it is treated as + missing: `{"foo", nil}` is the same as `{"foo"}`. vim.empty_dict() *vim.empty_dict()* - Creates a special empty table (marked with a metatable), which Nvim - converts to an empty dictionary when translating Lua values to - Vimscript or API types. Nvim by default converts an empty table `{}` - without this metatable to an list/array. + Creates a special empty table (marked with a metatable), which Nvim to an + empty dictionary when translating Lua values to Vimscript or API types. + Nvim by default converts an empty table `{}` without this metatable to an + list/array. - Note: If numeric keys are present in the table, Nvim ignores the - metatable marker and converts the dict to a list/array anyway. + Note: If numeric keys are present in the table, Nvim ignores the metatable + marker and converts the dict to a list/array anyway. vim.rpcnotify({channel}, {method} [, {args}...]) *vim.rpcnotify()* - Sends {event} to {channel} via |RPC| and returns immediately. If - {channel} is 0, the event is broadcast to all channels. + Sends {event} to {channel} via |RPC| and returns immediately. If {channel} + is 0, the event is broadcast to all channels. - This function also works in a fast callback |lua-loop-callbacks|. + This function also works in a fast callback |lua-loop-callbacks|. vim.rpcrequest({channel}, {method} [, {args}...]) *vim.rpcrequest()* - Sends a request to {channel} to invoke {method} via |RPC| and blocks - until a response is received. + Sends a request to {channel} to invoke {method} via |RPC| and blocks until + a response is received. - Note: NIL values as part of the return value is represented as - |vim.NIL| special value + Note: NIL values as part of the return value is represented as |vim.NIL| + special value vim.stricmp({a}, {b}) *vim.stricmp()* - Compares strings case-insensitively. Returns 0, 1 or -1 if strings - are equal, {a} is greater than {b} or {a} is lesser than {b}, - respectively. + Compares strings case-insensitively. Returns 0, 1 or -1 if strings are + equal, {a} is greater than {b} or {a} is lesser than {b}, respectively. vim.str_utfindex({str} [, {index}]) *vim.str_utfindex()* - Convert byte index to UTF-32 and UTF-16 indices. If {index} is not - supplied, the length of the string is used. All indices are zero-based. - Returns two values: the UTF-32 and UTF-16 indices respectively. + Convert byte index to UTF-32 and UTF-16 indices. If {index} is not + supplied, the length of the string is used. All indices are zero-based. + Returns two values: the UTF-32 and UTF-16 indices respectively. - Embedded NUL bytes are treated as terminating the string. Invalid - UTF-8 bytes, and embedded surrogates are counted as one code - point each. An {index} in the middle of a UTF-8 sequence is rounded - upwards to the end of that sequence. + Embedded NUL bytes are treated as terminating the string. Invalid UTF-8 + bytes, and embedded surrogates are counted as one code point each. An + {index} in the middle of a UTF-8 sequence is rounded upwards to the end of + that sequence. vim.str_byteindex({str}, {index} [, {use_utf16}]) *vim.str_byteindex()* - Convert UTF-32 or UTF-16 {index} to byte index. If {use_utf16} is not - supplied, it defaults to false (use UTF-32). Returns the byte index. + Convert UTF-32 or UTF-16 {index} to byte index. If {use_utf16} is not + supplied, it defaults to false (use UTF-32). Returns the byte index. + + Invalid UTF-8 and NUL is treated like by |vim.str_byteindex()|. + An {index} in the middle of a UTF-16 sequence is rounded upwards to + the end of that sequence. - Invalid UTF-8 and NUL is treated like by |vim.str_byteindex()|. - An {index} in the middle of a UTF-16 sequence is rounded upwards to - the end of that sequence. +vim.iconv({str}, {from}, {to}[, {opts}]) *vim.iconv()* + The result is a String, which is the text {str} converted from + encoding {from} to encoding {to}. When the conversion fails `nil` is + returned. When some characters could not be converted they + are replaced with "?". + The encoding names are whatever the iconv() library function + can accept, see ":Man 3 iconv". + + Parameters: ~ + {str} (string) Text to convert + {from} (string) Encoding of {str} + {to} (string) Target encoding + + Returns: ~ + Converted string if conversion succeeds, `nil` otherwise. vim.schedule({callback}) *vim.schedule()* - Schedules {callback} to be invoked soon by the main event-loop. Useful - to avoid |textlock| or other temporary restrictions. + Schedules {callback} to be invoked soon by the main event-loop. Useful + to avoid |textlock| or other temporary restrictions. vim.defer_fn({fn}, {timeout}) *vim.defer_fn* @@ -911,11 +917,11 @@ vim.defer_fn({fn}, {timeout}) *vim.defer_fn* |vim.loop|.new_timer() object vim.wait({time} [, {callback}, {interval}, {fast_only}]) *vim.wait()* - Wait for {time} in milliseconds until {callback} returns `true`. + Wait for {time} in milliseconds until {callback} returns `true`. - Executes {callback} immediately and at approximately {interval} - milliseconds (default 200). Nvim still processes other events during - this time. + Executes {callback} immediately and at approximately {interval} + milliseconds (default 200). Nvim still processes other events during + this time. Parameters: ~ {time} Number of milliseconds to wait @@ -962,49 +968,48 @@ vim.wait({time} [, {callback}, {interval}, {fast_only}]) *vim.wait()* < vim.type_idx *vim.type_idx* - Type index for use in |lua-special-tbl|. Specifying one of the values - from |vim.types| allows typing the empty table (it is unclear whether - empty Lua table represents empty list or empty array) and forcing - integral numbers to be |Float|. See |lua-special-tbl| for more - details. + Type index for use in |lua-special-tbl|. Specifying one of the values from + |vim.types| allows typing the empty table (it is unclear whether empty Lua + table represents empty list or empty array) and forcing integral numbers + to be |Float|. See |lua-special-tbl| for more details. vim.val_idx *vim.val_idx* - Value index for tables representing |Float|s. A table representing - floating-point value 1.0 looks like this: > - { - [vim.type_idx] = vim.types.float, - [vim.val_idx] = 1.0, - } -< See also |vim.type_idx| and |lua-special-tbl|. + Value index for tables representing |Float|s. A table representing + floating-point value 1.0 looks like this: > + { + [vim.type_idx] = vim.types.float, + [vim.val_idx] = 1.0, + } +< See also |vim.type_idx| and |lua-special-tbl|. vim.types *vim.types* - Table with possible values for |vim.type_idx|. Contains two sets of - key-value pairs: first maps possible values for |vim.type_idx| to - human-readable strings, second maps human-readable type names to - values for |vim.type_idx|. Currently contains pairs for `float`, - `array` and `dictionary` types. - - Note: One must expect that values corresponding to `vim.types.float`, - `vim.types.array` and `vim.types.dictionary` fall under only two - following assumptions: - 1. Value may serve both as a key and as a value in a table. Given the - properties of Lua tables this basically means “value is not `nil`”. - 2. For each value in `vim.types` table `vim.types[vim.types[value]]` - is the same as `value`. - No other restrictions are put on types, and it is not guaranteed that - values corresponding to `vim.types.float`, `vim.types.array` and - `vim.types.dictionary` will not change or that `vim.types` table will - only contain values for these three types. + Table with possible values for |vim.type_idx|. Contains two sets of + key-value pairs: first maps possible values for |vim.type_idx| to + human-readable strings, second maps human-readable type names to values + for |vim.type_idx|. Currently contains pairs for `float`, `array` and + `dictionary` types. + + Note: One must expect that values corresponding to `vim.types.float`, + `vim.types.array` and `vim.types.dictionary` fall under only two following + assumptions: + 1. Value may serve both as a key and as a value in a table. Given the + properties of Lua tables this basically means “value is not `nil`”. + 2. For each value in `vim.types` table `vim.types[vim.types[value]]` is the + same as `value`. + No other restrictions are put on types, and it is not guaranteed that + values corresponding to `vim.types.float`, `vim.types.array` and + `vim.types.dictionary` will not change or that `vim.types` table will only + contain values for these three types. *log_levels* *vim.log.levels* Log levels are one of the values defined in `vim.log.levels`: - vim.log.levels.DEBUG - vim.log.levels.ERROR - vim.log.levels.INFO - vim.log.levels.TRACE - vim.log.levels.WARN - vim.log.levels.OFF + vim.log.levels.DEBUG + vim.log.levels.ERROR + vim.log.levels.INFO + vim.log.levels.TRACE + vim.log.levels.WARN + vim.log.levels.OFF ------------------------------------------------------------------------------ LUA-VIMSCRIPT BRIDGE *lua-vimscript* @@ -1014,32 +1019,32 @@ editor commands and options. 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: > - vim.fn[func]({...}) + Invokes |vim-function| or |user-function| {func} with arguments {...}. + See also |vim.fn|. + Equivalent to: > + vim.fn[func]({...}) vim.cmd({command}) - See |vim.cmd()|. + See |vim.cmd()|. vim.fn.{func}({...}) *vim.fn* - Invokes |vim-function| or |user-function| {func} with arguments {...}. - To call autoload functions, use the syntax: > - vim.fn['some#function']({...}) + Invokes |vim-function| or |user-function| {func} with arguments {...}. + To call autoload functions, use the syntax: > + vim.fn['some#function']({...}) < - Unlike vim.api.|nvim_call_function()| this converts directly between Vim - objects and Lua objects. If the Vim function returns a float, it will - be represented directly as a Lua number. Empty lists and dictionaries - both are represented by an empty table. + Unlike vim.api.|nvim_call_function()| this converts directly between Vim + objects and Lua objects. If the Vim function returns a float, it will be + represented directly as a Lua number. Empty lists and dictionaries both + are represented by an empty table. - Note: |v:null| values as part of the return value is represented as - |vim.NIL| special value + Note: |v:null| values as part of the return value is represented as + |vim.NIL| special value - Note: vim.fn keys are generated lazily, thus `pairs(vim.fn)` only - enumerates functions that were called at least once. + Note: vim.fn keys are generated lazily, thus `pairs(vim.fn)` only + enumerates functions that were called at least once. - Note: The majority of functions cannot run in |api-fast| callbacks with some - undocumented exceptions which are allowed. + Note: The majority of functions cannot run in |api-fast| callbacks with some + undocumented exceptions which are allowed. *lua-vim-variables* The Vim editor global dictionaries |g:| |w:| |b:| |t:| |v:| can be accessed @@ -1055,48 +1060,44 @@ Example: > vim.b[2].foo = 6 -- Set b:foo for buffer 2 < vim.g *vim.g* - Global (|g:|) editor variables. - Key with no value returns `nil`. + Global (|g:|) editor variables. + Key with no value returns `nil`. vim.b *vim.b* - Buffer-scoped (|b:|) variables for the current buffer. - Invalid or unset key returns `nil`. Can be indexed with - an integer to access variables for a specific buffer. + Buffer-scoped (|b:|) variables for the current buffer. + Invalid or unset key returns `nil`. Can be indexed with + an integer to access variables for a specific buffer. vim.w *vim.w* - Window-scoped (|w:|) variables for the current window. - Invalid or unset key returns `nil`. Can be indexed with - an integer to access variables for a specific window. + Window-scoped (|w:|) variables for the current window. + Invalid or unset key returns `nil`. Can be indexed with + an integer to access variables for a specific window. vim.t *vim.t* - Tabpage-scoped (|t:|) variables for the current tabpage. - Invalid or unset key returns `nil`. Can be indexed with - an integer to access variables for a specific tabpage. + Tabpage-scoped (|t:|) variables for the current tabpage. + Invalid or unset key returns `nil`. Can be indexed with + an integer to access variables for a specific tabpage. vim.v *vim.v* - |v:| variables. - Invalid or unset key returns `nil`. + |v:| variables. + Invalid or unset key returns `nil`. 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: > - vim.env.FOO = 'bar' - print(vim.env.TERM) + Environment variables defined in the editor session. + See |expand-env| and |:let-environment| for the Vimscript behavior. + Invalid or unset key returns `nil`. + Example: > + vim.env.FOO = 'bar' + print(vim.env.TERM) < + *lua-options* *lua-vim-options* - *lua-vim-opt* *lua-vim-set* - *lua-vim-optlocal* *lua-vim-setlocal* -In Vimscript, there is a way to set options |set-option|. In Lua, the -corresponding method is `vim.opt`. - -`vim.opt` provides several conveniences for setting and controlling options -from within Lua. +Vim options can be accessed through |vim.o|, which behaves like Vimscript +|:set|. Examples: ~ @@ -1105,62 +1106,145 @@ from within Lua. `set number` In Lua: - `vim.opt.number = true` + `vim.o.number = true` - To set an array of values: + To set a string value: In Vimscript: `set wildignore=*.o,*.a,__pycache__` - In Lua, there are two ways you can do this now. One is very similar to - the Vimscript form: - `vim.opt.wildignore = '*.o,*.a,__pycache__'` + In Lua: + `vim.o.wildignore = '*.o,*.a,__pycache__'` + +Similarly, there exist |vim.bo| and |vim.wo| for setting buffer-local and +window-local options, respectively, similarly to |:setlocal|. There is also +|vim.go| that only sets the global value of a |global-local| option, see +|:setglobal|. The following table summarizes this relation. + + lua command global_value local_value ~ +vim.o :set set set +vim.bo/vim.wo :setlocal - set +vim.go :setglobal set - + - However, vim.opt also supports a more elegent way of setting - list-style options by using lua tables: +vim.o *vim.o* + Get or set editor options, like |:set|. Invalid key is an error. + + Example: > + vim.o.cmdheight = 4 + print(vim.o.columns) + print(vim.o.foo) -- error: invalid key +< +vim.go *vim.go* + Get or set an |option|. Invalid key is an error. + + This is a wrapper around |nvim_set_option_value()| and + |nvim_get_option_value()|. + + NOTE: This is different from |vim.o| because this ONLY sets the global + option, which generally produces confusing behavior for options with + |global-local| values. + + Example: > + vim.go.cmdheight = 4 + print(vim.go.columns) + print(vim.go.bar) -- error: invalid key +< +vim.bo[{bufnr}] *vim.bo* + Get or set buffer-scoped |local-options| for the buffer with number {bufnr}. + If [{bufnr}] is omitted, use the current buffer. Invalid {bufnr} or key is + an error. + + This is a wrapper around |nvim_set_option_value()| and + |nvim_get_option_value()| with `opts = {scope = local, buf = bufnr}` . + + Example: > + local bufnr = vim.api.nvim_get_current_buf() + vim.bo[bufnr].buflisted = true -- same as vim.bo.buflisted = true + print(vim.bo.comments) + print(vim.bo.baz) -- error: invalid key +< +vim.wo[{winid}] *vim.wo* + Get or set window-scoped |local-options| for the window with handle {winid}. + If [{winid}] is omitted, use the current window. Invalid {winid} or key + is an error. + + This is a wrapper around |nvim_set_option_value()| and + |nvim_get_option_value()| with `opts = {scope = local, win = winid}` . + + Example: > + local winid = vim.api.nvim_get_current_win() + vim.wo[winid].number = true -- same as vim.wo.number = true + print(vim.wo.foldmarker) + print(vim.wo.quux) -- error: invalid key +< + + + + *lua-vim-opt* + *lua-vim-optlocal* + *lua-vim-optglobal* + *vim.opt* + + +A special interface |vim.opt| exists for conveniently interacting with list- +and map-style option from Lua: It allows accessing them as Lua tables and +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: > - -- vim.opt supports appending options via the "+" operator - vim.opt.wildignore = vim.opt.wildignore + { "*.pyc", "node_modules" } - - -- or using the `:append(...)` method vim.opt.wildignore:append { "*.pyc", "node_modules" } < To replicate the behavior of |:set^=|, use: > - -- vim.opt supports prepending options via the "^" operator - vim.opt.wildignore = vim.opt.wildignore ^ { "new_first_value" } - - -- or using the `:prepend(...)` method vim.opt.wildignore:prepend { "new_first_value" } < To replicate the behavior of |:set-=|, use: > - -- vim.opt supports removing options via the "-" operator - vim.opt.wildignore = vim.opt.wildignore - { "node_modules" } - - -- or using the `:remove(...)` method vim.opt.wildignore:remove { "node_modules" } < - To set a map of values: + The following methods of setting a map-style option are equivalent: In Vimscript: `set listchars=space:_,tab:>~` - In Lua: + In Lua using `vim.o`: + `vim.o.listchars = 'space:_,tab:>~'` + + In Lua using `vim.opt`: `vim.opt.listchars = { space = '_', tab = '>~' }` +Note that |vim.opt| returns an `Option` object, not the value of the option, +which is accessed through |Option: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 any of the above examples, to replicate the behavior |setlocal|, use `vim.opt_local`. Additionally, to replicate the behavior of |setglobal|, use `vim.opt_global`. - *vim.opt* - -|vim.opt| returns an Option object. -For example: `local listchar_object = vim.opt.listchars` - -An `Option` has the following methods: *vim.opt:get()* @@ -1173,7 +1257,7 @@ Option:get() the values as entries in the array: > vim.cmd [[set wildignore=*.pyc,*.o]] - print(vim.inspect(vim.opt.wildignore:get())) + vim.pretty_print(vim.opt.wildignore:get()) -- { "*.pyc", "*.o", } for _, ignore_pattern in ipairs(vim.opt.wildignore:get()) do @@ -1186,7 +1270,7 @@ Option:get() the names as keys and the values as entries: > vim.cmd [[set listchars=space:_,tab:>~]] - print(vim.inspect(vim.opt.listchars:get())) + vim.pretty_print(vim.opt.listchars:get()) -- { space = "_", tab = ">~", } for char, representation in pairs(vim.opt.listchars:get()) do @@ -1197,7 +1281,7 @@ Option:get() as keys and `true` as entries. > vim.cmd [[set formatoptions=njtcroql]] - print(vim.inspect(vim.opt.formatoptions:get())) + vim.pretty_print(vim.opt.formatoptions:get()) -- { n = true, j = true, c = true, ... } local format_opts = vim.opt.formatoptions:get() @@ -1233,892 +1317,798 @@ Option:remove(value) `vim.opt.wildignore = vim.opt.wildignore - '*.pyc'` -In general, using `vim.opt` will provide the expected result when the user is -used to interacting with editor |options| via `set`. There are still times -where the user may want to set particular options via a shorthand in Lua, -which is where |vim.o|, |vim.bo|, |vim.wo|, and |vim.go| come into play. - -The behavior of |vim.o|, |vim.bo|, |vim.wo|, and |vim.go| is designed to -follow that of |:set|, |:setlocal|, and |:setglobal| which can be seen in the -table below: - - lua command global_value local_value ~ -vim.o :set set set -vim.bo/vim.wo :setlocal - set -vim.go :setglobal set - - -vim.o *vim.o* - Get or set editor options, like |:set|. Invalid key is an error. - Example: > - vim.o.cmdheight = 4 - print(vim.o.columns) -< -vim.go *vim.go* - Get or set an |option|. Invalid key is an error. - - This is a wrapper around |nvim_set_option()| and |nvim_get_option()|. - - NOTE: This is different than |vim.o| because this ONLY sets the global - option, which generally produces confusing behavior for options with - |global-local| values. - - Example: > - vim.go.cmdheight = 4 -< -vim.bo *vim.bo* - Get or set buffer-scoped |local-options|. Invalid key is an error. - - This is a wrapper around |nvim_buf_set_option()| and - |nvim_buf_get_option()|. - - Example: > - vim.bo.buflisted = true - print(vim.bo.comments) -< -vim.wo *vim.wo* - Get or set window-scoped |local-options|. Invalid key is an error. - - This is a wrapper around |nvim_win_set_option()| and - |nvim_win_get_option()|. - - Example: > - vim.wo.cursorcolumn = true - print(vim.wo.foldmarker) -< ============================================================================== Lua module: vim *lua-vim* cmd({command}) *vim.cmd()* - Execute Vim script commands. - - Note that `vim.cmd` can be indexed with a command name to - return a callable function to the command. - - Example: > - - vim.cmd('echo 42') - vim.cmd([[ - augroup My_group - autocmd! - autocmd FileType c setlocal cindent - augroup END - ]]) - - -- Ex command :echo "foo" - -- Note string literals need to be double quoted. - vim.cmd('echo "foo"') - vim.cmd { cmd = 'echo', args = { '"foo"' } } - vim.cmd.echo({ args = { '"foo"' } }) - vim.cmd.echo('"foo"') - - -- Ex command :write! myfile.txt - vim.cmd('write! myfile.txt') - vim.cmd { cmd = 'write', args = { "myfile.txt" }, bang = true } - vim.cmd.write { args = { "myfile.txt" }, bang = true } - vim.cmd.write { "myfile.txt", bang = true } - - -- Ex command :colorscheme blue - vim.cmd('colorscheme blue') - vim.cmd.colorscheme('blue') + Execute Vim script commands. + + Note that `vim.cmd` can be indexed with a command name to return a + callable function to the command. + + Example: > + + vim.cmd('echo 42') + vim.cmd([[ + augroup My_group + autocmd! + autocmd FileType c setlocal cindent + augroup END + ]]) + + -- Ex command :echo "foo" + -- Note string literals need to be double quoted. + vim.cmd('echo "foo"') + vim.cmd { cmd = 'echo', args = { '"foo"' } } + vim.cmd.echo({ args = { '"foo"' } }) + vim.cmd.echo('"foo"') + + -- Ex command :write! myfile.txt + vim.cmd('write! myfile.txt') + vim.cmd { cmd = 'write', args = { "myfile.txt" }, bang = true } + vim.cmd.write { args = { "myfile.txt" }, bang = true } + vim.cmd.write { "myfile.txt", bang = true } + + -- Ex command :colorscheme blue + vim.cmd('colorscheme blue') + vim.cmd.colorscheme('blue') < - Parameters: ~ - {command} string|table Command(s) to execute. If a - string, executes multiple lines of Vim script - at once. In this case, it is an alias to - |nvim_exec()|, where `output` is set to false. - Thus it works identical to |:source|. If a - table, executes a single command. In this case, - it is an alias to |nvim_cmd()| where `opts` is - empty. + Parameters: ~ + {command} string|table Command(s) to execute. If a string, executes + multiple lines of Vim script at once. In this case, it is + an alias to |nvim_exec()|, where `output` is set to false. + Thus it works identical to |:source|. If a table, executes + a single command. In this case, it is an alias to + |nvim_cmd()| where `opts` is empty. - See also: ~ - |ex-cmd-index| + See also: ~ + |ex-cmd-index| *vim.connection_failure_errmsg()* connection_failure_errmsg({consequence}) - TODO: Documentation + TODO: Documentation defer_fn({fn}, {timeout}) *vim.defer_fn()* - Defers calling `fn` until `timeout` ms passes. + Defers calling `fn` until `timeout` ms passes. - Use to do a one-shot timer that calls `fn` Note: The {fn} is |schedule_wrap|ped automatically, so API - functions are safe to call. + Use to do a one-shot timer that calls `fn` Note: The {fn} is |schedule_wrap|ped automatically, so API functions are + safe to call. - Parameters: ~ - {fn} Callback to call once `timeout` expires - {timeout} Number of milliseconds to wait before calling - `fn` + Parameters: ~ + {fn} Callback to call once `timeout` expires + {timeout} Number of milliseconds to wait before calling `fn` - Return: ~ - timer luv timer object + Return: ~ + timer luv timer object *vim.deprecate()* deprecate({name}, {alternative}, {version}, {plugin}, {backtrace}) - Display a deprecation notification to the user. - - Parameters: ~ - {name} string Deprecated function. - {alternative} (string|nil) Preferred alternative - function. - {version} string Version in which the deprecated - function will be removed. - {plugin} string|nil Plugin name that the function - will be removed from. Defaults to "Nvim". - {backtrace} boolean|nil Prints backtrace. Defaults to - true. + Display a deprecation notification to the user. + + Parameters: ~ + {name} string Deprecated function. + {alternative} (string|nil) Preferred alternative function. + {version} string Version in which the deprecated function will be + removed. + {plugin} string|nil Plugin name that the function will be + removed from. Defaults to "Nvim". + {backtrace} boolean|nil Prints backtrace. Defaults to true. inspect({object}, {options}) *vim.inspect()* - Return a human-readable representation of the given object. + Return a human-readable representation of the given object. - See also: ~ - https://github.com/kikito/inspect.lua - https://github.com/mpeterv/vinspect + See also: ~ + https://github.com/kikito/inspect.lua + https://github.com/mpeterv/vinspect notify({msg}, {level}, {opts}) *vim.notify()* - Display a notification to the user. + Display a notification to the user. - This function can be overridden by plugins to display - notifications using a custom provider (such as the system - notification provider). By default, writes to |:messages|. + This function can be overridden by plugins to display notifications using + a custom provider (such as the system notification provider). By default, + writes to |:messages|. - Parameters: ~ - {msg} (string) Content of the notification to show to - the user. - {level} (number|nil) One of the values from - |vim.log.levels|. - {opts} (table|nil) Optional parameters. Unused by - default. + Parameters: ~ + {msg} (string) Content of the notification to show to the user. + {level} (number|nil) One of the values from |vim.log.levels|. + {opts} (table|nil) Optional parameters. Unused by default. notify_once({msg}, {level}, {opts}) *vim.notify_once()* - Display a notification only one time. + Display a notification only one time. - Like |vim.notify()|, but subsequent calls with the same - message will not display a notification. + Like |vim.notify()|, but subsequent calls with the same message will not + display a notification. - Parameters: ~ - {msg} (string) Content of the notification to show to - the user. - {level} (number|nil) One of the values from - |vim.log.levels|. - {opts} (table|nil) Optional parameters. Unused by - default. + Parameters: ~ + {msg} (string) Content of the notification to show to the user. + {level} (number|nil) One of the values from |vim.log.levels|. + {opts} (table|nil) Optional parameters. Unused by default. - Return: ~ - (boolean) true if message was displayed, else false + Return: ~ + (boolean) true if message was displayed, else false on_key({fn}, {ns_id}) *vim.on_key()* - Adds Lua function {fn} with namespace id {ns_id} as a listener - to every, yes every, input key. + Adds Lua function {fn} with namespace id {ns_id} as a listener to every, + yes every, input key. - The Nvim command-line option |-w| is related but does not - support callbacks and cannot be toggled dynamically. + The Nvim command-line option |-w| is related but does not support + callbacks and cannot be toggled dynamically. - Note: - {fn} will not be cleared by |nvim_buf_clear_namespace()| + Note: + {fn} will not be cleared by |nvim_buf_clear_namespace()| - Note: - {fn} will receive the keys after mappings have been - evaluated + Note: + {fn} will receive the keys after mappings have been evaluated - Parameters: ~ - {fn} function: Callback function. It should take one - string argument. On each key press, Nvim passes - the key char to fn(). |i_CTRL-V| If {fn} is nil, - it removes the callback for the associated - {ns_id} - {ns_id} number? Namespace ID. If nil or 0, generates and - returns a new |nvim_create_namespace()| id. + Parameters: ~ + {fn} function: Callback function. It should take one string + argument. On each key press, Nvim passes the key char to + fn(). |i_CTRL-V| If {fn} is nil, it removes the callback for + the associated {ns_id} + {ns_id} number? Namespace ID. If nil or 0, generates and returns a + new |nvim_create_namespace()| id. - Return: ~ - (number) Namespace id associated with {fn}. Or count of - all callbacks if on_key() is called without arguments. + Return: ~ + (number) Namespace id associated with {fn}. Or count of all callbacks + if on_key() is called without arguments. - Note: - {fn} will be removed if an error occurs while calling. + Note: + {fn} will be removed if an error occurs while calling. 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: > - - vim.paste = (function(overridden) - return function(lines, phase) - for i,line in ipairs(lines) do - -- Scrub ANSI color codes from paste input. - lines[i] = line:gsub('\27%[[0-9;mK]+', '') - end - overridden(lines, phase) - end - end)(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: > + + vim.paste = (function(overridden) + return function(lines, phase) + for i,line in ipairs(lines) do + -- Scrub ANSI color codes from paste input. + lines[i] = line:gsub('\27%[[0-9;mK]+', '') + end + overridden(lines, phase) + end + end)(vim.paste) < - Parameters: ~ - {lines} |readfile()|-style list of lines to paste. - |channel-lines| - {phase} -1: "non-streaming" paste: the call contains all - lines. If paste is "streamed", `phase` indicates the stream state: - • 1: starts the paste (exactly once) - • 2: continues the paste (zero or more times) - • 3: ends the paste (exactly once) + Parameters: ~ + {lines} |readfile()|-style list of lines to paste. |channel-lines| + {phase} -1: "non-streaming" paste: the call contains all lines. If + paste is "streamed", `phase` indicates the stream state: + • 1: starts the paste (exactly once) + • 2: continues the paste (zero or more times) + • 3: ends the paste (exactly once) - Return: ~ - false if client should cancel the paste. + Return: ~ + false if client should cancel the paste. - See also: ~ - |paste| + See also: ~ + |paste| pretty_print({...}) *vim.pretty_print()* - Prints given arguments in human-readable format. Example: > - -- Print highlight group Normal and store it's contents in a variable. - local hl_normal = vim.pretty_print(vim.api.nvim_get_hl_by_name("Normal", true)) + Prints given arguments in human-readable format. Example: > + -- Print highlight group Normal and store it's contents in a variable. + local hl_normal = vim.pretty_print(vim.api.nvim_get_hl_by_name("Normal", true)) < - Return: ~ - given arguments. + Return: ~ + given arguments. - See also: ~ - |vim.inspect()| + See also: ~ + |vim.inspect()| region({bufnr}, {pos1}, {pos2}, {regtype}, {inclusive}) *vim.region()* - Get a table of lines with start, end columns for a region - marked by two points + Get a table of lines with start, end columns for a region marked by two + points - Parameters: ~ - {bufnr} (number) of buffer - {pos1} (line, column) tuple marking beginning of - region - {pos2} (line, column) tuple marking end of region - {regtype} type of selection (:help setreg) - {inclusive} (boolean) indicating whether the selection is - end-inclusive + Parameters: ~ + {bufnr} (number) of buffer + {pos1} (line, column) tuple marking beginning of region + {pos2} (line, column) tuple marking end of region + {regtype} type of selection (:help setreg) + {inclusive} (boolean) indicating whether the selection is + end-inclusive - Return: ~ - region lua table of the form {linenr = {startcol,endcol}} + Return: ~ + region lua table of the form {linenr = {startcol,endcol}} schedule_wrap({cb}) *vim.schedule_wrap()* - Defers callback `cb` until the Nvim API is safe to call. + Defers callback `cb` until the Nvim API is safe to call. - See also: ~ - |lua-loop-callbacks| - |vim.schedule()| - |vim.in_fast_event()| + See also: ~ + |lua-loop-callbacks| + |vim.schedule()| + |vim.in_fast_event()| deep_equal({a}, {b}) *vim.deep_equal()* - Deep compare values for equality + Deep compare values for equality - Tables are compared recursively unless they both provide the `eq` metamethod. All other types are compared using the equality `==` operator. + Tables are compared recursively unless they both provide the `eq` metamethod. All other types are compared using the equality `==` operator. - Parameters: ~ - {a} any First value - {b} any Second value + Parameters: ~ + {a} any First value + {b} any Second value - Return: ~ - (boolean) `true` if values are equals, else `false` + Return: ~ + (boolean) `true` if values are equals, else `false` deepcopy({orig}) *vim.deepcopy()* - Returns a deep copy of the given object. Non-table objects are - copied as in a typical Lua assignment, whereas table objects - are copied recursively. Functions are naively copied, so - functions in the copied table point to the same functions as - those in the input table. Userdata and threads are not copied - and will throw an error. + Returns a deep copy of the given object. Non-table objects are copied as + in a typical Lua assignment, whereas table objects are copied recursively. + Functions are naively copied, so functions in the copied table point to + the same functions as those in the input table. Userdata and threads are + not copied and will throw an error. - Parameters: ~ - {orig} (table) Table to copy + Parameters: ~ + {orig} (table) Table to copy - Return: ~ - (table) Table of copied keys and (nested) values. + Return: ~ + (table) Table of copied keys and (nested) values. endswith({s}, {suffix}) *vim.endswith()* - Tests if `s` ends with `suffix`. + Tests if `s` ends with `suffix`. - Parameters: ~ - {s} (string) String - {suffix} (string) Suffix to match + Parameters: ~ + {s} (string) String + {suffix} (string) Suffix to match - Return: ~ - (boolean) `true` if `suffix` is a suffix of `s` + Return: ~ + (boolean) `true` if `suffix` is a suffix of `s` gsplit({s}, {sep}, {plain}) *vim.gsplit()* - Splits a string at each instance of a separator. + Splits a string at each instance of a separator. - Parameters: ~ - {s} (string) String to split - {sep} (string) Separator or pattern - {plain} (boolean) If `true` use `sep` literally (passed - to string.find) + Parameters: ~ + {s} (string) String to split + {sep} (string) Separator or pattern + {plain} (boolean) If `true` use `sep` literally (passed to + string.find) - Return: ~ - (function) Iterator over the split components + Return: ~ + (function) Iterator over the split components - See also: ~ - |vim.split()| - https://www.lua.org/pil/20.2.html - http://lua-users.org/wiki/StringLibraryTutorial + See also: ~ + |vim.split()| + https://www.lua.org/pil/20.2.html + http://lua-users.org/wiki/StringLibraryTutorial is_callable({f}) *vim.is_callable()* - Returns true if object `f` can be called as a function. + Returns true if object `f` can be called as a function. - Parameters: ~ - {f} any Any object + Parameters: ~ + {f} any Any object - Return: ~ - (boolean) `true` if `f` is callable, else `false` + Return: ~ + (boolean) `true` if `f` is callable, else `false` list_extend({dst}, {src}, {start}, {finish}) *vim.list_extend()* - Extends a list-like table with the values of another list-like - table. + Extends a list-like table with the values of another list-like table. - NOTE: This mutates dst! + NOTE: This mutates dst! - Parameters: ~ - {dst} (table) List which will be modified and appended - to - {src} (table) List from which values will be inserted - {start} (number) Start index on src. Defaults to 1 - {finish} (number) Final index on src. Defaults to `#src` + Parameters: ~ + {dst} (table) List which will be modified and appended to + {src} (table) List from which values will be inserted + {start} (number) Start index on src. Defaults to 1 + {finish} (number) Final index on src. Defaults to `#src` - Return: ~ - (table) dst + Return: ~ + (table) dst - See also: ~ - |vim.tbl_extend()| + See also: ~ + |vim.tbl_extend()| list_slice({list}, {start}, {finish}) *vim.list_slice()* - Creates a copy of a table containing only elements from start - to end (inclusive) + Creates a copy of a table containing only elements from start to end + (inclusive) - Parameters: ~ - {list} (table) Table - {start} (number) Start range of slice - {finish} (number) End range of slice + Parameters: ~ + {list} (table) Table + {start} (number) Start range of slice + {finish} (number) End range of slice - Return: ~ - (table) Copy of table sliced from start to finish - (inclusive) + Return: ~ + (table) Copy of table sliced from start to finish (inclusive) pesc({s}) *vim.pesc()* - Escapes magic chars in a Lua pattern. + Escapes magic chars in |lua-patterns|. - Parameters: ~ - {s} (string) String to escape + Parameters: ~ + {s} (string) String to escape - Return: ~ - (string) %-escaped pattern string + Return: ~ + (string) %-escaped pattern string - See also: ~ - https://github.com/rxi/lume + See also: ~ + https://github.com/rxi/lume split({s}, {sep}, {kwargs}) *vim.split()* - Splits a string at each instance of a separator. + Splits a string at each instance of a separator. - Examples: > + Examples: > - 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'} < - Parameters: ~ - {s} (string) String to split - {sep} (string) Separator or pattern - {kwargs} (table) 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 + Parameters: ~ + {s} (string) String to split + {sep} (string) Separator or pattern + {kwargs} (table) 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 - Return: ~ - (table) List of split components + Return: ~ + (table) List of split components - See also: ~ - |vim.gsplit()| + See also: ~ + |vim.gsplit()| startswith({s}, {prefix}) *vim.startswith()* - Tests if `s` starts with `prefix`. + Tests if `s` starts with `prefix`. - Parameters: ~ - {s} (string) String - {prefix} (string) Prefix to match + Parameters: ~ + {s} (string) String + {prefix} (string) Prefix to match - Return: ~ - (boolean) `true` if `prefix` is a prefix of `s` + Return: ~ + (boolean) `true` if `prefix` is a prefix of `s` tbl_add_reverse_lookup({o}) *vim.tbl_add_reverse_lookup()* - Add the reverse lookup values to an existing table. For - example: `tbl_add_reverse_lookup { A = 1 } == { [1] = 'A', A = - 1 }` + Add the reverse lookup values to an existing table. For example: + `tbl_add_reverse_lookup { A = 1 } == { [1] = 'A', A = 1 }` - Note that this modifies the input. + Note that this modifies the input. - Parameters: ~ - {o} (table) Table to add the reverse to + Parameters: ~ + {o} (table) Table to add the reverse to - Return: ~ - (table) o + Return: ~ + (table) o tbl_contains({t}, {value}) *vim.tbl_contains()* - Checks if a list-like (vector) table contains `value`. + Checks if a list-like (vector) table contains `value`. - Parameters: ~ - {t} (table) Table to check - {value} any Value to compare + Parameters: ~ + {t} (table) Table to check + {value} any Value to compare - Return: ~ - (boolean) `true` if `t` contains `value` + Return: ~ + (boolean) `true` if `t` contains `value` tbl_count({t}) *vim.tbl_count()* - Counts the number of non-nil values in table `t`. + Counts the number of non-nil values in table `t`. > vim.tbl_count({ a=1, b=2 }) => 2 vim.tbl_count({ 1, 2 }) => 2 < - Parameters: ~ - {t} (table) Table + Parameters: ~ + {t} (table) Table - Return: ~ - (number) Number of non-nil values in table + Return: ~ + (number) Number of non-nil values in table - See also: ~ - https://github.com/Tieske/Penlight/blob/master/lua/pl/tablex.lua + See also: ~ + https://github.com/Tieske/Penlight/blob/master/lua/pl/tablex.lua tbl_deep_extend({behavior}, {...}) *vim.tbl_deep_extend()* - Merges recursively two or more map-like tables. + Merges recursively two or more map-like tables. - Parameters: ~ - {behavior} (string) Decides what to do if a key is found - in more than one map: - • "error": raise an error - • "keep": use value from the leftmost map - • "force": use value from the rightmost map - {...} (table) Two or more map-like tables + Parameters: ~ + {behavior} (string) Decides what to do if a key is found in more than + one map: + • "error": raise an error + • "keep": use value from the leftmost map + • "force": use value from the rightmost map + {...} (table) Two or more map-like tables - Return: ~ - (table) Merged table + Return: ~ + (table) Merged table - See also: ~ - |tbl_extend()| + See also: ~ + |tbl_extend()| tbl_extend({behavior}, {...}) *vim.tbl_extend()* - Merges two or more map-like tables. + Merges two or more map-like tables. - Parameters: ~ - {behavior} (string) Decides what to do if a key is found - in more than one map: - • "error": raise an error - • "keep": use value from the leftmost map - • "force": use value from the rightmost map - {...} (table) Two or more map-like tables + Parameters: ~ + {behavior} (string) Decides what to do if a key is found in more than + one map: + • "error": raise an error + • "keep": use value from the leftmost map + • "force": use value from the rightmost map + {...} (table) Two or more map-like tables - Return: ~ - (table) Merged table + Return: ~ + (table) Merged table - See also: ~ - |extend()| + See also: ~ + |extend()| tbl_filter({func}, {t}) *vim.tbl_filter()* - Filter a table using a predicate function + Filter a table using a predicate function - Parameters: ~ - {func} function|table Function or callable table - {t} (table) Table + Parameters: ~ + {func} function|table Function or callable table + {t} (table) Table - Return: ~ - (table) Table of filtered values + Return: ~ + (table) Table of filtered values tbl_flatten({t}) *vim.tbl_flatten()* - Creates a copy of a list-like table such that any nested - tables are "unrolled" and appended to the result. + Creates a copy of a list-like table such that any nested tables are + "unrolled" and appended to the result. - Parameters: ~ - {t} (table) List-like table + Parameters: ~ + {t} (table) List-like table - Return: ~ - (table) Flattened copy of the given list-like table + Return: ~ + (table) Flattened copy of the given list-like table - See also: ~ - From https://github.com/premake/premake-core/blob/master/src/base/table.lua + See also: ~ + From https://github.com/premake/premake-core/blob/master/src/base/table.lua tbl_get({o}, {...}) *vim.tbl_get()* - Index into a table (first argument) via string keys passed as - subsequent arguments. Return `nil` if the key does not exist. + Index into a table (first argument) via string keys passed as subsequent + arguments. Return `nil` if the key does not exist. - Examples: > + Examples: > - vim.tbl_get({ key = { nested_key = true }}, 'key', 'nested_key') == true - vim.tbl_get({ key = {}}, 'key', 'nested_key') == nil + vim.tbl_get({ key = { nested_key = true }}, 'key', 'nested_key') == true + vim.tbl_get({ key = {}}, 'key', 'nested_key') == nil < - Parameters: ~ - {o} (table) Table to index - {...} (string) Optional strings (0 or more, variadic) via - which to index the table + Parameters: ~ + {o} (table) Table to index + {...} (string) Optional strings (0 or more, variadic) via which to + index the table - Return: ~ - any Nested value indexed by key (if it exists), else nil + Return: ~ + any Nested value indexed by key (if it exists), else nil tbl_isempty({t}) *vim.tbl_isempty()* - Checks if a table is empty. + Checks if a table is empty. - Parameters: ~ - {t} (table) Table to check + Parameters: ~ + {t} (table) Table to check - Return: ~ - (boolean) `true` if `t` is empty + Return: ~ + (boolean) `true` if `t` is empty - See also: ~ - https://github.com/premake/premake-core/blob/master/src/base/table.lua + See also: ~ + https://github.com/premake/premake-core/blob/master/src/base/table.lua tbl_islist({t}) *vim.tbl_islist()* - Tests if a Lua table can be treated as an array. + Tests if a Lua table can be treated as an array. - Empty table `{}` is assumed to be an array, unless it was - created by |vim.empty_dict()| or returned as a dict-like |API| - or Vimscript result, for example from |rpcrequest()| or - |vim.fn|. + Empty table `{}` is assumed to be an array, unless it was created by + |vim.empty_dict()| or returned as a dict-like |API| or Vimscript result, + for example from |rpcrequest()| or |vim.fn|. - Parameters: ~ - {t} (table) Table + Parameters: ~ + {t} (table) Table - Return: ~ - (boolean) `true` if array-like table, else `false` + Return: ~ + (boolean) `true` if array-like table, else `false` tbl_keys({t}) *vim.tbl_keys()* - Return a list of all keys used in a table. However, the order - of the return table of keys is not guaranteed. + Return a list of all keys used in a table. However, the order of the + return table of keys is not guaranteed. - Parameters: ~ - {t} (table) Table + Parameters: ~ + {t} (table) Table - Return: ~ - (table) List of keys + Return: ~ + (table) List of keys - See also: ~ - From https://github.com/premake/premake-core/blob/master/src/base/table.lua + See also: ~ + From https://github.com/premake/premake-core/blob/master/src/base/table.lua tbl_map({func}, {t}) *vim.tbl_map()* - Apply a function to all values of a table. + Apply a function to all values of a table. - Parameters: ~ - {func} function|table Function or callable table - {t} (table) Table + Parameters: ~ + {func} function|table Function or callable table + {t} (table) Table - Return: ~ - (table) Table of transformed values + Return: ~ + (table) Table of transformed values tbl_values({t}) *vim.tbl_values()* - Return a list of all values used in a table. However, the - order of the return table of values is not guaranteed. + Return a list of all values used in a table. However, the order of the + return table of values is not guaranteed. - Parameters: ~ - {t} (table) Table + Parameters: ~ + {t} (table) Table - Return: ~ - (table) List of values + Return: ~ + (table) List of values trim({s}) *vim.trim()* - Trim whitespace (Lua pattern "%s") from both sides of a - string. + Trim whitespace (Lua pattern "%s") from both sides of a string. - Parameters: ~ - {s} (string) String to trim + Parameters: ~ + {s} (string) String to trim - Return: ~ - (string) String with whitespace removed from its beginning - and end + Return: ~ + (string) String with whitespace removed from its beginning and end - See also: ~ - https://www.lua.org/pil/20.2.html + See also: ~ + https://www.lua.org/pil/20.2.html validate({opt}) *vim.validate()* - Validates a parameter specification (types and values). - - Usage example: > - - function user.new(name, age, hobbies) - vim.validate{ - name={name, 'string'}, - age={age, 'number'}, - hobbies={hobbies, 'table'}, - } - ... - end + Validates a parameter specification (types and values). + + Usage example: > + + function user.new(name, age, hobbies) + vim.validate{ + name={name, 'string'}, + age={age, 'number'}, + hobbies={hobbies, 'table'}, + } + ... + end < - Examples with explicit argument values (can be run directly): > + Examples with explicit argument values (can be run directly): > - vim.validate{arg1={{'foo'}, 'table'}, arg2={'foo', 'string'}} - => NOP (success) + vim.validate{arg1={{'foo'}, 'table'}, arg2={'foo', 'string'}} + => NOP (success) - vim.validate{arg1={1, 'table'}} - => error('arg1: expected table, got number') + vim.validate{arg1={1, 'table'}} + => error('arg1: expected table, got number') - vim.validate{arg1={3, function(a) return (a % 2) == 0 end, 'even number'}} - => error('arg1: expected even number, got 3') + vim.validate{arg1={3, function(a) return (a % 2) == 0 end, 'even number'}} + => error('arg1: expected even number, got 3') < - If multiple types are valid they can be given as a list. > + If multiple types are valid they can be given as a list. > - vim.validate{arg1={{'foo'}, {'table', 'string'}}, arg2={'foo', {'table', 'string'}}} - => NOP (success) + vim.validate{arg1={{'foo'}, {'table', 'string'}}, arg2={'foo', {'table', 'string'}}} + => NOP (success) - vim.validate{arg1={1, {'string', table'}}} - => error('arg1: expected string|table, got number') + vim.validate{arg1={1, {'string', table'}}} + => error('arg1: expected string|table, got number') < - Parameters: ~ - {opt} (table) Names of parameters to validate. Each key - is a parameter name; each value is a tuple in one - of these forms: - 1. (arg_value, type_name, optional) - • arg_value: argument value - • type_name: string|table type name, one of: - ("table", "t", "string", "s", "number", "n", - "boolean", "b", "function", "f", "nil", - "thread", "userdata") or list of them. - • optional: (optional) boolean, if true, `nil` - is valid - - 2. (arg_value, fn, msg) - • arg_value: argument value - • fn: any function accepting one argument, - returns true if and only if the argument is - valid. Can optionally return an additional - informative error message as the second - returned value. - • msg: (optional) error string if validation - fails + Parameters: ~ + {opt} (table) Names of parameters to validate. Each key is a + parameter name; each value is a tuple in one of these forms: + 1. (arg_value, type_name, optional) + • arg_value: argument value + • type_name: string|table type name, one of: ("table", "t", + "string", "s", "number", "n", "boolean", "b", "function", + "f", "nil", "thread", "userdata") or list of them. + • optional: (optional) boolean, if true, `nil` is valid + + 2. (arg_value, fn, msg) + • arg_value: argument value + • fn: any function accepting one argument, returns true if + and only if the argument is valid. Can optionally return + an additional informative error message as the second + returned value. + • msg: (optional) error string if validation fails ============================================================================== Lua module: uri *lua-uri* uri_from_bufnr({bufnr}) *vim.uri_from_bufnr()* - Get a URI from a bufnr + Get a URI from a bufnr - Parameters: ~ - {bufnr} (number) + Parameters: ~ + {bufnr} (number) - Return: ~ - (string) URI + Return: ~ + (string) URI uri_from_fname({path}) *vim.uri_from_fname()* - Get a URI from a file path. + Get a URI from a file path. - Parameters: ~ - {path} (string) Path to file + Parameters: ~ + {path} (string) Path to file - Return: ~ - (string) URI + Return: ~ + (string) URI uri_to_bufnr({uri}) *vim.uri_to_bufnr()* - Get the buffer for a uri. Creates a new unloaded buffer if no - buffer for the uri already exists. + Get the buffer for a uri. Creates a new unloaded buffer if no buffer for + the uri already exists. - Parameters: ~ - {uri} (string) + Parameters: ~ + {uri} (string) - Return: ~ - (number) bufnr + Return: ~ + (number) bufnr uri_to_fname({uri}) *vim.uri_to_fname()* - Get a filename from a URI + Get a filename from a URI - Parameters: ~ - {uri} (string) + Parameters: ~ + {uri} (string) - Return: ~ - (string) filename or unchanged URI for non-file URIs + Return: ~ + (string) filename or unchanged URI for non-file URIs ============================================================================== Lua module: ui *lua-ui* input({opts}, {on_confirm}) *vim.ui.input()* - Prompts the user for input + Prompts the user for input - Example: > + Example: > - vim.ui.input({ prompt = 'Enter value for shiftwidth: ' }, function(input) - vim.o.shiftwidth = tonumber(input) - end) + vim.ui.input({ prompt = 'Enter value for shiftwidth: ' }, function(input) + vim.o.shiftwidth = tonumber(input) + end) < - Parameters: ~ - {opts} (table) Additional options. See |input()| - • prompt (string|nil) Text of the prompt - • default (string|nil) Default reply to the - input - • completion (string|nil) Specifies type of - completion supported for input. Supported - types are the same that can be supplied to - a user-defined command using the - "-complete=" argument. See - |:command-completion| - • highlight (function) Function that will be - used for 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. + Parameters: ~ + {opts} (table) Additional options. See |input()| + • prompt (string|nil) Text of the prompt + • default (string|nil) Default reply to the input + • completion (string|nil) Specifies type of completion + supported for input. Supported types are the same that + can be supplied to a user-defined command using the + "-complete=" argument. See |:command-completion| + • highlight (function) Function that will be used for + 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. select({items}, {opts}, {on_choice}) *vim.ui.select()* - Prompts the user to pick a single item from a collection of - entries - - Example: > - - vim.ui.select({ 'tabs', 'spaces' }, { - prompt = 'Select tabs or spaces:', - format_item = function(item) - return "I'd like to choose " .. item - end, - }, function(choice) - if choice == 'spaces' then - vim.o.expandtab = true - else - vim.o.expandtab = false - end - end) + Prompts the user to pick a single item from a collection of entries + + Example: > + + vim.ui.select({ 'tabs', 'spaces' }, { + prompt = 'Select tabs or spaces:', + format_item = function(item) + return "I'd like to choose " .. item + end, + }, function(choice) + if choice == 'spaces' then + vim.o.expandtab = true + else + vim.o.expandtab = false + end + end) < - Parameters: ~ - {items} (table) Arbitrary items - {opts} (table) Additional options - • prompt (string|nil) Text of the prompt. - Defaults to `Select one of:` - • format_item (function item -> text) - Function to format an individual item from - `items`. Defaults to `tostring`. - • kind (string|nil) Arbitrary hint string - indicating the item shape. Plugins - reimplementing `vim.ui.select` may wish to - use this to infer the structure or - semantics of `items`, or the context in - which select() was called. - {on_choice} (function) ((item|nil, idx|nil) -> ()) Called - once the user made a choice. `idx` is the - 1-based index of `item` within `items`. `nil` - if the user aborted the dialog. + Parameters: ~ + {items} (table) Arbitrary items + {opts} (table) Additional options + • prompt (string|nil) Text of the prompt. Defaults to + `Select one of:` + • format_item (function item -> text) Function to format + an individual item from `items`. Defaults to + `tostring`. + • kind (string|nil) Arbitrary hint string indicating the + item shape. Plugins reimplementing `vim.ui.select` may + wish to use this to infer the structure or semantics of + `items`, or the context in which select() was called. + {on_choice} (function) ((item|nil, idx|nil) -> ()) Called once the + user made a choice. `idx` is the 1-based index of `item` + within `items`. `nil` if the user aborted the dialog. ============================================================================== Lua module: filetype *lua-filetype* add({filetypes}) *vim.filetype.add()* - Add new filetype mappings. - - Filetype mappings can be added either by extension or by - filename (either the "tail" or the full file path). The full - file path is checked first, followed by the file name. If a - match is not found using the filename, then the filename is - matched against the list of |lua-patterns| (sorted by - priority) until a match is found. Lastly, if pattern matching - does not find a filetype, then the file extension is used. - - The filetype can be either a string (in which case it is used - as the filetype directly) or a function. If a function, it - takes the full path and buffer number of the file as arguments - (along with captures from the matched pattern, if any) and - should return a string that will be used as the buffer's - filetype. Optionally, the function can return a second - function value which, when called, modifies the state of the - buffer. This can be used to, for example, set - filetype-specific buffer variables. - - Filename patterns can specify an optional priority to resolve - cases when a file path matches multiple patterns. Higher - priorities are matched first. When omitted, the priority - defaults to 0. - - See $VIMRUNTIME/lua/vim/filetype.lua for more examples. - - Note that Lua filetype detection is disabled when - |g:do_legacy_filetype| is set. - - Example: > - - vim.filetype.add({ - extension = { - foo = 'fooscript', - bar = function(path, bufnr) - if some_condition() then - return 'barscript', function(bufnr) - -- Set a buffer variable - vim.b[bufnr].barscript_version = 2 - end - end - return 'bar' - end, - }, - filename = { - ['.foorc'] = 'toml', - ['/etc/foo/config'] = 'toml', - }, - pattern = { - ['.*/etc/foo/.*'] = 'fooscript', - -- Using an optional priority - ['.*/etc/foo/.*%.conf'] = { 'dosini', { priority = 10 } }, - ['README.(a+)$'] = function(path, bufnr, ext) - if ext == 'md' then - return 'markdown' - elseif ext == 'rst' then - return 'rst' - end - end, - }, - }) + Add new filetype mappings. + + Filetype mappings can be added either by extension or by filename (either + the "tail" or the full file path). The full file path is checked first, + followed by the file name. If a match is not found using the filename, + then the filename is matched against the list of |lua-patterns| (sorted by + priority) until a match is found. Lastly, if pattern matching does not + find a filetype, then the file extension is used. + + The filetype can be either a string (in which case it is used as the + filetype directly) or a function. If a function, it takes the full path + and buffer number of the file as arguments (along with captures from the + matched pattern, if any) and should return a string that will be used as + the buffer's filetype. Optionally, the function can return a second + function value which, when called, modifies the state of the buffer. This + can be used to, for example, set filetype-specific buffer variables. + + Filename patterns can specify an optional priority to resolve cases when a + file path matches multiple patterns. Higher priorities are matched first. + When omitted, the priority defaults to 0. + + See $VIMRUNTIME/lua/vim/filetype.lua for more examples. + + Note that Lua filetype detection is disabled when |g:do_legacy_filetype| + is set. + + Example: > + + vim.filetype.add({ + extension = { + foo = 'fooscript', + bar = function(path, bufnr) + if some_condition() then + return 'barscript', function(bufnr) + -- Set a buffer variable + vim.b[bufnr].barscript_version = 2 + end + end + return 'bar' + end, + }, + filename = { + ['.foorc'] = 'toml', + ['/etc/foo/config'] = 'toml', + }, + pattern = { + ['.*/etc/foo/.*'] = 'fooscript', + -- Using an optional priority + ['.*/etc/foo/.*%.conf'] = { 'dosini', { priority = 10 } }, + ['README.(a+)$'] = function(path, bufnr, ext) + if ext == 'md' then + return 'markdown' + elseif ext == 'rst' then + return 'rst' + end + end, + }, + }) < - To add a fallback match on contents (see - |new-filetype-scripts|), use > - - vim.filetype.add { - pattern = { - ['.*'] = { - priority = -math.huge, - function(path, bufnr) - local content = vim.filetype.getlines(bufnr, 1) - if vim.filetype.matchregex(content, [[^#!.*\<mine\>]]) then - return 'mine' - elseif vim.filetype.matchregex(content, [[\<drawing\>]]) then - return 'drawing' - end - end, - }, - }, - } + To add a fallback match on contents (see |new-filetype-scripts|), use > + + vim.filetype.add { + pattern = { + ['.*'] = { + priority = -math.huge, + function(path, bufnr) + local content = vim.filetype.getlines(bufnr, 1) + if vim.filetype.matchregex(content, [[^#!.*\<mine\>]]) then + return 'mine' + elseif vim.filetype.matchregex(content, [[\<drawing\>]]) then + return 'drawing' + end + end, + }, + }, + } < - Parameters: ~ - {filetypes} (table) A table containing new filetype maps - (see example). + Parameters: ~ + {filetypes} (table) A table containing new filetype maps (see + example). match({args}) *vim.filetype.match()* - Perform filetype detection. - - The filetype can be detected using one of three methods: - 1. Using an existing buffer - 2. Using only a file name - 3. Using only file contents - - Of these, option 1 provides the most accurate result as it - uses both the buffer's filename and (optionally) the buffer - contents. Options 2 and 3 can be used without an existing - buffer, but may not always provide a match in cases where the - filename (or contents) cannot unambiguously determine the - filetype. - - Each of the three options is specified using a key to the - single argument of this function. Example: + Perform filetype detection. + + The filetype can be detected using one of three methods: + 1. Using an existing buffer + 2. Using only a file name + 3. Using only file contents + + Of these, option 1 provides the most accurate result as it uses both the + buffer's filename and (optionally) the buffer contents. Options 2 and 3 + can be used without an existing buffer, but may not always provide a match + in cases where the filename (or contents) cannot unambiguously determine + the filetype. + + Each of the three options is specified using a key to the single argument + of this function. Example: > -- Using a buffer number @@ -2134,230 +2124,218 @@ match({args}) *vim.filetype.match()* vim.filetype.match({ contents = {'#!/usr/bin/env bash'} }) < - Parameters: ~ - {args} (table) Table specifying which matching strategy - to use. Accepted keys are: - • buf (number): Buffer number to use for matching. - Mutually exclusive with {contents} - • filename (string): Filename to use for matching. - When {buf} is given, defaults to the filename of - the given buffer number. The file need not - actually exist in the filesystem. When used - without {buf} only the name of the file is used - for filetype matching. This may result in - failure to detect the filetype in cases where - the filename alone is not enough to disambiguate - the filetype. - • contents (table): An array of lines representing - file contents to use for matching. Can be used - with {filename}. Mutually exclusive with {buf}. - - Return: ~ - (string|nil) If a match was found, the matched filetype. - (function|nil) A function that modifies buffer state when - called (for example, to set some filetype specific buffer - variables). The function accepts a buffer number as its - only argument. + Parameters: ~ + {args} (table) Table specifying which matching strategy to use. + Accepted keys are: + • buf (number): Buffer number to use for matching. Mutually + exclusive with {contents} + • filename (string): Filename to use for matching. When {buf} + is given, defaults to the filename of the given buffer + number. The file need not actually exist in the filesystem. + When used without {buf} only the name of the file is used + for filetype matching. This may result in failure to detect + the filetype in cases where the filename alone is not enough + to disambiguate the filetype. + • contents (table): An array of lines representing file + contents to use for matching. Can be used with {filename}. + Mutually exclusive with {buf}. + + Return: ~ + (string|nil) If a match was found, the matched filetype. + (function|nil) A function that modifies buffer state when called (for + example, to set some filetype specific buffer variables). The function + accepts a buffer number as its only argument. ============================================================================== Lua module: keymap *lua-keymap* del({modes}, {lhs}, {opts}) *vim.keymap.del()* - Remove an existing mapping. Examples: > + Remove an existing mapping. Examples: > - vim.keymap.del('n', 'lhs') + vim.keymap.del('n', 'lhs') - vim.keymap.del({'n', 'i', 'v'}, '<leader>w', { buffer = 5 }) + vim.keymap.del({'n', 'i', 'v'}, '<leader>w', { buffer = 5 }) < - Parameters: ~ - {opts} (table) A table of optional arguments: - • buffer: (number or boolean) Remove a mapping - from the given buffer. When "true" or 0, use the - current buffer. + Parameters: ~ + {opts} (table) A table of optional arguments: + • buffer: (number or boolean) Remove a mapping from the given + buffer. When "true" or 0, use the current buffer. - See also: ~ - |vim.keymap.set()| + See also: ~ + |vim.keymap.set()| set({mode}, {lhs}, {rhs}, {opts}) *vim.keymap.set()* - Add a new |mapping|. Examples: > + Add a new |mapping|. Examples: > - -- Can add mapping to Lua functions - vim.keymap.set('n', 'lhs', function() print("real lua function") end) + -- Can add mapping to Lua functions + vim.keymap.set('n', 'lhs', function() print("real lua function") end) - -- Can use it to map multiple modes - vim.keymap.set({'n', 'v'}, '<leader>lr', vim.lsp.buf.references, { buffer=true }) + -- Can use it to map multiple modes + vim.keymap.set({'n', 'v'}, '<leader>lr', vim.lsp.buf.references, { buffer=true }) - -- Can add mapping for specific buffer - vim.keymap.set('n', '<leader>w', "<cmd>w<cr>", { silent = true, buffer = 5 }) + -- Can add mapping for specific buffer + vim.keymap.set('n', '<leader>w', "<cmd>w<cr>", { silent = true, buffer = 5 }) - -- Expr mappings - vim.keymap.set('i', '<Tab>', function() - return vim.fn.pumvisible() == 1 and "<C-n>" or "<Tab>" - end, { expr = true }) - -- <Plug> mappings - vim.keymap.set('n', '[%', '<Plug>(MatchitNormalMultiBackward)') + -- Expr mappings + vim.keymap.set('i', '<Tab>', function() + return vim.fn.pumvisible() == 1 and "<C-n>" or "<Tab>" + end, { expr = true }) + -- <Plug> mappings + vim.keymap.set('n', '[%', '<Plug>(MatchitNormalMultiBackward)') < - Note that in a mapping like: > + Note that in a mapping like: > - vim.keymap.set('n', 'asdf', require('jkl').my_fun) + 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: > + the `require('jkl')` gets evaluated during this call in order to access the function. If you + want to avoid this cost at startup you can wrap it in a function, for + example: > - vim.keymap.set('n', 'asdf', function() return require('jkl').my_fun() end) + vim.keymap.set('n', 'asdf', function() return require('jkl').my_fun() end) < - Parameters: ~ - {mode} string|table Same mode short names as - |nvim_set_keymap()|. Can also be list of modes to - create mapping on multiple modes. - {lhs} (string) Left-hand side |{lhs}| of the mapping. - {rhs} string|function Right-hand side |{rhs}| of the - mapping. Can also be a Lua function. - {opts} (table) A table of |:map-arguments| such as - "silent". In addition to the options listed in - |nvim_set_keymap()|, this table also accepts the - following keys: - • buffer: (number or boolean) Add a mapping to the - given buffer. When "true" or 0, use the current - buffer. - • remap: (boolean) Make the mapping recursive. - This is the inverse of the "noremap" option from - |nvim_set_keymap()|. Default `false`. - • replace_keycodes: (boolean) defaults to true if - "expr" is true. - - See also: ~ - |nvim_set_keymap()| + Parameters: ~ + {mode} string|table Same mode short names as |nvim_set_keymap()|. Can + also be list of modes to create mapping on multiple modes. + {lhs} (string) Left-hand side |{lhs}| of the mapping. + {rhs} string|function Right-hand side |{rhs}| of the mapping. Can + also be a Lua function. + {opts} (table) A table of |:map-arguments|. + • Accepts options accepted by the {opts} parameter in + |nvim_set_keymap()|, with the following notable differences: + • replace_keycodes: Defaults to `true` if "expr" is `true`. + • noremap: Always overridden with the inverse of "remap" + (see below). + + • In addition to those options, the table accepts the + following keys: + • buffer: (number or boolean) Add a mapping to the given + buffer. When `0` or `true`, use the current buffer. + • remap: (boolean) Make the mapping recursive. This is the + inverse of the "noremap" option from |nvim_set_keymap()|. + Defaults to `false`. + + See also: ~ + |nvim_set_keymap()| ============================================================================== Lua module: fs *lua-fs* basename({file}) *vim.fs.basename()* - Return the basename of the given file or directory + Return the basename of the given file or directory - Parameters: ~ - {file} (string) File or directory + Parameters: ~ + {file} (string) File or directory - Return: ~ - (string) Basename of {file} + Return: ~ + (string) Basename of {file} dir({path}) *vim.fs.dir()* - Return an iterator over the files and directories located in - {path} + 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()|. + Parameters: ~ + {path} (string) An absolute or relative path to the directory to + iterate over. The path is first normalized + |vim.fs.normalize()|. - 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". + 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". dirname({file}) *vim.fs.dirname()* - Return the parent directory of the given file or directory + Return the parent directory of the given file or directory - Parameters: ~ - {file} (string) File or directory + Parameters: ~ + {file} (string) File or directory - Return: ~ - (string) Parent directory of {file} + Return: ~ + (string) Parent directory of {file} find({names}, {opts}) *vim.fs.find()* - Find files or directories in the given path. - - Finds any files or directories given in {names} starting from - {path}. If {upward} is "true" then the search traverses upward - through parent directories; otherwise, the search traverses - downward. Note that downward 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 specifying {type} to be "file" - or "directory", respectively. - - Parameters: ~ - {names} (string|table) Names of the files and directories - to find. Must be base names, paths and globs are - not supported. - {opts} (table) Optional keyword arguments: - • path (string): Path to begin searching from. If - omitted, the current working directory is used. - • upward (boolean, default false): If true, - search upward through parent directories. - Otherwise, search through child directories - (recursively). - • stop (string): Stop searching when this - directory is 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. - • 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 + Find files or directories in the given path. + + Finds any files or directories given in {names} starting from {path}. If + {upward} is "true" then the search traverses upward through parent + directories; otherwise, the search traverses downward. Note that downward + 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 + specifying {type} to be "file" or "directory", respectively. + + Parameters: ~ + {names} (string|table) Names of the files and directories to find. + Must be base names, paths and globs are not supported. + {opts} (table) Optional keyword arguments: + • path (string): Path to begin searching from. If omitted, + the current working directory is used. + • upward (boolean, default false): If true, search upward + through parent directories. Otherwise, search through child + directories (recursively). + • stop (string): Stop searching when this directory is + 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. + • 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 normalize({path}) *vim.fs.normalize()* - Normalize a path to a standard format. A tilde (~) character - at the beginning of the path is expanded to the user's home - directory and any backslash (\) characters are converted to - forward slashes (/). Environment variables are also expanded. + Normalize a path to a standard format. A tilde (~) character at the + beginning of the path is expanded to the user's home directory and any + backslash (\) characters are converted to forward slashes (/). Environment + variables are also expanded. - Example: > + Example: > - 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: ~ - {path} (string) Path to normalize + Parameters: ~ + {path} (string) Path to normalize - Return: ~ - (string) Normalized path + Return: ~ + (string) Normalized path parents({start}) *vim.fs.parents()* - Iterate over all the parents of the given file or directory. + Iterate over all the parents of the given file or directory. - Example: > + Example: > - 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 - root_dir = dir - break - end - end + 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 + root_dir = dir + break + end + end - if root_dir then - print("Found git repository at", root_dir) - end + if root_dir then + print("Found git repository at", root_dir) + end < - Parameters: ~ - {start} (string) Initial file or directory. + Parameters: ~ + {start} (string) Initial file or directory. - Return: ~ - (function) Iterator + Return: ~ + (function) Iterator - vim:tw=78:ts=8:ft=help:norl: + 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 new file mode 100644 index 0000000000..9dbd2d4de5 --- /dev/null +++ b/runtime/doc/luaref.txt @@ -0,0 +1,4966 @@ +*luaref.txt* Nvim + *luaref* *Lua-Reference* + + LUA REFERENCE MANUAL + + + Version 0.3.0 + August 7th, 2022 + + + Vimdoc version (c) 2006 by Luis Carvalho + <lexcarvalho at gmail dot com> + + Adapted from "Lua: 5.1 reference manual" + R. Ierusalimschy, L. H. de Figueiredo, W. Celes + Copyright (c) 2006 Lua.org, PUC-Rio. + + + See |luaref-doc| for information on this manual. + See |luaref-copyright| for copyright and licenses. + + + CONTENTS + ============ + + 1 INTRODUCTION........................|luaref-intro| + + 2 THE LANGUAGE........................|luaref-language| + 2.1 Lexical Conventions...............|luaref-langLexConv| + 2.2 Values and Types..................|luaref-langValTypes| + 2.2.1 Coercion........................|luaref-langCoercion| + 2.3 Variables.........................|luaref-langVariables| + 2.4 Statements........................|luaref-langStats| + 2.4.1 Chunks..........................|luaref-langChunks| + 2.4.2 Blocks..........................|luaref-langBlocks| + 2.4.3 Assignment......................|luaref-langAssign| + 2.4.4 Control Structures..............|luaref-langContStructs| + 2.4.5 For Statement...................|luaref-langForStat| + 2.4.6 Function Calls as Statements....|luaref-langFuncStat| + 2.4.7 Local Declarations..............|luaref-langLocalDec| + 2.5 Expressions.......................|luaref-langExpressions| + 2.5.1 Arithmetic Operators............|luaref-langArithOp| + 2.5.2 Relational Operators............|luaref-langRelOp| + 2.5.3 Logical Operators...............|luaref-langLogOp| + 2.5.4 Concatenation...................|luaref-langConcat| + 2.5.5 The Length Operator.............|luaref-langLength| + 2.5.6 Precedence......................|luaref-langPrec| + 2.5.7 Table Constructors..............|luaref-langTableConst| + 2.5.8 Function Calls..................|luaref-langFuncCalls| + 2.5.9 Function Definitions............|luaref-langFuncDefs| + 2.6 Visibility Rules..................|luaref-langVisibRules| + 2.7 Error Handling....................|luaref-langError| + 2.8 Metatables........................|luaref-langMetatables| + 2.9 Environments......................|luaref-langEnvironments| + 2.10 Garbage Collection...............|luaref-langGC| + 2.10.1 Garbage-Collection Metamethods.|luaref-langGCMeta| + 2.10.2 Weak Tables....................|luaref-langWeaktables| + 2.11 Coroutines.......................|luaref-langCoro| + + 3 THE APPLICATION PROGRAM INTERFACE...|luaref-api| + 3.1 The Stack.........................|luaref-apiStack| + 3.2 Stack Size........................|luaref-apiStackSize| + 3.3 Pseudo-Indices....................|luaref-apiPseudoIndices| + 3.4 C Closures........................|luaref-apiCClosures| + 3.5 Registry..........................|luaref-apiRegistry| + 3.6 Error Handling in C...............|luaref-apiError| + 3.7 Functions and Types...............|luaref-apiFunctions| + 3.8 The Debug Interface...............|luaref-apiDebug| + + 4 THE AUXILIARY LIBRARY...............|luaref-aux| + 4.1 Functions and Types...............|luaref-auxFunctions| + + 5 STANDARD LIBRARIES..................|luaref-lib| + 5.1 Basic Functions...................|luaref-libBasic| + 5.2 Coroutine Manipulation............|luaref-libCoro| + 5.3 Modules...........................|luaref-libModule| + 5.4 String Manipulation...............|luaref-libString| + 5.4.1 Patterns........................|luaref-libStringPat| + 5.5 Table Manipulation................|luaref-libTable| + 5.6 Mathematical Functions............|luaref-libMath| + 5.7 Input and Output Facilities.......|luaref-libIO| + 5.8 Operating System Facilities.......|luaref-libOS| + 5.9 The Debug Library.................|luaref-libDebug| + + A BIBLIOGRAPHY........................|luaref-bibliography| + B COPYRIGHT & LICENSES................|luaref-copyright| + C LUAREF DOC..........................|luaref-doc| + + +============================================================================== +1 INTRODUCTION *luaref-intro* +============================================================================== + +Lua is an extension programming language designed to support general +procedural programming with data description facilities. It also offers good +support for object-oriented programming, functional programming, and +data-driven programming. Lua is intended to be used as a powerful, +light-weight scripting language for any program that needs one. Lua is +implemented as a library, written in clean C (that is, in the common subset of +ANSI C and C++). + +Being an extension language, Lua has no notion of a "main" program: it only +works embedded in a host client, called the embedding program or simply the +host. This host program can invoke functions to execute a piece of Lua code, +can write and read Lua variables, and can register C functions to be called by +Lua code. Through the use of C functions, Lua can be augmented to cope with a +wide range of different domains, thus creating customized programming +languages sharing a syntactical framework. + +Lua is free software, and is provided as usual with no guarantees, as stated +in its license. The implementation described in this manual is available at +Lua's official web site, www.lua.org. + +Like any other reference manual, this document is dry in places. For a +discussion of the decisions behind the design of Lua, see references at +|luaref-bibliography|. For a detailed introduction to programming in Lua, see +Roberto's book, Programming in Lua. + +Lua means "moon" in Portuguese and is pronounced LOO-ah. + +============================================================================== +2 THE LANGUAGE *luaref-language* +============================================================================== + +This section describes the lexis, the syntax, and the semantics of Lua. In +other words, this section describes which tokens are valid, how they can be +combined, and what their combinations mean. + +The language constructs will be explained using the usual extended BNF +notation, in which { `a` } means 0 or more `a`'s, and [ `a` ] means an optional `a`. + +============================================================================== +2.1 Lexical Conventions *luaref-langLexConv* + + *luaref-names* *luaref-identifiers* +Names (also called identifiers) in Lua can be any string of letters, digits, +and underscores, not beginning with a digit. This coincides with the +definition of identifiers in most languages. (The definition of letter depends +on the current locale: any character considered alphabetic by the current +locale can be used in an identifier.) Identifiers are used to name variables +and table fields. + +The following keywords are reserved and cannot be used as names: +> + and break do else elseif + end false for function if + in local nil not or + repeat return then true until while +< +Lua is a case-sensitive language: `and` is a reserved word, but `And` and `AND` are +two different, valid names. As a convention, names starting with an underscore +followed by uppercase letters (such as `_VERSION`) are reserved for internal +global variables used by Lua. + +The following strings denote other tokens: +> + + - * / % ^ # + == ~= <= >= < > = + ( ) { } [ ] + ; : , . .. ... +< + *luaref-literal* +Literal strings can be delimited by matching single or double quotes, and can +contain the following C-like escape sequences: + + - `\a` bell + - `\b` backspace + - `\f` form feed + - `\n` newline + - `\r` carriage return + - `\t` horizontal tab + - `\v` vertical tab + - `\\` backslash + - `\"` quotation mark (double quote) + - `\'` apostrophe (single quote) + +Moreover, a backslash followed by a real newline results in a newline in the +string. A character in a string may also be specified by its numerical value +using the escape sequence `\ddd`, where `ddd` is a sequence of up to three +decimal digits. (Note that if a numerical escape is to be followed by a digit, +it must be expressed using exactly three digits.) Strings in Lua may contain +any 8-bit value, including embedded zeros, which can be specified as `\0`. + +To put a double (single) quote, a newline, a backslash, or an embedded zero +inside a literal string enclosed by double (single) quotes you must use an +escape sequence. Any other character may be directly inserted into the +literal. (Some control characters may cause problems for the file system, but +Lua has no problem with them.) + +Literal strings can also be defined using a long format enclosed by long +brackets. We define an opening long bracket of level n as an opening square +bracket followed by n equal signs followed by another opening square bracket. +So, an opening long bracket of level 0 is written as `[[`, an opening long +bracket of level 1 is written as `[=[`, and so on. +A closing long bracket is defined similarly; for instance, a closing long +bracket of level 4 is written as `]====]`. A long string starts with an +opening long bracket of any level and ends at the first closing long bracket +of the same level. Literals in this bracketed form may run for several lines, +do not interpret any escape sequences, and ignore long brackets of any other +level. They may contain anything except a closing bracket of the proper level. + +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: +> + a = 'alo\n123"' + a = "alo\n123\"" + a = '\97lo\10\04923"' + a = [[alo + 123"]] + a = [==[ + alo + 123"]==] +< + *luaref-numconstant* +A numerical constant may be written with an optional decimal part and an +optional decimal exponent. Lua also accepts integer hexadecimal constants, by +prefixing them with `0x`. Examples of valid numerical constants are +> + 3 3.0 3.1416 314.16e-2 0.31416E1 0xff 0x56 +< + *luaref-comment* +A comment starts with a double hyphen (`--`) anywhere outside a string. If the +text immediately after `--` is not an opening long bracket, the comment is a +short comment, which runs until the end of the line. Otherwise, it is a long +comment, which runs until the corresponding closing long bracket. Long +comments are frequently used to disable code temporarily. + +============================================================================== +2.2 Values and Types *luaref-langValTypes* + +Lua is a dynamically typed language. This means that variables do not have +types; only values do. There are no type definitions in the language. All +values carry their own type. + +All values in Lua are first-class values. This means that all values can be +stored in variables, passed as arguments to other functions, and returned as +results. + + *luaref-types* *luaref-nil* + *luaref-true* *luaref-false* + *luaref-number* *luaref-string* +There are eight basic types in Lua: `nil`, `boolean`, `number`, `string`, +`function`, `userdata`, `thread`, and `table`. Nil is the type of the value +`nil`, whose main property is to be different from any other value; it usually +represents the absence of a useful value. Boolean is the type of the values +`false` and `true`. Both `nil` and `false` make a condition false; any other +value makes it true. Number represents real (double-precision floating-point) +numbers. (It is easy to build Lua interpreters that use other internal +representations for numbers, such as single-precision float or long integers; +see file `luaconf.h`.) String represents arrays of characters. Lua is 8-bit +clean: strings may contain any 8-bit character, including embedded zeros +(`\0`) (see |luaref-literal|). + +Lua can call (and manipulate) functions written in Lua and functions written +in C (see |luaref-langFuncCalls|). + + *luaref-userdatatype* +The type userdata is provided to allow arbitrary C data to be stored in Lua +variables. This type corresponds to a block of raw memory and has no +pre-defined operations in Lua, except assignment and identity test. However, +by using metatables, the programmer can define operations for userdata values +(see |luaref-langMetatables|). Userdata values cannot be created or modified +in Lua, only through the C API. This guarantees the integrity of data owned by +the host program. + + *luaref-thread* +The type `thread` represents independent threads of execution and it is used to +implement coroutines (see |luaref-langCoro|). Do not confuse Lua threads with +operating-system threads. Lua supports coroutines on all systems, even those +that do not support threads. + + *luaref-table* +The type `table` implements associative arrays, that is, arrays that can be +indexed not only with numbers, but with any value (except `nil`). Tables can +be heterogeneous; that is, they can contain values of all types (except +`nil`). Tables are the sole data structuring mechanism in Lua; they may be +used to represent ordinary arrays, symbol tables, sets, records, graphs, +trees, etc. To represent records, Lua uses the field name as an index. The +language supports this representation by providing `a.name` as syntactic sugar +for `a["name"]`. There are several convenient ways to create tables in Lua +(see |luaref-langTableConst|). + +Like indices, the value of a table field can be of any type (except `nil`). In +particular, because functions are first-class values, table fields may contain +functions. Thus tables may also carry methods (see |luaref-langFuncDefs|). + +Tables, functions, threads and (full) userdata values are objects: variables +do not actually contain these values, only references to them. Assignment, +parameter passing, and function returns always manipulate references to such +values; these operations do not imply any kind of copy. + +The library function `type` returns a string describing the type of a given +value (see |luaref-type|). + +------------------------------------------------------------------------------ +2.2.1 Coercion *luaref-langCoercion* + +Lua provides automatic conversion between string and number values at run +time. Any arithmetic operation applied to a string tries to convert that +string to a number, following the usual conversion rules. Conversely, whenever +a number is used where a string is expected, the number is converted to a +string, in a reasonable format. For complete control of how numbers are +converted to strings, use the `format` function from the string library (see +|luaref-string.format|). + +============================================================================== +2.3 Variables *luaref-langVariables* + +Variables are places that store values. There are three kinds of variables in +Lua: global variables, local variables, and table fields. + +A single name can denote a global variable or a local variable (or a +function's formal parameter, which is a particular form of local variable): +> + var ::= Name +< +Name denotes identifiers, as defined in |luaref-langLexConv|. + +Any variable is assumed to be global unless explicitly declared as a local +(see |luaref-langLocalDec|). Local variables are lexically scoped: local +variables can be freely accessed by functions defined inside their scope (see +|luaref-langVisibRules|). + +Before the first assignment to a variable, its value is `nil`. + +Square brackets are used to index a table: +> + var ::= prefixexp [ exp ] +< +The first expression (`prefixexp`) should result in a table value; the second +expression (`exp`) identifies a specific entry inside that table. The +expression denoting the table to be indexed has a restricted syntax; see +|luaref-langExpressions| for details. + +The syntax `var.NAME` is just syntactic sugar for `var["NAME"]` : +> + var ::= prefixexp . Name +< +All global variables live as fields in ordinary Lua tables, called environment +tables or simply environments (see |luaref-langEnvironments|). Each function +has its own reference to an environment, so that all global variables in this +function will refer to this environment table. When a function is created, it +inherits the environment from the function that created it. To get the +environment table of a Lua function, you call `getfenv` (see +|luaref-getfenv|). To replace it, you call `setfenv` (see |luaref-setfenv|). +(You can only manipulate the environment of C functions through the debug +library; see |luaref-libDebug|.) + +An access to a global variable `x` is equivalent to `_env.x`, which in turn is +equivalent to +> + gettable_event(_env, "x") +< +where `_env` is the environment of the running function. (The `_env` variable is +not defined in Lua. We use it here only for explanatory purposes.) + +The meaning of accesses to global variables and table fields can be changed +via metatables. An access to an indexed variable `t[i]` is equivalent to a +call `gettable_event(t,i)`. (See |luaref-langMetatables| for a complete +description of the `gettable_event` function. This function is not defined or +callable in Lua. We use it here only for explanatory purposes.) + +============================================================================== +2.4 Statements *luaref-langStats* + +Lua supports an almost conventional set of statements, similar to those in +Pascal or C. This set includes assignment, control structures, function +calls, and variable declarations. + +------------------------------------------------------------------------------ +2.4.1 Chunks *luaref-chunk* *luaref-langChunks* + +The unit of execution of Lua is called a chunk. A chunk is simply a sequence +of statements, which are executed sequentially. Each statement can be +optionally followed by a semicolon: +> + chunk ::= {stat [ ; ]} +< +There are no empty statements and thus `;;` is not legal. + +Lua handles a chunk as the body of an anonymous function with a variable +number of arguments (see |luaref-langFuncDefs|). As such, chunks can define +local variables, receive arguments, and return values. + +A chunk may be stored in a file or in a string inside the host program. When a +chunk is executed, first it is pre-compiled into instructions for a virtual +machine, and then the compiled code is executed by an interpreter for the +virtual machine. + +Chunks may also be pre-compiled into binary form; see program `luac` for +details. Programs in source and compiled forms are interchangeable; Lua +automatically detects the file type and acts accordingly. + +------------------------------------------------------------------------------ +2.4.2 Blocks *luaref-block* *luaref-langBlocks* + +A block is a list of statements; syntactically, a block is the same as a +chunk: +> + block ::= chunk +< + *luaref-do* *luaref-end* +A block may be explicitly delimited to produce a single statement: +> + stat ::= do block end +< +Explicit blocks are useful to control the scope of variable declarations. +Explicit blocks are also sometimes used to add a `return` or `break` statement +in the middle of another block (see |luaref-langContStructs|). + +------------------------------------------------------------------------------ +2.4.3 Assignment *luaref-langAssign* + +Lua allows multiple assignment. Therefore, the syntax for assignment defines a +list of variables on the left side and a list of expressions on the right +side. The elements in both lists are separated by commas: +> + stat ::= varlist1 = explist1 + varlist1 ::= var { , var } + explist1 ::= exp { , exp } +< +Expressions are discussed in |luaref-langExpressions|. + +Before the assignment, the list of values is adjusted to the length of the +list of variables. If there are more values than needed, the excess values are +thrown away. If there are fewer values than needed, the list is extended with +as many `nil`s as needed. If the list of expressions ends with a function +call, then all values returned by this call enter in the list of values, +before the adjustment (except when the call is enclosed in parentheses; see +|luaref-langExpressions|). + +The assignment statement first evaluates all its expressions and only then are +the assignments performed. Thus the code +> + 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 +> + x, y = y, x +< +exchanges the values of `x` and `y`. + +The meaning of assignments to global variables and table fields can be changed +via metatables. An assignment to an indexed variable `t[i] = val` is +equivalent to `settable_event(t,i,val)`. (See |luaref-langMetatables| for a +complete description of the `settable_event` function. This function is not +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 +> + settable_event(_env, "x", val) +< +where `_env` is the environment of the running function. (The `_env` variable is +not defined in Lua. We use it here only for explanatory purposes.) + +------------------------------------------------------------------------------ +2.4.4 Control Structures *luaref-langContStructs* + + *luaref-if* *luaref-then* *luaref-else* *luaref-elseif* + *luaref-while* *luaref-repeat* *luaref-until* +The control structures `if`, `while`, and `repeat` have the usual meaning and +familiar syntax: +> + stat ::= while exp do block end + stat ::= repeat block until exp + stat ::= if exp then block { elseif exp then block } + [ else block ] end +< +Lua also has a `for` statement, in two flavors (see |luaref-langForStat|). + +The condition expression of a control structure may return any value. +Both `false` and `nil` are considered false. All values different +from `nil` and `false` are considered true (in particular, the number 0 and the +empty string are also true). + +In the `repeat-until` loop, the inner block does not end at the `until` keyword, +but only after the condition. So, the condition can refer to local variables +declared inside the loop block. + + *luaref-return* +The `return` statement is used to return values from a function or a chunk +(which is just a function). Functions and chunks may return more than one +value, so the syntax for the `return` statement is + + `stat ::=` `return` `[explist1]` + + *luaref-break* +The `break` statement is used to terminate the execution of a `while`, `repeat`, +or `for` loop, skipping to the next statement after the loop: + + `stat ::=` `break` + +A `break` ends the innermost enclosing loop. + +The `return` and `break` statements can only be written as the `last` +statement of a block. If it is really necessary to `return` or `break` in the +middle of a block, then an explicit inner block can be used, as in the idioms +`do return end` and `do break end`, because now `return` and `break` are +the last statements in their (inner) blocks. + +------------------------------------------------------------------------------ +2.4.5 For Statement *luaref-for* *luaref-langForStat* + +The `for` statement has two forms: one numeric and one generic. + +The numeric `for` loop repeats a block of code while a control variable runs +through an arithmetic progression. It has the following syntax: +> + stat ::= for Name = exp , exp [ , exp ] do block end +< +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 + + `for var =` `e1, e2, e3` `do` `block` `end` + +is equivalent to the code: + + `do` + `local` `var, limit, step` `= tonumber(e1), tonumber(e2), tonumber(e3)` + `if not (` `var` `and` `limit` `and` `step` `) then error() end` + `while (` `step` `>0 and` `var` `<=` `limit` `)` + `or (` `step` `<=0 and` `var` `>=` `limit` `) do` + `block` + `var` `=` `var` `+` `step` + `end` + `end` + +Note the following: + + - All three control expressions are evaluated only once, before the loop + starts. They must all result in numbers. + - `var`, `limit` and `step` are invisible variables. The names are here for + explanatory purposes only. + - If the third expression (the step) is absent, then a step of 1 is used. + - You can use `break` to exit a `for` loop. + - The loop variable `var` is local to the loop; you cannot use its value + after the `for` ends or is broken. If you need this value, assign it to + another variable before breaking or exiting the loop. + + *luaref-in* +The generic `for` statement works over functions, called iterators. On each +iteration, the iterator function is called to produce a new value, stopping +when this new value is `nil`. The generic `for` loop has the following syntax: +> + stat ::= for namelist in explist1 do block end + namelist ::= Name { , Name } +< +A `for` statement like + + `for` `var1, ..., varn` `in` `explist` `do` `block` `end` + +is equivalent to the code: + + `do` + `local` `f, s, var` `=` `explist` + `while true do` + `local` `var1, ..., varn` `=` `f(s, var)` + `var` `=` `var1` + `if` `var` `== nil then break end` + `block` + `end` + `end` + +Note the following: + + - `explist` is evaluated only once. Its results are an iterator function, + a `state`, and an initial value for the first iterator variable. + - `f`, `s`, and `var` are invisible variables. The names are here for + explanatory purposes only. + - You can use `break` to exit a `for` loop. + - The loop variables `var1, ..., varn` are local to the loop; you cannot use + their values after the `for` ends. If you need these values, then assign + them to other variables before breaking or exiting the loop. + +------------------------------------------------------------------------------ +2.4.6 Function Calls as Statements *luaref-langFuncStat* + +To allow possible side-effects, function calls can be executed as statements: +> + stat ::= functioncall +< +In this case, all returned values are thrown away. Function calls are +explained in |luaref-langFuncCalls|. + +------------------------------------------------------------------------------ +2.4.7 Local Declarations *luaref-local* *luaref-langLocalDec* + +Local variables may be declared anywhere inside a block. The declaration may +include an initial assignment: +> + stat ::= local namelist [ = explist1 ] + namelist ::= Name { , Name } +< +If present, an initial assignment has the same semantics of a multiple +assignment (see |luaref-langAssign|). Otherwise, all variables are initialized +with `nil`. + +A chunk is also a block (see |luaref-langChunks|), and so local variables can be +declared in a chunk outside any explicit block. The scope of such local +variables extends until the end of the chunk. + +The visibility rules for local variables are explained in +|luaref-langVisibRules|. + +============================================================================== +2.5 Expressions *luaref-langExpressions* + +The basic expressions in Lua are the following: +> + exp ::= prefixexp + exp ::= nil | false | true + exp ::= Number + exp ::= String + exp ::= function + exp ::= tableconstructor + exp ::= ... + exp ::= exp binop exp + exp ::= unop exp + prefixexp ::= var | functioncall | ( exp ) +< +Numbers and literal strings are explained in |luaref-langLexConv|; variables are +explained in |luaref-langVariables|; function definitions are explained in +|luaref-langFuncDefs|; function calls are explained in |luaref-langFuncCalls|; +table constructors are explained in |luaref-langTableConst|. Vararg expressions, +denoted by three dots (`...`), can only be used inside vararg functions; +they are explained in |luaref-langFuncDefs|. + +Binary operators comprise arithmetic operators (see |luaref-langArithOp|), +relational operators (see |luaref-langRelOp|), logical operators (see +|luaref-langLogOp|), and the concatenation operator (see |luaref-langConcat|). +Unary operators comprise the unary minus (see |luaref-labgArithOp|), the unary +`not` (see |luaref-langLogOp|), and the unary length operator (see +|luaref-langLength|). + +Both function calls and vararg expressions may result in multiple values. If +the expression is used as a statement (see |luaref-langFuncStat|) +(only possible for function calls), then its return list is adjusted to zero +elements, thus discarding all returned values. If the expression is used as +the last (or the only) element of a list of expressions, then no adjustment is +made (unless the call is enclosed in parentheses). In all other contexts, Lua +adjusts the result list to one element, discarding all values except the first +one. + +Here are some examples: +> + 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() + a,b,c = f(), x -- f() is adjusted to 1 result (c gets nil) + a,b = ... -- a gets the first vararg parameter, b gets + -- the second (both a and b may get nil if there + -- is no corresponding vararg parameter) + + a,b,c = x, f() -- f() is adjusted to 2 results + a,b,c = f() -- f() is adjusted to 3 results + return f() -- returns all results from f() + return ... -- returns all received vararg parameters + return x,y,f() -- returns x, y, and all results from f() + {f()} -- creates a list with all results from f() + {...} -- creates a list with all vararg parameters + {f(), nil} -- f() is adjusted to 1 result +< +An expression enclosed in parentheses always results in only one value. Thus, +`(f(x,y,z))` is always a single value, even if `f` returns several values. +(The value of `(f(x,y,z))` is the first value returned by `f` or `nil` if `f` does not +return any values.) + +------------------------------------------------------------------------------ +2.5.1 Arithmetic Operators *luaref-langArithOp* + +Lua supports the usual arithmetic operators: the binary `+` (addition), +`-` (subtraction), `*` (multiplication), `/` (division), `%` (modulo) +and `^` (exponentiation); and unary `-` (negation). If the operands are numbers, +or strings that can be converted to numbers (see |luaref-langCoercion|), then all +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 +> + a % b == a - math.floor(a/b)*b +< +That is, it is the remainder of a division that rounds the quotient towards +minus infinity. + +------------------------------------------------------------------------------ +2.5.2 Relational Operators *luaref-langRelOp* + +The relational operators in Lua are +> + == ~= < > <= >= +< +These operators always result in `false` or `true`. + +Equality (`==`) first compares the type of its operands. If the types are +different, then the result is `false`. Otherwise, the values of the operands +are compared. Numbers and strings are compared in the usual way. Objects +(tables, userdata, threads, and functions) are compared by reference: two +objects are considered equal only if they are the same object. Every time you +create a new object (a table, userdata, or function), this new object is +different from any previously existing object. + +You can change the way that Lua compares tables and userdata using the "eq" +metamethod (see |luaref-langMetatables|). + +The conversion rules of coercion |luaref-langCoercion| do not apply to +equality comparisons. Thus, `"0"==0` evaluates to `false`, and `t[0]` and +`t["0"]` denote different entries in a table. + +The operator `~=` is exactly the negation of equality (`==`). + +The order operators work as follows. If both arguments are numbers, then they +are compared as such. Otherwise, if both arguments are strings, then their +values are compared according to the current locale. Otherwise, Lua tries to +call the "lt" or the "le" metamethod (see |luaref-langMetatables|). + +------------------------------------------------------------------------------ +2.5.3 Logical Operators *luaref-langLogOp* + +The logical operators in Lua are +> + and or not +< +Like the control structures (see |luaref-langContStructs|), all logical operators +consider both `false` and `nil` as false and anything else as true. + + *luaref-not* *luaref-and* *luaref-or* +The negation operator `not` always returns `false` or `true`. The conjunction +operator `and` returns its first argument if this value is `false` or `nil`; +otherwise, `and` returns its second argument. The disjunction +operator `or` returns its first argument if this value is different +from `nil` and `false`; otherwise, `or` returns its second argument. +Both `and` and `or` use short-cut evaluation, that is, the second operand is +evaluated only if necessary. Here are some examples: +> + 10 or 20 --> 10 + 10 or error() --> 10 + nil or "a" --> "a" + nil and 10 --> nil + false and error() --> false + false and nil --> false + false or nil --> nil + 10 and 20 --> 20 +< +(In this manual, `-->` indicates the result of the preceding expression.) + +------------------------------------------------------------------------------ +2.5.4 Concatenation *luaref-langConcat* + +The string concatenation operator in Lua is denoted by two dots (`..`). +If both operands are strings or numbers, then they are converted to strings +according to the rules mentioned in |luaref-langCoercion|. Otherwise, the +"concat" metamethod is called (see |luaref-langMetatables|). + +------------------------------------------------------------------------------ +2.5.5 The Length Operator *luaref-langLength* + +The length operator is denoted by the unary operator `#`. The length of a +string is its number of bytes (that is, the usual meaning of string length +when each character is one byte). + +The length of a table `t` is defined to be any integer index `n` such that `t[n]` is +not `nil` and `t[n+1]` is `nil`; moreover, if `t[1]` is `nil`, `n` may be zero. For a +regular array, with non-nil values from 1 to a given `n`, its length is exactly +that `n`, the index of its last value. If the array has "holes" (that +is, `nil` values between other non-nil values), then `#t` may be any of the +indices that directly precedes a `nil` value (that is, it may consider any +such `nil` value as the end of the array). + +------------------------------------------------------------------------------ +2.5.6 Precedence *luaref-langPrec* + +Operator precedence in Lua follows the table below, from lower to higher +priority: +> + or + and + < > <= >= ~= == + .. + + - + * / + not # - (unary) + ^ +< +As usual, you can use parentheses to change the precedences in an expression. +The concatenation (`..`) and exponentiation (`^`) operators are right +associative. All other binary operators are left associative. + +------------------------------------------------------------------------------ +2.5.7 Table Constructors *luaref-langTableConst* + +Table constructors are expressions that create tables. Every time a +constructor is evaluated, a new table is created. Constructors can be used to +create empty tables, or to create a table and initialize some of its fields. +The general syntax for constructors is +> + tableconstructor ::= { [ fieldlist ] } + fieldlist ::= field { fieldsep field } [ fieldsep ] + field ::= [ exp ] = exp | Name = exp | exp + fieldsep ::= , | ; +< +Each field of the form `[exp1] = exp2` adds to the new table an entry with +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, +> + a = { [f(1)] = g; "x", "y"; x = 1, f(x), [30] = 23; 45 } +< +is equivalent to +> + do + local t = {} + t[f(1)] = g + t[1] = "x" -- 1st exp + t[2] = "y" -- 2nd exp + t.x = 1 -- temp["x"] = 1 + t[3] = f(x) -- 3rd exp + t[30] = 23 + t[4] = 45 -- 4th exp + a = t + end +< +If the last field in the list has the form `exp` and the expression is a +function call, then all values returned by the call enter the list +consecutively (see |luaref-langFuncCalls|). To avoid this, enclose the function +call in parentheses (see |luaref-langExpressions|). + +The field list may have an optional trailing separator, as a convenience for +machine-generated code. + +------------------------------------------------------------------------------ +2.5.8 Function Calls *luaref-function* *luaref-langFuncCalls* + +A function call in Lua has the following syntax: +> + functioncall ::= prefixexp args +< +In a function call, first `prefixexp` and `args` are evaluated. If the value +of `prefixexp` has type `function`, then this function is called with the given +arguments. Otherwise, the `prefixexp` "call" metamethod is called, having as +first parameter the value of `prefixexp`, followed by the original call +arguments (see |luaref-langMetatables|). + +The form +> + functioncall ::= prefixexp : Name args +< +can be used to call "methods". A call `v:name(` `args` `)` is syntactic sugar +for `v.name(v,` `args` `)`, except that `v` is evaluated only once. + +Arguments have the following syntax: +> + args ::= ( [ explist1 ] ) + args ::= tableconstructor + args ::= String +< +All argument expressions are evaluated before the call. A call of the +form `f{` `fields` `}` is syntactic sugar for `f({` `fields` `})`, that is, the +argument list is a single new table. A call of the form `f'` `string` `'` +(or `f"` `string` `"` or `f[[` `string` `]]`) is syntactic sugar for +`f('` `string` `')`, that is, the argument list is a single literal 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 +> + a = f + (g).x(a) +< +Lua would see that as a single statement, `a = f(g).x(a)`. So, if you want two +statements, you must add a semi-colon between them. If you actually want to +call `f`, you must remove the line break before `(g)`. + + *luaref-tailcall* +A call of the form `return` `functioncall` is called a tail call. Lua +implements proper tail calls (or proper tail recursion): in a tail call, the +called function reuses the stack entry of the calling function. Therefore, +there is no limit on the number of nested tail calls that a program can +execute. However, a tail call erases any debug information about the calling +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: +> + return (f(x)) -- results adjusted to 1 + return 2 * f(x) + return x, f(x) -- additional results + f(x); return -- results discarded + return x or f(x) -- results adjusted to 1 +< + +------------------------------------------------------------------------------ +2.5.9 Function Definitions *luaref-langFuncDefs* + +The syntax for function definition is +> + function ::= function funcbody + funcbody ::= ( [ parlist1 ] ) block end +< +The following syntactic sugar simplifies function definitions: +> + stat ::= function funcname funcbody + stat ::= local function Name funcbody + funcname ::= Name { . Name } [ : Name ] +< +The statement + + `function f ()` `body` `end` + +translates to + + `f = function ()` `body` `end` + +The statement + + `function t.a.b.c.f ()` `body` `end` + +translates to + + `t.a.b.c.f = function ()` `body` `end` + +The statement + + `local function f ()` `body` `end` + +translates to + + `local f; f = function f ()` `body` `end` + +not to + + `local f = function f ()` `body` `end` + +(This only makes a difference when the body of the function contains +references to `f`.) + + *luaref-closure* +A function definition is an executable expression, whose value has type +`function`. When Lua pre-compiles a chunk, all its function bodies are +pre-compiled too. Then, whenever Lua executes the function definition, the +function is instantiated (or closed). This function instance (or closure) is +the final value of the expression. Different instances of the same function +may refer to different external local variables and may have different +environment tables. + +Parameters act as local variables that are initialized with the argument +values: +> + parlist1 ::= namelist [ , ... ] | ... +< + *luaref-vararg* +When a function is called, the list of arguments is adjusted to the length of +the list of parameters, unless the function is a variadic or vararg function, +which is indicated by three dots (`...`) at the end of its parameter list. A +vararg function does not adjust its argument list; instead, it collects all +extra arguments and supplies them to the function through a vararg expression, +which is also written as three dots. The value of this expression is a list of +all actual extra arguments, similar to a function with multiple results. If a +vararg expression is used inside another expression or in the middle of a list +of expressions, then its return list is adjusted to one element. If the +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: +> + function f(a, b) end + function g(a, b, ...) end + function r() return 1,2,3 end +< +Then, we have the following mapping from arguments to parameters and to the +vararg expression: +> + CALL PARAMETERS + + f(3) a=3, b=nil + f(3, 4) a=3, b=4 + f(3, 4, 5) a=3, b=4 + f(r(), 10) a=1, b=10 + f(r()) a=1, b=2 + + g(3) a=3, b=nil, ... --> (nothing) + g(3, 4) a=3, b=4, ... --> (nothing) + g(3, 4, 5, 8) a=3, b=4, ... --> 5 8 + g(5, r()) a=5, b=1, ... --> 2 3 +< +Results are returned using the `return` statement (see |luaref-langContStructs|). +If control reaches the end of a function without encountering +a `return` statement, then the function returns with no results. + + *luaref-colonsyntax* +The colon syntax is used for defining methods, that is, functions that have an +implicit extra parameter `self`. Thus, the statement + + `function t.a.b.c:f (` `params` `)` `body` `end` + +is syntactic sugar for + + `t.a.b.c:f = function (self, (` `params` `)` `body` `end` + +============================================================================== +2.6 Visibility Rules *luaref-langVisibRules* + +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: +> + x = 10 -- global variable + do -- new block + local x = x -- new `x`, with value 10 + print(x) --> 10 + x = x+1 + do -- another block + local x = x+1 -- another `x` + print(x) --> 12 + end + print(x) --> 11 + end + print(x) --> 10 (the global one) +< +Notice that, in a declaration like `local x = x`, the new `x` being declared is +not in scope yet, and so the second `x` refers to the outside variable. + + *luaref-upvalue* +Because of the lexical scoping rules, local variables can be freely accessed +by functions defined inside their scope. A local variable used by an inner +function is called an upvalue, or external local variable, inside the inner +function. + +Notice that each execution of a local statement defines new local variables. +Consider the following example: +> + a = {} + local x = 20 + for i=1,10 do + local y = 0 + a[i] = function () y=y+1; return x+y end + end +< +The loop creates ten closures (that is, ten instances of the anonymous +function). Each of these closures uses a different `y` variable, while all of +them share the same `x`. + +============================================================================== +2.7 Error Handling *luaref-langError* + +Because Lua is an embedded extension language, all Lua actions start from +C code in the host program calling a function from the Lua library (see +|luaref-lua_pcall|). Whenever an error occurs during Lua compilation or +execution, control returns to C, which can take appropriate measures (such as +print an error message). + +Lua code can explicitly generate an error by calling the `error` function (see +|luaref-error|). If you need to catch errors in Lua, you can use +the `pcall` function (see |luaref-pcall|). + +============================================================================== +2.8 Metatables *luaref-metatable* *luaref-langMetatables* + +Every value in Lua may have a metatable. This metatable is an ordinary Lua +table that defines the behavior of the original table and userdata under +certain special operations. You can change several aspects of the behavior of +an object by setting specific fields in its metatable. For instance, when a +non-numeric value is the operand of an addition, Lua checks for a function in +the field `"__add"` in its metatable. If it finds one, Lua calls that function +to perform the addition. + +We call the keys in a metatable events and the values metamethods. In the +previous example, the event is "add" and the metamethod is the function that +performs the addition. + +You can query the metatable of any value through the `getmetatable` function +(see |luaref-getmetatable|). + +You can replace the metatable of tables through the `setmetatable` function (see +|luaref-setmetatable|). You cannot change the metatable of other types from Lua +(except using the debug library); you must use the C API for that. + +Tables and userdata have individual metatables (although multiple tables and +userdata can share a same table as their metatable); values of all other types +share one single metatable per type. So, there is one single metatable for all +numbers, and for all strings, etc. + +A metatable may control how an object behaves in arithmetic operations, order +comparisons, concatenation, length operation, and indexing. A metatable can +also define a function to be called when a userdata is garbage collected. For +each of those operations Lua associates a specific key called an event. When +Lua performs one of those operations over a value, it checks whether this +value has a metatable with the corresponding event. If so, the value +associated with that key (the metamethod) controls how Lua will perform the +operation. + +Metatables control the operations listed next. Each operation is identified by +its corresponding name. The key for each operation is a string with its name +prefixed by two underscores, `__`; for instance, the key for operation "add" +is the string "__add". The semantics of these operations is better explained +by a Lua function describing how the interpreter executes that operation. + +The code shown here in Lua is only illustrative; the real behavior is hard +coded in the interpreter and it is much more efficient than this simulation. +All functions used in these descriptions (`rawget`, `tonumber`, etc.) are +described in |luaref-libBasic|. In particular, to retrieve the metamethod of a +given object, we use the expression +> + metatable(obj)[event] +< +This should be read as +> + rawget(metatable(obj) or {}, event) +< +That is, the access to a metamethod does not invoke other metamethods, and the +access to objects with no metatables does not fail (it simply results +in `nil`). + +"add": *__add()* +------ +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. +> + 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 +> + function add_event (op1, op2) + local o1, o2 = tonumber(op1), tonumber(op2) + if o1 and o2 then -- both operands are numeric? + return o1 + o2 -- `+` here is the primitive `add` + else -- at least one of the operands is not numeric + local h = getbinhandler(op1, op2, "__add") + if h then + -- call the handler with both operands + return h(op1, op2) + else -- no handler available: default behavior + error(...) + end + end + end +< +"sub": *__sub()* +------ +the `-` operation. Behavior similar to the "add" operation. + +"mul": *__mul()* +------ +the `*` operation. Behavior similar to the "add" operation. + +"div": *__div()* +------ +the `/` operation. Behavior similar to the "add" operation. + +"mod": *__mod()* +------ +the `%` operation. Behavior similar to the "add" operation, with the +operation `o1 - floor(o1/o2)*o2` as the primitive operation. + +"pow": *__pow()* +------ +the `^` (exponentiation) operation. Behavior similar to the "add" operation, +with the function `pow` (from the C math library) as the primitive operation. + +"unm": *__unm()* +------ +the unary `-` operation. +> + function unm_event (op) + local o = tonumber(op) + if o then -- operand is numeric? + return -o -- `-` here is the primitive `unm` + else -- the operand is not numeric. + -- Try to get a handler from the operand + local h = metatable(op).__unm + if h then + -- call the handler with the operand + return h(op) + else -- no handler available: default behavior + error(...) + end + end + end +< +"concat": *__concat()* +--------- +the `..` (concatenation) operation. +> + function concat_event (op1, op2) + if (type(op1) == "string" or type(op1) == "number") and + (type(op2) == "string" or type(op2) == "number") then + return op1 .. op2 -- primitive string concatenation + else + local h = getbinhandler(op1, op2, "__concat") + if h then + return h(op1, op2) + else + error(...) + end + end + end +< +"len": *__len()* +------ +the `#` operation. +> + function len_event (op) + if type(op) == "string" then + return strlen(op) -- primitive string length + elseif type(op) == "table" then + return #op -- primitive table length + else + local h = metatable(op).__len + if h then + -- call the handler with the operand + return h(op) + else -- no handler available: default behavior + error(...) + end + end + end +< +"eq": *__eq()* +----- +the `==` operation. + +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. +> + function getcomphandler (op1, op2, event) + if type(op1) ~= type(op2) then return nil end + local mm1 = metatable(op1)[event] + local mm2 = metatable(op2)[event] + if mm1 == mm2 then return mm1 else return nil end + end +< +The "eq" event is defined as follows: +> + function eq_event (op1, op2) + if type(op1) ~= type(op2) then -- different types? + return false -- different objects + end + if op1 == op2 then -- primitive equal? + return true -- objects are equal + end + -- try metamethod + local h = getcomphandler(op1, op2, "__eq") + if h then + return h(op1, op2) + else + return false + end + end +< +`a ~= b` is equivalent to `not (a == b)`. + +"lt": *__lt()* +----- +the `<` operation. +> + function lt_event (op1, op2) + if type(op1) == "number" and type(op2) == "number" then + return op1 < op2 -- numeric comparison + elseif type(op1) == "string" and type(op2) == "string" then + return op1 < op2 -- lexicographic comparison + else + local h = getcomphandler(op1, op2, "__lt") + if h then + return h(op1, op2) + else + error(...); + end + end + end +< +`a > b` is equivalent to `b < a`. + +"le": *__le()* +----- +the `<=` operation. +> + function le_event (op1, op2) + if type(op1) == "number" and type(op2) == "number" then + return op1 <= op2 -- numeric comparison + elseif type(op1) == "string" and type(op2) == "string" then + return op1 <= op2 -- lexicographic comparison + else + local h = getcomphandler(op1, op2, "__le") + if h then + return h(op1, op2) + else + h = getcomphandler(op1, op2, "__lt") + if h then + return not h(op2, op1) + else + error(...); + end + end + end + end +< +`a >= b` is equivalent to `b <= a`. Note that, in the absence of a "le" +metamethod, Lua tries the "lt", assuming that `a <= b` is equivalent +to `not (b < a)`. + +"index": *__index()* +-------- +The indexing access `table[key]`. +> + function gettable_event (table, key) + local h + if type(table) == "table" then + local v = rawget(table, key) + if v ~= nil then return v end + h = metatable(table).__index + if h == nil then return nil end + else + h = metatable(table).__index + if h == nil then + error(...); + end + end + if type(h) == "function" then + return h(table, key) -- call the handler + else return h[key] -- or repeat operation on it + end +< +"newindex": *__newindex()* +----------- +The indexing assignment `table[key] = value`. +> + function settable_event (table, key, value) + local h + if type(table) == "table" then + local v = rawget(table, key) + if v ~= nil then rawset(table, key, value); return end + h = metatable(table).__newindex + if h == nil then rawset(table, key, value); return end + else + h = metatable(table).__newindex + if h == nil then + error(...); + end + end + if type(h) == "function" then + return h(table, key,value) -- call the handler + else h[key] = value -- or repeat operation on it + end +< +"call": *__call()* +------- +called when Lua calls a value. +> + function function_event (func, ...) + if type(func) == "function" then + return func(...) -- primitive call + else + local h = metatable(func).__call + if h then + return h(func, ...) + else + error(...) + end + end + end +< + +============================================================================== +2.9 Environments *luaref-environment* *luaref-langEnvironments* + +Besides metatables, objects of types thread, function, and userdata have +another table associated with them, called their environment. Like metatables, +environments are regular tables and multiple objects can share the same +environment. + +Environments associated with userdata have no meaning for Lua. It is only a +convenience feature for programmers to associate a table to a userdata. + +Environments associated with threads are called global environments. They are +used as the default environment for their threads and non-nested functions +created by the thread (through `loadfile` |luaref-loadfile|, `loadstring` +|luaref-loadstring| or `load` |luaref-load|) and can be directly accessed by C +code (see |luaref-apiPseudoIndices|). + +Environments associated with C functions can be directly accessed by C code +(see |luaref-apiPseudoIndices|). They are used as the default environment for +other C functions created by the function. + +Environments associated with Lua functions are used to resolve all accesses to +global variables within the function (see |luaref-langVariables|). They are +used as the default environment for other Lua functions created by the +function. + +You can change the environment of a Lua function or the running thread by +calling `setfenv` (see |luaref-setenv|). You can get the environment of a Lua +function or the running thread by calling `getfenv` (see |luaref-getfenv|). To +manipulate the environment of other objects (userdata, C functions, other +threads) you must use the C API. + +============================================================================== +2.10 Garbage Collection *luaref-langGC* + +Lua performs automatic memory management. This means that you do not have to +worry neither about allocating memory for new objects nor about freeing it +when the objects are no longer needed. Lua manages memory automatically by +running a garbage collector from time to time to collect all dead objects +(that is, these objects that are no longer accessible from Lua). All objects +in Lua are subject to automatic management: tables, userdata, functions, +threads, and strings. + +Lua implements an incremental mark-and-sweep collector. It uses two numbers to +control its garbage-collection cycles: the garbage-collector pause and the +garbage-collector step multiplier. + +The garbage-collector pause controls how long the collector waits before +starting a new cycle. Larger values make the collector less aggressive. Values +smaller than 1 mean the collector will not wait to start a new cycle. A value +of 2 means that the collector waits for the total memory in use to double +before starting a new cycle. + +The step multiplier controls the relative speed of the collector relative to +memory allocation. Larger values make the collector more aggressive but also +increase the size of each incremental step. Values smaller than 1 make the +collector too slow and may result in the collector never finishing a cycle. +The default, 2, means that the collector runs at "twice" the speed of memory +allocation. + +You can change these numbers by calling `lua_gc` (see |luaref-lua_gc|) in C or +`collectgarbage` (see |luaref-collectgarbage|) in Lua. Both get percentage +points as arguments (so an argument of 100 means a real value of 1). With +these functions you can also control the collector directly (e.g., stop and +restart it). + +------------------------------------------------------------------------------ +2.10.1 Garbage-Collection Metamethods *luaref-langGCMeta* + +Using the C API, you can set garbage-collector metamethods for userdata (see +|luaref-langMetatables|). These metamethods are also called finalizers. +Finalizers allow you to coordinate Lua's garbage collection with external +resource management (such as closing files, network or database connections, +or freeing your own memory). + + *__gc* +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: +> + function gc_event (udata) + local h = metatable(udata).__gc + if h then + h(udata) + end + end +< +At the end of each garbage-collection cycle, the finalizers for userdata are +called in reverse order of their creation, among these collected in that +cycle. That is, the first finalizer to be called is the one associated with +the userdata created last in the program. + +------------------------------------------------------------------------------ +2.10.2 - Weak Tables *luaref-weaktable* *luaref-langWeaktables* + +A weak table is a table whose elements are weak references. A weak reference +is ignored by the garbage collector. In other words, if the only references to +an object are weak references, then the garbage collector will collect this +object. + + *__mode* +A weak table can have weak keys, weak values, or both. A table with weak keys +allows the collection of its keys, but prevents the collection of its values. +A table with both weak keys and weak values allows the collection of both keys +and values. In any case, if either the key or the value is collected, the +whole pair is removed from the table. The weakness of a table is controlled by +the value of the `__mode` field of its metatable. If the `__mode` field is a +string containing the character `k`, the keys in the table are weak. +If `__mode` contains `v`, the values in the table are weak. + +After you use a table as a metatable, you should not change the value of its +field `__mode`. Otherwise, the weak behavior of the tables controlled by this +metatable is undefined. + +============================================================================== +2.11 Coroutines *luaref-coroutine* *luaref-langCoro* + +Lua supports coroutines, also called collaborative multithreading. A coroutine +in Lua represents an independent thread of execution. Unlike threads in +multithread systems, however, a coroutine only suspends its execution by +explicitly calling a yield function. + +You create a coroutine with a call to `coroutine.create` (see +|luaref-coroutine.create|). Its sole argument is a function that is the main +function of the coroutine. The `create` function only creates a new coroutine +and returns a handle to it (an object of type `thread`); it does not start the +coroutine execution. + +When you first call `coroutine.resume` (see |luaref-coroutine.resume|), +passing as its first argument the thread returned by `coroutine.create`, the +coroutine starts its execution, at the first line of its main function. Extra +arguments passed to `coroutine.resume` are passed on to the coroutine main +function. After the coroutine starts running, it runs until it terminates or +`yields`. + +A coroutine can terminate its execution in two ways: normally, when its main +function returns (explicitly or implicitly, after the last instruction); and +abnormally, if there is an unprotected error. In the first case, +`coroutine.resume` returns `true`, plus any values returned by the coroutine +main function. In case of errors, `coroutine.resume` returns `false` plus an +error message. + +A coroutine yields by calling `coroutine.yield` (see +|luaref-coroutine.yield|). When a coroutine yields, the corresponding +`coroutine.resume` returns immediately, even if the yield happens inside +nested function calls (that is, not in the main function, but in a function +directly or indirectly called by the main function). In the case of a yield, +`coroutine.resume` also returns `true`, plus any values passed to +`coroutine.yield`. The next time you resume the same coroutine, it continues +its execution from the point where it yielded, with the call to +`coroutine.yield` returning any extra arguments passed to `coroutine.resume`. + +Like `coroutine.create`, the `coroutine.wrap` function (see +|luaref-coroutine.wrap|) also creates a coroutine, but instead of returning +the coroutine itself, it returns a function that, when called, resumes the +coroutine. Any arguments passed to this function go as extra arguments to +`coroutine.resume`. `coroutine.wrap` returns all the values returned by +`coroutine.resume`, except the first one (the boolean error code). Unlike +`coroutine.resume`, `coroutine.wrap` does not catch errors; any error is +propagated to the caller. + +As an example, consider the next code: +> + function foo1 (a) + print("foo", a) + return coroutine.yield(2*a) + end + + co = coroutine.create(function (a,b) + print("co-body", a, b) + local r = foo1(a+1) + print("co-body", r) + local r, s = coroutine.yield(a+b, a-b) + print("co-body", r, s) + return b, "end" + end) + + print("main", coroutine.resume(co, 1, 10)) + print("main", coroutine.resume(co, "r")) + print("main", coroutine.resume(co, "x", "y")) + print("main", coroutine.resume(co, "x", "y")) +< +When you run it, it produces the following output: +> + co-body 1 10 + foo 2 + main true 4 + co-body r + main true 11 -9 + co-body x y + main true 10 end + main false cannot resume dead coroutine +< + +============================================================================== +3 THE APPLICATION PROGRAM INTERFACE *luaref-API* +============================================================================== + +This section describes the C API for Lua, that is, the set of C functions +available to the host program to communicate with Lua. All API functions and +related types and constants are declared in the header file `lua.h`. + +Even when we use the term "function", any facility in the API may be provided +as a `macro` instead. All such macros use each of its arguments exactly once +(except for the first argument, which is always a Lua state), and so do not +generate hidden side-effects. + +As in most C libraries, the Lua API functions do not check their arguments for +validity or consistency. However, you can change this behavior by compiling +Lua with a proper definition for the macro `luai_apicheck`,in file +`luaconf.h`. + +============================================================================== +3.1 The Stack *luaref-stack* *luaref-apiStack* + +Lua uses a virtual stack to pass values to and from C. Each element in this +stack represents a Lua value (`nil`, number, string, etc.). + +Whenever Lua calls C, the called function gets a new stack, which is +independent of previous stacks and of stacks of C functions that are still +active. This stack initially contains any arguments to the C function and it +is where the C function pushes its results to be returned to the caller (see +|luaref-lua_CFunction|). + + *luaref-stackindex* +For convenience, most query operations in the API do not follow a strict stack +discipline. Instead, they can refer to any element in the stack by using an +index: a positive index represents an absolute stack position (starting at 1); +a negative index represents an offset from the top of the stack. More +specifically, if the stack has `n` elements, then index 1 represents the first +element (that is, the element that was pushed onto the stack first) and index +`n` represents the last element; index `-1` also represents the last element +(that is, the element at the top) and index `-n` represents the first element. +We say that an index is valid if it lies between 1 and the stack top (that is, +if `1 <= abs(index) <= top`). + +============================================================================== +3.2 Stack Size *luaref-apiStackSize* + +When you interact with Lua API, you are responsible for ensuring consistency. +In particular, you are responsible for controlling stack overflow. You can +use the function `lua_checkstack` to grow the stack size (see +|luaref-lua_checkstack|). + +Whenever Lua calls C, it ensures that at least `LUA_MINSTACK` stack positions +are available. `LUA_MINSTACK` is defined as 20, so that usually you do not +have to worry about stack space unless your code has loops pushing elements +onto the stack. + +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: +> + (index < 0 && abs(index) <= top) || (index > 0 && index <= stackspace) +< +Note that 0 is never an acceptable index. + +============================================================================== +3.3 Pseudo-Indices *luaref-pseudoindex* *luaref-apiPseudoIndices* + +Unless otherwise noted, any function that accepts valid indices can also be +called with pseudo-indices, which represent some Lua values that are +accessible to the C code but which are not in the stack. Pseudo-indices are +used to access the thread environment, the function environment, the registry, +and the upvalues of a C function (see |luaref-apiCClosures|). + +The thread environment (where global variables live) is always at pseudo-index +`LUA_GLOBALSINDEX`. The environment of the running C function is always at +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 +> + lua_getfield(L, LUA_GLOBALSINDEX, varname); +< + +============================================================================== +3.4 C Closures *luaref-cclosure* *luaref-apiCClosures* + +When a C function is created, it is possible to associate some values with it, +thus creating a C closure; these values are called upvalues and are accessible +to the function whenever it is called (see |luaref-lua_pushcclosure|). + +Whenever a C function is called, its upvalues are located at specific +pseudo-indices. These pseudo-indices are produced by the macro +`lua_upvalueindex` (see |luaref-lua_upvalueindex|). The first value associated +with a function is at position `lua_upvalueindex(1)`, and so on. Any access to +`lua_upvalueindex(` `n` `)`, where `n` is greater than the number of upvalues of +the current function, produces an acceptable (but invalid) index. + +============================================================================== +3.5 Registry *luaref-registry* *luaref-apiRegistry* + +Lua provides a registry, a pre-defined table that can be used by any C code to +store whatever Lua value it needs to store. This table is always located at +pseudo-index `LUA_REGISTRYINDEX`. Any C library can store data into this +table, but it should take care to choose keys different from those used by +other libraries, to avoid collisions. Typically, you should use as key a +string containing your library name or a light userdata with the address of a +C object in your code. + +The integer keys in the registry are used by the reference mechanism, +implemented by the auxiliary library, and therefore should not be used for +other purposes. + +============================================================================== +3.6 Error Handling in C *luaref-apiError* + +Internally, Lua uses the C `longjmp` facility to handle errors. (You can also +choose to use exceptions if you use C++; see file `luaconf.h`.) When Lua faces +any error (such as memory allocation errors, type errors, syntax errors, and +runtime errors) it raises an error; that is, it does a long jump. A protected +environment uses `setjmp` to set a recover point; any error jumps to the most +recent active recover point. + +Almost any function in the API may raise an error, for instance due to a +memory allocation error. The following functions run in protected mode (that +is, they create a protected environment to run), so they never raise an error: +`lua_newstate`, `lua_close`, `lua_load`, `lua_pcall`, and `lua_cpcall` (see +|luaref-lua_newstate|, |luaref-lua_close|, |luaref-lua_load|, +|luaref-lua_pcall|, and |luaref-lua_cpcall|). + +Inside a C function you can raise an error by calling `lua_error` (see +|luaref-lua_error|). + +============================================================================== +3.7 Functions and Types *luaref-apiFunctions* + +Here we list all functions and types from the C API in alphabetical order. + +lua_Alloc *lua_Alloc()* +> + typedef void * (*lua_Alloc) (void *ud, + void *ptr, + size_t osize, + size_t nsize); +< + The type of the memory-allocation function used by Lua states. The + allocator function must provide a functionality similar to `realloc`, + but not exactly the same. Its arguments are `ud`, an opaque pointer + passed to `lua_newstate` (see |luaref-lua_newstate|); `ptr`, a pointer + to the block being allocated/reallocated/freed; `osize`, the original + size of the block; `nsize`, the new size of the block. `ptr` is `NULL` + if and only if `osize` is zero. When `nsize` is zero, the allocator + must return `NULL`; if `osize` is not zero, it should free the block + pointed to by `ptr`. When `nsize` is not zero, the allocator returns + `NULL` if and only if it cannot fill the request. When `nsize` is not + zero and `osize` is zero, the allocator should behave like `malloc`. + When `nsize` and `osize` are not zero, the allocator behaves like + `realloc`. Lua assumes that the allocator never fails when `osize >= + nsize`. + + Here is a simple implementation for the allocator function. It is used + in the auxiliary library by `luaL_newstate` (see + |luaref-luaL_newstate|). +> + static void *l_alloc (void *ud, void *ptr, size_t osize, + size_t nsize) { + (void)ud; (void)osize; /* not used */ + if (nsize == 0) { + free(ptr); + return NULL; + } + else + return realloc(ptr, nsize); + } +< + This code assumes that `free(NULL)` has no effect and that + `realloc(NULL, size)` is equivalent to `malloc(size)`. ANSI C ensures both + behaviors. + +lua_atpanic *lua_atpanic()* +> + lua_CFunction lua_atpanic (lua_State *L, lua_CFunction panicf); +< + Sets a new panic function and returns the old one. + + If an error happens outside any protected environment, Lua calls a + `panic` `function` and then calls `exit(EXIT_FAILURE)`, thus exiting + the host application. Your panic function may avoid this exit by never + returning (e.g., doing a long jump). + + The panic function can access the error message at the top of the + stack. + +lua_call *lua_call()* +> + void lua_call (lua_State *L, int nargs, int nresults); +< + Calls a function. + + To call a function you must use the following protocol: first, the + function to be called is pushed onto the stack; then, the arguments to + the function are pushed in direct order; that is, the first argument + is pushed first. Finally you call `lua_call`; `nargs` is the number of + arguments that you pushed onto the stack. All arguments and the + function value are popped from the stack when the function is called. + The function results are pushed onto the stack when the function + returns. The number of results is adjusted to `nresults`, unless + `nresults` is `LUA_MULTRET`. In this case, `all` results from the + function are pushed. Lua takes care that the returned values fit into + the stack space. The function results are pushed onto the stack in + direct order (the first result is pushed first), so that after the + call the last result is on the top of the stack. + + Any error inside the called function is propagated upwards (with a + `longjmp`). + + The following example shows how the host program may do the equivalent + to this Lua code: +> + a = f("how", t.x, 14) +< + Here it is in 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 + lua_getfield(L, -1, "x"); // push result of t.x (2nd arg) + lua_remove(L, -2); // remove 't' from the stack + lua_pushinteger(L, 14); // 3rd argument + lua_call(L, 3, 1); // call 'f' with 3 arguments and 1 result + lua_setfield(L, LUA_GLOBALSINDEX, "a"); // set global 'a' +< + Note that the code above is "balanced": at its end, the stack is back + to its original configuration. This is considered good programming + practice. + +lua_CFunction *luaref-cfunction* *lua_CFunction()* +> + typedef int (*lua_CFunction) (lua_State *L); +< + Type for C functions. + + In order to communicate properly with Lua, a C function must use the + following protocol, which defines the way parameters and results are + passed: a C function receives its arguments from Lua in its stack in + direct order (the first argument is pushed first). So, when the + function starts, `lua_gettop(L)` (see |luaref-lua_gettop|) returns the + number of arguments received by the function. The first argument (if + any) is at index 1 and its last argument is at index `lua_gettop(L)`. + To return values to Lua, a C function just pushes them onto the stack, + in direct order (the first result is pushed first), and returns the + number of results. Any other value in the stack below the results will + be properly discarded by Lua. Like a Lua function, a C function called + by Lua can also return many results. + + *luaref-cfunctionexample* + As an example, the following function receives a variable number of + numerical arguments and returns their average and sum: +> + static int foo (lua_State *L) { + int n = lua_gettop(L); /* number of arguments */ + lua_Number sum = 0; + int i; + for (i = 1; i <= n; i++) { + if (!lua_isnumber(L, i)) { + lua_pushstring(L, "incorrect argument"); + lua_error(L); + } + sum += lua_tonumber(L, i); + } + lua_pushnumber(L, sum/n); /* first result */ + lua_pushnumber(L, sum); /* second result */ + return 2; /* number of results */ + } +< + +lua_checkstack *lua_checkstack()* +> + int lua_checkstack (lua_State *L, int extra); +< + Ensures that there are at least `extra` free stack slots in the stack. + It returns false if it cannot grow the stack to that size. This + function never shrinks the stack; if the stack is already larger than + the new size, it is left unchanged. + +lua_close *lua_close()* +> + void lua_close (lua_State *L); +< + Destroys all objects in the given Lua state (calling the corresponding + garbage-collection metamethods, if any) and frees all dynamic memory + used by this state. On several platforms, you may not need to call + this function, because all resources are naturally released when the + host program ends. On the other hand, long-running programs, such as a + daemon or a web server, might need to release states as soon as they + are not needed, to avoid growing too large. + +lua_concat *lua_concat()* +> + void lua_concat (lua_State *L, int n); +< + Concatenates the `n` values at the top of the stack, pops them, and + leaves the result at the top. If `n` is 1, the result is the single + string on the stack (that is, the function does nothing); if `n` is 0, + the result is the empty string. Concatenation is done following the + usual semantics of Lua (see |luaref-langConcat|). + +lua_cpcall *lua_cpcall()* +> + int lua_cpcall (lua_State *L, lua_CFunction func, void *ud); +< + Calls the C function `func` in protected mode. `func` starts with only + one element in its stack, a light userdata containing `ud`. In case of + errors, `lua_cpcall` returns the same error codes as `lua_pcall` (see + |luaref-lua_pcall|), plus the error object on the top of the stack; + otherwise, it returns zero, and does not change the stack. All values + returned by `func` are discarded. + +lua_createtable *lua_createtable()* +> + void lua_createtable (lua_State *L, int narr, int nrec); +< + Creates a new empty table and pushes it onto the stack. The new table + has space pre-allocated for `narr` array elements and `nrec` non-array + elements. This pre-allocation is useful when you know exactly how many + elements the table will have. Otherwise you can use the function + `lua_newtable` (see |luaref-lua_newtable|). + +lua_dump *lua_dump()* +> + 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 + of the stack and produces a binary chunk that, if loaded again, + results in a function equivalent to the one dumped. As it produces + parts of the chunk, `lua_dump` calls function `writer` (see + |luaref-lua_Writer|) with the given `data` to write them. + + The value returned is the error code returned by the last call to the + writer; 0 means no errors. + + This function does not pop the Lua function from the stack. + +lua_equal *lua_equal()* +> + int lua_equal (lua_State *L, int index1, int index2); +< + Returns 1 if the two values in acceptable indices `index1` and + `index2` are equal, following the semantics of the Lua `==` operator + (that is, may call metamethods). Otherwise returns 0. Also returns 0 + if any of the indices is non valid. + +lua_error *lua_error()* +> + int lua_error (lua_State *L); +< + Generates a Lua error. The error message (which can actually be a Lua + value of any type) must be on the stack top. This function does a long + jump, and therefore never returns (see |luaref-luaL_error|). + +lua_gc *lua_gc()* +> + int lua_gc (lua_State *L, int what, int data); +< + Controls the garbage collector. + + This function performs several tasks, according to the value of the + parameter `what`: + + `LUA_GCSTOP` stops the garbage collector. + `LUA_GCRESTART` restarts the garbage collector. + `LUA_GCCOLLECT` performs a full garbage-collection cycle. + `LUA_GCCOUNT` returns the current amount of memory (in Kbytes) in + use by Lua. + `LUA_GCCOUNTB` returns the remainder of dividing the current + amount of bytes of memory in use by Lua by 1024. + `LUA_GCSTEP` performs an incremental step of garbage collection. + The step "size" is controlled by `data` (larger + values mean more steps) in a non-specified way. If + you want to control the step size you must + experimentally tune the value of `data`. The + function returns 1 if the step finished a + garbage-collection cycle. + `LUA_GCSETPAUSE` sets `data` /100 as the new value for the + `pause` of the collector (see |luaref-langGC|). + The function returns the previous value of the + pause. + `LUA_GCSETSTEPMUL` sets `data` /100 as the new value for the + `step` `multiplier` of the collector (see + |luaref-langGC|). The function returns the + previous value of the step multiplier. + +lua_getallocf *lua_getallocf()* +> + lua_Alloc lua_getallocf (lua_State *L, void **ud); +< + Returns the memory-allocation function of a given state. If `ud` is + not `NULL`, Lua stores in `*ud` the opaque pointer passed to + `lua_newstate` (see |luaref-lua_newstate|). + +lua_getfenv *lua_getfenv()* +> + 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()* +> + 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 + given valid index `index`. As in Lua, this function may trigger a + metamethod for the "index" event (see |luaref-langMetatables|). + +lua_getglobal *lua_getglobal()* +> + 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: +> + #define lua_getglobal(L,s) lua_getfield(L, LUA_GLOBALSINDEX, s) +< + +lua_getmetatable *lua_getmetatable()* +> + int lua_getmetatable (lua_State *L, int index); +< + Pushes onto the stack the metatable of the value at the given + acceptable index. If the index is not valid, or if the value does not + have a metatable, the function returns 0 and pushes nothing on the + stack. + +lua_gettable *lua_gettable()* +> + void lua_gettable (lua_State *L, int index); +< + Pushes onto the stack the value `t[k]`, where `t` is the value at the + given valid index `index` and `k` is the value at the top of the + stack. + + This function pops the key from the stack (putting the resulting value + in its place). As in Lua, this function may trigger a metamethod for + the "index" event (see |luaref-langMetatables|). + +lua_gettop *lua_gettop()* +> + int lua_gettop (lua_State *L); +< + Returns the index of the top element in the stack. Because indices + start at 1, this result is equal to the number of elements in the + stack (and so + 0 means an empty stack). + +lua_insert *lua_insert()* +> + void lua_insert (lua_State *L, int index); +< + Moves the top element into the given valid index, shifting up the + elements above this index to open space. Cannot be called with a + pseudo-index, because a pseudo-index is not an actual stack position. + +lua_Integer *lua_Integer()* +> + typedef ptrdiff_t lua_Integer; +< + The type used by the Lua API to represent integral values. + + By default it is a `ptrdiff_t`, which is usually the largest integral + type the machine handles "comfortably". + +lua_isboolean *lua_isboolean()* +> + 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()* +> + 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()* +> + 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()* +> + 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()* +> + 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()* +> + 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()* +> + 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()* +> + 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()* +> + 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()* +> + 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()* +> + int lua_lessthan (lua_State *L, int index1, int index2); +< + Returns 1 if the value at acceptable index `index1` is smaller than + the value at acceptable index `index2`, following the semantics of the + Lua `<` operator (that is, may call metamethods). Otherwise returns 0. + Also returns 0 if any of the indices is non valid. + +lua_load *lua_load()* +> + int lua_load (lua_State *L, + lua_Reader reader, + void *data, + const char *chunkname); +< + Loads a Lua chunk. If there are no errors, `lua_load` pushes the + compiled chunk as a Lua function on top of the stack. Otherwise, it + pushes an error message. The return values of `lua_load` are: + + - `0`: no errors; + - `LUA_ERRSYNTAX` : syntax error during pre-compilation; + - `LUA_ERRMEM` : memory allocation error. + + This function only loads a chunk; it does not run it. + + `lua_load` automatically detects whether the chunk is text or binary, + and loads it accordingly (see program `luac`, |luaref-luac|). + + The `lua_load` function uses a user-supplied `reader` function to read + the chunk (see |luaref-lua_Reader|). The `data` argument is an opaque + value passed to the reader function. + + The `chunkname` argument gives a name to the chunk, which is used for + error messages and in debug information (see |luaref-apiDebug|). + +lua_newstate *lua_newstate()* +> + lua_State *lua_newstate (lua_Alloc f, void *ud); +< + Creates a new, independent state. Returns `NULL` if cannot create the + state (due to lack of memory). The argument `f` is the allocator + function; Lua does all memory allocation for this state through this + function. The second argument, `ud`, is an opaque pointer that Lua + simply passes to the allocator in every call. + +lua_newtable *lua_newtable()* +> + void lua_newtable (lua_State *L); +< + Creates a new empty table and pushes it onto the stack. It is + equivalent to `lua_createtable(L, 0, 0)` (see + |luaref-lua_createtable|). + +lua_newthread *lua_newthread()* +> + lua_State *lua_newthread (lua_State *L); +< + Creates a new thread, pushes it on the stack, and returns a pointer to + a `lua_State` (see |luaref-lua_State|) that represents this new + thread. The new state returned by this function shares with the + original state all global objects (such as tables), but has an + independent execution stack. + + There is no explicit function to close or to destroy a thread. Threads + are subject to garbage collection, like any Lua object. + +lua_newuserdata *lua_newuserdata()* +> + void *lua_newuserdata (lua_State *L, size_t size); +< + This function allocates a new block of memory with the given size, + pushes onto the stack a new full userdata with the block address, and + returns this address. + *luaref-userdata* + Userdata represents C values in Lua. A full userdata represents a + block of memory. It is an object (like a table): you must create it, + it can have its own metatable, and you can detect when it is being + collected. A full userdata is only equal to itself (under raw + equality). + + When Lua collects a full userdata with a `gc` metamethod, Lua calls + the metamethod and marks the userdata as finalized. When this userdata + is collected again then Lua frees its corresponding memory. + +lua_next *lua_next()* +> + int lua_next (lua_State *L, int index); +< + Pops a key from the stack, and pushes a key-value pair from the table + at the given index (the "next" pair after the given key). If there are + no more elements in the table, then `lua_next` returns 0 (and pushes + nothing). + + *luaref-tabletraversal* + A typical traversal looks like this: +> + /* table is in the stack at index 't' */ + lua_pushnil(L); /* first key */ + while (lua_next(L, t) != 0) { + /* uses 'key' (at index -2) and 'value' (at index -1) */ + printf("%s - %s\n", + lua_typename(L, lua_type(L, -2)), + lua_typename(L, lua_type(L, -1))); + /* removes 'value'; keeps 'key' for next iteration */ + lua_pop(L, 1); + } +< + While traversing a table, do not call `lua_tolstring` (see + |luaref-lua_tolstring|) directly on a key, unless you know that the + key is actually a string. Recall that `lua_tolstring` `changes` the + value at the given index; this confuses the next call to `lua_next`. + +lua_Number *lua_Number()* +> + typedef double lua_Number; +< + The type of numbers in Lua. By default, it is double, but that can be + changed in `luaconf.h`. + + Through the configuration file you can change Lua to operate with + another type for numbers (e.g., float or long). + +lua_objlen *lua_objlen()* +> + size_t lua_objlen (lua_State *L, int index); +< + Returns the "length" of the value at the given acceptable index: for + strings, this is the string length; for tables, this is the result of + the length operator (`#`); for userdata, this is the size of the + block of memory allocated for the userdata; for other values, it is 0. + +lua_pcall *lua_pcall()* +> + lua_pcall (lua_State *L, int nargs, int nresults, int errfunc); +< + Calls a function in protected mode. + + Both `nargs` and `nresults` have the same meaning as in `lua_call` + (see |luaref-lua_call|). If there are no errors during the call, + `lua_pcall` behaves exactly like `lua_call`. However, if there is any + error, `lua_pcall` catches it, pushes a single value on the stack (the + error message), and returns an error code. Like `lua_call`, + `lua_pcall` always removes the function and its arguments from the + stack. + + If `errfunc` is 0, then the error message returned on the stack is + exactly the original error message. Otherwise, `errfunc` is the stack + index of an `error` `handler function`. (In the current + implementation, this index cannot be a pseudo-index.) In case of + runtime errors, this function will be called with the error message + and its return value will be the message returned on the stack by + `lua_pcall`. + + Typically, the error handler function is used to add more debug + information to the error message, such as a stack traceback. Such + information cannot be gathered after the return of `lua_pcall`, since + by then the stack has unwound. + + The `lua_pcall` function returns 0 in case of success or one of the + following error codes (defined in `lua.h`): + + - `LUA_ERRRUN` a runtime error. + - `LUA_ERRMEM` memory allocation error. For such errors, Lua does + not call the error handler function. + - `LUA_ERRERR` error while running the error handler function. + +lua_pop *lua_pop()* +> + void lua_pop (lua_State *L, int n); +< + Pops `n` elements from the stack. + +lua_pushboolean *lua_pushboolean()* +> + void lua_pushboolean (lua_State *L, int b); +< + Pushes a boolean value with value `b` onto the stack. + +lua_pushcclosure *lua_pushcclosure()* +> + void lua_pushcclosure (lua_State *L, lua_CFunction fn, int n); +< + Pushes a new C closure onto the stack. + + When a C function is created, it is possible to associate some values + with it, thus creating a C closure (see |luaref-apiCClosures|); these + values are then accessible to the function whenever it is called. To + associate values with a C function, first these values should be + pushed onto the stack (when there are multiple values, the first value + is pushed first). Then `lua_pushcclosure` is called to create and push + the C function onto the stack, with the argument `n` telling how many + values should be associated with the function. `lua_pushcclosure` also + pops these values from the stack. + +lua_pushcfunction *lua_pushcfunction()* +> + void lua_pushcfunction (lua_State *L, lua_CFunction f); +< + Pushes a C function onto the stack. This function receives a pointer + to a C function and pushes onto the stack a Lua value of type + `function` that, when called, invokes the corresponding C function. + + Any function to be registered in Lua must follow the correct protocol + to receive its parameters and return its results (see + |luaref-lua_CFunction|). + + `lua_pushcfunction` is defined as a macro: +> + #define lua_pushcfunction(L,f) lua_pushcclosure(L,f,0) +< + +lua_pushfstring *lua_pushfstring()* +> + const char *lua_pushfstring (lua_State *L, const char *fmt, ...); +< + Pushes onto the stack a formatted string and returns a pointer to this + string. It is similar to the C function `sprintf`, but has some + important differences: + + - You do not have to allocate space for the result: the result is a + Lua string and Lua takes care of memory allocation (and + deallocation, through garbage collection). + - The conversion specifiers are quite restricted. There are no flags, + widths, or precisions. The conversion specifiers can only be `%%` + (inserts a `%` in the string), `%s` (inserts a zero-terminated + string, with no size restrictions), `%f` (inserts a + `lua_Number`), `%p` (inserts a pointer as a hexadecimal numeral), + `%d` (inserts an `int`), and `%c` (inserts an `int` as a + character). + +lua_pushinteger *lua_pushinteger()* +> + void lua_pushinteger (lua_State *L, lua_Integer n); +< + Pushes a number with value `n` onto the stack. + +lua_pushlightuserdata *lua_pushlightuserdata()* +> + void lua_pushlightuserdata (lua_State *L, void *p); +< + Pushes a light userdata onto the stack. + *luaref-lightuserdata* + Userdata represents C values in Lua. A light userdata represents a + pointer. It is a value (like a number): you do not create it, it has + no individual metatable, and it is not collected (as it was never + created). A light userdata is equal to "any" light userdata with the + same C address. + +lua_pushlstring *lua_pushlstring()* +> + 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. + Lua makes (or reuses) an internal copy of the given string, so the + memory at `s` can be freed or reused immediately after the function + returns. The string can contain embedded zeros. + +lua_pushnil *lua_pushnil()* +> + void lua_pushnil (lua_State *L); +< + Pushes a nil value onto the stack. + +lua_pushnumber *lua_pushnumber()* +> + void lua_pushnumber (lua_State *L, lua_Number n); +< + Pushes a number with value `n` onto the stack. + +lua_pushstring *lua_pushstring()* +> + void lua_pushstring (lua_State *L, const char *s); +< + Pushes the zero-terminated string pointed to by `s` onto the stack. + Lua makes (or reuses) an internal copy of the given string, so the + memory at `s` can be freed or reused immediately after the function + returns. The string cannot contain embedded zeros; it is assumed to + end at the first zero. + +lua_pushthread *lua_pushthread()* +> + 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()* +> + 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()* +> + const char *lua_pushvfstring (lua_State *L, + const char *fmt, + va_list argp); +< + Equivalent to `lua_pushfstring` (see |luaref-pushfstring|), except + that it receives a `va_list` instead of a variable number of + arguments. + +lua_rawequal *lua_rawequal()* +> + int lua_rawequal (lua_State *L, int index1, int index2); +< + Returns 1 if the two values in acceptable indices `index1` and + `index2` are primitively equal (that is, without calling metamethods). + Otherwise returns 0. Also returns 0 if any of the indices are non + valid. + +lua_rawget *lua_rawget()* +> + void lua_rawget (lua_State *L, int index); +< + Similar to `lua_gettable` (see |luaref-lua_gettable|), but does a raw + access (i.e., without metamethods). + +lua_rawgeti *lua_rawgeti()* +> + 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 + given valid index `index`. The access is raw; that is, it does not + invoke metamethods. + +lua_rawset *lua_rawset()* +> + void lua_rawset (lua_State *L, int index); +< + Similar to `lua_settable` (see |luaref-lua_settable|), but does a raw + assignment (i.e., without metamethods). + +lua_rawseti *lua_rawseti()* +> + 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 + valid index `index` and `v` is the value at the top of the stack. + + This function pops the value from the stack. The assignment is raw; + that is, it does not invoke metamethods. + +lua_Reader *lua_Reader()* +> + typedef const char * (*lua_Reader) (lua_State *L, + void *data, + size_t *size); +< + The reader function used by `lua_load` (see |luaref-lua_load|). Every + time it needs another piece of the chunk, `lua_load` calls the reader, + passing along its `data` parameter. The reader must return a pointer + to a block of memory with a new piece of the chunk and set `size` to + the block size. The block must exist until the reader function is + called again. To signal the end of the chunk, the reader must return + `NULL`. The reader function may return pieces of any size greater than + zero. + +lua_register *lua_register()* +> + 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: +> + #define lua_register(L,n,f) \ + (lua_pushcfunction(L, f), lua_setglobal(L, n)) +< + +lua_remove *lua_remove()* +> + void lua_remove (lua_State *L, int index); +< + Removes the element at the given valid index, shifting down the + elements above this index to fill the gap. Cannot be called with a + pseudo-index, because a pseudo-index is not an actual stack position. + +lua_replace *lua_replace()* +> + void lua_replace (lua_State *L, int index); +< + Moves the top element into the given position (and pops it), without + shifting any element (therefore replacing the value at the given + position). + +lua_resume *lua_resume()* +> + int lua_resume (lua_State *L, int narg); +< + Starts and resumes a coroutine in a given thread. + + To start a coroutine, you first create a new thread (see + |luaref-lua_newthread|); then you push onto its stack the main + function plus any arguments; then you call `lua_resume` (see + |luaref-lua_resume|) with `narg` being the number of arguments. This + call returns when the coroutine suspends or finishes its execution. + When it returns, the stack contains all values passed to `lua_yield` + (see |luaref-lua_yield|), or all values returned by the body function. + `lua_resume` returns `LUA_YIELD` if the coroutine yields, 0 if the + coroutine finishes its execution without errors, or an error code in + case of errors (see |luaref-lua_pcall|). In case of errors, the stack + is not unwound, so you can use the debug API over it. The error + message is on the top of the stack. To restart a coroutine, you put on + its stack only the values to be passed as results from `lua_yield`, + and then call `lua_resume`. + +lua_setallocf *lua_setallocf()* +> + 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()* +> + int lua_setfenv (lua_State *L, int index); +< + Pops a table from the stack and sets it as the new environment for the + value at the given index. If the value at the given index is neither a + function nor a thread nor a userdata, `lua_setfenv` returns 0. + Otherwise it returns 1. + +lua_setfield *lua_setfield()* +> + 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 + valid index `index` and `v` is the value at the top of the stack. + + This function pops the value from the stack. As in Lua, this function + may trigger a metamethod for the "newindex" event (see + |luaref-langMetatables|). + +lua_setglobal *lua_setglobal()* +> + 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: +> + #define lua_setglobal(L,s) lua_setfield(L, LUA_GLOBALSINDEX, s) +< + +lua_setmetatable *lua_setmetatable()* +> + 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()* +> + void lua_settable (lua_State *L, int index); +< + Does the equivalent to `t[k] = v`, where `t` is the value at the given + valid index `index`, `v` is the value at the top of the stack, and `k` + is the value just below the top. + + This function pops both the key and the value from the stack. As in + Lua, this function may trigger a metamethod for the "newindex" event + (see |luaref-langMetatables|). + +lua_settop *lua_settop()* +> + void lua_settop (lua_State *L, int index); +< + Accepts any acceptable index, or 0, and sets the stack top to this + index. If the new top is larger than the old one, then the new + elements are filled with `nil`. If `index` is 0, then all stack + elements are removed. + +lua_State *lua_State()* +> + typedef struct lua_State lua_State; +< + Opaque structure that keeps the whole state of a Lua interpreter. The + Lua library is fully reentrant: it has no global variables. All + information about a state is kept in this structure. + + A pointer to this state must be passed as the first argument to every + function in the library, except to `lua_newstate` (see + |luaref-lua_newstate|), which creates a Lua state from scratch. + +lua_status *lua_status()* +> + int lua_status (lua_State *L); +< + Returns the status of the thread `L`. + + The status can be 0 for a normal thread, an error code if the thread + finished its execution with an error, or `LUA_YIELD` if the thread is + suspended. + +lua_toboolean *lua_toboolean()* +> + int lua_toboolean (lua_State *L, int index); +< + Converts the Lua value at the given acceptable index to a C boolean + value (0 or 1). Like all tests in Lua, `lua_toboolean` returns 1 for + any Lua value different from `false` and `nil`; otherwise it returns + 0. It also returns 0 when called with a non-valid index. (If you want + to accept only actual boolean values, use `lua_isboolean` + |luaref-lua_isboolean| to test the value's type.) + +lua_tocfunction *lua_tocfunction()* +> + 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()* +> + lua_Integer lua_tointeger (lua_State *L, int idx); +< + Converts the Lua value at the given acceptable index to the signed + integral type `lua_Integer` (see |luaref-lua_Integer|). The Lua value + must be a number or a string convertible to a number (see + |luaref-langCoercion|); otherwise, `lua_tointeger` returns 0. + + If the number is not an integer, it is truncated in some non-specified + way. + +lua_tolstring *lua_tolstring()* +> + 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 + `len` is not `NULL`, it also sets `*len` with the string length. The + Lua value must be a string or a number; otherwise, the function + returns `NULL`. If the value is a number, then `lua_tolstring` also + `changes the actual value in the stack to a` `string`. (This change + confuses `lua_next` |luaref-lua_next| when `lua_tolstring` is applied + to keys during a table traversal.) + + `lua_tolstring` returns a fully aligned pointer to a string inside the + Lua state. This string always has a zero (`\0`) after its last + character (as in C), but may contain other zeros in its body. Because + Lua has garbage collection, there is no guarantee that the pointer + returned by `lua_tolstring` will be valid after the corresponding + value is removed from the stack. + +lua_tonumber *lua_tonumber()* +> + lua_Number lua_tonumber (lua_State *L, int index); +< + Converts the Lua value at the given acceptable index to the C type + `lua_Number` (see |luaref-lua_Number|). The Lua value must be a number + or a string convertible to a number (see |luaref-langCoercion|); + otherwise, `lua_tonumber` returns 0. + +lua_topointer *lua_topointer()* +> + const void *lua_topointer (lua_State *L, int index); +< + Converts the value at the given acceptable index to a generic C + pointer (`void*`). The value may be a userdata, a table, a thread, or + a function; otherwise, `lua_topointer` returns `NULL`. Different + objects will give different pointers. There is no way to convert the + pointer back to its original value. + + Typically this function is used only for debug information. + +lua_tostring *lua_tostring()* +> + const char *lua_tostring (lua_State *L, int index); +< + Equivalent to `lua_tolstring` (see |luaref-lua_tolstring|) with `len` + equal to `NULL`. + +lua_tothread *lua_tothread()* +> + lua_State *lua_tothread (lua_State *L, int index); +< + Converts the value at the given acceptable index to a Lua thread + (represented as `lua_State*` |luaref-lua_State|). This value must be a + thread; otherwise, the function returns `NULL`. + +lua_touserdata *lua_touserdata()* +> + void *lua_touserdata (lua_State *L, int index); +< + If the value at the given acceptable index is a full userdata, returns + its block address. If the value is a light userdata, returns its + pointer. Otherwise, it returns `NULL`. + +lua_type *lua_type()* +> + int lua_type (lua_State *L, int index); +< + Returns the type of the value in the given acceptable index, or + `LUA_TNONE` for a non-valid index (that is, an index to an "empty" + stack position). The types returned by `lua_type` are coded by the + following constants defined in `lua.h` : `LUA_TNIL`, `LUA_TNUMBER`, + `LUA_TBOOLEAN`, `LUA_TSTRING`, `LUA_TTABLE`, `LUA_TFUNCTION`, + `LUA_TUSERDATA`, `LUA_TTHREAD`, and `LUA_TLIGHTUSERDATA`. + +lua_typename *lua_typename()* +> + 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()* +> + typedef int (*lua_Writer) (lua_State *L, + const void* p, + size_t sz, + void* ud); +< + The writer function used by `lua_dump` (see |luaref-lua_dump|). Every + time it produces another piece of chunk, `lua_dump` calls the writer, + passing along the buffer to be written (`p`), its size (`sz`), and the + `data` parameter supplied to `lua_dump`. + + The writer returns an error code: 0 means no errors; any other value + means an error and stops `lua_dump` from calling the writer again. + +lua_xmove *lua_xmove()* +> + void lua_xmove (lua_State *from, lua_State *to, int n); +< + Exchange values between different threads of the `same` global state. + + This function pops `n` values from the stack `from`, and pushes them + onto the stack `to`. + +lua_yield *lua_yield()* +> + 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: +> + return lua_yield (L, nresults); +< + When a C function calls `lua_yield` in that way, the running coroutine + suspends its execution, and the call to `lua_resume` (see + |luaref-lua_resume|) that started this coroutine returns. The + parameter `nresults` is the number of values from the stack that are + passed as results to `lua_resume`. + + *luaref-stackexample* + As an example of stack manipulation, if the stack starts as + `10 20 30 40 50*` (from bottom to top; the `*` marks the top), then +> + lua_pushvalue(L, 3) --> 10 20 30 40 50 30* + lua_pushvalue(L, -1) --> 10 20 30 40 50 30 30* + lua_remove(L, -3) --> 10 20 30 40 30 30* + lua_remove(L, 6) --> 10 20 30 40 30* + lua_insert(L, 1) --> 30 10 20 30 40* + lua_insert(L, -1) --> 30 10 20 30 40* (no effect) + lua_replace(L, 2) --> 30 40 20 30* + lua_settop(L, -3) --> 30 40* + lua_settop(L, 6) --> 30 40 nil nil nil nil* +< + +============================================================================== +3.8 The Debug Interface *luaref-apiDebug* + +Lua has no built-in debugging facilities. Instead, it offers a special +interface by means of functions and hooks. This interface allows the +construction of different kinds of debuggers, profilers, and other tools that +need "inside information" from the interpreter. + +lua_Debug *lua_Debug()* + + `typedef struct lua_Debug {` + `int event;` + `const char *name; /* (n) */` + `const char *namewhat; /* (n) */` + `const char *what; /* (S) */` + `const char *source; /* (S) */` + `int currentline; /* (l) */` + `int nups; /* (u) number of upvalues */` + `int linedefined; /* (S) */` + `int lastlinedefined; /* (S) */` + `char short_src[LUA_IDSIZE]; /* (S) */` + `/* private part */` + `other fields` + `} lua_Debug;` + +A structure used to carry different pieces of information about an active +function. `lua_getstack` (see |luaref-lua_getstack|) fills only the private part +of this structure, for later use. To fill the other fields of `lua_Debug` with +useful information, call `lua_getinfo` (see |luaref-lua_getinfo|). + +The fields of `lua_Debug` have the following meaning: + + `source` If the function was defined in a string, then `source` is + that string. If the function was defined in a file, then + `source` starts with a `@` followed by the file name. + `short_src` a "printable" version of `source`, to be used in error messages. + `linedefined` the line number where the definition of the function starts. + `lastlinedefined` the line number where the definition of the function ends. + `what` the string `"Lua"` if the function is a Lua function, + `"C"` if it is a C function, `"main"` if it is the main + part of a chunk, and `"tail"` if it was a function that + did a tail call. In the latter case, Lua has no other + information about the function. + `currentline` the current line where the given function is executing. + When no line information is available, `currentline` is + set to -1. + `name` a reasonable name for the given function. Because + functions in Lua are first-class values, they do not have + a fixed name: some functions may be the value of multiple + global variables, while others may be stored only in a + table field. The `lua_getinfo` function checks how the + function was called to find a suitable name. If it cannot + find a name, then `name` is set to `NULL`. + `namewhat` explains the `name` field. The value of `namewhat` can be + `"global"`, `"local"`, `"method"`, `"field"`, + `"upvalue"`, or `""` (the empty string), according to how + the function was called. (Lua uses the empty string when + no other option seems to apply.) `nups` the number of + upvalues of the function. + +lua_gethook *lua_gethook()* +> + lua_Hook lua_gethook (lua_State *L); +< + Returns the current hook function. + +lua_gethookcount *lua_gethookcount()* +> + int lua_gethookcount (lua_State *L); +< + Returns the current hook count. + +lua_gethookmask *lua_gethookmask()* +> + int lua_gethookmask (lua_State *L); +< + Returns the current hook mask. + +lua_getinfo *lua_getinfo()* +> + int lua_getinfo (lua_State *L, const char *what, lua_Debug *ar); +< + Returns information about a specific function or function invocation. + + To get information about a function invocation, the parameter `ar` + must be a valid activation record that was filled by a previous call + to `lua_getstack` (see |luaref-lua_getstack|) or given as argument to + a hook (see |luaref-lua_Hook|). + + To get information about a function you push it onto the stack and + start the `what` string with the character `>`. (In that case, + `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: +> + lua_Debug ar; + lua_getfield(L, LUA_GLOBALSINDEX, "f"); /* get global 'f' */ + lua_getinfo(L, ">S", &ar); + printf("%d\n", ar.linedefined); +< + Each character in the string `what` selects some fields of the + structure `ar` to be filled or a value to be pushed on the stack: + + `'n'` fills in the field `name` and `namewhat` + `'S'` fills in the fields `source`, `short_src`, `linedefined`, + `lastlinedefined`, and `what` + `'l'` fills in the field `currentline` + `'u'` fills in the field `nups` + `'f'` pushes onto the stack the function that is running at the + given level + `'L'` pushes onto the stack a table whose indices are the numbers + of the lines that are valid on the function. (A `valid line` is a + line with some associated code, that is, a line where you can put + a break point. Non-valid lines include empty lines and comments.) + + This function returns 0 on error (for instance, an invalid option in + `what`). + +lua_getlocal *lua_getlocal()* +> + const char *lua_getlocal (lua_State *L, lua_Debug *ar, int n); +< + Gets information about a local variable of a given activation record. + The parameter `ar` must be a valid activation record that was filled + by a previous call to `lua_getstack` (see |luaref-lua_getstack|) or + given as argument to a hook (see |luaref-lua_Hook|). The index `n` + selects which local variable to inspect (1 is the first parameter or + active local variable, and so on, until the last active local + variable). `lua_getlocal` pushes the variable's value onto the stack + and returns its name. + + Variable names starting with `(` (open parentheses) represent + internal variables (loop control variables, temporaries, and C + function locals). + + Returns `NULL` (and pushes nothing) when the index is greater than the + number of active local variables. + +lua_getstack *lua_getstack()* +> + int lua_getstack (lua_State *L, int level, lua_Debug *ar); +< + Gets information about the interpreter runtime stack. + + This function fills parts of a `lua_Debug` (see |luaref-lua_Debug|) + structure with an identification of the `activation record` of the + function executing at a given level. Level 0 is the current running + function, whereas level `n+1` is the function that has called level + `n`. When there are no errors, `lua_getstack` returns 1; when called + with a level greater than the stack depth, it returns 0. + +lua_getupvalue *lua_getupvalue()* +> + const char *lua_getupvalue (lua_State *L, int funcindex, int n); +< + Gets information about a closure's upvalue. (For Lua functions, + upvalues are the external local variables that the function uses, and + that are consequently included in its closure.) `lua_getupvalue` gets + the index `n` of an upvalue, pushes the upvalue's value onto the + stack, and returns its name. `funcindex` points to the closure in the + stack. (Upvalues have no particular order, as they are active through + the whole function. So, they are numbered in an arbitrary order.) + + Returns `NULL` (and pushes nothing) when the index is greater than the + number of upvalues. For C functions, this function uses the empty + string `""` as a name for all upvalues. + +lua_Hook *lua_Hook()* +> + typedef void (*lua_Hook) (lua_State *L, lua_Debug *ar); +< + Type for debugging hook functions. + + Whenever a hook is called, its `ar` argument has its field `event` set + to the specific event that triggered the hook. Lua identifies these + events with the following constants: `LUA_HOOKCALL`, `LUA_HOOKRET`, + `LUA_HOOKTAILRET`, `LUA_HOOKLINE`, and `LUA_HOOKCOUNT`. Moreover, for + line events, the field `currentline` is also set. To get the value of + any other field in `ar`, the hook must call `lua_getinfo` (see + |luaref-lua_getinfo|). For return events, `event` may be + `LUA_HOOKRET`, the normal value, or `LUA_HOOKTAILRET`. In the latter + case, Lua is simulating a return from a function that did a tail call; + in this case, it is useless to call `lua_getinfo`. + + While Lua is running a hook, it disables other calls to hooks. + Therefore, if a hook calls back Lua to execute a function or a chunk, + this execution occurs without any calls to hooks. + + +lua_sethook *lua_sethook()* +> + int lua_sethook (lua_State *L, lua_Hook f, int mask, int count); +< + Sets the debugging hook function. + + Argument `f` is the hook function. `mask` specifies on which events + the hook will be called: it is formed by a bitwise `or` of the + constants `LUA_MASKCALL`, `LUA_MASKRET`, `LUA_MASKLINE`, and + `LUA_MASKCOUNT`. The `count` argument is only meaningful when the mask + includes `LUA_MASKCOUNT`. For each event, the hook is called as + explained below: + + - `The call hook`: is called when the interpreter calls a function. + The hook is called just after Lua enters the new function, before + the function gets its arguments. + - `The return hook`: is called when the interpreter returns from a + function. The hook is called just before Lua leaves the function. + You have no access to the values to be returned by the function. + - `The line hook`: is called when the interpreter is about to start + the execution of a new line of code, or when it jumps back in the + code (even to the same line). (This event only happens while Lua is + executing a Lua function.) + - `The count hook`: is called after the interpreter executes every + `count` instructions. (This event only happens while Lua is + executing a Lua function.) + + A hook is disabled by setting `mask` to zero. + +lua_setlocal *lua_setlocal()* +> + const char *lua_setlocal (lua_State *L, lua_Debug *ar, int n); +< + Sets the value of a local variable of a given activation record. + Parameters `ar` and `n` are as in `lua_getlocal` (see + |luaref-lua_getlocal|). `lua_setlocal` assigns the value at the top of + the stack to the variable and returns its name. It also pops the value + from the stack. + + Returns `NULL` (and pops nothing) when the index is greater than the + number of active local variables. + +lua_setupvalue *lua_setupvalue()* +> + 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 + of the stack to the upvalue and returns its name. It also pops the + value from the stack. Parameters `funcindex` and `n` are as in the + `lua_getupvalue` (see |luaref-lua_getupvalue|). + + Returns `NULL` (and pops nothing) when the index is greater than the + number of upvalues. + + *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: +> + int listvars (lua_State *L, int level) { + lua_Debug ar; + int i; + const char *name; + if (lua_getstack(L, level, &ar) == 0) + return 0; /* failure: no such level in the stack */ + i = 1; + while ((name = lua_getlocal(L, &ar, i++)) != NULL) { + printf("local %d %s\n", i-1, name); + lua_pop(L, 1); /* remove variable value */ + } + lua_getinfo(L, "f", &ar); /* retrieves function */ + i = 1; + while ((name = lua_getupvalue(L, -1, i++)) != NULL) { + printf("upvalue %d %s\n", i-1, name); + lua_pop(L, 1); /* remove upvalue value */ + } + return 1; + } +< + +============================================================================== +4 THE AUXILIARY LIBRARY *luaref-aux* +============================================================================== + +The auxiliary library provides several convenient functions to interface C +with Lua. While the basic API provides the primitive functions for all +interactions between C and Lua, the auxiliary library provides higher-level +functions for some common tasks. + +All functions from the auxiliary library are defined in header file `lauxlib.h` +and have a prefix `luaL_`. + +All functions in the auxiliary library are built on top of the basic API, and +so they provide nothing that cannot be done with this API. + +Several functions in the auxiliary library are used to check C function +arguments. Their names are always `luaL_check*` or `luaL_opt*`. All of these +functions raise an error if the check is not satisfied. Because the error +message is formatted for arguments (e.g., "bad argument #1"), you should not +use these functions for other stack values. + +============================================================================== +4.1 Functions and Types *luaref-auxFunctions* + +Here we list all functions and types from the auxiliary library in +alphabetical order. + +luaL_addchar *luaL_addchar()* +> + void luaL_addchar (luaL_Buffer *B, char c); +< + Adds the character `c` to the buffer `B` (see |luaref-luaL_Buffer|). + +luaL_addlstring *luaL_addlstring()* +> + 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 |luaref-luaL_Buffer|). The string may contain embedded zeros. + +luaL_addsize *luaL_addsize()* +> + void luaL_addsize (luaL_Buffer *B, size_t n); +< + Adds to the buffer `B` (see |luaref-luaL_Buffer|) a string of length + `n` previously copied to the buffer area (see + |luaref-luaL_prepbuffer|). + +luaL_addstring *luaL_addstring()* +> + void luaL_addstring (luaL_Buffer *B, const char *s); +< + Adds the zero-terminated string pointed to by `s` to the buffer `B` + (see |luaref-luaL_Buffer|). The string may not contain embedded zeros. + +luaL_addvalue *luaL_addvalue()* +> + void luaL_addvalue (luaL_Buffer *B); +< + Adds the value at the top of the stack to the buffer `B` (see + |luaref-luaL_Buffer|). Pops the value. + + This is the only function on string buffers that can (and must) be + called with an extra element on the stack, which is the value to be + added to the buffer. + +luaL_argcheck *luaL_argcheck()* +> + void luaL_argcheck (lua_State *L, + int cond, + int narg, + const char *extramsg); +< + Checks whether `cond` is true. If not, raises an error with the + following message, where `func` is retrieved from the call stack: +> + bad argument #<narg> to <func> (<extramsg>) +< + +luaL_argerror *luaL_argerror()* +> + int luaL_argerror (lua_State *L, int narg, const char *extramsg); +< + Raises an error with the following message, where `func` is retrieved + from the call stack: +> + bad argument #<narg> to <func> (<extramsg>) +< + This function never returns, but it is an idiom to use it in C + functions as `return luaL_argerror(` `args` `)`. + +luaL_Buffer *luaL_Buffer()* +> + typedef struct luaL_Buffer luaL_Buffer; +< + Type for a `string buffer`. + + A string buffer allows C code to build Lua strings piecemeal. Its + pattern of use is as follows: + + - First you declare a variable `b` of type `luaL_Buffer`. + - Then you initialize it with a call `luaL_buffinit(L, &b)` (see + |luaref-luaL_buffinit|). + - Then you add string pieces to the buffer calling any of the + `luaL_add*` functions. + - You finish by calling `luaL_pushresult(&b)` (see + |luaref-luaL_pushresult|). This call leaves the final string on the + top of the stack. + + During its normal operation, a string buffer uses a variable number of + stack slots. So, while using a buffer, you cannot assume that you know + where the top of the stack is. You can use the stack between + successive calls to buffer operations as long as that use is balanced; + that is, when you call a buffer operation, the stack is at the same + level it was immediately after the previous buffer operation. (The + only exception to this rule is `luaL_addvalue` + |luaref-luaL_addvalue|.) After calling `luaL_pushresult` the stack is + back to its level when the buffer was initialized, plus the final + string on its top. + +luaL_buffinit *luaL_buffinit()* +> + 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 |luaref-luaL_Buffer|). + +luaL_callmeta *luaL_callmeta()* +> + int luaL_callmeta (lua_State *L, int obj, const char *e); +< + Calls a metamethod. + + If the object at index `obj` has a metatable and this metatable has a + field `e`, this function calls this field and passes the object as its + only argument. In this case this function returns 1 and pushes onto + the stack the value returned by the call. If there is no metatable or + no metamethod, this function returns + 0 (without pushing any value on the stack). + +luaL_checkany *luaL_checkany()* +> + 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()* +> + 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()* +> + 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 |luaref-lua_Integer|). + +luaL_checklong *luaL_checklong()* +> + 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()* +> + 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()* +> + lua_Number luaL_checknumber (lua_State *L, int narg); +< + Checks whether the function argument `narg` is a number and returns + this number (see |luaref-lua_Number|). + +luaL_checkoption *luaL_checkoption()* +> + int luaL_checkoption (lua_State *L, + int narg, + const char *def, + const char *const lst[]); +< + Checks whether the function argument `narg` is a string and searches + for this string in the array `lst` (which must be NULL-terminated). + Returns the index in the array where the string was found. Raises an + error if the argument is not a string or if the string cannot be + found. + + If `def` is not `NULL`, the function uses `def` as a default value + when there is no argument `narg` or if this argument is `nil`. + + This is a useful function for mapping strings to C enums. (The usual + convention in Lua libraries is to use strings instead of numbers to + select options.) + +luaL_checkstack *luaL_checkstack()* +> + void luaL_checkstack (lua_State *L, int sz, const char *msg); +< + Grows the stack size to `top + sz` elements, raising an error if the + stack cannot grow to that size. `msg` is an additional text to go into + the error message. + +luaL_checkstring *luaL_checkstring()* +> + 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()* +> + void luaL_checktype (lua_State *L, int narg, int t); +< + Checks whether the function argument `narg` has type `t` (see + |luaref-lua_type|). + +luaL_checkudata *luaL_checkudata()* +> + 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 |luaref-luaL_newmetatable|). + +luaL_dofile *luaL_dofile()* +> + int luaL_dofile (lua_State *L, const char *filename); +< + Loads and runs the given file. It is defined as the following macro: +> + (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()* +> + int luaL_dostring (lua_State *L, const char *str); +< + Loads and runs the given string. It is defined as the following macro: +> + (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()* +> + int luaL_error (lua_State *L, const char *fmt, ...); +< + Raises an error. The error message format is given by `fmt` plus any + extra arguments, following the same rules of `lua_pushfstring` (see + |luaref-lua_pushfstring|). It also adds at the beginning of the + message the file name and the line number where the error occurred, if + this information is available. + + This function never returns, but it is an idiom to use it in C + functions as `return luaL_error(` `args` `)`. + +luaL_getmetafield *luaL_getmetafield()* +> + int luaL_getmetafield (lua_State *L, int obj, const char *e); +< + Pushes onto the stack the field `e` from the metatable of the object + at index `obj`. If the object does not have a metatable, or if the + metatable does not have this field, returns 0 and pushes nothing. + +luaL_getmetatable *luaL_getmetatable()* +> + void luaL_getmetatable (lua_State *L, const char *tname); +< + Pushes onto the stack the metatable associated with name `tname` in + the registry (see |luaref-luaL_newmetatable|). + +luaL_gsub *luaL_gsub()* +> + const char *luaL_gsub (lua_State *L, + const char *s, + const char *p, + const char *r); +< + Creates a copy of string `s` by replacing any occurrence of the string + `p` with the string `r`. Pushes the resulting string on the stack and + returns it. + +luaL_loadbuffer *luaL_loadbuffer()* +> + int luaL_loadbuffer (lua_State *L, + const char *buff, + size_t sz, + const char *name); +< + Loads a buffer as a Lua chunk. This function uses `lua_load` (see + |luaref-lua_load|) to load the chunk in the buffer pointed to by + `buff` with size `sz`. + + This function returns the same results as `lua_load`. `name` is the + chunk name, used for debug information and error messages. + +luaL_loadfile *luaL_loadfile()* +> + int luaL_loadfile (lua_State *L, const char *filename); +< + Loads a file as a Lua chunk. This function uses `lua_load` (see + |luaref-lua_load|) to load the chunk in the file named `filename`. If + `filename` is `NULL`, then it loads from the standard input. The first + line in the file is ignored if it starts with a `#`. + + This function returns the same results as `lua_load`, but it has an + extra error code `LUA_ERRFILE` if it cannot open/read the file. + + As `lua_load`, this function only loads the chunk; it does not run it. + +luaL_loadstring *luaL_loadstring()* +> + int luaL_loadstring (lua_State *L, const char *s); +< + Loads a string as a Lua chunk. This function uses `lua_load` (see + |luaref-lua_load|) to load the chunk in the zero-terminated string + `s`. + + This function returns the same results as `lua_load`. + + Also as `lua_load`, this function only loads the chunk; it does not + run it. + +luaL_newmetatable *luaL_newmetatable()* +> + int luaL_newmetatable (lua_State *L, const char *tname); +< + If the registry already has the key `tname`, returns 0. Otherwise, + creates a new table to be used as a metatable for userdata, adds it to + the registry with key `tname`, and returns 1. + + In both cases pushes onto the stack the final value associated with + `tname` in the registry. + +luaL_newstate *luaL_newstate()* +> + lua_State *luaL_newstate (void); +< + Creates a new Lua state. It calls `lua_newstate` (see + |luaref-lua_newstate|) with an allocator based on the standard C + `realloc` function and then sets a panic function (see + |luaref-lua_atpanic|) that prints an error message to the standard + error output in case of fatal errors. + + Returns the new state, or `NULL` if there is a memory allocation + error. + +luaL_openlibs *luaL_openlibs()* +> + 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()* +> + int luaL_optint (lua_State *L, int narg, int d); +< + If the function argument `narg` is a number, returns this number cast + to an `int`. If this argument is absent or is `nil`, returns `d`. + Otherwise, raises an error. + +luaL_optinteger *luaL_optinteger()* +> + lua_Integer luaL_optinteger (lua_State *L, + int narg, + lua_Integer d); +< + If the function argument `narg` is a number, returns this number cast + to a `lua_Integer` (see |luaref-lua_Integer|). If this argument is + absent or is `nil`, returns `d`. Otherwise, raises an error. + +luaL_optlong *luaL_optlong()* +> + long luaL_optlong (lua_State *L, int narg, long d); +< + If the function argument `narg` is a number, returns this number cast + to a `long`. If this argument is absent or is `nil`, returns `d`. + Otherwise, raises an error. + +luaL_optlstring *luaL_optlstring()* +> + const char *luaL_optlstring (lua_State *L, + int narg, + const char *d, + size_t *l); +< + If the function argument `narg` is a string, returns this string. If + this argument is absent or is `nil`, returns `d`. Otherwise, raises an + error. + + If `l` is not `NULL`, fills the position `*l` with the results' length. + +luaL_optnumber *luaL_optnumber()* +> + lua_Number luaL_optnumber (lua_State *L, int narg, lua_Number d); +< + If the function argument `narg` is a number, returns this number. If + this argument is absent or is `nil`, returns `d`. Otherwise, raises an + error. + +luaL_optstring *luaL_optstring()* +> + const char *luaL_optstring (lua_State *L, + int narg, + const char *d); +< + If the function argument `narg` is a string, returns this string. If + this argument is absent or is `nil`, returns `d`. Otherwise, raises an + error. + +luaL_prepbuffer *luaL_prepbuffer()* +> + char *luaL_prepbuffer (luaL_Buffer *B); +< + Returns an address to a space of size `LUAL_BUFFERSIZE` where you can + copy a string to be added to buffer `B` (see |luaref-luaL_Buffer|). + After copying the string into this space you must call `luaL_addsize` + (see |luaref-luaL_addsize|) with the size of the string to actually + add it to the buffer. + +luaL_pushresult *luaL_pushresult()* +> + 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()* +> + int luaL_ref (lua_State *L, int t); +< + Creates and returns a `reference`, in the table at index `t`, for the + object at the top of the stack (and pops the object). + + A reference is a unique integer key. As long as you do not manually + add integer keys into table `t`, `luaL_ref` ensures the uniqueness of + the key it returns. You can retrieve an object referred by reference + `r` by calling `lua_rawgeti(L, t, r)` (see |luaref-lua_rawgeti|). + Function `luaL_unref` (see |luaref-luaL_unref|) frees a reference and + its associated object. + + If the object at the top of the stack is `nil`, `luaL_ref` returns the + constant `LUA_REFNIL`. The constant `LUA_NOREF` is guaranteed to be + different from any reference returned by `luaL_ref`. + +luaL_Reg *luaL_Reg()* +> + typedef struct luaL_Reg { + const char *name; + lua_CFunction func; + } luaL_Reg; +< + Type for arrays of functions to be registered by `luaL_register` (see + |luaref-luaL_register|). `name` is the function name and `func` is a + pointer to the function. Any array of `luaL_Reg` must end with a + sentinel entry in which both `name` and `func` are `NULL`. + +luaL_register *luaL_register()* +> + void luaL_register (lua_State *L, + const char *libname, + const luaL_Reg *l); +< + Opens a library. + + When called with `libname` equal to `NULL`, it simply registers all + functions in the list `l` (see |luaref-luaL_Reg|) into the table on + the top of the stack. + + When called with a non-null `libname`, `luaL_register` creates a new + table `t`, sets it as the value of the global variable `libname`, sets + it as the value of `package.loaded[libname]`, and registers on it all + functions in the list `l`. If there is a table in + `package.loaded[libname]` or in variable `libname`, reuses this table + instead of creating a new one. + + In any case the function leaves the table on the top of the stack. + +luaL_typename *luaL_typename()* +> + 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()* +> + int luaL_typerror (lua_State *L, int narg, const char *tname); +< + Generates an error with a message like the following: + + `location` `: bad argument` `narg` `to` `'func'` `(` `tname` + `expected, got` `rt` `)` + + where `location` is produced by `luaL_where` (see + |luaref-luaL_where|), `func` is the name of the current function, and + `rt` is the type name of the actual argument. + +luaL_unref *luaL_unref()* +> + void luaL_unref (lua_State *L, int t, int ref); +< + Releases reference `ref` from the table at index `t` (see + |luaref-luaL_ref|). The entry is removed from the table, so that the + referred object can be collected. The reference `ref` is also freed to + be used again. + + If `ref` is `LUA_NOREF` or `LUA_REFNIL`, `luaL_unref` does nothing. + +luaL_where *luaL_where()* +> + void luaL_where (lua_State *L, int lvl); +< + Pushes onto the stack a string identifying the current position of the + control at level `lvl` in the call stack. Typically this string has + the following format: + + `chunkname:currentline:` + + Level 0 is the running function, level 1 is the function that called + the running function, etc. + + This function is used to build a prefix for error messages. + +============================================================================== +5 STANDARD LIBRARIES *luaref-Lib* +============================================================================== + +The standard libraries provide useful functions that are implemented directly +through the C API. Some of these functions provide essential services to the +language (e.g., `type` and `getmetatable`); others provide access to "outside" +services (e.g., I/O); and others could be implemented in Lua itself, but are +quite useful or have critical performance requirements that deserve an +implementation in C (e.g., `sort`). + +All libraries are implemented through the official C API and are provided as +separate C modules. Currently, Lua has the following standard libraries: + +- basic library; +- package library; +- string manipulation; +- table manipulation; +- mathematical functions (sin, log, etc.); +- input and output; +- operating system facilities; +- debug facilities. + +Except for the basic and package libraries, each library provides all its +functions as fields of a global table or as methods of its objects. + + *luaref-openlibs* +To have access to these libraries, the C host program should call the +`luaL_openlibs` function, which opens all standard libraries (see +|luaref-luaL_openlibs|). Alternatively, the host program can open the libraries +individually by calling `luaopen_base` (for the basic library), +`luaopen_package` (for the package library), `luaopen_string` (for the string +library), `luaopen_table` (for the table library), `luaopen_math` (for the +mathematical library), `luaopen_io` (for the I/O and the Operating System +libraries), and `luaopen_debug` (for the debug library). These functions are +declared in `lualib.h` and should not be called directly: you must call them +like any other Lua C function, e.g., by using `lua_call` (see |luaref-lua_call|). + +============================================================================== +5.1 Basic Functions *luaref-libBasic* + +The basic library provides some core functions to Lua. If you do not include +this library in your application, you should check carefully whether you need +to provide implementations for some of its facilities. + +assert({v} [, {message}]) *luaref-assert()* + Issues an error when the value of its argument `v` is false (i.e., `nil` or + `false`); otherwise, returns all its arguments. `message` is an error message; + when absent, it defaults to "assertion failed!" + +collectgarbage({opt} [, {arg}]) *luaref-collectgarbage()* + This function is a generic interface to the garbage collector. It + performs different functions according to its first argument, {opt}: + + `"stop"` stops the garbage collector. + `"restart"` restarts the garbage collector. + `"collect"` performs a full garbage-collection cycle. + `"count"` returns the total memory in use by Lua (in Kbytes). + `"step"` performs a garbage-collection step. The step "size" is + controlled by {arg} (larger values mean more steps) in a + non-specified way. If you want to control the step size + you must experimentally tune the value of {arg}. Returns + `true` if the step finished a collection cycle. + `"setpause"` sets {arg} /100 as the new value for the `pause` of + the collector (see |luaref-langGC|). + `"setstepmul"` sets {arg} /100 as the new value for the `step + multiplier` of the collector (see |luaref-langGC|). + +dofile({filename}) *luaref-dofile()* + Opens the named file and executes its contents as a Lua chunk. When + called without arguments, `dofile` executes the contents of the + standard input (`stdin`). Returns all values returned by the chunk. In + case of errors, `dofile` propagates the error to its caller (that is, + `dofile` does not run in protected mode). + +error({message} [, {level}]) *luaref-error()* + Terminates the last protected function called and returns `message` as + the error message. Function {error} never returns. + + Usually, {error} adds some information about the error position at the + beginning of the message. The {level} argument specifies how to get + the error position. With level 1 (the default), the error position is + where the {error} function was called. Level 2 points the error to + where the function that called {error} was called; and so on. Passing + a level 0 avoids the addition of error position information to the + message. + +_G *luaref-_G()* + A global variable (not a function) that holds the global environment + (that is, `_G._G = _G`). Lua itself does not use this variable; + changing its value does not affect any environment, nor vice-versa. + (Use `setfenv` to change environments.) + +getfenv({f}) *luaref-getfenv()* + Returns the current environment in use by the function. {f} can be a + Lua function or a number that specifies the function at that stack + level: Level 1 is the function calling `getfenv`. If the given + function is not a Lua function, or if {f} is 0, `getfenv` returns the + global environment. The default for {f} is 1. + +getmetatable({object}) *luaref-getmetatable()* + If {object} does not have a metatable, returns `nil`. Otherwise, if + the object's metatable has a `"__metatable"` field, returns the + associated value. Otherwise, returns the metatable of the given + object. + +ipairs({t}) *luaref-ipairs()* + Returns three values: an iterator function, the table {t}, and 0, so + that the construction + + `for i,v in ipairs(t) do` `body` `end` + + will iterate over the pairs (`1,t[1]`), (`2,t[2]`), ..., up to the + first integer key absent from the table. + +load({func} [, {chunkname}]) *luaref-load()* + Loads a chunk using function {func} to get its pieces. Each call to + {func} must return a string that concatenates with previous results. A + return of `nil` (or no value) signals the end of the chunk. + + If there are no errors, returns the compiled chunk as a function; + otherwise, returns `nil` plus the error message. The environment of + the returned function is the global environment. + + {chunkname} is used as the chunk name for error messages and debug + information. + +loadfile([{filename}]) *luaref-loadfile()* + Similar to `load` (see |luaref-load|), but gets the chunk from file + {filename} or from the standard input, if no file name is given. + +loadstring({string} [, {chunkname}]) *luaref-loadstring()* + Similar to `load` (see |luaref-load|), but gets the chunk from the + given {string}. + + To load and run a given string, use the idiom +> + assert(loadstring(s))() +< + +next({table} [, {index}]) *luaref-next()* + Allows a program to traverse all fields of a table. Its first argument + is a table and its second argument is an index in this table. `next` + returns the next index of the table and its associated value. When + called with `nil` as its second argument, `next` returns an initial + index and its associated value. When called with the last index, or + with `nil` in an empty table, `next` returns `nil`. If the second + argument is absent, then it is interpreted as `nil`. In particular, + you can use `next(t)` to check whether a table is empty. + + The order in which the indices are enumerated is not specified, `even + for` `numeric indices`. (To traverse a table in numeric order, use a + numerical `for` or the `ipairs` |luaref-ipairs| function.) + + The behavior of `next` is `undefined` if, during the traversal, you + assign any value to a non-existent field in the table. You may however + modify existing fields. In particular, you may clear existing fields. + +pairs({t}) *luaref-pairs()* + Returns three values: the `next` |luaref-next| function, the table + {t}, and `nil`, so that the construction + + `for k,v in pairs(t) do` `body` `end` + + will iterate over all key-value pairs of table {t}. + +pcall({f}, {arg1}, {...}) *luaref-pcall()* + Calls function {f} with the given arguments in `protected mode`. This + means that any error inside {f} is not propagated; instead, `pcall` + catches the error and returns a status code. Its first result is the + status code (a boolean), which is `true` if the call succeeds without + errors. In such case, `pcall` also returns all results from the call, + after this first result. In case of any error, `pcall` returns `false` + plus the error message. + +print({...}) *luaref-print()* + Receives any number of arguments, and prints their values to `stdout`, + using the `tostring` |luaref-tostring| function to convert them to + strings. `print` is not intended for formatted output, but only as a + quick way to show a value, typically for debugging. For formatted + output, use `string.format` (see |luaref-string.format|). + +rawequal({v1}, {v2}) *luaref-rawequal()* + Checks whether {v1} is equal to {v2}, without invoking any metamethod. + Returns a boolean. + +rawget({table}, {index}) *luaref-rawget()* + Gets the real value of `table[index]`, without invoking any + metamethod. {table} must be a table; {index} may be any value. + +rawset({table}, {index}, {value}) *luaref-rawset()* + Sets the real value of `table[index]` to {value}, without invoking any + metamethod. {table} must be a table, {index} any value different from + `nil`, and {value} any Lua value. + + This function returns {table}. + +select({index}, {...}) *luaref-select()* + If {index} is a number, returns all arguments after argument number + {index}. Otherwise, {index} must be the string `"#"`, and `select` + returns the total number of extra arguments it received. + +setfenv({f}, {table}) *luaref-setfenv()* + Sets the environment to be used by the given function. {f} can be a + Lua function or a number that specifies the function at that stack + level: Level 1 is the function calling `setfenv`. `setfenv` returns + the given function. + + As a special case, when {f} is 0 `setfenv` changes the environment of + the running thread. In this case, `setfenv` returns no values. + +setmetatable({table}, {metatable}) *luaref-setmetatable()* + Sets the metatable for the given table. (You cannot change the + metatable of other types from Lua, only from C.) If {metatable} is + `nil`, removes the metatable of the given table. If the original + metatable has a `"__metatable"` field, raises an error. + + This function returns {table}. + +tonumber({e} [, {base}]) *luaref-tonumber()* + Tries to convert its argument to a number. If the argument is already + a number or a string convertible to a number, then `tonumber` returns + this number; otherwise, it returns `nil`. + + An optional argument specifies the base to interpret the numeral. The + base may be any integer between 2 and 36, inclusive. In bases above + 10, the letter `A` (in either upper or lower case) represents 10, `B` + represents 11, and so forth, with `Z'` representing 35. In base 10 + (the default), the number may have a decimal part, as well as an + optional exponent part (see |luaref-langLexConv|). In other bases, + only unsigned integers are accepted. + +tostring({e}) *luaref-tostring()* + Receives an argument of any type and converts it to a string in a + reasonable format. For complete control of how numbers are converted, + use `string.format` (see |luaref-string.format|). + + *__tostring* + If the metatable of {e} has a `"__tostring"` field, `tostring` calls + the corresponding value with {e} as argument, and uses the result of + the call as its result. + +type({v}) *luaref-type()* + Returns the type of its only argument, coded as a string. The possible + results of this function are `"nil"` (a string, not the value `nil`), + `"number"`, `"string"`, `"boolean`, `"table"`, `"function"`, + `"thread"`, and `"userdata"`. + +unpack({list} [, {i} [, {j}]]) *luaref-unpack()* + Returns the elements from the given table. This function is equivalent + to +> + return list[i], list[i+1], ..., list[j] +< + except that the above code can be written only for a fixed number of + elements. By default, {i} is 1 and {j} is the length of the list, as + defined by the length operator(see |luaref-langLength|). + +_VERSION *luaref-_VERSION()* + A global variable (not a function) that holds a string containing the + current interpreter version. The current contents of this string is + `"Lua 5.1"` . + +xpcall({f}, {err}) *luaref-xpcall()* + This function is similar to `pcall` (see |luaref-pcall|), except that + you can set a new error handler. + + `xpcall` calls function {f} in protected mode, using {err} as the + error handler. Any error inside {f} is not propagated; instead, + `xpcall` catches the error, calls the {err} function with the original + error object, and returns a status code. Its first result is the + status code (a boolean), which is true if the call succeeds without + errors. In this case, `xpcall` also returns all results from the call, + after this first result. In case of any error, `xpcall` returns + `false` plus the result from {err}. + +============================================================================== +5.2 Coroutine Manipulation *luaref-libCoro* + +The operations related to coroutines comprise a sub-library of the basic +library and come inside the table `coroutine`. See |luaref-langCoro| for a +general description of coroutines. + +coroutine.create({f}) *coroutine.create()* + Creates a new coroutine, with body {f}. {f} must be a Lua function. + Returns this new coroutine, an object with type `"thread"`. + +coroutine.resume({co} [, {val1}, {...}]) *coroutine.resume()* + Starts or continues the execution of coroutine {co}. The first time + you resume a coroutine, it starts running its body. The values {val1}, + {...} are passed as arguments to the body function. If the coroutine has + yielded, `resume` restarts it; the values {val1}, {...} are passed as + the results from the yield. + + If the coroutine runs without any errors, `resume` returns `true` plus + any values passed to `yield` (if the coroutine yields) or any values + returned by the body function(if the coroutine terminates). If there + is any error, `resume` returns `false` plus the error message. + +coroutine.running() *coroutine.running()* + Returns the running coroutine, or `nil` when called by the main + thread. + +coroutine.status({co}) *coroutine.status()* + Returns the status of coroutine {co}, as a string: `"running"`, if the + coroutine is running (that is, it called `status`); `"suspended"`, if + the coroutine is suspended in a call to `yield`, or if it has not + started running yet; `"normal"` if the coroutine is active but not + running (that is, it has resumed another coroutine); and `"dead"` if + the coroutine has finished its body function, or if it has stopped + with an error. + +coroutine.wrap({f}) *coroutine.wrap()* + Creates a new coroutine, with body {f}. {f} must be a Lua function. + Returns a function that resumes the coroutine each time it is called. + Any arguments passed to the function behave as the extra arguments to + `resume`. Returns the same values returned by `resume`, except the + first boolean. In case of error, propagates the error. + +coroutine.yield({...}) *coroutine.yield()* + Suspends the execution of the calling coroutine. The coroutine cannot + be running a C function, a metamethod, or an iterator. Any arguments + to `yield` are passed as extra results to `resume`. + +============================================================================== +5.3 - Modules *luaref-libModule* + +The package library provides basic facilities for loading and building modules +in Lua. It exports two of its functions directly in the global environment: +`require` and `module` (see |luaref-require| and |luaref-module|). Everything else is +exported in a table `package`. + +module({name} [, {...}]) *luaref-module()* + Creates a module. If there is a table in `package.loaded[name]`, this + table is the module. Otherwise, if there is a global table `t` with + the given name, this table is the module. Otherwise creates a new + table `t` and sets it as the value of the global {name} and the value + of `package.loaded[name]`. This function also initializes `t._NAME` + with the given name, `t._M` with the module (`t` itself), and + `t._PACKAGE` with the package name (the full module name minus last + component; see below). Finally, `module` sets `t` as the new + environment of the current function and the new value of + `package.loaded[name]`, so that `require` (see |luaref-require|) + returns `t`. + + If {name} is a compound name (that is, one with components separated + by dots), `module` creates (or reuses, if they already exist) tables + for each component. For instance, if {name} is `a.b.c`, then `module` + stores the module table in field `c` of field `b` of global `a`. + + This function may receive optional `options` after the module name, + where each option is a function to be applied over the module. + +require({modname}) *luaref-require()* + Loads the given module. The function starts by looking into the + `package.loaded` table to determine whether {modname} is already + loaded. If it is, then `require` returns the value stored at + `package.loaded[modname]`. Otherwise, it tries to find a `loader` for + the module. + + To find a loader, first `require` queries `package.preload[modname]`. + If it has a value, this value (which should be a function) is the + loader. Otherwise `require` searches for a Lua loader using the path + stored in `package.path`. If that also fails, it searches for a C + loader using the path stored in `package.cpath`. If that also fails, + it tries an `all-in-one` loader (see below). + + When loading a C library, `require` first uses a dynamic link facility + to link the application with the library. Then it tries to find a C + function inside this library to be used as the loader. The name of + this C function is the string `"luaopen_"` concatenated with a copy of + the module name where each dot is replaced by an underscore. Moreover, + if the module name has a hyphen, its prefix up to (and including) the + first hyphen is removed. For instance, if the module name is + `a.v1-b.c`, the function name will be `luaopen_b_c`. + + If `require` finds neither a Lua library nor a C library for a module, + it calls the `all-in-one loader`. This loader searches the C path for + a library for the root name of the given module. For instance, when + requiring `a.b.c`, it will search for a C library for `a`. If found, + it looks into it for an open function for the submodule; in our + example, that would be `luaopen_a_b_c`. With this facility, a package + can pack several C submodules into one single library, with each + submodule keeping its original open function. + + Once a loader is found, `require` calls the loader with a single + argument, {modname}. If the loader returns any value, `require` + assigns the returned value to `package.loaded[modname]`. If the loader + returns no value and has not assigned any value to + `package.loaded[modname]`, then `require` assigns `true` to this + entry. In any case, `require` returns the final value of + `package.loaded[modname]`. + + If there is any error loading or running the module, or if it cannot + find any loader for the module, then `require` signals an error. + +package.cpath *package.cpath()* + The path used by `require` to search for a C loader. + + Lua initializes the C path `package.cpath` in the same way it + initializes the Lua path `package.path`, using the environment + variable `LUA_CPATH` (plus another default path defined in + `luaconf.h`). + +package.loaded *package.loaded()* + A table used by `require` to control which modules are already loaded. + When you require a module `modname` and `package.loaded[modname]` is + not false, `require` simply returns the value stored there. + +package.loadlib({libname}, {funcname}) *package.loadlib()* + Dynamically links the host program with the C library {libname}. + Inside this library, looks for a function {funcname} and returns this + function as a C function. (So, {funcname} must follow the protocol + (see |luaref-lua_CFunction|)). + + This is a low-level function. It completely bypasses the package and + module system. Unlike `require`, it does not perform any path + searching and does not automatically adds extensions. {libname} must + be the complete file name of the C library, including if necessary a + path and extension. {funcname} must be the exact name exported by the + C library (which may depend on the C compiler and linker used). + + This function is not supported by ANSI C. As such, it is only + available on some platforms (Windows, Linux, Mac OS X, Solaris, BSD, + plus other Unix systems that support the `dlfcn` standard). + +package.path *package.path()* + The path used by `require` to search for a Lua loader. + + At start-up, Lua initializes this variable with the value of the + environment variable `LUA_PATH` or with a default path defined in + `luaconf.h`, if the environment variable is not defined. Any `";;"` in + the value of the environment variable is replaced by the default path. + + A path is a sequence of `templates` separated by semicolons. For each + template, `require` will change each interrogation mark in the + template by `filename`, which is `modname` with each dot replaced by a + "directory separator" (such as `"/"` in Unix); then it will try to + load the resulting file name. So, for instance, if the Lua path is +> + "./?.lua;./?.lc;/usr/local/?/init.lua" +< + the search for a Lua loader for module `foo` will try to load the + files `./foo.lua`, `./foo.lc`, and `/usr/local/foo/init.lua`, in that + order. + +package.preload *package.preload()* + A table to store loaders for specific modules (see |luaref-require|). + +package.seeall({module}) *package.seeall()* + Sets a metatable for {module} with its `__index` field referring to + the global environment, so that this module inherits values from the + global environment. To be used as an option to function {module}. + +============================================================================== +5.4 - String Manipulation *luaref-libString* + +This library provides generic functions for string manipulation, such as +finding and extracting substrings, and pattern matching. When indexing a +string in Lua, the first character is at position 1 (not at 0, as in C). +Indices are allowed to be negative and are interpreted as indexing backwards, +from the end of the string. Thus, the last character is at position -1, and +so on. + +The string library provides all its functions inside the table `string`. +It also sets a metatable for strings where the `__index` field points to the +`string` table. Therefore, you can use the string functions in object-oriented +style. For instance, `string.byte(s, i)` can be written as `s:byte(i)`. + +string.byte({s} [, {i} [, {j}]]) *string.byte()* + Returns the internal numerical codes of the characters `s[i]`, + `s[i+1]`,..., `s[j]`. The default value for {i} is 1; the default + value for {j} is {i}. + + Note that numerical codes are not necessarily portable across + platforms. + +string.char({...}) *string.char()* + Receives zero or more integers. Returns a string with length equal to + the number of arguments, in which each character has the internal + numerical code equal to its correspondent argument. + + Note that numerical codes are not necessarily portable across + platforms. + +string.dump({function}) *string.dump()* + Returns a string containing a binary representation of the given + function, so that a later |luaref-loadstring| on this string returns a + copy of the function. {function} must be a Lua function without + upvalues. + +string.find({s}, {pattern} [, {init} [, {plain}]]) *string.find()* + Looks for the first match of {pattern} in the string {s}. If it finds + a match, then {find} returns the indices of {s} where this occurrence + starts and ends; otherwise, it returns `nil`. A third, optional + numerical argument {init} specifies where to start the search; its + default value is 1 and may be negative. A value of {true} as a fourth, + optional argument {plain} turns off the pattern matching facilities, + so the function does a plain "find substring" operation, with no + characters in {pattern} being considered "magic". Note that if {plain} + is given, then {init} must be given as well. + + If the pattern has captures, then in a successful match the captured + values are also returned, after the two indices. + +string.format({formatstring}, {...}) *string.format()* + Returns a formatted version of its variable number of arguments + following the description given in its first argument (which must be a + string). The format string follows the same rules as the `printf` + family of standard C functions. The only differences are that the + options/modifiers `*`, `l`, `L`, `n`, `p`, and `h` are not supported + and that there is an extra option, `q`. The `q` option formats a + string in a form suitable to be safely read back by the Lua + 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 +> + string.format('%q', 'a string with "quotes" and \n new line') +< + will produce the string: +> + "a string with \"quotes\" and \ + new line" +< + The options `c`, `d`, `E`, `e`, `f`, `g`, `G`, `i`, `o`, `u`, `X`, and + `x` all expect a number as argument, whereas `q` and `s` expect a + string. + + This function does not accept string values containing embedded zeros. + +string.gmatch({s}, {pattern}) *string.gmatch()* + Returns an iterator function that, each time it is called, returns the + next captures from {pattern} over string {s}. + + If {pattern} specifies no captures, then the whole match is produced + in each call. + + As an example, the following loop +> + s = "hello world from Lua" + for w in string.gmatch(s, "%a+") do + print(w) + end +< + 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: +> + t = {} + s = "from=world, to=Lua" + for k, v in string.gmatch(s, "(%w+)=(%w+)") do + t[k] = v + end +< + +string.gsub({s}, {pattern}, {repl} [, {n}]) *string.gsu{b}()* + Returns a copy of {s} in which all occurrences of the {pattern} have + been replaced by a replacement string specified by {repl}, which may + be a string, a table, or a function. `gsub` also returns, as its + second value, the total number of substitutions made. + + If {repl} is a string, then its value is used for replacement. The + character `%` works as an escape character: any sequence in {repl} of + the form `%n`, with {n} between 1 and 9, stands for the value of the + {n} -th captured substring (see below). The sequence `%0` stands for + the whole match. The sequence `%%` stands for a single `%`. + + If {repl} is a table, then the table is queried for every match, using + the first capture as the key; if the pattern specifies no captures, + then the whole match is used as the key. + + If {repl} is a function, then this function is called every time a + match occurs, with all captured substrings passed as arguments, in + order; if the pattern specifies no captures, then the whole match is + passed as a sole argument. + + If the value returned by the table query or by the function call is a + string or a number, then it is used as the replacement string; + otherwise, if it is `false` or `nil`, then there is no replacement + (that is, the original match is kept in the string). + + The optional last parameter {n} limits the maximum number of + substitutions to occur. For instance, when {n} is 1 only the first + occurrence of `pattern` is replaced. + + Here are some examples: +> + x = string.gsub("hello world", "(%w+)", "%1 %1") + --> x="hello hello world world" + + x = string.gsub("hello world", "%w+", "%0 %0", 1) + --> x="hello hello world" + + x = string.gsub("hello world from Lua", "(%w+)%s*(%w+)", "%2 %1") + --> x="world hello Lua from" + + x = string.gsub("home = `HOME, user = ` USER", "%$(%w+)", os.getenv) + --> x="home = /home/roberto, user = roberto" + + x = string.gsub("4+5 = `return 4+5` ", "% `(.-)%` ", function (s) + return loadstring(s)() + end) + --> x="4+5 = 9" + + local t = {name="lua", version="5.1"} + x = string.gsub(" `name%-` version.tar.gz", "%$(%w+)", t) + --> x="lua-5.1.tar.gz" +< + +string.len({s}) *string.len()* + Receives a string and returns its length. The empty string `""` has + length 0. Embedded zeros are counted, so `"a\000b\000c"` has length 5. + +string.lower({s}) *string.lower()* + Receives a string and returns a copy of this string with all uppercase + letters changed to lowercase. All other characters are left unchanged. + The definition of what an uppercase letter is depends on the current + locale. + +string.match({s}, {pattern} [, {init}]) *string.match()* + Looks for the first `match` of {pattern} in the string {s}. If it + finds one, then `match` returns the captures from the pattern; + otherwise it returns `nil`. If {pattern} specifies no captures, then + the whole match is returned. A third, optional numerical argument + {init} specifies where to start the search; its default value is 1 and + may be negative. + +string.rep({s}, {n}) *string.rep()* + Returns a string that is the concatenation of {n} copies of the string + {s}. + +string.reverse({s}) *string.reverse()* + Returns a string that is the string {s} reversed. + +string.sub({s}, {i} [, {j}]) *string.sub()* + Returns the substring of {s} that starts at {i} and continues until + {j}; {i} and {j} may be negative. If {j} is absent, then it is assumed + to be equal to `-1` (which is the same as the string length). In + particular, the call `string.sub(s,1,j)` returns a prefix of {s} with + length {j}, and `string.sub(s,-i)` returns a suffix of {s} with length + {i}. + +string.upper({s}) *string.upper()* + Receives a string and returns a copy of that string with all lowercase + letters changed to uppercase. All other characters are left unchanged. + The definition of what a lowercase letter is depends on the current + locale. + +------------------------------------------------------------------------------ +5.4.1 Patterns *luaref-patterns* *luaref-libStringPat* + +A character class is used to represent a set of characters. The following +combinations are allowed in describing a character class: + + - `x` (where `x` is not one of the magic characters `^$()%.[]*+-?`) + represents the character `x` itself. + - `.` (a dot) represents all characters. + - `%a` represents all letters. + - `%c` represents all control characters. + - `%d` represents all digits. + - `%l` represents all lowercase letters. + - `%p` represents all punctuation characters. + - `%s` represents all space characters. + - `%u` represents all uppercase letters. + - `%w` represents all alphanumeric characters. + - `%x` represents all hexadecimal digits. + - `%z` represents the character with representation `0`. + - `%x` (where `x` is any non-alphanumeric character) represents the + character `x`. This is the standard way to escape the magic + characters. Any punctuation character (even the non-magic) can be + preceded by a `%` when used to represent itself in a pattern. + + - `[set]` represents the class which is the union of all characters in + `set`. A range of characters may be specified by separating the end + characters of the range with a `-`. All classes `%x` described + above may also be used as components in `set`. All other characters + in `set` represent themselves. For example, `[%w_]` (or `[_%w]`) + represents all alphanumeric characters plus the underscore, `[0-7]` + represents the octal digits, and `[0-7%l%-]` represents the octal + digits plus the lowercase letters plus the `-` character. + + The interaction between ranges and classes is not defined. Therefore, + patterns like `[%a-z]` or `[a-%%]` have no meaning. + + - `[^set]` represents the complement of `set`, where `set` is interpreted + as above. + +For all classes represented by single letters (`%a`, `%c`, etc.), the +corresponding uppercase letter represents the complement of the class. For +instance, `%S` represents all non-space characters. + +The definitions of letter, space, and other character groups depend on the +current locale. In particular, the class `[a-z]` may not be equivalent to `%l`. + + *luaref-patternitem* +Pattern Item:~ +------------- +A pattern item may be + + - a single character class, which matches any single character in the + class; + - a single character class followed by `*`, which matches 0 or more + repetitions of characters in the class. These repetition items will + always match the longest possible sequence; + - a single character class followed by `+`, which matches 1 or more + repetitions of characters in the class. These repetition items will + always match the longest possible sequence; + - a single character class followed by `-`, which also matches 0 or + more repetitions of characters in the class. Unlike `*`, these + repetition items will always match the shortest possible sequence; + - a single character class followed by `?`, which matches 0 or 1 + occurrences of a character in the class; + - `%n`, for `n` between 1 and 9; such item matches a substring equal to the + `n` -th captured string (see below); + - `%bxy`, where `x` and `y` are two distinct characters; such item matches + strings that start with `x`, end with `y`, and where the `x` and `y` + are balanced. This means that, if one reads the string from left to + right, counting `+1` for an `x` and `-1` for a `y`, the ending `y` is the first + `y` where the count reaches 0. For instance, the item `%b()` matches + expressions with balanced parentheses. + + *luaref-pattern* +Pattern:~ +-------- +A pattern is a sequence of pattern items. A `^` at the beginning of a pattern +anchors the match at the beginning of the subject string. A `$` at the end of +a pattern anchors the match at the end of the subject string. At other +positions, `^` and `$` have no special meaning and represent themselves. + + *luaref-capture* +Captures:~ +--------- +A pattern may contain sub-patterns enclosed in parentheses; they describe +captures. When a match succeeds, the substrings of the subject string that +match captures are stored (captured) for future use. Captures are numbered +according to their left parentheses. For instance, in the pattern +`"(a*(.)%w(%s*))"`, the part of the string matching `"a*(.)%w(%s*)"` is stored +as the first capture (and therefore has number 1); the character matching `.` +is captured with number 2, and the part matching `%s*` has number 3. + +As a special case, the empty capture `()` captures the current string position +(a number). For instance, if we apply the pattern `"()aa()"` on the +string `"flaaap"`, there will be two captures: 3 and 5. + +A pattern cannot contain embedded zeros. Use `%z` instead. + +============================================================================== +5.5 Table Manipulation *luaref-libTable* + +This library provides generic functions for table manipulation. It provides +all its functions inside the table `table`. + +Most functions in the table library assume that the table represents an array +or a list. For those functions, when we talk about the "length" of a table we +mean the result of the length operator. + +table.concat({table} [, {sep} [, {i} [, {j}]]]) *table.concat()* + Given an array where all elements are strings or numbers, returns + `table[i]..sep..table[i+1] ... sep..table[j]`. The default value for + {sep} is the empty string, the default for {i} is 1, and the default + for {j} is the length of the table. If {i} is greater than {j}, + returns the empty string. + +table.foreach({table}, {f}) *table.foreach()* + Executes the given {f} over all elements of {table}. For each element, + {f} is called with the index and respective value as arguments. If {f} + returns a non-`nil` value, then the loop is broken, and this value is + returned as the final value of `table.foreach`. + + See |luaref-next| for extra information about table traversals. + +table.foreachi({table}, {f}) *table.foreachi()* + Executes the given {f} over the numerical indices of {table}. For each + index, {f} is called with the index and respective value as arguments. + Indices are visited in sequential order, from 1 to `n`, where `n` is + the length of the table. If {f} returns a non-`nil` value, then the + loop is broken and this value is returned as the result of + `table.foreachi`. + +table.insert({table}, [{pos},] {value}) *table.insert()* + Inserts element {value} at position {pos} in {table}, shifting up + other elements to open space, if necessary. The default value for + {pos} is `n+1`, where `n` is the length of the table (see + |luaref-langLength|), so that a call `table.insert(t,x)` inserts `x` + at the end of table `t`. + +table.maxn({table}) *table.maxn()* + Returns the largest positive numerical index of the given table, or + zero if the table has no positive numerical indices. (To do its job + this function does a linear traversal of the whole table.) + +table.remove({table} [, {pos}]) *table.remove()* + Removes from {table} the element at position {pos}, shifting down + other elements to close the space, if necessary. Returns the value of + the removed element. The default value for {pos} is `n`, where `n` is + the length of the table (see |luaref-langLength|), so that a call + `table.remove(t)` removes the last element of table `t`. + +table.sort({table} [, {comp}]) *table.sort()* + Sorts table elements in a given order, `in-place`, from `table[1]` to + `table[n]`, where `n` is the length of the table (see + |luaref-langLength|). If {comp} is given, then it must be a function + that receives two table elements, and returns true when the first is + less than the second (so that `not comp(a[i+1],a[i])` will be true + after the sort). If {comp} is not given, then the standard Lua + operator `<` is used instead. + +The sort algorithm is `not` stable, that is, elements considered equal by the +given order may have their relative positions changed by the sort. + +============================================================================== +5.6 Mathematical Functions *luaref-libMath* + +This library is an interface to most of the functions of the standard C math +library. It provides all its functions inside the table `math`. + +math.abs({x}) *math.abs()* + Returns the absolute value of {x}. + +math.acos({x}) *math.acos()* + Returns the arc cosine of {x} (in radians). + +math.asin({x}) *math.asin()* + Returns the arc sine of {x} (in radians). + +math.atan({x}) *math.atan()* + Returns the arc tangent of {x} (in radians). + +math.atan2({x}, {y}) *math.atan2()* + Returns the arc tangent of `x/y` (in radians), but uses the signs of + both parameters to find the quadrant of the result. (It also handles + correctly the case of {y} being zero.) + +math.ceil({x}) *math.ceil()* + Returns the smallest integer larger than or equal to {x}. + +math.cos({x}) *math.cos()* + Returns the cosine of {x} (assumed to be in radians). + +math.cosh({x}) *math.cosh()* + Returns the hyperbolic cosine of {x}. + +math.deg({x}) *math.deg()* + Returns the angle {x} (given in radians) in degrees. + +math.exp({x}) *math.exp()* + Returns the value `e^x`. + +math.floor({x}) *math.floor()* + Returns the largest integer smaller than or equal to {x}. + +math.fmod({x}, {y}) *math.fmod()* + Returns the remainder of the division of {x} by {y}. + +math.frexp({x}) *math.frexp()* + Returns `m` and `e` such that `x = m * 2^e`, `e` is an integer and the + absolute value of `m` is in the range `[0.5, 1)` (or zero when {x} is + zero). + +math.huge *math.huge()* + The value `HUGE_VAL`, a value larger than or equal to any other + numerical value. + +math.ldexp({m}, {e}) *math.ldexp()* + Returns `m * 2^e` (`e` should be an integer). + +math.log({x}) *math.log()* + Returns the natural logarithm of {x}. + +math.log10({x}) *math.log10()* + Returns the base-10 logarithm of {x}. + +math.max({x}, {...}) *math.max()* + Returns the maximum value among its arguments. + +math.min({x}, {...}) *math.min()* + Returns the minimum value among its arguments. + +math.modf({x}) *math.modf()* + Returns two numbers, the integral part of {x} and the fractional part + of {x}. + +math.pi *math.pi()* + The value of `pi`. + +math.pow({x}, {y}) *math.pow()* + Returns `x^y`. (You can also use the expression `x^y` to compute this + value.) + +math.rad({x}) *math.rad()* + Returns the angle {x} (given in degrees) in radians. + +math.random([{m} [, {n}]]) *math.random()* + This function is an interface to the simple pseudo-random generator + function `rand` provided by ANSI C. (No guarantees can be given for + its statistical properties.) + + When called without arguments, returns a pseudo-random real number in + the range `[0,1)`. When called with a number {m}, `math.random` + returns a pseudo-random integer in the range `[1, m]`. When called + with two numbers {m} and {n}, `math.random` returns a pseudo-random + integer in the range `[m, n]`. + +math.randomseed({x}) *math.randomseed()* + Sets {x} as the "seed" for the pseudo-random generator: equal seeds + produce equal sequences of numbers. + +math.sin({x}) *math.sin()* + Returns the sine of {x} (assumed to be in radians). + +math.sinh({x}) *math.sinh()* + Returns the hyperbolic sine of {x}. + +math.sqrt({x}) *math.sqrt()* + Returns the square root of {x}. (You can also use the expression + `x^0.5` to compute this value.) + +math.tan({x}) *math.tan()* + Returns the tangent of {x} (assumed to be in radians). + +math.tanh({x}) *math.tanh()* + Returns the hyperbolic tangent of {x}. + +============================================================================== +5.6 Input and Output Facilities *luaref-libIO* + +The I/O library provides two different styles for file manipulation. The first +one uses implicit file descriptors; that is, there are operations to set a +default input file and a default output file, and all input/output operations +are over these default files. The second style uses explicit file +descriptors. + +When using implicit file descriptors, all operations are supplied by +table `io`. When using explicit file descriptors, the operation `io.open` returns +a file descriptor and then all operations are supplied as methods of the file +descriptor. + +The table `io` also provides three predefined file descriptors with their usual +meanings from C: `io.stdin`, `io.stdout`, and `io.stderr`. + +Unless otherwise stated, all I/O functions return `nil` on failure (plus an +error message as a second result) and some value different from `nil` on +success. + +io.close([{file}]) *io.close()* + Equivalent to `file:close`. Without a {file}, closes the default + output file. + +io.flush() *io.flush()* + Equivalent to `file:flush` over the default output file. + +io.input([{file}]) *io.input()* + When called with a file name, it opens the named file (in text mode), + and sets its handle as the default input file. When called with a file + handle, it simply sets this file handle as the default input file. + When called without parameters, it returns the current default input + file. + + In case of errors this function raises the error, instead of returning + an error code. + +io.lines([{filename}]) *io.lines()* + Opens the given file name in read mode and returns an iterator + function that, each time it is called, returns a new line from the + file. Therefore, the construction + + `for line in io.lines(filename) do` `body` `end` + + will iterate over all lines of the file. When the iterator function + detects the end of file, it returns `nil` (to finish the loop) and + automatically closes the file. + + The call `io.lines()` (without a file name) is equivalent to + `io.input():lines()`; that is, it iterates over the lines of the + default input file. In this case it does not close the file when the + loop ends. + +io.open({filename} [, {mode}]) *io.open()* + This function opens a file, in the mode specified in the string + {mode}. It returns a new file handle, or, in case of errors, `nil` + plus an error message. + + The {mode} string can be any of the following: + + - `"r"` read mode (the default); + - `"w"` write mode; + - `"a"` append mode; + - `"r+"` update mode, all previous data is preserved; + - `"w+"` update mode, all previous data is erased; + - `"a+"` append update mode, previous data is preserved, writing is + only allowed at the end of file. + + The {mode} string may also have a `b` at the end, which is needed in + some systems to open the file in binary mode. This string is exactly + what is used in the standard C function `fopen`. + +io.output([{file}]) *io.output()* + Similar to `io.input`, but operates over the default output file. + +io.popen({prog} [, {mode}]) *io.popen()* + Starts program {prog} in a separated process and returns a file handle + that you can use to read data from this program (if {mode} is `"r"`, + the default) or to write data to this program (if {mode} is `"w"`). + + This function is system dependent and is not available on all + platforms. + +io.read({...}) *io.read()* + Equivalent to `io.input():read`. + +io.tmpfile() *io.tmpfile()* + Returns a handle for a temporary file. This file is opened in update + mode and it is automatically removed when the program ends. + +io.type({obj}) *io.type()* + Checks whether {obj} is a valid file handle. Returns the string + `"file"` if {obj} is an open file handle, `"closed file"` if {obj} is + a closed file handle, or `nil` if {obj} is not a file handle. + +io.write({...}) *io.write()* + Equivalent to `io.output():write`. + +file:close() *luaref-file:close()* + Closes `file`. Note that files are automatically closed when their + handles are garbage collected, but that takes an unpredictable amount + of time to happen. + +file:flush() *luaref-file:flush()* + Saves any written data to `file`. + +file:lines() *luaref-file:lines()* + Returns an iterator function that, each time it is called, returns a + new line from the file. Therefore, the construction + + `for line in file:lines() do` `body` `end` + + will iterate over all lines of the file. (Unlike `io.lines`, this + function does not close the file when the loop ends.) + +file:read({...}) *luaref-file:read()* + Reads the file `file`, according to the given formats, which specify + what to read. For each format, the function returns a string (or a + number) with the characters read, or `nil` if it cannot read data with + the specified format. When called without formats, it uses a default + format that reads the entire next line (see below). + + The available formats are + + `"*n"` reads a number; this is the only format that returns a + number instead of a string. + `"*a"` reads the whole file, starting at the current position. On + end of file, it returns the empty string. + `"*l"` reads the next line (skipping the end of line), returning + `nil` on end of file. This is the default format. + `number` reads a string with up to that number of characters, + returning `nil` on end of file. If number is zero, it reads + nothing and returns an empty string, or `nil` on end of file. + +file:seek([{whence}] [, {offset}]) *luaref-file:seek()* + Sets and gets the file position, measured from the beginning of the + file, to the position given by {offset} plus a base specified by the + string {whence}, as follows: + + - `"set"`: base is position 0 (beginning of the file); + - `"cur"`: base is current position; + - `"end"`: base is end of file; + + In case of success, function `seek` returns the final file position, + measured in bytes from the beginning of the file. If this function + fails, it returns `nil`, plus a string describing the error. + + The default value for {whence} is `"cur"`, and for {offset} is 0. + Therefore, the call `file:seek()` returns the current file position, + without changing it; the call `file:seek("set")` sets the position to + the beginning of the file (and returns 0); and the call + `file:seek("end")` sets the position to the end of the file, and + returns its size. + +file:setvbuf({mode} [, {size}]) *luaref-file:setvbuf()* + Sets the buffering mode for an output file. There are three available + modes: + + `"no"` no buffering; the result of any output operation appears + immediately. + `"full"` full buffering; output operation is performed only when + the buffer is full (or when you explicitly `flush` the file + (see |luaref-io.flush|). + `"line"` line buffering; output is buffered until a newline is + output or there is any input from some special files (such as + a terminal device). + + For the last two cases, {size} specifies the size of the buffer, in + bytes. The default is an appropriate size. + +file:write({...}) *luaref-file:write()* + Writes the value of each of its arguments to `file`. The arguments + must be strings or numbers. To write other values, use `tostring` + |luaref-tostring| or `string.format` |luaref-string.format| before + `write`. + +============================================================================== +5.8 Operating System Facilities *luaref-libOS* + +This library is implemented through table `os`. + +os.clock() *os.clock()* + Returns an approximation of the amount in seconds of CPU time used by + the program. + +os.date([{format} [, {time}]]) *os.date()* + Returns a string or a table containing date and time, formatted + according to the given string {format}. + + If the {time} argument is present, this is the time to be formatted + (see the `os.time` function |luaref-os.time| for a description of this + value). Otherwise, `date` formats the current time. + + If {format} starts with `!`, then the date is formatted in + Coordinated Universal Time. After this optional character, if {format} + is the string `"*t"`, then `date` returns a table with the following + fields: `year` (four digits), `month` (1-12), `day` (1-31), `hour` + (0-23), `min` (0-59), `sec` (0-61), `wday` (weekday, Sunday is 1), + `yday` (day of the year), and `isdst` (daylight saving flag, a + boolean). + + If {format} is not `"*t"`, then `date` returns the date as a string, + formatted according to the same rules as the C function `strftime`. + + When called without arguments, `date` returns a reasonable date and + time representation that depends on the host system and on the current + locale (that is, `os.date()` is equivalent to `os.date("%c")`). + +os.difftime({t2}, {t1}) *os.difftime()* + Returns the number of seconds from time {t1} to time {t2}. In POSIX, + Windows, and some other systems, this value is exactly `t2 - t1` . + +os.execute([{command}]) *os.execute()* + This function is equivalent to the C function `system`. It passes + {command} to be executed by an operating system shell. It returns a + status code, which is system-dependent. If {command} is absent, then + it returns nonzero if a shell is available and zero otherwise. + +os.exit([{code}]) *os.exit()* + Calls the C function `exit`, with an optional {code}, to terminate the + host program. The default value for {code} is the success code. + +os.getenv({varname}) *os.getenv()* + Returns the value of the process environment variable {varname}, or + `nil` if the variable is not defined. + +os.remove({filename}) *os.remove()* + Deletes the file with the given name. Directories must be empty to be + removed. If this function fails, it returns `nil`, plus a string + describing the error. + +os.rename({oldname}, {newname}) *os.rename()* + Renames file named {oldname} to {newname}. If this function fails, it + returns `nil`, plus a string describing the error. + +os.setlocale({locale} [, {category}]) *os.setlocale()* + Sets the current locale of the program. {locale} is a string + specifying a locale; {category} is an optional string describing which + category to change: `"all"`, `"collate"`, `"ctype"`, `"monetary"`, + `"numeric"`, or `"time"`; the default category is `"all"`. The + function returns the name of the new locale, or `nil` if the request + cannot be honored. + +os.time([{table}]) *os.time()* + Returns the current time when called without arguments, or a time + representing the date and time specified by the given table. This + table must have fields `year`, `month`, and `day`, and may have fields + `hour`, `min`, `sec`, and `isdst` (for a description of these fields, + see the `os.date` function |luaref-os.date|). + + The returned value is a number, whose meaning depends on your system. + In POSIX, Windows, and some other systems, this number counts the + number of seconds since some given start time (the "epoch"). In other + systems, the meaning is not specified, and the number returned by + `time` can be used only as an argument to `date` and `difftime`. + +os.tmpname() *os.tmpname()* + Returns a string with a file name that can be used for a temporary + file. The file must be explicitly opened before its use and explicitly + removed when no longer needed. + +============================================================================== +5.9 The Debug Library *luaref-libDebug* + +This library provides the functionality of the debug interface to Lua +programs. You should exert care when using this library. The functions +provided here should be used exclusively for debugging and similar tasks, such +as profiling. Please resist the temptation to use them as a usual programming +tool: they can be very slow. Moreover, several of its functions violate some +assumptions about Lua code (e.g., that variables local to a function cannot be +accessed from outside or that userdata metatables cannot be changed by Lua +code) and therefore can compromise otherwise secure code. + +All functions in this library are provided inside the `debug` table. All +functions that operate over a thread have an optional first argument which is +the thread to operate over. The default is always the current thread. + +debug.debug() *debug.debug()* + Enters an interactive mode with the user, running each string that the + user enters. Using simple commands and other debug facilities, the + user can inspect global and local variables, change their values, + evaluate expressions, and so on. A line containing only the word + `cont` finishes this function, so that the caller continues its + execution. + + Note that commands for `debug.debug` are not lexically nested within + any function, and so have no direct access to local variables. + +debug.getfenv(o) *debug.getfenv()* + Returns the environment of object {o}. + +debug.gethook([{thread}]) *debug.gethook()* + Returns the current hook settings of the thread, as three values: the + current hook function, the current hook mask, and the current hook + count (as set by the `debug.sethook` function). + +debug.getinfo([{thread},] {function} [, {what}]) *debug.getinfo()* + Returns a table with information about a function. You can give the + function directly, or you can give a number as the value of + {function}, which means the function running at level {function} of + the call stack of the given thread: level 0 is the current function + (`getinfo` itself); level 1 is the function that called `getinfo`; and + so on. If {function} is a number larger than the number of active + functions, then `getinfo` returns `nil`. + + The returned table may contain all the fields returned by + `lua_getinfo` (see |luaref-lua_getinfo|), with the string {what} + describing which fields to fill in. The default for {what} is to get + all information available, except the table of valid lines. If + present, the option `f` adds a field named `func` with the function + itself. If present, the option `L` adds a field named `activelines` + with the table of valid lines. + + For instance, the expression `debug.getinfo(1,"n").name` returns the + name of the current function, if a reasonable name can be found, and + `debug.getinfo(print)` returns a table with all available information + about the `print` function. + +debug.getlocal([{thread},] {level}, {local}) *debug.getlocal()* + This function returns the name and the value of the local variable + with index {local} of the function at level {level} of the stack. (The + first parameter or local variable has index 1, and so on, until the + last active local variable.) The function returns `nil` if there is no + local variable with the given index, and raises an error when called + with a {level} out of range. (You can call `debug.getinfo` + |luaref-debug.getinfo| to check whether the level is valid.) + + Variable names starting with `(` (open parentheses) represent + internal variables (loop control variables, temporaries, and C + function locals). + +debug.getmetatable({object}) *debug.getmetatable()* + Returns the metatable of the given {object} or `nil` if it does not + have a metatable. + +debug.getregistry() *debug.getregistry()* + Returns the registry table (see |luaref-apiRegistry|). + +debug.getupvalue({func}, {up}) *debug.getupvalue()* + This function returns the name and the value of the upvalue with index + {up} of the function {func}. The function returns `nil` if there is no + upvalue with the given index. + +debug.setfenv({object}, {table}) *debug.setfenv()* + Sets the environment of the given {object} to the given {table}. + Returns {object}. + +debug.sethook([{thread},] {hook}, {mask} [, {count}]) *debug.sethook()* + Sets the given function as a hook. The string {mask} and the number + {count} describe when the hook will be called. The string mask may + have the following characters, with the given meaning: + + - `"c"` : The hook is called every time Lua calls a function; + - `"r"` : The hook is called every time Lua returns from a function; + - `"l"` : The hook is called every time Lua enters a new line of + code. + + With a {count} different from zero, the hook is called after every + {count} instructions. + + When called without arguments, the `debug.sethook` turns off the hook. + + When the hook is called, its first parameter is a string describing + the event that triggered its call: `"call"`, `"return"` (or `"tail + return"`), `"line"`, and `"count"`. For line events, the hook also + gets the new line number as its second parameter. Inside a hook, you + can call `getinfo` with level 2 to get more information about the + running function (level 0 is the `getinfo` function, and level 1 is + the hook function), unless the event is `"tail return"`. In this case, + Lua is only simulating the return, and a call to `getinfo` will return + invalid data. + +debug.setlocal([{thread},] {level}, {local}, {value}) *debug.setlocal()* + This function assigns the value {value} to the local variable with + index {local} of the function at level {level} of the stack. The + function returns `nil` if there is no local variable with the given + index, and raises an error when called with a {level} out of range. + (You can call `getinfo` to check whether the level is valid.) + Otherwise, it returns the name of the local variable. + +debug.setmetatable({object}, {table}) *debug.setmetatable()* + Sets the metatable for the given {object} to the given {table} (which + can be `nil`). + +debug.setupvalue({func}, {up}, {value}) *debug.setupvalue()* + This function assigns the value {value} to the upvalue with index {up} + of the function {func}. The function returns `nil` if there is no + upvalue with the given index. Otherwise, it returns the name of the + upvalue. + +debug.traceback([{thread},] [{message}] [,{level}]) *debug.traceback()* + Returns a string with a traceback of the call stack. An optional + {message} string is appended at the beginning of the traceback. An + optional {level} number tells at which level to start the traceback + (default is 1, the function calling `traceback`). + +============================================================================== +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 is discussed in these references: + + - R. Ierusalimschy, L. H. de Figueiredo, and W. Celes., + "Lua --- an extensible extension language". + "Software: Practice & Experience" 26, 6 (1996) 635-652. + + - L. H. de Figueiredo, R. Ierusalimschy, and W. Celes., + "The design and implementation of a language for extending applications". + "Proc. of XXI Brazilian Seminar on Software and Hardware" (1994) 273-283. + + - L. H. de Figueiredo, R. Ierusalimschy, and W. Celes., + "Lua: an extensible embedded language". + "Dr. Dobb's Journal" 21, 12 (Dec 1996) 26-33. + + - R. Ierusalimschy, L. H. de Figueiredo, and W. Celes., + "The evolution of an extension language: a history of Lua". + "Proc. of V Brazilian Symposium on Programming Languages" (2001) B-14-B-28. + +============================================================================== +B COPYRIGHT & LICENSES *luaref-copyright* +============================================================================== + +This help file has the same copyright and license as Lua 5.1 and the Lua 5.1 + manual: + +Copyright (c) 1994-2006 Lua.org, PUC-Rio. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +============================================================================== +C LUAREF DOC *luarefvim* *luarefvimdoc* *luaref-help* *luaref-doc* +============================================================================== + +This is a Vim help file containing a reference for Lua 5.1, and it is -- with +a few exceptions and adaptations -- a copy of the Lua 5.1 Reference Manual +(see |luaref-bibliography|). For usage information, refer to +|luaref-docUsage|. For copyright information, see |luaref-copyright|. + +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). + +Adapted for bundled Nvim documentation; the original plugin can be found at +http://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 new file mode 100644 index 0000000000..ee45444b42 --- /dev/null +++ b/runtime/doc/luvref.txt @@ -0,0 +1,3898 @@ +*luvref.txt* Nvim + + + LUV REFERENCE MANUAL + + +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()`). + +For information about this manual, see |luv-credits|. + +For further examples, see https://github.com/luvit/luv/tree/master/examples. + +============================================================================== +INTRODUCTION *luv* *luv-intro* *uv* + +The luv (https://github.com/luvit/luv) project provides access to the +multi-platform support library libuv (https://github.com/libuv/libuv) in Lua +code. It was primarily developed for the luvit +(https://github.com/luvit/luvit) project as the built-in `uv` module, but can +be used in other Lua environments. + +More information about the core libuv library can be found at the original +libuv documentation page (https://docs.libuv.org/). + +TCP Echo Server Example~ + +Here is a small example showing a TCP echo server: + + > + local uv = vim.loop + + local server = uv.new_tcp() + server:bind("127.0.0.1", 1337) + server:listen(128, function (err) + assert(not err, err) + local client = uv.new_tcp() + server:accept(client) + client:read_start(function (err, chunk) + assert(not err, err) + if chunk then + client:write(chunk) + else + client:shutdown() + client:close() + end + end) + end) + print("TCP server listening at 127.0.0.1 port 1337") + uv.run() -- an explicit run call is necessary outside of luvit +< + +Module Layout~ + +The luv library contains a single Lua module referred to hereafter as `uv` for +simplicity. This module consists mostly of functions with names corresponding +to their original libuv versions. For example, the libuv function +`uv_tcp_bind` has a luv version at |uv.tcp_bind()|. Currently, only one +non-function field exists: `uv.constants`, which is a table. + +Functions vs Methods~ + +In addition to having simple functions, luv provides an optional method-style +API. For example, `uv.tcp_bind(server, host, port)` can alternatively be +called as `server:bind(host, port)` . Note that the first argument `server` +becomes the object and `tcp_` is removed from the function name. Method forms +are documented below where they exist. + +Synchronous vs Asynchronous Functions~ + +Functions that accept a callback are asynchronous. These functions may +immediately return results to the caller to indicate their initial status, but +their final execution is deferred until at least the next libuv loop +iteration. After completion, their callbacks are executed with any results +passed to it. + +Functions that do not accept a callback are synchronous. These functions +immediately return their results to the caller. + +Some (generally FS and DNS) functions can behave either synchronously or +asynchronously. If a callback is provided to these functions, they behave +asynchronously; if no callback is provided, they behave synchronously. + +Pseudo-Types~ + +Some unique types are defined. These are not actual types in Lua, but they are +used here to facilitate documenting consistent behavior: +- `fail`: an assertable `nil, string, string` tuple (see |luv-error-handling|) +- `callable`: a `function`; or a `table` or `userdata` with a `__call` + metamethod +- `buffer`: a `string` or a sequential `table` of `string`s +- `threadargs`: variable arguments (`...`) of type `nil`, `boolean`, `number`, + `string`, or `userdata` + +============================================================================== +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. +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. + +- |luv-error-handling| — Error handling +- |luv-version-checking| — Version checking +- |uv_loop_t| — Event loop +- |uv_req_t| — Base request +- |uv_handle_t| — Base handle + - |uv_timer_t| — Timer handle + - |uv_prepare_t| — Prepare handle + - |uv_check_t| — Check handle + - |uv_idle_t| — Idle handle + - |uv_async_t| — Async handle + - |uv_poll_t| — Poll handle + - |uv_signal_t| — Signal handle + - |uv_process_t| — Process handle + - |uv_stream_t| — Stream handle + - |uv_tcp_t| — TCP handle + - |uv_pipe_t| — Pipe handle + - |uv_tty_t| — TTY handle + - |uv_udp_t| — UDP handle + - |uv_fs_event_t| — FS Event handle + - |uv_fs_poll_t| — FS Poll handle +- |luv-file-system-operations| — File system operations +- |luv-thread-pool-work-scheduling| — Thread pool work scheduling +- |luv-dns-utility-functions| — DNS utility functions +- |luv-threading-and-synchronization-utilities| — Threading and + synchronization utilities +- |luv-miscellaneous-utilities| — Miscellaneous utilities +- |luv-metrics-operations| — Metrics operations + +============================================================================== +ERROR HANDLING *luv-error-handling* + +In libuv, errors are negative numbered constants; however, these errors and +the functions used to handle them are not exposed to luv users. Instead, if an +internal error is encountered, the luv function will return to the caller an +assertable `nil, err, name` tuple. + +- `nil` idiomatically indicates failure +- `err` is a string with the format `{name}: {message}` + - `{name}` is the error name provided internally by `uv_err_name` + - `{message}` is a human-readable message provided internally by + `uv_strerror` +- `name` is the same string used to construct `err` + +This tuple is referred to below as the `fail` pseudo-type. + +When a function is called successfully, it will return either a value that is +relevant to the operation of the function, or the integer `0` to indicate +success, or sometimes nothing at all. These cases are documented below. + +============================================================================== +VERSION CHECKING *luv-version-checking* + +uv.version() *uv.version()* + + Returns the libuv version packed into a single integer. 8 bits + are used for each component, with the patch number stored in + the 8 least significant bits. For example, this would be + 0x010203 in libuv 1.2.3. + + Returns: `integer` + +uv.version_string() *uv.version_string()* + + Returns the libuv version number as a string. For example, + this would be "1.2.3" in libuv 1.2.3. For non-release + versions, the version suffix is included. + + Returns: `string` + +============================================================================== +`uv_loop_t` — Event loop *luv-event-loop* *uv_loop_t* + +The event loop is the central part of libuv's functionality. It takes care of +polling for I/O and scheduling callbacks to be run based on different sources +of events. + +In luv, there is an implicit uv loop for every Lua state that loads the +library. You can use this library in an multi-threaded environment as long as +each thread has it's own Lua state with its corresponding own uv loop. This +loop is not directly exposed to users in the Lua module. + +uv.loop_close() *uv.loop_close()* + + Closes all internal loop resources. In normal execution, the + loop will automatically be closed when it is garbage collected + by Lua, so it is not necessary to explicitly call + `loop_close()`. Call this function only after the loop has + finished executing and all open handles and requests have been + closed, or it will return `EBUSY`. + + Returns: `0` or `fail` + +uv.run([{mode}]) *uv.run()* + + Parameters: + - `mode`: `string` or `nil` (default: `"default"`) + + This function runs the event loop. It will act differently + depending on the specified mode: + + - `"default"`: Runs the event loop until there are no more + active and referenced handles or requests. Returns `true` + if |uv.stop()| was called and there are still active + handles or requests. Returns `false` in all other cases. + + - `"once"`: Poll for I/O once. Note that this function + blocks if there are no pending callbacks. Returns `false` + when done (no active handles or requests left), or `true` + if more callbacks are expected (meaning you should run the + event loop again sometime in the future). + + - `"nowait"`: Poll for I/O once but don't block if there are + no pending callbacks. Returns `false` if done (no active + handles or requests left), or `true` if more callbacks are + expected (meaning you should run the event loop again + sometime in the future). + + Returns: `boolean` or `fail` + + Note: Luvit will implicitly call `uv.run()` after loading user + code, but if you use the luv bindings directly, you need to + call this after registering your initial set of event + callbacks to start the event loop. + +uv.loop_configure({option}, {...}) *uv.loop_configure()* + + Parameters: + - `option`: `string` + - `...`: depends on `option`, see below + + Set additional loop options. You should normally call this + before the first call to uv_run() unless mentioned otherwise. + + Supported options: + + - `"block_signal"`: Block a signal when polling for new + events. The second argument to loop_configure() is the + signal name (as a lowercase string) or the signal number. + This operation is currently only implemented for + `"sigprof"` signals, to suppress unnecessary wakeups when + using a sampling profiler. Requesting other signals will + fail with `EINVAL`. + - `"metrics_idle_time"`: Accumulate the amount of idle time + the event loop spends in the event provider. This option + is necessary to use `metrics_idle_time()`. + + An example of a valid call to this function is: + + > + uv.loop_configure("block_signal", "sigprof") +< + + Returns: `0` or `fail` + + Note: Be prepared to handle the `ENOSYS` error; it means the + loop option is not supported by the platform. + +uv.loop_mode() *uv.loop_mode()* + + If the loop is running, returns a string indicating the mode + in use. If the loop is not running, `nil` is returned instead. + + Returns: `string` or `nil` + +uv.loop_alive() *uv.loop_alive()* + + Returns `true` if there are referenced active handles, active + requests, or closing handles in the loop; otherwise, `false`. + + Returns: `boolean` or `fail` + +uv.stop() *uv.stop()* + + Stop the event loop, causing |uv.run()| to end as soon as + possible. This will happen not sooner than the next loop + iteration. If this function was called before blocking for + I/O, the loop won't block for I/O on this iteration. + + Returns: Nothing. + +uv.backend_fd() *uv.backend_fd()* + + Get backend file descriptor. Only kqueue, epoll, and event + ports are supported. + + This can be used in conjunction with `uv.run("nowait")` to + poll in one thread and run the event loop's callbacks in + another + + Returns: `integer` or `nil` + + Note: Embedding a kqueue fd in another kqueue pollset doesn't + work on all platforms. It's not an error to add the fd but it + never generates events. + +uv.backend_timeout() *uv.backend_timeout()* + + Get the poll timeout. The return value is in milliseconds, or + -1 for no timeout. + + Returns: `integer` + +uv.now() *uv.now()* + + Returns the current timestamp in milliseconds. The timestamp + is cached at the start of the event loop tick, see + |uv.update_time()| for details and rationale. + + The timestamp increases monotonically from some arbitrary + point in time. Don't make assumptions about the starting + point, you will only get disappointed. + + Returns: `integer` + + Note: Use |uv.hrtime()| if you need sub-millisecond + granularity. + +uv.update_time() *uv.update_time()* + + Update the event loop's concept of "now". Libuv caches the + current time at the start of the event loop tick in order to + reduce the number of time-related system calls. + + You won't normally need to call this function unless you have + callbacks that block the event loop for longer periods of + time, where "longer" is somewhat subjective but probably on + the order of a millisecond or more. + + Returns: Nothing. + +uv.walk({callback}) *uv.walk()* + + Parameters: + - `callback`: `callable` + - `handle`: `userdata` for sub-type of |uv_handle_t| + + Walk the list of handles: `callback` will be executed with + each handle. + + Returns: Nothing. + + > + -- Example usage of uv.walk to close all handles that + -- aren't already closing. + uv.walk(function (handle) + if not handle:is_closing() then + handle:close() + end + end) +< + +============================================================================== +`uv_req_t` — Base request *luv-base-request* *uv_req_t* + +`uv_req_t` is the base type for all libuv request types. + +uv.cancel({req}) *uv.cancel()* + + > method form `req:cancel()` + + Parameters: + - `req`: `userdata` for sub-type of |uv_req_t| + + Cancel a pending request. Fails if the request is executing or + has finished executing. Only cancellation of |uv_fs_t|, + `uv_getaddrinfo_t`, `uv_getnameinfo_t` and `uv_work_t` + requests is currently supported. + + Returns: `0` or `fail` + +uv.req_get_type({req}) *uv.req_get_type()* + + > method form `req:get_type()` + + Parameters: + - `req`: `userdata` for sub-type of |uv_req_t| + + Returns the name of the struct for a given request (e.g. + `"fs"` for |uv_fs_t|) and the libuv enum integer for the + request's type (`uv_req_type`). + + Returns: `string, integer` + +============================================================================== +`uv_handle_t` — Base handle *luv-base-handle* *uv_handle_t* + +`uv_handle_t` is the base type for all libuv handle types. All API functions +defined here work with any handle type. + +uv.is_active({handle}) *uv.is_active()* + + > method form `handle:is_active()` + + Parameters: + - `handle`: `userdata` for sub-type of |uv_handle_t| + + Returns `true` if the handle is active, `false` if it's + inactive. What "active” means depends on the type of handle: + + - A |uv_async_t| handle is always active and cannot be + deactivated, except by closing it with |uv.close()|. + + - A |uv_pipe_t|, |uv_tcp_t|, |uv_udp_t|, etc. + handle - basically any handle that deals with I/O - is + active when it is doing something that involves I/O, like + reading, writing, connecting, accepting new connections, + etc. + + - A |uv_check_t|, |uv_idle_t|, |uv_timer_t|, + etc. handle is active when it has been started with a call + to |uv.check_start()|, |uv.idle_start()|, + |uv.timer_start()| etc. until it has been stopped with a + call to its respective stop function. + + Returns: `boolean` or `fail` + +uv.is_closing({handle}) *uv.is_closing()* + + > method form `handle:is_closing()` + + Parameters: + - `handle`: `userdata` for sub-type of |uv_handle_t| + + Returns `true` if the handle is closing or closed, `false` + otherwise. + + Returns: `boolean` or `fail` + + Note: This function should only be used between the + initialization of the handle and the arrival of the close + callback. + +uv.close({handle} [, {callback}]) *uv.close()* + + > method form `handle:close([callback])` + + Parameters: + - `handle`: `userdata` for sub-type of |uv_handle_t| + - `callback`: `callable` or `nil` + + Request handle to be closed. `callback` will be called + asynchronously after this call. This MUST be called on each + handle before memory is released. + + Handles that wrap file descriptors are closed immediately but + `callback` will still be deferred to the next iteration of the + event loop. It gives you a chance to free up any resources + associated with the handle. + + In-progress requests, like `uv_connect_t` or `uv_write_t`, are + cancelled and have their callbacks called asynchronously with + `ECANCELED`. + + Returns: Nothing. + +uv.ref({handle}) *uv.ref()* + + > method form `handle:ref()` + + Parameters: + - `handle`: `userdata` for sub-type of |uv_handle_t| + + Reference the given handle. References are idempotent, that + is, if a handle is already referenced calling this function + again will have no effect. + + Returns: Nothing. + + See |luv-reference-counting|. + +uv.unref({handle}) *uv.unref()* + + > method form `handle:unref()` + + Parameters: + - `handle`: `userdata` for sub-type of |uv_handle_t| + + Un-reference the given handle. References are idempotent, that + is, if a handle is not referenced calling this function again + will have no effect. + + Returns: Nothing. + +See |luv-reference-counting|. + +uv.has_ref({handle}) *uv.has_ref()* + + > method form `handle:has_ref()` + + Parameters: + - `handle`: `userdata` for sub-type of |uv_handle_t| + + Returns `true` if the handle referenced, `false` if not. + + Returns: `boolean` or `fail` + + See |luv-reference-counting|. + +uv.send_buffer_size({handle} [, {size}]) *uv.send_buffer_size()* + + > method form `handle:send_buffer_size([size])` + + Parameters: + - `handle`: `userdata` for sub-type of |uv_handle_t| + - `size`: `integer` or `nil` (default: `0`) + + Gets or sets the size of the send buffer that the operating + system uses for the socket. + + If `size` is omitted (or `0`), this will return the current + send buffer size; otherwise, this will use `size` to set the + new send buffer size. + + This function works for TCP, pipe and UDP handles on Unix and + for TCP and UDP handles on Windows. + + Returns: + - `integer` or `fail` (if `size` is `nil` or `0`) + - `0` or `fail` (if `size` is not `nil` and not `0`) + + Note: Linux will set double the size and return double the + size of the original set value. + +uv.recv_buffer_size({handle} [, {size}]) *uv.recv_buffer_size()* + + > method form `handle:recv_buffer_size([size])` + + Parameters: + - `handle`: `userdata` for sub-type of |uv_handle_t| + - `size`: `integer` or `nil` (default: `0`) + + Gets or sets the size of the receive buffer that the operating + system uses for the socket. + + If `size` is omitted (or `0`), this will return the current + send buffer size; otherwise, this will use `size` to set the + new send buffer size. + + This function works for TCP, pipe and UDP handles on Unix and + for TCP and UDP handles on Windows. + + Returns: + - `integer` or `fail` (if `size` is `nil` or `0`) + - `0` or `fail` (if `size` is not `nil` and not `0`) + + Note: Linux will set double the size and return double the + size of the original set value. + +uv.fileno({handle}) *uv.fileno()* + + > method form `handle:fileno()` + + Parameters: + - `handle`: `userdata` for sub-type of |uv_handle_t| + + Gets the platform dependent file descriptor equivalent. + + The following handles are supported: TCP, pipes, TTY, UDP and + poll. Passing any other handle type will fail with `EINVAL`. + + If a handle doesn't have an attached file descriptor yet or + the handle itself has been closed, this function will return + `EBADF`. + + Returns: `integer` or `fail` + + WARNING: Be very careful when using this function. libuv + assumes it's in control of the file descriptor so any change + to it may lead to malfunction. + +uv.handle_get_type({handle}) *uv.handle_get_type()* + + > method form `handle:get_type()` + + Parameters: + - `handle`: `userdata` for sub-type of |uv_handle_t| + + Returns the name of the struct for a given handle (e.g. + `"pipe"` for |uv_pipe_t|) and the libuv enum integer for the + handle's type (`uv_handle_type`). + + Returns: `string, integer` + +============================================================================== +REFERENCE COUNTING *luv-reference-counting* + +The libuv event loop (if run in the default mode) will run until there are no +active and referenced handles left. The user can force the loop to exit early +by unreferencing handles which are active, for example by calling |uv.unref()| +after calling |uv.timer_start()|. + +A handle can be referenced or unreferenced, the refcounting scheme doesn't use +a counter, so both operations are idempotent. + +All handles are referenced when active by default, see |uv.is_active()| for a +more detailed explanation on what being active involves. + +============================================================================== +`uv_timer_t` — Timer handle *luv-timer-handle* *uv_timer_t* + +> |uv_handle_t| functions also apply. + +Timer handles are used to schedule callbacks to be called in the future. + +uv.new_timer() *uv.new_timer()* + + Creates and initializes a new |uv_timer_t|. Returns the Lua + userdata wrapping it. + + Returns: `uv_timer_t userdata` or `fail` + + > + -- Creating a simple setTimeout wrapper + local function setTimeout(timeout, callback) + local timer = uv.new_timer() + timer:start(timeout, 0, function () + timer:stop() + timer:close() + callback() + end) + return timer + end + + -- Creating a simple setInterval wrapper + local function setInterval(interval, callback) + local timer = uv.new_timer() + timer:start(interval, interval, function () + callback() + end) + return timer + end + + -- And clearInterval + local function clearInterval(timer) + timer:stop() + timer:close() + end +< + +uv.timer_start({timer}, {timeout}, {repeat}, {callback}) *uv.timer_start()* + + > method form `timer:start(timeout, repeat, callback)` + + Parameters: + - `timer`: `uv_timer_t userdata` + - `timeout`: `integer` + - `repeat`: `integer` + - `callback`: `callable` + + Start the timer. `timeout` and `repeat` are in milliseconds. + + If `timeout` is zero, the callback fires on the next event + loop iteration. If `repeat` is non-zero, the callback fires + first after `timeout` milliseconds and then repeatedly after + `repeat` milliseconds. + + Returns: `0` or `fail` + +uv.timer_stop({timer}) *uv.timer_stop()* + + > method form `timer:stop()` + + Parameters: + - `timer`: `uv_timer_t userdata` + + Stop the timer, the callback will not be called anymore. + + Returns: `0` or `fail` + +uv.timer_again({timer}) *uv.timer_again()* + + > method form `timer:again()` + + Parameters: + - `timer`: `uv_timer_t userdata` + + Stop the timer, and if it is repeating restart it using the + repeat value as the timeout. If the timer has never been + started before it raises `EINVAL`. + + Returns: `0` or `fail` + +uv.timer_set_repeat({timer}, {repeat}) *uv.timer_set_repeat()* + + > method form `timer:set_repeat(repeat)` + + Parameters: + - `timer`: `uv_timer_t userdata` + - `repeat`: `integer` + + Set the repeat interval value in milliseconds. The timer will + be scheduled to run on the given interval, regardless of the + callback execution duration, and will follow normal timer + semantics in the case of a time-slice overrun. + + For example, if a 50 ms repeating timer first runs for 17 ms, + it will be scheduled to run again 33 ms later. If other tasks + consume more than the 33 ms following the first timer + callback, then the callback will run as soon as possible. + + Returns: Nothing. + +uv.timer_get_repeat({timer}) *uv.timer_get_repeat()* + + > method form `timer:get_repeat()` + + Parameters: + - `timer`: `uv_timer_t userdata` + + Get the timer repeat value. + + Returns: `integer` + +uv.timer_get_due_in({timer}) *uv.timer_get_due_in()* + + > method form `timer:get_due_in()` + + Parameters: + - `timer`: `uv_timer_t userdata` + + Get the timer due value or 0 if it has expired. The time is + relative to |uv.now()|. + + Returns: `integer` + + Note: New in libuv version 1.40.0. + +============================================================================== +`uv_prepare_t` — Prepare handle *luv-prepare-handle* *uv_prepare_t* + +> |uv_handle_t| functions also apply. + +Prepare handles will run the given callback once per loop iteration, right +before polling for I/O. + + > + local prepare = uv.new_prepare() + prepare:start(function() + print("Before I/O polling") + end) +< + +uv.new_prepare() *uv.new_prepare()* + + Creates and initializes a new |uv_prepare_t|. Returns the Lua + userdata wrapping it. + + Returns: `uv_prepare_t userdata` or `fail` + +uv.prepare_start({prepare}, {callback}) *uv.prepare_start()* + + > method form `prepare:start(callback)` + + Parameters: + - `prepare`: `uv_prepare_t userdata` + - `callback`: `callable` + + Start the handle with the given callback. + + Returns: `0` or `fail` + +uv.prepare_stop({prepare}) *uv.prepare_stop()* + + > method form `prepare:stop()` + + Parameters: + - `prepare`: `uv_prepare_t userdata` + + Stop the handle, the callback will no longer be called. + + Returns: `0` or `fail` + +============================================================================== +`uv_check_t` — Check handle *luv-check-handle* *uv_check_t* + +> |uv_handle_t| functions also apply. + +Check handles will run the given callback once per loop iteration, right after +polling for I/O. + + > + local check = uv.new_check() + check:start(function() + print("After I/O polling") + end) +< + +uv.new_check() *uv.new_check()* + + Creates and initializes a new |uv_check_t|. Returns the Lua + userdata wrapping it. + + Returns: `uv_check_t userdata` or `fail` + +uv.check_start({check}, {callback}) *uv.check_start()* + + > method form `check:start(callback)` + + Parameters: + - `check`: `uv_check_t userdata` + - `callback`: `callable` + + Start the handle with the given callback. + + Returns: `0` or `fail` + +uv.check_stop({check}) *uv.check_stop()* + + > method form `check:stop()` + + Parameters: + - `check`: `uv_check_t userdata` + + Stop the handle, the callback will no longer be called. + + Returns: `0` or `fail` + +============================================================================== +`uv_idle_t` — Idle handle *luv-idle-handle* *uv_idle_t* + +> |uv_handle_t| functions also apply. + +Idle handles will run the given callback once per loop iteration, right before +the |uv_prepare_t| handles. + +Note: The notable difference with prepare handles is that when there are +active idle handles, the loop will perform a zero timeout poll instead of +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". + + > + local idle = uv.new_idle() + idle:start(function() + print("Before I/O polling, no blocking") + end) +< + +uv.new_idle() *uv.new_idle()* + + Creates and initializes a new |uv_idle_t|. Returns the Lua + userdata wrapping it. + + Returns: `uv_idle_t userdata` or `fail` + +uv.idle_start({idle}, {callback}) *uv.idle_start()* + + > method form `idle:start(callback)` + + Parameters: + - `idle`: `uv_idle_t userdata` + - `callback`: `callable` + + Start the handle with the given callback. + + Returns: `0` or `fail` + +uv.idle_stop({check}) *uv.idle_stop()* + + > method form `idle:stop()` + + Parameters: + - `idle`: `uv_idle_t userdata` + + Stop the handle, the callback will no longer be called. + + Returns: `0` or `fail` + +============================================================================== +`uv_async_t` — Async handle *luv-async-handle* *uv_async_t* + +> |uv_handle_t| functions also apply. + +Async handles allow the user to "wakeup" the event loop and get a callback +called from another thread. + + > + local async + async = uv.new_async(function() + print("async operation ran") + async:close() + end) + + async:send() +< + +uv.new_async([{callback}]) *uv.new_async()* + + Parameters: + - `callback`: `callable` or `nil` + - `...`: `threadargs` passed to/from + `uv.async_send(async, ...)` + + Creates and initializes a new |uv_async_t|. Returns the Lua + userdata wrapping it. A `nil` callback is allowed. + + Returns: `uv_async_t userdata` or `fail` + + Note: Unlike other handle initialization functions, this + immediately starts the handle. + +uv.async_send({async}, {...}) *uv.async_send()* + + > method form `async:send(...)` + + Parameters: + - `async`: `uv_async_t userdata` + - `...`: `threadargs` + + Wakeup the event loop and call the async handle's callback. + + Returns: `0` or `fail` + + Note: It's safe to call this function from any thread. The + callback will be called on the loop thread. + + WARNING: libuv will coalesce calls to `uv.async_send(async)`, + that is, not every call to it will yield an execution of the + callback. For example: if `uv.async_send()` is called 5 times + in a row before the callback is called, the callback will only + be called once. If `uv.async_send()` is called again after the + callback was called, it will be called again. + +============================================================================== +`uv_poll_t` — Poll handle *luv-poll-handle* *uv_poll_t* + +> |uv_handle_t| functions also apply. + +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). + +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 +c-ares or libssh2. Using `uv_poll_t` for any other purpose is not recommended; +|uv_tcp_t|, |uv_udp_t|, etc. provide an implementation that is faster and more +scalable than what can be achieved with `uv_poll_t`, especially on Windows. + +It is possible that poll handles occasionally signal that a file descriptor is +readable or writable even when it isn't. The user should therefore always be +prepared to handle EAGAIN or equivalent when it attempts to read from or write +to the fd. + +It is not okay to have multiple active poll handles for the same socket, this +can cause libuv to busyloop or otherwise malfunction. + +The user should not close a file descriptor while it is being polled by an +active poll handle. This can cause the handle to report an error, but it might +also start polling another socket. However the fd can be safely closed +immediately after a call to |uv.poll_stop()| or |uv.close()|. + +Note: On windows only sockets can be polled with poll handles. On Unix any +file descriptor that would be accepted by poll(2) can be used. + +uv.new_poll({fd}) *uv.new_poll()* + + Parameters: + - `fd`: `integer` + + Initialize the handle using a file descriptor. + + The file descriptor is set to non-blocking mode. + + Returns: `uv_poll_t userdata` or `fail` + +uv.new_socket_poll({fd}) *uv.new_socket_poll()* + + Parameters: + - `fd`: `integer` + + Initialize the handle using a socket descriptor. On Unix this + is identical to |uv.new_poll()|. On windows it takes a SOCKET + handle. + + The socket is set to non-blocking mode. + + Returns: `uv_poll_t userdata` or `fail` + +uv.poll_start({poll}, {events}, {callback}) *uv.poll_start()* + + > method form `poll:start(events, callback)` + + Parameters: + - `poll`: `uv_poll_t userdata` + - `events`: `string` or `nil` (default: `"rw"`) + - `callback`: `callable` + - `err`: `nil` or `string` + - `events`: `string` or `nil` + + Starts polling the file descriptor. `events` are: `"r"`, + `"w"`, `"rw"`, `"d"`, `"rd"`, `"wd"`, `"rwd"`, `"p"`, `"rp"`, + `"wp"`, `"rwp"`, `"dp"`, `"rdp"`, `"wdp"`, or `"rwdp"` where + `r` is `READABLE`, `w` is `WRITABLE`, `d` is `DISCONNECT`, and + `p` is `PRIORITIZED`. As soon as an event is detected the + callback will be called with status set to 0, and the detected + events set on the events field. + + The user should not close the socket while the handle is + active. If the user does that anyway, the callback may be + called reporting an error status, but this is not guaranteed. + + Returns: `0` or `fail` + + Note Calling `uv.poll_start()` on a handle that is already + active is fine. Doing so will update the events mask that is + being watched for. + +uv.poll_stop({poll}) *uv.poll_stop()* + + > method form `poll:stop()` + + Parameters: + - `poll`: `uv_poll_t userdata` + + Stop polling the file descriptor, the callback will no longer + be called. + + Returns: `0` or `fail` + +============================================================================== +`uv_signal_t` — Signal handle *luv-signal-handle* *uv_signal_t* + +> |uv_handle_t| functions also apply. + +Signal handles implement Unix style signal handling on a per-event loop bases. + +Windows Notes: + +Reception of some signals is emulated on Windows: + - SIGINT is normally delivered when the user presses CTRL+C. However, like + on Unix, it is not generated when terminal raw mode is enabled. + - SIGBREAK is delivered when the user pressed CTRL + BREAK. + - SIGHUP is generated when the user closes the console window. On SIGHUP the + program is given approximately 10 seconds to perform cleanup. After that + Windows will unconditionally terminate it. + - SIGWINCH is raised whenever libuv detects that the console has been + resized. SIGWINCH is emulated by libuv when the program uses a uv_tty_t + handle to write to the console. SIGWINCH may not always be delivered in a + timely manner; libuv will only detect size changes when the cursor is + being moved. When a readable |uv_tty_t| handle is used in raw mode, + resizing the console buffer will also trigger a SIGWINCH signal. + - Watchers for other signals can be successfully created, but these signals + are never received. These signals are: SIGILL, SIGABRT, SIGFPE, SIGSEGV, + SIGTERM and SIGKILL. + - Calls to raise() or abort() to programmatically raise a signal are not + detected by libuv; these will not trigger a signal watcher. + +Unix Notes: + + - SIGKILL and SIGSTOP are impossible to catch. + - Handling SIGBUS, SIGFPE, SIGILL or SIGSEGV via libuv results into + undefined behavior. + - SIGABRT will not be caught by libuv if generated by abort(), e.g. through + assert(). + - On Linux SIGRT0 and SIGRT1 (signals 32 and 33) are used by the NPTL + pthreads library to manage threads. Installing watchers for those signals + will lead to unpredictable behavior and is strongly discouraged. Future + versions of libuv may simply reject them. + + > + -- Create a new signal handler + local signal = uv.new_signal() + -- Define a handler function + uv.signal_start(signal, "sigint", function(signal) + print("got " .. signal .. ", shutting down") + os.exit(1) + end) +< + +uv.new_signal() *uv.new_signal()* + + Creates and initializes a new |uv_signal_t|. Returns the Lua + userdata wrapping it. + + Returns: `uv_signal_t userdata` or `fail` + +uv.signal_start({signal}, {signum}, {callback}) *uv.signal_start()* + + > method form `signal:start(signum, callback)` + + Parameters: + - `signal`: `uv_signal_t userdata` + - `signum`: `integer` or `string` + - `callback`: `callable` + - `signum`: `string` + + Start the handle with the given callback, watching for the + given signal. + + Returns: `0` or `fail` + *uv.signal_start_oneshot()* +uv.signal_start_oneshot({signal}, {signum}, {callback}) + + > method form `signal:start_oneshot(signum, callback)` + + Parameters: + - `signal`: `uv_signal_t userdata` + - `signum`: `integer` or `string` + - `callback`: `callable` + - `signum`: `string` + + Same functionality as |uv.signal_start()| but the signal + handler is reset the moment the signal is received. + + Returns: `0` or `fail` + +uv.signal_stop({signal}) *uv.signal_stop()* + + > method form `signal:stop()` + + Parameters: + - `signal`: `uv_signal_t userdata` + + Stop the handle, the callback will no longer be called. + + Returns: `0` or `fail` + +============================================================================== +`uv_process_t` — Process handle *luv-process-handle* *uv_process_t* + +> |uv_handle_t| functions also apply. + +Process handles will spawn a new process and allow the user to control it and +establish communication channels with it using streams. + +uv.disable_stdio_inheritance() *uv.disable_stdio_inheritance()* + + Disables inheritance for file descriptors / handles that this + process inherited from its parent. The effect is that child + processes spawned by this process don't accidentally inherit + these handles. + + It is recommended to call this function as early in your + program as possible, before the inherited file descriptors can + be closed or duplicated. + + Returns: Nothing. + + Note: This function works on a best-effort basis: there is no + guarantee that libuv can discover all file descriptors that + were inherited. In general it does a better job on Windows + than it does on Unix. + +uv.spawn({path}, {options}, {on_exit}) *uv.spawn()* + + Parameters: + - `path`: `string` + - `options`: `table` (see below) + - `on_exit`: `callable` + - `code`: `integer` + - `signal`: `integer` + + Initializes the process handle and starts the process. If the + process is successfully spawned, this function will return the + handle and pid of the child process. + + Possible reasons for failing to spawn would include (but not + be limited to) the file to execute not existing, not having + permissions to use the setuid or setgid specified, or not + having enough memory to allocate for the new process. + + > + local stdin = uv.new_pipe() + local stdout = uv.new_pipe() + local stderr = uv.new_pipe() + + print("stdin", stdin) + print("stdout", stdout) + print("stderr", stderr) + + local handle, pid = uv.spawn("cat", { + stdio = {stdin, stdout, stderr} + }, function(code, signal) -- on exit + print("exit code", code) + print("exit signal", signal) + end) + + print("process opened", handle, pid) + + uv.read_start(stdout, function(err, data) + assert(not err, err) + if data then + print("stdout chunk", stdout, data) + else + print("stdout end", stdout) + end + end) + + uv.read_start(stderr, function(err, data) + assert(not err, err) + if data then + print("stderr chunk", stderr, data) + else + print("stderr end", stderr) + end + end) + + uv.write(stdin, "Hello World") + + uv.shutdown(stdin, function() + print("stdin shutdown", stdin) + uv.close(handle, function() + print("process closed", handle, pid) + end) + end) +< + *uv.spawn-options* + The `options` table accepts the following fields: + + - `options.args` - Command line arguments as a list of + string. The first string should be the path to the + program. On Windows, this uses CreateProcess which + concatenates the arguments into a string. This can cause + some strange errors. (See `options.verbatim` below for + Windows.) + - `options.stdio` - Set the file descriptors that will be + made available to the child process. The convention is + that the first entries are stdin, stdout, and stderr. + (Note: On Windows, file descriptors after the third are + available to the child process only if the child processes + uses the MSVCRT runtime.) + - `options.env` - Set environment variables for the new + process. + - `options.cwd` - Set the current working directory for the + sub-process. + - `options.uid` - Set the child process' user id. + - `options.gid` - Set the child process' group id. + - `options.verbatim` - If true, do not wrap any arguments in + quotes, or perform any other escaping, when converting the + argument list into a command line string. This option is + only meaningful on Windows systems. On Unix it is silently + ignored. + - `options.detached` - If true, spawn the child process in a + detached state - this will make it a process group leader, + and will effectively enable the child to keep running + after the parent exits. Note that the child process will + still keep the parent's event loop alive unless the parent + process calls |uv.unref()| on the child's process handle. + - `options.hide` - If true, hide the subprocess console + window that would normally be created. This option is only + meaningful on Windows systems. On Unix it is silently + ignored. + + The `options.stdio` entries can take many shapes. + + - If they are numbers, then the child process inherits that + same zero-indexed fd from the parent process. + - If |uv_stream_t| handles are passed in, those are used as + a read-write pipe or inherited stream depending if the + stream has a valid fd. + - Including `nil` placeholders means to ignore that fd in + the child process. + + When the child process exits, `on_exit` is called with an exit + code and signal. + + Returns: `uv_process_t userdata`, `integer` + +uv.process_kill({process}, {signum}) *uv.process_kill()* + + > method form `process:kill(signum)` + + Parameters: + - `process`: `uv_process_t userdata` + - `signum`: `integer` or `string` + + Sends the specified signal to the given process handle. Check + the documentation on |uv_signal_t| for signal support, + specially on Windows. + + Returns: `0` or `fail` + +uv.kill({pid}, {signum}) *uv.kill()* + + Parameters: + - `pid`: `integer` + - `signum`: `integer` or `string` + + Sends the specified signal to the given PID. Check the + documentation on |uv_signal_t| for signal support, specially + on Windows. + + Returns: `0` or `fail` + +uv.process_get_pid({process}) *uv.process_get_pid()* + + > method form `process:get_pid()` + + Parameters: + - `process`: `uv_process_t userdata` + + Returns the handle's pid. + + Returns: `integer` + +============================================================================== +`uv_stream_t` — Stream handle *luv-stream-handle* *uv_stream_t* + +> |uv_handle_t| functions also apply. + +Stream handles provide an abstraction of a duplex communication channel. +`uv_stream_t` is an abstract type, libuv provides 3 stream implementations +in the form of |uv_tcp_t|, |uv_pipe_t| and |uv_tty_t|. + +uv.shutdown({stream} [, {callback}]) *uv.shutdown()* + + > method form `stream:shutdown([callback])` + + Parameters: + - `stream`: `userdata` for sub-type of |uv_stream_t| + - `callback`: `callable` or `nil` + - `err`: `nil` or `string` + + Shutdown the outgoing (write) side of a duplex stream. It + waits for pending write requests to complete. The callback is + called after shutdown is complete. + + Returns: `uv_shutdown_t userdata` or `fail` + +uv.listen({stream}, {backlog}, {callback}) *uv.listen()* + + > method form `stream:listen(backlog, callback)` + + Parameters: + - `stream`: `userdata` for sub-type of |uv_stream_t| + - `backlog`: `integer` + - `callback`: `callable` + - `err`: `nil` or `string` + + Start listening for incoming connections. `backlog` indicates + the number of connections the kernel might queue, same as + `listen(2)`. When a new incoming connection is received the + callback is called. + + Returns: `0` or `fail` + +uv.accept({stream}, {client_stream}) *uv.accept()* + + > method form `stream:accept(client_stream)` + + Parameters: + - `stream`: `userdata` for sub-type of |uv_stream_t| + - `client_stream`: `userdata` for sub-type of |uv_stream_t| + + This call is used in conjunction with |uv.listen()| to accept + incoming connections. Call this function after receiving a + callback to accept the connection. + + When the connection callback is called it is guaranteed that + this function will complete successfully the first time. If + you attempt to use it more than once, it may fail. It is + suggested to only call this function once per connection call. + + Returns: `0` or `fail` + + > + server:listen(128, function (err) + local client = uv.new_tcp() + server:accept(client) + end) +< + +uv.read_start({stream}, {callback}) *uv.read_start()* + + > method form `stream:read_start(callback)` + + Parameters: + - `stream`: `userdata` for sub-type of |uv_stream_t| + - `callback`: `callable` + - `err`: `nil` or `string` + - `data`: `string` or `nil` + + Read data from an incoming stream. The callback will be made + several times until there is no more data to read or + |uv.read_stop()| is called. When we've reached EOF, `data` + will be `nil`. + + Returns: `0` or `fail` + + > + stream:read_start(function (err, chunk) + if err then + -- handle read error + elseif chunk then + -- handle data + else + -- handle disconnect + end + end) +< + +uv.read_stop({stream}) *uv.read_stop()* + + > method form `stream:read_stop()` + + Parameters: + - `stream`: `userdata` for sub-type of |uv_stream_t| + + Stop reading data from the stream. The read callback will no + longer be called. + + This function is idempotent and may be safely called on a + stopped stream. + + Returns: `0` or `fail` + +uv.write({stream}, {data} [, {callback}]) *uv.write()* + + > method form `stream:write(data, [callback])` + + Parameters: + - `stream`: `userdata` for sub-type of |uv_stream_t| + - `data`: `buffer` + - `callback`: `callable` or `nil` + - `err`: `nil` or `string` + + Write data to stream. + + `data` can either be a Lua string or a table of strings. If a + table is passed in, the C backend will use writev to send all + strings in a single system call. + + The optional `callback` is for knowing when the write is + complete. + + Returns: `uv_write_t userdata` or `fail` + +uv.write2({stream}, {data}, {send_handle} [, {callback}]) *uv.write2()* + + > method form `stream:write2(data, send_handle, [callback])` + + Parameters: + - `stream`: `userdata` for sub-type of |uv_stream_t| + - `data`: `buffer` + - `send_handle`: `userdata` for sub-type of |uv_stream_t| + - `callback`: `callable` or `nil` + - `err`: `nil` or `string` + + Extended write function for sending handles over a pipe. The + pipe must be initialized with `ipc` option `true`. + + Returns: `uv_write_t userdata` or `fail` + + Note: `send_handle` must be a TCP socket or pipe, which is a + server or a connection (listening or connected state). Bound + sockets or pipes will be assumed to be servers. + +uv.try_write({stream}, {data}) *uv.try_write()* + + > method form `stream:try_write(data)` + + Parameters: + - `stream`: `userdata` for sub-type of |uv_stream_t| + - `data`: `buffer` + + Same as |uv.write()|, but won't queue a write request if it + can't be completed immediately. + + Will return number of bytes written (can be less than the + supplied buffer size). + + Returns: `integer` or `fail` + +uv.try_write2({stream}, {data}, {send_handle}) *uv.try_write2()* + + > method form `stream:try_write2(data, send_handle)` + + Parameters: + - `stream`: `userdata` for sub-type of |uv_stream_t| + - `data`: `buffer` + - `send_handle`: `userdata` for sub-type of |uv_stream_t| + + Like |uv.write2()|, but with the properties of + |uv.try_write()|. Not supported on Windows, where it returns + `UV_EAGAIN`. + + Will return number of bytes written (can be less than the + supplied buffer size). + + Returns: `integer` or `fail` + +uv.is_readable({stream}) *uv.is_readable()* + + > method form `stream:is_readable()` + + Parameters: + - `stream`: `userdata` for sub-type of |uv_stream_t| + + Returns `true` if the stream is readable, `false` otherwise. + + Returns: `boolean` + +uv.is_writable({stream}) *uv.is_writable()* + + > method form `stream:is_writable()` + + Parameters: + - `stream`: `userdata` for sub-type of |uv_stream_t| + + Returns `true` if the stream is writable, `false` otherwise. + + Returns: `boolean` + +uv.stream_set_blocking({stream}, {blocking}) *uv.stream_set_blocking()* + + > method form `stream:set_blocking(blocking)` + + Parameters: + - `stream`: `userdata` for sub-type of |uv_stream_t| + - `blocking`: `boolean` + + Enable or disable blocking mode for a stream. + + When blocking mode is enabled all writes complete + synchronously. The interface remains unchanged otherwise, e.g. + completion or failure of the operation will still be reported + through a callback which is made asynchronously. + + Returns: `0` or `fail` + + WARNING: Relying too much on this API is not recommended. It + is likely to change significantly in the future. Currently + this only works on Windows and only for |uv_pipe_t| handles. + Also libuv currently makes no ordering guarantee when the + blocking mode is changed after write requests have already + been submitted. Therefore it is recommended to set the + blocking mode immediately after opening or creating the + stream. + +uv.stream_get_write_queue_size() *uv.stream_get_write_queue_size()* + + > method form `stream:get_write_queue_size()` + + Returns the stream's write queue size. + + Returns: `integer` + +============================================================================== +`uv_tcp_t` — TCP handle *luv-tcp-handle* *uv_tcp_t* + +> |uv_handle_t| and |uv_stream_t| functions also apply. + +TCP handles are used to represent both TCP streams and servers. + +uv.new_tcp([{flags}]) *uv.new_tcp()* + + Parameters: + - `flags`: `string` or `nil` + + Creates and initializes a new |uv_tcp_t|. Returns the Lua + userdata wrapping it. Flags may be a family string: `"unix"`, + `"inet"`, `"inet6"`, `"ipx"`, `"netlink"`, `"x25"`, `"ax25"`, + `"atmpvc"`, `"appletalk"`, or `"packet"`. + + Returns: `uv_tcp_t userdata` or `fail` + +uv.tcp_open({tcp}, {sock}) *uv.tcp_open()* + + > method form `tcp:open(sock)` + + Parameters: + - `tcp`: `uv_tcp_t userdata` + - `sock`: `integer` + + Open an existing file descriptor or SOCKET as a TCP handle. + + Returns: `0` or `fail` + + Note: The passed file descriptor or SOCKET is not checked for + its type, but it's required that it represents a valid stream + socket. + +uv.tcp_nodelay({tcp}, {enable}) *uv.tcp_nodelay()* + + > method form `tcp:nodelay(enable)` + + Parameters: + - `tcp`: `uv_tcp_t userdata` + - `enable`: `boolean` + + Enable / disable Nagle's algorithm. + + Returns: `0` or `fail` + +uv.tcp_keepalive({tcp}, {enable} [, {delay}]) *uv.tcp_keepalive()* + + > method form `tcp:keepalive(enable, [delay])` + + Parameters: + - `tcp`: `uv_tcp_t userdata` + - `enable`: `boolean` + - `delay`: `integer` or `nil` + + Enable / disable TCP keep-alive. `delay` is the initial delay + in seconds, ignored when enable is `false`. + + Returns: `0` or `fail` + +uv.tcp_simultaneous_accepts({tcp}, {enable}) *uv.tcp_simultaneous_accepts()* + + > method form `tcp:simultaneous_accepts(enable)` + + Parameters: + - `tcp`: `uv_tcp_t userdata` + - `enable`: `boolean` + + Enable / disable simultaneous asynchronous accept requests + that are queued by the operating system when listening for new + TCP connections. + + This setting is used to tune a TCP server for the desired + performance. Having simultaneous accepts can significantly + improve the rate of accepting connections (which is why it is + enabled by default) but may lead to uneven load distribution + in multi-process setups. + + Returns: `0` or `fail` + +uv.tcp_bind({tcp}, {host}, {port} [, {flags}]) *uv.tcp_bind()* + + > method form `tcp:bind(host, port, [flags])` + + Parameters: + - `tcp`: `uv_tcp_t userdata` + - `host`: `string` + - `port`: `integer` + - `flags`: `table` or `nil` + - `ipv6only`: `boolean` + + Bind the handle to an host and port. `host` should be an IP + address and not a domain name. Any `flags` are set with a + table with field `ipv6only` equal to `true` or `false`. + + When the port is already taken, you can expect to see an + `EADDRINUSE` error from either `uv.tcp_bind()`, |uv.listen()| + or |uv.tcp_connect()|. That is, a successful call to this + function does not guarantee that the call to |uv.listen()| or + |uv.tcp_connect()| will succeed as well. + + Use a port of `0` to let the OS assign an ephemeral port. You + can look it up later using |uv.tcp_getsockname()|. + + Returns: `0` or `fail` + +uv.tcp_getpeername({tcp}) *uv.tcp_getpeername()* + + > method form `tcp:getpeername()` + + Parameters: + - `tcp`: `uv_tcp_t userdata` + + Get the address of the peer connected to the handle. + + Returns: `table` or `fail` + - `ip` : `string` + - `family` : `string` + - `port` : `integer` + +uv.tcp_getsockname({tcp}) *uv.tcp_getsockname()* + + > method form `tcp:getsockname()` + + Parameters: + - `tcp`: `uv_tcp_t userdata` + + Get the current address to which the handle is bound. + + Returns: `table` or `fail` + - `ip` : `string` + - `family` : `string` + - `port` : `integer` + +uv.tcp_connect({tcp}, {host}, {port}, {callback}) *uv.tcp_connect()* + + > method form `tcp:connect(host, port, callback)` + + Parameters: + - `tcp`: `uv_tcp_t userdata` + - `host`: `string` + - `port`: `integer` + - `callback`: `callable` + - `err`: `nil` or `string` + + Establish an IPv4 or IPv6 TCP connection. + + Returns: `uv_connect_t userdata` or `fail` + + > + local client = uv.new_tcp() + client:connect("127.0.0.1", 8080, function (err) + -- check error and carry on. + end) +< + +uv.tcp_write_queue_size({tcp}) *uv.tcp_write_queue_size()* + + > method form `tcp:write_queue_size()` + + DEPRECATED: Please use |uv.stream_get_write_queue_size()| + instead. + +uv.tcp_close_reset([{callback}]) *uv.tcp_close_reset()* + + > method form `tcp:close_reset([callback])` + + Parameters: + - `tcp`: `uv_tcp_t userdata` + - `callback`: `callable` or `nil` + + Resets a TCP connection by sending a RST packet. This is + accomplished by setting the SO_LINGER socket option with a + linger interval of zero and then calling |uv.close()|. Due to + some platform inconsistencies, mixing of |uv.shutdown()| and + `uv.tcp_close_reset()` calls is not allowed. + + Returns: `0` or `fail` + *uv.socketpair()* +uv.socketpair([{socktype}, [{protocol}, [{flags1}, [{flags2}]]]]) + + Parameters: + - `socktype`: `string`, `integer` or `nil` (default: `stream`) + - `protocol`: `string`, `integer` or `nil` (default: 0) + - `flags1`: `table` or `nil` + - `nonblock`: `boolean` (default: `false`) + - `flags2`: `table` or `nil` + - `nonblock`: `boolean` (default: `false`) + + Create a pair of connected sockets with the specified + properties. The resulting handles can be passed to + |uv.tcp_open()|, used with |uv.spawn()|, or for any other + purpose. + + When specified as a string, `socktype` must be one of + `"stream"`, `"dgram"`, `"raw"`, `"rdm"`, or `"seqpacket"`. + + When `protocol` is set to 0 or nil, it will be automatically + chosen based on the socket's domain and type. When `protocol` + is specified as a string, it will be looked up using the + `getprotobyname(3)` function (examples: `"ip"`, `"icmp"`, + `"tcp"`, `"udp"`, etc). + + Flags: + - `nonblock`: Opens the specified socket handle for + `OVERLAPPED` or `FIONBIO`/`O_NONBLOCK` I/O usage. This is + recommended for handles that will be used by libuv, and not + usually recommended otherwise. + + Equivalent to `socketpair(2)` with a domain of `AF_UNIX`. + + Returns: `table` or `fail` + - `[1, 2]` : `integer` (file descriptor) + + > + -- Simple read/write with tcp + local fds = uv.socketpair(nil, nil, {nonblock=true}, {nonblock=true}) + + local sock1 = uv.new_tcp() + sock1:open(fds[1]) + + local sock2 = uv.new_tcp() + sock2:open(fds[2]) + + sock1:write("hello") + sock2:read_start(function(err, chunk) + assert(not err, err) + print(chunk) + end) +< + +============================================================================== +`uv_pipe_t` — Pipe handle *luv-pipe-handle* *uv_pipe_t* + +> |uv_handle_t| and |uv_stream_t| functions also apply. + +Pipe handles provide an abstraction over local domain sockets on Unix and +named pipes on Windows. + + > + local pipe = uv.new_pipe(false) + + pipe:bind('/tmp/sock.test') + + pipe:listen(128, function() + local client = uv.new_pipe(false) + pipe:accept(client) + client:write("hello!\n") + client:close() + end) +< + +uv.new_pipe([{ipc}]) *uv.new_pipe()* + + Parameters: + - `ipc`: `boolean` or `nil` (default: `false`) + + Creates and initializes a new |uv_pipe_t|. Returns the Lua + userdata wrapping it. The `ipc` argument is a boolean to + indicate if this pipe will be used for handle passing between + processes. + + Returns: `uv_pipe_t userdata` or `fail` + +uv.pipe_open({pipe}, {fd}) *uv.pipe_open()* + + > method form `pipe:open(fd)` + + Parameters: + - `pipe`: `uv_pipe_t userdata` + - `fd`: `integer` + + Open an existing file descriptor or |uv_handle_t| as a + pipe. + + Returns: `0` or `fail` + + Note: The file descriptor is set to non-blocking mode. + +uv.pipe_bind({pipe}, {name}) *uv.pipe_bind()* + + > method form `pipe:bind(name)` + + Parameters: + - `pipe`: `uv_pipe_t userdata` + - `name`: `string` + + Bind the pipe to a file path (Unix) or a name (Windows). + + Returns: `0` or `fail` + + Note: Paths on Unix get truncated to + sizeof(sockaddr_un.sun_path) bytes, typically between 92 and + 108 bytes. + +uv.pipe_connect({pipe}, {name} [, {callback}]) *uv.pipe_connect()* + + > method form `pipe:connect(name, [callback])` + + Parameters: + - `pipe`: `uv_pipe_t userdata` + - `name`: `string` + - `callback`: `callable` or `nil` + - `err`: `nil` or `string` + + Connect to the Unix domain socket or the named pipe. + + Returns: `uv_connect_t userdata` or `fail` + + Note: Paths on Unix get truncated to + sizeof(sockaddr_un.sun_path) bytes, typically between 92 and + 108 bytes. + +uv.pipe_getsockname({pipe}) *uv.pipe_getsockname()* + + > method form `pipe:getsockname()` + + Parameters: + - `pipe`: `uv_pipe_t userdata` + + Get the name of the Unix domain socket or the named pipe. + + Returns: `string` or `fail` + +uv.pipe_getpeername({pipe}) *uv.pipe_getpeername()* + + > method form `pipe:getpeername()` + + Parameters: + - `pipe`: `uv_pipe_t userdata` + + Get the name of the Unix domain socket or the named pipe to + which the handle is connected. + + Returns: `string` or `fail` + +uv.pipe_pending_instances({pipe}, {count}) *uv.pipe_pending_instances()* + + > method form `pipe:pending_instances(count)` + + Parameters: + - `pipe`: `uv_pipe_t userdata` + - `count`: `integer` + + Set the number of pending pipe instance handles when the pipe + server is waiting for connections. + + Returns: Nothing. + + Note: This setting applies to Windows only. + +uv.pipe_pending_count({pipe}) *uv.pipe_pending_count()* + + > method form `pipe:pending_count()` + + Parameters: + - `pipe`: `uv_pipe_t userdata` + + Returns the pending pipe count for the named pipe. + + Returns: `integer` + +uv.pipe_pending_type({pipe}) *uv.pipe_pending_type()* + + > method form `pipe:pending_type()` + + Parameters: + - `pipe`: `uv_pipe_t userdata` + + Used to receive handles over IPC pipes. + + First - call |uv.pipe_pending_count()|, if it's > 0 then + initialize a handle of the given type, returned by + `uv.pipe_pending_type()` and call `uv.accept(pipe, handle)` . + + Returns: `string` + +uv.pipe_chmod({pipe}, {flags}) *uv.pipe_chmod()* + + > method form `pipe:chmod(flags)` + + Parameters: + - `pipe`: `uv_pipe_t userdata` + - `flags`: `string` + + Alters pipe permissions, allowing it to be accessed from + processes run by different users. Makes the pipe writable or + readable by all users. `flags` are: `"r"`, `"w"`, `"rw"`, or + `"wr"` where `r` is `READABLE` and `w` is `WRITABLE`. This + function is blocking. + + Returns: `0` or `fail` + +uv.pipe({read_flags}, {write_flags}) *uv.pipe()* + + Parameters: + - `read_flags`: `table` or `nil` + - `nonblock`: `boolean` (default: `false`) + - `write_flags`: `table` or `nil` + - `nonblock`: `boolean` (default: `false`) + + Create a pair of connected pipe handles. Data may be written + to the `write` fd and read from the `read` fd. The resulting + handles can be passed to `pipe_open`, used with `spawn`, or + for any other purpose. + + Flags: + - `nonblock`: Opens the specified socket handle for + `OVERLAPPED` or `FIONBIO`/`O_NONBLOCK` I/O usage. This is + recommended for handles that will be used by libuv, and not + usually recommended otherwise. + + Equivalent to `pipe(2)` with the `O_CLOEXEC` flag set. + + Returns: `table` or `fail` + - `read` : `integer` (file descriptor) + - `write` : `integer` (file descriptor) + + > + -- Simple read/write with pipe_open + local fds = uv.pipe({nonblock=true}, {nonblock=true}) + + local read_pipe = uv.new_pipe() + read_pipe:open(fds.read) + + local write_pipe = uv.new_pipe() + write_pipe:open(fds.write) + + write_pipe:write("hello") + read_pipe:read_start(function(err, chunk) + assert(not err, err) + print(chunk) + end) +< + +============================================================================== +`uv_tty_t` — TTY handle *luv-tty-handle* *uv_tty_t* + +> |uv_handle_t| and |uv_stream_t| functions also apply. + +TTY handles represent a stream for the console. + + > + -- Simple echo program + local stdin = uv.new_tty(0, true) + local stdout = uv.new_tty(1, false) + + stdin:read_start(function (err, data) + assert(not err, err) + if data then + stdout:write(data) + else + stdin:close() + stdout:close() + end + end) +< + +uv.new_tty({fd}, {readable}) *uv.new_tty()* + + Parameters: + - `fd`: `integer` + - `readable`: `boolean` + + Initialize a new TTY stream with the given file descriptor. + Usually the file descriptor will be: + + - 0 - stdin + - 1 - stdout + - 2 - stderr + + On Unix this function will determine the path of the fd of the + terminal using ttyname_r(3), open it, and use it if the passed + file descriptor refers to a TTY. This lets libuv put the tty + in non-blocking mode without affecting other processes that + share the tty. + + This function is not thread safe on systems that don’t support + ioctl TIOCGPTN or TIOCPTYGNAME, for instance OpenBSD and + Solaris. + + Returns: `uv_tty_t userdata` or `fail` + + Note: If reopening the TTY fails, libuv falls back to blocking + writes. + +uv.tty_set_mode({tty}, {mode}) *uv.tty_set_mode()* + + > method form `tty:set_mode(mode)` + + Parameters: + - `tty`: `uv_tty_t userdata` + - `mode`: `integer` + + Set the TTY using the specified terminal mode. + + Parameter `mode` is a C enum with the following values: + + - 0 - UV_TTY_MODE_NORMAL: Initial/normal terminal mode + - 1 - UV_TTY_MODE_RAW: Raw input mode (On Windows, + ENABLE_WINDOW_INPUT is also enabled) + - 2 - UV_TTY_MODE_IO: Binary-safe I/O mode for IPC + (Unix-only) + + Returns: `0` or `fail` + +uv.tty_reset_mode() *uv.tty_reset_mode()* + + To be called when the program exits. Resets TTY settings to + default values for the next process to take over. + + This function is async signal-safe on Unix platforms but can + fail with error code `EBUSY` if you call it when execution is + inside |uv.tty_set_mode()|. + + Returns: `0` or `fail` + +uv.tty_get_winsize({tty}) *uv.tty_get_winsize()* + + > method form `tty:get_winsize()` + + Parameters: + - `tty`: `uv_tty_t userdata` + + Gets the current Window width and height. + + Returns: `integer, integer` or `fail` + +uv.tty_set_vterm_state({state}) *uv.tty_set_vterm_state()* + + Parameters: + - `state`: `string` + + Controls whether console virtual terminal sequences are + processed by libuv or console. Useful in particular for + enabling ConEmu support of ANSI X3.64 and Xterm 256 colors. + Otherwise Windows10 consoles are usually detected + automatically. State should be one of: `"supported"` or + `"unsupported"`. + + This function is only meaningful on Windows systems. On Unix + it is silently ignored. + + Returns: none + +uv.tty_get_vterm_state() *uv.tty_get_vterm_state()* + + Get the current state of whether console virtual terminal + sequences are handled by libuv or the console. The return + value is `"supported"` or `"unsupported"`. + + This function is not implemented on Unix, where it returns + `ENOTSUP`. + + Returns: `string` or `fail` + +============================================================================== +`uv_udp_t` — UDP handle *luv-udp-handle* *uv_udp_t* + +> |uv_handle_t| functions also apply. + +UDP handles encapsulate UDP communication for both clients and servers. + +uv.new_udp([{flags}]) *uv.new_udp()* + + Parameters: + - `flags`: `table` or `nil` + - `family`: `string` or `nil` + - `mmsgs`: `integer` or `nil` (default: `1`) + + Creates and initializes a new |uv_udp_t|. Returns the Lua + userdata wrapping it. The actual socket is created lazily. + + When specified, `family` must be one of `"unix"`, `"inet"`, + `"inet6"`, `"ipx"`, `"netlink"`, `"x25"`, `"ax25"`, + `"atmpvc"`, `"appletalk"`, or `"packet"`. + + When specified, `mmsgs` determines the number of messages able + to be received at one time via `recvmmsg(2)` (the allocated + buffer will be sized to be able to fit the specified number of + max size dgrams). Only has an effect on platforms that support + `recvmmsg(2)`. + + Note: For backwards compatibility reasons, `flags` can also be + a string or integer. When it is a string, it will be treated + like the `family` key above. When it is an integer, it will be + used directly as the `flags` parameter when calling + `uv_udp_init_ex`. + + Returns: `uv_udp_t userdata` or `fail` + +uv.udp_get_send_queue_size() *uv.udp_get_send_queue_size()* + + > method form `udp:get_send_queue_size()` + + Returns the handle's send queue size. + + Returns: `integer` + +uv.udp_get_send_queue_count() *uv.udp_get_send_queue_count()* + + > method form `udp:get_send_queue_count()` + + Returns the handle's send queue count. + + Returns: `integer` + +uv.udp_open({udp}, {fd}) *uv.udp_open()* + + > method form `udp:open(fd)` + + Parameters: + - `udp`: `uv_udp_t userdata` + - `fd`: `integer` + + Opens an existing file descriptor or Windows SOCKET as a UDP + handle. + + Unix only: The only requirement of the sock argument is that + it follows the datagram contract (works in unconnected mode, + supports sendmsg()/recvmsg(), etc). In other words, other + datagram-type sockets like raw sockets or netlink sockets can + also be passed to this function. + + The file descriptor is set to non-blocking mode. + + Note: The passed file descriptor or SOCKET is not checked for + its type, but it's required that it represents a valid + datagram socket. + + Returns: `0` or `fail` + +uv.udp_bind({udp}, {host}, {port} [, {flags}]) *uv.udp_bind()* + + > method form `udp:bind(host, port, [flags])` + + Parameters: + - `udp`: `uv_udp_t userdata` + - `host`: `string` + - `port`: `number` + - `flags`: `table` or `nil` + - `ipv6only`: `boolean` + - `reuseaddr`: `boolean` + + Bind the UDP handle to an IP address and port. Any `flags` are + set with a table with fields `reuseaddr` or `ipv6only` equal + to `true` or `false`. + + Returns: `0` or `fail` + +uv.udp_getsockname({udp}) *uv.udp_getsockname()* + + > method form `udp:getsockname()` + + Parameters: + - `udp`: `uv_udp_t userdata` + + Get the local IP and port of the UDP handle. + + Returns: `table` or `fail` + - `ip` : `string` + - `family` : `string` + - `port` : `integer` + +uv.udp_getpeername({udp}) *uv.udp_getpeername()* + + > method form `udp:getpeername()` + + Parameters: + - `udp`: `uv_udp_t userdata` + + Get the remote IP and port of the UDP handle on connected UDP + handles. + + Returns: `table` or `fail` + - `ip` : `string` + - `family` : `string` + - `port` : `integer` + + *uv.udp_set_membership()* +uv.udp_set_membership({udp}, {multicast_addr}, {interface_addr}, {membership}) + + > method form + > `udp:set_membership(multicast_addr, interface_addr, membership)` + + Parameters: + - `udp`: `uv_udp_t userdata` + - `multicast_addr`: `string` + - `interface_addr`: `string` or `nil` + - `membership`: `string` + + Set membership for a multicast address. `multicast_addr` is + multicast address to set membership for. `interface_addr` is + interface address. `membership` can be the string `"leave"` or + `"join"`. + + Returns: `0` or `fail` + + *uv.udp_set_source_membership()* +uv.udp_set_source_membership({udp}, {multicast_addr}, {interface_addr}, {source_addr}, {membership}) + + > method form + > `udp:set_source_membership(multicast_addr, interface_addr, source_addr, membership)` + + Parameters: + - `udp`: `uv_udp_t userdata` + - `multicast_addr`: `string` + - `interface_addr`: `string` or `nil` + - `source_addr`: `string` + - `membership`: `string` + + Set membership for a source-specific multicast group. + `multicast_addr` is multicast address to set membership for. + `interface_addr` is interface address. `source_addr` is source + address. `membership` can be the string `"leave"` or `"join"`. + + Returns: `0` or `fail` + +uv.udp_set_multicast_loop({udp}, {on}) *uv.udp_set_multicast_loop()* + + > method form `udp:set_multicast_loop(on)` + + Parameters: + - `udp`: `uv_udp_t userdata` + - `on`: `boolean` + + Set IP multicast loop flag. Makes multicast packets loop back + to local sockets. + + Returns: `0` or `fail` + +uv.udp_set_multicast_ttl({udp}, {ttl}) *uv.udp_set_multicast_ttl()* + + > method form `udp:set_multicast_ttl(ttl)` + + Parameters: + - `udp`: `uv_udp_t userdata` + - `ttl`: `integer` + + Set the multicast ttl. + + `ttl` is an integer 1 through 255. + + Returns: `0` or `fail` + + *uv.udp_set_multicast_interface()* +uv.udp_set_multicast_interface({udp}, {interface_addr}) + + > method form `udp:set_multicast_interface(interface_addr)` + + Parameters: + - `udp`: `uv_udp_t userdata` + - `interface_addr`: `string` + + Set the multicast interface to send or receive data on. + + Returns: `0` or `fail` + +uv.udp_set_broadcast({udp}, {on}) *uv.udp_set_broadcast()* + + > method form `udp:set_broadcast(on)` + + Parameters: + - `udp`: `uv_udp_t userdata` + - `on`: `boolean` + + Set broadcast on or off. + + Returns: `0` or `fail` + +uv.udp_set_ttl({udp}, {ttl}) *uv.udp_set_ttl()* + + > method form `udp:set_ttl(ttl)` + + Parameters: + - `udp`: `uv_udp_t userdata` + - `ttl`: `integer` + + Set the time to live. + + `ttl` is an integer 1 through 255. + + Returns: `0` or `fail` + +uv.udp_send({udp}, {data}, {host}, {port}, {callback}) *uv.udp_send()* + + > method form `udp:send(data, host, port, callback)` + + Parameters: + - `udp`: `uv_udp_t userdata` + - `data`: `buffer` + - `host`: `string` + - `port`: `integer` + - `callback`: `callable` + - `err`: `nil` or `string` + + Send data over the UDP socket. If the socket has not + previously been bound with |uv.udp_bind()| it will be bound to + `0.0.0.0` (the "all interfaces" IPv4 address) and a random + port number. + + Returns: `uv_udp_send_t userdata` or `fail` + +uv.udp_try_send({udp}, {data}, {host}, {port}) *uv.udp_try_send()* + + > method form `udp:try_send(data, host, port)` + + Parameters: + - `udp`: `uv_udp_t userdata` + - `data`: `buffer` + - `host`: `string` + - `port`: `integer` + + Same as |uv.udp_send()|, but won't queue a send request if it + can't be completed immediately. + + Returns: `integer` or `fail` + +uv.udp_recv_start({udp}, {callback}) *uv.udp_recv_start()* + + > method form `udp:recv_start(callback)` + + Parameters: + - `udp`: `uv_udp_t userdata` + - `callback`: `callable` + - `err`: `nil` or `string` + - `data`: `string` or `nil` + - `addr`: `table` or `nil` + - `ip`: `string` + - `port`: `integer` + - `family`: `string` + - `flags`: `table` + - `partial`: `boolean` or `nil` + - `mmsg_chunk`: `boolean` or `nil` + + Prepare for receiving data. If the socket has not previously + been bound with |uv.udp_bind()| it is bound to `0.0.0.0` (the + "all interfaces" IPv4 address) and a random port number. + + Returns: `0` or `fail` + +uv.udp_recv_stop({udp}) *uv.udp_recv_stop()* + + > method form `udp:recv_stop()` + + Parameters: + - `udp`: `uv_udp_t userdata` + + Stop listening for incoming datagrams. + + Returns: `0` or `fail` + +uv.udp_connect({udp}, {host}, {port}) *uv.udp_connect()* + + > method form `udp:connect(host, port)` + + Parameters: + - `udp`: `uv_udp_t userdata` + - `host`: `string` + - `port`: `integer` + + Associate the UDP handle to a remote address and port, so + every message sent by this handle is automatically sent to + that destination. Calling this function with a NULL addr + disconnects the handle. Trying to call `uv.udp_connect()` on + an already connected handle will result in an `EISCONN` error. + Trying to disconnect a handle that is not connected will + return an `ENOTCONN` error. + + Returns: `0` or `fail` + +============================================================================== +`uv_fs_event_t` — FS Event handle *luv-fs-event-handle* *uv_fs_event_t* + +> |uv_handle_t| functions also apply. + +FS Event handles allow the user to monitor a given path for changes, for +example, if the file was renamed or there was a generic change in it. This +handle uses the best backend for the job on each platform. + +uv.new_fs_event() *uv.new_fs_event()* + + Creates and initializes a new |uv_fs_event_t|. Returns the Lua + userdata wrapping it. + + Returns: `uv_fs_event_t userdata` or `fail` + +uv.fs_event_start({fs_event}, {path}, {flags}, {callback}) *uv.fs_event_start()* + + > method form `fs_event:start(path, flags, callback)` + + Parameters: + - `fs_event`: `uv_fs_event_t userdata` + - `path`: `string` + - `flags`: `table` + - `watch_entry`: `boolean` or `nil` (default: `false`) + - `stat`: `boolean` or `nil` (default: `false`) + - `recursive`: `boolean` or `nil` (default: `false`) + - `callback`: `callable` + - `err`: `nil` or `string` + - `filename`: `string` + - `events`: `table` + - `change`: `boolean` or `nil` + - `rename`: `boolean` or `nil` + + Start the handle with the given callback, which will watch the + specified path for changes. + + Returns: `0` or `fail` + +uv.fs_event_stop() *uv.fs_event_stop()* + + > method form `fs_event:stop()` + + Stop the handle, the callback will no longer be called. + + Returns: `0` or `fail` + +uv.fs_event_getpath() *uv.fs_event_getpath()* + + > method form `fs_event:getpath()` + + Get the path being monitored by the handle. + + Returns: `string` or `fail` + +============================================================================== +`uv_fs_poll_t` — FS Poll handle *luv-fs-poll-handle* *uv_fs_poll_t* + +> |uv_handle_t| functions also apply. + +FS Poll handles allow the user to monitor a given path for changes. Unlike +|uv_fs_event_t|, fs poll handles use `stat` to detect when a file has changed +so they can work on file systems where fs event handles can't. + +uv.new_fs_poll() *uv.new_fs_poll()* + + Creates and initializes a new |uv_fs_poll_t|. Returns the Lua + userdata wrapping it. + + Returns: `uv_fs_poll_t userdata` or `fail` + +uv.fs_poll_start({fs_poll}, {path}, {interval}, {callback}) *uv.fs_poll_start()* + + > method form `fs_poll:start(path, interval, callback)` + + Parameters: + - `fs_event`: `uv_fs_event_t userdata` + - `path`: `string` + - `interval`: `integer` + - `callback`: `callable` + - `err`: `nil` or `string` + - `prev`: `table` or `nil` (see `uv.fs_stat`) + - `curr`: `table` or `nil` (see `uv.fs_stat`) + + Check the file at `path` for changes every `interval` + milliseconds. + + Note: For maximum portability, use multi-second intervals. + Sub-second intervals will not detect all changes on many file + systems. + + Returns: `0` or `fail` + +uv.fs_poll_stop() *uv.fs_poll_stop()* + + > method form `fs_poll:stop()` + + Stop the handle, the callback will no longer be called. + + Returns: `0` or `fail` + +uv.fs_poll_getpath() *uv.fs_poll_getpath()* + + > method form `fs_poll:getpath()` + + Get the path being monitored by the handle. + + Returns: `string` or `fail` + +============================================================================== +FILE SYSTEM OPERATIONS *luv-file-system-operations* *uv_fs_t* + +Most file system functions can operate synchronously or asynchronously. When a +synchronous version is called (by omitting a callback), the function will +immediately return the results of the FS call. When an asynchronous version is +called (by providing a callback), the function will immediately return a +`uv_fs_t userdata` and asynchronously execute its callback; if an error is +encountered, the first and only argument passed to the callback will be the +`err` error string; if the operation completes successfully, the first +argument will be `nil` and the remaining arguments will be the results of the +FS call. + +Synchronous and asynchronous versions of `readFile` (with naive error +handling) are implemented below as an example: + + > + local function readFileSync(path) + local fd = assert(uv.fs_open(path, "r", 438)) + local stat = assert(uv.fs_fstat(fd)) + local data = assert(uv.fs_read(fd, stat.size, 0)) + assert(uv.fs_close(fd)) + return data + end + + local data = readFileSync("main.lua") + print("synchronous read", data) +< + + > + local function readFile(path, callback) + uv.fs_open(path, "r", 438, function(err, fd) + assert(not err, err) + uv.fs_fstat(fd, function(err, stat) + assert(not err, err) + uv.fs_read(fd, stat.size, 0, function(err, data) + assert(not err, err) + uv.fs_close(fd, function(err) + assert(not err, err) + return callback(data) + end) + end) + end) + end) + end + + readFile("main.lua", function(data) + print("asynchronous read", data) + end) +< + +uv.fs_close({fd} [, {callback}]) *uv.fs_close()* + + Parameters: + - `fd`: `integer` + - `callback`: `callable` (async version) or `nil` (sync + version) + - `err`: `nil` or `string` + - `success`: `boolean` or `nil` + + Equivalent to `close(2)`. + + Returns (sync version): `boolean` or `fail` + + Returns (async version): `uv_fs_t userdata` + +uv.fs_open({path}, {flags}, {mode} [, {callback}]) *uv.fs_open()* + + Parameters: + - `path`: `string` + - `flags`: `string` or `integer` + - `mode`: `integer` + - `callback`: `callable` (async version) or `nil` (sync + version) + - `err`: `nil` or `string` + - `fd`: `integer` or `nil` + + Equivalent to `open(2)`. Access `flags` may be an integer or + one of: `"r"`, `"rs"`, `"sr"`, `"r+"`, `"rs+"`, `"sr+"`, + `"w"`, `"wx"`, `"xw"`, `"w+"`, `"wx+"`, `"xw+"`, `"a"`, + `"ax"`, `"xa"`, `"a+"`, `"ax+"`, or "`xa+`". + + Returns (sync version): `integer` or `fail` + + Returns (async version): `uv_fs_t userdata` + + Note: On Windows, libuv uses `CreateFileW` and thus the file + is always opened in binary mode. Because of this, the + `O_BINARY` and `O_TEXT` flags are not supported. + +uv.fs_read({fd}, {size} [, {offset} [, {callback}]]) *uv.fs_read()* + + Parameters: + - `fd`: `integer` + - `size`: `integer` + - `offset`: `integer` or `nil` + - `callback`: `callable` (async version) or `nil` (sync + version) + - `err`: `nil` or `string` + - `data`: `string` or `nil` + + Equivalent to `preadv(2)`. Returns any data. An empty string + indicates EOF. + + If `offset` is nil or omitted, it will default to `-1`, which + indicates 'use and update the current file offset.' + + Note: When `offset` is >= 0, the current file offset will not + be updated by the read. + + Returns (sync version): `string` or `fail` + + Returns (async version): `uv_fs_t userdata` + +uv.fs_unlink({path} [, {callback}]) *uv.fs_unlink()* + + Parameters: + - `path`: `string` + - `callback`: `callable` (async version) or `nil` (sync + version) + - `err`: `nil` or `string` + - `success`: `boolean` or `nil` + + Equivalent to `unlink(2)`. + + Returns (sync version): `boolean` or `fail` + + Returns (async version): `uv_fs_t userdata` + +uv.fs_write({fd}, {data} [, {offset} [, {callback}]]) *uv.fs_write()* + + Parameters: + - `fd`: `integer` + - `data`: `buffer` + - `offset`: `integer` or `nil` + - `callback`: `callable` (async version) or `nil` (sync + version) + - `err`: `nil` or `string` + - `bytes`: `integer` or `nil` + + Equivalent to `pwritev(2)`. Returns the number of bytes + written. + + If `offset` is nil or omitted, it will default to `-1`, which + indicates 'use and update the current file offset.' + + Note: When `offset` is >= 0, the current file offset will not + be updated by the write. + + Returns (sync version): `integer` or `fail` + + Returns (async version): `uv_fs_t userdata` + +uv.fs_mkdir({path}, {mode} [, {callback}]) *uv.fs_mkdir()* + + Parameters: + - `path`: `string` + - `mode`: `integer` + - `callback`: `callable` (async version) or `nil` (sync + version) + - `err`: `nil` or `string` + - `success`: `boolean` or `nil` + + Equivalent to `mkdir(2)`. + + Returns (sync version): `boolean` or `fail` + + Returns (async version): `uv_fs_t userdata` + +uv.fs_mkdtemp({template} [, {callback}]) *uv.fs_mkdtemp()* + + Parameters: + - `template`: `string` + - `callback`: `callable` (async version) or `nil` (sync + version) + - `err`: `nil` or `string` + - `path`: `string` or `nil` + + Equivalent to `mkdtemp(3)`. + + Returns (sync version): `string` or `fail` + + Returns (async version): `uv_fs_t userdata` + +uv.fs_mkstemp({template} [, {callback}]) *uv.fs_mkstemp()* + + Parameters: + - `template`: `string` + - `callback`: `callable` (async version) or `nil` (sync + version) + - `err`: `nil` or `string` + - `fd`: `integer` or `nil` + - `path`: `string` or `nil` + + Equivalent to `mkstemp(3)`. Returns a temporary file handle + and filename. + + Returns (sync version): `integer, string` or `fail` + + Returns (async version): `uv_fs_t userdata` + +uv.fs_rmdir({path} [, {callback}]) *uv.fs_rmdir()* + + Parameters: + - `path`: `string` + - `callback`: `callable` (async version) or `nil` (sync + version) + - `err`: `nil` or `string` + - `success`: `boolean` or `nil` + + Equivalent to `rmdir(2)`. + + Returns (sync version): `boolean` or `fail` + + Returns (async version): `uv_fs_t userdata` + +uv.fs_scandir({path} [, {callback}]) *uv.fs_scandir()* + + Parameters: + - `path`: `string` + - `callback`: `callable` + - `err`: `nil` or `string` + - `success`: `uv_fs_t userdata` or `nil` + + Equivalent to `scandir(3)`, with a slightly different API. + Returns a handle that the user can pass to + |uv.fs_scandir_next()|. + + Note: This function can be used synchronously or + asynchronously. The request userdata is always synchronously + returned regardless of whether a callback is provided and the + same userdata is passed to the callback if it is provided. + + Returns: `uv_fs_t userdata` or `fail` + +uv.fs_scandir_next({fs}) *uv.fs_scandir_next()* + + Parameters: + - `fs`: `uv_fs_t userdata` + + Called on a |uv_fs_t| returned by |uv.fs_scandir()| to get the + next directory entry data as a `name, type` pair. When there + are no more entries, `nil` is returned. + + Note: This function only has a synchronous version. See + |uv.fs_opendir()| and its related functions for an + asynchronous version. + + Returns: `string, string` or `nil` or `fail` + +uv.fs_stat({path} [, {callback}]) *uv.fs_stat()* + + Parameters: + - `path`: `string` + - `callback`: `callable` (async version) or `nil` (sync + version) + - `err`: `nil` or `string` + - `stat`: `table` or `nil` (see below) + + Equivalent to `stat(2)`. + + Returns (sync version): `table` or `fail` + - `dev` : `integer` + - `mode` : `integer` + - `nlink` : `integer` + - `uid` : `integer` + - `gid` : `integer` + - `rdev` : `integer` + - `ino` : `integer` + - `size` : `integer` + - `blksize` : `integer` + - `blocks` : `integer` + - `flags` : `integer` + - `gen` : `integer` + - `atime` : `table` + - `sec` : `integer` + - `nsec` : `integer` + - `mtime` : `table` + - `sec` : `integer` + - `nsec` : `integer` + - `ctime` : `table` + - `sec` : `integer` + - `nsec` : `integer` + - `birthtime` : `table` + - `sec` : `integer` + - `nsec` : `integer` + - `type` : `string` + + Returns (async version): `uv_fs_t userdata` + +uv.fs_fstat({fd} [, {callback}]) *uv.fs_fstat()* + + Parameters: + - `fd`: `integer` + - `callback`: `callable` (async version) or `nil` (sync + version) + - `err`: `nil` or `string` + - `stat`: `table` or `nil` (see `uv.fs_stat`) + + Equivalent to `fstat(2)`. + + Returns (sync version): `table` or `fail` (see `uv.fs_stat`) + + Returns (async version): `uv_fs_t userdata` + +uv.fs_lstat({path} [, {callback}]) *uv.fs_lstat()* + + Parameters: + - `fd`: `integer` + - `callback`: `callable` (async version) or `nil` (sync + version) + - `err`: `nil` or `string` + - `stat`: `table` or `nil` (see `uv.fs_stat`) + + Equivalent to `lstat(2)`. + + Returns (sync version): `table` or `fail` (see |uv.fs_stat()|) + + Returns (async version): `uv_fs_t userdata` + +uv.fs_rename({path}, {new_path} [, {callback}]) *uv.fs_rename()* + + Parameters: + - `path`: `string` + - `new_path`: `string` + - `callback`: `callable` (async version) or `nil` (sync + version) + - `err`: `nil` or `string` + - `success`: `boolean` or `nil` + + Equivalent to `rename(2)`. + + Returns (sync version): `boolean` or `fail` + + Returns (async version): `uv_fs_t userdata` + +uv.fs_fsync({fd} [, {callback}]) *uv.fs_fsync()* + + Parameters: + - `fd`: `integer` + - `callback`: `callable` (async version) or `nil` (sync + version) + - `err`: `nil` or `string` + - `success`: `boolean` or `nil` + + Equivalent to `fsync(2)`. + + Returns (sync version): `boolean` or `fail` + + Returns (async version): `uv_fs_t userdata` + +uv.fs_fdatasync({fd} [, {callback}]) *uv.fs_fdatasync()* + + Parameters: + - `fd`: `integer` + - `callback`: `callable` (async version) or `nil` (sync + version) + - `err`: `nil` or `string` + - `success`: `boolean` or `nil` + + Equivalent to `fdatasync(2)`. + + Returns (sync version): `boolean` or `fail` + + Returns (async version): `uv_fs_t userdata` + +uv.fs_ftruncate({fd}, {offset} [, {callback}]) *uv.fs_ftruncate()* + + Parameters: + - `fd`: `integer` + - `offset`: `integer` + - `callback`: `callable` (async version) or `nil` (sync + version) + - `err`: `nil` or `string` + - `success`: `boolean` or `nil` + + Equivalent to `ftruncate(2)`. + + Returns (sync version): `boolean` or `fail` + + Returns (async version): `uv_fs_t userdata` + + *uv.fs_sendfile()* +uv.fs_sendfile({out_fd}, {in_fd}, {in_offset}, {size} [, {callback}]) + + Parameters: + - `out_fd`: `integer` + - `in_fd`: `integer` + - `in_offset`: `integer` + - `size`: `integer` + - `callback`: `callable` (async version) or `nil` (sync + version) + - `err`: `nil` or `string` + - `bytes`: `integer` or `nil` + + Limited equivalent to `sendfile(2)`. Returns the number of + bytes written. + + Returns (sync version): `integer` or `fail` + + Returns (async version): `uv_fs_t userdata` + +uv.fs_access({path}, {mode} [, {callback}]) *uv.fs_access()* + + Parameters: + - `path`: `string` + - `mode`: `integer` + - `callback`: `callable` (async version) or `nil` (sync + version) + - `err`: `nil` or `string` + - `permission`: `boolean` or `nil` + + Equivalent to `access(2)` on Unix. Windows uses + `GetFileAttributesW()`. Access `mode` can be an integer or a + string containing `"R"` or `"W"` or `"X"`. Returns `true` or + `false` indicating access permission. + + Returns (sync version): `boolean` or `fail` + + Returns (async version): `uv_fs_t userdata` + +uv.fs_chmod({path}, {mode} [, {callback}]) *uv.fs_chmod()* + + Parameters: + - `path`: `string` + - `mode`: `integer` + - `callback`: `callable` (async version) or `nil` (sync + version) + - `err`: `nil` or `string` + - `success`: `boolean` or `nil` + + Equivalent to `chmod(2)`. + + Returns (sync version): `boolean` or `fail` + + Returns (async version): `uv_fs_t userdata` + +uv.fs_fchmod({fd}, {mode} [, {callback}]) *uv.fs_fchmod()* + + Parameters: + - `fd`: `integer` + - `mode`: `integer` + - `callback`: `callable` (async version) or `nil` (sync + version) + - `err`: `nil` or `string` + - `success`: `boolean` or `nil` + + Equivalent to `fchmod(2)`. + + Returns (sync version): `boolean` or `fail` + + Returns (async version): `uv_fs_t userdata` + +uv.fs_utime({path}, {atime}, {mtime} [, {callback}]) *uv.fs_utime()* + + Parameters: + - `path`: `string` + - `atime`: `number` + - `mtime`: `number` + - `callback`: `callable` (async version) or `nil` (sync + version) + - `err`: `nil` or `string` + - `success`: `boolean` or `nil` + + Equivalent to `utime(2)`. + + Returns (sync version): `boolean` or `fail` + + Returns (async version): `uv_fs_t userdata` + +uv.fs_futime({fd}, {atime}, {mtime} [, {callback}]) *uv.fs_futime()* + + Parameters: + - `fd`: `integer` + - `atime`: `number` + - `mtime`: `number` + - `callback`: `callable` (async version) or `nil` (sync + version) + - `err`: `nil` or `string` + - `success`: `boolean` or `nil` + + Equivalent to `futime(2)`. + + Returns (sync version): `boolean` or `fail` + + Returns (async version): `uv_fs_t userdata` + +uv.fs_lutime({path}, {atime}, {mtime} [, {callback}]) *uv.fs_lutime()* + + Parameters: + - `path`: `string` + - `atime`: `number` + - `mtime`: `number` + - `callback`: `callable` (async version) or `nil` (sync + version) + - `err`: `nil` or `string` + - `success`: `boolean` or `nil` + + Equivalent to `lutime(2)`. + + Returns (sync version): `boolean` or `fail` + + Returns (async version): `uv_fs_t userdata` + +uv.fs_link({path}, {new_path} [, {callback}]) *uv.fs_link()* + + Parameters: + - `path`: `string` + - `new_path`: `string` + - `callback`: `callable` (async version) or `nil` (sync + version) + - `err`: `nil` or `string` + - `success`: `boolean` or `nil` + + Equivalent to `link(2)`. + + Returns (sync version): `boolean` or `fail` + + Returns (async version): `uv_fs_t userdata` + +uv.fs_symlink({path}, {new_path} [, {flags} [, {callback}]]) *uv.fs_symlink()* + + Parameters: + - `path`: `string` + - `new_path`: `string` + - `flags`: `table`, `integer`, or `nil` + - `dir`: `boolean` + - `junction`: `boolean` + - `callback`: `callable` (async version) or `nil` (sync + version) + - `err`: `nil` or `string` + - `success`: `boolean` or `nil` + + Equivalent to `symlink(2)`. If the `flags` parameter is + omitted, then the 3rd parameter will be treated as the + `callback`. + + Returns (sync version): `boolean` or `fail` + + Returns (async version): `uv_fs_t userdata` + +uv.fs_readlink({path} [, {callback}]) *uv.fs_readlink()* + + Parameters: + - `path`: `string` + - `callback`: `callable` (async version) or `nil` (sync + version) + - `err`: `nil` or `string` + - `path`: `string` or `nil` + + Equivalent to `readlink(2)`. + + Returns (sync version): `string` or `fail` + + Returns (async version): `uv_fs_t userdata` + +uv.fs_realpath({path} [, {callback}]) *uv.fs_realpath()* + + Parameters: + - `path`: `string` + - `callback`: `callable` (async version) or `nil` (sync + version) + - `err`: `nil` or `string` + - `path`: `string` or `nil` + + Equivalent to `realpath(3)`. + + Returns (sync version): `string` or `fail` + + Returns (async version): `uv_fs_t userdata` + +uv.fs_chown({path}, {uid}, {gid} [, {callback}]) *uv.fs_chown()* + + Parameters: + - `path`: `string` + - `uid`: `integer` + - `gid`: `integer` + - `callback`: `callable` (async version) or `nil` (sync + version) + - `err`: `nil` or `string` + - `success`: `boolean` or `nil` + + Equivalent to `chown(2)`. + + Returns (sync version): `boolean` or `fail` + + Returns (async version): `uv_fs_t userdata` + +uv.fs_fchown({fd}, {uid}, {gid} [, {callback}]) *uv.fs_fchown()* + + Parameters: + - `fd`: `integer` + - `uid`: `integer` + - `gid`: `integer` + - `callback`: `callable` (async version) or `nil` (sync + version) + - `err`: `nil` or `string` + - `success`: `boolean` or `nil` + + Equivalent to `fchown(2)`. + + Returns (sync version): `boolean` or `fail` + + Returns (async version): `uv_fs_t userdata` + +uv.fs_lchown({fd}, {uid}, {gid} [, {callback}]) *uv.fs_lchown()* + + Parameters: + - `fd`: `integer` + - `uid`: `integer` + - `gid`: `integer` + - `callback`: `callable` (async version) or `nil` (sync + version) + - `err`: `nil` or `string` + - `success`: `boolean` or `nil` + + Equivalent to `lchown(2)`. + + Returns (sync version): `boolean` or `fail` + + Returns (async version): `uv_fs_t userdata` + +uv.fs_copyfile({path}, {new_path} [, {flags} [, {callback}]]) *uv.fs_copyfile()* + + Parameters: + - `path`: `string` + - `new_path`: `string` + - `flags`: `table`, `integer`, or `nil` + - `excl`: `boolean` + - `ficlone`: `boolean` + - `ficlone_force`: `boolean` + - `callback`: `callable` (async version) or `nil` (sync + version) + - `err`: `nil` or `string` + - `success`: `boolean` or `nil` + + Copies a file from path to new_path. If the `flags` parameter + is omitted, then the 3rd parameter will be treated as the + `callback`. + + Returns (sync version): `boolean` or `fail` + + Returns (async version): `uv_fs_t userdata` + +uv.fs_opendir({path} [, {callback} [, {entries}]]) *uv.fs_opendir()* + + Parameters: + - `path`: `string` + - `callback`: `callable` (async version) or `nil` (sync + version) + - `err`: `nil` or `string` + - `dir`: `luv_dir_t userdata` or `nil` + - `entries`: `integer` or `nil` + + Opens path as a directory stream. Returns a handle that the + user can pass to |uv.fs_readdir()|. The `entries` parameter + defines the maximum number of entries that should be returned + by each call to |uv.fs_readdir()|. + + Returns (sync version): `luv_dir_t userdata` or `fail` + + Returns (async version): `uv_fs_t userdata` + +uv.fs_readdir({dir} [, {callback}]) *uv.fs_readdir()* + + > method form `dir:readdir([callback])` + + Parameters: + - `dir`: `luv_dir_t userdata` + - `callback`: `callable` (async version) or `nil` (sync + version) + - `err`: `nil` or `string` + - `entries`: `table` or `nil` (see below) + + Iterates over the directory stream `luv_dir_t` returned by a + successful |uv.fs_opendir()| call. A table of data tables is + returned where the number of entries `n` is equal to or less + than the `entries` parameter used in the associated + |uv.fs_opendir()| call. + + Returns (sync version): `table` or `fail` + - `[1, 2, 3, ..., n]` : `table` + - `name` : `string` + - `type` : `string` + + Returns (async version): `uv_fs_t userdata` + +uv.fs_closedir({dir} [, {callback}]) *uv.fs_closedir()* + + > method form `dir:closedir([callback])` + + Parameters: + - `dir`: `luv_dir_t userdata` + - `callback`: `callable` (async version) or `nil` (sync + version) + - `err`: `nil` or `string` + - `success`: `boolean` or `nil` + + Closes a directory stream returned by a successful + |uv.fs_opendir()| call. + + Returns (sync version): `boolean` or `fail` + + Returns (async version): `uv_fs_t userdata` + +uv.fs_statfs({path} [, {callback}]) *uv.fs_statfs()* + + Parameters: + - `path`: `string` + - `callback`: `callable` (async version) or `nil` (sync + version) + - `err`: `nil` or `string` + - `table` or `nil` (see below) + + Equivalent to `statfs(2)`. + + Returns `table` or `nil` + - `type` : `integer` + - `bsize` : `integer` + - `blocks` : `integer` + - `bfree` : `integer` + - `bavail` : `integer` + - `files` : `integer` + - `ffree` : `integer` + +============================================================================== +THREAD POOL WORK SCHEDULING *luv-thread-pool-work-scheduling* + +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. + + > + local function work_callback(a, b) + return a + b + end + + local function after_work_callback(c) + print("The result is: " .. c) + end + + local work = uv.new_work(work_callback, after_work_callback) + + work:queue(1, 2) + + -- output: "The result is: 3" +< + +uv.new_work({work_callback}, {after_work_callback}) *uv.new_work()* + + Parameters: + - `work_callback`: `function` + - `...`: `threadargs` passed to/from + `uv.queue_work(work_ctx, ...)` + - `after_work_callback`: `function` + - `...`: `threadargs` returned from `work_callback` + + Creates and initializes a new `luv_work_ctx_t` (not + `uv_work_t`). Returns the Lua userdata wrapping it. + + Returns: `luv_work_ctx_t userdata` + +uv.queue_work({work_ctx}, {...}) *uv.queue_work()* + + > method form `work_ctx:queue(...)` + + Parameters: + - `work_ctx`: `luv_work_ctx_t userdata` + - `...`: `threadargs` + + Queues a work request which will run `work_callback` in a new + Lua state in a thread from the threadpool with any additional + arguments from `...`. Values returned from `work_callback` are + passed to `after_work_callback`, which is called in the main + loop thread. + + Returns: `boolean` or `fail` + +============================================================================== +DNS UTILITY FUNCTIONS *luv-dns-utility-functions* + +uv.getaddrinfo({host}, {service} [, {hints} [, {callback}]]) *uv.getaddrinfo()* + + Parameters: + - `host`: `string` or `nil` + - `service`: `string` or `nil` + - `hints`: `table` or `nil` + - `family`: `string` or `integer` or `nil` + - `socktype`: `string` or `integer` or `nil` + - `protocol`: `string` or `integer` or `nil` + - `addrconfig`: `boolean` or `nil` + - `v4mapped`: `boolean` or `nil` + - `all`: `boolean` or `nil` + - `numerichost`: `boolean` or `nil` + - `passive`: `boolean` or `nil` + - `numericserv`: `boolean` or `nil` + - `canonname`: `boolean` or `nil` + - `callback`: `callable` (async version) or `nil` (sync + version) + - `err`: `nil` or `string` + - `addresses`: `table` or `nil` (see below) + + Equivalent to `getaddrinfo(3)`. Either `node` or `service` may + be `nil` but not both. + + Valid hint strings for the keys that take a string: + - `family`: `"unix"`, `"inet"`, `"inet6"`, `"ipx"`, + `"netlink"`, `"x25"`, `"ax25"`, `"atmpvc"`, `"appletalk"`, + or `"packet"` + - `socktype`: `"stream"`, `"dgram"`, `"raw"`, `"rdm"`, or + `"seqpacket"` + - `protocol`: will be looked up using the `getprotobyname(3)` + function (examples: `"ip"`, `"icmp"`, `"tcp"`, `"udp"`, etc) + + Returns (sync version): `table` or `fail` + - `[1, 2, 3, ..., n]` : `table` + - `addr` : `string` + - `family` : `string` + - `port` : `integer` or `nil` + - `socktype` : `string` + - `protocol` : `string` + - `canonname` : `string` or `nil` + + Returns (async version): `uv_getaddrinfo_t userdata` or `fail` + +uv.getnameinfo({address} [, {callback}]) *uv.getnameinfo()* + + Parameters: + - `address`: `table` + - `ip`: `string` or `nil` + - `port`: `integer` or `nil` + - `family`: `string` or `integer` or `nil` + - `callback`: `callable` (async version) or `nil` (sync + version) + - `err`: `nil` or `sring` + - `host`: `string` or `nil` + - `service`: `string` or `nil` + + Equivalent to `getnameinfo(3)`. + + When specified, `family` must be one of `"unix"`, `"inet"`, + `"inet6"`, `"ipx"`, `"netlink"`, `"x25"`, `"ax25"`, + `"atmpvc"`, `"appletalk"`, or `"packet"`. + + Returns (sync version): `string, string` or `fail` + + Returns (async version): `uv_getnameinfo_t userdata` or `fail` + +============================================================================== +THREADING AND SYNCHRONIZATION UTILITIES *luv-threading-and-synchronization-utilities* + +Libuv provides cross-platform implementations for multiple threading an +synchronization primitives. The API largely follows the pthreads API. + +uv.new_thread([{options}, ] {entry}, {...}) *uv.new_thread()* + + Parameters: + - `options`: `table` or `nil` + - `stack_size`: `integer` or `nil` + - `entry`: `function` + - `...`: `threadargs` passed to `entry` + + Creates and initializes a `luv_thread_t` (not `uv_thread_t`). + Returns the Lua userdata wrapping it and asynchronously + executes `entry`, which can be either a Lua function or a Lua + function dumped to a string. Additional arguments `...` are + passed to the `entry` function and an optional `options` table + may be provided. Currently accepted `option` fields are + `stack_size`. + + Returns: `luv_thread_t userdata` or `fail` + +uv.thread_equal({thread}, {other_thread}) *uv.thread_equal()* + + > method form `thread:equal(other_thread)` + + Parameters: + - `thread`: `luv_thread_t userdata` + - `other_thread`: `luv_thread_t userdata` + + Returns a boolean indicating whether two threads are the same. + This function is equivalent to the `__eq` metamethod. + + Returns: `boolean` + +uv.thread_self() *uv.thread_self()* + + Returns the handle for the thread in which this is called. + + Returns: `luv_thread_t` + +uv.thread_join({thread}) *uv.thread_join()* + + > method form `thread:join()` + + Parameters: + - `thread`: `luv_thread_t userdata` + + Waits for the `thread` to finish executing its entry function. + + Returns: `boolean` or `fail` + +uv.sleep({msec}) *uv.sleep()* + + Parameters: + - `msec`: `integer` + + Pauses the thread in which this is called for a number of + milliseconds. + + Returns: Nothing. + +============================================================================== +MISCELLANEOUS UTILITIES *luv-miscellaneous-utilities* + +uv.exepath() *uv.exepath()* + + Returns the executable path. + + Returns: `string` or `fail` + +uv.cwd() *uv.cwd()* + + Returns the current working directory. + + Returns: `string` or `fail` + +uv.chdir({cwd}) *uv.chdir()* + + Parameters: + - `cwd`: `string` + + Sets the current working directory with the string `cwd`. + + Returns: `0` or `fail` + +uv.get_process_title() *uv.get_process_title()* + + Returns the title of the current process. + + Returns: `string` or `fail` + +uv.set_process_title({title}) *uv.set_process_title()* + + Parameters: + - `title`: `string` + + Sets the title of the current process with the string `title`. + + Returns: `0` or `fail` + +uv.get_total_memory() *uv.get_total_memory()* + + Returns the current total system memory in bytes. + + Returns: `number` + +uv.get_free_memory() *uv.get_free_memory()* + + Returns the current free system memory in bytes. + + Returns: `number` + +uv.get_constrained_memory() *uv.get_constrained_memory()* + + Gets the amount of memory available to the process in bytes + based on limits imposed by the OS. If there is no such + constraint, or the constraint is unknown, 0 is returned. Note + that it is not unusual for this value to be less than or + greater than the total system memory. + + Returns: `number` + +uv.resident_set_memory() *uv.resident_set_memory()* + + Returns the resident set size (RSS) for the current process. + + Returns: `integer` or `fail` + +uv.getrusage() *uv.getrusage()* + + Returns the resource usage. + + Returns: `table` or `fail` + - `utime` : `table` (user CPU time used) + - `sec` : `integer` + - `usec` : `integer` + - `stime` : `table` (system CPU time used) + - `sec` : `integer` + - `usec` : `integer` + - `maxrss` : `integer` (maximum resident set size) + - `ixrss` : `integer` (integral shared memory size) + - `idrss` : `integer` (integral unshared data size) + - `isrss` : `integer` (integral unshared stack size) + - `minflt` : `integer` (page reclaims (soft page faults)) + - `majflt` : `integer` (page faults (hard page faults)) + - `nswap` : `integer` (swaps) + - `inblock` : `integer` (block input operations) + - `oublock` : `integer` (block output operations) + - `msgsnd` : `integer` (IPC messages sent) + - `msgrcv` : `integer` (IPC messages received) + - `nsignals` : `integer` (signals received) + - `nvcsw` : `integer` (voluntary context switches) + - `nivcsw` : `integer` (involuntary context switches) + +uv.available_parallelism() *uv.available_parallelism()* + + Returns an estimate of the default amount of parallelism a + program should use. Always returns a non-zero value. + + On Linux, inspects the calling thread’s CPU affinity mask to + determine if it has been pinned to specific CPUs. + + On Windows, the available parallelism may be underreported on + systems with more than 64 logical CPUs. + + On other platforms, reports the number of CPUs that the + operating system considers to be online. + + Returns: `integer` + +uv.cpu_info() *uv.cpu_info()* + + Returns information about the CPU(s) on the system as a table + of tables for each CPU found. + + Returns: `table` or `fail` + - `[1, 2, 3, ..., n]` : `table` + - `model` : `string` + - `speed` : `number` + - `times` : `table` + - `user` : `number` + - `nice` : `number` + - `sys` : `number` + - `idle` : `number` + - `irq` : `number` + +uv.getpid() *uv.getpid()* + + DEPRECATED: Please use |uv.os_getpid()| instead. + +uv.getuid() *uv.getuid()* + + Returns the user ID of the process. + + Returns: `integer` + + Note: This is not a libuv function and is not supported on + Windows. + +uv.getgid() *uv.getgid()* + + Returns the group ID of the process. + + Returns: `integer` + + Note: This is not a libuv function and is not supported on + Windows. + +uv.setuid({id}) *uv.setuid()* + + Parameters: + - `id`: `integer` + + Sets the user ID of the process with the integer `id`. + + Returns: Nothing. + + Note: This is not a libuv function and is not supported on + Windows. + +uv.setgid({id}) *uv.setgid()* + + Parameters: + - `id`: `integer` + + Sets the group ID of the process with the integer `id`. + + Returns: Nothing. + + Note: This is not a libuv function and is not supported on + Windows. + +uv.hrtime() *uv.hrtime()* + + Returns a current high-resolution time in nanoseconds as a + number. This is relative to an arbitrary time in the past. It + is not related to the time of day and therefore not subject to + clock drift. The primary use is for measuring time between + intervals. + + Returns: `number` + +uv.uptime() *uv.uptime()* + + Returns the current system uptime in seconds. + + Returns: `number` or `fail` + +uv.print_all_handles() *uv.print_all_handles()* + + Prints all handles associated with the main loop to stderr. + The format is `[flags] handle-type handle-address` . Flags are + `R` for referenced, `A` for active and `I` for internal. + + Returns: Nothing. + + Note: This is not available on Windows. + + WARNING: This function is meant for ad hoc debugging, there + are no API/ABI stability guarantees. + +uv.print_active_handles() *uv.print_active_handles()* + + The same as |uv.print_all_handles()| except only active + handles are printed. + + Returns: Nothing. + + Note: This is not available on Windows. + + WARNING: This function is meant for ad hoc debugging, there + are no API/ABI stability guarantees. + +uv.guess_handle({fd}) *uv.guess_handle()* + + Parameters: + - `fd`: `integer` + + Used to detect what type of stream should be used with a given + file descriptor `fd`. Usually this will be used during + initialization to guess the type of the stdio streams. + + Returns: `string` + +uv.gettimeofday() *uv.gettimeofday()* + + Cross-platform implementation of `gettimeofday(2)`. Returns + the seconds and microseconds of a unix time as a pair. + + Returns: `integer, integer` or `fail` + +uv.interface_addresses() *uv.interface_addresses()* + + Returns address information about the network interfaces on + the system in a table. Each table key is the name of the + interface while each associated value is an array of address + information where fields are `ip`, `family`, `netmask`, + `internal`, and `mac`. + + Returns: `table` + - `[name(s)]` : `table` + - `ip` : `string` + - `family` : `string` + - `netmask` : `string` + - `internal` : `boolean` + - `mac` : `string` + +uv.if_indextoname({ifindex}) *uv.if_indextoname()* + + Parameters: + - `ifindex`: `integer` + + IPv6-capable implementation of `if_indextoname(3)`. + + Returns: `string` or `fail` + +uv.if_indextoiid({ifindex}) *uv.if_indextoiid()* + + Parameters: + - `ifindex`: `integer` + + Retrieves a network interface identifier suitable for use in + an IPv6 scoped address. On Windows, returns the numeric + `ifindex` as a string. On all other platforms, + |uv.if_indextoname()| is used. + + Returns: `string` or `fail` + +uv.loadavg() *uv.loadavg()* + + Returns the load average as a triad. Not supported on Windows. + + Returns: `number, number, number` + +uv.os_uname() *uv.os_uname()* + + Returns system information. + + Returns: `table` + - `sysname` : `string` + - `release` : `string` + - `version` : `string` + - `machine` : `string` + +uv.os_gethostname() *uv.os_gethostname()* + + Returns the hostname. + + Returns: `string` + +uv.os_getenv({name} [, {size}]) *uv.os_getenv()* + + Parameters: + - `name`: `string` + - `size`: `integer` (default = `LUAL_BUFFERSIZE`) + + Returns the environment variable specified by `name` as + string. The internal buffer size can be set by defining + `size`. If omitted, `LUAL_BUFFERSIZE` is used. If the + environment variable exceeds the storage available in the + internal buffer, `ENOBUFS` is returned. If no matching + environment variable exists, `ENOENT` is returned. + + Returns: `string` or `fail` + + WARNING: This function is not thread safe. + +uv.os_setenv({name}, {value}) *uv.os_setenv()* + + Parameters: + - `name`: `string` + - `value`: `string` + + Sets the environmental variable specified by `name` with the + string `value`. + + Returns: `boolean` or `fail` + + WARNING: This function is not thread safe. + +uv.os_unsetenv() *uv.os_unsetenv()* + + Returns: `boolean` or `fail` + + WARNING: This function is not thread safe. + +uv.os_environ() *uv.os_environ()* + + Returns all environmental variables as a dynamic table of + names associated with their corresponding values. + + Returns: `table` + + WARNING: This function is not thread safe. + +uv.os_homedir() *uv.os_homedir()* + + Returns: `string` or `fail` + + WARNING: This function is not thread safe. + +uv.os_tmpdir() *uv.os_tmpdir()* + + Returns: `string` or `fail` + + WARNING: This function is not thread safe. + +uv.os_get_passwd() *uv.os_get_passwd()* + + Returns password file information. + + Returns: `table` + - `username` : `string` + - `uid` : `integer` + - `gid` : `integer` + - `shell` : `string` + - `homedir` : `string` + +uv.os_getpid() *uv.os_getpid()* + + Returns the current process ID. + + Returns: `number` + +uv.os_getppid() *uv.os_getppid()* + + Returns the parent process ID. + + Returns: `number` + +uv.os_getpriority({pid}) *uv.os_getpriority()* + + Parameters: + - `pid`: `integer` + + Returns the scheduling priority of the process specified by + `pid`. + + Returns: `number` or `fail` + +uv.os_setpriority({pid}, {priority}) *uv.os_setpriority()* + + Parameters: + - `pid`: `integer` + - `priority`: `integer` + + Sets the scheduling priority of the process specified by + `pid`. The `priority` range is between -20 (high priority) and + 19 (low priority). + + Returns: `boolean` or `fail` + +uv.random({len}, {flags} [, {callback}]) *uv.random()* + + Parameters: + - `len`: `integer` + - `flags`: `nil` (see below) + - `callback`: `callable` (async version) or `nil` (sync + version) + - `err`: `nil` or `string` + - `bytes`: `string` or `nil` + + Fills a string of length `len` with cryptographically strong + random bytes acquired from the system CSPRNG. `flags` is + reserved for future extension and must currently be `nil` or + `0` or `{}`. + + Short reads are not possible. When less than `len` random + bytes are available, a non-zero error value is returned or + passed to the callback. If the callback is omitted, this + function is completed synchronously. + + The synchronous version may block indefinitely when not enough + entropy is available. The asynchronous version may not ever + finish when the system is low on entropy. + + Returns (sync version): `string` or `fail` + + Returns (async version): `0` or `fail` + +uv.translate_sys_error({errcode}) *uv.translate_sys_error()* + + Parameters: + - `errcode`: `integer` + + Returns the libuv error message and error name (both in string + form, see `err` and `name` in |luv-error-handling|) equivalent + to the given platform dependent error code: POSIX error codes + on Unix (the ones stored in errno), and Win32 error codes on + Windows (those returned by GetLastError() or + WSAGetLastError()). + + Returns: `string, string` or `nil` + +============================================================================== +METRICS OPERATIONS *luv-metrics-operations* + +uv.metrics_idle_time() *uv.metrics_idle_time()* + + Retrieve the amount of time the event loop has been idle in + the kernel’s event provider (e.g. `epoll_wait`). The call is + thread safe. + + The return value is the accumulated time spent idle in the + kernel’s event provider starting from when the |uv_loop_t| was + configured to collect the idle time. + + Note: The event loop will not begin accumulating the event + provider’s idle time until calling `loop_configure` with + `"metrics_idle_time"`. + + Returns: `number` + +============================================================================== +CREDITS *luv-credits* + +This document is a reformatted version of the LUV documentation, based on +commit c51e705 (5 May 2022) of the luv repository +https://github.com/luvit/luv/commit/c51e7052ec4f0a25058f70c1b4ee99dd36180e59. + +Included from https://github.com/nanotee/luv-vimdocs with kind permission. + + +vim:tw=78:ts=8:ft=help:norl: diff --git a/runtime/doc/map.txt b/runtime/doc/map.txt index b0ce2d4164..ca1ddaabd4 100644 --- a/runtime/doc/map.txt +++ b/runtime/doc/map.txt @@ -905,6 +905,17 @@ or `unnamedplus`. The `mode()` function will return the state as it will be after applying the operator. +Here is an example for using a lambda function to create a normal-mode +operator to add quotes around text in the current line: > + + nnoremap <F4> <Cmd>let &opfunc='{t -> + \ getline(".") + \ ->split("\\zs") + \ ->insert("\"", col("'']")) + \ ->insert("\"", col("''[") - 1) + \ ->join("") + \ ->setline(".")}'<CR>g@ + ============================================================================== 2. Abbreviations *abbreviations* *Abbreviations* @@ -1452,6 +1463,9 @@ Incremental preview ~ Commands can show an 'inccommand' (as-you-type) preview by defining a preview handler (only from Lua, see |nvim_create_user_command()|). +Before the preview callback is executed, Nvim will temporarily disable +'cursorline' and 'cursorcolumn' to avoid highlighting issues. + The preview callback must be a Lua function with this signature: > function cmdpreview(opts, ns, buf) @@ -1463,10 +1477,10 @@ results (for "inccommand=split", or nil for "inccommand=nosplit"). Your command preview routine must implement this protocol: -1. Modify the current buffer as required for the preview (see +1. Modify the target buffers as required for the preview (see |nvim_buf_set_text()| and |nvim_buf_set_lines()|). 2. If preview buffer is provided, add necessary text to the preview buffer. -3. Add required highlights to the current buffer. If preview buffer is +3. Add required highlights to the target buffers. If preview buffer is provided, add required highlights to the preview buffer as well. All highlights must be added to the preview namespace which is provided as an argument to the preview callback (see |nvim_buf_add_highlight()| and @@ -1477,8 +1491,8 @@ Your command preview routine must implement this protocol: 2: Preview is shown and preview window is opened (if "inccommand=split"). For "inccommand=nosplit" this is the same as 1. -After preview ends, Nvim discards all changes to the buffer and all highlights -in the preview namespace. +After preview ends, Nvim discards all changes to all buffers made during the +preview and clears all highlights in the preview namespace. Here's an example of a command to trim trailing whitespace from lines that supports incremental command preview: diff --git a/runtime/doc/motion.txt b/runtime/doc/motion.txt index 9655d07a84..511b1bd7b2 100644 --- a/runtime/doc/motion.txt +++ b/runtime/doc/motion.txt @@ -588,7 +588,8 @@ i) *v_i)* *i)* *i(* i( *vib* *v_ib* *v_i(* *ib* ib "inner block", select [count] blocks, from "[count] [(" to the matching ')', excluding the '(' and ')' (see - |[(|). + |[(|). If the cursor is not inside a () block, then + find the next "(". When used in Visual mode it is made charwise. a> *v_a>* *v_a<* *a>* *a<* diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index 9d03397821..9e396dd3e8 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -312,6 +312,17 @@ Note: In the future more global options can be made |global-local|. Using ":setlocal" on a global option might work differently then. + *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: +> + set opfunc=MyOpFunc + set opfunc=function("MyOpFunc") + set opfunc=funcref("MyOpFunc") + set opfunc={t\ ->\ MyOpFunc(t)} +< + Setting the filetype :setf[iletype] [FALLBACK] {filetype} *:setf* *:setfiletype* @@ -591,7 +602,11 @@ A jump table for the options with a short description can be found at |Q_op|. "double": Use twice the width of ASCII characters. *E834* *E835* The value "double" cannot be used if 'listchars' or 'fillchars' - contains a character that would be double width. + contains a character that would be double width. These errors may + also be given when calling setcellwidths(). + + The values are overruled for characters specified with + |setcellwidths()|. There are a number of CJK fonts for which the width of glyphs for those characters are solely based on how many octets they take in @@ -614,7 +629,8 @@ A jump table for the options with a short description can be found at |Q_op|. When on, Vim will change the current working directory whenever you open a file, switch buffers, delete a buffer or open/close a window. It will change to the directory containing the file which was opened - or selected. + or selected. When a buffer has no name it also has no directory, thus + the current directory won't change when navigating to it. Note: When this option is on some plugins may not work. *'arabic'* *'arab'* *'noarabic'* *'noarab'* @@ -1321,7 +1337,9 @@ A jump table for the options with a short description can be found at |Q_op|. page can have a different value. When 'cmdheight' is zero, there is no command-line unless it is being - used. Any messages will cause the |hit-enter| prompt. + used. Some informative messages will not be displayed, any other + messages will cause the |hit-enter| prompt. Expect some other + unexpected behavior too. *'cmdwinheight'* *'cwh'* 'cmdwinheight' 'cwh' number (default 7) @@ -2139,7 +2157,8 @@ A jump table for the options with a short description can be found at |Q_op|. When on all Unicode emoji characters are considered to be full width. This excludes "text emoji" characters, which are normally displayed as single width. Unfortunately there is no good specification for this - and it has been determined on trial-and-error basis. + and it has been determined on trial-and-error basis. Use the + |setcellwidths()| function to change the behavior. *'encoding'* *'enc'* *E543* 'encoding' 'enc' @@ -2435,28 +2454,30 @@ A jump table for the options with a short description can be found at |Q_op|. *'fillchars'* *'fcs'* 'fillchars' 'fcs' string (default "") global or local to window |global-local| - Characters to fill the statuslines and vertical separators. - It is a comma-separated list of items: + Characters to fill the statuslines, vertical separators and special + lines in the window. + It is a comma-separated list of items. Each item has a name, a colon + and the value of that item: item default Used for ~ - stl:c ' ' or '^' statusline of the current window - stlnc:c ' ' or '=' statusline of the non-current windows - wbr:c ' ' window bar - horiz:c '─' or '-' horizontal separators |:split| - horizup:c '┴' or '-' upwards facing horizontal separator - horizdown:c '┬' or '-' downwards facing horizontal separator - vert:c '│' or '|' vertical separators |:vsplit| - vertleft:c '┤' or '|' left facing vertical separator - vertright:c '├' or '|' right facing vertical separator - verthoriz:c '┼' or '+' overlapping vertical and horizontal + stl ' ' or '^' statusline of the current window + stlnc ' ' or '=' statusline of the non-current windows + wbr ' ' window bar + horiz '─' or '-' horizontal separators |:split| + horizup '┴' or '-' upwards facing horizontal separator + horizdown '┬' or '-' downwards facing horizontal separator + vert '│' or '|' vertical separators |:vsplit| + vertleft '┤' or '|' left facing vertical separator + vertright '├' or '|' right facing vertical separator + verthoriz '┼' or '+' overlapping vertical and horizontal separator - fold:c '·' or '-' filling 'foldtext' - foldopen:c '-' mark the beginning of a fold - foldclose:c '+' show a closed fold - foldsep:c '│' or '|' open fold middle marker - diff:c '-' deleted lines of the 'diff' option - msgsep:c ' ' message separator 'display' - eob:c '~' empty lines at the end of a buffer + fold '·' or '-' filling 'foldtext' + foldopen '-' mark the beginning of a fold + foldclose '+' show a closed fold + foldsep '│' or '|' open fold middle marker + diff '-' deleted lines of the 'diff' option + msgsep ' ' message separator 'display' + eob '~' empty lines at the end of a buffer Any one that is omitted will fall back to the default. For "stl" and "stlnc" the space will be used when there is highlighting, '^' or '=' @@ -2481,19 +2502,19 @@ A jump table for the options with a short description can be found at |Q_op|. The highlighting used for these items: item highlight group ~ - stl:c StatusLine |hl-StatusLine| - stlnc:c StatusLineNC |hl-StatusLineNC| - wbr:c WinBar |hl-WinBar| or |hl-WinBarNC| - horiz:c WinSeparator |hl-WinSeparator| - horizup:c WinSeparator |hl-WinSeparator| - horizdown:c WinSeparator |hl-WinSeparator| - vert:c WinSeparator |hl-WinSeparator| - vertleft:c WinSeparator |hl-WinSeparator| - vertright:c WinSeparator |hl-WinSeparator| - verthoriz:c WinSeparator |hl-WinSeparator| - fold:c Folded |hl-Folded| - diff:c DiffDelete |hl-DiffDelete| - eob:c EndOfBuffer |hl-EndOfBuffer| + stl StatusLine |hl-StatusLine| + stlnc StatusLineNC |hl-StatusLineNC| + wbr WinBar |hl-WinBar| or |hl-WinBarNC| + horiz WinSeparator |hl-WinSeparator| + horizup WinSeparator |hl-WinSeparator| + horizdown WinSeparator |hl-WinSeparator| + vert WinSeparator |hl-WinSeparator| + vertleft WinSeparator |hl-WinSeparator| + vertright WinSeparator |hl-WinSeparator| + verthoriz WinSeparator |hl-WinSeparator| + fold Folded |hl-Folded| + diff DiffDelete |hl-DiffDelete| + eob EndOfBuffer |hl-EndOfBuffer| *'fixendofline'* *'fixeol'* *'nofixendofline'* *'nofixeol'* 'fixendofline' 'fixeol' boolean (default on) @@ -3731,8 +3752,8 @@ A jump table for the options with a short description can be found at |Q_op|. *'lispwords'* *'lw'* 'lispwords' 'lw' string (default is very long) global or local to buffer |global-local| - Comma-separated list of words that influence the Lisp indenting. - |'lisp'| + Comma-separated list of words that influence the Lisp indenting when + enabled with the |'lisp'| option. *'list'* *'nolist'* 'list' boolean (default off) @@ -4395,7 +4416,9 @@ A jump table for the options with a short description can be found at |Q_op|. 'operatorfunc' 'opfunc' string (default: empty) global This option specifies a function to be called by the |g@| operator. - See |:map-operator| for more info and an example. + See |:map-operator| for more info and an example. 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. @@ -4689,8 +4712,9 @@ A jump table for the options with a short description can be found at |Q_op|. customize the information displayed in the quickfix or location window for each entry in the corresponding quickfix or location list. See |quickfix-window-function| for an explanation of how to write the - function and an example. The value can be the name of a function or a - lambda. + 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. This option cannot be set from a |modeline| or in the |sandbox|, for security reasons. @@ -4712,8 +4736,8 @@ A jump table for the options with a short description can be found at |Q_op|. in read-only mode ("vim -R") or when the executable is called "view". When using ":w!" the 'readonly' option is reset for the current buffer, unless the 'Z' flag is in 'cpoptions'. - When using the ":view" command the 'readonly' option is - set for the newly edited buffer. + When using the ":view" command the 'readonly' option is set for the + newly edited buffer. See 'modifiable' for disallowing changes to the buffer. *'redrawdebug'* *'rdb'* @@ -4834,7 +4858,7 @@ A jump table for the options with a short description can be found at |Q_op|. search "/" and "?" commands - This is useful for languages such as Hebrew and Arabic. + This is useful for languages such as Hebrew, Arabic and Farsi. The 'rightleft' option must be set for 'rightleftcmd' to take effect. *'ruler'* *'ru'* *'noruler'* *'noru'* @@ -5870,6 +5894,11 @@ A jump table for the options with a short description can be found at |Q_op|. suggestions is never more than the value of 'lines' minus two. + timeout:{millisec} Limit the time searching for suggestions to + {millisec} milli seconds. Applies to the following + methods. When omitted the limit is 5000. When + negative there is no limit. + file:{filename} Read file {filename}, which must have two columns, separated by a slash. The first column contains the bad word, the second column the suggested good word. @@ -6185,7 +6214,7 @@ A jump table for the options with a short description can be found at |Q_op|. global This option controls the behavior when switching between buffers. Mostly for |quickfix| commands some values are also used for other - commands, as mentioned below. + commands, as mentioned below. Possible values (comma-separated list): useopen If included, jump to the first open window that contains the specified buffer (if there is one). @@ -6911,15 +6940,18 @@ A jump table for the options with a short description can be found at |Q_op|. *'wildmenu'* *'wmnu'* *'nowildmenu'* *'nowmnu'* 'wildmenu' 'wmnu' boolean (default on) global - Enables "enhanced mode" of command-line completion. When user hits - <Tab> (or 'wildchar') to invoke completion, the possible matches are - shown in a menu just above the command-line (see 'wildoptions'), with - the first match highlighted (overwriting the statusline). Keys that - show the previous/next match (<Tab>/CTRL-P/CTRL-N) highlight the - match. + When 'wildmenu' is on, command-line completion operates in an enhanced + mode. On pressing 'wildchar' (usually <Tab>) to invoke completion, + the possible matches are shown. + When 'wildoptions' contains "pum", then the completion matches are + shown in a popup menu. Otherwise they are displayed just above the + command line, with the first match highlighted (overwriting the status + line, if there is one). + Keys that show the previous/next match, such as <Tab> or + CTRL-P/CTRL-N, cause the highlight to move to the appropriate match. 'wildmode' must specify "full": "longest" and "list" do not start 'wildmenu' mode. You can check the current mode with |wildmenumode()|. - The menu is canceled when a key is hit that is not used for selecting + The menu is cancelled when a key is hit that is not used for selecting a completion. While the menu is active these keys have special meanings: @@ -6993,7 +7025,8 @@ A jump table for the options with a short description can be found at |Q_op|. *'wildoptions'* *'wop'* 'wildoptions' 'wop' string (default "pum,tagfile") global - List of words that change how |cmdline-completion| is done. + A list of words that change how |cmdline-completion| is done. + The following values are supported: 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 @@ -7084,10 +7117,12 @@ A jump table for the options with a short description can be found at |Q_op|. 'winhighlight' 'winhl' string (default empty) local to window Window-local highlights. Comma-delimited list of highlight - |group-name| pairs "{hl-builtin}:{hl},..." where each {hl-builtin} is - a built-in |highlight-groups| item to be overridden by {hl} group in - the window. Only built-in |highlight-groups| are supported, not - syntax highlighting (use |:ownsyntax| for that). + |group-name| pairs "{hl-from}:{hl-to},..." where each {hl-from} is + a |highlight-groups| item to be overridden by {hl-to} group in + the window. + + Note: highlight namespaces take precedence over 'winhighlight'. + See |nvim_win_set_hl_ns| and |nvim_set_hl|. Highlights of vertical separators are determined by the window to the left of the separator. The 'tabline' highlight of a tabpage is diff --git a/runtime/doc/syntax.txt b/runtime/doc/syntax.txt index 9ed3c37b8c..6fcf292513 100644 --- a/runtime/doc/syntax.txt +++ b/runtime/doc/syntax.txt @@ -181,16 +181,16 @@ Vim will only load the first syntax file found, assuming that it sets b:current_syntax. -NAMING CONVENTIONS *group-name* *{group-name}* *E669* *W18* +NAMING CONVENTIONS *group-name* *{group-name}* *E669* *E5248* A syntax group name is to be used for syntax items that match the same kind of thing. These are then linked to a highlight group that specifies the color. A syntax group name doesn't specify any color or attributes itself. -The name for a highlight or syntax group must consist of ASCII letters, digits -and the underscore. As a regexp: "[a-zA-Z0-9_]*". However, Vim does not give -an error when using other characters. The maximum length of a group name is -about 200 bytes. *E1249* +The name for a highlight or syntax group must consist of ASCII letters, +digits, underscores, periods and `@` characters. As a regexp it is +`[a-zA-Z0-9_.@]*`. The maximum length of a group name is about 200 bytes. +*E1249* To be able to allow each user to pick their favorite set of colors, there must be preferred names for highlight groups that are common for many languages. @@ -5250,7 +5250,7 @@ TabLineSel Tab pages line, active tab page label. Title Titles for output from ":set all", ":autocmd" etc. *hl-Visual* Visual Visual mode selection. - *hl-VisualNOS* + *hl-VisualNOS* VisualNOS Visual mode selection when vim is "Not Owning the Selection". *hl-WarningMsg* WarningMsg Warning messages. diff --git a/runtime/doc/treesitter.txt b/runtime/doc/treesitter.txt index eb19bf5934..2c6c9e4ed8 100644 --- a/runtime/doc/treesitter.txt +++ b/runtime/doc/treesitter.txt @@ -1,15 +1,15 @@ *treesitter.txt* Nvim - NVIM REFERENCE MANUAL + NVIM REFERENCE MANUAL -Tree-sitter integration *treesitter* +Tree-sitter integration *treesitter* Type |gO| to see the table of contents. ------------------------------------------------------------------------------ -VIM.TREESITTER *lua-treesitter* +VIM.TREESITTER *lua-treesitter* Nvim integrates the tree-sitter library for incremental parsing of buffers. @@ -21,7 +21,7 @@ library. The earliest parser ABI version that is supported by the bundled tree-sitter library. -Parser files *treesitter-parsers* +Parser files *treesitter-parsers* Parsers are the heart of tree-sitter. They are libraries that tree-sitter will search for in the `parser` runtime directory. Currently Nvim does not provide @@ -44,9 +44,9 @@ is subject to change. A plugin should keep a reference to the parser object as long as it wants incremental updates. -Parser methods *lua-treesitter-parser* +Parser methods *lua-treesitter-parser* -tsparser:parse() *tsparser:parse()* +tsparser:parse() *tsparser:parse()* Whenever you need to access the current syntax tree, parse the buffer: > tstree = parser:parse() @@ -64,120 +64,119 @@ be very frequent. Rather a plugin that does any kind of analysis on a tree should use a timer to throttle too frequent updates. tsparser:set_included_regions({region_list}) *tsparser:set_included_regions()* - Changes the regions the parser should consider. This is used for - language injection. {region_list} should be of the form - (all zero-based): > - { - {node1, node2}, - ... - } + Changes the regions the parser should consider. This is used for language + injection. {region_list} should be of the form (all zero-based): > + { + {node1, node2}, + ... + } < - `node1` and `node2` are both considered part of the same region and - will be parsed together with the parser in the same context. + `node1` and `node2` are both considered part of the same region and will + be parsed together with the parser in the same context. -Tree methods *lua-treesitter-tree* +Tree methods *lua-treesitter-tree* -tstree:root() *tstree:root()* - Return the root node of this tree. +tstree:root() *tstree:root()* + Return the root node of this tree. -tstree:copy() *tstree:copy()* - Returns a copy of the `tstree`. +tstree:copy() *tstree:copy()* + Returns a copy of the `tstree`. -Node methods *lua-treesitter-node* +Node methods *lua-treesitter-node* -tsnode:parent() *tsnode:parent()* - Get the node's immediate parent. +tsnode:parent() *tsnode:parent()* + Get the node's immediate parent. -tsnode:next_sibling() *tsnode:next_sibling()* - Get the node's next sibling. +tsnode:next_sibling() *tsnode:next_sibling()* + Get the node's next sibling. -tsnode:prev_sibling() *tsnode:prev_sibling()* - Get the node's previous sibling. +tsnode:prev_sibling() *tsnode:prev_sibling()* + Get the node's previous sibling. tsnode:next_named_sibling() *tsnode:next_named_sibling()* - Get the node's next named sibling. + Get the node's next named sibling. -tsnode:prev_named_sibling() *tsnode:prev_named_sibling()* - Get the node's previous named sibling. +tsnode:prev_named_sibling() *tsnode:prev_named_sibling()* + Get the node's previous named sibling. -tsnode:iter_children() *tsnode:iter_children()* - Iterates over all the direct children of {tsnode}, regardless of - whether they are named or not. - Returns the child node plus the eventual field name corresponding to - this child node. +tsnode:iter_children() *tsnode:iter_children()* + Iterates over all the direct children of {tsnode}, regardless of whether + they are named or not. + Returns the child node plus the eventual field name corresponding to this + child node. -tsnode:field({name}) *tsnode:field()* - Returns a table of the nodes corresponding to the {name} field. +tsnode:field({name}) *tsnode:field()* + Returns a table of the nodes corresponding to the {name} field. -tsnode:child_count() *tsnode:child_count()* - Get the node's number of children. +tsnode:child_count() *tsnode:child_count()* + Get the node's number of children. -tsnode:child({index}) *tsnode:child()* - Get the node's child at the given {index}, where zero represents the - first child. +tsnode:child({index}) *tsnode:child()* + Get the node's child at the given {index}, where zero represents the first + child. -tsnode:named_child_count() *tsnode:named_child_count()* - Get the node's number of named children. +tsnode:named_child_count() *tsnode:named_child_count()* + Get the node's number of named children. -tsnode:named_child({index}) *tsnode:named_child()* - Get the node's named child at the given {index}, where zero represents - the first named child. +tsnode:named_child({index}) *tsnode:named_child()* + Get the node's named child at the given {index}, where zero represents the + first named child. -tsnode:start() *tsnode:start()* - Get the node's start position. Return three values: the row, column - and total byte count (all zero-based). +tsnode:start() *tsnode:start()* + Get the node's start position. Return three values: the row, column and + total byte count (all zero-based). -tsnode:end_() *tsnode:end_()* - Get the node's end position. Return three values: the row, column - and total byte count (all zero-based). +tsnode:end_() *tsnode:end_()* + Get the node's end position. Return three values: the row, column and + total byte count (all zero-based). -tsnode:range() *tsnode:range()* - Get the range of the node. Return four values: the row, column - of the start position, then the row, column of the end position. +tsnode:range() *tsnode:range()* + Get the range of the node. Return four values: the row, column of the + start position, then the row, column of the end position. -tsnode:type() *tsnode:type()* - Get the node's type as a string. +tsnode:type() *tsnode:type()* + Get the node's type as a string. -tsnode:symbol() *tsnode:symbol()* - Get the node's type as a numerical id. +tsnode:symbol() *tsnode:symbol()* + Get the node's type as a numerical id. -tsnode:named() *tsnode:named()* - Check if the node is named. Named nodes correspond to named rules in - the grammar, whereas anonymous nodes correspond to string literals - in the grammar. +tsnode:named() *tsnode:named()* + Check if the node is named. Named nodes correspond to named rules in the + grammar, whereas anonymous nodes correspond to string literals in the + grammar. -tsnode:missing() *tsnode:missing()* - Check if the node is missing. Missing nodes are inserted by the - parser in order to recover from certain kinds of syntax errors. +tsnode:missing() *tsnode:missing()* + Check if the node is missing. Missing nodes are inserted by the parser in + order to recover from certain kinds of syntax errors. -tsnode:has_error() *tsnode:has_error()* - Check if the node is a syntax error or contains any syntax errors. +tsnode:has_error() *tsnode:has_error()* + Check if the node is a syntax error or contains any syntax errors. -tsnode:sexpr() *tsnode:sexpr()* - Get an S-expression representing the node as a string. +tsnode:sexpr() *tsnode:sexpr()* + Get an S-expression representing the node as a string. -tsnode:id() *tsnode:id()* - Get an unique identifier for the node inside its own tree. +tsnode:id() *tsnode:id()* + Get an unique identifier for the node inside its own tree. - No guarantees are made about this identifier's internal - representation, except for being a primitive lua type with value - equality (so not a table). Presently it is a (non-printable) string. + No guarantees are made about this identifier's internal representation, + except for being a primitive lua type with value equality (so not a + table). Presently it is a (non-printable) string. - Note: the id is not guaranteed to be unique for nodes from different - trees. + Note: the id is not guaranteed to be unique for nodes from different + trees. - *tsnode:descendant_for_range()* + *tsnode:descendant_for_range()* tsnode:descendant_for_range({start_row}, {start_col}, {end_row}, {end_col}) - Get the smallest node within this node that spans the given range of - (row, column) positions + Get the smallest node within this node that spans the given range of (row, + column) positions - *tsnode:named_descendant_for_range()* + *tsnode:named_descendant_for_range()* tsnode:named_descendant_for_range({start_row}, {start_col}, {end_row}, {end_col}) - Get the smallest named node within this node that spans the given - range of (row, column) positions + Get the smallest named node within this node that spans the given range of + (row, column) positions -Query *lua-treesitter-query* +Query *lua-treesitter-query* Tree-sitter queries are supported, they are a way to do pattern-matching over a tree, using a simple to write lisp-like format. See @@ -195,105 +194,102 @@ and predicates. A `capture` allows you to associate names with a specific node in a pattern. A `predicate` adds arbitrary metadata and conditional data to a match. -Treesitter Query Predicates *lua-treesitter-predicates* +Treesitter Query Predicates *lua-treesitter-predicates* When writing queries for treesitter, one might use `predicates`, that is, special scheme nodes that are evaluated to verify things on a captured node for example, the |eq?| predicate : > - ((identifier) @foo (#eq? @foo "foo")) + ((identifier) @foo (#eq? @foo "foo")) This will only match identifier corresponding to the `"foo"` text. Here is a list of built-in predicates : - `eq?` *ts-predicate-eq?* - This predicate will check text correspondence between nodes or - strings: > - ((identifier) @foo (#eq? @foo "foo")) - ((node1) @left (node2) @right (#eq? @left @right)) + `eq?` *ts-predicate-eq?* + This predicate will check text correspondence between nodes or + strings: > + ((identifier) @foo (#eq? @foo "foo")) + ((node1) @left (node2) @right (#eq? @left @right)) < - `match?` *ts-predicate-match?* - `vim-match?` *ts-predicate-vim-match?* - This will match if the provided vim regex matches the text - corresponding to a node: > - ((identifier) @constant (#match? @constant "^[A-Z_]+$")) -< Note: the `^` and `$` anchors will respectively match the - start and end of the node's text. - - `lua-match?` *ts-predicate-lua-match?* - This will match the same way than |match?| but using lua - regexes. - - `contains?` *ts-predicate-contains?* - Will check if any of the following arguments appears in the - text corresponding to the node: > - ((identifier) @foo (#contains? @foo "foo")) - ((identifier) @foo-bar (#contains @foo-bar "foo" "bar")) + `match?` *ts-predicate-match?* + `vim-match?` *ts-predicate-vim-match?* + This will match if the provided vim regex matches the text + corresponding to a node: > + ((identifier) @constant (#match? @constant "^[A-Z_]+$")) +< Note: the `^` and `$` anchors will respectively match the start and + end of the node's text. + + `lua-match?` *ts-predicate-lua-match?* + This will match the same way than |match?| but using lua regexes. + + `contains?` *ts-predicate-contains?* + Will check if any of the following arguments appears in the text + corresponding to the node: > + ((identifier) @foo (#contains? @foo "foo")) + ((identifier) @foo-bar (#contains @foo-bar "foo" "bar")) < - `any-of?` *ts-predicate-any-of?* - Will check if the text is the same as any of the following - arguments: > - ((identifier) @foo (#any-of? @foo "foo" "bar")) + `any-of?` *ts-predicate-any-of?* + Will check if the text is the same as any of the following arguments: > + ((identifier) @foo (#any-of? @foo "foo" "bar")) < - This is the recommended way to check if the node matches one - of many keywords for example, as it has been optimized for - this. + This is the recommended way to check if the node matches one of many + keywords for example, as it has been optimized for this. < - *lua-treesitter-not-predicate* + *lua-treesitter-not-predicate* Each predicate has a `not-` prefixed predicate that is just the negation of the predicate. - *vim.treesitter.query.add_predicate()* + *vim.treesitter.query.add_predicate()* vim.treesitter.query.add_predicate({name}, {handler}) This adds a predicate with the name {name} to be used in queries. {handler} should be a function whose signature will be : > - handler(match, pattern, bufnr, predicate) + handler(match, pattern, bufnr, predicate) < - *vim.treesitter.query.list_predicates()* + *vim.treesitter.query.list_predicates()* vim.treesitter.query.list_predicates() This lists the currently available predicates to use in queries. -Treesitter Query Directive *lua-treesitter-directives* +Treesitter Query Directive *lua-treesitter-directives* Treesitter queries can also contain `directives`. Directives store metadata for a node or match and perform side effects. For example, the |set!| predicate sets metadata on the match or node : > - ((identifier) @foo (#set! "type" "parameter")) + ((identifier) @foo (#set! "type" "parameter")) Built-in directives: - `set!` *ts-directive-set!* - Sets key/value metadata for a specific match or capture. - Value is accessible as either `metadata[key]` (match - specific) or `metadata[capture_id][key]` (capture specific). + `set!` *ts-directive-set!* + Sets key/value metadata for a specific match or capture. Value is + accessible as either `metadata[key]` (match specific) or + `metadata[capture_id][key]` (capture specific). - Parameters: ~ - {capture_id} (optional) - {key} - {value} + Parameters: ~ + {capture_id} (optional) + {key} + {value} - Examples: > - ((identifier) @foo (#set! @foo "kind" "parameter")) - ((node1) @left (node2) @right (#set! "type" "pair")) + Examples: > + ((identifier) @foo (#set! @foo "kind" "parameter")) + ((node1) @left (node2) @right (#set! "type" "pair")) < - `offset!` *ts-directive-offset!* - Takes the range of the captured node and applies an offset. - This will generate a new range object for the captured node - as `metadata[capture_id].range`. - - Parameters: ~ - {capture_id} - {start_row} - {start_col} - {end_row} - {end_col} - - Example: > - ((identifier) @constant (#offset! @constant 0 1 0 -1)) + `offset!` *ts-directive-offset!* + Takes the range of the captured node and applies an offset. This will + generate a new range object for the captured node as + `metadata[capture_id].range`. + + Parameters: ~ + {capture_id} + {start_row} + {start_col} + {end_row} + {end_col} + + Example: > + ((identifier) @constant (#offset! @constant 0 1 0 -1)) < -Treesitter syntax highlighting (WIP) *lua-treesitter-highlight* +Treesitter syntax highlighting (WIP) *lua-treesitter-highlight* NOTE: This is a partially implemented feature, and not usable as a default solution yet. What is documented here is a temporary interface intended @@ -327,20 +323,38 @@ for a buffer with this code: > local query2 = [[ ... ]] highlighter:set_query(query2) -As mentioned above the supported predicate is currently only `eq?`. `match?` -predicates behave like matching always fails. As an addition a capture which -begin with an upper-case letter like `@WarningMsg` will map directly to this -highlight group, if defined. Also if the predicate begins with upper-case and -contains a dot only the part before the first will be interpreted as the -highlight group. As an example, this warns of a binary expression with two + + *lua-treesitter-highlight-groups* +The capture names, with `@` included, are directly usable as highlight groups. +A fallback system is implemented, so that more specific groups fallback to +more generic ones. For instance, in a language that has separate doc +comments, `@comment.doc` could be used. If this group is not defined, the +highlighting for an ordinary `@comment` is used. This way, existing color +schemes already work out of the box, but it is possible to add +more specific variants for queries that make them available. + +As an additional rule, captures highlights can always be specialized by +language, by appending the language name after an additional dot. For +instance, to highlight comments differently per language: > + + hi @comment.c guifg=Blue + hi @comment.lua @guifg=DarkBlue + hi link @comment.doc.java String +< +It is possible to use custom highlight groups. As an example, if we +define the `@warning` group: > + + hi link @warning WarningMsg +< +the following query warns of a binary expression with two identical identifiers, highlighting both as |hl-WarningMsg|: > - ((binary_expression left: (identifier) @WarningMsg.left right: (identifier) @WarningMsg.right) - (eq? @WarningMsg.left @WarningMsg.right)) + ((binary_expression left: (identifier) @warning.left right: (identifier) @warning.right) + (eq? @warning.left @warning.right)) < Treesitter Highlighting Priority *lua-treesitter-highlight-priority* -Tree-sitter uses |nvim_buf_set_extmark()| to set highlights with a default +Tree-sitter uses |nvim_buf_set_extmark()| to set highlights with a default priority of 100. This enables plugins to set a highlighting priority lower or higher than tree-sitter. It is also possible to change the priority of an individual query pattern manually by setting its `"priority"` metadata @@ -356,154 +370,204 @@ attribute: > ============================================================================== Lua module: vim.treesitter *lua-treesitter-core* + *get_captures_at_position()* +get_captures_at_position({bufnr}, {row}, {col}) + Gets a list of captures for a given cursor position + + Parameters: ~ + {bufnr} (number) The buffer number + {row} (number) The position row + {col} (number) The position column + + Return: ~ + (table) A table of captures + +get_node_range({node_or_range}) *get_node_range()* + Get the node's range or unpack a range table + + Parameters: ~ + {node_or_range} (table) + + Return: ~ + start_row, start_col, end_row, end_col + get_parser({bufnr}, {lang}, {opts}) *get_parser()* - Gets the parser for this bufnr / ft combination. + Gets the parser for this bufnr / ft combination. - If needed this will create the parser. Unconditionally attach - the provided callback + If needed this will create the parser. Unconditionally attach the provided + callback - Parameters: ~ - {bufnr} The buffer the parser should be tied to - {lang} The filetype of this parser - {opts} Options object to pass to the created language - tree + Parameters: ~ + {bufnr} The buffer the parser should be tied to + {lang} The filetype of this parser + {opts} Options object to pass to the created language tree - Return: ~ - The parser + Return: ~ + The parser get_string_parser({str}, {lang}, {opts}) *get_string_parser()* - Gets a string parser + Gets a string parser - Parameters: ~ - {str} The string to parse - {lang} The language of this string - {opts} Options to pass to the created language tree + Parameters: ~ + {str} The string to parse + {lang} The language of this string + {opts} Options to pass to the created language tree + +is_ancestor({dest}, {source}) *is_ancestor()* + Determines whether a node is the ancestor of another + + Parameters: ~ + {dest} (table) the possible ancestor + {source} (table) the possible descendant node + + Return: ~ + (boolean) True if dest is an ancestor of source + +is_in_node_range({node}, {line}, {col}) *is_in_node_range()* + Determines whether (line, col) position is in node range + + Parameters: ~ + {node} Node defining the range + {line} A line (0-based) + {col} A column (0-based) + +node_contains({node}, {range}) *node_contains()* + Determines if a node contains a range + + Parameters: ~ + {node} (table) The node + {range} (table) The range + + Return: ~ + (boolean) True if the node contains the range ============================================================================== Lua module: vim.treesitter.language *treesitter-language* inspect_language({lang}) *inspect_language()* - Inspects the provided language. + Inspects the provided language. - Inspecting provides some useful information on the language - like node names, ... + Inspecting provides some useful information on the language like node + names, ... - Parameters: ~ - {lang} The language. + Parameters: ~ + {lang} The language. -require_language({lang}, {path}, {silent}) *require_language()* - Asserts that the provided language is installed, and - optionally provide a path for the parser + *require_language()* +require_language({lang}, {path}, {silent}, {symbol_name}) + Asserts that the provided language is installed, and optionally provide a + path for the parser - Parsers are searched in the `parser` runtime directory. + Parsers are searched in the `parser` runtime directory. - Parameters: ~ - {lang} The language the parser should parse - {path} Optional path the parser is located at - {silent} Don't throw an error if language not found + Parameters: ~ + {lang} (string) The language the parser should parse + {path} (string|nil) Optional path the parser is located at + {silent} (boolean|nil) Don't throw an error if language not + found + {symbol_name} (string|nil) Internal symbol name for the language to + load ============================================================================== Lua module: vim.treesitter.query *treesitter-query* add_directive({name}, {handler}, {force}) *add_directive()* - Adds a new directive to be used in queries + Adds a new directive to be used in queries - Handlers can set match level data by setting directly on the - metadata object `metadata.key = value`, additionally, handlers - can set node level data by using the capture id on the - metadata table `metadata[capture_id].key = value` + Handlers can set match level data by setting directly on the metadata + object `metadata.key = value`, additionally, handlers can set node level + data by using the capture id on the metadata table + `metadata[capture_id].key = value` - Parameters: ~ - {name} the name of the directive, without leading # - {handler} the handler function to be used signature will - be (match, pattern, bufnr, predicate, metadata) + Parameters: ~ + {name} the name of the directive, without leading # + {handler} the handler function to be used signature will be (match, + pattern, bufnr, predicate, metadata) add_predicate({name}, {handler}, {force}) *add_predicate()* - Adds a new predicate to be used in queries + Adds a new predicate to be used in queries - Parameters: ~ - {name} the name of the predicate, without leading # - {handler} the handler function to be used signature will - be (match, pattern, bufnr, predicate) + Parameters: ~ + {name} the name of the predicate, without leading # + {handler} the handler function to be used signature will be (match, + pattern, bufnr, predicate) -get_node_text({node}, {source}) *get_node_text()* - Gets the text corresponding to a given node +get_node_text({node}, {source}, {opts}) *get_node_text()* + Gets the text corresponding to a given node - Parameters: ~ - {node} the node - {source} The buffer or string from which the node is - extracted + Parameters: ~ + {node} (table) The node + {source} (table) The buffer or string from which the node is + extracted + {opts} (table) Optional parameters. + • concat: (boolean default true) Concatenate result in a + string get_query({lang}, {query_name}) *get_query()* - Returns the runtime query {query_name} for {lang}. + Returns the runtime query {query_name} for {lang}. - Parameters: ~ - {lang} The language to use for the query - {query_name} The name of the query (i.e. "highlights") + Parameters: ~ + {lang} The language to use for the query + {query_name} The name of the query (i.e. "highlights") - Return: ~ - The corresponding query, parsed. + Return: ~ + The corresponding query, parsed. *get_query_files()* get_query_files({lang}, {query_name}, {is_included}) - Gets the list of files used to make up a query + Gets the list of files used to make up a query - Parameters: ~ - {lang} The language - {query_name} The name of the query to load - {is_included} Internal parameter, most of the time left - as `nil` + Parameters: ~ + {lang} The language + {query_name} The name of the query to load + {is_included} Internal parameter, most of the time left as `nil` list_directives() *list_directives()* - Lists the currently available directives to use in queries. + Lists the currently available directives to use in queries. - Return: ~ - The list of supported directives. + Return: ~ + The list of supported directives. list_predicates() *list_predicates()* - Return: ~ - The list of supported predicates. + Return: ~ + The list of supported predicates. parse_query({lang}, {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). + Parse {query} as a string. (If the query is in a file, the caller should + read the contents into a string before calling). - Returns a `Query` (see |lua-treesitter-query|) object which - can be used to search nodes in the syntax tree for the - patterns defined in {query} using `iter_*` methods below. + Returns a `Query` (see |lua-treesitter-query|) object which can be used to + search nodes in the syntax tree for the patterns defined in {query} using + `iter_*` methods below. - Exposes `info` and `captures` with additional context about {query}. - • `captures` contains the list of unique capture names defined - in {query}. -`info.captures` also points to `captures`. - • `info.patterns` contains information about predicates. + Exposes `info` and `captures` with additional context about {query}. + • `captures` contains the list of unique capture names defined in {query}. + -`info.captures` also points to `captures`. + • `info.patterns` contains information about predicates. - Parameters: ~ - {lang} (string) The language - {query} (string) A string containing the query (s-expr - syntax) + Parameters: ~ + {lang} (string) The language + {query} (string) A string containing the query (s-expr syntax) - Return: ~ - The query + Return: ~ + The query *Query:iter_captures()* Query:iter_captures({self}, {node}, {source}, {start}, {stop}) - Iterate over all captures from all matches inside {node} - - {source} is needed if the query contains predicates, then the - caller must ensure to use a freshly parsed tree consistent - with the current text of the buffer (if relevant). {start_row} - and {end_row} can be used to limit matches inside a row range - (this is typically used with root node as the node, i e to get - syntax highlight matches in the current viewport). When - omitted the start and end row values are used from the given - node. - - 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: + Iterate over all captures from all matches inside {node} + + {source} is needed if the query contains predicates, then the caller must + ensure to use a freshly parsed tree consistent with the current text of + the buffer (if relevant). {start_row} and {end_row} can be used to limit + matches inside a row range (this is typically used with root node as the + node, i e to get syntax highlight matches in the current viewport). When + omitted the start and end row values are used from the given node. + + 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: > for id, node, metadata in query:iter_captures(tree:root(), bufnr, first, last) do @@ -515,29 +579,28 @@ Query:iter_captures({self}, {node}, {source}, {start}, {stop}) end < - Parameters: ~ - {node} The node under which the search will occur - {source} The source buffer or string to extract text from - {start} The starting line of the search - {stop} The stopping line of the search (end-exclusive) - {self} + Parameters: ~ + {node} The node under which the search will occur + {source} The source buffer or string to extract text from + {start} The starting line of the search + {stop} The stopping line of the search (end-exclusive) + {self} - Return: ~ - The matching capture id - The captured node + Return: ~ + The matching capture id + The captured node *Query:iter_matches()* Query:iter_matches({self}, {node}, {source}, {start}, {stop}) - Iterates the matches of self on a given range. - - Iterate over all matches within a node. The arguments are the - same as for |query:iter_captures()| but the iterated values - are different: an (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 an example - iterating over all captures in every match: + Iterates the matches of self on a given range. + + Iterate over all matches within a node. The arguments are the same as for + |query:iter_captures()| but the iterated values are different: an + (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 an example + iterating over all captures in every match: > for pattern, match, metadata in cquery:iter_matches(tree:root(), bufnr, first, last) do @@ -552,240 +615,242 @@ Query:iter_matches({self}, {node}, {source}, {start}, {stop}) end < - Parameters: ~ - {node} The node under which the search will occur - {source} The source buffer or string to search - {start} The starting line of the search - {stop} The stopping line of the search (end-exclusive) - {self} + Parameters: ~ + {node} The node under which the search will occur + {source} The source buffer or string to search + {start} The starting line of the search + {stop} The stopping line of the search (end-exclusive) + {self} - Return: ~ - The matching pattern id - The matching match + Return: ~ + The matching pattern id + The matching match set_query({lang}, {query_name}, {text}) *set_query()* - Sets the runtime query {query_name} for {lang} + Sets the runtime query {query_name} for {lang} - This allows users to override any runtime files and/or - configuration set by plugins. + This allows users to override any runtime files and/or configuration set + by plugins. - Parameters: ~ - {lang} string: The language to use for the query - {query_name} string: The name of the query (i.e. - "highlights") - {text} string: The query text (unparsed). + Parameters: ~ + {lang} string: The language to use for the query + {query_name} string: The name of the query (i.e. "highlights") + {text} string: The query text (unparsed). ============================================================================== Lua module: vim.treesitter.highlighter *treesitter-highlighter* new({tree}, {opts}) *highlighter.new()* - Creates a new highlighter using + Creates a new highlighter using - Parameters: ~ - {tree} The language tree to use for highlighting - {opts} Table used to configure the highlighter - • queries: Table to overwrite queries used by the - highlighter + Parameters: ~ + {tree} The language tree to use for highlighting + {opts} Table used to configure the highlighter + • queries: Table to overwrite queries used by the highlighter TSHighlighter:destroy({self}) *TSHighlighter:destroy()* - Removes all internal references to the highlighter + Removes all internal references to the highlighter - Parameters: ~ - {self} + Parameters: ~ + {self} TSHighlighter:get_query({self}, {lang}) *TSHighlighter:get_query()* - Gets the query used for + Gets the query used for - Parameters: ~ - {lang} A language used by the highlighter. - {self} + Parameters: ~ + {lang} A language used by the highlighter. + {self} ============================================================================== Lua module: vim.treesitter.languagetree *treesitter-languagetree* LanguageTree:add_child({self}, {lang}) *LanguageTree:add_child()* - Adds a child language to this tree. + Adds a child language to this tree. - If the language already exists as a child, it will first be - removed. + If the language already exists as a child, it will first be removed. - Parameters: ~ - {lang} The language to add. - {self} + Parameters: ~ + {lang} The language to add. + {self} LanguageTree:children({self}) *LanguageTree:children()* - Returns a map of language to child tree. + Returns a map of language to child tree. - Parameters: ~ - {self} + Parameters: ~ + {self} LanguageTree:contains({self}, {range}) *LanguageTree:contains()* - Determines whether {range} is contained in this language tree + Determines whether {range} is contained in this language tree - Parameters: ~ - {range} A range, that is a `{ start_line, start_col, - end_line, end_col }` table. - {self} + Parameters: ~ + {range} A range, that is a `{ start_line, start_col, end_line, + end_col }` table. + {self} LanguageTree:destroy({self}) *LanguageTree:destroy()* - Destroys this language tree and all its children. + Destroys this language tree and all its children. - Any cleanup logic should be performed here. + Any cleanup logic should be performed here. - Note: This DOES NOT remove this tree from a parent. Instead, `remove_child` must be called on the parent to remove it. + Note: This DOES NOT remove this tree from a parent. Instead, `remove_child` must be called on the parent to remove it. - Parameters: ~ - {self} + Parameters: ~ + {self} *LanguageTree:for_each_child()* LanguageTree:for_each_child({self}, {fn}, {include_self}) - Invokes the callback for each LanguageTree and it's children - recursively + Invokes the callback for each LanguageTree and it's children recursively - Parameters: ~ - {fn} The function to invoke. This is invoked - with arguments (tree: LanguageTree, lang: - string) - {include_self} Whether to include the invoking tree in - the results. - {self} + Parameters: ~ + {fn} The function to invoke. This is invoked with arguments + (tree: LanguageTree, lang: string) + {include_self} Whether to include the invoking tree in the results. + {self} LanguageTree:for_each_tree({self}, {fn}) *LanguageTree:for_each_tree()* - Invokes the callback for each treesitter trees recursively. + Invokes the callback for each treesitter trees recursively. - Note, this includes the invoking language tree's trees as - well. + Note, this includes the invoking language tree's trees as well. - Parameters: ~ - {fn} The callback to invoke. The callback is invoked - with arguments (tree: TSTree, languageTree: - LanguageTree) - {self} + Parameters: ~ + {fn} The callback to invoke. The callback is invoked with arguments + (tree: TSTree, languageTree: LanguageTree) + {self} LanguageTree:included_regions({self}) *LanguageTree:included_regions()* - Gets the set of included regions + Gets the set of included regions - Parameters: ~ - {self} + Parameters: ~ + {self} LanguageTree:invalidate({self}, {reload}) *LanguageTree:invalidate()* - Invalidates this parser and all its children + Invalidates this parser and all its children - Parameters: ~ - {self} + Parameters: ~ + {self} LanguageTree:is_valid({self}) *LanguageTree:is_valid()* - Determines whether this tree is valid. If the tree is invalid, - call `parse()` . This will return the updated tree. + Determines whether this tree is valid. If the tree is invalid, call `parse()` . This will return the updated tree. - Parameters: ~ - {self} + Parameters: ~ + {self} LanguageTree:lang({self}) *LanguageTree:lang()* - Gets the language of this tree node. + Gets the language of this tree node. - Parameters: ~ - {self} + Parameters: ~ + {self} *LanguageTree:language_for_range()* LanguageTree:language_for_range({self}, {range}) - Gets the appropriate language that contains {range} + Gets the appropriate language that contains {range} + + Parameters: ~ + {range} A text range, see |LanguageTree:contains| + {self} - Parameters: ~ - {range} A text range, see |LanguageTree:contains| - {self} + *LanguageTree:named_node_for_range()* +LanguageTree:named_node_for_range({self}, {range}, {opts}) + Gets the smallest named node that contains {range} + + Parameters: ~ + {range} (table) A text range + {opts} (table) Options table + {opts.ignore_injections} (boolean) (default true) Ignore injected + languages. + {self} LanguageTree:parse({self}) *LanguageTree:parse()* - Parses all defined regions using a treesitter parser for the - language this tree represents. This will run the injection - query for this language to determine if any child languages - should be created. + Parses all defined regions using a treesitter parser for the language this + tree represents. This will run the injection query for this language to + determine if any child languages should be created. - Parameters: ~ - {self} + Parameters: ~ + {self} LanguageTree:register_cbs({self}, {cbs}) *LanguageTree:register_cbs()* - Registers callbacks for the parser. - - Parameters: ~ - {cbs} (table) An |nvim_buf_attach()|-like table argument - with the following keys : - • `on_bytes` : see |nvim_buf_attach()|, but this will be - called after the parsers callback. - • `on_changedtree` : a callback that will be - called every time the tree has syntactical - changes. It will only be passed one argument, - which is a table of the ranges (as node ranges) - that changed. - • `on_child_added` : emitted when a child is added - to the tree. - • `on_child_removed` : emitted when a child is - removed from the tree. - {self} + Registers callbacks for the parser. + + Parameters: ~ + {cbs} (table) An |nvim_buf_attach()|-like table argument with the + following keys : + • `on_bytes` : see |nvim_buf_attach()|, but this will be called after the parsers callback. + • `on_changedtree` : a callback that will be called every time + the tree has syntactical changes. It will only be passed one + argument, which is a table of the ranges (as node ranges) + that changed. + • `on_child_added` : emitted when a child is added to the + tree. + • `on_child_removed` : emitted when a child is removed from + the tree. + {self} LanguageTree:remove_child({self}, {lang}) *LanguageTree:remove_child()* - Removes a child language from this tree. + Removes a child language from this tree. - Parameters: ~ - {lang} The language to remove. - {self} + Parameters: ~ + {lang} The language to remove. + {self} *LanguageTree:set_included_regions()* LanguageTree:set_included_regions({self}, {regions}) - Sets the included regions that should be parsed by this - parser. A region is a set of nodes and/or ranges that will be - parsed in the same context. + Sets the included regions that should be parsed by this parser. A region + is a set of nodes and/or ranges that will be parsed in the same context. - For example, `{ { node1 }, { node2} }` is two separate - regions. This will be parsed by the parser in two different - contexts... thus resulting in two separate trees. + For example, `{ { node1 }, { node2} }` is two separate regions. This will + be parsed by the parser in two different contexts... thus resulting in two + separate trees. - `{ { node1, node2 } }` is a single region consisting of two - nodes. This will be parsed by the parser in a single - context... thus resulting in a single tree. + `{ { node1, node2 } }` is a single region consisting of two nodes. This + will be parsed by the parser in a single context... thus resulting in a + single tree. - This allows for embedded languages to be parsed together - across different nodes, which is useful for templating - languages like ERB and EJS. + This allows for embedded languages to be parsed together across different + nodes, which is useful for templating languages like ERB and EJS. - Note, this call invalidates the tree and requires it to be - parsed again. + Note, this call invalidates the tree and requires it to be parsed again. - Parameters: ~ - {regions} (table) list of regions this tree should manage - and parse. - {self} + Parameters: ~ + {regions} (table) list of regions this tree should manage and parse. + {self} LanguageTree:source({self}) *LanguageTree:source()* - Returns the source content of the language tree (bufnr or - string). + Returns the source content of the language tree (bufnr or string). + + Parameters: ~ + {self} + + *LanguageTree:tree_for_range()* +LanguageTree:tree_for_range({self}, {range}, {opts}) + Gets the tree that contains {range} - Parameters: ~ - {self} + Parameters: ~ + {range} (table) A text range + {opts} (table) Options table + {opts.ignore_injections} (boolean) (default true) Ignore injected + languages. + {self} LanguageTree:trees({self}) *LanguageTree:trees()* - Returns all trees this language tree contains. Does not - include child languages. + Returns all trees this language tree contains. Does not include child + languages. - Parameters: ~ - {self} + Parameters: ~ + {self} new({source}, {lang}, {opts}) *languagetree.new()* - Represents a single treesitter parser for a language. The - language can contain child languages with in its range, hence - the tree. - - Parameters: ~ - {source} Can be a bufnr or a string of text to - parse - {lang} The language this tree represents - {opts} Options table - {opts.injections} A table of language to injection query - strings. This is useful for overriding - the built-in runtime file searching for - the injection language query per - language. - - vim:tw=78:ts=8:ft=help:norl: + Represents a single treesitter parser for a language. The language can + contain child languages with in its range, hence the tree. + + Parameters: ~ + {source} Can be a bufnr or a string of text to parse + {lang} The language this tree represents + {opts} Options table + {opts.injections} A table of language to injection query strings. + This is useful for overriding the built-in runtime + file searching for the injection language query per + language. + + vim:tw=78:ts=8:sw=4:sts=4:et:ft=help:norl: diff --git a/runtime/doc/usr_41.txt b/runtime/doc/usr_41.txt index 008b9b4e58..76c2f8454f 100644 --- a/runtime/doc/usr_41.txt +++ b/runtime/doc/usr_41.txt @@ -202,9 +202,9 @@ message when it doesn't, append !: > :unlet! s:count -When a script finishes, the local variables used there will not be -automatically freed. The next time the script executes, it can still use the -old value. Example: > +When a script has been processed to the end, the local variables declared +there will not be deleted. Functions defined in the script can use them. +Example: :if !exists("s:call_count") : let s:call_count = 0 @@ -606,6 +606,7 @@ String manipulation: *string-functions* strtrans() translate a string to make it printable tolower() turn a string to lowercase toupper() turn a string to uppercase + charclass() class of a character match() position where a pattern matches in a string matchend() position where a pattern match ends in a string matchfuzzy() fuzzy matches a string in a list of strings @@ -619,6 +620,7 @@ String manipulation: *string-functions* strchars() length of a string in characters strwidth() size of string when displayed strdisplaywidth() size of string when displayed, deals with tabs + setcellwidths() set 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 @@ -742,6 +744,7 @@ Cursor and mark position: *cursor-functions* *mark-functions* cursor() position the cursor at a line/column screencol() get screen column of the cursor screenrow() get screen row of the cursor + screenpos() screen row and col of a text character getcurpos() get position of the cursor getpos() get position of cursor, mark, etc. setpos() set position of cursor, mark, etc. @@ -851,9 +854,9 @@ Buffers, windows and the argument list: win_gotoid() go to window with ID win_id2tabwin() get tab and window nr from window ID win_id2win() get window nr from window ID - win_splitmove() move window to a split of another window win_move_separator() move window vertical separator win_move_statusline() move window status line + win_splitmove() move window to a split of another window getbufinfo() get a list with buffer information gettabinfo() get a list with tab page information getwininfo() get a list with window information @@ -869,6 +872,7 @@ Command line: *command-line-functions* getcmdpos() get position of the cursor in the command line getcmdscreenpos() get screen position of the cursor in the command line + setcmdline() set the current command line setcmdpos() set position of the cursor in the command line getcmdtype() return the current command-line type getcmdwintype() return the current command-line window type diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index 27c953a460..0011cd9821 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -207,9 +207,8 @@ Commands: |:checkhealth| |:drop| is always available |:Man| is available by default, with many improvements such as completion - |:sign-define| accepts a `numhl` argument, to highlight the line number |:match| can be invoked before highlight group is defined - |:source| works with Lua and anonymous (no file) scripts + |:source| works with Lua User commands can support |:command-preview| to show results as you type Events: @@ -271,7 +270,6 @@ Options: 'signcolumn' supports up to 9 dynamic/fixed columns 'statusline' supports unlimited alignment sections 'tabline' %@Func@foo%X can call any function on mouse-click - 'wildoptions' "pum" flag to use popupmenu for wildmode completion 'winblend' pseudo-transparency in floating windows |api-floatwin| 'winhighlight' window-local highlights @@ -373,6 +371,10 @@ Lua interface (|lua.txt|): Commands: |:doautocmd| does not warn about "No matching autocommands". + |:wincmd| accepts a count. + +Command line completion: + The meanings of arrow keys do not change depending on 'wildoptions'. Functions: |input()| and |inputdialog()| support for each other’s features (return on @@ -388,6 +390,9 @@ Highlight groups: using |n| or |N| |hl-CursorLine| is low-priority unless foreground color is set |hl-VertSplit| superseded by |hl-WinSeparator| + Highlight groups names are allowed to contain the characters `.` and `@`. + It is an error to define a highlight group with a name that doesn't match + the regexp `[a-zA-Z0-9_.@]*` (see |group-name|). Macro/|recording| behavior Replay of a macro recorded during :lmap produces the same actions as when it diff --git a/runtime/doc/windows.txt b/runtime/doc/windows.txt index 8062b9e28f..2627068a14 100644 --- a/runtime/doc/windows.txt +++ b/runtime/doc/windows.txt @@ -163,6 +163,8 @@ CTRL-W v *CTRL-W_v* 3. 'eadirection' isn't "ver", and 4. one of the other windows is wider than the current or new window. + If N was given make the new window N columns wide, if + possible. Note: In other places CTRL-Q does the same as CTRL-V, but here it doesn't! @@ -372,7 +374,7 @@ CTRL-W o *CTRL-W_o* *E445* CTRL-W CTRL-O *CTRL-W_CTRL-O* *:on* *:only* Make the current window the only one on the screen. All other windows are closed. For {count} see the `:quit` command - above |:count_quit|. + above |:count_quit|. When the 'hidden' option is set, all buffers in closed windows become hidden. @@ -442,6 +444,7 @@ position is set to keep the same Visual area selected. These commands can also be executed with ":wincmd": :[count]winc[md] {arg} +:winc[md] [count] {arg} Like executing CTRL-W [count] {arg}. Example: > :wincmd j < Moves to the window below the current one. diff --git a/runtime/filetype.vim b/runtime/filetype.vim index fbb4b9f6aa..98d9df8b5c 100644 --- a/runtime/filetype.vim +++ b/runtime/filetype.vim @@ -173,6 +173,9 @@ 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 @@ -694,7 +697,10 @@ au BufNewFile,BufRead *.mo,*.gdmo setf gdmo au BufNewFile,BufRead *.gd setf gdscript " Godot resource -au BufRead,BufNewFile *.tscn,*.tres setf gdresource +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 @@ -1161,6 +1167,7 @@ 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 @@ -1532,6 +1539,9 @@ 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 @@ -2088,6 +2098,11 @@ au BufNewFile,BufRead */.config/upstart/*.override setf upstart " Vala au BufNewFile,BufRead *.vala setf vala +" 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 diff --git a/runtime/ftplugin/abaqus.vim b/runtime/ftplugin/abaqus.vim index 5ce565ef3f..3faeff621a 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 <osterwischc@asme.org> -" Last Change: 2022 May 09 +" Maintainer: Carl Osterwisch <costerwi@gmail.com> +" Last Change: 2022 Aug 03 " Only do this when not done yet for this buffer if exists("b:did_ftplugin") | finish | endif @@ -46,7 +46,7 @@ if has("folding") endif " Set the file browse filter (currently only supported under Win32 gui) -if has("gui_win32") && !exists("b:browsefilter") +if (has("gui_win32") || has("gui_gtk")) && !exists("b:browsefilter") let b:browsefilter = "Abaqus Input Files (*.inp *.inc)\t*.inp;*.inc\n" . \ "Abaqus Results (*.dat)\t*.dat\n" . \ "Abaqus Messages (*.pre *.msg *.sta)\t*.pre;*.msg;*.sta\n" . @@ -57,7 +57,7 @@ endif " Define patterns for the matchit plugin if exists("loaded_matchit") && !exists("b:match_words") let b:match_ignorecase = 1 - let b:match_words = + let b:match_words = \ '\*part:\*end\s*part,' . \ '\*assembly:\*end\s*assembly,' . \ '\*instance:\*end\s*instance,' . @@ -65,25 +65,27 @@ if exists("loaded_matchit") && !exists("b:match_words") let b:undo_ftplugin .= "|unlet! b:match_ignorecase b:match_words" endif -" Define keys used to move [count] keywords backward or forward. -noremap <silent><buffer> [[ ?^\*\a<CR>:nohlsearch<CR> -noremap <silent><buffer> ]] /^\*\a<CR>:nohlsearch<CR> - -" Define key 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 -endfunction - -let b:undo_ftplugin .= "|unmap <buffer> [[|unmap <buffer> ]]" - \ . "|unmap <buffer> <LocalLeader><LocalLeader>" +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> + + " Define key 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 + endfunction + + let b:undo_ftplugin .= "|unmap <buffer> [[|unmap <buffer> ]]" + \ . "|unmap <buffer> <LocalLeader><LocalLeader>" +endif " Undo must be done in nocompatible mode for <LocalLeader>. let b:undo_ftplugin = "let b:cpo_save = &cpoptions|" diff --git a/runtime/ftplugin/elixir.vim b/runtime/ftplugin/elixir.vim index 3b38051d08..c423c2acb7 100644 --- a/runtime/ftplugin/elixir.vim +++ b/runtime/ftplugin/elixir.vim @@ -1,11 +1,29 @@ " Elixir filetype plugin " Language: Elixir " Maintainer: Mitchell Hanberg <vimNOSPAM@mitchellhanberg.com> -" Last Change: 2022 Apr 20 +" Last Change: 2022 August 10 if exists("b:did_ftplugin") finish endif let b:did_ftplugin = 1 +let s:save_cpo = &cpo +set cpo&vim + +" Matchit support +if exists('loaded_matchit') && !exists('b:match_words') + let b:match_ignorecase = 0 + + let b:match_words = '\:\@<!\<\%(do\|fn\)\:\@!\>' . + \ ':' . + \ '\<\%(else\|catch\|after\|rescue\)\:\@!\>' . + \ ':' . + \ '\:\@<!\<end\>' . + \ ',{:},\[:\],(:)' +endif + setlocal commentstring=#\ %s + +let &cpo = s:save_cpo +unlet s:save_cpo diff --git a/runtime/ftplugin/j.vim b/runtime/ftplugin/j.vim index 3cd0cb8e2b..ae235abba8 100644 --- a/runtime/ftplugin/j.vim +++ b/runtime/ftplugin/j.vim @@ -2,7 +2,7 @@ " Language: J " Maintainer: David Bürgin <dbuergin@gluet.ch> " URL: https://gitlab.com/glts/vim-j -" Last Change: 2015-10-27 +" Last Change: 2022-08-06 if exists('b:did_ftplugin') finish @@ -29,41 +29,43 @@ let b:undo_ftplugin = 'setlocal suffixesadd< includeexpr< include< path< matchpa " Section movement with ]] ][ [[ []. The start/end patterns below are amended " inside the function in order to avoid matching on the current cursor line. -let s:sectionstart = '\%(\s*Note\|.\{-}\<\%([0-4]\|13\|noun\|adverb\|conjunction\|verb\|monad\|dyad\)\s\+\%(:\s*0\|def\s\+0\|define\)\)\>.*' -let s:sectionend = '\s*)\s*' +if !exists('no_plugin_maps') && !exists('no_j_maps') + let s:sectionstart = '\%(\s*Note\|.\{-}\<\%([0-4]\|13\|noun\|adverb\|conjunction\|verb\|monad\|dyad\)\s\+\%(:\s*0\|def\s\+0\|define\)\)\>.*' + let s:sectionend = '\s*)\s*' -function! s:SearchSection(end, backwards, visualmode) abort - if a:visualmode !=# '' - normal! gv - endif - let l:flags = a:backwards ? 'bsW' : 'sW' - if a:end - call search('^' . s:sectionend . (a:backwards ? '\n\_.\{-}\%#' : '$'), l:flags) - else - call search('^' . s:sectionstart . (a:backwards ? '\n\_.\{-}\%#' : '$'), l:flags) - endif -endfunction + function! s:SearchSection(end, backwards, visualmode) abort + if a:visualmode !=# '' + normal! gv + endif + let l:flags = a:backwards ? 'bsW' : 'sW' + if a:end + call search('^' . s:sectionend . (a:backwards ? '\n\_.\{-}\%#' : '$'), l:flags) + else + call search('^' . s:sectionstart . (a:backwards ? '\n\_.\{-}\%#' : '$'), l:flags) + endif + endfunction -noremap <buffer> <silent> ]] :<C-U>call <SID>SearchSection(0, 0, '')<CR> -xnoremap <buffer> <silent> ]] :<C-U>call <SID>SearchSection(0, 0, visualmode())<CR> -sunmap <buffer> ]] -noremap <buffer> <silent> ][ :<C-U>call <SID>SearchSection(1, 0, '')<CR> -xnoremap <buffer> <silent> ][ :<C-U>call <SID>SearchSection(1, 0, visualmode())<CR> -sunmap <buffer> ][ -noremap <buffer> <silent> [[ :<C-U>call <SID>SearchSection(0, 1, '')<CR> -xnoremap <buffer> <silent> [[ :<C-U>call <SID>SearchSection(0, 1, visualmode())<CR> -sunmap <buffer> [[ -noremap <buffer> <silent> [] :<C-U>call <SID>SearchSection(1, 1, '')<CR> -xnoremap <buffer> <silent> [] :<C-U>call <SID>SearchSection(1, 1, visualmode())<CR> -sunmap <buffer> [] + noremap <buffer> <silent> ]] :<C-U>call <SID>SearchSection(0, 0, '')<CR> + xnoremap <buffer> <silent> ]] :<C-U>call <SID>SearchSection(0, 0, visualmode())<CR> + sunmap <buffer> ]] + noremap <buffer> <silent> ][ :<C-U>call <SID>SearchSection(1, 0, '')<CR> + xnoremap <buffer> <silent> ][ :<C-U>call <SID>SearchSection(1, 0, visualmode())<CR> + sunmap <buffer> ][ + noremap <buffer> <silent> [[ :<C-U>call <SID>SearchSection(0, 1, '')<CR> + xnoremap <buffer> <silent> [[ :<C-U>call <SID>SearchSection(0, 1, visualmode())<CR> + sunmap <buffer> [[ + noremap <buffer> <silent> [] :<C-U>call <SID>SearchSection(1, 1, '')<CR> + xnoremap <buffer> <silent> [] :<C-U>call <SID>SearchSection(1, 1, visualmode())<CR> + sunmap <buffer> [] -let b:undo_ftplugin .= ' | silent! execute "unmap <buffer> ]]"' - \ . ' | silent! execute "unmap <buffer> ]["' - \ . ' | silent! execute "unmap <buffer> [["' - \ . ' | silent! execute "unmap <buffer> []"' + let b:undo_ftplugin .= ' | silent! execute "unmap <buffer> ]]"' + \ . ' | silent! execute "unmap <buffer> ]["' + \ . ' | silent! execute "unmap <buffer> [["' + \ . ' | silent! execute "unmap <buffer> []"' +endif -" Browse dialog filter on Windows (see ":help browsefilter") -if has('gui_win32') && !exists('b:browsefilter') +" Browse dialog filter on Windows and GTK (see ":help browsefilter") +if (has('gui_win32') || has('gui_gtk')) && !exists('b:browsefilter') let b:browsefilter = "J Script Files (*.ijs)\t*.ijs\n" \ . "All Files (*.*)\t*.*\n" let b:undo_ftplugin .= ' | unlet! b:browsefilter' diff --git a/runtime/ftplugin/php.vim b/runtime/ftplugin/php.vim index 2824a5853b..540653e030 100644 --- a/runtime/ftplugin/php.vim +++ b/runtime/ftplugin/php.vim @@ -1,12 +1,12 @@ " Vim filetype plugin file -" Language: php -" -" This runtime file is looking for a new maintainer. -" -" Former maintainer: Dan Sharp -" Last Changed: 20 Jan 2009 +" Language: PHP +" Maintainer: Doug Kearns <dougkearns@gmail.com> +" Previous Maintainer: Dan Sharp +" Last Changed: 2022 Jul 20 -if exists("b:did_ftplugin") | finish | endif +if exists("b:did_ftplugin") + finish +endif " Make sure the continuation lines below do not cause problems in " compatibility mode. @@ -15,8 +15,8 @@ set cpo&vim " Define some defaults in case the included ftplugins don't set them. let s:undo_ftplugin = "" -let s:browsefilter = "HTML Files (*.html, *.htm)\t*.html;*.htm\n" . - \ "All Files (*.*)\t*.*\n" +let s:browsefilter = "HTML Files (*.html, *.htm)\t*.html;*.htm\n" .. + \ "All Files (*.*)\t*.*\n" let s:match_words = "" runtime! ftplugin/html.vim ftplugin/html_*.vim ftplugin/html/*.vim @@ -24,63 +24,130 @@ let b:did_ftplugin = 1 " Override our defaults if these were set by an included ftplugin. if exists("b:undo_ftplugin") - let s:undo_ftplugin = b:undo_ftplugin +" let b:undo_ftplugin = "setlocal comments< commentstring< formatoptions< omnifunc<" + let s:undo_ftplugin = b:undo_ftplugin endif if exists("b:browsefilter") - let s:browsefilter = b:browsefilter +" let b:undo_ftplugin ..= " | unlet! b:browsefilter b:html_set_browsefilter" + let s:browsefilter = b:browsefilter endif if exists("b:match_words") - let s:match_words = b:match_words +" let b:undo_ftplugin ..= " | unlet! b:match_ignorecase b:match_words b:html_set_match_words" + let s:match_words = b:match_words endif if exists("b:match_skip") - unlet b:match_skip + unlet b:match_skip endif -" Change the :browse e filter to primarily show PHP-related files. -if has("gui_win32") - let b:browsefilter="PHP Files (*.php)\t*.php\n" . s:browsefilter +setlocal comments=s1:/*,mb:*,ex:*/,://,:# +setlocal commentstring=/*%s*/ +setlocal formatoptions+=l formatoptions-=t + +if get(g:, "php_autocomment", 1) + setlocal formatoptions+=croq + " NOTE: set g:PHP_autoformatcomment = 0 to prevent the indent plugin from + " overriding this 'comments' value + setlocal comments-=:# + " space after # comments to exclude attributes + setlocal comments+=b:# endif +if exists('&omnifunc') + setlocal omnifunc=phpcomplete#CompletePHP +endif + +setlocal suffixesadd=.php + " ### " Provided by Mikolaj Machowski <mikmach at wp dot pl> setlocal include=\\\(require\\\|include\\\)\\\(_once\\\)\\\? " Disabled changing 'iskeyword', it breaks a command such as "*" " setlocal iskeyword+=$ -if exists("loaded_matchit") - let b:match_words = '<?php:?>,\<switch\>:\<endswitch\>,' . - \ '\<if\>:\<elseif\>:\<else\>:\<endif\>,' . - \ '\<while\>:\<endwhile\>,' . - \ '\<do\>:\<while\>,' . - \ '\<for\>:\<endfor\>,' . - \ '\<foreach\>:\<endforeach\>,' . - \ '(:),[:],{:},' . - \ s:match_words +let b:undo_ftplugin = "setlocal include< suffixesadd<" + +if exists("loaded_matchit") && exists("b:html_set_match_words") + let b:match_ignorecase = 1 + let b:match_words = 'PhpMatchWords()' + + if !exists("*PhpMatchWords") + function! PhpMatchWords() + " The PHP syntax file uses the Delimiter syntax group for the phpRegion + " matchgroups, without a "php" prefix, so use the stack to test for the + " outer phpRegion group. This also means the closing ?> tag which is + " outside of the matched region just uses the Delimiter group for the + " end match. + let stack = synstack(line('.'), col('.')) + let php_region = !empty(stack) && synIDattr(stack[0], "name") =~# '\<php' + if php_region || getline(".") =~ '.\=\%.c\&?>' + let b:match_skip = "PhpMatchSkip('html')" + return '<?php\|<?=\=:?>,' .. + \ '\<if\>:\<elseif\>:\<else\>:\<endif\>,' .. + \ '\<switch\>:\<case\>:\<break\>:\<continue\>:\<endswitch\>,' .. + \ '\<while\>.\{-})\s*\::\<break\>:\<continue\>:\<endwhile\>,' .. + \ '\<do\>:\<break\>:\<continue\>:\<while\>,' .. + \ '\<for\>:\<break\>:\<continue\>:\<endfor\>,' .. + \ '\<foreach\>:\<break\>:\<continue\>:\<endforeach\>,' .. + \ '\%(<<<\s*\)\@<=''\=\(\h\w*\)''\=:^\s*\1\>' + + " TODO: these probably aren't worth adding and really need syntax support + " '<\_s*script\_s*language\_s*=\_s*[''"]\=\_s*php\_s*[''"]\=\_s*>:<\_s*\_s*/\_s*script\_s*>,' .. + " '<%:%>,' .. + else + let b:match_skip = "PhpMatchSkip('php')" + return s:match_words + endif + endfunction + endif + if !exists("*PhpMatchSkip") + function! PhpMatchSkip(skip) + let name = synIDattr(synID(line('.'), col('.'), 1), 'name') + if a:skip == "html" + " ?> in line comments will also be correctly matched as Delimiter + return name =~? 'comment\|string' || name !~? 'php\|delimiter' + else " php + return name =~? 'comment\|string\|php' + endif + endfunction + endif + let b:undo_ftplugin ..= " | unlet! b:match_skip" endif " ### -if exists('&omnifunc') - setlocal omnifunc=phpcomplete#CompletePHP +" Change the :browse e filter to primarily show PHP-related files. +if (has("gui_win32") || has("gui_gtk")) && exists("b:html_set_browsefilter") + let b:browsefilter = "PHP Files (*.php)\t*.php\n" .. + \ "PHP Test Files (*.phpt)\t*.phpt\n" .. + \ s:browsefilter endif -" Section jumping: [[ and ]] provided by Antony Scriven <adscriven at gmail dot com> -let s:function = '\(abstract\s\+\|final\s\+\|private\s\+\|protected\s\+\|public\s\+\|static\s\+\)*function' -let s:class = '\(abstract\s\+\|final\s\+\)*class' -let s:interface = 'interface' -let s:section = '\(.*\%#\)\@!\_^\s*\zs\('.s:function.'\|'.s:class.'\|'.s:interface.'\)' -exe 'nno <buffer> <silent> [[ ?' . escape(s:section, '|') . '?<CR>:nohls<CR>' -exe 'nno <buffer> <silent> ]] /' . escape(s:section, '|') . '/<CR>:nohls<CR>' -exe 'ono <buffer> <silent> [[ ?' . escape(s:section, '|') . '?<CR>:nohls<CR>' -exe 'ono <buffer> <silent> ]] /' . escape(s:section, '|') . '/<CR>:nohls<CR>' +if !exists("no_plugin_maps") && !exists("no_php_maps") + " Section jumping: [[ and ]] provided by Antony Scriven <adscriven at gmail dot com> + let s:function = '\%(abstract\s\+\|final\s\+\|private\s\+\|protected\s\+\|public\s\+\|static\s\+\)*function' + let s:class = '\%(abstract\s\+\|final\s\+\)*class' + let s:section = escape('^\s*\zs\%(' .. s:function .. '\|' .. s:class .. '\|interface\|trait\|enum\)\>', "|") -setlocal suffixesadd=.php -setlocal commentstring=/*%s*/ + function! s:Jump(pattern, count, flags) + normal! m' + for i in range(a:count) + if !search(a:pattern, a:flags) + break + endif + endfor + endfunction -" Undo the stuff we changed. -let b:undo_ftplugin = "setlocal suffixesadd< commentstring< include< omnifunc<" . - \ " | unlet! b:browsefilter b:match_words | " . - \ s:undo_ftplugin + for mode in ["n", "o", "x"] + exe mode .. "noremap <buffer> <silent> ]] <Cmd>call <SID>Jump('" .. s:section .. "', v:count1, 'W')<CR>" + exe mode .. "noremap <buffer> <silent> [[ <Cmd>call <SID>Jump('" .. s:section .. "', v:count1, 'bW')<CR>" + let b:undo_ftplugin ..= " | sil! exe '" .. mode .. "unmap <buffer> ]]'" .. + \ " | sil! exe '" .. mode .. "unmap <buffer> [['" + endfor +endif + +let b:undo_ftplugin ..= " | " .. s:undo_ftplugin " Restore the saved compatibility options. let &cpo = s:keepcpo unlet s:keepcpo + +" vim: nowrap sw=2 sts=2 ts=8 noet: diff --git a/runtime/ftplugin/vim.vim b/runtime/ftplugin/vim.vim index 64b64b45e3..772899cb42 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: 2021 Apr 11 +" Last Change: 2022 Aug 4 " Only do this when not done yet for this buffer if exists("b:did_ftplugin") @@ -109,7 +109,7 @@ if exists("loaded_matchit") " - set spl=de,en " - au! FileType javascript syntax region foldBraces start=/{/ end=/}/ … let b:match_skip = 'synIDattr(synID(line("."),col("."),1),"name") - \ =~? "comment\\|string\\|vimSynReg\\|vimSet"' + \ =~? "comment\\|string\\|vimLetHereDoc\\|vimSynReg\\|vimSet"' endif let &cpo = s:cpo_save diff --git a/runtime/indent/lisp.vim b/runtime/indent/lisp.vim index b0c4eed1bd..1bce39510c 100644 --- a/runtime/indent/lisp.vim +++ b/runtime/indent/lisp.vim @@ -1,7 +1,7 @@ " Vim indent file " Language: Lisp -" Maintainer: Sergey Khorev <sergey.khorev@gmail.com> -" URL: http://sites.google.com/site/khorser/opensource/vim +" Maintainer: Sergey Khorev <sergey.khorev@gmail.com> +" URL: http://sites.google.com/site/khorser/opensource/vim " Last Change: 2012 Jan 10 " Only load this indent file when no other was loaded. diff --git a/runtime/indent/systemverilog.vim b/runtime/indent/systemverilog.vim index f6114dc1fd..a5f4d5b90d 100644 --- a/runtime/indent/systemverilog.vim +++ b/runtime/indent/systemverilog.vim @@ -2,7 +2,7 @@ " Language: SystemVerilog " Maintainer: kocha <kocha.lsifrontend@gmail.com> " Last Change: 05-Feb-2017 by Bilal Wasim -" 2022 April: b:undo_indent added by Doug Kearns +" 03-Aug-2022 Improved indent " Only load this indent file when no other was loaded. if exists("b:did_indent") @@ -15,7 +15,7 @@ setlocal indentkeys=!^F,o,O,0),0},=begin,=end,=join,=endcase,=join_any,=join_non setlocal indentkeys+==endmodule,=endfunction,=endtask,=endspecify setlocal indentkeys+==endclass,=endpackage,=endsequence,=endclocking setlocal indentkeys+==endinterface,=endgroup,=endprogram,=endproperty,=endchecker -setlocal indentkeys+==`else,=`endif +setlocal indentkeys+==`else,=`elsif,=`endif let b:undo_indent = "setl inde< indk<" @@ -27,6 +27,9 @@ endif let s:cpo_save = &cpo set cpo&vim +let s:multiple_comment = 0 +let s:open_statement = 0 + function SystemVerilogIndent() if exists('b:systemverilog_indent_width') @@ -40,6 +43,12 @@ function SystemVerilogIndent() let indent_modules = 0 endif + if exists('b:systemverilog_indent_ifdef_off') + let indent_ifdef = 0 + else + let indent_ifdef = 1 + endif + " Find a non-blank line above the current line. let lnum = prevnonblank(v:lnum - 1) @@ -54,48 +63,55 @@ function SystemVerilogIndent() let last_line2 = getline(lnum2) let ind = indent(lnum) let ind2 = indent(lnum - 1) - let offset_comment1 = 1 " Define the condition of an open statement " Exclude the match of //, /* or */ let sv_openstat = '\(\<or\>\|\([*/]\)\@<![*(,{><+-/%^&|!=?:]\([*/]\)\@!\)' " Define the condition when the statement ends with a one-line comment let sv_comment = '\(//.*\|/\*.*\*/\s*\)' - if exists('b:verilog_indent_verbose') - let vverb_str = 'INDENT VERBOSE:' + if exists('b:systemverilog_indent_verbose') + let vverb_str = 'INDENT VERBOSE: '. v:lnum .":" let vverb = 1 else let vverb = 0 endif - " Indent according to last line - " End of multiple-line comment - if last_line =~ '\*/\s*$' && last_line !~ '/\*.\{-}\*/' - let ind = ind - offset_comment1 - if vverb - echo vverb_str "De-indent after a multiple-line comment." - endif + " Multiple-line comment count + if curr_line =~ '^\s*/\*' && curr_line !~ '/\*.\{-}\*/' + let s:multiple_comment += 1 + if vverb | echom vverb_str "Start of multiple-line commnt" | endif + elseif curr_line =~ '\*/\s*$' && curr_line !~ '/\*.\{-}\*/' + let s:multiple_comment -= 1 + if vverb | echom vverb_str "End of multiple-line commnt" | endif + return ind + endif + " Maintain indentation during commenting. + if s:multiple_comment > 0 + return ind + endif " Indent after if/else/for/case/always/initial/specify/fork blocks - elseif last_line =~ '`\@<!\<\(if\|else\)\>' || - \ last_line =~ '^\s*\<\(for\|case\%[[zx]]\|do\|foreach\|forever\|randcase\)\>' || + if last_line =~ '^\s*\(end\)\=\s*`\@<!\<\(if\|else\)\>' || + \ last_line =~ '^\s*\<\(for\|while\|repeat\|case\%[[zx]]\|do\|foreach\|forever\|randcase\)\>' || \ last_line =~ '^\s*\<\(always\|always_comb\|always_ff\|always_latch\)\>' || \ last_line =~ '^\s*\<\(initial\|specify\|fork\|final\)\>' - if last_line !~ '\(;\|\<end\>\)\s*' . sv_comment . '*$' || + if last_line !~ '\(;\|\<end\>\|\*/\)\s*' . sv_comment . '*$' || \ last_line =~ '\(//\|/\*\).*\(;\|\<end\>\)\s*' . sv_comment . '*$' let ind = ind + offset - if vverb | echo vverb_str "Indent after a block statement." | endif + if vverb | echom vverb_str "Indent after a block statement." | endif endif " Indent after function/task/class/package/sequence/clocking/ " interface/covergroup/property/checkerprogram blocks elseif last_line =~ '^\s*\<\(function\|task\|class\|package\)\>' || \ last_line =~ '^\s*\<\(sequence\|clocking\|interface\)\>' || \ last_line =~ '^\s*\(\w\+\s*:\)\=\s*\<covergroup\>' || - \ last_line =~ '^\s*\<\(property\|checker\|program\)\>' + \ last_line =~ '^\s*\<\(property\|checker\|program\)\>' || + \ ( last_line =~ '^\s*\<virtual\>' && last_line =~ '\<\(function\|task\|class\|interface\)\>' ) || + \ ( last_line =~ '^\s*\<pure\>' && last_line =~ '\<virtual\>' && last_line =~ '\<\(function\|task\)\>' ) if last_line !~ '\<end\>\s*' . sv_comment . '*$' || \ last_line =~ '\(//\|/\*\).*\(;\|\<end\>\)\s*' . sv_comment . '*$' let ind = ind + offset if vverb - echo vverb_str "Indent after function/task/class block statement." + echom vverb_str "Indent after function/task/class block statement." endif endif @@ -103,13 +119,13 @@ function SystemVerilogIndent() elseif last_line =~ '^\s*\(\<extern\>\s*\)\=\<module\>' let ind = ind + indent_modules if vverb && indent_modules - echo vverb_str "Indent after module statement." + echom vverb_str "Indent after module statement." endif if last_line =~ '[(,]\s*' . sv_comment . '*$' && \ last_line !~ '\(//\|/\*\).*[(,]\s*' . sv_comment . '*$' let ind = ind + offset if vverb - echo vverb_str "Indent after a multiple-line module statement." + echom vverb_str "Indent after a multiple-line module statement." endif endif @@ -119,7 +135,7 @@ function SystemVerilogIndent() \ ( last_line2 !~ sv_openstat . '\s*' . sv_comment . '*$' || \ last_line2 =~ '^\s*[^=!]\+\s*:\s*' . sv_comment . '*$' ) let ind = ind + offset - if vverb | echo vverb_str "Indent after begin statement." | endif + if vverb | echom vverb_str "Indent after begin statement." | endif " Indent after a '{' or a '(' elseif last_line =~ '[{(]' . sv_comment . '*$' && @@ -127,7 +143,21 @@ function SystemVerilogIndent() \ ( last_line2 !~ sv_openstat . '\s*' . sv_comment . '*$' || \ last_line2 =~ '^\s*[^=!]\+\s*:\s*' . sv_comment . '*$' ) let ind = ind + offset - if vverb | echo vverb_str "Indent after begin statement." | endif + if vverb | echom vverb_str "Indent after begin statement." | endif + + " Ignore de-indent for the end of one-line block + elseif ( last_line !~ '\<begin\>' || + \ last_line =~ '\(//\|/\*\).*\<begin\>' ) && + \ last_line2 =~ '\<\(`\@<!if\|`\@<!else\|for\|always\|initial\|do\|foreach\|forever\|final\)\>.*' . + \ sv_comment . '*$' && + \ last_line2 !~ '\(//\|/\*\).*\<\(`\@<!if\|`\@<!else\|for\|always\|initial\|do\|foreach\|forever\|final\)\>' && + \ last_line2 !~ sv_openstat . '\s*' . sv_comment . '*$' && + \ ( last_line2 !~ '\<begin\>' || + \ last_line2 =~ '\(//\|/\*\).*\<begin\>' ) && + \ last_line2 =~ ')*\s*;\s*' . sv_comment . '*$' + if vverb + echom vverb_str "Ignore de-indent after the end of one-line statement." + endif " De-indent for the end of one-line block elseif ( last_line !~ '\<begin\>' || @@ -136,39 +166,29 @@ function SystemVerilogIndent() \ sv_comment . '*$' && \ last_line2 !~ '\(//\|/\*\).*\<\(`\@<!if\|`\@<!else\|for\|always\|initial\|do\|foreach\|forever\|final\)\>' && \ last_line2 !~ sv_openstat . '\s*' . sv_comment . '*$' && + \ last_line2 !~ '\(;\|\<end\>\|\*/\)\s*' . sv_comment . '*$' && \ ( last_line2 !~ '\<begin\>' || \ last_line2 =~ '\(//\|/\*\).*\<begin\>' ) let ind = ind - offset if vverb - echo vverb_str "De-indent after the end of one-line statement." + echom vverb_str "De-indent after the end of one-line statement." endif - " Multiple-line statement (including case statement) - " Open statement - " Ident the first open line - elseif last_line =~ sv_openstat . '\s*' . sv_comment . '*$' && - \ last_line !~ '\(//\|/\*\).*' . sv_openstat . '\s*$' && - \ last_line2 !~ sv_openstat . '\s*' . sv_comment . '*$' - let ind = ind + offset - if vverb | echo vverb_str "Indent after an open statement." | endif - - " Close statement - " De-indent for an optional close parenthesis and a semicolon, and only - " if there exists precedent non-whitespace char - elseif last_line =~ ')*\s*;\s*' . sv_comment . '*$' && - \ last_line !~ '^\s*)*\s*;\s*' . sv_comment . '*$' && - \ last_line !~ '\(//\|/\*\).*\S)*\s*;\s*' . sv_comment . '*$' && - \ ( last_line2 =~ sv_openstat . '\s*' . sv_comment . '*$' && - \ last_line2 !~ ';\s*//.*$') && - \ last_line2 !~ '^\s*' . sv_comment . '$' - let ind = ind - offset - if vverb | echo vverb_str "De-indent after a close statement." | endif + " Multiple-line statement (including case statement) + " Open statement + " Ident the first open line + elseif last_line =~ sv_openstat . '\s*' . sv_comment . '*$' && + \ last_line !~ '\(//\|/\*\).*' . sv_openstat . '\s*$' && + \ last_line2 !~ sv_openstat . '\s*' . sv_comment . '*$' + let ind = ind + offset + let s:open_statement = 1 + if vverb | echom vverb_str "Indent after an open statement." | endif - " `ifdef and `else - elseif last_line =~ '^\s*`\<\(ifdef\|else\)\>' + " `ifdef or `ifndef or `elsif or `else + elseif last_line =~ '^\s*`\<\(ifn\?def\|elsif\|else\)\>' && indent_ifdef let ind = ind + offset if vverb - echo vverb_str "Indent after a `ifdef or `else statement." + echom vverb_str "Indent after a `ifdef or `ifndef or `elsif or `else statement." endif endif @@ -177,17 +197,21 @@ function SystemVerilogIndent() " De-indent on the end of the block " join/end/endcase/endfunction/endtask/endspecify - if curr_line =~ '^\s*\<\(join\|join_any\|join_none\|\|end\|endcase\|while\)\>' || + if curr_line =~ '^\s*\<\(join\|join_any\|join_none\|\|end\|endcase\)\>' || \ curr_line =~ '^\s*\<\(endfunction\|endtask\|endspecify\|endclass\)\>' || \ curr_line =~ '^\s*\<\(endpackage\|endsequence\|endclocking\|endinterface\)\>' || - \ curr_line =~ '^\s*\<\(endgroup\|endproperty\|endchecker\|endprogram\)\>' || - \ curr_line =~ '^\s*}' + \ curr_line =~ '^\s*\<\(endgroup\|endproperty\|endchecker\|endprogram\)\>' let ind = ind - offset - if vverb | echo vverb_str "De-indent the end of a block." | endif + if vverb | echom vverb_str "De-indent the end of a block." | endif + if s:open_statement == 1 + let ind = ind - offset + let s:open_statement = 0 + if vverb | echom vverb_str "De-indent the close statement." | endif + endif elseif curr_line =~ '^\s*\<endmodule\>' let ind = ind - indent_modules if vverb && indent_modules - echo vverb_str "De-indent the end of a module." + echom vverb_str "De-indent the end of a module." endif " De-indent on a stand-alone 'begin' @@ -202,25 +226,46 @@ function SystemVerilogIndent() \ last_line =~ sv_openstat . '\s*' . sv_comment . '*$' ) let ind = ind - offset if vverb - echo vverb_str "De-indent a stand alone begin statement." + echom vverb_str "De-indent a stand alone begin statement." + endif + if s:open_statement == 1 + let ind = ind - offset + let s:open_statement = 0 + if vverb | echom vverb_str "De-indent the close statement." | endif endif endif - " De-indent after the end of multiple-line statement - elseif curr_line =~ '^\s*)' && - \ ( last_line =~ sv_openstat . '\s*' . sv_comment . '*$' || - \ last_line !~ sv_openstat . '\s*' . sv_comment . '*$' && - \ last_line2 =~ sv_openstat . '\s*' . sv_comment . '*$' ) - let ind = ind - offset - if vverb - echo vverb_str "De-indent the end of a multiple statement." - endif + " " Close statement + " " De-indent for an optional close parenthesis and a semicolon, and only + " " if there exists precedent non-whitespace char + " elseif last_line =~ ')*\s*;\s*' . sv_comment . '*$' && + " \ last_line !~ '^\s*)*\s*;\s*' . sv_comment . '*$' && + " \ last_line !~ '\(//\|/\*\).*\S)*\s*;\s*' . sv_comment . '*$' && + " \ ( last_line2 =~ sv_openstat . '\s*' . sv_comment . '*$' && + " \ last_line2 !~ ';\s*//.*$') && + " \ last_line2 !~ '^\s*' . sv_comment . '$' + " let ind = ind - offset + " if vverb | echom vverb_str "De-indent after a close statement." | endif - " De-indent `else and `endif - elseif curr_line =~ '^\s*`\<\(else\|endif\)\>' - let ind = ind - offset - if vverb | echo vverb_str "De-indent `else and `endif statement." | endif + " " De-indent after the end of multiple-line statement + " elseif curr_line =~ '^\s*)' && + " \ ( last_line =~ sv_openstat . '\s*' . sv_comment . '*$' || + " \ last_line !~ sv_openstat . '\s*' . sv_comment . '*$' && + " \ last_line2 =~ sv_openstat . '\s*' . sv_comment . '*$' ) + " let ind = ind - offset + " if vverb + " echom vverb_str "De-indent the end of a multiple statement." + " endif + " De-indent `elsif or `else or `endif + elseif curr_line =~ '^\s*`\<\(elsif\|else\|endif\)\>' && indent_ifdef + let ind = ind - offset + if vverb | echom vverb_str "De-indent `elsif or `else or `endif statement." | endif + if b:systemverilog_open_statement == 1 + let ind = ind - offset + let b:systemverilog_open_statement = 0 + if vverb | echom vverb_str "De-indent the open statement." | endif + endif endif " Return the indentation @@ -231,3 +276,4 @@ let &cpo = s:cpo_save unlet s:cpo_save " vim:sw=2 + diff --git a/runtime/indent/testdir/html.in b/runtime/indent/testdir/html.in index b62c67ddb2..4783a096d0 100644 --- a/runtime/indent/testdir/html.in +++ b/runtime/indent/testdir/html.in @@ -1,7 +1,7 @@ -" vim: set ft=html sw=4 ts=8 : +% vim: set ft=html sw=4 ts=8 : -" START_INDENT +% START_INDENT <html> <body> <style> @@ -50,7 +50,7 @@ text </body> </html> -" END_INDENT +% END_INDENT % START_INDENT % INDENT_EXE let g:html_indent_style1 = "inc" diff --git a/runtime/indent/testdir/html.ok b/runtime/indent/testdir/html.ok index 938e965d8c..4963634465 100644 --- a/runtime/indent/testdir/html.ok +++ b/runtime/indent/testdir/html.ok @@ -1,7 +1,7 @@ -" vim: set ft=html sw=4 ts=8 : +% vim: set ft=html sw=4 ts=8 : -" START_INDENT +% START_INDENT <html> <body> <style> @@ -50,7 +50,7 @@ div#d2 { color: green; } </body> </html> -" END_INDENT +% END_INDENT % START_INDENT % INDENT_EXE let g:html_indent_style1 = "inc" diff --git a/runtime/indent/testdir/python.in b/runtime/indent/testdir/python.in new file mode 100644 index 0000000000..57719ee430 --- /dev/null +++ b/runtime/indent/testdir/python.in @@ -0,0 +1,94 @@ +# vim: set ft=python sw=4 et: + +# START_INDENT +dict = { +'a': 1, +'b': 2, +'c': 3, +} +# END_INDENT + +# START_INDENT +# INDENT_EXE let [g:python_indent.open_paren, g:python_indent.closed_paren_align_last_line] = ['shiftwidth()', v:false] +dict = { +'a': 1, +'b': 2, +'c': 3, +} +# END_INDENT + +# START_INDENT +# INDENT_EXE let g:python_indent.open_paren = 'shiftwidth() * 2' +# INDENT_EXE syntax match pythonFoldMarkers /{{{\d*/ contained containedin=pythonComment +# xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx {{{1 + +if True: +pass +# END_INDENT + +# START_INDENT +open_paren_not_at_EOL(100, +(200, +300), +400) + +open_paren_at_EOL( +100, 200, 300, 400) + +open_paren_not_at_EOL(100, +(200, +300), +400) + +open_paren_at_EOL( +100, 200, 300, 400) + +open_paren_not_at_EOL(100, +(200, +300), +400) + +open_paren_at_EOL( +100, 200, 300, 400) + +open_paren_not_at_EOL(100, +(200, +300), +400) + +open_paren_at_EOL( +100, 200, 300, 400) + +open_paren_not_at_EOL(100, +(200, +300), +400) + +open_paren_at_EOL( +100, 200, 300, 400) + +open_paren_not_at_EOL(100, +(200, +300), +400) + +open_paren_at_EOL( +100, 200, 300, 400) + +open_paren_not_at_EOL(100, +(200, +300), +400) + +open_paren_at_EOL( +100, 200, 300, 400) + +open_paren_not_at_EOL(100, +(200, +300), +400) + +open_paren_at_EOL( +100, 200, 300, 400) + +# END_INDENT diff --git a/runtime/indent/testdir/python.ok b/runtime/indent/testdir/python.ok new file mode 100644 index 0000000000..f5ebbc2285 --- /dev/null +++ b/runtime/indent/testdir/python.ok @@ -0,0 +1,94 @@ +# vim: set ft=python sw=4 et: + +# START_INDENT +dict = { + 'a': 1, + 'b': 2, + 'c': 3, + } +# END_INDENT + +# START_INDENT +# INDENT_EXE let [g:python_indent.open_paren, g:python_indent.closed_paren_align_last_line] = ['shiftwidth()', v:false] +dict = { + 'a': 1, + 'b': 2, + 'c': 3, +} +# END_INDENT + +# START_INDENT +# INDENT_EXE let g:python_indent.open_paren = 'shiftwidth() * 2' +# INDENT_EXE syntax match pythonFoldMarkers /{{{\d*/ contained containedin=pythonComment +# xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx {{{1 + +if True: + pass +# END_INDENT + +# START_INDENT +open_paren_not_at_EOL(100, + (200, + 300), + 400) + +open_paren_at_EOL( + 100, 200, 300, 400) + +open_paren_not_at_EOL(100, + (200, + 300), + 400) + +open_paren_at_EOL( + 100, 200, 300, 400) + +open_paren_not_at_EOL(100, + (200, + 300), + 400) + +open_paren_at_EOL( + 100, 200, 300, 400) + +open_paren_not_at_EOL(100, + (200, + 300), + 400) + +open_paren_at_EOL( + 100, 200, 300, 400) + +open_paren_not_at_EOL(100, + (200, + 300), + 400) + +open_paren_at_EOL( + 100, 200, 300, 400) + +open_paren_not_at_EOL(100, + (200, + 300), + 400) + +open_paren_at_EOL( + 100, 200, 300, 400) + +open_paren_not_at_EOL(100, + (200, + 300), + 400) + +open_paren_at_EOL( + 100, 200, 300, 400) + +open_paren_not_at_EOL(100, + (200, + 300), + 400) + +open_paren_at_EOL( + 100, 200, 300, 400) + +# END_INDENT diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua index 3f71d4f70d..db92085423 100644 --- a/runtime/lua/vim/diagnostic.lua +++ b/runtime/lua/vim/diagnostic.lua @@ -45,18 +45,24 @@ local bufnr_and_namespace_cacher_mt = { end, } -local diagnostic_cache = setmetatable({}, { - __index = function(t, bufnr) - assert(bufnr > 0, 'Invalid buffer number') - vim.api.nvim_buf_attach(bufnr, false, { - on_detach = function() - rawset(t, bufnr, nil) -- clear cache - end, - }) - t[bufnr] = {} - return t[bufnr] - end, -}) +local diagnostic_cache +do + local group = vim.api.nvim_create_augroup('DiagnosticBufDelete', {}) + diagnostic_cache = setmetatable({}, { + __index = function(t, bufnr) + assert(bufnr > 0, 'Invalid buffer number') + vim.api.nvim_create_autocmd('BufDelete', { + group = group, + buffer = bufnr, + callback = function() + rawset(t, bufnr, nil) + end, + }) + t[bufnr] = {} + return t[bufnr] + end, + }) +end local diagnostic_cache_extmarks = setmetatable({}, bufnr_and_namespace_cacher_mt) local diagnostic_attached_buffers = {} diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 1b209e6a9d..fcd697a7c1 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -142,6 +142,7 @@ local extension = { asp = function(path, bufnr) return require('vim.filetype.detect').asp(bufnr) end, + astro = 'astro', atl = 'atlas', as = 'atlas', ahk = 'autohotkey', @@ -421,9 +422,11 @@ local extension = { gdb = 'gdb', gdmo = 'gdmo', mo = 'gdmo', - tres = 'gdresource', tscn = 'gdresource', + tres = 'gdresource', gd = 'gdscript', + gdshader = 'gdshader', + shader = 'gdshader', ged = 'gedcom', gmi = 'gemtext', gemini = 'gemtext', @@ -668,6 +671,21 @@ local extension = { moo = 'moo', moon = 'moonscript', mp = 'mp', + mpiv = function(path, bufnr) + return 'mp', function(b) + vim.b[b].mp_metafun = 1 + end + end, + mpvi = function(path, bufnr) + return 'mp', function(b) + vim.b[b].mp_metafun = 1 + end + end, + mpxl = function(path, bufnr) + return 'mp', function(b) + vim.b[b].mp_metafun = 1 + end + end, mof = 'msidl', odl = 'msidl', msql = 'msql', @@ -776,6 +794,7 @@ local extension = { ptl = 'python', ql = 'ql', qll = 'ql', + qmd = 'quarto', R = function(path, bufnr) return require('vim.filetype.detect').r(bufnr) end, @@ -994,6 +1013,11 @@ local extension = { dsm = 'vb', ctl = 'vb', vbs = 'vb', + vdmpp = 'vdmpp', + vpp = 'vdmpp', + vdmrt = 'vdmrt', + vdmsl = 'vdmsl', + vdm = 'vdmsl', vr = 'vera', vri = 'vera', vrh = 'vera', @@ -2472,7 +2496,15 @@ function M.match(args) -- Finally, check file contents if contents or bufnr then - contents = contents or M.getlines(bufnr) + if contents == nil then + if api.nvim_buf_line_count(bufnr) > 101 then + -- only need first 100 and last line for current checks + contents = M.getlines(bufnr, 1, 100) + contents[#contents + 1] = M.getlines(bufnr, -1) + else + contents = M.getlines(bufnr) + end + end -- If name is nil, catch any errors from the contents filetype detection function. -- If the function tries to use the filename that is nil then it will fail, -- but this enables checks which do not need a filename to still work. diff --git a/runtime/lua/vim/filetype/detect.lua b/runtime/lua/vim/filetype/detect.lua index 14f076717f..2be9dcff88 100644 --- a/runtime/lua/vim/filetype/detect.lua +++ b/runtime/lua/vim/filetype/detect.lua @@ -930,7 +930,7 @@ function M.progress_pascal(bufnr) return 'progress' end --- Distinguish between "default" and Cproto prototype file. +-- Distinguish between "default", Prolog and Cproto prototype file. function M.proto(bufnr, 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 @@ -940,7 +940,18 @@ function M.proto(bufnr, default) if getlines(bufnr, 2):find('.;$') then return 'cpp' else - return default + -- Recognize Prolog by specific text in the first non-empty line; + -- require a blank after the '%' because Perl uses "%list" and "%translate" + local line = nextnonblank(bufnr, 1) + if + line and line:find(':%-') + or matchregex(line, [[\c\<prolog\>]]) + or findany(line, { '^%s*%%+%s', '^%s*%%+$', '^%s*/%*' }) + then + return 'prolog' + else + return default + end end end diff --git a/runtime/lua/vim/inspect.lua b/runtime/lua/vim/inspect.lua index 0a53fb203b..c232f69590 100644 --- a/runtime/lua/vim/inspect.lua +++ b/runtime/lua/vim/inspect.lua @@ -89,8 +89,38 @@ local function escape(str) ) end +-- List of lua keywords +local luaKeywords = { + ['and'] = true, + ['break'] = true, + ['do'] = true, + ['else'] = true, + ['elseif'] = true, + ['end'] = true, + ['false'] = true, + ['for'] = true, + ['function'] = true, + ['goto'] = true, + ['if'] = true, + ['in'] = true, + ['local'] = true, + ['nil'] = true, + ['not'] = true, + ['or'] = true, + ['repeat'] = true, + ['return'] = true, + ['then'] = true, + ['true'] = true, + ['until'] = true, + ['while'] = true, +} + local function isIdentifier(str) - return type(str) == 'string' and not not str:match('^[_%a][_%a%d]*$') + return type(str) == 'string' + -- identifier must start with a letter and underscore, and be followed by letters, numbers, and underscores + and not not str:match('^[_%a][_%a%d]*$') + -- lua keywords are not valid identifiers + and not luaKeywords[str] end local flr = math.floor diff --git a/runtime/lua/vim/keymap.lua b/runtime/lua/vim/keymap.lua index 7265beb56b..219de16b5c 100644 --- a/runtime/lua/vim/keymap.lua +++ b/runtime/lua/vim/keymap.lua @@ -36,14 +36,17 @@ local keymap = {} ---@param lhs string Left-hand side |{lhs}| of the mapping. ---@param rhs string|function Right-hand side |{rhs}| of the mapping. Can also be a Lua function. -- ----@param opts table A table of |:map-arguments| such as "silent". In addition to the options ---- listed in |nvim_set_keymap()|, this table also accepts the following keys: ---- - buffer: (number or boolean) Add a mapping to the given buffer. When "true" ---- or 0, use the current buffer. ---- - remap: (boolean) Make the mapping recursive. This is the ---- inverse of the "noremap" option from |nvim_set_keymap()|. ---- Default `false`. ---- - replace_keycodes: (boolean) defaults to true if "expr" is true. +---@param opts table A table of |:map-arguments|. +--- + Accepts options accepted by the {opts} parameter in |nvim_set_keymap()|, +--- with the following notable differences: +--- - replace_keycodes: Defaults to `true` if "expr" is `true`. +--- - noremap: Always overridden with the inverse of "remap" (see below). +--- + In addition to those options, the table accepts the following keys: +--- - buffer: (number or boolean) Add a mapping to the given buffer. +--- When `0` or `true`, use the current buffer. +--- - remap: (boolean) Make the mapping recursive. +--- This is the inverse of the "noremap" option from |nvim_set_keymap()|. +--- Defaults to `false`. ---@see |nvim_set_keymap()| function keymap.set(mode, lhs, rhs, opts) vim.validate({ diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index bf2201d9c8..1dc1a045fd 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -289,7 +289,12 @@ local function validate_client_config(config) 'flags.debounce_text_changes must be a number with the debounce time in milliseconds' ) - local cmd, cmd_args = lsp._cmd_parts(config.cmd) + local cmd, cmd_args + if type(config.cmd) == 'function' then + cmd = config.cmd + else + cmd, cmd_args = lsp._cmd_parts(config.cmd) + end local offset_encoding = valid_encodings.UTF16 if config.offset_encoding then offset_encoding = validate_encoding(config.offset_encoding) @@ -338,54 +343,166 @@ end local changetracking = {} do - --@private - --- client_id → state + ---@private + --- + --- LSP has 3 different sync modes: + --- - None (Servers will read the files themselves when needed) + --- - Full (Client sends the full buffer content on updates) + --- - Incremental (Client sends only the changed parts) --- - --- state - --- use_incremental_sync: bool - --- buffers: bufnr -> buffer_state + --- Changes are tracked per buffer. + --- A buffer can have multiple clients attached and each client needs to send the changes + --- To minimize the amount of changesets to compute, computation is grouped: --- - --- buffer_state - --- pending_change?: function that the timer starts to trigger didChange - --- pending_changes: table (uri -> list of pending changeset tables)); - --- Only set if incremental_sync is used + --- None: One group for all clients + --- Full: One group for all clients + --- Incremental: One group per `offset_encoding` --- - --- timer?: uv_timer - --- lines: table - local state_by_client = {} + --- Sending changes can be debounced per buffer. To simplify the implementation the + --- smallest debounce interval is used and we don't group clients by different intervals. + --- + --- @class CTGroup + --- @field sync_kind number TextDocumentSyncKind, considers config.flags.allow_incremental_sync + --- @field offset_encoding "utf-8"|"utf-16"|"utf-32" + --- + --- @class CTBufferState + --- @field name string name of the buffer + --- @field lines string[] snapshot of buffer lines from last didChange + --- @field lines_tmp string[] + --- @field pending_changes table[] List of debounced changes in incremental sync mode + --- @field timer nil|userdata uv_timer + --- @field last_flush nil|number uv.hrtime of the last flush/didChange-notification + --- @field needs_flush boolean true if buffer updates haven't been sent to clients/servers yet + --- @field refs number how many clients are using this group + --- + --- @class CTGroupState + --- @field buffers table<number, CTBufferState> + --- @field debounce number debounce duration in ms + --- @field clients table<number, table> clients using this state. {client_id, client} ---@private - function changetracking.init(client, bufnr) - local use_incremental_sync = ( - if_nil(client.config.flags.allow_incremental_sync, true) - and vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'change') - == protocol.TextDocumentSyncKind.Incremental + ---@param group CTGroup + ---@return string + local function group_key(group) + if group.sync_kind == protocol.TextDocumentSyncKind.Incremental then + return tostring(group.sync_kind) .. '\0' .. group.offset_encoding + end + return tostring(group.sync_kind) + end + + ---@private + ---@type table<CTGroup, CTGroupState> + local state_by_group = setmetatable({}, { + __index = function(tbl, k) + return rawget(tbl, group_key(k)) + end, + __newindex = function(tbl, k, v) + rawset(tbl, group_key(k), v) + end, + }) + + ---@private + ---@return CTGroup + local function get_group(client) + local allow_inc_sync = if_nil(client.config.flags.allow_incremental_sync, true) + local change_capability = + vim.tbl_get(client.server_capabilities or {}, 'textDocumentSync', 'change') + local sync_kind = change_capability or protocol.TextDocumentSyncKind.None + if not allow_inc_sync and change_capability == protocol.TextDocumentSyncKind.Incremental then + sync_kind = protocol.TextDocumentSyncKind.Full + end + return { + sync_kind = sync_kind, + offset_encoding = client.offset_encoding, + } + end + + ---@private + ---@param state CTBufferState + local function incremental_changes(state, encoding, bufnr, firstline, lastline, new_lastline) + local prev_lines = state.lines + local curr_lines = state.lines_tmp + + local changed_lines = nvim_buf_get_lines(bufnr, firstline, new_lastline, true) + for i = 1, firstline do + curr_lines[i] = prev_lines[i] + end + for i = firstline + 1, new_lastline do + curr_lines[i] = changed_lines[i - firstline] + end + for i = lastline + 1, #prev_lines do + curr_lines[i - lastline + new_lastline] = prev_lines[i] + end + if tbl_isempty(curr_lines) then + -- Can happen when deleting the entire contents of a buffer, see https://github.com/neovim/neovim/issues/16259. + curr_lines[1] = '' + end + + local line_ending = buf_get_line_ending(bufnr) + local incremental_change = sync.compute_diff( + state.lines, + curr_lines, + firstline, + lastline, + new_lastline, + encoding, + line_ending ) - local state = state_by_client[client.id] - if not state then + + -- Double-buffering of lines tables is used to reduce the load on the garbage collector. + -- At this point the prev_lines table is useless, but its internal storage has already been allocated, + -- so let's keep it around for the next didChange event, in which it will become the next + -- curr_lines table. Note that setting elements to nil doesn't actually deallocate slots in the + -- internal storage - it merely marks them as free, for the GC to deallocate them. + for i in ipairs(prev_lines) do + prev_lines[i] = nil + end + state.lines = curr_lines + state.lines_tmp = prev_lines + + return incremental_change + end + + ---@private + function changetracking.init(client, bufnr) + assert(client.offset_encoding, 'lsp client must have an offset_encoding') + local group = get_group(client) + local state = state_by_group[group] + if state then + state.debounce = math.min(state.debounce, client.config.flags.debounce_text_changes or 150) + state.clients[client.id] = client + else state = { buffers = {}, debounce = client.config.flags.debounce_text_changes or 150, - use_incremental_sync = use_incremental_sync, + clients = { + [client.id] = client, + }, } - state_by_client[client.id] = state + state_by_group[group] = state end - if not state.buffers[bufnr] then - local buf_state = { + local buf_state = state.buffers[bufnr] + if buf_state then + buf_state.refs = buf_state.refs + 1 + else + buf_state = { name = api.nvim_buf_get_name(bufnr), + lines = {}, + lines_tmp = {}, + pending_changes = {}, + needs_flush = false, + refs = 1, } state.buffers[bufnr] = buf_state - if use_incremental_sync then + if group.sync_kind == protocol.TextDocumentSyncKind.Incremental then buf_state.lines = nvim_buf_get_lines(bufnr, 0, -1, true) - buf_state.lines_tmp = {} - buf_state.pending_changes = {} end end end ---@private function changetracking._get_and_set_name(client, bufnr, name) - local state = state_by_client[client.id] or {} + local state = state_by_group[get_group(client)] or {} local buf_state = (state.buffers or {})[bufnr] local old_name = buf_state.name buf_state.name = name @@ -395,32 +512,33 @@ do ---@private function changetracking.reset_buf(client, bufnr) changetracking.flush(client, bufnr) - local state = state_by_client[client.id] - if state and state.buffers then - local buf_state = state.buffers[bufnr] + local state = state_by_group[get_group(client)] + if not state then + return + end + assert(state.buffers, 'CTGroupState must have buffers') + local buf_state = state.buffers[bufnr] + buf_state.refs = buf_state.refs - 1 + assert(buf_state.refs >= 0, 'refcount on buffer state must not get negative') + if buf_state.refs == 0 then state.buffers[bufnr] = nil - if buf_state and buf_state.timer then - buf_state.timer:stop() - buf_state.timer:close() - buf_state.timer = nil - end + changetracking._reset_timer(buf_state) end end ---@private - function changetracking.reset(client_id) - local state = state_by_client[client_id] + function changetracking.reset(client) + local state = state_by_group[get_group(client)] if not state then return end - for _, buf_state in pairs(state.buffers) do - if buf_state.timer then - buf_state.timer:stop() - buf_state.timer:close() - buf_state.timer = nil + state.clients[client.id] = nil + if vim.tbl_count(state.clients) == 0 then + for _, buf_state in pairs(state.buffers) do + changetracking._reset_timer(buf_state) end + state.buffers = {} end - state.buffers = {} end ---@private @@ -430,6 +548,10 @@ do -- debounce can be skipped and otherwise maybe reduced. -- -- This turns the debounce into a kind of client rate limiting + -- + ---@param debounce number + ---@param buf_state CTBufferState + ---@return number local function next_debounce(debounce, buf_state) if debounce == 0 then return 0 @@ -444,83 +566,36 @@ do end ---@private - function changetracking.prepare(bufnr, firstline, lastline, new_lastline) - local incremental_changes = function(client, buf_state) - local prev_lines = buf_state.lines - local curr_lines = buf_state.lines_tmp - - local changed_lines = nvim_buf_get_lines(bufnr, firstline, new_lastline, true) - for i = 1, firstline do - curr_lines[i] = prev_lines[i] - end - for i = firstline + 1, new_lastline do - curr_lines[i] = changed_lines[i - firstline] - end - for i = lastline + 1, #prev_lines do - curr_lines[i - lastline + new_lastline] = prev_lines[i] - end - if tbl_isempty(curr_lines) then - -- Can happen when deleting the entire contents of a buffer, see https://github.com/neovim/neovim/issues/16259. - curr_lines[1] = '' - end - - local line_ending = buf_get_line_ending(bufnr) - local incremental_change = sync.compute_diff( - buf_state.lines, - curr_lines, - firstline, - lastline, - new_lastline, - client.offset_encoding or 'utf-16', - line_ending - ) - - -- Double-buffering of lines tables is used to reduce the load on the garbage collector. - -- At this point the prev_lines table is useless, but its internal storage has already been allocated, - -- so let's keep it around for the next didChange event, in which it will become the next - -- curr_lines table. Note that setting elements to nil doesn't actually deallocate slots in the - -- internal storage - it merely marks them as free, for the GC to deallocate them. - for i in ipairs(prev_lines) do - prev_lines[i] = nil - end - buf_state.lines = curr_lines - buf_state.lines_tmp = prev_lines + ---@param bufnr number + ---@param sync_kind number protocol.TextDocumentSyncKind + ---@param state CTGroupState + ---@param buf_state CTBufferState + local function send_changes(bufnr, sync_kind, state, buf_state) + if not buf_state.needs_flush then + return + end + buf_state.last_flush = uv.hrtime() + buf_state.needs_flush = false - return incremental_change + if not api.nvim_buf_is_valid(bufnr) then + buf_state.pending_changes = {} + return end - local full_changes = once(function() - return { - text = buf_get_full_text(bufnr), + + local changes + if sync_kind == protocol.TextDocumentSyncKind.None then + return + elseif sync_kind == protocol.TextDocumentSyncKind.Incremental then + changes = buf_state.pending_changes + buf_state.pending_changes = {} + else + changes = { + { text = buf_get_full_text(bufnr) }, } - end) + end local uri = vim.uri_from_bufnr(bufnr) - return function(client) - if - vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'change') - == protocol.TextDocumentSyncKind.None - then - return - end - local state = state_by_client[client.id] - local buf_state = state.buffers[bufnr] - changetracking._reset_timer(buf_state) - local debounce = next_debounce(state.debounce, buf_state) - if state.use_incremental_sync then - -- This must be done immediately and cannot be delayed - -- The contents would further change and startline/endline may no longer fit - table.insert(buf_state.pending_changes, incremental_changes(client, buf_state)) - end - buf_state.pending_change = function() - if buf_state.pending_change == nil then - return - end - buf_state.pending_change = nil - buf_state.last_flush = uv.hrtime() - if client.is_stopped() or not api.nvim_buf_is_valid(bufnr) then - return - end - local changes = state.use_incremental_sync and buf_state.pending_changes - or { full_changes() } + for _, client in pairs(state.clients) do + if not client.is_stopped() and lsp.buf_is_attached(bufnr, client.id) then client.notify('textDocument/didChange', { textDocument = { uri = uri, @@ -528,46 +603,90 @@ do }, contentChanges = changes, }) - buf_state.pending_changes = {} + end + end + end + + ---@private + function changetracking.send_changes(bufnr, firstline, lastline, new_lastline) + local groups = {} + for _, client in pairs(lsp.get_active_clients({ bufnr = bufnr })) do + local group = get_group(client) + groups[group_key(group)] = group + end + for _, group in pairs(groups) do + local state = state_by_group[group] + if not state then + error( + string.format( + 'changetracking.init must have been called for all LSP clients. group=%s states=%s', + vim.inspect(group), + vim.inspect(vim.tbl_keys(state_by_group)) + ) + ) + end + local buf_state = state.buffers[bufnr] + buf_state.needs_flush = true + changetracking._reset_timer(buf_state) + local debounce = next_debounce(state.debounce, buf_state) + if group.sync_kind == protocol.TextDocumentSyncKind.Incremental then + -- This must be done immediately and cannot be delayed + -- The contents would further change and startline/endline may no longer fit + local changes = incremental_changes( + buf_state, + group.offset_encoding, + bufnr, + firstline, + lastline, + new_lastline + ) + table.insert(buf_state.pending_changes, changes) end if debounce == 0 then - buf_state.pending_change() + send_changes(bufnr, group.sync_kind, state, buf_state) else local timer = uv.new_timer() buf_state.timer = timer - -- Must use schedule_wrap because `full_changes()` calls nvim_buf_get_lines - timer:start(debounce, 0, vim.schedule_wrap(buf_state.pending_change)) + timer:start( + debounce, + 0, + vim.schedule_wrap(function() + changetracking._reset_timer(buf_state) + send_changes(bufnr, group.sync_kind, state, buf_state) + end) + ) end end end + ---@private function changetracking._reset_timer(buf_state) - if buf_state.timer then - buf_state.timer:stop() - buf_state.timer:close() + local timer = buf_state.timer + if timer then buf_state.timer = nil + if not timer:is_closing() then + timer:stop() + timer:close() + end end end --- Flushes any outstanding change notification. ---@private function changetracking.flush(client, bufnr) - local state = state_by_client[client.id] + local group = get_group(client) + local state = state_by_group[group] if not state then return end if bufnr then local buf_state = state.buffers[bufnr] or {} changetracking._reset_timer(buf_state) - if buf_state.pending_change then - buf_state.pending_change() - end + send_changes(bufnr, group.sync_kind, state, buf_state) else - for _, buf_state in pairs(state.buffers) do + for buf, buf_state in pairs(state.buffers) do changetracking._reset_timer(buf_state) - if buf_state.pending_change then - buf_state.pending_change() - end + send_changes(buf, group.sync_kind, state, buf_state) end end end @@ -577,7 +696,7 @@ end --- Default handler for the 'textDocument/didOpen' LSP notification. --- ---@param bufnr number Number of the buffer, or 0 for current ----@param client Client object +---@param client table Client object local function text_document_did_open_handler(bufnr, client) changetracking.init(client, bufnr) if not vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'openClose') then @@ -741,14 +860,17 @@ end --- Used on all running clients. --- The default implementation re-uses a client if name --- and root_dir matches. ----@return number client_id +---@return number|nil client_id function lsp.start(config, opts) opts = opts or {} local reuse_client = opts.reuse_client or function(client, conf) return client.config.root_dir == conf.root_dir and client.name == conf.name end - config.name = config.name or (config.cmd[1] and vim.fs.basename(config.cmd[1])) or nil + config.name = config.name + if not config.name and type(config.cmd) == 'table' then + config.name = config.cmd[1] and vim.fs.basename(config.cmd[1]) or nil + end local bufnr = api.nvim_get_current_buf() for _, clients in ipairs({ uninitialized_clients, lsp.get_active_clients() }) do for _, client in pairs(clients) do @@ -779,8 +901,13 @@ end --- The following parameters describe fields in the {config} table. --- --- ----@param cmd: (required, string or list treated like |jobstart()|) Base command ---- that initiates the LSP client. +---@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`. @@ -871,7 +998,7 @@ end --- - 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, default 500): Milliseconds to wait for server to +--- - 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. --- @@ -968,12 +1095,20 @@ function lsp.start_client(config) ---@private local function set_defaults(client, bufnr) - if client.server_capabilities.definitionProvider and vim.bo[bufnr].tagfunc == '' then + local capabilities = client.server_capabilities + if capabilities.definitionProvider and vim.bo[bufnr].tagfunc == '' then vim.bo[bufnr].tagfunc = 'v:lua.vim.lsp.tagfunc' end - if client.server_capabilities.completionProvider and vim.bo[bufnr].omnifunc == '' then + if capabilities.completionProvider and vim.bo[bufnr].omnifunc == '' then vim.bo[bufnr].omnifunc = 'v:lua.vim.lsp.omnifunc' end + if + capabilities.documentRangeFormattingProvider + and vim.bo[bufnr].formatprg == '' + and vim.bo[bufnr].formatexpr == '' + then + vim.bo[bufnr].formatexpr = 'v:lua.vim.lsp.formatexpr()' + end end ---@private @@ -986,6 +1121,9 @@ function lsp.start_client(config) if vim.bo[bufnr].omnifunc == 'v:lua.vim.lsp.omnifunc' then vim.bo[bufnr].omnifunc = nil end + if vim.bo[bufnr].formatexpr == 'v:lua.vim.lsp.formatexpr()' then + vim.bo[bufnr].formatexpr = nil + end end ---@private @@ -1019,11 +1157,16 @@ function lsp.start_client(config) end) end end - + local client = active_clients[client_id] and active_clients[client_id] + or uninitialized_clients[client_id] active_clients[client_id] = nil uninitialized_clients[client_id] = nil - changetracking.reset(client_id) + -- Client can be absent if executable starts, but initialize fails + -- init/attach won't have happened + if client then + changetracking.reset(client) + end if code ~= 0 or (signal ~= 0 and signal ~= 15) then local msg = string.format('Client %s quit with exit code %s and signal %s', client_id, code, signal) @@ -1034,11 +1177,16 @@ function lsp.start_client(config) end -- Start the RPC client. - local rpc = lsp_rpc.start(cmd, cmd_args, dispatch, { - cwd = config.cmd_cwd, - env = config.cmd_env, - detached = config.detached, - }) + local rpc + if type(cmd) == 'function' then + rpc = cmd(dispatch) + else + rpc = lsp_rpc.start(cmd, cmd_args, dispatch, { + cwd = config.cmd_cwd, + env = config.cmd_env, + detached = config.detached, + }) + end -- Return nil if client fails to start if not rpc then @@ -1334,14 +1482,13 @@ function lsp.start_client(config) --- you request to stop a client which has previously been requested to --- shutdown, it will automatically escalate and force shutdown. --- - ---@param force (bool, optional) + ---@param force boolean|nil function client.stop(force) - local handle = rpc.handle - if handle:is_closing() then + if rpc.is_closing() then return end if force or not client.initialized or graceful_shutdown_failed then - handle:kill(15) + rpc.terminate() return end -- Sending a signal after a process has exited is acceptable. @@ -1350,7 +1497,7 @@ function lsp.start_client(config) rpc.notify('exit') else -- If there was an error in the shutdown request, then term to be safe. - handle:kill(15) + rpc.terminate() graceful_shutdown_failed = true end end) @@ -1362,7 +1509,7 @@ function lsp.start_client(config) ---@returns (bool) true if client is stopped or in the process of being ---stopped; false otherwise function client.is_stopped() - return rpc.handle:is_closing() + return rpc.is_closing() end ---@private @@ -1403,9 +1550,7 @@ do return true end util.buf_versions[bufnr] = changedtick - local compute_change_and_notify = - changetracking.prepare(bufnr, firstline, lastline, new_lastline) - for_each_buffer_client(bufnr, compute_change_and_notify) + changetracking.send_changes(bufnr, firstline, lastline, new_lastline) end end @@ -1681,7 +1826,7 @@ api.nvim_create_autocmd('VimLeavePre', { local send_kill = false for client_id, client in pairs(active_clients) do - local timeout = if_nil(client.config.flags.exit_timeout, 500) + local timeout = if_nil(client.config.flags.exit_timeout, false) if timeout then send_kill = true timeouts[client_id] = timeout @@ -1717,13 +1862,14 @@ api.nvim_create_autocmd('VimLeavePre', { end, }) +---@private --- Sends an async request for all active clients attached to the --- buffer. --- ---@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 handler (optional, function) See |lsp-handler| +---@param params table|nil Parameters to send to the server +---@param handler function|nil See |lsp-handler| --- If nil, follows resolution strategy defined in |lsp-handler-configuration| --- ---@returns 2-tuple: @@ -1982,29 +2128,32 @@ function lsp.formatexpr(opts) return 1 end - local start_line = vim.v.lnum - local end_line = start_line + vim.v.count - 1 - - if start_line > 0 and end_line > 0 then - local params = { - textDocument = util.make_text_document_params(), - range = { - start = { line = start_line - 1, character = 0 }, - ['end'] = { line = end_line - 1, character = 0 }, - }, - } - params.options = util.make_formatting_params().options - local client_results = - vim.lsp.buf_request_sync(0, 'textDocument/rangeFormatting', params, timeout_ms) + local start_lnum = vim.v.lnum + local end_lnum = start_lnum + vim.v.count - 1 - -- Apply the text edits from one and only one of the clients. - for client_id, response in pairs(client_results) do + if start_lnum <= 0 or end_lnum <= 0 then + return 0 + end + local bufnr = api.nvim_get_current_buf() + for _, client in pairs(lsp.get_active_clients({ bufnr = bufnr })) do + if client.supports_method('textDocument/rangeFormatting') then + local params = util.make_formatting_params() + local end_line = vim.fn.getline(end_lnum) + local end_col = util._str_utfindex_enc(end_line, nil, client.offset_encoding) + params.range = { + start = { + line = start_lnum - 1, + character = 0, + }, + ['end'] = { + line = end_lnum - 1, + character = end_col, + }, + } + local response = + client.request_sync('textDocument/rangeFormatting', params, timeout_ms, bufnr) if response.result then - vim.lsp.util.apply_text_edits( - response.result, - 0, - vim.lsp.get_client_by_id(client_id).offset_encoding - ) + vim.lsp.util.apply_text_edits(response.result, 0, client.offset_encoding) return 0 end end diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index 63f4688d94..6a070928d9 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -61,7 +61,7 @@ end --- ---@param options table|nil additional options --- - reuse_win: (boolean) Jump to existing window if buffer is already open. ---- - on_list: (function) handler for list results. See |on-list-handler| +--- - on_list: (function) handler for list results. See |lsp-on-list-handler| function M.declaration(options) local params = util.make_position_params() request_with_options('textDocument/declaration', params, options) @@ -71,7 +71,7 @@ end --- ---@param options table|nil additional options --- - reuse_win: (boolean) Jump to existing window if buffer is already open. ---- - on_list: (function) handler for list results. See |on-list-handler| +--- - on_list: (function) handler for list results. See |lsp-on-list-handler| function M.definition(options) local params = util.make_position_params() request_with_options('textDocument/definition', params, options) @@ -81,7 +81,7 @@ end --- ---@param options table|nil additional options --- - reuse_win: (boolean) Jump to existing window if buffer is already open. ---- - on_list: (function) handler for list results. See |on-list-handler| +--- - on_list: (function) handler for list results. See |lsp-on-list-handler| function M.type_definition(options) local params = util.make_position_params() request_with_options('textDocument/typeDefinition', params, options) @@ -91,7 +91,7 @@ end --- quickfix window. --- ---@param options table|nil additional options ---- - on_list: (function) handler for list results. See |on-list-handler| +--- - on_list: (function) handler for list results. See |lsp-on-list-handler| function M.implementation(options) local params = util.make_position_params() request_with_options('textDocument/implementation', params, options) @@ -503,7 +503,7 @@ end ---@param context (table) 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 |on-list-handler| +--- - on_list: (function) handler for list results. See |lsp-on-list-handler| function M.references(context, options) validate({ context = { context, 't', true } }) local params = util.make_position_params() @@ -516,7 +516,7 @@ end --- Lists all symbols in the current buffer in the quickfix window. --- ---@param options table|nil additional options ---- - on_list: (function) handler for list results. See |on-list-handler| +--- - on_list: (function) handler for list results. See |lsp-on-list-handler| function M.document_symbol(options) local params = { textDocument = util.make_text_document_params() } request_with_options('textDocument/documentSymbol', params, options) @@ -659,7 +659,7 @@ end --- ---@param query (string, optional) ---@param options table|nil additional options ---- - on_list: (function) handler for list results. See |on-list-handler| +--- - on_list: (function) handler for list results. See |lsp-on-list-handler| function M.workspace_symbol(query, options) query = query or npcall(vim.fn.input, 'Query: ') if query == nil then diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua index 913eee19a2..70f838f34d 100644 --- a/runtime/lua/vim/lsp/rpc.lua +++ b/runtime/lua/vim/lsp/rpc.lua @@ -58,12 +58,10 @@ end ---@private --- Parses an LSP Message's header --- ----@param header: The header to parse. ----@returns Parsed headers +---@param header string: The header to parse. +---@return table parsed headers local function parse_headers(header) - if type(header) ~= 'string' then - return nil - end + assert(type(header) == 'string', 'header must be a string') local headers = {} for line in vim.gsplit(header, '\r\n', true) do if line == '' then @@ -189,9 +187,9 @@ end --- Creates an RPC response object/table. --- ----@param code RPC error code defined in `vim.lsp.protocol.ErrorCodes` ----@param message (optional) arbitrary message to send to server ----@param data (optional) arbitrary data to send to server +---@param code number RPC error code defined in `vim.lsp.protocol.ErrorCodes` +---@param message string|nil arbitrary message to send to server +---@param data any|nil arbitrary data to send to server local function rpc_response_error(code, message, data) -- TODO should this error or just pick a sane error (like InternalError)? local code_name = assert(protocol.ErrorCodes[code], 'Invalid RPC error code') @@ -243,392 +241,515 @@ function default_dispatchers.on_error(code, err) local _ = log.error() and log.error('client_error:', client_errors[code], err) end ---- Starts an LSP server process and create an LSP RPC client object to ---- interact with it. Communication with the server is currently limited to stdio. ---- ----@param cmd (string) Command to start the LSP server. ----@param cmd_args (table) List of additional string arguments to pass to {cmd}. ----@param dispatchers (table, optional) Dispatchers for LSP message types. Valid ----dispatcher names are: ---- - `"notification"` ---- - `"server_request"` ---- - `"on_error"` ---- - `"on_exit"` ----@param extra_spawn_params (table, optional) Additional context for the LSP ---- server process. May contain: ---- - {cwd} (string) Working directory for the LSP server process ---- - {env} (table) Additional environment variables for LSP server process ----@returns Client RPC object. ---- ----@returns Methods: ---- - `notify()` |vim.lsp.rpc.notify()| ---- - `request()` |vim.lsp.rpc.request()| +---@private +local function create_read_loop(handle_body, on_no_chunk, on_error) + local parse_chunk = coroutine.wrap(request_parser_loop) + parse_chunk() + return function(err, chunk) + if err then + on_error(err) + return + end + + if not chunk then + if on_no_chunk then + on_no_chunk() + end + return + end + + while true do + local headers, body = parse_chunk(chunk) + if headers then + handle_body(body) + chunk = '' + else + break + end + end + end +end + +---@class RpcClient +---@field message_index number +---@field message_callbacks table +---@field notify_reply_callbacks table +---@field transport table +---@field dispatchers table + +---@class RpcClient +local Client = {} + +---@private +function Client:encode_and_send(payload) + local _ = log.debug() and log.debug('rpc.send', payload) + if self.transport.is_closing() then + return false + end + local encoded = vim.json.encode(payload) + self.transport.write(format_message_with_content_length(encoded)) + return true +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 +---@returns (bool) `true` if notification could be sent, `false` if not +function Client:notify(method, params) + return self:encode_and_send({ + jsonrpc = '2.0', + method = method, + params = params, + }) +end + +---@private +--- sends an error object to the remote LSP process. +function Client:send_response(request_id, err, result) + return self:encode_and_send({ + id = request_id, + jsonrpc = '2.0', + error = err, + result = result, + }) +end + +---@private +--- Sends a request to the LSP server and runs {callback} upon response. --- ----@returns Members: ---- - {pid} (number) The LSP server's PID. ---- - {handle} A handle for low-level interaction with the LSP server process ---- |vim.loop|. -local function start(cmd, cmd_args, dispatchers, extra_spawn_params) - local _ = log.info() - and log.info('Starting RPC client', { cmd = cmd, args = cmd_args, extra = extra_spawn_params }) +---@param method (string) The invoked LSP method +---@param params (table|nil) Parameters for the invoked LSP method +---@param callback (function) Callback to invoke +---@param notify_reply_callback (function|nil) Callback to invoke as soon as a request is no longer pending +---@returns (bool, number) `(true, message_id)` if request could be sent, `false` if not +function Client:request(method, params, callback, notify_reply_callback) validate({ - cmd = { cmd, 's' }, - cmd_args = { cmd_args, 't' }, - dispatchers = { dispatchers, 't', true }, + callback = { callback, 'f' }, + notify_reply_callback = { notify_reply_callback, 'f', true }, + }) + self.message_index = self.message_index + 1 + local message_id = self.message_index + local result = self:encode_and_send({ + id = message_id, + jsonrpc = '2.0', + method = method, + params = params, }) + local message_callbacks = self.message_callbacks + local notify_reply_callbacks = self.notify_reply_callbacks + if result then + if message_callbacks then + message_callbacks[message_id] = schedule_wrap(callback) + else + return false + end + if notify_reply_callback and notify_reply_callbacks then + notify_reply_callbacks[message_id] = schedule_wrap(notify_reply_callback) + end + return result, message_id + else + return false + end +end - if extra_spawn_params and extra_spawn_params.cwd then - assert(is_dir(extra_spawn_params.cwd), 'cwd must be a directory') +---@private +function Client:on_error(errkind, ...) + assert(client_errors[errkind]) + -- TODO what to do if this fails? + pcall(self.dispatchers.on_error, errkind, ...) +end + +---@private +function Client:pcall_handler(errkind, status, head, ...) + if not status then + self:on_error(errkind, head, ...) + return status, head end - if dispatchers then - local user_dispatchers = dispatchers - dispatchers = {} - for dispatch_name, default_dispatch in pairs(default_dispatchers) do - local user_dispatcher = user_dispatchers[dispatch_name] - if user_dispatcher then - if type(user_dispatcher) ~= 'function' then - error(string.format('dispatcher.%s must be a function', dispatch_name)) + return status, head, ... +end + +---@private +function Client:try_call(errkind, fn, ...) + return self:pcall_handler(errkind, pcall(fn, ...)) +end + +-- TODO periodically check message_callbacks for old requests past a certain +-- time and log them. This would require storing the timestamp. I could call +-- them with an error then, perhaps. + +---@private +function Client:handle_body(body) + local ok, decoded = pcall(vim.json.decode, body, { luanil = { object = true } }) + if not ok then + self:on_error(client_errors.INVALID_SERVER_JSON, decoded) + return + end + local _ = log.debug() and log.debug('rpc.receive', decoded) + + if type(decoded.method) == 'string' and decoded.id then + local err + -- 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 } + ) + if status then + if not (result or err) then + -- TODO this can be a problem if `null` is sent for result. needs vim.NIL + error( + string.format( + 'method %q: either a result or an error must be sent to the server in response', + decoded.method + ) + ) end - -- server_request is wrapped elsewhere. - if - not (dispatch_name == 'server_request' or dispatch_name == 'on_exit') -- TODO this blocks the loop exiting for some reason. - then - user_dispatcher = schedule_wrap(user_dispatcher) + 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 - dispatchers[dispatch_name] = user_dispatcher else - dispatchers[dispatch_name] = default_dispatch + -- 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) + end) + -- This works because we are expecting vim.NIL here + elseif decoded.id and (decoded.result ~= vim.NIL or decoded.error ~= vim.NIL) then + -- We sent a number, so we expect a number. + local result_id = assert(tonumber(decoded.id), 'response id must be a number') + + -- Notify the user that a response was received for the request + local notify_reply_callbacks = self.notify_reply_callbacks + local notify_reply_callback = notify_reply_callbacks and notify_reply_callbacks[result_id] + if notify_reply_callback then + validate({ + notify_reply_callback = { notify_reply_callback, 'f' }, + }) + notify_reply_callback(result_id) + notify_reply_callbacks[result_id] = nil end - else - dispatchers = default_dispatchers - end - local stdin = uv.new_pipe(false) - local stdout = uv.new_pipe(false) - local stderr = uv.new_pipe(false) + local message_callbacks = self.message_callbacks - local message_index = 0 - local message_callbacks = {} - local notify_reply_callbacks = {} + -- Do not surface RequestCancelled to users, it is RPC-internal. + if decoded.error then + local mute_error = false + if decoded.error.code == protocol.ErrorCodes.RequestCancelled then + local _ = log.debug() and log.debug('Received cancellation ack', decoded) + mute_error = true + end - local handle, pid - do - ---@private - --- Callback for |vim.loop.spawn()| Closes all streams and runs the `on_exit` dispatcher. - ---@param code (number) Exit code - ---@param signal (number) Signal that was used to terminate (if any) - local function onexit(code, signal) - stdin:close() - stdout:close() - stderr:close() - handle:close() - -- Make sure that message_callbacks/notify_reply_callbacks can be gc'd. - message_callbacks = nil - notify_reply_callbacks = nil - dispatchers.on_exit(code, signal) - end - local spawn_params = { - args = cmd_args, - stdio = { stdin, stdout, stderr }, - detached = not is_win, - } - if extra_spawn_params then - spawn_params.cwd = extra_spawn_params.cwd - spawn_params.env = env_merge(extra_spawn_params.env) - if extra_spawn_params.detached ~= nil then - spawn_params.detached = extra_spawn_params.detached + if mute_error then + -- Clear any callback since this is cancelled now. + -- This is safe to do assuming that these conditions hold: + -- - The server will not send a result callback after this cancellation. + -- - If the server sent this cancellation ACK after sending the result, the user of this RPC + -- client will ignore the result themselves. + if result_id and message_callbacks then + message_callbacks[result_id] = nil + end + return end end - handle, pid = uv.spawn(cmd, spawn_params, onexit) - if handle == nil then - local msg = string.format('Spawning language server with cmd: `%s` failed', cmd) - if string.match(pid, 'ENOENT') then - msg = msg - .. '. The language server is either not installed, missing from PATH, or not executable.' - else - msg = msg .. string.format(' with error message: %s', pid) + + local callback = message_callbacks and message_callbacks[result_id] + if callback then + message_callbacks[result_id] = nil + validate({ + callback = { callback, 'f' }, + }) + if decoded.error then + decoded.error = setmetatable(decoded.error, { + __tostring = format_rpc_error, + }) end - vim.notify(msg, vim.log.levels.WARN) - return + self:try_call( + client_errors.SERVER_RESULT_CALLBACK_ERROR, + callback, + decoded.error, + decoded.result + ) + else + self:on_error(client_errors.NO_RESULT_CALLBACK_FOUND, decoded) + local _ = log.error() and log.error('No callback found for server response id ' .. result_id) end + elseif type(decoded.method) == 'string' then + -- Notification + self:try_call( + client_errors.NOTIFICATION_HANDLER_ERROR, + self.dispatchers.notification, + decoded.method, + decoded.params + ) + else + -- Invalid server message + self:on_error(client_errors.INVALID_SERVER_MESSAGE, decoded) end +end - ---@private - --- Encodes {payload} into a JSON-RPC message and sends it to the remote - --- process. - --- - ---@param payload table - ---@returns true if the payload could be scheduled, false if the main event-loop is in the process of closing. - local function encode_and_send(payload) - local _ = log.debug() and log.debug('rpc.send', payload) - if handle == nil or handle:is_closing() then - return false - end - local encoded = vim.json.encode(payload) - stdin:write(format_message_with_content_length(encoded)) - return true - end +---@private +---@return RpcClient +local function new_client(dispatchers, transport) + local state = { + message_index = 0, + message_callbacks = {}, + notify_reply_callbacks = {}, + transport = transport, + dispatchers = dispatchers, + } + return setmetatable(state, { __index = Client }) +end - -- FIXME: DOC: Should be placed on the RPC client object returned by - -- `start()` - -- - --- Sends a notification to the LSP server. - ---@param method (string) The invoked LSP method - ---@param params (table): Parameters for the invoked LSP method - ---@returns (bool) `true` if notification could be sent, `false` if not - local function notify(method, params) - return encode_and_send({ - jsonrpc = '2.0', - method = method, - params = params, - }) +---@private +---@param client RpcClient +local function public_client(client) + local result = {} + + ---@private + function result.is_closing() + return client.transport.is_closing() end ---@private - --- sends an error object to the remote LSP process. - local function send_response(request_id, err, result) - return encode_and_send({ - id = request_id, - jsonrpc = '2.0', - error = err, - result = result, - }) + function result.terminate() + client.transport.terminate() end - -- FIXME: DOC: Should be placed on the RPC client object returned by - -- `start()` - -- --- Sends a request to the LSP server and runs {callback} upon response. --- ---@param method (string) The invoked LSP method - ---@param params (table) Parameters for the invoked LSP method + ---@param params (table|nil) Parameters for the invoked LSP method ---@param callback (function) Callback to invoke ---@param notify_reply_callback (function|nil) Callback to invoke as soon as a request is no longer pending ---@returns (bool, number) `(true, message_id)` if request could be sent, `false` if not - local function request(method, params, callback, notify_reply_callback) - validate({ - callback = { callback, 'f' }, - notify_reply_callback = { notify_reply_callback, 'f', true }, - }) - message_index = message_index + 1 - local message_id = message_index - local result = encode_and_send({ - id = message_id, - jsonrpc = '2.0', - method = method, - params = params, - }) - if result then - if message_callbacks then - message_callbacks[message_id] = schedule_wrap(callback) + function result.request(method, params, callback, notify_reply_callback) + return client:request(method, params, callback, notify_reply_callback) + end + + --- Sends a notification to the LSP server. + ---@param method (string) The invoked LSP method + ---@param params (table|nil): Parameters for the invoked LSP method + ---@returns (bool) `true` if notification could be sent, `false` if not + function result.notify(method, params) + return client:notify(method, params) + end + + return result +end + +---@private +local function merge_dispatchers(dispatchers) + if dispatchers then + local user_dispatchers = dispatchers + dispatchers = {} + for dispatch_name, default_dispatch in pairs(default_dispatchers) do + local user_dispatcher = user_dispatchers[dispatch_name] + if user_dispatcher then + if type(user_dispatcher) ~= 'function' then + error(string.format('dispatcher.%s must be a function', dispatch_name)) + end + -- server_request is wrapped elsewhere. + if + not (dispatch_name == 'server_request' or dispatch_name == 'on_exit') -- TODO this blocks the loop exiting for some reason. + then + user_dispatcher = schedule_wrap(user_dispatcher) + end + dispatchers[dispatch_name] = user_dispatcher else - return false - end - if notify_reply_callback and notify_reply_callbacks then - notify_reply_callbacks[message_id] = schedule_wrap(notify_reply_callback) + dispatchers[dispatch_name] = default_dispatch end - return result, message_id - else - return false end + else + dispatchers = default_dispatchers end + return dispatchers +end - stderr:read_start(function(_err, chunk) - if chunk then - local _ = log.error() and log.error('rpc', cmd, 'stderr', chunk) - end - end) +--- Create a LSP RPC client factory that connects via TCP to the given host +--- and port +--- +---@param host string +---@param port number +---@return function +local function connect(host, port) + return function(dispatchers) + dispatchers = merge_dispatchers(dispatchers) + local tcp = uv.new_tcp() + local closing = false + local transport = { + write = function(msg) + tcp:write(msg) + end, + is_closing = function() + return closing + end, + terminate = function() + if not closing then + closing = true + tcp:shutdown() + tcp:close() + dispatchers.on_exit(0, 0) + end + end, + } + local client = new_client(dispatchers, transport) + tcp:connect(host, port, function(err) + if err then + vim.schedule(function() + vim.notify( + string.format('Could not connect to %s:%s, reason: %s', host, port, vim.inspect(err)), + vim.log.levels.WARN + ) + end) + return + end + local handle_body = function(body) + client:handle_body(body) + end + tcp:read_start(create_read_loop(handle_body, transport.terminate, function(read_err) + client:on_error(client_errors.READ_ERROR, read_err) + end)) + end) - ---@private - local function on_error(errkind, ...) - assert(client_errors[errkind]) - -- TODO what to do if this fails? - pcall(dispatchers.on_error, errkind, ...) - end - ---@private - local function pcall_handler(errkind, status, head, ...) - if not status then - on_error(errkind, head, ...) - return status, head - end - return status, head, ... - end - ---@private - local function try_call(errkind, fn, ...) - return pcall_handler(errkind, pcall(fn, ...)) + return public_client(client) end +end - -- TODO periodically check message_callbacks for old requests past a certain - -- time and log them. This would require storing the timestamp. I could call - -- them with an error then, perhaps. +--- Starts an LSP server process and create an LSP RPC client object to +--- interact with it. Communication with the server is currently limited to stdio. +--- +---@param cmd (string) Command to start the LSP server. +---@param cmd_args (table) List of additional string arguments to pass to {cmd}. +---@param dispatchers table|nil Dispatchers for LSP message types. Valid +---dispatcher names are: +--- - `"notification"` +--- - `"server_request"` +--- - `"on_error"` +--- - `"on_exit"` +---@param extra_spawn_params table|nil Additional context for the LSP +--- server process. May contain: +--- - {cwd} (string) Working directory for the LSP server process +--- - {env} (table) Additional environment variables for LSP server process +---@returns Client RPC object. +--- +---@returns Methods: +--- - `notify()` |vim.lsp.rpc.notify()| +--- - `request()` |vim.lsp.rpc.request()| +--- - `is_closing()` returns a boolean indicating if the RPC is closing. +--- - `terminate()` terminates the RPC client. +local function start(cmd, cmd_args, dispatchers, extra_spawn_params) + local _ = log.info() + and log.info('Starting RPC client', { cmd = cmd, args = cmd_args, extra = extra_spawn_params }) + validate({ + cmd = { cmd, 's' }, + cmd_args = { cmd_args, 't' }, + dispatchers = { dispatchers, 't', true }, + }) - ---@private - local function handle_body(body) - local ok, decoded = pcall(vim.json.decode, body, { luanil = { object = true } }) - if not ok then - on_error(client_errors.INVALID_SERVER_JSON, decoded) - return - end - local _ = log.debug() and log.debug('rpc.receive', decoded) - - if type(decoded.method) == 'string' and decoded.id then - local err - -- 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 = try_call( - client_errors.SERVER_REQUEST_HANDLER_ERROR, - dispatchers.server_request, - decoded.method, - decoded.params - ) - local _ = log.debug() - and log.debug( - 'server_request: callback result', - { status = status, result = result, err = err } - ) - if status then - if not (result or err) then - -- TODO this can be a problem if `null` is sent for result. needs vim.NIL - 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 - send_response(decoded.id, err, result) - end) - -- This works because we are expecting vim.NIL here - elseif decoded.id and (decoded.result ~= vim.NIL or decoded.error ~= vim.NIL) then - -- We sent a number, so we expect a number. - local result_id = tonumber(decoded.id) - - -- Notify the user that a response was received for the request - local notify_reply_callback = notify_reply_callbacks and notify_reply_callbacks[result_id] - if notify_reply_callback then - validate({ - notify_reply_callback = { notify_reply_callback, 'f' }, - }) - notify_reply_callback(result_id) - notify_reply_callbacks[result_id] = nil - end + if extra_spawn_params and extra_spawn_params.cwd then + assert(is_dir(extra_spawn_params.cwd), 'cwd must be a directory') + end - -- Do not surface RequestCancelled to users, it is RPC-internal. - if decoded.error then - local mute_error = false - if decoded.error.code == protocol.ErrorCodes.RequestCancelled then - local _ = log.debug() and log.debug('Received cancellation ack', decoded) - mute_error = true - end + dispatchers = merge_dispatchers(dispatchers) + local stdin = uv.new_pipe(false) + local stdout = uv.new_pipe(false) + local stderr = uv.new_pipe(false) + local handle, pid - if mute_error then - -- Clear any callback since this is cancelled now. - -- This is safe to do assuming that these conditions hold: - -- - The server will not send a result callback after this cancellation. - -- - If the server sent this cancellation ACK after sending the result, the user of this RPC - -- client will ignore the result themselves. - if result_id and message_callbacks then - message_callbacks[result_id] = nil - end - return - end + local client = new_client(dispatchers, { + write = function(msg) + stdin:write(msg) + end, + is_closing = function() + return handle == nil or handle:is_closing() + end, + terminate = function() + if handle then + handle:kill(15) end + end, + }) - local callback = message_callbacks and message_callbacks[result_id] - if callback then - message_callbacks[result_id] = nil - validate({ - callback = { callback, 'f' }, - }) - if decoded.error then - decoded.error = setmetatable(decoded.error, { - __tostring = format_rpc_error, - }) - end - try_call( - client_errors.SERVER_RESULT_CALLBACK_ERROR, - callback, - decoded.error, - decoded.result - ) - else - on_error(client_errors.NO_RESULT_CALLBACK_FOUND, decoded) - local _ = log.error() - and log.error('No callback found for server response id ' .. result_id) - end - elseif type(decoded.method) == 'string' then - -- Notification - try_call( - client_errors.NOTIFICATION_HANDLER_ERROR, - dispatchers.notification, - decoded.method, - decoded.params - ) + ---@private + --- Callback for |vim.loop.spawn()| Closes all streams and runs the `on_exit` dispatcher. + ---@param code (number) Exit code + ---@param signal (number) Signal that was used to terminate (if any) + local function onexit(code, signal) + stdin:close() + stdout:close() + stderr:close() + handle:close() + dispatchers.on_exit(code, signal) + end + local spawn_params = { + args = cmd_args, + stdio = { stdin, stdout, stderr }, + detached = not is_win, + } + if extra_spawn_params then + spawn_params.cwd = extra_spawn_params.cwd + spawn_params.env = env_merge(extra_spawn_params.env) + if extra_spawn_params.detached ~= nil then + spawn_params.detached = extra_spawn_params.detached + end + end + handle, pid = uv.spawn(cmd, spawn_params, onexit) + if handle == nil then + stdin:close() + stdout:close() + stderr:close() + local msg = string.format('Spawning language server with cmd: `%s` failed', cmd) + if string.match(pid, 'ENOENT') then + msg = msg + .. '. The language server is either not installed, missing from PATH, or not executable.' else - -- Invalid server message - on_error(client_errors.INVALID_SERVER_MESSAGE, decoded) + msg = msg .. string.format(' with error message: %s', pid) end + vim.notify(msg, vim.log.levels.WARN) + return end - local request_parser = coroutine.wrap(request_parser_loop) - request_parser() - stdout:read_start(function(err, chunk) - if err then - -- TODO better handling. Can these be intermittent errors? - on_error(client_errors.READ_ERROR, err) - return - end - -- This should signal that we are done reading from the client. - if not chunk then - return - end - -- Flush anything in the parser by looping until we don't get a result - -- anymore. - while true do - local headers, body = request_parser(chunk) - -- If we successfully parsed, then handle the response. - if headers then - handle_body(body) - -- Set chunk to empty so that we can call request_parser to get - -- anything existing in the parser to flush. - chunk = '' - else - break - end + stderr:read_start(function(_, chunk) + if chunk then + local _ = log.error() and log.error('rpc', cmd, 'stderr', chunk) end end) - return { - pid = pid, - handle = handle, - request = request, - notify = notify, - } + local handle_body = function(body) + client:handle_body(body) + end + stdout:read_start(create_read_loop(handle_body, nil, function(err) + client:on_error(client_errors.READ_ERROR, err) + end)) + + return public_client(client) end return { start = start, + connect = connect, rpc_response_error = rpc_response_error, format_rpc_error = format_rpc_error, client_errors = client_errors, + create_read_loop = create_read_loop, } -- vim:sw=2 ts=2 et diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index eac21db386..283099bbcf 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -113,7 +113,7 @@ end --- Convert byte index to `encoding` index. --- Convenience wrapper around vim.str_utfindex ---@param line string line to be indexed ----@param index number byte index (utf-8), or `nil` for length +---@param index number|nil byte index (utf-8), or `nil` for length ---@param encoding string utf-8|utf-16|utf-32|nil defaults to utf-16 ---@return number `encoding` index of `index` in `line` function M._str_utfindex_enc(line, index, encoding) diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua index d6c3e25b3b..e1b4ed4ea9 100644 --- a/runtime/lua/vim/shared.lua +++ b/runtime/lua/vim/shared.lua @@ -526,7 +526,7 @@ function vim.trim(s) return s:match('^%s*(.*%S)') or '' end ---- Escapes magic chars in a Lua pattern. +--- Escapes magic chars in |lua-patterns|. --- ---@see https://github.com/rxi/lume ---@param s string String to escape diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index 70f2c425ed..6431162799 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -118,4 +118,127 @@ function M.get_string_parser(str, lang, opts) return LanguageTree.new(str, lang, opts) end +--- Determines whether a node is the ancestor of another +--- +---@param dest table the possible ancestor +---@param source table the possible descendant node +--- +---@returns (boolean) True if dest is an ancestor of source +function M.is_ancestor(dest, source) + if not (dest and source) then + return false + end + + local current = source + while current ~= nil do + if current == dest then + return true + end + + current = current:parent() + end + + return false +end + +--- Get the node's range or unpack a range table +--- +---@param node_or_range table +--- +---@returns start_row, start_col, end_row, end_col +function M.get_node_range(node_or_range) + if type(node_or_range) == 'table' then + return unpack(node_or_range) + else + return node_or_range:range() + end +end + +---Determines whether (line, col) position is in node range +--- +---@param node Node defining the range +---@param line A line (0-based) +---@param col A column (0-based) +function M.is_in_node_range(node, line, col) + local start_line, start_col, end_line, end_col = M.get_node_range(node) + if line >= start_line and line <= end_line then + if line == start_line and line == end_line then + return col >= start_col and col < end_col + elseif line == start_line then + return col >= start_col + elseif line == end_line then + return col < end_col + else + return true + end + else + return false + end +end + +---Determines if a node contains a range +---@param node table The node +---@param range table The range +--- +---@returns (boolean) True if the node contains the range +function M.node_contains(node, range) + local start_row, start_col, end_row, end_col = node:range() + local start_fits = start_row < range[1] or (start_row == range[1] and start_col <= range[2]) + local end_fits = end_row > range[3] or (end_row == range[3] and end_col >= range[4]) + + return start_fits and end_fits +end + +---Gets a list of captures for a given cursor position +---@param bufnr number The buffer number +---@param row number The position row +---@param col number The position column +--- +---@returns (table) A table of captures +function M.get_captures_at_position(bufnr, row, col) + if bufnr == 0 then + bufnr = a.nvim_get_current_buf() + end + local buf_highlighter = M.highlighter.active[bufnr] + + if not buf_highlighter then + return {} + end + + local matches = {} + + buf_highlighter.tree:for_each_tree(function(tstree, tree) + if not tstree then + return + end + + local root = tstree:root() + local root_start_row, _, root_end_row, _ = root:range() + + -- Only worry about trees within the line range + if root_start_row > row or root_end_row < row then + return + end + + local q = buf_highlighter:get_query(tree:lang()) + + -- Some injected languages may not have highlight queries. + if not q:query() then + return + end + + local iter = q:query():iter_captures(root, buf_highlighter.bufnr, row, row + 1) + + for capture, node, metadata in iter do + 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, priority = metadata.priority }) + end + end + end + end, true) + return matches +end + return M diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua index e27a5fa9c3..1f242b0fdd 100644 --- a/runtime/lua/vim/treesitter/highlighter.lua +++ b/runtime/lua/vim/treesitter/highlighter.lua @@ -12,105 +12,18 @@ TSHighlighterQuery.__index = TSHighlighterQuery local ns = a.nvim_create_namespace('treesitter/highlighter') -local _default_highlights = {} -local _link_default_highlight_once = function(from, to) - if not _default_highlights[from] then - _default_highlights[from] = true - a.nvim_set_hl(0, from, { link = to, default = true }) - end - - return from -end - --- If @definition.special does not exist use @definition instead -local subcapture_fallback = { - __index = function(self, capture) - local rtn - local shortened = capture - while not rtn and shortened do - shortened = shortened:match('(.*)%.') - rtn = shortened and rawget(self, shortened) - end - rawset(self, capture, rtn or '__notfound') - return rtn - end, -} - -TSHighlighter.hl_map = setmetatable({ - ['error'] = 'Error', - ['text.underline'] = 'Underlined', - ['todo'] = 'Todo', - ['debug'] = 'Debug', - - -- Miscs - ['comment'] = 'Comment', - ['punctuation.delimiter'] = 'Delimiter', - ['punctuation.bracket'] = 'Delimiter', - ['punctuation.special'] = 'Delimiter', - - -- Constants - ['constant'] = 'Constant', - ['constant.builtin'] = 'Special', - ['constant.macro'] = 'Define', - ['define'] = 'Define', - ['macro'] = 'Macro', - ['string'] = 'String', - ['string.regex'] = 'String', - ['string.escape'] = 'SpecialChar', - ['character'] = 'Character', - ['character.special'] = 'SpecialChar', - ['number'] = 'Number', - ['boolean'] = 'Boolean', - ['float'] = 'Float', - - -- Functions - ['function'] = 'Function', - ['function.special'] = 'Function', - ['function.builtin'] = 'Special', - ['function.macro'] = 'Macro', - ['parameter'] = 'Identifier', - ['method'] = 'Function', - ['field'] = 'Identifier', - ['property'] = 'Identifier', - ['constructor'] = 'Special', - - -- Keywords - ['conditional'] = 'Conditional', - ['repeat'] = 'Repeat', - ['label'] = 'Label', - ['operator'] = 'Operator', - ['keyword'] = 'Keyword', - ['exception'] = 'Exception', - - ['type'] = 'Type', - ['type.builtin'] = 'Type', - ['type.qualifier'] = 'Type', - ['type.definition'] = 'Typedef', - ['storageclass'] = 'StorageClass', - ['structure'] = 'Structure', - ['include'] = 'Include', - ['preproc'] = 'PreProc', -}, subcapture_fallback) - ----@private -local function is_highlight_name(capture_name) - local firstc = string.sub(capture_name, 1, 1) - return firstc ~= string.lower(firstc) -end - ---@private function TSHighlighterQuery.new(lang, query_string) local self = setmetatable({}, { __index = TSHighlighterQuery }) self.hl_cache = setmetatable({}, { __index = function(table, capture) - local hl, is_vim_highlight = self:_get_hl_from_capture(capture) - if not is_vim_highlight then - hl = _link_default_highlight_once(lang .. hl, hl) + local name = self._query.captures[capture] + local id = 0 + if not vim.startswith(name, '_') then + id = a.nvim_get_hl_id_by_name('@' .. name .. '.' .. lang) end - local id = a.nvim_get_hl_id_by_name(hl) - rawset(table, capture, id) return id end, @@ -130,20 +43,6 @@ function TSHighlighterQuery:query() return self._query end ----@private ---- Get the hl from capture. ---- Returns a tuple { highlight_name: string, is_builtin: bool } -function TSHighlighterQuery:_get_hl_from_capture(capture) - local name = self._query.captures[capture] - - if is_highlight_name(name) then - -- From "Normal.left" only keep "Normal" - return vim.split(name, '.', true)[1], true - else - return TSHighlighter.hl_map[name] or 0, false - end -end - --- Creates a new highlighter using @param tree --- ---@param tree The language tree to use for highlighting diff --git a/runtime/lua/vim/treesitter/language.lua b/runtime/lua/vim/treesitter/language.lua index dfb6f5be84..d14b825603 100644 --- a/runtime/lua/vim/treesitter/language.lua +++ b/runtime/lua/vim/treesitter/language.lua @@ -6,10 +6,11 @@ local M = {} --- --- Parsers are searched in the `parser` runtime directory. --- ----@param lang The language the parser should parse ----@param path Optional path the parser is located at ----@param silent Don't throw an error if language not found -function M.require_language(lang, path, silent) +---@param lang string The language the parser should parse +---@param path string|nil Optional path the parser is located at +---@param silent boolean|nil Don't throw an error if language not found +---@param symbol_name string|nil Internal symbol name for the language to load +function M.require_language(lang, path, silent, symbol_name) if vim._ts_has_language(lang) then return true end @@ -21,7 +22,6 @@ function M.require_language(lang, path, silent) return false end - -- TODO(bfredl): help tag? error("no parser for '" .. lang .. "' language, see :help treesitter-parsers") end path = paths[1] @@ -29,10 +29,10 @@ function M.require_language(lang, path, silent) if silent then return pcall(function() - vim._ts_add_language(path, lang) + vim._ts_add_language(path, lang, symbol_name) end) else - vim._ts_add_language(path, lang) + vim._ts_add_language(path, lang, symbol_name) end return true diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index 4d3b0631a2..70317a9f94 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -299,7 +299,7 @@ function LanguageTree:included_regions() end ---@private -local function get_node_range(node, id, metadata) +local function get_range_from_metadata(node, id, metadata) if metadata[id] and metadata[id].range then return metadata[id].range end @@ -362,7 +362,7 @@ function LanguageTree:_get_injections() elseif name == 'combined' then combined = true elseif name == 'content' and #ranges == 0 then - table.insert(ranges, get_node_range(node, id, metadata)) + table.insert(ranges, get_range_from_metadata(node, id, metadata)) -- Ignore any tags that start with "_" -- Allows for other tags to be used in matches elseif string.sub(name, 1, 1) ~= '_' then @@ -371,7 +371,7 @@ function LanguageTree:_get_injections() end if #ranges == 0 then - table.insert(ranges, get_node_range(node, id, metadata)) + table.insert(ranges, get_range_from_metadata(node, id, metadata)) end end end @@ -549,6 +549,44 @@ function LanguageTree:contains(range) return false end +--- Gets the tree that contains {range} +--- +---@param range table A text range +---@param opts table Options table +---@param opts.ignore_injections boolean (default true) Ignore injected languages. +function LanguageTree:tree_for_range(range, opts) + opts = opts or {} + local ignore = vim.F.if_nil(opts.ignore_injections, true) + + if not ignore then + for _, child in pairs(self._children) do + for _, tree in pairs(child:trees()) do + if tree_contains(tree, range) then + return tree + end + end + end + end + + for _, tree in pairs(self._trees) do + if tree_contains(tree, range) then + return tree + end + end + + return nil +end + +--- Gets the smallest named node that contains {range} +--- +---@param range table A text range +---@param opts table Options table +---@param opts.ignore_injections boolean (default true) Ignore injected languages. +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)) +end + --- Gets the appropriate language that contains {range} --- ---@param range A text range, see |LanguageTree:contains| diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 103e85abfd..697e2e7691 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -181,9 +181,14 @@ end --- Gets the text corresponding to a given node --- ----@param node the node ----@param source The buffer or string from which the node is extracted -function M.get_node_text(node, source) +---@param node table The node +---@param source table The buffer or string from which the node is extracted +---@param opts table Optional parameters. +--- - concat: (boolean default true) Concatenate result in a string +function M.get_node_text(node, source, opts) + opts = opts or {} + local concat = vim.F.if_nil(opts.concat, true) + local start_row, start_col, start_byte = node:start() local end_row, end_col, end_byte = node:end_() @@ -210,7 +215,7 @@ function M.get_node_text(node, source) end end - return table.concat(lines, '\n') + return concat and table.concat(lines, '\n') or lines elseif type(source) == 'string' then return source:sub(start_byte + 1, end_byte) end diff --git a/runtime/pack/dist/opt/matchit/doc/matchit.txt b/runtime/pack/dist/opt/matchit/doc/matchit.txt index 553359ffaf..45033ce3f1 100644 --- a/runtime/pack/dist/opt/matchit/doc/matchit.txt +++ b/runtime/pack/dist/opt/matchit/doc/matchit.txt @@ -243,9 +243,6 @@ Examples: comment character) you can > :let b:match_skip = 'r:\(^\|[^\\]\)\(\\\\\)*%' < - See the $VIMRUNTIME/ftplugin/vim.vim for an example that uses both - syntax and a regular expression. - ============================================================================== 4. Supporting a New Language *matchit-newlang* *b:match_words* diff --git a/runtime/plugin/matchparen.vim b/runtime/plugin/matchparen.vim index cc4f38f669..ce2225c5f8 100644 --- a/runtime/plugin/matchparen.vim +++ b/runtime/plugin/matchparen.vim @@ -5,8 +5,7 @@ " Exit quickly when: " - this plugin was already loaded (or disabled) " - when 'compatible' is set -" - the "CursorMoved" autocmd event is not available. -if exists("g:loaded_matchparen") || &cp || !exists("##CursorMoved") +if exists("g:loaded_matchparen") || &cp finish endif let g:loaded_matchparen = 1 @@ -20,7 +19,7 @@ endif augroup matchparen " Replace all matchparen autocommands - autocmd! CursorMoved,CursorMovedI,WinEnter * call s:Highlight_Matching_Pair() + autocmd! CursorMoved,CursorMovedI,WinEnter,WinScrolled * call s:Highlight_Matching_Pair() autocmd! WinLeave * call s:Remove_Matches() if exists('##TextChanged') autocmd! TextChanged,TextChangedI * call s:Highlight_Matching_Pair() diff --git a/runtime/syntax/README.txt b/runtime/syntax/README.txt index d88bd7ed5d..d6a86e5ca9 100644 --- a/runtime/syntax/README.txt +++ b/runtime/syntax/README.txt @@ -13,6 +13,9 @@ synload.vim Contains autocommands to load a language file when a certain nosyntax.vim Used for the ":syntax off" command. Undo the loading of synload.vim. +The "shared" directory contains generated files and what is used by more than +one syntax. + A few special files: diff --git a/runtime/syntax/abaqus.vim b/runtime/syntax/abaqus.vim index db2717f818..e6f025d8f4 100644 --- a/runtime/syntax/abaqus.vim +++ b/runtime/syntax/abaqus.vim @@ -1,6 +1,6 @@ " Vim syntax file " Language: Abaqus finite element input file (www.hks.com) -" Maintainer: Carl Osterwisch <osterwischc@asme.org> +" Maintainer: Carl Osterwisch <costerwi@gmail.com> " Last Change: 2002 Feb 24 " Remark: Huge improvement in folding performance--see filetype plugin @@ -28,8 +28,7 @@ syn match abaqusBadLine "^\s\+\*.*" display hi def link abaqusComment Comment hi def link abaqusKeyword Statement hi def link abaqusParameter Identifier -hi def link abaqusValue Constant -hi def link abaqusBadLine Error - +hi def link abaqusValue Constant +hi def link abaqusBadLine Error let b:current_syntax = "abaqus" diff --git a/runtime/syntax/plsql.vim b/runtime/syntax/plsql.vim index 7b36c0a180..21681d59e4 100644 --- a/runtime/syntax/plsql.vim +++ b/runtime/syntax/plsql.vim @@ -4,12 +4,13 @@ " Previous Maintainer: Jeff Lanzarotta (jefflanzarotta at yahoo dot com) " Previous Maintainer: C. Laurence Gonsalves (clgonsal@kami.com) " URL: https://github.com/lee-lindley/vim_plsql_syntax -" Last Change: April 28, 2022 +" Last Change: Aug 21, 2022 " History Lee Lindley (lee dot lindley at gmail dot com) +" use get with default 0 instead of exists per Bram suggestion +" make procedure folding optional " updated to 19c keywords. refined quoting. " separated reserved, non-reserved keywords and functions -" revised folding, giving up on procedure folding due to issue -" with multiple ways to enter <begin>. +" revised folding " Eugene Lysyonok (lysyonok at inbox ru) " Added folding. " Geoff Evans & Bill Pribyl (bill at plnet dot org) @@ -23,12 +24,19 @@ " To enable folding (It does setlocal foldmethod=syntax) " let plsql_fold = 1 " +" To disable folding procedure/functions (recommended if you habitually +" do not put the method name on the END statement) +" let plsql_disable_procedure_fold = 1 +" " From my vimrc file -- turn syntax and syntax folding on, " associate file suffixes as plsql, open all the folds on file open +" syntax enable " let plsql_fold = 1 " au BufNewFile,BufRead *.sql,*.pls,*.tps,*.tpb,*.pks,*.pkb,*.pkg,*.trg set filetype=plsql " au BufNewFile,BufRead *.sql,*.pls,*.tps,*.tpb,*.pks,*.pkb,*.pkg,*.trg syntax on " au Syntax plsql normal zR +" au Syntax plsql set foldcolumn=2 "optional if you want to see choosable folds on the left + if exists("b:current_syntax") finish @@ -49,12 +57,12 @@ syn match plsqlIdentifier "[a-z][a-z0-9$_#]*" syn match plsqlHostIdentifier ":[a-z][a-z0-9$_#]*" " When wanted, highlight the trailing whitespace. -if exists("plsql_space_errors") - if !exists("plsql_no_trail_space_error") +if get(g:,"plsql_space_errors",0) == 1 + if get(g:,"plsql_no_trail_space_error",0) == 0 syn match plsqlSpaceError "\s\+$" endif - if !exists("plsql_no_tab_space_error") + if get(g:,"plsql_no_tab_space_error",0) == 0 syn match plsqlSpaceError " \+\t"me=e-1 endif endif @@ -134,7 +142,8 @@ syn keyword plsqlKeyword CPU_TIME CRASH CREATE_FILE_DEST CREATE_STORED_OUTLINES syn keyword plsqlKeyword CREDENTIALS CRITICAL CROSS CROSSEDITION CSCONVERT CUBE CUBE_AJ CUBE_GB CUBE_SJ syn keyword plsqlKeyword CUME_DIST CUME_DISTM CURRENT CURRENTV CURRENT_DATE CURRENT_INSTANCE CURRENT_PARTSET_KEY syn keyword plsqlKeyword CURRENT_SCHEMA CURRENT_SHARD_KEY CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER -syn keyword plsqlKeyword CURSOR CURSOR_SHARING_EXACT CURSOR_SPECIFIC_SEGMENT CV CYCLE DAGG_OPTIM_GSETS +syn match plsqlKeyword "\<CURSOR\>" +syn keyword plsqlKeyword CURSOR_SHARING_EXACT CURSOR_SPECIFIC_SEGMENT CV CYCLE DAGG_OPTIM_GSETS syn keyword plsqlKeyword DANGLING DATA DATABASE DATABASES DATAFILE DATAFILES DATAMOVEMENT DATAOBJNO syn keyword plsqlKeyword DATAOBJ_TO_MAT_PARTITION DATAOBJ_TO_PARTITION DATAPUMP DATASTORE DATA_LINK_DML syn keyword plsqlKeyword DATA_SECURITY_REWRITE_LIMIT DATA_VALIDATE DATE_MODE DAYS DBA DBA_RECYCLEBIN @@ -515,7 +524,7 @@ syn match plsqlFunction "\.DELETE\>"hs=s+1 syn match plsqlFunction "\.PREV\>"hs=s+1 syn match plsqlFunction "\.NEXT\>"hs=s+1 -if exists("plsql_legacy_sql_keywords") +if get(g:,"plsql_legacy_sql_keywords",0) == 1 " Some of Oracle's SQL keywords. syn keyword plsqlSQLKeyword ABORT ACCESS ACCESSED ADD AFTER ALL ALTER AND ANY syn keyword plsqlSQLKeyword ASC ATTRIBUTE AUDIT AUTHORIZATION AVG BASE_TABLE @@ -565,7 +574,7 @@ syn keyword plsqlException SUBSCRIPT_OUTSIDE_LIMIT SYS_INVALID_ROWID syn keyword plsqlException TIMEOUT_ON_RESOURCE TOO_MANY_ROWS VALUE_ERROR syn keyword plsqlException ZERO_DIVIDE -if exists("plsql_highlight_triggers") +if get(g:,"plsql_highlight_triggers",0) == 1 syn keyword plsqlTrigger INSERTING UPDATING DELETING endif @@ -576,7 +585,7 @@ syn match plsqlISAS "\<\(IS\|AS\)\>" " Various types of comments. syntax region plsqlCommentL start="--" skip="\\$" end="$" keepend extend contains=@plsqlCommentGroup,plsqlSpaceError -if exists("plsql_fold") +if get(g:,"plsql_fold",0) == 1 syntax region plsqlComment \ start="/\*" end="\*/" \ extend @@ -612,7 +621,7 @@ syn region plsqlQuotedIdentifier matchgroup=plsqlOperator start=+n\?"+ end=+ syn cluster plsqlIdentifiers contains=plsqlIdentifier,plsqlQuotedIdentifier " quoted string literals -if exists("plsql_fold") +if get(g:,"plsql_fold",0) == 1 syn region plsqlStringLiteral matchgroup=plsqlOperator start=+n\?'+ skip=+''+ end=+'+ fold keepend extend syn region plsqlStringLiteral matchgroup=plsqlOperator start=+n\?q'\z([^[(<{]\)+ end=+\z1'+ fold keepend extend syn region plsqlStringLiteral matchgroup=plsqlOperator start=+n\?q'<+ end=+>'+ fold keepend extend @@ -639,10 +648,10 @@ syn match plsqlAttribute "%\(BULK_EXCEPTIONS\|BULK_ROWCOUNT\|ISOPEN\|FOUND\|NOTF " This'll catch mis-matched close-parens. syn cluster plsqlParenGroup contains=plsqlParenError,@plsqlCommentGroup,plsqlCommentSkip,plsqlIntLiteral,plsqlFloatLiteral,plsqlNumbersCom -if exists("plsql_bracket_error") +if get(g:,"plsql_bracket_error",0) == 1 " I suspect this code was copied from c.vim and never properly considered. Do " we even use braces or brackets in sql or pl/sql? - if exists("plsql_fold") + if get(g:,"plsql_fold",0) == 1 syn region plsqlParen start='(' end=')' contains=ALLBUT,@plsqlParenGroup,plsqlErrInBracket fold keepend extend transparent else syn region plsqlParen transparent start='(' end=')' contains=ALLBUT,@plsqlParenGroup,plsqlErrInBracket @@ -652,7 +661,7 @@ if exists("plsql_bracket_error") syn region plsqlBracket transparent start='\[' end=']' contains=ALLBUT,@plsqlParenGroup,plsqlErrInParen syn match plsqlErrInBracket contained "[);{}]" else - if exists("plsql_fold") + if get(g:,"plsql_fold",0) == 1 syn region plsqlParen start='(' end=')' contains=ALLBUT,@plsqlParenGroup,plsqlErrInParen fold keepend extend transparent else syn region plsqlParen transparent start='(' end=')' contains=ALLBUT,@plsqlParenGroup,plsqlErrInParen @@ -673,12 +682,12 @@ syn match plsqlConditional "\<END\>\_s\+\<IF\>" syn match plsqlCase "\<END\>\_s\+\<CASE\>" syn match plsqlCase "\<CASE\>" -if exists("plsql_fold") +if get(g:,"plsql_fold",0) == 1 setlocal foldmethod=syntax syn sync fromstart syn cluster plsqlProcedureGroup contains=plsqlProcedure - syn cluster plsqlOnlyGroup contains=@plsqlProcedure,plsqlConditionalBlock,plsqlLoopBlock,plsqlBlock + syn cluster plsqlOnlyGroup contains=@plsqlProcedure,plsqlConditionalBlock,plsqlLoopBlock,plsqlBlock,plsqlCursor syntax region plsqlUpdateSet \ start="\(\<update\>\_s\+\(\<set\>\)\@![a-z][a-z0-9$_#]*\_s\+\(\(\<set\>\)\@![a-z][a-z0-9$_#]*\_s\+\)\?\)\|\(\<when\>\_s\+\<matched\>\_s\+\<then\>\_s\+\<update\>\_s\+\)\<set\>" @@ -698,24 +707,40 @@ if exists("plsql_fold") \ transparent \ contains=ALLBUT,@plsqlOnlyGroup,plsqlUpdateSet - " this is brute force and requires you have the procedure/function name in the END - " statement. ALthough Oracle makes it optional, we cannot. If you do not - " have it, then you can fold the BEGIN/END block of the procedure but not - " the specification of it (other than a paren group). You also cannot fold - " BEGIN/END blocks in the procedure body. Local procedures will fold as - " long as the END statement includes the procedure/function name. - " As for why we cannot make it work any other way, I don't know. It is - " something to do with both plsqlBlock and plsqlProcedure both consuming BEGIN and END, - " even if we use a lookahead for one of them. - syntax region plsqlProcedure - "\ start="\(create\(\_s\+or\_s\+replace\)\?\_s\+\)\?\<\(procedure\|function\)\>\_s\+\z([a-z][a-z0-9$_#]*\)" - \ start="\<\(procedure\|function\)\>\_s\+\(\z([a-z][a-z0-9$_#]*\)\)\([^;]\|\n\)\{-}\<\(is\|as\)\>\_.\{-}\(\<end\>\_s\+\2\_s*;\)\@=" - \ end="\<end\>\_s\+\z1\_s*;" + if get(g:,"plsql_disable_procedure_fold",0) == 0 + " this is brute force and requires you have the procedure/function name in the END + " statement. ALthough Oracle makes it optional, we cannot. If you do not + " have it, then you can fold the BEGIN/END block of the procedure but not + " the specification of it (other than a paren group). You also cannot fold + " BEGIN/END blocks in the procedure body. Local procedures will fold as + " long as the END statement includes the procedure/function name. + " As for why we cannot make it work any other way, I don't know. It is + " something to do with both plsqlBlock and plsqlProcedure both consuming BEGIN and END, + " even if we use a lookahead for one of them. + " + " If you habitualy do not put the method name in the END statement, + " this can be expensive because it searches to end of file on every + " procedure/function declaration + " + "\ start="\(create\(\_s\+or\_s\+replace\)\?\_s\+\)\?\<\(procedure\|function\)\>\_s\+\z([a-z][a-z0-9$_#]*\)" + syntax region plsqlProcedure + \ start="\<\(procedure\|function\)\>\_s\+\(\z([a-z][a-z0-9$_#]*\)\)\([^;]\|\n\)\{-}\<\(is\|as\)\>\_.\{-}\(\<end\>\_s\+\2\_s*;\)\@=" + \ end="\<end\>\_s\+\z1\_s*;" + \ fold + \ keepend + \ extend + \ transparent + \ contains=ALLBUT,plsqlBlock + endif + + syntax region plsqlCursor + \ start="\<cursor\>\_s\+[a-z][a-z0-9$_#]*\(\_s*([^)]\+)\)\?\(\_s\+return\_s\+\S\+\)\?\_s\+is" + \ end=";" \ fold \ keepend \ extend \ transparent - \ contains=ALLBUT,plsqlBlock + \ contains=ALLBUT,@plsqlOnlyGroup syntax region plsqlBlock \ start="\<begin\>" @@ -802,7 +827,7 @@ hi def link plsqlTrigger Function hi def link plsqlTypeAttribute StorageClass hi def link plsqlTodo Todo " to be able to change them after loading, need override whether defined or not -if exists("plsql_legacy_sql_keywords") +if get(g:,"plsql_legacy_sql_keywords",0) == 1 hi link plsqlSQLKeyword Function hi link plsqlSymbol Normal hi link plsqlParen Normal diff --git a/runtime/syntax/shared/README.txt b/runtime/syntax/shared/README.txt new file mode 100644 index 0000000000..f519d44faf --- /dev/null +++ b/runtime/syntax/shared/README.txt @@ -0,0 +1,2 @@ +This directory "runtime/syntax/shared" contains Vim script files that are +generated or used by more then one syntax file. diff --git a/runtime/syntax/typescriptcommon.vim b/runtime/syntax/shared/typescriptcommon.vim index ef362fc721..ef362fc721 100644 --- a/runtime/syntax/typescriptcommon.vim +++ b/runtime/syntax/shared/typescriptcommon.vim diff --git a/runtime/syntax/swayconfig.vim b/runtime/syntax/swayconfig.vim index 2abfa38bd9..d9f31da47b 100644 --- a/runtime/syntax/swayconfig.vim +++ b/runtime/syntax/swayconfig.vim @@ -2,8 +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.0 -" Last Change: 2022 Jun 07 +" Version: 0.11.1 +" Last Change: 2022 Aug 08 " References: " http://i3wm.org/docs/userguide.html#configuring @@ -36,8 +36,8 @@ syn keyword swayConfigBindGestureCommand swipe pinch hold contained syn keyword swayConfigBindGestureDirection up down left right next prev contained syn keyword swayConfigBindGesturePinchDirection inward outward clockwise counterclockwise contained syn match swayConfigBindGestureHold /^\s*\(bindgesture\)\s\+hold\(:[1-5]\)\?\s\+.*$/ contains=swayConfigBindKeyword,swayConfigBindGestureCommand,swayConfigBindGestureDirection,i3ConfigWorkspaceKeyword,i3ConfigAction -syn match swayConfigBindGestureSwipe /^\s*\(bindgesture\)\s\+swipe\(:[1-5]\)\?:\(up\|down\|left\|right\)\s\+.*$/ contains=swayConfigBindKeyword,swayConfigBindGestureCommand,swayConfigBindGestureDirection,i3ConfigWorkspaceKeyword,i3ConfigAction -syn match swayConfigBindGesturePinch /^\s*\(bindgesture\)\s\+\(pinch\):.*$/ contains=swayConfigBindKeyword,swayConfigBindGestureCommand,swayConfigBindGestureDirection,swayConfigBindGesturePinchDirection,i3ConfigWorkspaceKeyword,i3ConfigAction +syn match swayConfigBindGestureSwipe /^\s*\(bindgesture\)\s\+swipe\(:[3-5]\)\?:\(up\|down\|left\|right\)\s\+.*$/ contains=swayConfigBindKeyword,swayConfigBindGestureCommand,swayConfigBindGestureDirection,i3ConfigWorkspaceKeyword,i3ConfigAction +syn match swayConfigBindGesturePinch /^\s*\(bindgesture\)\s\+pinch\(:[2-5]\)\?:\(up\|down\|left\|right\|inward\|outward\|clockwise\|counterclockwise\)\(+\(up\|down\|left\|right\|inward\|outward\|clockwise\|counterclockwise\)\)\?.*$/ contains=swayConfigBindKeyword,swayConfigBindGestureCommand,swayConfigBindGestureDirection,swayConfigBindGesturePinchDirection,i3ConfigWorkspaceKeyword,i3ConfigAction " floating syn keyword swayConfigFloatingKeyword floating contained diff --git a/runtime/syntax/synload.vim b/runtime/syntax/synload.vim index b88cd95103..056e38bf79 100644 --- a/runtime/syntax/synload.vim +++ b/runtime/syntax/synload.vim @@ -30,7 +30,7 @@ fun! s:SynSet() unlet b:current_syntax endif - let s = expand("<amatch>") + 0verbose let s = expand("<amatch>") if s == "ON" " :set syntax=ON if &filetype == "" diff --git a/runtime/syntax/syntax.vim b/runtime/syntax/syntax.vim index ac7dc314b9..55a2ee6d71 100644 --- a/runtime/syntax/syntax.vim +++ b/runtime/syntax/syntax.vim @@ -28,8 +28,9 @@ endif " Set up the connection between FileType and Syntax autocommands. " This makes the syntax automatically set when the file type is detected. +" Avoid an error when 'verbose' is set and <amatch> expansion fails. augroup syntaxset - au! FileType * exe "set syntax=" . expand("<amatch>") + au! FileType * 0verbose exe "set syntax=" . expand("<amatch>") augroup END diff --git a/runtime/syntax/typescript.vim b/runtime/syntax/typescript.vim index 767ba56d42..af71938a8e 100644 --- a/runtime/syntax/typescript.vim +++ b/runtime/syntax/typescript.vim @@ -35,7 +35,7 @@ syntax region typescriptTypeCast matchgroup=typescriptTypeBrackets """"""""""""""""""""""""""""""""""""""""""""""""""" " Source the part common with typescriptreact.vim -source <sfile>:h/typescriptcommon.vim +source <sfile>:h/shared/typescriptcommon.vim let b:current_syntax = "typescript" diff --git a/runtime/syntax/typescriptreact.vim b/runtime/syntax/typescriptreact.vim index f29fe785b9..c4c2d45745 100644 --- a/runtime/syntax/typescriptreact.vim +++ b/runtime/syntax/typescriptreact.vim @@ -133,7 +133,7 @@ syntax region tsxEscJs """"""""""""""""""""""""""""""""""""""""""""""""""" " Source the part common with typescriptreact.vim -source <sfile>:h/typescriptcommon.vim +source <sfile>:h/shared/typescriptcommon.vim syntax cluster typescriptExpression add=tsxRegion,tsxFragment diff --git a/runtime/tools/check_colors.vim b/runtime/tools/check_colors.vim index 85df882d1e..035914980d 100644 --- a/runtime/tools/check_colors.vim +++ b/runtime/tools/check_colors.vim @@ -8,10 +8,23 @@ set cpo&vim func! Test_check_colors() let l:savedview = winsaveview() call cursor(1,1) + + " err is + " { + " colors_name: "message", + " init: "message", + " background: "message", + " ....etc + " highlight: { + " 'Normal': "Missing ...", + " 'Conceal': "Missing ..." + " ....etc + " } + " } let err = {} " 1) Check g:colors_name is existing - if !search('\<\%(g:\)\?colors_name\>', 'cnW') + if search('\<\%(g:\)\?colors_name\>', 'cnW') == 0 let err['colors_name'] = 'g:colors_name not set' else let err['colors_name'] = 'OK' @@ -202,11 +215,12 @@ func! Test_check_colors() call Result(err) endfu + fu! Result(err) let do_groups = 0 echohl Title|echomsg "---------------"|echohl Normal for key in sort(keys(a:err)) - if key is# 'highlight' + if key ==# 'highlight' let do_groups = !empty(a:err[key]) continue else diff --git a/runtime/tools/emoji_list.vim b/runtime/tools/emoji_list.vim new file mode 100644 index 0000000000..c335b8c88f --- /dev/null +++ b/runtime/tools/emoji_list.vim @@ -0,0 +1,21 @@ +" Script to fill the window with emoji characters, one per line. +" Source this script: :source % + +if &modified + new +else + enew +endif + +lua << EOF + local lnum = 1 + for c = 0x100, 0x1ffff do + local cs = vim.fn.nr2char(c) + if vim.fn.charclass(cs) == 3 then + vim.fn.setline(lnum, '|' .. cs .. '| ' .. vim.fn.strwidth(cs)) + lnum = lnum + 1 + end + end +EOF + +set nomodified diff --git a/scripts/check-includes.py b/scripts/check-includes.py deleted file mode 100755 index ed1fe407c5..0000000000 --- a/scripts/check-includes.py +++ /dev/null @@ -1,64 +0,0 @@ -#!/usr/bin/env python - -import sys -import re -import os - -from subprocess import Popen, PIPE -from argparse import ArgumentParser - - -GENERATED_INCLUDE_RE = re.compile( - r'^\s*#\s*include\s*"([/a-z_0-9.]+\.generated\.h)"(\s+//.*)?$') - - -def main(argv): - argparser = ArgumentParser() - argparser.add_argument('--generated-includes-dir', action='append', - help='Directory where generated includes are located.') - argparser.add_argument('--file', type=open, help='File to check.') - argparser.add_argument('iwyu_args', nargs='*', - help='IWYU arguments, must go after --.') - args = argparser.parse_args(argv) - - with args.file: - iwyu = Popen(['include-what-you-use', '-xc'] + args.iwyu_args + ['/dev/stdin'], - stdin=PIPE, stdout=PIPE, stderr=PIPE) - - for line in args.file: - match = GENERATED_INCLUDE_RE.match(line) - if match: - for d in args.generated_includes_dir: - try: - f = open(os.path.join(d, match.group(1))) - except IOError: - continue - else: - with f: - for generated_line in f: - iwyu.stdin.write(generated_line) - break - else: - raise IOError('Failed to find {0}'.format(match.group(1))) - else: - iwyu.stdin.write(line) - - iwyu.stdin.close() - - out = iwyu.stdout.read() - err = iwyu.stderr.read() - - ret = iwyu.wait() - - if ret != 2: - print('IWYU failed with exit code {0}:'.format(ret)) - print('{0} stdout {0}'.format('=' * ((80 - len(' stdout ')) // 2))) - print(out) - print('{0} stderr {0}'.format('=' * ((80 - len(' stderr ')) // 2))) - print(err) - return 1 - return 0 - - -if __name__ == '__main__': - raise SystemExit(main(sys.argv[1:])) diff --git a/scripts/gen_vimdoc.py b/scripts/gen_vimdoc.py index c17742ddaf..23ed0e3f08 100755 --- a/scripts/gen_vimdoc.py +++ b/scripts/gen_vimdoc.py @@ -26,7 +26,7 @@ Each function :help block is formatted as follows: - Max width of 78 columns (`text_width`). - Indent with spaces (not tabs). - - Indent of 16 columns for body text. + - Indent of 4 columns for body text (`indentation`). - Function signature and helptag (right-aligned) on the same line. - Signature and helptag must have a minimum of 8 spaces between them. - If the signature is too long, it is placed on the line after the helptag. @@ -80,6 +80,7 @@ LOG_LEVELS = { } text_width = 78 +indentation = 4 script_path = os.path.abspath(__file__) base_dir = os.path.dirname(os.path.dirname(script_path)) out_dir = os.path.join(base_dir, 'tmp-{target}-doc') @@ -456,7 +457,7 @@ def max_name(names): return max(len(name) for name in names) -def update_params_map(parent, ret_map, width=62): +def update_params_map(parent, ret_map, width=text_width - indentation): """Updates `ret_map` with name:desc key-value pairs extracted from Doxygen XML node `parent`. """ @@ -483,7 +484,8 @@ def update_params_map(parent, ret_map, width=62): return ret_map -def render_node(n, text, prefix='', indent='', width=62, fmt_vimhelp=False): +def render_node(n, text, prefix='', indent='', width=text_width - indentation, + fmt_vimhelp=False): """Renders a node as Vim help text, recursively traversing all descendants.""" def ind(s): @@ -562,7 +564,7 @@ def render_node(n, text, prefix='', indent='', width=62, fmt_vimhelp=False): return text -def para_as_map(parent, indent='', width=62, fmt_vimhelp=False): +def para_as_map(parent, indent='', width=text_width - indentation, fmt_vimhelp=False): """Extracts a Doxygen XML <para> node to a map. Keys: @@ -656,7 +658,8 @@ def para_as_map(parent, indent='', width=62, fmt_vimhelp=False): return chunks, xrefs -def fmt_node_as_vimhelp(parent, width=62, indent='', fmt_vimhelp=False): +def fmt_node_as_vimhelp(parent, width=text_width - indentation, indent='', + fmt_vimhelp=False): """Renders (nested) Doxygen <para> nodes as Vim :help text. NB: Blank lines in a docstring manifest as <para> tags. @@ -798,7 +801,7 @@ 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')) + if a[0] not in ('void', 'Error', 'Arena')) if not fmt_vimhelp: c_decl = '%s %s(%s);' % (return_type, name, ', '.join(c_args)) @@ -838,7 +841,8 @@ def extract_from_xml(filename, target, width, fmt_vimhelp): log.debug( textwrap.indent( re.sub(r'\n\s*\n+', '\n', - desc.toprettyxml(indent=' ', newl='\n')), ' ' * 16)) + desc.toprettyxml(indent=' ', newl='\n')), + ' ' * indentation)) fn = { 'annotations': list(annotations), @@ -918,7 +922,7 @@ def fmt_doxygen_xml_as_vimhelp(filename, target): doc += '\n<' func_doc = fn['signature'] + '\n' - func_doc += textwrap.indent(clean_lines(doc), ' ' * 16) + func_doc += textwrap.indent(clean_lines(doc), ' ' * indentation) # Verbatim handling. func_doc = re.sub(r'^\s+([<>])$', r'\1', func_doc, flags=re.M) @@ -1114,7 +1118,7 @@ def main(config, args): docs += '\n\n\n' docs = docs.rstrip() + '\n\n' - docs += ' vim:tw=78:ts=8:ft=help:norl:\n' + docs += f' vim:tw=78:ts=8:sw={indentation}:sts={indentation}:et:ft=help:norl:\n' doc_file = os.path.join(base_dir, 'runtime', 'doc', CONFIG[target]['filename']) diff --git a/scripts/vim-patch.sh b/scripts/vim-patch.sh index 3825d9f0ea..ad1973603e 100755 --- a/scripts/vim-patch.sh +++ b/scripts/vim-patch.sh @@ -241,6 +241,14 @@ preprocess_patch() { LC_ALL=C sed -e 's/\( [ab]\/src\/nvim\)\/map\(\.[ch]\)/\1\/mapping\2/g' \ "$file" > "$file".tmp && mv "$file".tmp "$file" + # Rename profiler.c to profile.c + LC_ALL=C sed -e 's/\( [ab]\/src\/nvim\)\/profiler\(\.[ch]\)/\1\/profile\2/g' \ + "$file" > "$file".tmp && mv "$file".tmp "$file" + + # Rename scriptfile.c to runtime.c + LC_ALL=C sed -e 's/\( [ab]\/src\/nvim\)\/scriptfile\(\.[ch]\)/\1\/runtime\2/g' \ + "$file" > "$file".tmp && mv "$file".tmp "$file" + # Rename session.c to ex_session.c LC_ALL=C sed -e 's/\( [ab]\/src\/nvim\)\/session\(\.[ch]\)/\1\/ex_session\2/g' \ "$file" > "$file".tmp && mv "$file".tmp "$file" diff --git a/src/mpack/mpack_core.h b/src/mpack/mpack_core.h index 1d601bc82d..336b778931 100644 --- a/src/mpack/mpack_core.h +++ b/src/mpack/mpack_core.h @@ -8,6 +8,7 @@ #include <assert.h> #include <limits.h> #include <stddef.h> +#include <stdbool.h> #ifdef __GNUC__ # define FPURE __attribute__((const)) diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index b743e9923f..635833748d 100755 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -676,12 +676,17 @@ function(get_test_target prefix sfile relative_path_var target_var) endif() endfunction() -set(NO_SINGLE_CHECK_HEADERS - os/win_defs.h - os/pty_process_win.h - os/pty_conpty_win.h - os/os_win_console.h -) +if(WIN32) + set(NO_SINGLE_CHECK_HEADERS + os/pty_process_unix.h + os/unix_defs.h) +else() + set(NO_SINGLE_CHECK_HEADERS + os/win_defs.h + os/pty_process_win.h + os/pty_conpty_win.h + os/os_win_console.h) +endif() foreach(hfile ${NVIM_HEADERS}) get_test_target(test-includes "${hfile}" relative_path texe) diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c index bf6402f938..1cf0211f43 100644 --- a/src/nvim/api/autocmd.c +++ b/src/nvim/api/autocmd.c @@ -9,9 +9,9 @@ #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/eval/typval.h" -#include "nvim/fileio.h" #include "nvim/lua/executor.h" #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -887,12 +887,12 @@ static bool check_autocmd_string_array(Array arr, char *k, Error *err) static bool unpack_string_or_array(Array *array, Object *v, char *k, bool required, Error *err) { if (v->type == kObjectTypeString) { - ADD(*array, copy_object(*v)); + ADD(*array, copy_object(*v, NULL)); } else if (v->type == kObjectTypeArray) { if (!check_autocmd_string_array(v->data.array, k, err)) { return false; } - *array = copy_array(v->data.array); + *array = copy_array(v->data.array, NULL); } else { if (required) { api_set_error(err, diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index d3895d31cf..199650fc55 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -18,6 +18,7 @@ #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" @@ -1020,7 +1021,7 @@ void nvim_buf_del_var(Buffer buffer, String name, Error *err) /// @param buffer Buffer handle, or 0 for current buffer /// @param[out] err Error details, if any /// @return Buffer name -String nvim_buf_get_name(Buffer buffer, Error *err) +String nvim_buf_get_name(Buffer buffer, Arena *arena, Error *err) FUNC_API_SINCE(1) { String rv = STRING_INIT; @@ -1030,7 +1031,7 @@ String nvim_buf_get_name(Buffer buffer, Error *err) return rv; } - return cstr_to_string((char *)buf->b_ffname); + return cstr_as_string(buf->b_ffname); } /// Sets the full file name for a buffer diff --git a/src/nvim/api/command.c b/src/nvim/api/command.c index bc766ff39c..1323fc347b 100644 --- a/src/nvim/api/command.c +++ b/src/nvim/api/command.c @@ -11,6 +11,7 @@ #include "nvim/api/private/helpers.h" #include "nvim/autocmd.h" #include "nvim/ex_docmd.h" +#include "nvim/ex_eval.h" #include "nvim/lua/executor.h" #include "nvim/ops.h" #include "nvim/regexp.h" @@ -300,10 +301,10 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error FUNC_API_SINCE(10) { exarg_T ea; - memset(&ea, 0, sizeof(ea)); + CLEAR_FIELD(ea); CmdParseInfo cmdinfo; - memset(&cmdinfo, 0, sizeof(cmdinfo)); + CLEAR_FIELD(cmdinfo); char *cmdline = NULL; char *cmdname = NULL; @@ -625,6 +626,7 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error garray_T capture_local; const int save_msg_silent = msg_silent; garray_T * const save_capture_ga = capture_ga; + const int save_msg_col = msg_col; if (output) { ga_init(&capture_local, 1, 80); @@ -635,6 +637,7 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error try_start(); if (output) { msg_silent++; + msg_col = 0; // prevent leading spaces } WITH_SCRIPT_CONTEXT(channel_id, { @@ -644,6 +647,8 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error if (output) { capture_ga = save_capture_ga; msg_silent = save_msg_silent; + // Put msg_col back where it was, since nothing should have been written. + msg_col = save_msg_col; } try_end(err); @@ -819,9 +824,12 @@ static void build_cmdline_str(char **cmdlinep, exarg_T *eap, CmdParseInfo *cmdin char *p = replace_makeprg(eap, eap->arg, cmdlinep); if (p != eap->arg) { // If replace_makeprg modified the cmdline string, correct the argument pointers. - assert(argc == 1); eap->arg = p; - eap->args[0] = p; + // We can only know the position of the first argument because the argument list can be used + // multiple times in makeprg / grepprg. + if (argc >= 1) { + eap->args[0] = p; + } } } diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c index da1b6beeda..09b004637f 100644 --- a/src/nvim/api/extmark.c +++ b/src/nvim/api/extmark.c @@ -10,11 +10,11 @@ #include "nvim/api/private/helpers.h" #include "nvim/charset.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/memline.h" -#include "nvim/screen.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/extmark.c.generated.h" @@ -51,7 +51,7 @@ Integer nvim_create_namespace(String name) } id = next_namespace_id++; if (name.size > 0) { - String name_alloc = copy_string(name); + String name_alloc = copy_string(name, NULL); map_put(String, handle_T)(&namespace_ids, name_alloc, id); } return (Integer)id; @@ -441,8 +441,9 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e /// the extmark end position (if it exists) will be shifted /// in when new text is inserted (true for right, false /// for left). Defaults to false. -/// - priority: a priority value for the highlight group. For -/// example treesitter highlighting uses a value of 100. +/// - priority: a priority value for the highlight group or sign +/// attribute. For example treesitter highlighting uses a +/// value of 100. /// - strict: boolean that indicates extmark should not be placed /// if the line or column value is past the end of the /// buffer or end of the line respectively. Defaults to true. @@ -992,7 +993,7 @@ void nvim_set_decoration_provider(Integer ns_id, DictionaryOf(LuaRef) opts, Erro decor_provider_clear(p); // regardless of what happens, it seems good idea to redraw - redraw_all_later(NOT_VALID); // TODO(bfredl): too soon? + redraw_all_later(UPD_NOT_VALID); // TODO(bfredl): too soon? struct { const char *name; @@ -1030,6 +1031,8 @@ void nvim_set_decoration_provider(Integer ns_id, DictionaryOf(LuaRef) opts, Erro } p->active = true; + p->hl_valid++; + p->hl_cached = false; return; error: decor_provider_clear(p); diff --git a/src/nvim/api/keysets.lua b/src/nvim/api/keysets.lua index 4f4ac40ce9..6fad52ba75 100644 --- a/src/nvim/api/keysets.lua +++ b/src/nvim/api/keysets.lua @@ -104,7 +104,6 @@ return { "reverse"; "nocombine"; "default"; - "global"; "cterm"; "foreground"; "fg"; "background"; "bg"; @@ -112,9 +111,9 @@ return { "ctermbg"; "special"; "sp"; "link"; + "global_link"; "fallback"; "blend"; - "temp"; }; highlight_cterm = { "bold"; diff --git a/src/nvim/api/private/dispatch.h b/src/nvim/api/private/dispatch.h index 4b7c394944..f92b205531 100644 --- a/src/nvim/api/private/dispatch.h +++ b/src/nvim/api/private/dispatch.h @@ -5,18 +5,23 @@ 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. -typedef struct { +struct MsgpackRpcRequestHandler { const char *name; ApiDispatchWrapper fn; bool fast; // Function is safe to be executed immediately while running the // uv loop (the loop is run very frequently due to breakcheck). // If "fast" is false, the function is deferred, i e the call will // be put in the event queue, for safe handling later. -} MsgpackRpcRequestHandler; + bool arena_return; // return value is allocated in the arena (or statically) + // and should not be freed as such. +}; + +extern const MsgpackRpcRequestHandler method_handlers[]; #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/private/dispatch.h.generated.h" diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index fad75d55be..ebcf6cca6d 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -19,8 +19,8 @@ #include "nvim/eval.h" #include "nvim/eval/typval.h" #include "nvim/ex_cmds_defs.h" +#include "nvim/ex_eval.h" #include "nvim/extmark.h" -#include "nvim/fileio.h" #include "nvim/highlight_group.h" #include "nvim/lib/kvec.h" #include "nvim/lua/executor.h" @@ -54,10 +54,11 @@ void try_enter(TryState *const tstate) // save_dbg_stuff()/restore_dbg_stuff(). *tstate = (TryState) { .current_exception = current_exception, - .msg_list = (const struct msglist *const *)msg_list, + .msg_list = (const msglist_T *const *)msg_list, .private_msg_list = NULL, .trylevel = trylevel, .got_int = got_int, + .did_throw = did_throw, .need_rethrow = need_rethrow, .did_emsg = did_emsg, }; @@ -65,6 +66,7 @@ void try_enter(TryState *const tstate) current_exception = NULL; trylevel = 1; got_int = false; + did_throw = false; need_rethrow = false; did_emsg = false; } @@ -85,14 +87,16 @@ bool try_leave(const TryState *const tstate, Error *const err) assert(trylevel == 0); assert(!need_rethrow); assert(!got_int); + assert(!did_throw); assert(!did_emsg); assert(msg_list == &tstate->private_msg_list); assert(*msg_list == NULL); assert(current_exception == NULL); - msg_list = (struct msglist **)tstate->msg_list; + msg_list = (msglist_T **)tstate->msg_list; current_exception = tstate->current_exception; trylevel = tstate->trylevel; got_int = tstate->got_int; + did_throw = tstate->did_throw; need_rethrow = tstate->need_rethrow; did_emsg = tstate->did_emsg; return ret; @@ -127,7 +131,7 @@ bool try_end(Error *err) force_abort = false; if (got_int) { - if (current_exception) { + if (did_throw) { // If we got an interrupt, discard the current exception discard_current_exception(); } @@ -146,7 +150,7 @@ bool try_end(Error *err) if (should_free) { xfree(msg); } - } else if (current_exception) { + } else if (did_throw) { api_set_error(err, kErrorTypeException, "%s", current_exception->value); discard_current_exception(); } @@ -618,6 +622,7 @@ void api_clear_error(Error *value) value->type = kErrorTypeNone; } +/// @returns a shared value. caller must not modify it! Dictionary api_metadata(void) { static Dictionary metadata = ARRAY_DICT_INIT; @@ -630,7 +635,7 @@ Dictionary api_metadata(void) init_type_metadata(&metadata); } - return copy_object(DICTIONARY_OBJ(metadata)).data.dictionary; + return metadata; } static void init_function_metadata(Dictionary *metadata) @@ -715,36 +720,40 @@ static void init_type_metadata(Dictionary *metadata) PUT(*metadata, "types", DICTIONARY_OBJ(types)); } -String copy_string(String str) +// all the copy_[object] functions allow arena=NULL, +// then global allocations are used, and the resulting object +// should be freed with an api_free_[object] function + +String copy_string(String str, Arena *arena) { if (str.data != NULL) { - return (String){ .data = xmemdupz(str.data, str.size), .size = str.size }; + return (String){ .data = arena_memdupz(arena, str.data, str.size), .size = str.size }; } else { return (String)STRING_INIT; } } -Array copy_array(Array array) +Array copy_array(Array array, Arena *arena) { - Array rv = ARRAY_DICT_INIT; + Array rv = arena_array(arena, array.size); for (size_t i = 0; i < array.size; i++) { - ADD(rv, copy_object(array.items[i])); + ADD(rv, copy_object(array.items[i], arena)); } return rv; } -Dictionary copy_dictionary(Dictionary dict) +Dictionary copy_dictionary(Dictionary dict, Arena *arena) { - Dictionary rv = ARRAY_DICT_INIT; + Dictionary rv = arena_dict(arena, dict.size); for (size_t i = 0; i < dict.size; i++) { KeyValuePair item = dict.items[i]; - PUT(rv, item.key.data, copy_object(item.value)); + PUT_C(rv, copy_string(item.key, arena).data, copy_object(item.value, arena)); } return rv; } /// Creates a deep clone of an object -Object copy_object(Object obj) +Object copy_object(Object obj, Arena *arena) { switch (obj.type) { case kObjectTypeBuffer: @@ -757,13 +766,13 @@ Object copy_object(Object obj) return obj; case kObjectTypeString: - return STRING_OBJ(copy_string(obj.data.string)); + return STRING_OBJ(copy_string(obj.data.string, arena)); case kObjectTypeArray: - return ARRAY_OBJ(copy_array(obj.data.array)); + return ARRAY_OBJ(copy_array(obj.data.array, arena)); case kObjectTypeDictionary: - return DICTIONARY_OBJ(copy_dictionary(obj.data.dictionary)); + return DICTIONARY_OBJ(copy_dictionary(obj.data.dictionary, arena)); case kObjectTypeLuaRef: return LUAREF_OBJ(api_new_luaref(obj.data.luaref)); @@ -844,7 +853,7 @@ HlMessage parse_hl_msg(Array chunks, Error *err) goto free_exit; } - String str = copy_string(chunk.items[0].data.string); + String str = copy_string(chunk.items[0].data.string, NULL); int attr = 0; if (chunk.size == 2) { diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h index 1441da853c..2157ad0ec2 100644 --- a/src/nvim/api/private/helpers.h +++ b/src/nvim/api/private/helpers.h @@ -3,7 +3,7 @@ #include "nvim/api/private/defs.h" #include "nvim/decoration.h" -#include "nvim/ex_eval.h" +#include "nvim/ex_eval_defs.h" #include "nvim/getchar.h" #include "nvim/lib/kvec.h" #include "nvim/memory.h" @@ -130,10 +130,11 @@ EXTERN PMap(handle_T) tabpage_handles INIT(= MAP_INIT); /// processed and that “other VimL code” must not be affected. typedef struct { except_T *current_exception; - struct msglist *private_msg_list; - const struct msglist *const *msg_list; + msglist_T *private_msg_list; + const msglist_T *const *msg_list; int trylevel; int got_int; + bool did_throw; int need_rethrow; int did_emsg; } TryState; @@ -144,8 +145,8 @@ typedef struct { // TODO(bfredl): prepare error-handling at "top level" (nv_event). #define TRY_WRAP(code) \ do { \ - struct msglist **saved_msg_list = msg_list; \ - struct msglist *private_msg_list; \ + msglist_T **saved_msg_list = msg_list; \ + msglist_T *private_msg_list; \ msg_list = &private_msg_list; \ private_msg_list = NULL; \ code \ diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c index 6239e414a7..e34dcbdb46 100644 --- a/src/nvim/api/ui.c +++ b/src/nvim/api/ui.c @@ -11,14 +11,14 @@ #include "nvim/api/ui.h" #include "nvim/channel.h" #include "nvim/cursor_shape.h" +#include "nvim/grid.h" #include "nvim/highlight.h" #include "nvim/map.h" #include "nvim/memory.h" #include "nvim/msgpack_rpc/channel.h" #include "nvim/msgpack_rpc/helpers.h" #include "nvim/option.h" -#include "nvim/popupmnu.h" -#include "nvim/screen.h" +#include "nvim/popupmenu.h" #include "nvim/ui.h" #include "nvim/vim.h" #include "nvim/window.h" @@ -224,7 +224,7 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, Dictiona ui->event = remote_ui_event; ui->inspect = remote_ui_inspect; - memset(ui->ui_ext, 0, sizeof(ui->ui_ext)); + CLEAR_FIELD(ui->ui_ext); for (size_t i = 0; i < options.size; i++) { ui_set_option(ui, true, options.items[i].key, options.items[i].value, err); @@ -965,7 +965,7 @@ static Array translate_contents(UI *ui, Array contents) } else { ADD(new_item, DICTIONARY_OBJ((Dictionary)ARRAY_DICT_INIT)); } - ADD(new_item, copy_object(item.items[1])); + ADD(new_item, copy_object(item.items[1], NULL)); ADD(new_contents, ARRAY_OBJ(new_item)); } return new_contents; @@ -978,7 +978,7 @@ static Array translate_firstarg(UI *ui, Array args) ADD(new_args, ARRAY_OBJ(translate_contents(ui, contents))); for (size_t i = 1; i < args.size; i++) { - ADD(new_args, copy_object(args.items[i])); + ADD(new_args, copy_object(args.items[i], NULL)); } return new_args; } @@ -1024,7 +1024,7 @@ static void remote_ui_event(UI *ui, char *name, Array args) Array items = args.items[0].data.array; Array new_items = ARRAY_DICT_INIT; for (size_t i = 0; i < items.size; i++) { - ADD(new_items, copy_object(items.items[i].data.array.items[0])); + ADD(new_items, copy_object(items.items[i].data.array.items[0], NULL)); } ADD_C(new_args, ARRAY_OBJ(new_items)); push_call(ui, "wildmenu_show", new_args); diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index e2f58dba62..b164106cef 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -23,17 +23,19 @@ #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_cmds2.h" #include "nvim/ex_cmds_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" @@ -50,11 +52,13 @@ #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/process.h" -#include "nvim/popupmnu.h" -#include "nvim/screen.h" +#include "nvim/popupmenu.h" +#include "nvim/runtime.h" #include "nvim/state.h" +#include "nvim/statusline.h" #include "nvim/types.h" #include "nvim/ui.h" #include "nvim/vim.h" @@ -91,7 +95,6 @@ Dictionary nvim_get_hl_by_name(String name, Boolean rgb, Error *err) } /// Gets a highlight definition by id. |hlID()| -/// /// @param hl_id Highlight id as returned by |hlID()| /// @param rgb Export RGB colors /// @param[out] err Error details, if any @@ -180,35 +183,38 @@ void nvim_set_hl(Integer ns_id, String name, Dict(highlight) *val, Error *err) } } -/// Set active namespace for highlights. -/// -/// NB: this function can be called from async contexts, but the -/// semantics are not yet well-defined. To start with -/// |nvim_set_decoration_provider| on_win and on_line callbacks -/// are explicitly allowed to change the namespace during a redraw cycle. +/// Set active namespace for highlights. This can be set for a single window, +/// see |nvim_win_set_hl_ns|. /// -/// @param ns_id the namespace to activate +/// @param ns_id the namespace to use /// @param[out] err Error details, if any -void nvim__set_hl_ns(Integer ns_id, Error *err) - FUNC_API_FAST +void nvim_set_hl_ns(Integer ns_id, Error *err) + FUNC_API_SINCE(10) { - if (ns_id >= 0) { - ns_hl_active = (NS)ns_id; + if (ns_id < 0) { + api_set_error(err, kErrorTypeValidation, "no such namespace"); + return; } - // TODO(bfredl): this is a little bit hackish. Eventually we want a standard - // event path for redraws caused by "fast" events. This could tie in with - // better throttling of async events causing redraws, such as non-batched - // nvim_buf_set_extmark calls from async contexts. - if (!provider_active && !ns_hl_changed && must_redraw < NOT_VALID) { - multiqueue_put(main_loop.events, on_redraw_event, 0); - } - ns_hl_changed = true; + ns_hl_global = (NS)ns_id; + hl_check_ns(); + redraw_all_later(UPD_NOT_VALID); } -static void on_redraw_event(void **argv) +/// Set active namespace for highlights while redrawing. +/// +/// This function meant to be called while redrawing, primarily from +/// |nvim_set_decoration_provider| on_win and on_line callbacks, which +/// are allowed to change the namespace during a redraw cycle. +/// +/// @param ns_id the namespace to activate +/// @param[out] err Error details, if any +void nvim_set_hl_ns_fast(Integer ns_id, Error *err) + FUNC_API_SINCE(10) + FUNC_API_FAST { - redraw_all_later(NOT_VALID); + ns_hl_fast = (NS)ns_id; + hl_check_ns(); } /// Sends input-keys to Nvim, subject to various quirks controlled by `mode` @@ -478,7 +484,7 @@ Object nvim_notify(String msg, Integer log_level, Dictionary opts, Error *err) ADD_C(args, INTEGER_OBJ(log_level)); ADD_C(args, DICTIONARY_OBJ(opts)); - return nlua_exec(STATIC_CSTR_AS_STRING("return vim.notify(...)"), args, err); + return NLUA_EXEC_STATIC("return vim.notify(...)", args, err); } /// Calculates the number of display cells occupied by `text`. @@ -1473,14 +1479,14 @@ void nvim_del_keymap(uint64_t channel_id, String mode, String lhs, Error *err) /// 1 is the |api-metadata| map (Dictionary). /// /// @returns 2-tuple [{channel-id}, {api-metadata}] -Array nvim_get_api_info(uint64_t channel_id) +Array nvim_get_api_info(uint64_t channel_id, Arena *arena) FUNC_API_SINCE(1) FUNC_API_FAST FUNC_API_REMOTE_ONLY { - Array rv = ARRAY_DICT_INIT; + Array rv = arena_array(arena, 2); assert(channel_id <= INT64_MAX); - ADD(rv, INTEGER_OBJ((int64_t)channel_id)); - ADD(rv, DICTIONARY_OBJ(api_metadata())); + ADD_C(rv, INTEGER_OBJ((int64_t)channel_id)); + ADD_C(rv, DICTIONARY_OBJ(api_metadata())); return rv; } @@ -1539,9 +1545,9 @@ void nvim_set_client_info(uint64_t channel_id, String name, Dictionary version, FUNC_API_SINCE(4) FUNC_API_REMOTE_ONLY { Dictionary info = ARRAY_DICT_INIT; - PUT(info, "name", copy_object(STRING_OBJ(name))); + PUT(info, "name", copy_object(STRING_OBJ(name), NULL)); - version = copy_dictionary(version); + version = copy_dictionary(version, NULL); bool has_major = false; for (size_t i = 0; i < version.size; i++) { if (strequal(version.items[i].key.data, "major")) { @@ -1554,9 +1560,9 @@ void nvim_set_client_info(uint64_t channel_id, String name, Dictionary version, } PUT(info, "version", DICTIONARY_OBJ(version)); - PUT(info, "type", copy_object(STRING_OBJ(type))); - PUT(info, "methods", DICTIONARY_OBJ(copy_dictionary(methods))); - PUT(info, "attributes", DICTIONARY_OBJ(copy_dictionary(attributes))); + PUT(info, "type", copy_object(STRING_OBJ(type), NULL)); + PUT(info, "methods", DICTIONARY_OBJ(copy_dictionary(methods, NULL))); + PUT(info, "attributes", DICTIONARY_OBJ(copy_dictionary(attributes, NULL))); rpc_set_client_info(channel_id, info); } @@ -1623,11 +1629,11 @@ Array nvim_list_chans(void) /// an error, it is a three-element array with the zero-based index of the call /// which resulted in an error, the error type and the error message. If an /// error occurred, the values from all preceding calls will still be returned. -Array nvim_call_atomic(uint64_t channel_id, Array calls, Error *err) +Array nvim_call_atomic(uint64_t channel_id, Array calls, Arena *arena, Error *err) FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY { - Array rv = ARRAY_DICT_INIT; - Array results = ARRAY_DICT_INIT; + Array rv = arena_array(arena, 2); + Array results = arena_array(arena, calls.size); Error nested_error = ERROR_INIT; size_t i; // also used for freeing the variables @@ -1636,21 +1642,21 @@ Array nvim_call_atomic(uint64_t channel_id, Array calls, Error *err) api_set_error(err, kErrorTypeValidation, "Items in calls array must be arrays"); - goto validation_error; + goto theend; } Array call = calls.items[i].data.array; if (call.size != 2) { api_set_error(err, kErrorTypeValidation, "Items in calls array must be arrays of size 2"); - goto validation_error; + goto theend; } if (call.items[0].type != kObjectTypeString) { api_set_error(err, kErrorTypeValidation, "Name must be String"); - goto validation_error; + goto theend; } String name = call.items[0].data.string; @@ -1658,7 +1664,7 @@ Array nvim_call_atomic(uint64_t channel_id, Array calls, Error *err) api_set_error(err, kErrorTypeValidation, "Args must be Array"); - goto validation_error; + goto theend; } Array args = call.items[1].data.array; @@ -1670,29 +1676,32 @@ Array nvim_call_atomic(uint64_t channel_id, Array calls, Error *err) if (ERROR_SET(&nested_error)) { break; } - Object result = handler.fn(channel_id, args, &nested_error); + + Object result = handler.fn(channel_id, args, arena, &nested_error); if (ERROR_SET(&nested_error)) { // error handled after loop break; } - - ADD(results, result); + // TODO(bfredl): wastefull 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)); + if (!handler.arena_return) { + api_free_object(result); + } } - ADD(rv, ARRAY_OBJ(results)); + ADD_C(rv, ARRAY_OBJ(results)); if (ERROR_SET(&nested_error)) { - Array errval = ARRAY_DICT_INIT; - ADD(errval, INTEGER_OBJ((Integer)i)); - ADD(errval, INTEGER_OBJ(nested_error.type)); - ADD(errval, STRING_OBJ(cstr_to_string(nested_error.msg))); - ADD(rv, ARRAY_OBJ(errval)); + Array errval = arena_array(arena, 3); + ADD_C(errval, INTEGER_OBJ((Integer)i)); + ADD_C(errval, INTEGER_OBJ(nested_error.type)); + ADD_C(errval, STRING_OBJ(copy_string(cstr_as_string(nested_error.msg), arena))); + ADD_C(rv, ARRAY_OBJ(errval)); } else { - ADD(rv, NIL); + ADD_C(rv, NIL); } - goto theend; -validation_error: - api_free_array(results); theend: api_clear_error(&nested_error); return rv; @@ -1745,7 +1754,7 @@ static void write_msg(String message, bool to_err) /// @return its argument. Object nvim__id(Object obj) { - return copy_object(obj); + return copy_object(obj, NULL); } /// Returns array given as argument. @@ -1758,7 +1767,7 @@ Object nvim__id(Object obj) /// @return its argument. Array nvim__id_array(Array arr) { - return copy_object(ARRAY_OBJ(arr)).data.array; + return copy_array(arr, NULL); } /// Returns dictionary given as argument. @@ -1771,7 +1780,7 @@ Array nvim__id_array(Array arr) /// @return its argument. Dictionary nvim__id_dictionary(Dictionary dct) { - return copy_object(DICTIONARY_OBJ(dct)).data.dictionary; + return copy_dictionary(dct, NULL); } /// Returns floating-point value given as argument. @@ -1797,6 +1806,7 @@ Dictionary nvim__stats(void) PUT(rv, "log_skip", INTEGER_OBJ(g_stats.log_skip)); PUT(rv, "lua_refcount", INTEGER_OBJ(nlua_get_global_ref_count())); PUT(rv, "redraw", INTEGER_OBJ(g_stats.redraw)); + PUT(rv, "arena_alloc_count", INTEGER_OBJ((Integer)arena_alloc_count)); return rv; } @@ -1833,11 +1843,9 @@ Array nvim_get_proc_children(Integer pid, Error *err) if (rv == 2) { // syscall failed (possibly because of kernel options), try shelling out. DLOG("fallback to vim._os_proc_children()"); - Array a = ARRAY_DICT_INIT; + MAXSIZE_TEMP_ARRAY(a, 1); ADD(a, INTEGER_OBJ(pid)); - String s = STATIC_CSTR_AS_STRING("return vim._os_proc_children(...)"); - Object o = nlua_exec(s, a, err); - api_free_array(a); + Object o = NLUA_EXEC_STATIC("return vim._os_proc_children(...)", a, err); if (o.type == kObjectTypeArray) { rvobj = o.data.array; } else if (!ERROR_SET(err)) { @@ -1878,12 +1886,9 @@ Object nvim_get_proc(Integer pid, Error *err) } #else // Cross-platform process info APIs are miserable, so use `ps` instead. - Array a = ARRAY_DICT_INIT; + MAXSIZE_TEMP_ARRAY(a, 1); ADD(a, INTEGER_OBJ(pid)); - String s = cstr_to_string("return vim._os_proc_info(select(1, ...))"); - Object o = nlua_exec(s, a, err); - api_free_string(s); - api_free_array(a); + Object o = NLUA_EXEC_STATIC("return vim._os_proc_info(...)", a, err); if (o.type == kObjectTypeArray && o.data.array.size == 0) { return NIL; // Process not found. } else if (o.type == kObjectTypeDictionary) { @@ -2039,7 +2044,7 @@ Array nvim_get_mark(String name, Dictionary opts, Error *err) // Marks are from an open buffer it fnum is non zero if (mark->fmark.fnum != 0) { bufnr = mark->fmark.fnum; - filename = (char *)buflist_nr2name(bufnr, true, true); + filename = buflist_nr2name(bufnr, true, true); allocated = true; // Marks comes from shada } else { @@ -2087,7 +2092,7 @@ Array nvim_get_mark(String name, Dictionary opts, Error *err) /// 'fillchars'). Treated as single-width even if it isn't. /// - highlights: (boolean) Return highlight information. /// - use_winbar: (boolean) Evaluate winbar instead of statusline. -/// - use_tabline: (boolean) Evaluate tabline instead of statusline. When |TRUE|, {winid} +/// - use_tabline: (boolean) Evaluate tabline instead of statusline. When true, {winid} /// is ignored. Mutually exclusive with {use_winbar}. /// /// @param[out] err Error details, if any. @@ -2095,7 +2100,7 @@ Array nvim_get_mark(String name, Dictionary opts, Error *err) /// - str: (string) Characters that will be displayed on the statusline. /// - width: (number) Display width of the statusline. /// - highlights: Array containing highlight information of the statusline. Only included when -/// the "highlights" key in {opts} is |TRUE|. Each element of the array is a +/// the "highlights" key in {opts} is true. Each element of the array is a /// |Dictionary| with these keys: /// - start: (number) Byte index (0-based) of first character that uses the highlight. /// - group: (string) Name of highlight group. @@ -2227,7 +2232,7 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error * // If first character doesn't have a defined highlight, // add the default highlight at the beginning of the highlight list - if (hltab->start == NULL || ((char *)hltab->start - buf) != 0) { + if (hltab->start == NULL || (hltab->start - buf) != 0) { Dictionary hl_info = ARRAY_DICT_INIT; grpname = get_default_stl_hl(wp, use_winbar); diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c index 478e146781..f6d0e39327 100644 --- a/src/nvim/api/vimscript.c +++ b/src/nvim/api/vimscript.c @@ -14,8 +14,9 @@ #include "nvim/eval.h" #include "nvim/eval/typval.h" #include "nvim/eval/userfunc.h" -#include "nvim/ex_cmds2.h" +#include "nvim/ex_docmd.h" #include "nvim/ops.h" +#include "nvim/runtime.h" #include "nvim/strings.h" #include "nvim/vim.h" #include "nvim/viml/parser/expressions.h" @@ -48,6 +49,7 @@ String nvim_exec(uint64_t channel_id, String src, Boolean output, Error *err) { const int save_msg_silent = msg_silent; garray_T *const save_capture_ga = capture_ga; + const int save_msg_col = msg_col; garray_T capture_local; if (output) { ga_init(&capture_local, 1, 80); @@ -57,6 +59,7 @@ String nvim_exec(uint64_t channel_id, String src, Boolean output, Error *err) try_start(); if (output) { msg_silent++; + msg_col = 0; // prevent leading spaces } const sctx_T save_current_sctx = api_set_sctx(channel_id); @@ -65,6 +68,8 @@ String nvim_exec(uint64_t channel_id, String src, Boolean output, Error *err) if (output) { capture_ga = save_capture_ga; msg_silent = save_msg_silent; + // Put msg_col back where it was, since nothing should have been written. + msg_col = save_msg_col; } current_sctx = save_current_sctx; @@ -132,7 +137,7 @@ Object nvim_eval(String expr, Error *err) if (!recursive) { force_abort = false; suppress_errthrow = false; - current_exception = NULL; + did_throw = false; // `did_emsg` is set by emsg(), which cancels execution. did_emsg = false; } @@ -191,7 +196,7 @@ static Object _call_function(String fn, Array args, dict_T *self, Error *err) if (!recursive) { force_abort = false; suppress_errthrow = false; - current_exception = NULL; + did_throw = false; // `did_emsg` is set by emsg(), which cancels execution. did_emsg = false; } diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c index 969643eeef..96c560efa1 100644 --- a/src/nvim/api/win_config.c +++ b/src/nvim/api/win_config.c @@ -10,9 +10,9 @@ #include "nvim/api/private/helpers.h" #include "nvim/api/win_config.h" #include "nvim/ascii.h" +#include "nvim/drawscreen.h" #include "nvim/highlight_group.h" #include "nvim/option.h" -#include "nvim/screen.h" #include "nvim/strings.h" #include "nvim/syntax.h" #include "nvim/ui.h" @@ -167,7 +167,7 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dict(float_config) *config, E if (fconfig.style == kWinStyleMinimal) { win_set_minimal_style(wp); - didset_window_options(wp); + didset_window_options(wp, true); } return wp->handle; } @@ -202,14 +202,14 @@ void nvim_win_set_config(Window window, Dict(float_config) *config, Error *err) if (!win_new_float(win, false, fconfig, err)) { return; } - redraw_later(win, NOT_VALID); + redraw_later(win, UPD_NOT_VALID); } else { win_config_float(win, fconfig); win->w_pos_changed = true; } if (fconfig.style == kWinStyleMinimal) { win_set_minimal_style(win); - didset_window_options(win); + didset_window_options(win, true); } } diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c index 5a4ff70257..5203003369 100644 --- a/src/nvim/api/window.c +++ b/src/nvim/api/window.c @@ -12,12 +12,12 @@ #include "nvim/ascii.h" #include "nvim/buffer.h" #include "nvim/cursor.h" +#include "nvim/drawscreen.h" #include "nvim/ex_docmd.h" #include "nvim/globals.h" #include "nvim/lua/executor.h" #include "nvim/move.h" #include "nvim/option.h" -#include "nvim/screen.h" #include "nvim/syntax.h" #include "nvim/vim.h" #include "nvim/window.h" @@ -118,7 +118,7 @@ void nvim_win_set_cursor(Window window, ArrayOf(Integer, 2) pos, Error *err) // make sure cursor is in visible range even if win != curwin update_topline_win(win); - redraw_later(win, VALID); + redraw_later(win, UPD_VALID); win->w_redr_status = true; } @@ -426,3 +426,28 @@ Object nvim_win_call(Window window, LuaRef fun, Error *err) try_end(err); return res; } + +/// Set highlight namespace for a window. This will use highlights defined in +/// this namespace, but fall back to global highlights (ns=0) when missing. +/// +/// This takes predecence over the 'winhighlight' option. +/// +/// @param ns_id the namespace to use +/// @param[out] err Error details, if any +void nvim_win_set_hl_ns(Window window, Integer ns_id, Error *err) + FUNC_API_SINCE(10) +{ + win_T *win = find_window_by_handle(window, err); + if (!win) { + return; + } + + // -1 is allowed as inherit global namespace + if (ns_id < -1) { + api_set_error(err, kErrorTypeValidation, "no such namespace"); + } + + win->w_ns_hl = (NS)ns_id; + win->w_hl_needs_update = true; + redraw_later(win, UPD_NOT_VALID); +} diff --git a/src/nvim/arabic.c b/src/nvim/arabic.c index 06536e6e2b..b57019286f 100644 --- a/src/nvim/arabic.c +++ b/src/nvim/arabic.c @@ -320,22 +320,22 @@ int arabic_shape(int c, int *ccp, int *c1p, int prev_c, int prev_c1, int next_c) int backward_combine = !prev_laa && can_join(prev_c, c); int forward_combine = can_join(c, next_c); - if (backward_combine && forward_combine) { - curr_c = (int)curr_a->medial; - } - if (backward_combine && !forward_combine) { - curr_c = (int)curr_a->final; - } - if (!backward_combine && forward_combine) { - curr_c = (int)curr_a->initial; - } - if (!backward_combine && !forward_combine) { - curr_c = (int)curr_a->isolated; + if (backward_combine) { + if (forward_combine) { + curr_c = (int)curr_a->medial; + } else { + curr_c = (int)curr_a->final; + } + } else { + if (forward_combine) { + curr_c = (int)curr_a->initial; + } else { + curr_c = (int)curr_a->isolated; + } } } - // Sanity check -- curr_c should, in the future, never be 0. - // We should, in the future, insert a fatal error here. + // Character missing from the table means using original character. if (curr_c == NUL) { curr_c = c; } diff --git a/src/nvim/arglist.c b/src/nvim/arglist.c new file mode 100644 index 0000000000..70b2e71949 --- /dev/null +++ b/src/nvim/arglist.c @@ -0,0 +1,1156 @@ +// 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 + +// arglist.c: functions for dealing with the argument list + +#include <assert.h> +#include <stdbool.h> + +#include "nvim/arglist.h" +#include "nvim/buffer.h" +#include "nvim/charset.h" +#include "nvim/eval.h" +#include "nvim/ex_cmds.h" +#include "nvim/ex_cmds2.h" +#include "nvim/ex_getln.h" +#include "nvim/fileio.h" +#include "nvim/garray.h" +#include "nvim/globals.h" +#include "nvim/mark.h" +#include "nvim/memory.h" +#include "nvim/os/input.h" +#include "nvim/path.h" +#include "nvim/regexp.h" +#include "nvim/strings.h" +#include "nvim/undo.h" +#include "nvim/version.h" +#include "nvim/vim.h" +#include "nvim/window.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "arglist.c.generated.h" +#endif + +enum { + AL_SET = 1, + AL_ADD = 2, + AL_DEL = 3, +}; + +/// Clear an argument list: free all file names and reset it to zero entries. +void alist_clear(alist_T *al) +{ +#define FREE_AENTRY_FNAME(arg) xfree((arg)->ae_fname) + GA_DEEP_CLEAR(&al->al_ga, aentry_T, FREE_AENTRY_FNAME); +} + +/// Init an argument list. +void alist_init(alist_T *al) +{ + ga_init(&al->al_ga, (int)sizeof(aentry_T), 5); +} + +/// Remove a reference from an argument list. +/// Ignored when the argument list is the global one. +/// If the argument list is no longer used by any window, free it. +void alist_unlink(alist_T *al) +{ + if (al != &global_alist && --al->al_refcount <= 0) { + alist_clear(al); + xfree(al); + } +} + +/// Create a new argument list and use it for the current window. +void alist_new(void) +{ + curwin->w_alist = xmalloc(sizeof(*curwin->w_alist)); + curwin->w_alist->al_refcount = 1; + curwin->w_alist->id = ++max_alist_id; + alist_init(curwin->w_alist); +} + +#if !defined(UNIX) + +/// Expand the file names in the global argument list. +/// If "fnum_list" is not NULL, use "fnum_list[fnum_len]" as a list of buffer +/// numbers to be re-used. +void alist_expand(int *fnum_list, int fnum_len) +{ + char *save_p_su = p_su; + + // Don't use 'suffixes' here. This should work like the shell did the + // expansion. Also, the vimrc file isn't read yet, thus the user + // can't set the options. + p_su = empty_option; + char **old_arg_files = xmalloc(sizeof(*old_arg_files) * GARGCOUNT); + for (int i = 0; i < GARGCOUNT; i++) { + old_arg_files[i] = vim_strsave(GARGLIST[i].ae_fname); + } + int old_arg_count = GARGCOUNT; + char **new_arg_files; + int new_arg_file_count; + if (expand_wildcards(old_arg_count, old_arg_files, + &new_arg_file_count, &new_arg_files, + EW_FILE|EW_NOTFOUND|EW_ADDSLASH|EW_NOERROR) == OK + && new_arg_file_count > 0) { + alist_set(&global_alist, new_arg_file_count, new_arg_files, + true, fnum_list, fnum_len); + FreeWild(old_arg_count, old_arg_files); + } + p_su = save_p_su; +} +#endif + +/// Set the argument list for the current window. +/// Takes over the allocated files[] and the allocated fnames in it. +void alist_set(alist_T *al, int count, char **files, int use_curbuf, int *fnum_list, int fnum_len) +{ + static int recursive = 0; + + if (recursive) { + emsg(_(e_au_recursive)); + return; + } + recursive++; + + alist_clear(al); + ga_grow(&al->al_ga, count); + { + for (int i = 0; i < count; i++) { + if (got_int) { + // When adding many buffers this can take a long time. Allow + // interrupting here. + while (i < count) { + xfree(files[i++]); + } + break; + } + + // May set buffer name of a buffer previously used for the + // argument list, so that it's re-used by alist_add. + if (fnum_list != NULL && i < fnum_len) { + buf_set_name(fnum_list[i], files[i]); + } + + alist_add(al, files[i], use_curbuf ? 2 : 1); + os_breakcheck(); + } + xfree(files); + } + + if (al == &global_alist) { + arg_had_last = false; + } + recursive--; +} + +/// Add file "fname" to argument list "al". +/// "fname" must have been allocated and "al" must have been checked for room. +/// +/// @param set_fnum 1: set buffer number; 2: re-use curbuf +void alist_add(alist_T *al, char *fname, int set_fnum) +{ + if (fname == NULL) { // don't add NULL file names + return; + } +#ifdef BACKSLASH_IN_FILENAME + slash_adjust(fname); +#endif + AARGLIST(al)[al->al_ga.ga_len].ae_fname = fname; + if (set_fnum > 0) { + AARGLIST(al)[al->al_ga.ga_len].ae_fnum = + buflist_add(fname, BLN_LISTED | (set_fnum == 2 ? BLN_CURBUF : 0)); + } + al->al_ga.ga_len++; +} + +#if defined(BACKSLASH_IN_FILENAME) + +/// Adjust slashes in file names. Called after 'shellslash' was set. +void alist_slash_adjust(void) +{ + for (int i = 0; i < GARGCOUNT; i++) { + if (GARGLIST[i].ae_fname != NULL) { + slash_adjust(GARGLIST[i].ae_fname); + } + } + + FOR_ALL_TAB_WINDOWS(tp, wp) { + if (wp->w_alist != &global_alist) { + for (int i = 0; i < WARGCOUNT(wp); i++) { + if (WARGLIST(wp)[i].ae_fname != NULL) { + slash_adjust(WARGLIST(wp)[i].ae_fname); + } + } + } + } +} + +#endif + +/// Isolate one argument, taking backticks. +/// Changes the argument in-place, puts a NUL after it. Backticks remain. +/// +/// @return a pointer to the start of the next argument. +static char *do_one_arg(char *str) +{ + char *p; + bool inbacktick; + + inbacktick = false; + for (p = str; *str; str++) { + // When the backslash is used for escaping the special meaning of a + // character we need to keep it until wildcard expansion. + if (rem_backslash(str)) { + *p++ = *str++; + *p++ = *str; + } else { + // An item ends at a space not in backticks + if (!inbacktick && ascii_isspace(*str)) { + break; + } + if (*str == '`') { + inbacktick ^= true; + } + *p++ = *str; + } + } + str = skipwhite(str); + *p = NUL; + + return str; +} + +/// Separate the arguments in "str" and return a list of pointers in the +/// growarray "gap". +static void get_arglist(garray_T *gap, char *str, int escaped) +{ + ga_init(gap, (int)sizeof(char *), 20); + while (*str != NUL) { + GA_APPEND(char *, gap, str); + + // If str is escaped, don't handle backslashes or spaces + if (!escaped) { + return; + } + + // Isolate one argument, change it in-place, put a NUL after it. + str = do_one_arg(str); + } +} + +/// Parse a list of arguments (file names), expand them and return in +/// "fnames[fcountp]". When "wig" is true, removes files matching 'wildignore'. +/// +/// @return FAIL or OK. +int get_arglist_exp(char *str, int *fcountp, char ***fnamesp, bool wig) +{ + garray_T ga; + int i; + + get_arglist(&ga, str, true); + + if (wig) { + i = expand_wildcards(ga.ga_len, ga.ga_data, + fcountp, fnamesp, EW_FILE|EW_NOTFOUND|EW_NOTWILD); + } else { + i = gen_expand_wildcards(ga.ga_len, ga.ga_data, + fcountp, fnamesp, EW_FILE|EW_NOTFOUND|EW_NOTWILD); + } + + ga_clear(&ga); + return i; +} + +/// Check the validity of the arg_idx for each other window. +static void alist_check_arg_idx(void) +{ + FOR_ALL_TAB_WINDOWS(tp, win) { + if (win->w_alist == curwin->w_alist) { + check_arg_idx(win); + } + } +} + +/// Add files[count] to the arglist of the current window after arg "after". +/// The file names in files[count] must have been allocated and are taken over. +/// Files[] itself is not taken over. +/// +/// @param after: where to add: 0 = before first one +/// @param will_edit will edit adding argument +static void alist_add_list(int count, char **files, int after, bool will_edit) + FUNC_ATTR_NONNULL_ALL +{ + int old_argcount = ARGCOUNT; + ga_grow(&ALIST(curwin)->al_ga, count); + { + if (after < 0) { + after = 0; + } + if (after > ARGCOUNT) { + after = ARGCOUNT; + } + if (after < ARGCOUNT) { + memmove(&(ARGLIST[after + count]), &(ARGLIST[after]), + (size_t)(ARGCOUNT - after) * sizeof(aentry_T)); + } + for (int i = 0; i < count; i++) { + const int flags = BLN_LISTED | (will_edit ? BLN_CURBUF : 0); + ARGLIST[after + i].ae_fname = files[i]; + ARGLIST[after + i].ae_fnum = buflist_add(files[i], flags); + } + ALIST(curwin)->al_ga.ga_len += count; + if (old_argcount > 0 && curwin->w_arg_idx >= after) { + curwin->w_arg_idx += count; + } + return; + } +} + +/// @param str +/// @param what +/// AL_SET: Redefine the argument list to 'str'. +/// AL_ADD: add files in 'str' to the argument list after "after". +/// AL_DEL: remove files in 'str' from the argument list. +/// @param after +/// 0 means before first one +/// @param will_edit will edit added argument +/// +/// @return FAIL for failure, OK otherwise. +static int do_arglist(char *str, int what, int after, bool will_edit) + FUNC_ATTR_NONNULL_ALL +{ + garray_T new_ga; + int exp_count; + char **exp_files; + char *p; + int match; + int arg_escaped = true; + + // Set default argument for ":argadd" command. + if (what == AL_ADD && *str == NUL) { + if (curbuf->b_ffname == NULL) { + return FAIL; + } + str = curbuf->b_fname; + arg_escaped = false; + } + + // Collect all file name arguments in "new_ga". + get_arglist(&new_ga, str, arg_escaped); + + if (what == AL_DEL) { + regmatch_T regmatch; + bool didone; + + // Delete the items: use each item as a regexp and find a match in the + // argument list. + regmatch.rm_ic = p_fic; // ignore case when 'fileignorecase' is set + for (int i = 0; i < new_ga.ga_len && !got_int; i++) { + p = ((char **)new_ga.ga_data)[i]; + p = file_pat_to_reg_pat(p, NULL, NULL, false); + if (p == NULL) { + break; + } + regmatch.regprog = vim_regcomp(p, p_magic ? RE_MAGIC : 0); + if (regmatch.regprog == NULL) { + xfree(p); + break; + } + + didone = false; + for (match = 0; match < ARGCOUNT; match++) { + if (vim_regexec(®match, alist_name(&ARGLIST[match]), (colnr_T)0)) { + didone = true; + xfree(ARGLIST[match].ae_fname); + memmove(ARGLIST + match, ARGLIST + match + 1, + (size_t)(ARGCOUNT - match - 1) * sizeof(aentry_T)); + ALIST(curwin)->al_ga.ga_len--; + if (curwin->w_arg_idx > match) { + curwin->w_arg_idx--; + } + match--; + } + } + + vim_regfree(regmatch.regprog); + xfree(p); + if (!didone) { + semsg(_(e_nomatch2), ((char **)new_ga.ga_data)[i]); + } + } + ga_clear(&new_ga); + } else { + int i = expand_wildcards(new_ga.ga_len, new_ga.ga_data, + &exp_count, &exp_files, + EW_DIR|EW_FILE|EW_ADDSLASH|EW_NOTFOUND); + ga_clear(&new_ga); + if (i == FAIL || exp_count == 0) { + emsg(_(e_nomatch)); + return FAIL; + } + + if (what == AL_ADD) { + alist_add_list(exp_count, exp_files, after, will_edit); + xfree(exp_files); + } else { + assert(what == AL_SET); + alist_set(ALIST(curwin), exp_count, exp_files, will_edit, NULL, 0); + } + } + + alist_check_arg_idx(); + + return OK; +} + +/// Redefine the argument list. +void set_arglist(char *str) +{ + do_arglist(str, AL_SET, 0, false); +} + +/// @return true if window "win" is editing the file at the current argument +/// index. +bool editing_arg_idx(win_T *win) +{ + return !(win->w_arg_idx >= WARGCOUNT(win) + || (win->w_buffer->b_fnum + != WARGLIST(win)[win->w_arg_idx].ae_fnum + && (win->w_buffer->b_ffname == NULL + || !(path_full_compare(alist_name(&WARGLIST(win)[win->w_arg_idx]), + win->w_buffer->b_ffname, true, + true) & kEqualFiles)))); +} + +/// Check if window "win" is editing the w_arg_idx file in its argument list. +void check_arg_idx(win_T *win) +{ + if (WARGCOUNT(win) > 1 && !editing_arg_idx(win)) { + // We are not editing the current entry in the argument list. + // Set "arg_had_last" if we are editing the last one. + win->w_arg_idx_invalid = true; + if (win->w_arg_idx != WARGCOUNT(win) - 1 + && arg_had_last == false + && ALIST(win) == &global_alist + && GARGCOUNT > 0 + && win->w_arg_idx < GARGCOUNT + && (win->w_buffer->b_fnum == GARGLIST[GARGCOUNT - 1].ae_fnum + || (win->w_buffer->b_ffname != NULL + && (path_full_compare(alist_name(&GARGLIST[GARGCOUNT - 1]), + win->w_buffer->b_ffname, true, true) + & kEqualFiles)))) { + arg_had_last = true; + } + } else { + // We are editing the current entry in the argument list. + // Set "arg_had_last" if it's also the last one + win->w_arg_idx_invalid = false; + if (win->w_arg_idx == WARGCOUNT(win) - 1 && win->w_alist == &global_alist) { + arg_had_last = true; + } + } +} + +/// ":args", ":argslocal" and ":argsglobal". +void ex_args(exarg_T *eap) +{ + if (eap->cmdidx != CMD_args) { + alist_unlink(ALIST(curwin)); + if (eap->cmdidx == CMD_argglobal) { + ALIST(curwin) = &global_alist; + } else { // eap->cmdidx == CMD_arglocal + alist_new(); + } + } + + if (*eap->arg != NUL) { + // ":args file ..": define new argument list, handle like ":next" + // Also for ":argslocal file .." and ":argsglobal file ..". + ex_next(eap); + } else if (eap->cmdidx == CMD_args) { + // ":args": list arguments. + if (ARGCOUNT > 0) { + char **items = xmalloc(sizeof(char *) * (size_t)ARGCOUNT); + // Overwrite the command, for a short list there is no scrolling + // required and no wait_return(). + gotocmdline(true); + for (int i = 0; i < ARGCOUNT; i++) { + items[i] = alist_name(&ARGLIST[i]); + } + list_in_columns(items, ARGCOUNT, curwin->w_arg_idx); + xfree(items); + } + } else if (eap->cmdidx == CMD_arglocal) { + garray_T *gap = &curwin->w_alist->al_ga; + + // ":argslocal": make a local copy of the global argument list. + ga_grow(gap, GARGCOUNT); + for (int i = 0; i < GARGCOUNT; i++) { + if (GARGLIST[i].ae_fname != NULL) { + AARGLIST(curwin->w_alist)[gap->ga_len].ae_fname = xstrdup(GARGLIST[i].ae_fname); + AARGLIST(curwin->w_alist)[gap->ga_len].ae_fnum = GARGLIST[i].ae_fnum; + gap->ga_len++; + } + } + } +} + +/// ":previous", ":sprevious", ":Next" and ":sNext". +void ex_previous(exarg_T *eap) +{ + // If past the last one already, go to the last one. + if (curwin->w_arg_idx - (int)eap->line2 >= ARGCOUNT) { + do_argfile(eap, ARGCOUNT - 1); + } else { + do_argfile(eap, curwin->w_arg_idx - (int)eap->line2); + } +} + +/// ":rewind", ":first", ":sfirst" and ":srewind". +void ex_rewind(exarg_T *eap) +{ + do_argfile(eap, 0); +} + +/// ":last" and ":slast". +void ex_last(exarg_T *eap) +{ + do_argfile(eap, ARGCOUNT - 1); +} + +/// ":argument" and ":sargument". +void ex_argument(exarg_T *eap) +{ + int i; + + if (eap->addr_count > 0) { + i = (int)eap->line2 - 1; + } else { + i = curwin->w_arg_idx; + } + do_argfile(eap, i); +} + +/// Edit file "argn" of the argument lists. +void do_argfile(exarg_T *eap, int argn) +{ + int other; + char *p; + int old_arg_idx = curwin->w_arg_idx; + + if (argn < 0 || argn >= ARGCOUNT) { + if (ARGCOUNT <= 1) { + emsg(_("E163: There is only one file to edit")); + } else if (argn < 0) { + emsg(_("E164: Cannot go before first file")); + } 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; + } + } + + 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. +void ex_next(exarg_T *eap) +{ + int i; + + // check for changed buffer now, if this fails the argument list is not + // redefined. + if (buf_hide(curbuf) + || eap->cmdidx == CMD_snext + || !check_changed(curbuf, CCGD_AW + | (eap->forceit ? CCGD_FORCEIT : 0) + | CCGD_EXCMD)) { + if (*eap->arg != NUL) { // redefine file list + if (do_arglist(eap->arg, AL_SET, 0, true) == FAIL) { + return; + } + i = 0; + } else { + i = curwin->w_arg_idx + (int)eap->line2; + } + do_argfile(eap, i); + } +} + +/// ":argdedupe" +void ex_argdedupe(exarg_T *eap FUNC_ATTR_UNUSED) +{ + for (int i = 0; i < ARGCOUNT; i++) { + for (int j = i + 1; j < ARGCOUNT; j++) { + if (FNAMECMP(ARGLIST[i].ae_fname, ARGLIST[j].ae_fname) == 0) { + xfree(ARGLIST[j].ae_fname); + memmove(ARGLIST + j, ARGLIST + j + 1, + (size_t)(ARGCOUNT - j - 1) * sizeof(aentry_T)); + ARGCOUNT--; + + if (curwin->w_arg_idx == j) { + curwin->w_arg_idx = i; + } else if (curwin->w_arg_idx > j) { + curwin->w_arg_idx--; + } + + j--; + } + } + } +} + +/// ":argedit" +void ex_argedit(exarg_T *eap) +{ + int i = eap->addr_count ? (int)eap->line2 : curwin->w_arg_idx + 1; + // Whether curbuf will be reused, curbuf->b_ffname will be set. + bool curbuf_is_reusable = curbuf_reusable(); + + if (do_arglist(eap->arg, AL_ADD, i, true) == FAIL) { + return; + } + maketitle(); + + if (curwin->w_arg_idx == 0 + && (curbuf->b_ml.ml_flags & ML_EMPTY) + && (curbuf->b_ffname == NULL || curbuf_is_reusable)) { + i = 0; + } + // Edit the argument. + if (i < ARGCOUNT) { + do_argfile(eap, i); + } +} + +/// ":argadd" +void ex_argadd(exarg_T *eap) +{ + do_arglist(eap->arg, AL_ADD, + eap->addr_count > 0 ? (int)eap->line2 : curwin->w_arg_idx + 1, + false); + maketitle(); +} + +/// ":argdelete" +void ex_argdelete(exarg_T *eap) +{ + if (eap->addr_count > 0 || *eap->arg == NUL) { + // ":argdel" works like ":.argdel" + if (eap->addr_count == 0) { + if (curwin->w_arg_idx >= ARGCOUNT) { + emsg(_("E610: No argument to delete")); + return; + } + eap->line1 = eap->line2 = curwin->w_arg_idx + 1; + } else if (eap->line2 > ARGCOUNT) { + // ":1,4argdel": Delete all arguments in the range. + eap->line2 = ARGCOUNT; + } + linenr_T n = eap->line2 - eap->line1 + 1; + if (*eap->arg != NUL) { + // Can't have both a range and an argument. + emsg(_(e_invarg)); + } else if (n <= 0) { + // Don't give an error for ":%argdel" if the list is empty. + if (eap->line1 != 1 || eap->line2 != 0) { + emsg(_(e_invrange)); + } + } else { + for (linenr_T i = eap->line1; i <= eap->line2; i++) { + xfree(ARGLIST[i - 1].ae_fname); + } + memmove(ARGLIST + eap->line1 - 1, ARGLIST + eap->line2, + (size_t)(ARGCOUNT - eap->line2) * sizeof(aentry_T)); + ALIST(curwin)->al_ga.ga_len -= (int)n; + if (curwin->w_arg_idx >= eap->line2) { + curwin->w_arg_idx -= (int)n; + } else if (curwin->w_arg_idx > eap->line1) { + curwin->w_arg_idx = (int)eap->line1; + } + if (ARGCOUNT == 0) { + curwin->w_arg_idx = 0; + } else if (curwin->w_arg_idx >= ARGCOUNT) { + curwin->w_arg_idx = ARGCOUNT - 1; + } + } + } else { + do_arglist(eap->arg, AL_DEL, 0, false); + } + maketitle(); +} + +/// Function given to ExpandGeneric() to obtain the possible arguments of the +/// argedit and argdelete commands. +char *get_arglist_name(expand_T *xp FUNC_ATTR_UNUSED, int idx) +{ + if (idx >= ARGCOUNT) { + return NULL; + } + return alist_name(&ARGLIST[idx]); +} + +/// Get the file name for an argument list entry. +char *alist_name(aentry_T *aep) +{ + buf_T *bp; + + // Use the name from the associated buffer if it exists. + bp = buflist_findnr(aep->ae_fnum); + if (bp == NULL || bp->b_fname == NULL) { + return aep->ae_fname; + } + return bp->b_fname; +} + +/// do_arg_all(): Open up to 'count' windows, one for each argument. +/// +/// @param forceit hide buffers in current windows +/// @param keep_tabs keep current tabs, for ":tab drop file" +static void do_arg_all(int count, int forceit, int keep_tabs) +{ + uint8_t *opened; // Array of weight for which args are open: + // 0: not opened + // 1: opened in other tab + // 2: opened in curtab + // 3: opened in curtab and curwin + + int opened_len; // length of opened[] + int use_firstwin = false; // use first window for arglist + bool tab_drop_empty_window = false; + int split_ret = OK; + bool p_ea_save; + alist_T *alist; // argument list to be used + buf_T *buf; + tabpage_T *tpnext; + int had_tab = cmdmod.cmod_tab; + win_T *old_curwin, *last_curwin; + tabpage_T *old_curtab, *last_curtab; + win_T *new_curwin = NULL; + tabpage_T *new_curtab = NULL; + + assert(firstwin != NULL); // satisfy coverity + + if (ARGCOUNT <= 0) { + // Don't give an error message. We don't want it when the ":all" command is in the .vimrc. + return; + } + setpcmark(); + + opened_len = ARGCOUNT; + opened = xcalloc((size_t)opened_len, 1); + + // Autocommands may do anything to the argument list. Make sure it's not + // freed while we are working here by "locking" it. We still have to + // watch out for its size to be changed. + alist = curwin->w_alist; + alist->al_refcount++; + + old_curwin = curwin; + old_curtab = curtab; + + // Try closing all windows that are not in the argument list. + // Also close windows that are not full width; + // When 'hidden' or "forceit" set the buffer becomes hidden. + // Windows that have a changed buffer and can't be hidden won't be closed. + // When the ":tab" modifier was used do this for all tab pages. + if (had_tab > 0) { + goto_tabpage_tp(first_tabpage, true, true); + } + for (;;) { + win_T *wpnext = NULL; + tpnext = curtab->tp_next; + for (win_T *wp = firstwin; wp != NULL; wp = wpnext) { + int i; + wpnext = wp->w_next; + buf = wp->w_buffer; + if (buf->b_ffname == NULL + || (!keep_tabs && (buf->b_nwindows > 1 || wp->w_width != Columns))) { + i = opened_len; + } else { + // check if the buffer in this window is in the arglist + for (i = 0; i < opened_len; i++) { + if (i < alist->al_ga.ga_len + && (AARGLIST(alist)[i].ae_fnum == buf->b_fnum + || path_full_compare(alist_name(&AARGLIST(alist)[i]), + buf->b_ffname, + true, true) & kEqualFiles)) { + int weight = 1; + + if (old_curtab == curtab) { + weight++; + if (old_curwin == wp) { + weight++; + } + } + + if (weight > (int)opened[i]) { + opened[i] = (uint8_t)weight; + if (i == 0) { + if (new_curwin != NULL) { + new_curwin->w_arg_idx = opened_len; + } + new_curwin = wp; + new_curtab = curtab; + } + } else if (keep_tabs) { + i = opened_len; + } + + if (wp->w_alist != alist) { + // Use the current argument list for all windows containing a file from it. + alist_unlink(wp->w_alist); + wp->w_alist = alist; + wp->w_alist->al_refcount++; + } + break; + } + } + } + wp->w_arg_idx = i; + + if (i == opened_len && !keep_tabs) { // close this window + if (buf_hide(buf) || forceit || buf->b_nwindows > 1 + || !bufIsChanged(buf)) { + // If the buffer was changed, and we would like to hide it, try autowriting. + if (!buf_hide(buf) && buf->b_nwindows <= 1 && bufIsChanged(buf)) { + bufref_T bufref; + set_bufref(&bufref, buf); + (void)autowrite(buf, false); + // Check if autocommands removed the window. + if (!win_valid(wp) || !bufref_valid(&bufref)) { + wpnext = firstwin; // Start all over... + continue; + } + } + // don't close last window + if (ONE_WINDOW + && (first_tabpage->tp_next == NULL || !had_tab)) { + use_firstwin = true; + } else { + win_close(wp, !buf_hide(buf) && !bufIsChanged(buf), false); + // check if autocommands removed the next window + if (!win_valid(wpnext)) { + // start all over... + wpnext = firstwin; + } + } + } + } + } + + // Without the ":tab" modifier only do the current tab page. + if (had_tab == 0 || tpnext == NULL) { + break; + } + + // check if autocommands removed the next tab page + if (!valid_tabpage(tpnext)) { + tpnext = first_tabpage; // start all over... + } + goto_tabpage_tp(tpnext, true, true); + } + + // Open a window for files in the argument list that don't have one. + // ARGCOUNT may change while doing this, because of autocommands. + if (count > opened_len || count <= 0) { + count = opened_len; + } + + // Don't execute Win/Buf Enter/Leave autocommands here. + autocmd_no_enter++; + autocmd_no_leave++; + last_curwin = curwin; + last_curtab = curtab; + win_enter(lastwin, false); + // ":tab drop file" should re-use an empty window to avoid "--remote-tab" + // leaving an empty tab page when executed locally. + if (keep_tabs && buf_is_empty(curbuf) && curbuf->b_nwindows == 1 + && curbuf->b_ffname == NULL && !curbuf->b_changed) { + use_firstwin = true; + tab_drop_empty_window = true; + } + + for (int i = 0; i < count && !got_int; i++) { + if (alist == &global_alist && i == global_alist.al_ga.ga_len - 1) { + arg_had_last = true; + } + if (opened[i] > 0) { + // Move the already present window to below the current window + if (curwin->w_arg_idx != i) { + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_arg_idx == i) { + if (keep_tabs) { + new_curwin = wp; + new_curtab = curtab; + } else if (wp->w_frame->fr_parent != curwin->w_frame->fr_parent) { + emsg(_("E249: window layout changed unexpectedly")); + i = count; + break; + } else { + win_move_after(wp, curwin); + } + break; + } + } + } + } else if (split_ret == OK) { + // trigger events for tab drop + if (tab_drop_empty_window && i == count - 1) { + autocmd_no_enter--; + } + if (!use_firstwin) { // split current window + p_ea_save = p_ea; + p_ea = true; // use space from all windows + split_ret = win_split(0, WSP_ROOM | WSP_BELOW); + p_ea = p_ea_save; + if (split_ret == FAIL) { + continue; + } + } else { // first window: do autocmd for leaving this buffer + autocmd_no_leave--; + } + + // edit file "i" + curwin->w_arg_idx = i; + if (i == 0) { + new_curwin = curwin; + new_curtab = curtab; + } + (void)do_ecmd(0, alist_name(&AARGLIST(alist)[i]), NULL, NULL, ECMD_ONE, + ((buf_hide(curwin->w_buffer) + || bufIsChanged(curwin->w_buffer)) + ? ECMD_HIDE : 0) + ECMD_OLDBUF, + curwin); + if (tab_drop_empty_window && i == count - 1) { + autocmd_no_enter++; + } + if (use_firstwin) { + autocmd_no_leave++; + } + use_firstwin = false; + } + os_breakcheck(); + + // When ":tab" was used open a new tab for a new window repeatedly. + if (had_tab > 0 && tabpage_index(NULL) <= p_tpm) { + cmdmod.cmod_tab = 9999; + } + } + + // Remove the "lock" on the argument list. + alist_unlink(alist); + + autocmd_no_enter--; + // restore last referenced tabpage's curwin + if (last_curtab != new_curtab) { + if (valid_tabpage(last_curtab)) { + goto_tabpage_tp(last_curtab, true, true); + } + if (win_valid(last_curwin)) { + win_enter(last_curwin, false); + } + } + // to window with first arg + if (valid_tabpage(new_curtab)) { + goto_tabpage_tp(new_curtab, true, true); + } + if (win_valid(new_curwin)) { + win_enter(new_curwin, false); + } + + autocmd_no_leave--; + xfree(opened); +} + +/// ":all" and ":sall". +/// Also used for ":tab drop file ..." after setting the argument list. +void ex_all(exarg_T *eap) +{ + if (eap->addr_count == 0) { + eap->line2 = 9999; + } + do_arg_all((int)eap->line2, eap->forceit, eap->cmdidx == CMD_drop); +} + +/// Concatenate all files in the argument list, separated by spaces, and return +/// it in one allocated string. +/// Spaces and backslashes in the file names are escaped with a backslash. +char *arg_all(void) +{ + char *retval = NULL; + + // Do this loop two times: + // first time: compute the total length + // second time: concatenate the names + for (;;) { + int len = 0; + for (int idx = 0; idx < ARGCOUNT; idx++) { + char *p = alist_name(&ARGLIST[idx]); + if (p == NULL) { + continue; + } + if (len > 0) { + // insert a space in between names + if (retval != NULL) { + retval[len] = ' '; + } + len++; + } + for (; *p != NUL; p++) { + if (*p == ' ' +#ifndef BACKSLASH_IN_FILENAME + || *p == '\\' +#endif + || *p == '`') { + // insert a backslash + if (retval != NULL) { + retval[len] = '\\'; + } + len++; + } + if (retval != NULL) { + retval[len] = *p; + } + len++; + } + } + + // second time: break here + if (retval != NULL) { + retval[len] = NUL; + break; + } + + // allocate memory + retval = xmalloc((size_t)len + 1); + } + + return retval; +} + +/// "argc([window id])" function +void f_argc(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + if (argvars[0].v_type == VAR_UNKNOWN) { + // use the current window + rettv->vval.v_number = ARGCOUNT; + } else if (argvars[0].v_type == VAR_NUMBER + && tv_get_number(&argvars[0]) == -1) { + // use the global argument list + rettv->vval.v_number = GARGCOUNT; + } else { + // use the argument list of the specified window + win_T *wp = find_win_by_nr_or_id(&argvars[0]); + if (wp != NULL) { + rettv->vval.v_number = WARGCOUNT(wp); + } else { + rettv->vval.v_number = -1; + } + } +} + +/// "argidx()" function +void f_argidx(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + rettv->vval.v_number = curwin->w_arg_idx; +} + +/// "arglistid()" function +void f_arglistid(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + rettv->vval.v_number = -1; + win_T *wp = find_tabwin(&argvars[0], &argvars[1]); + if (wp != NULL) { + rettv->vval.v_number = wp->w_alist->id; + } +} + +/// Get the argument list for a given window +static void get_arglist_as_rettv(aentry_T *arglist, int argcount, typval_T *rettv) +{ + tv_list_alloc_ret(rettv, argcount); + if (arglist != NULL) { + for (int idx = 0; idx < argcount; idx++) { + tv_list_append_string(rettv->vval.v_list, + (const char *)alist_name(&arglist[idx]), -1); + } + } +} + +/// "argv(nr)" function +void f_argv(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + aentry_T *arglist = NULL; + int argcount = -1; + + if (argvars[0].v_type != VAR_UNKNOWN) { + if (argvars[1].v_type == VAR_UNKNOWN) { + arglist = ARGLIST; + argcount = ARGCOUNT; + } else if (argvars[1].v_type == VAR_NUMBER + && tv_get_number(&argvars[1]) == -1) { + arglist = GARGLIST; + argcount = GARGCOUNT; + } else { + win_T *wp = find_win_by_nr_or_id(&argvars[1]); + if (wp != NULL) { + // Use the argument list of the specified window + arglist = WARGLIST(wp); + argcount = WARGCOUNT(wp); + } + } + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + int idx = (int)tv_get_number_chk(&argvars[0], NULL); + if (arglist != NULL && idx >= 0 && idx < argcount) { + rettv->vval.v_string = xstrdup((const char *)alist_name(&arglist[idx])); + } else if (idx == -1) { + get_arglist_as_rettv(arglist, argcount, rettv); + } + } else { + get_arglist_as_rettv(ARGLIST, ARGCOUNT, rettv); + } +} diff --git a/src/nvim/arglist.h b/src/nvim/arglist.h new file mode 100644 index 0000000000..b2e0f411d4 --- /dev/null +++ b/src/nvim/arglist.h @@ -0,0 +1,11 @@ +#ifndef NVIM_ARGLIST_H +#define NVIM_ARGLIST_H + +#include "nvim/eval/typval.h" +#include "nvim/ex_cmds_defs.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "arglist.h.generated.h" +#endif + +#endif // NVIM_ARGLIST_H diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c index bbb044fba3..1c1de214cd 100644 --- a/src/nvim/autocmd.c +++ b/src/nvim/autocmd.c @@ -11,20 +11,25 @@ #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/userfunc.h" #include "nvim/eval/vars.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/grid.h" #include "nvim/insexpand.h" #include "nvim/lua/executor.h" #include "nvim/map.h" -#include "nvim/option.h" +#include "nvim/optionstr.h" #include "nvim/os/input.h" +#include "nvim/profile.h" #include "nvim/regexp.h" +#include "nvim/runtime.h" #include "nvim/search.h" #include "nvim/state.h" #include "nvim/ui_compositor.h" @@ -681,7 +686,7 @@ const char *event_nr2name(event_T event) static bool event_ignored(event_T event) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { - char *p = (char *)p_ei; + char *p = p_ei; while (*p != NUL) { if (STRNICMP(p, "all", 3) == 0 && (p[3] == NUL || p[3] == ',')) { @@ -698,7 +703,7 @@ static bool event_ignored(event_T event) // Return OK when the contents of p_ei is valid, FAIL otherwise. int check_ei(void) { - char *p = (char *)p_ei; + char *p = p_ei; while (*p) { if (STRNICMP(p, "all", 3) == 0 && (p[3] == NUL || p[3] == ',')) { @@ -719,8 +724,8 @@ int check_ei(void) // Returns the old value of 'eventignore' in allocated memory. char *au_event_disable(char *what) { - char *save_ei = (char *)vim_strsave(p_ei); - char *new_ei = (char *)vim_strnsave(p_ei, STRLEN(p_ei) + STRLEN(what)); + char *save_ei = xstrdup(p_ei); + char *new_ei = xstrnsave(p_ei, STRLEN(p_ei) + STRLEN(what)); if (*what == ',' && *p_ei == NUL) { STRCPY(new_ei, what + 1); } else { @@ -1114,6 +1119,7 @@ int autocmd_register(int64_t id, event_T event, char *pat, int patlen, int group 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; } @@ -1141,7 +1147,7 @@ int autocmd_register(int64_t id, event_T event, char *pat, int patlen, int group ac->id = id; ac->exec = aucmd_exec_copy(aucmd); ac->script_ctx = current_sctx; - ac->script_ctx.sc_lnum += sourcing_lnum; + ac->script_ctx.sc_lnum += SOURCING_LNUM; nlua_set_sctx(&ac->script_ctx); ac->next = NULL; ac->once = once; @@ -1715,9 +1721,9 @@ bool apply_autocmds_group(event_T event, char *fname, char *fname_io, bool force fname = NULL; } else { if (event == EVENT_SYNTAX) { - fname = (char *)buf->b_p_syn; + fname = buf->b_p_syn; } else if (event == EVENT_FILETYPE) { - fname = (char *)buf->b_p_ft; + fname = buf->b_p_ft; } else { if (buf->b_sfname != NULL) { sfname = xstrdup(buf->b_sfname); @@ -1769,10 +1775,9 @@ bool apply_autocmds_group(event_T event, char *fname, char *fname_io, bool force // Don't redraw while doing autocommands. RedrawingDisabled++; - char *save_sourcing_name = sourcing_name; - sourcing_name = NULL; // don't free this one - linenr_T save_sourcing_lnum = sourcing_lnum; - sourcing_lnum = 0; // no line number here + + // name and lnum are filled in later + estack_push(ETYPE_AUCMD, NULL, 0); const sctx_T save_current_sctx = current_sctx; @@ -1807,16 +1812,15 @@ bool apply_autocmds_group(event_T event, char *fname, char *fname_io, bool force char *tail = path_tail(fname); // Find first autocommand that matches - AutoPatCmd patcmd; - patcmd.curpat = first_autopat[(int)event]; - patcmd.nextcmd = NULL; - patcmd.group = group; - patcmd.fname = fname; - patcmd.sfname = sfname; - patcmd.tail = tail; - patcmd.event = event; - patcmd.arg_bufnr = autocmd_bufnr; - patcmd.next = NULL; + AutoPatCmd patcmd = { + .curpat = first_autopat[(int)event], + .group = group, + .fname = fname, + .sfname = sfname, + .tail = tail, + .event = event, + .arg_bufnr = autocmd_bufnr, + }; auto_next_pat(&patcmd, false); // found one, start executing the autocommands @@ -1876,9 +1880,8 @@ bool apply_autocmds_group(event_T event, char *fname, char *fname_io, bool force autocmd_busy = save_autocmd_busy; filechangeshell_busy = false; autocmd_nested = save_autocmd_nested; - xfree(sourcing_name); - sourcing_name = save_sourcing_name; - sourcing_lnum = save_sourcing_lnum; + xfree(SOURCING_NAME); + estack_pop(); xfree(autocmd_fname); autocmd_fname = save_autocmd_fname; autocmd_bufnr = save_autocmd_bufnr; @@ -1982,7 +1985,11 @@ void auto_next_pat(AutoPatCmd *apc, int stop_at_last) AutoCmd *cp; char *s; - XFREE_CLEAR(sourcing_name); + estack_T *const entry = ((estack_T *)exestack.ga_data) + exestack.ga_len - 1; + + // Clear the exestack entry for this ETYPE_AUCMD entry. + XFREE_CLEAR(entry->es_name); + entry->es_info.aucmd = NULL; for (ap = apc->curpat; ap != NULL && !got_int; ap = ap->next) { apc->curpat = NULL; @@ -2007,14 +2014,18 @@ void auto_next_pat(AutoPatCmd *apc, int stop_at_last) const size_t sourcing_name_len = (STRLEN(s) + strlen(name) + (size_t)ap->patlen + 1); - sourcing_name = xmalloc(sourcing_name_len); - snprintf(sourcing_name, sourcing_name_len, s, name, ap->pat); + char *const namep = xmalloc(sourcing_name_len); + snprintf(namep, sourcing_name_len, s, name, ap->pat); if (p_verbose >= 8) { verbose_enter(); - smsg(_("Executing %s"), sourcing_name); + smsg(_("Executing %s"), namep); verbose_leave(); } + // Update the exestack entry for this autocmd. + entry->es_name = namep; + entry->es_info.aucmd = apc; + apc->curpat = ap; apc->nextcmd = ap->cmds; // mark last command @@ -2047,7 +2058,7 @@ static bool call_autocmd_callback(const AutoCmd *ac, const AutoPatCmd *apc) PUT(data, "buf", INTEGER_OBJ(autocmd_bufnr)); if (apc->data) { - PUT(data, "data", copy_object(*apc->data)); + PUT(data, "data", copy_object(*apc->data, NULL)); } int group = apc->curpat->group; @@ -2146,6 +2157,7 @@ char *getnextac(int c, void *cookie, int indent, bool do_concat) // lua code, so that it works properly autocmd_nested = ac->nested; current_sctx = ac->script_ctx; + acp->script_ctx = current_sctx; if (ac->exec.type == CALLABLE_CB) { if (call_autocmd_callback(ac, acp)) { diff --git a/src/nvim/autocmd.h b/src/nvim/autocmd.h index a085a03455..75a8a7aaa1 100644 --- a/src/nvim/autocmd.h +++ b/src/nvim/autocmd.h @@ -22,19 +22,21 @@ typedef struct { bool save_VIsual_active; ///< saved VIsual_active } aco_save_T; -typedef struct AutoCmd { +typedef struct AutoCmd_S AutoCmd; +struct AutoCmd_S { AucmdExecutable exec; bool once; // "One shot": removed after execution bool nested; // If autocommands nest here bool last; // last command in list int64_t id; // ID used for uniquely tracking an autocmd. - sctx_T script_ctx; // script context where defined + sctx_T script_ctx; // script context where it is defined char *desc; // Description for the autocmd. - struct AutoCmd *next; // Next AutoCmd in list -} AutoCmd; + AutoCmd *next; // Next AutoCmd in list +}; -typedef struct AutoPat { - struct AutoPat *next; // next AutoPat in AutoPat list; MUST +typedef struct AutoPat_S AutoPat; +struct AutoPat_S { + AutoPat *next; // next AutoPat in AutoPat list; MUST // be the first entry char *pat; // pattern as typed (NULL when pattern // has been removed) @@ -45,10 +47,11 @@ typedef struct AutoPat { int buflocal_nr; // !=0 for buffer-local AutoPat char allow_dirs; // Pattern may match whole path char last; // last pattern for apply_autocmds() -} AutoPat; +}; /// Struct used to keep status while executing autocommands for an event. -typedef struct AutoPatCmd { +typedef struct AutoPatCmd_S AutoPatCmd; +struct AutoPatCmd_S { AutoPat *curpat; // next AutoPat to examine AutoCmd *nextcmd; // next AutoCmd to execute int group; // group being used @@ -56,10 +59,11 @@ typedef struct AutoPatCmd { char *sfname; // sfname to match with char *tail; // tail of fname event_T event; // current event + sctx_T script_ctx; // script context where it is defined int arg_bufnr; // initially equal to <abuf>, set to zero when buf is deleted Object *data; // arbitrary data - struct AutoPatCmd *next; // chain of active apc-s for auto-invalidation -} AutoPatCmd; + AutoPatCmd *next; // chain of active apc-s for auto-invalidation +}; // Set by the apply_autocmds_group function if the given event is equal to // EVENT_FILETYPE. Used by the readfile function in order to determine if diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index f23a1caf8b..514be4c56b 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -25,17 +25,21 @@ #include <string.h> #include "nvim/api/private/helpers.h" +#include "nvim/arglist.h" #include "nvim/ascii.h" #include "nvim/assert.h" +#include "nvim/autocmd.h" #include "nvim/buffer.h" #include "nvim/buffer_updates.h" #include "nvim/change.h" #include "nvim/channel.h" #include "nvim/charset.h" +#include "nvim/cmdexpand.h" #include "nvim/cursor.h" #include "nvim/decoration.h" #include "nvim/diff.h" #include "nvim/digraph.h" +#include "nvim/drawscreen.h" #include "nvim/eval.h" #include "nvim/eval/vars.h" #include "nvim/ex_cmds.h" @@ -50,6 +54,7 @@ #include "nvim/garray.h" #include "nvim/getchar.h" #include "nvim/hashtab.h" +#include "nvim/help.h" #include "nvim/highlight_group.h" #include "nvim/indent.h" #include "nvim/indent_c.h" @@ -62,6 +67,7 @@ #include "nvim/message.h" #include "nvim/move.h" #include "nvim/option.h" +#include "nvim/optionstr.h" #include "nvim/os/input.h" #include "nvim/os/os.h" #include "nvim/os/time.h" @@ -69,9 +75,10 @@ #include "nvim/plines.h" #include "nvim/quickfix.h" #include "nvim/regexp.h" -#include "nvim/screen.h" +#include "nvim/runtime.h" #include "nvim/sign.h" #include "nvim/spell.h" +#include "nvim/statusline.h" #include "nvim/strings.h" #include "nvim/syntax.h" #include "nvim/ui.h" @@ -85,11 +92,6 @@ # include "buffer.c.generated.h" #endif -// Determines how deeply nested %{} blocks will be evaluated in statusline. -#define MAX_STL_EVAL_DEPTH 100 - -static char *msg_loclist = N_("[Location List]"); -static char *msg_qflist = N_("[Quickfix List]"); 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"); @@ -163,11 +165,12 @@ static int read_buffer(int read_stdin, exarg_T *eap, int flags) /// /// @param read_stdin read file from stdin /// @param eap for forced 'ff' and 'fenc' or NULL -/// @param flags extra flags for readfile() +/// @param flags_arg extra flags for readfile() /// /// @return FAIL for failure, OK otherwise. -int open_buffer(int read_stdin, exarg_T *eap, int flags) +int open_buffer(int read_stdin, exarg_T *eap, int flags_arg) { + int flags = flags_arg; int retval = OK; bufref_T old_curbuf; long old_tw = curbuf->b_p_tw; @@ -222,6 +225,13 @@ int open_buffer(int read_stdin, exarg_T *eap, int flags) // mark cursor position as being invalid curwin->w_valid = 0; + // A buffer without an actual file should not use the buffer name to read a + // file. + if (bt_nofileread(curbuf)) { + flags |= READ_NOFILE; + } + + // Read the file if there is one. if (curbuf->b_ffname != NULL) { #ifdef UNIX int save_bin = curbuf->b_p_bin; @@ -793,8 +803,8 @@ static void free_buffer(buf_T *buf) if (autocmd_busy) { // Do not free the buffer structure while autocommands are executing, // it's still needed. Free it when autocmd_busy is reset. - memset(&buf->b_namedm[0], 0, sizeof(buf->b_namedm)); - memset(&buf->b_changelist[0], 0, sizeof(buf->b_changelist)); + CLEAR_FIELD(buf->b_namedm); + CLEAR_FIELD(buf->b_changelist); buf->b_next = au_pending_free_buf; au_pending_free_buf = buf; } else { @@ -802,6 +812,18 @@ static void free_buffer(buf_T *buf) } } +/// Free the b_wininfo list for buffer "buf". +static void clear_wininfo(buf_T *buf) +{ + wininfo_T *wip; + + while (buf->b_wininfo != NULL) { + wip = buf->b_wininfo; + buf->b_wininfo = wip->wi_next; + free_wininfo(wip, buf); + } +} + /// Free stuff in the buffer for ":bdel" and when wiping out the buffer. /// /// @param buf Buffer pointer @@ -836,18 +858,6 @@ static void free_buffer_stuff(buf_T *buf, int free_flags) buf_updates_unload(buf, false); } -/// Free the b_wininfo list for buffer "buf". -static void clear_wininfo(buf_T *buf) -{ - wininfo_T *wip; - - while (buf->b_wininfo != NULL) { - wip = buf->b_wininfo; - buf->b_wininfo = wip->wi_next; - free_wininfo(wip, buf); - } -} - /// Go to another buffer. Handles the result of the ATTENTION dialog. void goto_buffer(exarg_T *eap, int start, int dir, int count) { @@ -1532,6 +1542,15 @@ void set_curbuf(buf_T *buf, int action) /// be pointing to freed memory. void enter_buffer(buf_T *buf) { + // when closing the current buffer stop Visual mode + if (VIsual_active +#if defined(EXITFREE) + && !entered_free_all_mem +#endif + ) { + end_visual_mode(); + } + // Get the buffer in the current window. curwin->w_buffer = buf; curbuf = buf; @@ -1612,7 +1631,7 @@ void enter_buffer(buf_T *buf) } curbuf->b_last_used = time(NULL); - redraw_later(curwin, NOT_VALID); + redraw_later(curwin, UPD_NOT_VALID); } /// Change to the directory of the current buffer. @@ -2595,7 +2614,7 @@ void get_winopts(buf_T *buf) } if (curwin->w_float_config.style == kWinStyleMinimal) { - didset_window_options(curwin); + didset_window_options(curwin, false); win_set_minimal_style(curwin); } @@ -2603,7 +2622,7 @@ void get_winopts(buf_T *buf) if (p_fdls >= 0) { curwin->w_p_fdl = p_fdls; } - didset_window_options(curwin); + didset_window_options(curwin, false); } /// Find the mark for the buffer 'buf' for the current window. @@ -3155,14 +3174,14 @@ void maketitle(void) use_sandbox = was_set_insecurely(curwin, "titlestring", 0); build_stl_str_hl(curwin, buf, sizeof(buf), - (char *)p_titlestring, use_sandbox, + p_titlestring, use_sandbox, 0, maxlen, NULL, NULL); title_str = buf; if (called_emsg > called_emsg_before) { set_string_option_direct("titlestring", -1, "", OPT_FREE, SID_ERROR); } } else { - title_str = (char *)p_titlestring; + title_str = p_titlestring; } } else { // Format: "fname + (path) (1 of 2) - VIM". @@ -3269,13 +3288,13 @@ void maketitle(void) use_sandbox = was_set_insecurely(curwin, "iconstring", 0); build_stl_str_hl(curwin, icon_str, sizeof(buf), - (char *)p_iconstring, use_sandbox, + p_iconstring, use_sandbox, 0, 0, NULL, NULL); if (called_emsg > called_emsg_before) { set_string_option_direct("iconstring", -1, "", OPT_FREE, SID_ERROR); } } else { - icon_str = (char *)p_iconstring; + icon_str = p_iconstring; } } else { char *buf_p; @@ -3346,1192 +3365,6 @@ void free_titles(void) #endif -/// Enumeration specifying the valid numeric bases that can -/// be used when printing numbers in the status line. -typedef enum { - kNumBaseDecimal = 10, - kNumBaseHexadecimal = 16, -} NumberBase; - -/// Build a string from the status line items in "fmt". -/// Return length of string in screen cells. -/// -/// Normally works for window "wp", except when working for 'tabline' then it -/// is "curwin". -/// -/// Items are drawn interspersed with the text that surrounds it -/// Specials: %-<wid>(xxx%) => group, %= => separation marker, %< => truncation -/// Item: %-<minwid>.<maxwid><itemch> All but <itemch> are optional -/// -/// If maxwidth is not zero, the string will be filled at any middle marker -/// or truncated if too long, fillchar is used for all whitespace. -/// -/// @param wp The window to build a statusline for -/// @param out The output buffer to write the statusline to -/// Note: This should not be NameBuff -/// @param outlen The length of the output buffer -/// @param fmt The statusline format string -/// @param use_sandbox Use a sandboxed environment when evaluating fmt -/// @param fillchar Character to use when filling empty space in the statusline -/// @param maxwidth The maximum width to make the statusline -/// @param hltab HL attributes (can be NULL) -/// @param tabtab Tab clicks definition (can be NULL). -/// -/// @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) -{ - static size_t stl_items_len = 20; // Initial value, grows as needed. - static stl_item_t *stl_items = NULL; - static int *stl_groupitems = NULL; - static stl_hlrec_t *stl_hltab = NULL; - static StlClickRecord *stl_tabtab = NULL; - static int *stl_separator_locations = NULL; - -#define TMPLEN 70 - char buf_tmp[TMPLEN]; - char win_tmp[TMPLEN]; - char *usefmt = fmt; - const int save_must_redraw = must_redraw; - const int save_redr_type = curwin->w_redr_type; - - if (stl_items == NULL) { - stl_items = xmalloc(sizeof(stl_item_t) * stl_items_len); - stl_groupitems = xmalloc(sizeof(int) * stl_items_len); - - // Allocate one more, because the last element is used to indicate the - // end of the list. - stl_hltab = xmalloc(sizeof(stl_hlrec_t) * (stl_items_len + 1)); - stl_tabtab = xmalloc(sizeof(StlClickRecord) * (stl_items_len + 1)); - - stl_separator_locations = xmalloc(sizeof(int) * stl_items_len); - } - - // 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] == '!') { - typval_T tv = { - .v_type = VAR_NUMBER, - .vval.v_number = wp->handle, - }; - set_var(S_LEN("g:statusline_winid"), &tv, false); - - usefmt = eval_to_string_safe(fmt + 2, NULL, use_sandbox); - if (usefmt == NULL) { - usefmt = fmt; - } - - do_unlet(S_LEN("g:statusline_winid"), true); - } - - if (fillchar == 0) { - fillchar = ' '; - } - - // The cursor in windows other than the current one isn't always - // up-to-date, esp. because of autocommands and timers. - linenr_T lnum = wp->w_cursor.lnum; - if (lnum > wp->w_buffer->b_ml.ml_line_count) { - lnum = wp->w_buffer->b_ml.ml_line_count; - wp->w_cursor.lnum = lnum; - } - - // Get line & check if empty (cursorpos will show "0-1"). - const char *line_ptr = (char *)ml_get_buf(wp->w_buffer, lnum, false); - bool empty_line = (*line_ptr == NUL); - - // Get the byte value now, in case we need it below. This is more - // efficient than making a copy of the line. - int byteval; - const size_t len = STRLEN(line_ptr); - if (wp->w_cursor.col > (colnr_T)len) { - // Line may have changed since checking the cursor column, or the lnum - // was adjusted above. - wp->w_cursor.col = (colnr_T)len; - wp->w_cursor.coladd = 0; - byteval = 0; - } else { - byteval = utf_ptr2char(line_ptr + wp->w_cursor.col); - } - - int groupdepth = 0; - int evaldepth = 0; - - int curitem = 0; - bool prevchar_isflag = true; - bool prevchar_isitem = false; - - // out_p is the current position in the output buffer - char *out_p = out; - - // out_end_p is the last valid character in the output buffer - // Note: The null termination character must occur here or earlier, - // so any user-visible characters must occur before here. - char *out_end_p = (out + outlen) - 1; - - // Proceed character by character through the statusline format string - // fmt_p is the current position in the input buffer - for (char *fmt_p = usefmt; *fmt_p != NUL;) { - if (curitem == (int)stl_items_len) { - size_t new_len = stl_items_len * 3 / 2; - - stl_items = xrealloc(stl_items, sizeof(stl_item_t) * new_len); - stl_groupitems = xrealloc(stl_groupitems, sizeof(int) * new_len); - stl_hltab = xrealloc(stl_hltab, sizeof(stl_hlrec_t) * (new_len + 1)); - stl_tabtab = xrealloc(stl_tabtab, sizeof(StlClickRecord) * (new_len + 1)); - stl_separator_locations = - xrealloc(stl_separator_locations, sizeof(int) * new_len); - - stl_items_len = new_len; - } - - if (*fmt_p != '%') { - prevchar_isflag = prevchar_isitem = false; - } - - // Copy the formatting verbatim until we reach the end of the string - // or find a formatting item (denoted by `%`) - // or run out of room in our output buffer. - while (*fmt_p != NUL && *fmt_p != '%' && out_p < out_end_p) { - *out_p++ = *fmt_p++; - } - - // If we have processed the entire format string or run out of - // room in our output buffer, exit the loop. - if (*fmt_p == NUL || out_p >= out_end_p) { - break; - } - - // The rest of this loop will handle a single `%` item. - // Note: We increment here to skip over the `%` character we are currently - // on so we can process the item's contents. - fmt_p++; - - // Ignore `%` at the end of the format string - if (*fmt_p == NUL) { - break; - } - - // Two `%` in a row is the escape sequence to print a - // single `%` in the output buffer. - if (*fmt_p == '%') { - *out_p++ = *fmt_p++; - prevchar_isflag = prevchar_isitem = false; - continue; - } - - // STL_SEPARATE: Separation place between left and right aligned items. - if (*fmt_p == STL_SEPARATE) { - fmt_p++; - // Ignored when we are inside of a grouping - if (groupdepth > 0) { - continue; - } - stl_items[curitem].type = Separate; - stl_items[curitem++].start = out_p; - continue; - } - - // STL_TRUNCMARK: Where to begin truncating if the statusline is too long. - if (*fmt_p == STL_TRUNCMARK) { - fmt_p++; - stl_items[curitem].type = Trunc; - stl_items[curitem++].start = out_p; - continue; - } - - // The end of a grouping - if (*fmt_p == ')') { - fmt_p++; - // Ignore if we are not actually inside a group currently - if (groupdepth < 1) { - continue; - } - groupdepth--; - - // Determine how long the group is. - // Note: We set the current output position to null - // so `vim_strsize` will work. - char *t = stl_items[stl_groupitems[groupdepth]].start; - *out_p = NUL; - long group_len = vim_strsize(t); - - // If the group contained internal items - // and the group did not have a minimum width, - // and if there were no normal items in the group, - // move the output pointer back to where the group started. - // Note: This erases any non-item characters that were in the group. - // Otherwise there would be no reason to do this step. - if (curitem > stl_groupitems[groupdepth] + 1 - && stl_items[stl_groupitems[groupdepth]].minwid == 0) { - // remove group if all items are empty and highlight group - // doesn't change - int group_start_userhl = 0; - int group_end_userhl = 0; - int n; - for (n = stl_groupitems[groupdepth] - 1; n >= 0; n--) { - if (stl_items[n].type == Highlight) { - group_start_userhl = group_end_userhl = stl_items[n].minwid; - break; - } - } - for (n = stl_groupitems[groupdepth] + 1; n < curitem; n++) { - if (stl_items[n].type == Normal) { - break; - } - if (stl_items[n].type == Highlight) { - group_end_userhl = stl_items[n].minwid; - } - } - if (n == curitem && group_start_userhl == group_end_userhl) { - // empty group - out_p = t; - group_len = 0; - for (n = stl_groupitems[groupdepth] + 1; n < curitem; n++) { - // do not use the highlighting from the removed group - if (stl_items[n].type == Highlight) { - stl_items[n].type = Empty; - } - // adjust the start position of TabPage to the next - // item position - if (stl_items[n].type == TabPage) { - stl_items[n].start = out_p; - } - } - } - } - - // If the group is longer than it is allowed to be - // truncate by removing bytes from the start of the group text. - if (group_len > stl_items[stl_groupitems[groupdepth]].maxwid) { - // { Determine the number of bytes to remove - - // Find the first character that should be included. - long n = 0; - while (group_len >= stl_items[stl_groupitems[groupdepth]].maxwid) { - group_len -= ptr2cells(t + n); - n += utfc_ptr2len(t + n); - } - // } - - // Prepend the `<` to indicate that the output was truncated. - *t = '<'; - - // { Move the truncated output - memmove(t + 1, t + n, (size_t)(out_p - (t + n))); - out_p = out_p - n + 1; - // Fill up space left over by half a double-wide char. - while (++group_len < stl_items[stl_groupitems[groupdepth]].minwid) { - MB_CHAR2BYTES(fillchar, out_p); - } - // } - - // correct the start of the items for the truncation - for (int idx = stl_groupitems[groupdepth] + 1; idx < curitem; idx++) { - // Shift everything back by the number of removed bytes - // Minus one for the leading '<' added above. - stl_items[idx].start -= n - 1; - - // If the item was partially or completely truncated, set its - // start to the start of the group - if (stl_items[idx].start < t) { - stl_items[idx].start = t; - } - } - // 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; - // If the group is left-aligned, add characters to the right. - if (min_group_width < 0) { - min_group_width = 0 - min_group_width; - while (group_len++ < min_group_width && out_p < out_end_p) { - MB_CHAR2BYTES(fillchar, out_p); - } - // If the group is right-aligned, shift everything to the right and - // prepend with filler characters. - } else { - // { Move the group to the right - 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); - } - out_p += group_len; - // } - - // Adjust item start positions - for (int n = stl_groupitems[groupdepth] + 1; n < curitem; n++) { - stl_items[n].start += group_len; - } - - // Prepend the fill characters - for (; group_len > 0; group_len--) { - MB_CHAR2BYTES(fillchar, t); - } - } - } - continue; - } - int minwid = 0; - int maxwid = 9999; - bool left_align = false; - - // Denotes that numbers should be left-padded with zeros - bool zeropad = (*fmt_p == '0'); - if (zeropad) { - fmt_p++; - } - - // Denotes that the item should be left-aligned. - // This is tracked by using a negative length. - if (*fmt_p == '-') { - fmt_p++; - left_align = true; - } - - // The first digit group is the item's min width - if (ascii_isdigit(*fmt_p)) { - minwid = getdigits_int(&fmt_p, false, 0); - } - - // User highlight groups override the min width field - // to denote the styling to use. - if (*fmt_p == STL_USER_HL) { - stl_items[curitem].type = Highlight; - stl_items[curitem].start = out_p; - stl_items[curitem].minwid = minwid > 9 ? 1 : minwid; - fmt_p++; - curitem++; - continue; - } - - // TABPAGE pairs are used to denote a region that when clicked will - // either switch to or close a tab. - // - // Ex: tabline=%0Ttab\ zero%X - // This tabline has a TABPAGENR item with minwid `0`, - // which is then closed with a TABCLOSENR item. - // Clicking on this region with mouse enabled will switch to tab 0. - // Setting the minwid to a different value will switch - // to that tab, if it exists - // - // Ex: tabline=%1Xtab\ one%X - // This tabline has a TABCLOSENR item with minwid `1`, - // which is then closed with a TABCLOSENR item. - // Clicking on this region with mouse enabled will close tab 0. - // This is determined by the following formula: - // tab to close = (1 - minwid) - // This is because for TABPAGENR we use `minwid` = `tab number`. - // For TABCLOSENR we store the tab number as a negative value. - // Because 0 is a valid TABPAGENR value, we have to - // start our numbering at `-1`. - // So, `-1` corresponds to us wanting to close tab `0` - // - // Note: These options are only valid when creating a tabline. - if (*fmt_p == STL_TABPAGENR || *fmt_p == STL_TABCLOSENR) { - if (*fmt_p == STL_TABCLOSENR) { - if (minwid == 0) { - // %X ends the close label, go back to the previous tab label nr. - for (long n = curitem - 1; n >= 0; n--) { - if (stl_items[n].type == TabPage && stl_items[n].minwid >= 0) { - minwid = stl_items[n].minwid; - break; - } - } - } else { - // close nrs are stored as negative values - minwid = -minwid; - } - } - stl_items[curitem].type = TabPage; - stl_items[curitem].start = out_p; - stl_items[curitem].minwid = minwid; - fmt_p++; - curitem++; - continue; - } - - if (*fmt_p == STL_CLICK_FUNC) { - fmt_p++; - char *t = fmt_p; - while (*fmt_p != STL_CLICK_FUNC && *fmt_p) { - fmt_p++; - } - if (*fmt_p != STL_CLICK_FUNC) { - break; - } - 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].minwid = minwid; - fmt_p++; - curitem++; - continue; - } - - // Denotes the end of the minwid - // the maxwid may follow immediately after - if (*fmt_p == '.') { - fmt_p++; - if (ascii_isdigit(*fmt_p)) { - maxwid = getdigits_int(&fmt_p, false, 50); - } - } - - // Bound the minimum width at 50. - // Make the number negative to denote left alignment of the item - minwid = (minwid > 50 ? 50 : minwid) * (left_align ? -1 : 1); - - // Denotes the start of a new group - if (*fmt_p == '(') { - stl_groupitems[groupdepth++] = curitem; - stl_items[curitem].type = Group; - stl_items[curitem].start = out_p; - stl_items[curitem].minwid = minwid; - stl_items[curitem].maxwid = maxwid; - fmt_p++; - curitem++; - continue; - } - - // Denotes end of expanded %{} block - if (*fmt_p == '}' && evaldepth > 0) { - fmt_p++; - evaldepth--; - continue; - } - - // An invalid item was specified. - // Continue processing on the next character of the format string. - if (vim_strchr(STL_ALL, *fmt_p) == NULL) { - fmt_p++; - continue; - } - - // The status line item type - char opt = *fmt_p++; - - // OK - now for the real work - NumberBase base = kNumBaseDecimal; - bool itemisflag = false; - bool fillable = true; - long num = -1; - char *str = NULL; - switch (opt) { - case STL_FILEPATH: - case STL_FULLPATH: - case STL_FILENAME: - // Set fillable to false so that ' ' in the filename will not - // get replaced with the fillchar - fillable = false; - if (buf_spname(wp->w_buffer) != NULL) { - STRLCPY(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); - } - trans_characters((char *)NameBuff, MAXPATHL); - if (opt != STL_FILENAME) { - str = (char *)NameBuff; - } else { - str = path_tail((char *)NameBuff); - } - break; - case STL_VIM_EXPR: // '{' - { - char *block_start = fmt_p - 1; - int reevaluate = (*fmt_p == '%'); - itemisflag = true; - - if (reevaluate) { - fmt_p++; - } - - // Attempt to copy the expression to evaluate into - // the output buffer as a null-terminated string. - char *t = out_p; - while ((*fmt_p != '}' || (reevaluate && fmt_p[-1] != '%')) - && *fmt_p != NUL && out_p < out_end_p) { - *out_p++ = *fmt_p++; - } - if (*fmt_p != '}') { // missing '}' or out of space - break; - } - fmt_p++; - if (reevaluate) { - out_p[-1] = 0; // remove the % at the end of %{% expr %} - } else { - *out_p = 0; - } - - // Move our position in the output buffer - // to the beginning of the expression - out_p = t; - - // { Evaluate the expression - - // Store the current buffer number as a string variable - vim_snprintf(buf_tmp, sizeof(buf_tmp), "%d", curbuf->b_fnum); - set_internal_string_var("g:actual_curbuf", buf_tmp); - vim_snprintf((char *)win_tmp, sizeof(win_tmp), "%d", curwin->handle); - set_internal_string_var("g:actual_curwin", (char *)win_tmp); - - buf_T *const save_curbuf = curbuf; - win_T *const save_curwin = curwin; - const int save_VIsual_active = VIsual_active; - curwin = wp; - curbuf = wp->w_buffer; - // Visual mode is only valid in the current window. - if (curwin != save_curwin) { - VIsual_active = false; - } - - // Note: The result stored in `t` is unused. - str = eval_to_string_safe(out_p, &t, use_sandbox); - - curwin = save_curwin; - curbuf = save_curbuf; - VIsual_active = save_VIsual_active; - - // Remove the variable we just stored - do_unlet(S_LEN("g:actual_curbuf"), true); - do_unlet(S_LEN("g:actual_curwin"), true); - - // } - - // Check if the evaluated result is a number. - // If so, convert the number to an int and free the string. - if (str != NULL && *str != 0) { - if (*skipdigits(str) == NUL) { - num = atoi(str); - XFREE_CLEAR(str); - itemisflag = false; - } - } - - // If the output of the expression needs to be evaluated - // replace the %{} block with the result of evaluation - if (reevaluate && str != NULL && *str != 0 - && strchr((const char *)str, '%') != NULL - && evaldepth < MAX_STL_EVAL_DEPTH) { - size_t parsed_usefmt = (size_t)(block_start - usefmt); - size_t str_length = STRLEN(str); - size_t fmt_length = STRLEN(fmt_p); - size_t new_fmt_len = parsed_usefmt + str_length + fmt_length + 3; - char *new_fmt = xmalloc(new_fmt_len * sizeof(char)); - char *new_fmt_p = new_fmt; - - new_fmt_p = (char *)memcpy(new_fmt_p, usefmt, parsed_usefmt) + parsed_usefmt; - new_fmt_p = (char *)memcpy(new_fmt_p, str, str_length) + str_length; - new_fmt_p = (char *)memcpy(new_fmt_p, "%}", 2) + 2; - new_fmt_p = (char *)memcpy(new_fmt_p, fmt_p, fmt_length) + fmt_length; - *new_fmt_p = 0; - new_fmt_p = NULL; - - if (usefmt != fmt) { - xfree(usefmt); - } - XFREE_CLEAR(str); - usefmt = new_fmt; - fmt_p = usefmt + parsed_usefmt; - evaldepth++; - continue; - } - break; - } - - case STL_LINE: - num = (wp->w_buffer->b_ml.ml_flags & ML_EMPTY) - ? 0L : (long)(wp->w_cursor.lnum); - break; - - case STL_NUMLINES: - num = wp->w_buffer->b_ml.ml_line_count; - break; - - case STL_COLUMN: - num = (State & MODE_INSERT) == 0 && empty_line ? 0 : (int)wp->w_cursor.col + 1; - break; - - case STL_VIRTCOL: - case STL_VIRTCOL_ALT: { - colnr_T virtcol = wp->w_virtcol + 1; - // Don't display %V if it's the same as %c. - if (opt == STL_VIRTCOL_ALT - && (virtcol == (colnr_T)((State & MODE_INSERT) == 0 && empty_line - ? 0 : (int)wp->w_cursor.col + 1))) { - break; - } - num = (long)virtcol; - break; - } - - case STL_PERCENTAGE: - num = (int)(((long)wp->w_cursor.lnum * 100L) / - (long)wp->w_buffer->b_ml.ml_line_count); - break; - - case STL_ALTPERCENT: - // Store the position percentage in our temporary buffer. - // Note: We cannot store the value in `num` because - // `get_rel_pos` can return a named position. Ex: "Top" - get_rel_pos(wp, buf_tmp, TMPLEN); - str = buf_tmp; - break; - - case STL_ARGLISTSTAT: - fillable = false; - - // Note: This is important because `append_arg_number` starts appending - // at the end of the null-terminated string. - // Setting the first byte to null means it will place the argument - // number string at the beginning of the buffer. - buf_tmp[0] = 0; - - // Note: The call will only return true if it actually - // appended data to the `buf_tmp` buffer. - if (append_arg_number(wp, buf_tmp, (int)sizeof(buf_tmp), false)) { - str = buf_tmp; - } - break; - - case STL_KEYMAP: - fillable = false; - if (get_keymap_str(wp, "<%s>", buf_tmp, TMPLEN)) { - str = buf_tmp; - } - break; - case STL_PAGENUM: - num = printer_page_num; - break; - - case STL_BUFNO: - num = wp->w_buffer->b_fnum; - break; - - case STL_OFFSET_X: - base = kNumBaseHexadecimal; - FALLTHROUGH; - case STL_OFFSET: { - long l = ml_find_line_or_offset(wp->w_buffer, wp->w_cursor.lnum, NULL, - false); - num = (wp->w_buffer->b_ml.ml_flags & ML_EMPTY) || l < 0 ? - 0L : l + 1 + ((State & MODE_INSERT) == 0 && empty_line ? - 0 : (int)wp->w_cursor.col); - break; - } - case STL_BYTEVAL_X: - base = kNumBaseHexadecimal; - FALLTHROUGH; - case STL_BYTEVAL: - num = byteval; - if (num == NL) { - num = 0; - } else if (num == CAR && get_fileformat(wp->w_buffer) == EOL_MAC) { - num = NL; - } - break; - - case STL_ROFLAG: - case STL_ROFLAG_ALT: - itemisflag = true; - if (wp->w_buffer->b_p_ro) { - str = (opt == STL_ROFLAG_ALT) ? ",RO" : _("[RO]"); - } - break; - - case STL_HELPFLAG: - case STL_HELPFLAG_ALT: - itemisflag = true; - if (wp->w_buffer->b_help) { - str = (opt == STL_HELPFLAG_ALT) ? ",HLP" : _("[Help]"); - } - break; - - case STL_FILETYPE: - // Copy the filetype if it is not null and the formatted string will fit - // in the temporary buffer - // (including the brackets and null terminating character) - if (*wp->w_buffer->b_p_ft != NUL - && STRLEN(wp->w_buffer->b_p_ft) < TMPLEN - 3) { - vim_snprintf(buf_tmp, sizeof(buf_tmp), "[%s]", - wp->w_buffer->b_p_ft); - str = buf_tmp; - } - break; - - case STL_FILETYPE_ALT: - itemisflag = true; - // Copy the filetype if it is not null and the formatted string will fit - // in the temporary buffer - // (including the comma and null terminating character) - if (*wp->w_buffer->b_p_ft != NUL - && STRLEN(wp->w_buffer->b_p_ft) < TMPLEN - 2) { - 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); - } - str = buf_tmp; - } - break; - case STL_PREVIEWFLAG: - case STL_PREVIEWFLAG_ALT: - itemisflag = true; - if (wp->w_p_pvw) { - str = (opt == STL_PREVIEWFLAG_ALT) ? ",PRV" : _("[Preview]"); - } - break; - - case STL_QUICKFIX: - if (bt_quickfix(wp->w_buffer)) { - str = wp->w_llist_ref ? _(msg_loclist) : _(msg_qflist); - } - break; - - case STL_MODIFIED: - case STL_MODIFIED_ALT: - itemisflag = true; - switch ((opt == STL_MODIFIED_ALT) - + bufIsChanged(wp->w_buffer) * 2 - + (!MODIFIABLE(wp->w_buffer)) * 4) { - case 2: - str = "[+]"; break; - case 3: - str = ",+"; break; - case 4: - str = "[-]"; break; - case 5: - str = ",-"; break; - case 6: - str = "[+-]"; break; - case 7: - str = ",+-"; break; - } - break; - - case STL_HIGHLIGHT: { - // { The name of the highlight is surrounded by `#` - char *t = fmt_p; - while (*fmt_p != '#' && *fmt_p != NUL) { - fmt_p++; - } - // } - - // Create a highlight item based on the name - if (*fmt_p == '#') { - stl_items[curitem].type = Highlight; - stl_items[curitem].start = out_p; - stl_items[curitem].minwid = -syn_name2id_len(t, (size_t)(fmt_p - t)); - curitem++; - fmt_p++; - } - continue; - } - } - - // If we made it this far, the item is normal and starts at - // our current position in the output buffer. - // Non-normal items would have `continued`. - stl_items[curitem].start = out_p; - stl_items[curitem].type = Normal; - - // Copy the item string into the output buffer - if (str != NULL && *str) { - // { Skip the leading `,` or ` ` if the item is a flag - // and the proper conditions are met - char *t = str; - if (itemisflag) { - if ((t[0] && t[1]) - && ((!prevchar_isitem && *t == ',') - || (prevchar_isflag && *t == ' '))) { - t++; - } - prevchar_isflag = true; - } - // } - - long l = vim_strsize(t); - - // If this item is non-empty, record that the last thing - // we put in the output buffer was an item - if (l > 0) { - prevchar_isitem = true; - } - - // If the item is too wide, truncate it from the beginning - if (l > maxwid) { - while (l >= maxwid) { - l -= ptr2cells(t); - t += utfc_ptr2len(t); - } - - // Early out if there isn't enough room for the truncation marker - if (out_p >= out_end_p) { - break; - } - - // Add the truncation marker - *out_p++ = '<'; - } - - // If the item is right aligned and not wide enough, - // pad with fill characters. - if (minwid > 0) { - for (; l < minwid && out_p < out_end_p; l++) { - // Don't put a "-" in front of a digit. - if (l + 1 == minwid && fillchar == '-' && ascii_isdigit(*t)) { - *out_p++ = ' '; - } else { - MB_CHAR2BYTES(fillchar, out_p); - } - } - minwid = 0; - } else { - // Note: The negative value denotes a left aligned item. - // Here we switch the minimum width back to a positive value. - minwid *= -1; - } - - // { Copy the string text into the output buffer - for (; *t && out_p < out_end_p; t++) { - // Change a space by fillchar, unless fillchar is '-' and a - // digit follows. - if (fillable && *t == ' ' - && (!ascii_isdigit(*(t + 1)) || fillchar != '-')) { - MB_CHAR2BYTES(fillchar, out_p); - } else { - *out_p++ = *t; - } - } - // } - - // For left-aligned items, fill any remaining space with the fillchar - for (; l < minwid && out_p < out_end_p; l++) { - MB_CHAR2BYTES(fillchar, out_p); - } - - // Otherwise if the item is a number, copy that to the output buffer. - } else if (num >= 0) { - if (out_p + 20 > out_end_p) { - break; // not sufficient space - } - prevchar_isitem = true; - - // { Build the formatting string - char nstr[20]; - char *t = nstr; - if (opt == STL_VIRTCOL_ALT) { - *t++ = '-'; - minwid--; - } - *t++ = '%'; - if (zeropad) { - *t++ = '0'; - } - - // Note: The `*` means we take the width as one of the arguments - *t++ = '*'; - *t++ = base == kNumBaseHexadecimal ? 'X' : 'd'; - *t = 0; - // } - - // { Determine how many characters the number will take up when printed - // Note: We have to cast the base because the compiler uses - // unsigned ints for the enum values. - long num_chars = 1; - for (long n = num; n >= (int)base; n /= (int)base) { - num_chars++; - } - - // VIRTCOL_ALT takes up an extra character because - // of the `-` we added above. - if (opt == STL_VIRTCOL_ALT) { - num_chars++; - } - // } - - assert(out_end_p >= out_p); - size_t remaining_buf_len = (size_t)(out_end_p - out_p) + 1; - - // If the number is going to take up too much room - // Figure out the approximate number in "scientific" type notation. - // Ex: 14532 with maxwid of 4 -> '14>3' - if (num_chars > maxwid) { - // Add two to the width because the power piece will take - // two extra characters - num_chars += 2; - - // How many extra characters there are - long n = num_chars - maxwid; - - // { Reduce the number by base^n - while (num_chars-- > maxwid) { - num /= (long)base; - } - // } - - // { Add the format string for the exponent bit - *t++ = '>'; - *t++ = '%'; - // Use the same base as the first number - *t = t[-3]; - *++t = 0; - // } - - vim_snprintf(out_p, remaining_buf_len, nstr, 0, num, n); - } else { - vim_snprintf(out_p, remaining_buf_len, nstr, minwid, num); - } - - // Advance the output buffer position to the end of the - // number we just printed - out_p += STRLEN(out_p); - - // Otherwise, there was nothing to print so mark the item as empty - } else { - stl_items[curitem].type = Empty; - } - - // Only free the string buffer if we allocated it. - // Note: This is not needed if `str` is pointing at `tmp` - if (opt == STL_VIM_EXPR) { - XFREE_CLEAR(str); - } - - if (num >= 0 || (!itemisflag && str && *str)) { - prevchar_isflag = false; // Item not NULL, but not a flag - } - - // Item processed, move to the next - curitem++; - } - - *out_p = NUL; - int itemcnt = curitem; - - // Free the format buffer if we allocated it internally - if (usefmt != fmt) { - xfree(usefmt); - } - - // We have now processed the entire statusline format string. - // What follows is post-processing to handle alignment and highlighting. - - int width = vim_strsize(out); - if (maxwidth > 0 && width > maxwidth) { - // Result is too long, must truncate somewhere. - int item_idx = 0; - char *trunc_p; - - // If there are no items, truncate from beginning - if (itemcnt == 0) { - trunc_p = out; - - // Otherwise, look for the truncation item - } else { - // Default to truncating at the first item - trunc_p = stl_items[0].start; - item_idx = 0; - - for (int i = 0; i < itemcnt; i++) { - if (stl_items[i].type == Trunc) { - // Truncate at %< stl_items. - trunc_p = stl_items[i].start; - item_idx = i; - break; - } - } - } - - // If the truncation point we found is beyond the maximum - // length of the string, truncate the end of the string. - if (width - vim_strsize(trunc_p) >= maxwidth) { - // Walk from the beginning of the - // string to find the last character that will fit. - trunc_p = out; - width = 0; - for (;;) { - width += ptr2cells(trunc_p); - if (width >= maxwidth) { - break; - } - - // Note: Only advance the pointer if the next - // character will fit in the available output space - trunc_p += utfc_ptr2len(trunc_p); - } - - // Ignore any items in the statusline that occur after - // the truncation point - for (int i = 0; i < itemcnt; i++) { - if (stl_items[i].start > trunc_p) { - itemcnt = i; - break; - } - } - - // Truncate the output - *trunc_p++ = '>'; - *trunc_p = 0; - - // Truncate at the truncation point we found - } else { - // { Determine how many bytes to remove - long trunc_len = 0; - while (width >= maxwidth) { - width -= ptr2cells(trunc_p + trunc_len); - trunc_len += utfc_ptr2len(trunc_p + trunc_len); - } - // } - - // { Truncate the string - char *trunc_end_p = trunc_p + trunc_len; - STRMOVE(trunc_p + 1, trunc_end_p); - - // Put a `<` to mark where we truncated at - *trunc_p = '<'; - - if (width + 1 < maxwidth) { - // Advance the pointer to the end of the string - trunc_p = trunc_p + STRLEN(trunc_p); - } - - // Fill up for half a double-wide character. - while (++width < maxwidth) { - MB_CHAR2BYTES(fillchar, trunc_p); - *trunc_p = NUL; - } - // } - - // { Change the start point for items based on - // their position relative to our truncation point - - // Note: The offset is one less than the truncation length because - // the truncation marker `<` is not counted. - long item_offset = trunc_len - 1; - - for (int i = item_idx; i < itemcnt; i++) { - // Items starting at or after the end of the truncated section need - // to be moved backwards. - if (stl_items[i].start >= trunc_end_p) { - stl_items[i].start -= item_offset; - // Anything inside the truncated area is set to start - // at the `<` truncation character. - } else { - stl_items[i].start = trunc_p; - } - } - // } - } - width = maxwidth; - - // If there is room left in our statusline, and room left in our buffer, - // add characters at the separate marker (if there is one) to - // fill up the available space. - } else if (width < maxwidth - && STRLEN(out) + (size_t)(maxwidth - width) + 1 < outlen) { - // Find how many separators there are, which we will use when - // figuring out how many groups there are. - int num_separators = 0; - for (int i = 0; i < itemcnt; i++) { - if (stl_items[i].type == Separate) { - // Create an array of the start location for each - // separator mark. - stl_separator_locations[num_separators] = i; - num_separators++; - } - } - - // If we have separated groups, then we deal with it now - if (num_separators) { - int standard_spaces = (maxwidth - width) / num_separators; - int final_spaces = (maxwidth - width) - - standard_spaces * (num_separators - 1); - - for (int i = 0; i < num_separators; i++) { - int dislocation = (i == (num_separators - 1)) ? final_spaces : standard_spaces; - dislocation *= utf_char2len(fillchar); - char *start = stl_items[stl_separator_locations[i]].start; - char *seploc = start + dislocation; - STRMOVE(seploc, start); - for (char *s = start; s < seploc;) { - MB_CHAR2BYTES(fillchar, s); - } - - for (int item_idx = stl_separator_locations[i] + 1; - item_idx < itemcnt; - item_idx++) { - stl_items[item_idx].start += dislocation; - } - } - - width = maxwidth; - } - } - - // Store the info about highlighting. - if (hltab != NULL) { - *hltab = stl_hltab; - stl_hlrec_t *sp = stl_hltab; - for (long l = 0; l < itemcnt; l++) { - if (stl_items[l].type == Highlight) { - sp->start = stl_items[l].start; - sp->userhl = stl_items[l].minwid; - sp++; - } - } - sp->start = NULL; - sp->userhl = 0; - } - - // Store the info about tab pages labels. - if (tabtab != NULL) { - *tabtab = stl_tabtab; - StlClickRecord *cur_tab_rec = stl_tabtab; - for (long l = 0; l < itemcnt; l++) { - if (stl_items[l].type == TabPage) { - cur_tab_rec->start = stl_items[l].start; - if (stl_items[l].minwid == 0) { - cur_tab_rec->def.type = kStlClickDisabled; - cur_tab_rec->def.tabnr = 0; - } else { - int tabnr = stl_items[l].minwid; - if (stl_items[l].minwid > 0) { - cur_tab_rec->def.type = kStlClickTabSwitch; - } else { - cur_tab_rec->def.type = kStlClickTabClose; - tabnr = -tabnr; - } - cur_tab_rec->def.tabnr = tabnr; - } - cur_tab_rec->def.func = NULL; - cur_tab_rec++; - } else if (stl_items[l].type == ClickFunc) { - cur_tab_rec->start = stl_items[l].start; - cur_tab_rec->def.type = kStlClickFuncRun; - cur_tab_rec->def.tabnr = stl_items[l].minwid; - cur_tab_rec->def.func = stl_items[l].cmd; - cur_tab_rec++; - } - } - cur_tab_rec->start = NULL; - cur_tab_rec->def.type = kStlClickDisabled; - cur_tab_rec->def.tabnr = 0; - cur_tab_rec->def.func = NULL; - } - - // When inside update_screen we do not want redrawing a statusline, ruler, - // title, etc. to trigger another redraw, it may cause an endless loop. - if (updating_screen) { - must_redraw = save_must_redraw; - curwin->w_redr_type = save_redr_type; - } - - return width; -} - /// Get relative cursor position in window into "buf[buflen]", in the form 99%, /// using "Top", "Bot" or "All" when appropriate. void get_rel_pos(win_T *wp, char *buf, int buflen) @@ -4571,7 +3404,7 @@ void get_rel_pos(win_T *wp, char *buf, int buflen) /// @param add_file if true, add "file" before the arg number /// /// @return true if it was appended. -static bool append_arg_number(win_T *wp, char *buf, int buflen, bool add_file) +bool append_arg_number(win_T *wp, char *buf, int buflen, bool add_file) FUNC_ATTR_NONNULL_ALL { // Nothing to do @@ -4628,279 +3461,6 @@ void fname_expand(buf_T *buf, char **ffname, char **sfname) #endif } -/// Get the file name for an argument list entry. -char *alist_name(aentry_T *aep) -{ - buf_T *bp; - - // Use the name from the associated buffer if it exists. - bp = buflist_findnr(aep->ae_fnum); - if (bp == NULL || bp->b_fname == NULL) { - return (char *)aep->ae_fname; - } - return bp->b_fname; -} - -/// do_arg_all(): Open up to 'count' windows, one for each argument. -/// -/// @param forceit hide buffers in current windows -/// @param keep_tabs keep current tabs, for ":tab drop file" -void do_arg_all(int count, int forceit, int keep_tabs) -{ - uint8_t *opened; // Array of weight for which args are open: - // 0: not opened - // 1: opened in other tab - // 2: opened in curtab - // 3: opened in curtab and curwin - - int opened_len; // length of opened[] - int use_firstwin = false; // use first window for arglist - bool tab_drop_empty_window = false; - int split_ret = OK; - bool p_ea_save; - alist_T *alist; // argument list to be used - buf_T *buf; - tabpage_T *tpnext; - int had_tab = cmdmod.cmod_tab; - win_T *old_curwin, *last_curwin; - tabpage_T *old_curtab, *last_curtab; - win_T *new_curwin = NULL; - tabpage_T *new_curtab = NULL; - - assert(firstwin != NULL); // satisfy coverity - - if (ARGCOUNT <= 0) { - // Don't give an error message. We don't want it when the ":all" command is in the .vimrc. - return; - } - setpcmark(); - - opened_len = ARGCOUNT; - opened = xcalloc((size_t)opened_len, 1); - - // Autocommands may do anything to the argument list. Make sure it's not - // freed while we are working here by "locking" it. We still have to - // watch out for its size to be changed. - alist = curwin->w_alist; - alist->al_refcount++; - - old_curwin = curwin; - old_curtab = curtab; - - // Try closing all windows that are not in the argument list. - // Also close windows that are not full width; - // When 'hidden' or "forceit" set the buffer becomes hidden. - // Windows that have a changed buffer and can't be hidden won't be closed. - // When the ":tab" modifier was used do this for all tab pages. - if (had_tab > 0) { - goto_tabpage_tp(first_tabpage, true, true); - } - for (;;) { - win_T *wpnext = NULL; - tpnext = curtab->tp_next; - for (win_T *wp = firstwin; wp != NULL; wp = wpnext) { - int i; - wpnext = wp->w_next; - buf = wp->w_buffer; - if (buf->b_ffname == NULL - || (!keep_tabs && (buf->b_nwindows > 1 || wp->w_width != Columns))) { - i = opened_len; - } else { - // check if the buffer in this window is in the arglist - for (i = 0; i < opened_len; i++) { - if (i < alist->al_ga.ga_len - && (AARGLIST(alist)[i].ae_fnum == buf->b_fnum - || path_full_compare(alist_name(&AARGLIST(alist)[i]), - buf->b_ffname, - true, true) & kEqualFiles)) { - int weight = 1; - - if (old_curtab == curtab) { - weight++; - if (old_curwin == wp) { - weight++; - } - } - - if (weight > (int)opened[i]) { - opened[i] = (uint8_t)weight; - if (i == 0) { - if (new_curwin != NULL) { - new_curwin->w_arg_idx = opened_len; - } - new_curwin = wp; - new_curtab = curtab; - } - } else if (keep_tabs) { - i = opened_len; - } - - if (wp->w_alist != alist) { - // Use the current argument list for all windows containing a file from it. - alist_unlink(wp->w_alist); - wp->w_alist = alist; - wp->w_alist->al_refcount++; - } - break; - } - } - } - wp->w_arg_idx = i; - - if (i == opened_len && !keep_tabs) { // close this window - if (buf_hide(buf) || forceit || buf->b_nwindows > 1 - || !bufIsChanged(buf)) { - // If the buffer was changed, and we would like to hide it, try autowriting. - if (!buf_hide(buf) && buf->b_nwindows <= 1 && bufIsChanged(buf)) { - bufref_T bufref; - set_bufref(&bufref, buf); - (void)autowrite(buf, false); - // Check if autocommands removed the window. - if (!win_valid(wp) || !bufref_valid(&bufref)) { - wpnext = firstwin; // Start all over... - continue; - } - } - // don't close last window - if (ONE_WINDOW - && (first_tabpage->tp_next == NULL || !had_tab)) { - use_firstwin = true; - } else { - win_close(wp, !buf_hide(buf) && !bufIsChanged(buf), false); - // check if autocommands removed the next window - if (!win_valid(wpnext)) { - // start all over... - wpnext = firstwin; - } - } - } - } - } - - // Without the ":tab" modifier only do the current tab page. - if (had_tab == 0 || tpnext == NULL) { - break; - } - - // check if autocommands removed the next tab page - if (!valid_tabpage(tpnext)) { - tpnext = first_tabpage; // start all over... - } - goto_tabpage_tp(tpnext, true, true); - } - - // Open a window for files in the argument list that don't have one. - // ARGCOUNT may change while doing this, because of autocommands. - if (count > opened_len || count <= 0) { - count = opened_len; - } - - // Don't execute Win/Buf Enter/Leave autocommands here. - autocmd_no_enter++; - autocmd_no_leave++; - last_curwin = curwin; - last_curtab = curtab; - win_enter(lastwin, false); - // ":tab drop file" should re-use an empty window to avoid "--remote-tab" - // leaving an empty tab page when executed locally. - if (keep_tabs && buf_is_empty(curbuf) && curbuf->b_nwindows == 1 - && curbuf->b_ffname == NULL && !curbuf->b_changed) { - use_firstwin = true; - tab_drop_empty_window = true; - } - - for (int i = 0; i < count && !got_int; i++) { - if (alist == &global_alist && i == global_alist.al_ga.ga_len - 1) { - arg_had_last = true; - } - if (opened[i] > 0) { - // Move the already present window to below the current window - if (curwin->w_arg_idx != i) { - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->w_arg_idx == i) { - if (keep_tabs) { - new_curwin = wp; - new_curtab = curtab; - } else if (wp->w_frame->fr_parent != curwin->w_frame->fr_parent) { - emsg(_("E249: window layout changed unexpectedly")); - i = count; - break; - } else { - win_move_after(wp, curwin); - } - break; - } - } - } - } else if (split_ret == OK) { - // trigger events for tab drop - if (tab_drop_empty_window && i == count - 1) { - autocmd_no_enter--; - } - if (!use_firstwin) { // split current window - p_ea_save = p_ea; - p_ea = true; // use space from all windows - split_ret = win_split(0, WSP_ROOM | WSP_BELOW); - p_ea = p_ea_save; - if (split_ret == FAIL) { - continue; - } - } else { // first window: do autocmd for leaving this buffer - autocmd_no_leave--; - } - - // edit file "i" - curwin->w_arg_idx = i; - if (i == 0) { - new_curwin = curwin; - new_curtab = curtab; - } - (void)do_ecmd(0, alist_name(&AARGLIST(alist)[i]), NULL, NULL, ECMD_ONE, - ((buf_hide(curwin->w_buffer) - || bufIsChanged(curwin->w_buffer)) - ? ECMD_HIDE : 0) + ECMD_OLDBUF, - curwin); - if (tab_drop_empty_window && i == count - 1) { - autocmd_no_enter++; - } - if (use_firstwin) { - autocmd_no_leave++; - } - use_firstwin = false; - } - os_breakcheck(); - - // When ":tab" was used open a new tab for a new window repeatedly. - if (had_tab > 0 && tabpage_index(NULL) <= p_tpm) { - cmdmod.cmod_tab = 9999; - } - } - - // Remove the "lock" on the argument list. - alist_unlink(alist); - - autocmd_no_enter--; - // restore last referenced tabpage's curwin - if (last_curtab != new_curtab) { - if (valid_tabpage(last_curtab)) { - goto_tabpage_tp(last_curtab, true, true); - } - if (win_valid(last_curwin)) { - win_enter(last_curwin, false); - } - } - // to window with first arg - if (valid_tabpage(new_curtab)) { - goto_tabpage_tp(new_curtab, true, true); - } - if (win_valid(new_curwin)) { - win_enter(new_curwin, false); - } - - autocmd_no_leave--; - xfree(opened); -} - /// @return true if "buf" is a prompt buffer. bool bt_prompt(buf_T *buf) FUNC_ATTR_PURE @@ -5139,8 +3699,6 @@ static int chk_modeline(linenr_T lnum, int flags) intmax_t vers; int end; int retval = OK; - char *save_sourcing_name; - linenr_T save_sourcing_lnum; prev = -1; for (s = (char *)ml_get(lnum); *s != NUL; s++) { @@ -5185,10 +3743,8 @@ static int chk_modeline(linenr_T lnum, int flags) s = linecopy = xstrdup(s); // copy the line, it will change - save_sourcing_lnum = sourcing_lnum; - save_sourcing_name = sourcing_name; - sourcing_lnum = lnum; // prepare for emsg() - sourcing_name = "modelines"; + // prepare for emsg() + estack_push(ETYPE_MODELINE, "modelines", lnum); end = false; while (end == false) { @@ -5228,7 +3784,7 @@ static int chk_modeline(linenr_T lnum, int flags) const sctx_T save_current_sctx = current_sctx; current_sctx.sc_sid = SID_MODELINE; current_sctx.sc_seq = 0; - current_sctx.sc_lnum = 0; + current_sctx.sc_lnum = lnum; // Make sure no risky things are executed as a side effect. secure = 1; @@ -5243,9 +3799,7 @@ static int chk_modeline(linenr_T lnum, int flags) s = e + 1; // advance to next part } - sourcing_lnum = save_sourcing_lnum; - sourcing_name = save_sourcing_name; - + estack_pop(); xfree(linecopy); return retval; @@ -5280,7 +3834,8 @@ bool bt_terminal(const buf_T *const buf) } /// @return true if "buf" is a "nofile", "acwrite", "terminal" or "prompt" -/// buffer. This means the buffer name is not a file name. +/// buffer. This means the buffer name may not be a file name, +/// at least not for writing the buffer. bool bt_nofilename(const buf_T *const buf) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { @@ -5290,6 +3845,17 @@ bool bt_nofilename(const buf_T *const buf) || buf->b_p_bt[0] == 'p'); } +/// @return true if "buf" is a "nofile", "quickfix", "terminal" or "prompt" +/// buffer. This means the buffer is not to be read from a file. +static bool bt_nofileread(const buf_T *const buf) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ + return buf != NULL && ((buf->b_p_bt[0] == 'n' && buf->b_p_bt[2] == 'f') + || buf->b_p_bt[0] == 't' + || buf->b_p_bt[0] == 'q' + || buf->b_p_bt[0] == 'p'); +} + /// @return true if "buf" has 'buftype' set to "nofile". bool bt_nofile(const buf_T *const buf) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT @@ -5488,7 +4054,7 @@ void buf_signcols_add_check(buf_T *buf, sign_entry_T *added) buf->b_signcols.max++; } buf->b_signcols.size++; - redraw_buf_later(buf, NOT_VALID); + redraw_buf_later(buf, UPD_NOT_VALID); return; } @@ -5509,7 +4075,7 @@ void buf_signcols_add_check(buf_T *buf, sign_entry_T *added) buf->b_signcols.size = linesum; buf->b_signcols.max = linesum; buf->b_signcols.sentinel = added->se_lnum; - redraw_buf_later(buf, NOT_VALID); + redraw_buf_later(buf, UPD_NOT_VALID); } } @@ -5528,7 +4094,7 @@ int buf_signcols(buf_T *buf, int maximum) if (signcols != buf->b_signcols.size) { buf->b_signcols.size = signcols; buf->b_signcols.max = maximum; - redraw_buf_later(buf, NOT_VALID); + redraw_buf_later(buf, UPD_NOT_VALID); } buf->b_signcols.valid = true; @@ -5637,14 +4203,19 @@ void wipe_buffer(buf_T *buf, bool aucmd) /// @param bufnr Buffer to switch to, or 0 to create a new buffer. /// /// @see curbufIsChanged() -void buf_open_scratch(handle_T bufnr, char *bufname) +/// +/// @return FAIL for failure, OK otherwise +int buf_open_scratch(handle_T bufnr, char *bufname) { - (void)do_ecmd((int)bufnr, NULL, NULL, NULL, ECMD_ONE, ECMD_HIDE, NULL); + if (do_ecmd((int)bufnr, NULL, NULL, NULL, ECMD_ONE, ECMD_HIDE, NULL) == FAIL) { + return FAIL; + } apply_autocmds(EVENT_BUFFILEPRE, NULL, NULL, false, curbuf); (void)setfname(curbuf, bufname, NULL, true); apply_autocmds(EVENT_BUFFILEPOST, NULL, NULL, false, curbuf); - set_option_value("bh", 0L, "hide", OPT_LOCAL); - set_option_value("bt", 0L, "nofile", OPT_LOCAL); - set_option_value("swf", 0L, NULL, OPT_LOCAL); + set_option_value_give_err("bh", 0L, "hide", OPT_LOCAL); + set_option_value_give_err("bt", 0L, "nofile", OPT_LOCAL); + set_option_value_give_err("swf", 0L, NULL, OPT_LOCAL); RESET_BINDING(curwin); + return OK; } diff --git a/src/nvim/buffer.h b/src/nvim/buffer.h index b452eb227e..d56a70dc7e 100644 --- a/src/nvim/buffer.h +++ b/src/nvim/buffer.h @@ -1,12 +1,13 @@ #ifndef NVIM_BUFFER_H #define NVIM_BUFFER_H -#include "nvim/eval.h" +#include "nvim/eval/typval.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/screen.h" // for StlClickRecord // Values for buflist_getfile() enum getf_values { @@ -61,6 +62,9 @@ enum bfa_values { BFA_IGNORE_ABORT = 8, // do not abort for aborting() }; +EXTERN char *msg_loclist INIT(= N_("[Location List]")); +EXTERN char *msg_qflist INIT(= N_("[Quickfix List]")); + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "buffer.h.generated.h" #endif diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 9457051947..748e94f0a1 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -30,14 +30,12 @@ typedef struct { #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; needs buf_T. +// 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 proftime_T -#include "nvim/profile.h" // for String #include "nvim/api/private/defs.h" // for Map(K, V) @@ -118,11 +116,11 @@ typedef uint64_t disptick_T; // display tick type * The taggy struct is used to store the information about a :tag command. */ typedef struct taggy { - char_u *tagname; // tag name + char *tagname; // tag name fmark_T fmark; // cursor position BEFORE ":tag" int cur_match; // match number int cur_fnum; // buffer number used for cur_match - char_u *user_data; // used with tagfunc + char *user_data; // used with tagfunc } taggy_T; typedef struct buffblock buffblock_T; @@ -133,7 +131,7 @@ typedef struct buffheader buffheader_T; */ struct buffblock { buffblock_T *b_next; // pointer to next buffblock - char_u b_str[1]; // contents (actually longer) + char b_str[1]; // contents (actually longer) }; /* @@ -161,39 +159,39 @@ typedef struct { #define w_p_arab w_onebuf_opt.wo_arab // 'arabic' int wo_bri; #define w_p_bri w_onebuf_opt.wo_bri // 'breakindent' - char_u *wo_briopt; + char *wo_briopt; #define w_p_briopt w_onebuf_opt.wo_briopt // 'breakindentopt' int wo_diff; #define w_p_diff w_onebuf_opt.wo_diff // 'diff' - char_u *wo_fdc; + char *wo_fdc; #define w_p_fdc w_onebuf_opt.wo_fdc // 'foldcolumn' - char_u *wo_fdc_save; + char *wo_fdc_save; #define w_p_fdc_save w_onebuf_opt.wo_fdc_save // 'fdc' saved for diff mode int wo_fen; #define w_p_fen w_onebuf_opt.wo_fen // 'foldenable' int wo_fen_save; // 'foldenable' saved for diff mode #define w_p_fen_save w_onebuf_opt.wo_fen_save - char_u *wo_fdi; + char *wo_fdi; #define w_p_fdi w_onebuf_opt.wo_fdi // 'foldignore' long wo_fdl; #define w_p_fdl w_onebuf_opt.wo_fdl // 'foldlevel' long wo_fdl_save; // 'foldlevel' state saved for diff mode #define w_p_fdl_save w_onebuf_opt.wo_fdl_save - char_u *wo_fdm; + char *wo_fdm; #define w_p_fdm w_onebuf_opt.wo_fdm // 'foldmethod' - char_u *wo_fdm_save; + char *wo_fdm_save; #define w_p_fdm_save w_onebuf_opt.wo_fdm_save // 'fdm' saved for diff mode long wo_fml; #define w_p_fml w_onebuf_opt.wo_fml // 'foldminlines' long wo_fdn; #define w_p_fdn w_onebuf_opt.wo_fdn // 'foldnestmax' - char_u *wo_fde; + char *wo_fde; #define w_p_fde w_onebuf_opt.wo_fde // 'foldexpr' - char_u *wo_fdt; + char *wo_fdt; #define w_p_fdt w_onebuf_opt.wo_fdt // 'foldtext' - char_u *wo_fmr; + char *wo_fmr; #define w_p_fmr w_onebuf_opt.wo_fmr // 'foldmarker' int wo_lbr; #define w_p_lbr w_onebuf_opt.wo_lbr // 'linebreak' @@ -203,7 +201,7 @@ typedef struct { #define w_p_nu w_onebuf_opt.wo_nu // 'number' int wo_rnu; #define w_p_rnu w_onebuf_opt.wo_rnu // 'relativenumber' - char_u *wo_ve; + char *wo_ve; #define w_p_ve w_onebuf_opt.wo_ve // 'virtualedit' unsigned wo_ve_flags; #define w_ve_flags w_onebuf_opt.wo_ve_flags // flags for 'virtualedit' @@ -217,7 +215,7 @@ typedef struct { #define w_p_pvw w_onebuf_opt.wo_pvw // 'previewwindow' int wo_rl; #define w_p_rl w_onebuf_opt.wo_rl // 'rightleft' - char_u *wo_rlc; + char *wo_rlc; #define w_p_rlc w_onebuf_opt.wo_rlc // 'rightleftcmd' long wo_scr; #define w_p_scr w_onebuf_opt.wo_scr // 'scroll' @@ -227,13 +225,13 @@ typedef struct { #define w_p_cuc w_onebuf_opt.wo_cuc // 'cursorcolumn' int wo_cul; #define w_p_cul w_onebuf_opt.wo_cul // 'cursorline' - char_u *wo_culopt; + char *wo_culopt; #define w_p_culopt w_onebuf_opt.wo_culopt // 'cursorlineopt' - char_u *wo_cc; + char *wo_cc; #define w_p_cc w_onebuf_opt.wo_cc // 'colorcolumn' - char_u *wo_sbr; + char *wo_sbr; #define w_p_sbr w_onebuf_opt.wo_sbr // 'showbreak' - char_u *wo_stl; + char *wo_stl; #define w_p_stl w_onebuf_opt.wo_stl // 'statusline' char *wo_wbr; #define w_p_wbr w_onebuf_opt.wo_wbr // 'winbar' @@ -247,7 +245,7 @@ typedef struct { #define w_p_wrap w_onebuf_opt.wo_wrap // 'wrap' int wo_wrap_save; // 'wrap' state saved for diff mode #define w_p_wrap_save w_onebuf_opt.wo_wrap_save - char_u *wo_cocu; // 'concealcursor' + char *wo_cocu; // 'concealcursor' #define w_p_cocu w_onebuf_opt.wo_cocu long wo_cole; // 'conceallevel' #define w_p_cole w_onebuf_opt.wo_cole @@ -255,14 +253,14 @@ typedef struct { #define w_p_crb w_onebuf_opt.wo_crb // 'cursorbind' int wo_crb_save; // 'cursorbind' state saved for diff mode #define w_p_crb_save w_onebuf_opt.wo_crb_save - char_u *wo_scl; + char *wo_scl; #define w_p_scl w_onebuf_opt.wo_scl // 'signcolumn' - char_u *wo_winhl; + char *wo_winhl; #define w_p_winhl w_onebuf_opt.wo_winhl // 'winhighlight' - char_u *wo_fcs; -#define w_p_fcs w_onebuf_opt.wo_fcs // 'fillchars' - char_u *wo_lcs; + char *wo_lcs; #define w_p_lcs w_onebuf_opt.wo_lcs // 'listchars' + char *wo_fcs; +#define w_p_fcs w_onebuf_opt.wo_fcs // 'fillchars' long wo_winbl; #define w_p_winbl w_onebuf_opt.wo_winbl // 'winblend' @@ -310,8 +308,8 @@ typedef struct arglist { // // TODO(Felipe): move aentry_T to another header typedef struct argentry { - char_u *ae_fname; // file name as specified - int ae_fnum; // buffer number with expanded file name + char *ae_fname; // file name as specified + int ae_fnum; // buffer number with expanded file name } aentry_T; #define ALIST(win) (win)->w_alist @@ -327,8 +325,8 @@ typedef struct argentry { * Used for the typeahead buffer: typebuf. */ typedef struct { - char_u *tb_buf; // buffer for typed characters - char_u *tb_noremap; // mapping flags for characters in tb_buf[] + uint8_t *tb_buf; // buffer for typed characters + uint8_t *tb_noremap; // mapping flags for characters in tb_buf[] int tb_buflen; // size of tb_buf[] int tb_off; // current position in tb_buf[] int tb_len; // number of valid bytes in tb_buf[] @@ -354,11 +352,11 @@ typedef struct { */ typedef struct mapblock mapblock_T; struct mapblock { - mapblock_T *m_next; // next mapblock in list - char_u *m_keys; // mapped from, lhs - char_u *m_str; // mapped to, rhs - char_u *m_orig_str; // rhs as entered by the user - LuaRef m_luaref; // lua function reference as rhs + mapblock_T *m_next; // next mapblock in list + uint8_t *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 int m_keylen; // strlen(m_keys) int m_mode; // valid mode int m_simplified; // m_keys was simplified, do no use this map @@ -368,8 +366,8 @@ struct mapblock { char m_nowait; // <nowait> used char m_expr; // <expr> used, m_str is an expression sctx_T m_script_ctx; // SCTX where map was defined - char *m_desc; // description of keymap - bool m_replace_keycodes; // replace termcodes in lua function + char *m_desc; // description of mapping + bool m_replace_keycodes; // replace keycodes in result of expression }; /// Used for highlighting in the status line. @@ -433,7 +431,7 @@ typedef struct { typedef struct { hashtab_T b_keywtab; // syntax keywords hash table hashtab_T b_keywtab_ic; // idem, ignore case - int b_syn_error; // TRUE when error occurred in HL + bool b_syn_error; // true when error occurred in HL bool b_syn_slow; // true when 'redrawtime' reached int b_syn_ic; // ignore case for :syn cmds int b_syn_foldlevel; // how to compute foldlevel on a line @@ -442,14 +440,14 @@ typedef struct { garray_T b_syn_clusters; // table for syntax clusters int b_spell_cluster_id; // @Spell cluster ID or 0 int b_nospell_cluster_id; // @NoSpell cluster ID or 0 - int b_syn_containedin; // TRUE when there is an item with a + int b_syn_containedin; // true when there is an item with a // "containedin" argument int b_syn_sync_flags; // flags about how to sync int16_t b_syn_sync_id; // group to sync on linenr_T b_syn_sync_minlines; // minimal sync lines offset linenr_T b_syn_sync_maxlines; // maximal sync lines offset linenr_T b_syn_sync_linebreaks; // offset for multi-line pattern - char_u *b_syn_linecont_pat; // line continuation pattern + char *b_syn_linecont_pat; // line continuation pattern regprog_T *b_syn_linecont_prog; // line continuation program syn_time_T b_syn_linecont_time; int b_syn_linecont_ic; // ignore-case flag for above @@ -478,17 +476,17 @@ typedef struct { disptick_T b_sst_lasttick; // last display tick // for spell checking - garray_T b_langp; // list of pointers to slang_T, see spell.c - bool b_spell_ismw[256]; // flags: is midword char - char_u *b_spell_ismw_mb; // multi-byte midword chars - char_u *b_p_spc; // 'spellcapcheck' + garray_T b_langp; // list of pointers to slang_T, see spell.c + bool b_spell_ismw[256]; // flags: is midword char + char *b_spell_ismw_mb; // multi-byte midword chars + char *b_p_spc; // 'spellcapcheck' regprog_T *b_cap_prog; // program for 'spellcapcheck' - char_u *b_p_spf; // 'spellfile' - char_u *b_p_spl; // 'spelllang' - char_u *b_p_spo; // 'spelloptions' - int b_cjk; // all CJK letters as OK - char_u b_syn_chartab[32]; // syntax iskeyword option - char_u *b_syn_isk; // iskeyword option + char *b_p_spf; // 'spellfile' + char *b_p_spl; // 'spelllang' + char *b_p_spo; // 'spelloptions' + int b_cjk; // all CJK letters as OK + uint8_t b_syn_chartab[32]; // syntax iskeyword option + char *b_syn_isk; // iskeyword option } synblock_T; /// Type used for changedtick_di member in buf_T @@ -602,7 +600,7 @@ struct file_buffer { fmark_T b_namedm[NMARKS]; // current named marks (mark.c) - // These variables are set when VIsual_active becomes FALSE + // These variables are set when VIsual_active becomes false visualinfo_T b_visual; int b_visual_mode_eval; // b_visual.vi_mode for visualmode() @@ -655,10 +653,8 @@ struct file_buffer { time_t b_u_time_cur; // uh_time of header below which we are now long b_u_save_nr_cur; // file write nr after which we are now - /* - * variables for "U" command in undo.c - */ - char_u *b_u_line_ptr; // saved line for "U" command + // variables for "U" command in undo.c + char *b_u_line_ptr; // saved line for "U" command linenr_T b_u_line_lnum; // line number of line in u_line colnr_T b_u_line_colnr; // optional column number @@ -688,73 +684,73 @@ struct file_buffer { int b_p_ai; ///< 'autoindent' int b_p_ai_nopaste; ///< b_p_ai saved for paste mode - char_u *b_p_bkc; ///< 'backupco + char *b_p_bkc; ///< 'backupco unsigned int b_bkc_flags; ///< flags for 'backupco int b_p_ci; ///< 'copyindent' int b_p_bin; ///< 'binary' int b_p_bomb; ///< 'bomb' - char_u *b_p_bh; ///< 'bufhidden' - char_u *b_p_bt; ///< 'buftype' + char *b_p_bh; ///< 'bufhidden' + char *b_p_bt; ///< 'buftype' int b_has_qf_entry; ///< quickfix exists for buffer int b_p_bl; ///< 'buflisted' long b_p_channel; ///< 'channel' int b_p_cin; ///< 'cindent' - char_u *b_p_cino; ///< 'cinoptions' - char_u *b_p_cink; ///< 'cinkeys' - char_u *b_p_cinw; ///< 'cinwords' - char_u *b_p_cinsd; ///< 'cinscopedecls' - char_u *b_p_com; ///< 'comments' - char_u *b_p_cms; ///< 'commentstring' - char_u *b_p_cpt; ///< 'complete' + char *b_p_cino; ///< 'cinoptions' + char *b_p_cink; ///< 'cinkeys' + char *b_p_cinw; ///< 'cinwords' + char *b_p_cinsd; ///< 'cinscopedecls' + char *b_p_com; ///< 'comments' + char *b_p_cms; ///< 'commentstring' + char *b_p_cpt; ///< 'complete' #ifdef BACKSLASH_IN_FILENAME - char_u *b_p_csl; ///< 'completeslash' + char *b_p_csl; ///< 'completeslash' #endif - char_u *b_p_cfu; ///< 'completefunc' - char_u *b_p_ofu; ///< 'omnifunc' - char_u *b_p_tfu; ///< 'tagfunc' - char_u *b_p_umf; ///< 'usermarkfunc' + char *b_p_cfu; ///< 'completefunc' + char *b_p_ofu; ///< 'omnifunc' + char *b_p_tfu; ///< 'tagfunc' + char *b_p_umf; ///< 'usermarkfunc' int b_p_eol; ///< 'endofline' int b_p_fixeol; ///< 'fixendofline' int b_p_et; ///< 'expandtab' int b_p_et_nobin; ///< b_p_et saved for binary mode int b_p_et_nopaste; ///< b_p_et saved for paste mode - char_u *b_p_fenc; ///< 'fileencoding' - char_u *b_p_ff; ///< 'fileformat' - char_u *b_p_ft; ///< 'filetype' - char_u *b_p_fo; ///< 'formatoptions' - char_u *b_p_flp; ///< 'formatlistpat' + char *b_p_fenc; ///< 'fileencoding' + char *b_p_ff; ///< 'fileformat' + char *b_p_ft; ///< 'filetype' + char *b_p_fo; ///< 'formatoptions' + char *b_p_flp; ///< 'formatlistpat' int b_p_inf; ///< 'infercase' - char_u *b_p_isk; ///< 'iskeyword' - char_u *b_p_def; ///< 'define' local value - char_u *b_p_inc; ///< 'include' - char_u *b_p_inex; ///< 'includeexpr' + char *b_p_isk; ///< 'iskeyword' + char *b_p_def; ///< 'define' local value + char *b_p_inc; ///< 'include' + char *b_p_inex; ///< 'includeexpr' uint32_t b_p_inex_flags; ///< flags for 'includeexpr' - char_u *b_p_inde; ///< 'indentexpr' + char *b_p_inde; ///< 'indentexpr' uint32_t b_p_inde_flags; ///< flags for 'indentexpr' - char_u *b_p_indk; ///< 'indentkeys' - char_u *b_p_fp; ///< 'formatprg' - char_u *b_p_fex; ///< 'formatexpr' + char *b_p_indk; ///< 'indentkeys' + char *b_p_fp; ///< 'formatprg' + char *b_p_fex; ///< 'formatexpr' uint32_t b_p_fex_flags; ///< flags for 'formatexpr' - char_u *b_p_kp; ///< 'keywordprg' + char *b_p_kp; ///< 'keywordprg' int b_p_lisp; ///< 'lisp' - char_u *b_p_menc; ///< 'makeencoding' - char_u *b_p_mps; ///< 'matchpairs' + char *b_p_menc; ///< 'makeencoding' + char *b_p_mps; ///< 'matchpairs' int b_p_ml; ///< 'modeline' int b_p_ml_nobin; ///< b_p_ml saved for binary mode int b_p_ma; ///< 'modifiable' - char_u *b_p_nf; ///< 'nrformats' + char *b_p_nf; ///< 'nrformats' int b_p_pi; ///< 'preserveindent' - char_u *b_p_qe; ///< 'quoteescape' + char *b_p_qe; ///< 'quoteescape' int b_p_ro; ///< 'readonly' long b_p_sw; ///< 'shiftwidth' long b_p_scbk; ///< 'scrollback' int b_p_si; ///< 'smartindent' long b_p_sts; ///< 'softtabstop' long b_p_sts_nopaste; ///< b_p_sts saved for paste mode - char_u *b_p_sua; ///< 'suffixesadd' + char *b_p_sua; ///< 'suffixesadd' int b_p_swf; ///< 'swapfile' long b_p_smc; ///< 'synmaxcol' - char_u *b_p_syn; ///< 'syntax' + char *b_p_syn; ///< 'syntax' long b_p_ts; ///< 'tabstop' long b_p_tw; ///< 'textwidth' long b_p_tw_nobin; ///< b_p_tw saved for binary mode @@ -762,29 +758,29 @@ struct file_buffer { long b_p_wm; ///< 'wrapmargin' long b_p_wm_nobin; ///< b_p_wm saved for binary mode long b_p_wm_nopaste; ///< b_p_wm saved for paste mode - char_u *b_p_vsts; ///< 'varsofttabstop' - long *b_p_vsts_array; ///< 'varsofttabstop' in internal format - char_u *b_p_vsts_nopaste; ///< b_p_vsts saved for paste mode - char_u *b_p_vts; ///< 'vartabstop' - long *b_p_vts_array; ///< 'vartabstop' in internal format - char_u *b_p_keymap; ///< 'keymap' + char *b_p_vsts; ///< 'varsofttabstop' + long *b_p_vsts_array; ///< 'varsofttabstop' in internal format + char *b_p_vsts_nopaste; ///< b_p_vsts saved for paste mode + char *b_p_vts; ///< 'vartabstop' + long *b_p_vts_array; ///< 'vartabstop' in internal format + char *b_p_keymap; ///< 'keymap' // local values for options which are normally global - char_u *b_p_gp; ///< 'grepprg' local value - char_u *b_p_mp; ///< 'makeprg' local value - char_u *b_p_efm; ///< 'errorformat' local value - char_u *b_p_ep; ///< 'equalprg' local value - char_u *b_p_path; ///< 'path' local value + char *b_p_gp; ///< 'grepprg' local value + char *b_p_mp; ///< 'makeprg' local value + char *b_p_efm; ///< 'errorformat' local value + char *b_p_ep; ///< 'equalprg' local value + char *b_p_path; ///< 'path' local value int b_p_ar; ///< 'autoread' local value - char_u *b_p_tags; ///< 'tags' local value - char_u *b_p_tc; ///< 'tagcase' local value + char *b_p_tags; ///< 'tags' local value + char *b_p_tc; ///< 'tagcase' local value unsigned b_tc_flags; ///< flags for 'tagcase' - char_u *b_p_dict; ///< 'dictionary' local value - char_u *b_p_tsr; ///< 'thesaurus' local value - char_u *b_p_tsrfu; ///< 'thesaurusfunc' local value + char *b_p_dict; ///< 'dictionary' local value + char *b_p_tsr; ///< 'thesaurus' local value + char *b_p_tsrfu; ///< 'thesaurusfunc' local value long b_p_ul; ///< 'undolevels' local value int b_p_udf; ///< 'undofile' - char_u *b_p_lw; ///< 'lispwords' local value + char *b_p_lw; ///< 'lispwords' local value // end of buffer options @@ -852,7 +848,7 @@ struct file_buffer { * spell buffer - used for spell info, never displayed and doesn't have a * file name. */ - bool b_help; // TRUE for help file buffer (when set b_p_bt + bool b_help; // true for help file buffer (when set b_p_bt // is "help") bool b_spell; // True for a spell file buffer, most fields // are not used! Use the B_SPELL macro to @@ -977,15 +973,15 @@ struct tabpage_S { * When the display is changed (e.g., when clearing the screen) w_lines_valid * is changed to exclude invalid entries. * When making changes to the buffer, wl_valid is reset to indicate wl_size - * may not reflect what is actually in the buffer. When wl_valid is FALSE, + * may not reflect what is actually in the buffer. When wl_valid is false, * the entries can only be used to count the number of displayed lines used. * wl_lnum and wl_lastlnum are invalid too. */ typedef struct w_line { linenr_T wl_lnum; // buffer line number for logical line uint16_t wl_size; // height in screen lines - char wl_valid; // TRUE values are valid for text in buffer - char wl_folded; // TRUE when this is a range of folded lines + char wl_valid; // true values are valid for text in buffer + char wl_folded; // true when this is a range of folded lines linenr_T wl_lastlnum; // last buffer line number for logical line } wline_T; @@ -1135,43 +1131,6 @@ typedef struct { pos_T w_cursor_corr; // corrected cursor position } pos_save_T; -/// Indices into vimmenu_T->strings[] and vimmenu_T->noremap[] for each mode -/// \addtogroup MENU_INDEX -/// @{ -enum { - MENU_INDEX_INVALID = -1, - MENU_INDEX_NORMAL = 0, - MENU_INDEX_VISUAL = 1, - MENU_INDEX_SELECT = 2, - MENU_INDEX_OP_PENDING = 3, - MENU_INDEX_INSERT = 4, - MENU_INDEX_CMDLINE = 5, - MENU_INDEX_TERMINAL = 6, - MENU_INDEX_TIP = 7, - MENU_MODES = 8, -}; - -typedef struct VimMenu vimmenu_T; - -struct VimMenu { - int modes; ///< Which modes is this menu visible for - int enabled; ///< for which modes the menu is enabled - char *name; ///< Name of menu, possibly translated - char *dname; ///< Displayed Name ("name" without '&') - char *en_name; ///< "name" untranslated, NULL when - ///< was not translated - char *en_dname; ///< NULL when "dname" untranslated - int mnemonic; ///< mnemonic key (after '&') - char *actext; ///< accelerator text (after TAB) - long priority; ///< Menu order priority - char *strings[MENU_MODES]; ///< Mapped string for each mode - int noremap[MENU_MODES]; ///< A \ref REMAP_VALUES flag for each mode - bool silent[MENU_MODES]; ///< A silent flag for each mode - vimmenu_T *children; ///< Children of sub-menu - vimmenu_T *parent; ///< Parent of menu - vimmenu_T *next; ///< Next item in menu -}; - /// Structure which contains all information that belongs to a window. /// /// All row numbers are relative to the start of the window, except w_winrow. @@ -1183,11 +1142,14 @@ struct window_S { synblock_T *w_s; ///< for :ownsyntax + int w_ns_hl; + int w_ns_hl_winhl; + int w_ns_hl_active; + int *w_ns_hl_attr; + int w_hl_id_normal; ///< 'winhighlight' normal id int w_hl_attr_normal; ///< 'winhighlight' normal final attrs - - int w_hl_ids[HLF_COUNT]; ///< 'winhighlight' id - int w_hl_attrs[HLF_COUNT]; ///< 'winhighlight' final attrs + int w_hl_attr_normalnc; ///< 'winhighlight' NormalNC final attrs int w_hl_needs_update; ///< attrs need to be recalculated @@ -1266,8 +1228,8 @@ struct window_S { */ linenr_T w_topline; /* buffer line number of the line at the top of the window */ - char w_topline_was_set; /* flag set to TRUE when topline is set, - e.g. by winrestview() */ + char w_topline_was_set; // flag set to true when topline is set, + // e.g. by winrestview() int w_topfill; // number of filler lines above w_topline int w_old_topfill; // w_topfill at last redraw bool w_botfill; // true when filler lines are actually @@ -1279,9 +1241,10 @@ struct window_S { colnr_T w_skipcol; // starting column when a single line // doesn't fit in the window - // four fields that are only used when there is a WinScrolled autocommand + // five fields that are only used when there is a WinScrolled autocommand linenr_T w_last_topline; ///< last known value for w_topline colnr_T w_last_leftcol; ///< last known value for w_leftcol + colnr_T w_last_skipcol; ///< last known value for w_skipcol int w_last_width; ///< last known value for w_width int w_last_height; ///< last known value for w_height @@ -1390,7 +1353,7 @@ struct window_S { int w_redr_type; // type of redraw to be performed on win int w_upd_rows; // number of window lines to update when - // w_redr_type is REDRAW_TOP + // w_redr_type is UPD_REDRAW_TOP linenr_T w_redraw_top; // when != 0: first line needing redraw linenr_T w_redraw_bot; // when != 0: last line needing redraw bool w_redr_status; // if true statusline/winbar must be redrawn @@ -1402,7 +1365,7 @@ struct window_S { linenr_T w_ru_topline; // topline shown in ruler linenr_T w_ru_line_count; // line count used for ruler int w_ru_topfill; // topfill shown in ruler - char w_ru_empty; // TRUE if ruler shows 0-1 (empty line) + char w_ru_empty; // true if ruler shows 0-1 (empty line) int w_alt_fnum; // alternate file (for # and CTRL-^) @@ -1509,11 +1472,6 @@ struct window_S { size_t w_winbar_click_defs_size; }; -static inline int win_hl_attr(win_T *wp, int hlf) -{ - return wp->w_hl_attrs[hlf]; -} - /// Macros defined in Vim, but not in Neovim #define CHANGEDTICK(buf) \ (=== Include buffer.h & use buf_(get|set|inc) _changedtick ===) diff --git a/src/nvim/change.c b/src/nvim/change.c index c063ece907..85ab92e49b 100644 --- a/src/nvim/change.c +++ b/src/nvim/change.c @@ -10,6 +10,7 @@ #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/extmark.h" @@ -23,9 +24,9 @@ #include "nvim/move.h" #include "nvim/option.h" #include "nvim/plines.h" -#include "nvim/screen.h" #include "nvim/search.h" #include "nvim/state.h" +#include "nvim/textformat.h" #include "nvim/ui.h" #include "nvim/undo.h" @@ -96,8 +97,7 @@ void changed(void) // Create a swap file if that is wanted. // Don't do this for "nofile" and "nowrite" buffer types. - if (curbuf->b_may_swap - && !bt_dontwrite(curbuf)) { + if (curbuf->b_may_swap && !bt_dontwrite(curbuf)) { bool save_need_wait_return = need_wait_return; need_wait_return = false; @@ -223,8 +223,8 @@ static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume, linenr_T FOR_ALL_TAB_WINDOWS(tp, wp) { if (wp->w_buffer == curbuf) { // Mark this window to be redrawn later. - if (wp->w_redr_type < VALID) { - wp->w_redr_type = VALID; + if (wp->w_redr_type < UPD_VALID) { + wp->w_redr_type = UPD_VALID; } // Check if a change in the buffer has invalidated the cached @@ -301,17 +301,17 @@ static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume, linenr_T // requires a redraw. if (wp->w_p_rnu && xtra != 0) { wp->w_last_cursor_lnum_rnu = 0; - redraw_later(wp, VALID); + redraw_later(wp, UPD_VALID); } // Cursor line highlighting probably need to be updated with - // "VALID" if it's below the change. + // "UPD_VALID" if it's below the change. // If the cursor line is inside the change we need to redraw more. if (wp->w_p_cul) { if (xtra == 0) { - redraw_later(wp, VALID); + redraw_later(wp, UPD_VALID); } else if (lnum <= wp->w_last_cursorline) { - redraw_later(wp, SOME_VALID); + redraw_later(wp, UPD_SOME_VALID); } } } @@ -319,8 +319,8 @@ static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume, linenr_T // Call update_screen() later, which checks out what needs to be redrawn, // since it notices b_mod_set and then uses b_mod_*. - if (must_redraw < VALID) { - must_redraw = VALID; + if (must_redraw < UPD_VALID) { + must_redraw = UPD_VALID; } // when the cursor line is changed always trigger CursorMoved @@ -364,7 +364,7 @@ void changed_bytes(linenr_T lnum, colnr_T col) if (curwin->w_p_diff) { FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { if (wp->w_p_diff && wp != curwin) { - redraw_later(wp, VALID); + redraw_later(wp, UPD_VALID); linenr_T wlnum = diff_lnum_win(lnum, wp); if (wlnum > 0) { changedOneline(wp->w_buffer, wlnum); @@ -421,12 +421,12 @@ void deleted_lines(linenr_T lnum, linenr_T count) /// be triggered to display the cursor. void deleted_lines_mark(linenr_T lnum, long count) { - // if we deleted the entire buffer, we need to implicitly add a new empty line bool made_empty = (count > 0) && curbuf->b_ml.ml_flags & ML_EMPTY; - mark_adjust(lnum, (linenr_T)(lnum + count - 1), (long)MAXLNUM, - -(linenr_T)count + (made_empty?1:0), - kExtmarkUndo); + mark_adjust(lnum, (linenr_T)(lnum + count - 1), MAXLNUM, -(linenr_T)count, kExtmarkNOOP); + // if we deleted the entire buffer, we need to implicitly add a new empty line + extmark_adjust(curbuf, lnum, (linenr_T)(lnum + count - 1), MAXLNUM, + -(linenr_T)count + (made_empty ? 1 : 0), kExtmarkUndo); changed_lines(lnum, 0, lnum + (linenr_T)count, (linenr_T)(-count), true); } @@ -493,7 +493,7 @@ void changed_lines(linenr_T lnum, colnr_T col, linenr_T lnume, linenr_T xtra, bo FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { if (wp->w_p_diff && wp != curwin) { - redraw_later(wp, VALID); + redraw_later(wp, UPD_VALID); wlnum = diff_lnum_win(lnum, wp); if (wlnum > 0) { changed_lines_buf(wp->w_buffer, wlnum, @@ -534,21 +534,72 @@ void unchanged(buf_T *buf, int ff, bool always_inc_changedtick) } } +/// Save the current values of 'fileformat' and 'fileencoding', so that we know +/// the file must be considered changed when the value is different. +void save_file_ff(buf_T *buf) +{ + buf->b_start_ffc = (unsigned char)(*buf->b_p_ff); + buf->b_start_eol = buf->b_p_eol; + buf->b_start_bomb = buf->b_p_bomb; + + // Only use free/alloc when necessary, they take time. + if (buf->b_start_fenc == NULL + || STRCMP(buf->b_start_fenc, buf->b_p_fenc) != 0) { + xfree(buf->b_start_fenc); + buf->b_start_fenc = xstrdup(buf->b_p_fenc); + } +} + +/// Return true if 'fileformat' and/or 'fileencoding' has a different value +/// from when editing started (save_file_ff() called). +/// Also when 'endofline' was changed and 'binary' is set, or when 'bomb' was +/// changed and 'binary' is not set. +/// Also when 'endofline' was changed and 'fixeol' is not set. +/// When "ignore_empty" is true don't consider a new, empty buffer to be +/// changed. +bool file_ff_differs(buf_T *buf, bool ignore_empty) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +{ + // In a buffer that was never loaded the options are not valid. + if (buf->b_flags & BF_NEVERLOADED) { + return false; + } + if (ignore_empty + && (buf->b_flags & BF_NEW) + && buf->b_ml.ml_line_count == 1 + && *ml_get_buf(buf, (linenr_T)1, false) == NUL) { + return false; + } + 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) { + return true; + } + if (!buf->b_p_bin && buf->b_start_bomb != buf->b_p_bomb) { + return true; + } + if (buf->b_start_fenc == NULL) { + return *buf->b_p_fenc != NUL; + } + return STRCMP(buf->b_start_fenc, buf->b_p_fenc) != 0; +} + /// Insert string "p" at the cursor position. Stops at a NUL byte. /// Handles Replace mode and multi-byte characters. -void ins_bytes(char_u *p) +void ins_bytes(char *p) { ins_bytes_len(p, STRLEN(p)); } /// Insert string "p" with length "len" at the cursor position. /// Handles Replace mode and multi-byte characters. -void ins_bytes_len(char_u *p, size_t len) +void ins_bytes_len(char *p, size_t len) { size_t n; for (size_t i = 0; i < len; i += n) { // avoid reading past p[len] - n = (size_t)utfc_ptr2len_len(p + i, (int)(len - i)); + n = (size_t)utfc_ptr2len_len((char_u *)p + i, (int)(len - i)); ins_char_bytes(p + i, n); } } @@ -560,18 +611,18 @@ void ins_bytes_len(char_u *p, size_t len) /// convert bytes to a character. void ins_char(int c) { - char_u buf[MB_MAXBYTES + 1]; - size_t n = (size_t)utf_char2bytes(c, (char *)buf); + char buf[MB_MAXBYTES + 1]; + size_t n = (size_t)utf_char2bytes(c, buf); // When "c" is 0x100, 0x200, etc. we don't want to insert a NUL byte. // Happens for CTRL-Vu9900. if (buf[0] == 0) { buf[0] = '\n'; } - ins_char_bytes((char_u *)buf, n); + ins_char_bytes(buf, n); } -void ins_char_bytes(char_u *buf, size_t charlen) +void ins_char_bytes(char *buf, size_t charlen) { // Break tabs if needed. if (virtual_active() && curwin->w_cursor.coladd > 0) { @@ -580,7 +631,7 @@ void ins_char_bytes(char_u *buf, size_t charlen) size_t col = (size_t)curwin->w_cursor.col; linenr_T lnum = curwin->w_cursor.lnum; - char_u *oldp = ml_get(lnum); + char *oldp = (char *)ml_get(lnum); size_t linelen = STRLEN(oldp) + 1; // length of old line including NUL // The lengths default to the values for when not replacing. @@ -610,7 +661,7 @@ void ins_char_bytes(char_u *buf, size_t charlen) if (vcol > new_vcol && oldp[col + oldlen] == TAB) { break; } - oldlen += (size_t)utfc_ptr2len((char *)oldp + col + oldlen); + oldlen += (size_t)utfc_ptr2len(oldp + col + oldlen); // Deleted a bit too much, insert spaces. if (vcol > new_vcol) { newlen += (size_t)(vcol - new_vcol); @@ -619,7 +670,7 @@ void ins_char_bytes(char_u *buf, size_t charlen) curwin->w_p_list = old_list; } else if (oldp[col] != NUL) { // normal replace - oldlen = (size_t)utfc_ptr2len((char *)oldp + col); + oldlen = (size_t)utfc_ptr2len(oldp + col); } // Push the replaced bytes onto the replace stack, so that they can be @@ -632,7 +683,7 @@ void ins_char_bytes(char_u *buf, size_t charlen) } } - char_u *newp = xmalloc(linelen + newlen - oldlen); + char *newp = xmalloc(linelen + newlen - oldlen); // Copy bytes before the cursor. if (col > 0) { @@ -640,7 +691,7 @@ void ins_char_bytes(char_u *buf, size_t charlen) } // Copy bytes after the changed character(s). - char_u *p = newp + col; + char *p = newp + col; if (linelen > col + oldlen) { memmove(p + newlen, oldp + col + oldlen, (size_t)(linelen - col - oldlen)); @@ -655,7 +706,7 @@ void ins_char_bytes(char_u *buf, size_t charlen) } // Replace the line in the buffer. - ml_replace(lnum, (char *)newp, false); + ml_replace(lnum, newp, false); // mark the buffer as changed and prepare for displaying inserted_bytes(lnum, (colnr_T)col, (int)oldlen, (int)newlen); @@ -665,7 +716,7 @@ void ins_char_bytes(char_u *buf, size_t charlen) if (p_sm && (State & MODE_INSERT) && msg_silent == 0 && !ins_compl_active()) { - showmatch(utf_ptr2char((char *)buf)); + showmatch(utf_ptr2char(buf)); } if (!p_ri || (State & REPLACE_FLAG)) { @@ -678,7 +729,7 @@ void ins_char_bytes(char_u *buf, size_t charlen) /// Insert a string at the cursor position. /// Note: Does NOT handle Replace mode. /// Caller must have prepared for undo. -void ins_str(char_u *s) +void ins_str(char *s) { int newlen = (int)STRLEN(s); linenr_T lnum = curwin->w_cursor.lnum; @@ -688,10 +739,10 @@ void ins_str(char_u *s) } colnr_T col = curwin->w_cursor.col; - char_u *oldp = ml_get(lnum); + char *oldp = (char *)ml_get(lnum); int oldlen = (int)STRLEN(oldp); - char_u *newp = (char_u *)xmalloc((size_t)oldlen + (size_t)newlen + 1); + char *newp = xmalloc((size_t)oldlen + (size_t)newlen + 1); if (col > 0) { memmove(newp, oldp, (size_t)col); } @@ -699,7 +750,7 @@ void ins_str(char_u *s) int bytes = oldlen - col + 1; assert(bytes >= 0); memmove(newp + col + newlen, oldp + col, (size_t)bytes); - ml_replace(lnum, (char *)newp, false); + ml_replace(lnum, newp, false); inserted_bytes(lnum, col, 0, newlen); curwin->w_cursor.col += newlen; } @@ -723,9 +774,9 @@ int del_char(bool fixpos) int del_chars(long count, int fixpos) { int bytes = 0; - char_u *p = get_cursor_pos_ptr(); + char *p = (char *)get_cursor_pos_ptr(); for (long i = 0; i < count && *p != NUL; i++) { - int l = utfc_ptr2len((char *)p); + int l = utfc_ptr2len(p); bytes += l; p += l; } @@ -746,7 +797,7 @@ int del_bytes(colnr_T count, bool fixpos_arg, bool use_delcombine) linenr_T lnum = curwin->w_cursor.lnum; colnr_T col = curwin->w_cursor.col; bool fixpos = fixpos_arg; - char_u *oldp = ml_get(lnum); + char *oldp = (char *)ml_get(lnum); colnr_T oldlen = (colnr_T)STRLEN(oldp); // Can't do anything when the cursor is on the NUL after the line. @@ -766,7 +817,7 @@ int del_bytes(colnr_T count, bool fixpos_arg, bool use_delcombine) // If 'delcombine' is set and deleting (less than) one character, only // delete the last combining character. if (p_deco && use_delcombine - && utfc_ptr2len((char *)oldp + col) >= count) { + && utfc_ptr2len(oldp + col) >= count) { int cc[MAX_MCO]; (void)utfc_ptr2char(oldp + col, cc); @@ -775,9 +826,9 @@ int del_bytes(colnr_T count, bool fixpos_arg, bool use_delcombine) int n = col; do { col = n; - count = utf_ptr2len((char *)oldp + n); + count = utf_ptr2len(oldp + n); n += count; - } while (utf_composinglike(oldp + col, oldp + n)); + } while (utf_composinglike((char_u *)oldp + col, (char_u *)oldp + n)); fixpos = false; } } @@ -792,7 +843,7 @@ int del_bytes(colnr_T count, bool fixpos_arg, bool use_delcombine) && (get_ve_flags() & VE_ONEMORE) == 0) { curwin->w_cursor.col--; curwin->w_cursor.coladd = 0; - curwin->w_cursor.col -= utf_head_off(oldp, oldp + curwin->w_cursor.col); + curwin->w_cursor.col -= utf_head_off((char_u *)oldp, (char_u *)oldp + curwin->w_cursor.col); } count = oldlen - col; movelen = 1; @@ -801,7 +852,7 @@ int del_bytes(colnr_T count, bool fixpos_arg, bool use_delcombine) // If the old line has been allocated the deletion can be done in the // existing line. Otherwise a new line has to be allocated. bool was_alloced = ml_line_alloced(); // check if oldp was allocated - char_u *newp; + char *newp; if (was_alloced) { ml_add_deleted_len(curbuf->b_ml.ml_line_ptr, oldlen); newp = oldp; // use same allocated memory @@ -811,7 +862,7 @@ int del_bytes(colnr_T count, bool fixpos_arg, bool use_delcombine) } memmove(newp + col, oldp + col + count, (size_t)movelen); if (!was_alloced) { - ml_replace(lnum, (char *)newp, false); + ml_replace(lnum, newp, false); } // mark the buffer as changed and prepare for displaying @@ -823,10 +874,10 @@ int del_bytes(colnr_T count, bool fixpos_arg, bool use_delcombine) /// Copy the indent from ptr to the current line (and fill to size). /// Leaves the cursor on the first non-blank in the line. /// @return true if the line was changed. -int copy_indent(int size, char_u *src) +int copy_indent(int size, char *src) { - char_u *p = NULL; - char_u *line = NULL; + char *p = NULL; + char *line = NULL; int ind_len; int line_len = 0; int tab_pad; @@ -838,7 +889,7 @@ int copy_indent(int size, char_u *src) ind_len = 0; int ind_done = 0; int ind_col = 0; - char_u *s = src; + char *s = src; // Count/copy the usable portion of the source line. while (todo > 0 && ascii_iswhite(*s)) { @@ -924,7 +975,7 @@ int copy_indent(int size, char_u *src) memmove(p, get_cursor_line_ptr(), (size_t)line_len); // Replace the line - ml_replace(curwin->w_cursor.lnum, (char *)line, false); + ml_replace(curwin->w_cursor.lnum, line, false); // Put the cursor after the indent. curwin->w_cursor.col = ind_len; @@ -955,8 +1006,8 @@ int copy_indent(int size, char_u *src) /// @return true on success, false on failure int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) { - char_u *next_line = NULL; // copy of the next line - char_u *p_extra = NULL; // what goes to next line + char *next_line = NULL; // copy of the next line + char *p_extra = NULL; // what goes to next line colnr_T less_cols = 0; // less columns for mark in new line colnr_T less_cols_off = 0; // columns to skip for mark adjust pos_T old_cursor; // old cursor position @@ -969,9 +1020,9 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) int comment_start = 0; // start index of the comment leader char *lead_flags; // position in 'comments' for comment leader char *leader = NULL; // copy of comment leader - char_u *allocated = NULL; // allocated memory + char *allocated = NULL; // allocated memory char *p; - char_u saved_char = NUL; // init for GCC + char saved_char = NUL; // init for GCC pos_T *pos; bool do_si = may_do_si(); bool do_cindent; @@ -985,7 +1036,7 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) colnr_T mincol = curwin->w_cursor.col + 1; // make a copy of the current line so we can mess with it - char_u *saved_line = vim_strsave(get_cursor_line_ptr()); + char *saved_line = (char *)vim_strsave(get_cursor_line_ptr()); if (State & VREPLACE_FLAG) { // With MODE_VREPLACE we make a copy of the next line, which we will be @@ -996,9 +1047,9 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) // the line, replacing what was there before and pushing the right // stuff onto the replace stack. -- webb. if (curwin->w_cursor.lnum < orig_line_count) { - next_line = vim_strsave(ml_get(curwin->w_cursor.lnum + 1)); + next_line = (char *)vim_strsave(ml_get(curwin->w_cursor.lnum + 1)); } else { - next_line = vim_strsave((char_u *)""); + next_line = xstrdup(""); } // In MODE_VREPLACE state, a NL replaces the rest of the line, and @@ -1008,9 +1059,9 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) // autoindent etc) a bit later. replace_push(NUL); // Call twice because BS over NL expects it replace_push(NUL); - p = (char *)saved_line + curwin->w_cursor.col; + p = saved_line + curwin->w_cursor.col; while (*p != NUL) { - p += replace_push_mb((char_u *)p); + p += replace_push_mb(p); } saved_line[curwin->w_cursor.col] = NUL; } @@ -1018,7 +1069,7 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) if ((State & MODE_INSERT) && (State & VREPLACE_FLAG) == 0) { p_extra = saved_line + curwin->w_cursor.col; if (do_si) { // need first char after new line break - p = skipwhite((char *)p_extra); + p = skipwhite(p_extra); first_char = (unsigned char)(*p); } extra_len = (int)STRLEN(p_extra); @@ -1058,7 +1109,7 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) char *ptr; old_cursor = curwin->w_cursor; - ptr = (char *)saved_line; + ptr = saved_line; if (flags & OPENLINE_DO_COM) { lead_len = get_leader_len(ptr, NULL, false, true); } else { @@ -1142,7 +1193,7 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) // Don't do this if the previous line ended in ';' or // '}'. } else if (last_char != ';' && last_char != '}' - && cin_is_cinword((char_u *)ptr)) { + && cin_is_cinword(ptr)) { did_si = true; } } @@ -1192,13 +1243,13 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) // This may then be inserted in front of the new line. end_comment_pending = NUL; if (flags & OPENLINE_DO_COM) { - lead_len = get_leader_len((char *)saved_line, &lead_flags, dir == BACKWARD, true); + lead_len = get_leader_len(saved_line, &lead_flags, dir == BACKWARD, true); if (lead_len == 0 && curbuf->b_p_cin && do_cindent && dir == FORWARD && (!has_format_option(FO_NO_OPEN_COMS) || (flags & OPENLINE_FORMAT))) { // Check for a line comment after code. comment_start = check_linecomment(saved_line); if (comment_start != MAXCOL) { - lead_len = get_leader_len((char *)saved_line + comment_start, &lead_flags, false, true); + lead_len = get_leader_len(saved_line + comment_start, &lead_flags, false, true); if (lead_len != 0) { lead_len += comment_start; if (did_do_comment != NULL) { @@ -1213,13 +1264,13 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) if (lead_len > 0) { char *lead_repl = NULL; // replaces comment leader int lead_repl_len = 0; // length of *lead_repl - char_u lead_middle[COM_MAX_LEN]; // middle-comment string - char_u lead_end[COM_MAX_LEN]; // end-comment string - char_u *comment_end = NULL; // where lead_end has been found + char lead_middle[COM_MAX_LEN]; // middle-comment string + char lead_end[COM_MAX_LEN]; // end-comment string + char *comment_end = NULL; // where lead_end has been found int extra_space = false; // append extra space int current_flag; int require_blank = false; // requires blank after middle - char_u *p2; + char *p2; // If the comment leader has the start, middle or end flag, it may not // be used or may be replaced with the middle leader. @@ -1261,15 +1312,15 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) size_t n = copy_option_part(&p, (char *)lead_end, COM_MAX_LEN, ","); if (end_comment_pending == -1) { // we can set it now - end_comment_pending = lead_end[n - 1]; + end_comment_pending = (unsigned char)lead_end[n - 1]; } // If the end of the comment is in the same line, don't use // the comment leader. if (dir == FORWARD) { - for (p = (char *)saved_line + lead_len; *p; p++) { + for (p = saved_line + lead_len; *p; p++) { if (STRNCMP(p, lead_end, n) == 0) { - comment_end = (char_u *)p; + comment_end = p; lead_len = 0; break; } @@ -1302,17 +1353,17 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) // Remember where the end is, might want to use it to find the // start (for C-comments). if (dir == FORWARD) { - comment_end = (char_u *)skipwhite((char *)saved_line); + comment_end = skipwhite(saved_line); lead_len = 0; break; } // Doing "O" on the end of a comment inserts the middle leader. // Find the string for the middle leader, searching backwards. - while (p > (char *)curbuf->b_p_com && *p != ',') { + while (p > curbuf->b_p_com && *p != ',') { p--; } - for (lead_repl = p; lead_repl > (char *)curbuf->b_p_com + for (lead_repl = p; lead_repl > curbuf->b_p_com && lead_repl[-1] != ':'; lead_repl--) {} lead_repl_len = (int)(p - lead_repl); @@ -1321,7 +1372,7 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) extra_space = true; // Check whether we allow automatic ending of comments - for (p2 = (char_u *)p; *p2 && *p2 != ':'; p2++) { + for (p2 = p; *p2 && *p2 != ':'; p2++) { if (*p2 == COM_AUTO_END) { end_comment_pending = -1; // means we want to set it } @@ -1331,7 +1382,7 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) while (*p2 && *p2 != ',') { p2++; } - end_comment_pending = p2[-1]; + end_comment_pending = (unsigned char)p2[-1]; } break; } @@ -1357,7 +1408,7 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) + 1; assert(bytes >= 0); leader = xmalloc((size_t)bytes); - allocated = (char_u *)leader; // remember to free it later + allocated = leader; // remember to free it later STRLCPY(leader, saved_line, lead_len + 1); @@ -1391,7 +1442,7 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) // Compute the length of the replaced characters in // screen characters, not bytes. { - int repl_size = vim_strnsize((char_u *)lead_repl, lead_repl_len); + int repl_size = vim_strnsize(lead_repl, lead_repl_len); int old_size = 0; char *endp = p; int l; @@ -1436,13 +1487,13 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) // screen characters, not bytes. Move the part that is // not to be overwritten. { - int repl_size = vim_strnsize((char_u *)lead_repl, lead_repl_len); + int repl_size = vim_strnsize(lead_repl, lead_repl_len); int i; int l; for (i = 0; i < lead_len && p[i] != NUL; i += l) { l = utfc_ptr2len(p + i); - if (vim_strnsize((char_u *)p, i + l) > repl_size) { + if (vim_strnsize(p, i + l) > repl_size) { break; } } @@ -1485,7 +1536,7 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) // Recompute the indent, it may have changed. if (curbuf->b_p_ai || do_si) { - newindent = get_indent_str_vtab((char_u *)leader, + newindent = get_indent_str_vtab(leader, curbuf->b_p_ts, curbuf->b_p_vts_array, false); } @@ -1568,7 +1619,7 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) } if (curbuf->b_p_ai || (flags & OPENLINE_DELSPACES)) { while ((*p_extra == ' ' || *p_extra == '\t') - && !utf_iscomposing(utf_ptr2char((char *)p_extra + 1))) { + && !utf_iscomposing(utf_ptr2char(p_extra + 1))) { if (REPLACE_NORMAL(State)) { replace_push(*p_extra); } @@ -1582,7 +1633,7 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) } if (p_extra == NULL) { - p_extra = (char_u *)""; // append empty line + p_extra = ""; // append empty line } // concatenate leader and p_extra, if there is a leader @@ -1602,7 +1653,7 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) } } STRCAT(leader, p_extra); - p_extra = (char_u *)leader; + p_extra = leader; did_ai = true; // So truncating blanks works with comments less_cols -= lead_len; } else { @@ -1615,7 +1666,7 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) curwin->w_cursor.lnum--; } if ((State & VREPLACE_FLAG) == 0 || old_cursor.lnum >= orig_line_count) { - if (ml_append(curwin->w_cursor.lnum, (char *)p_extra, (colnr_T)0, false) == FAIL) { + if (ml_append(curwin->w_cursor.lnum, p_extra, (colnr_T)0, false) == FAIL) { goto theend; } // Postpone calling changed_lines(), because it would mess up folding @@ -1637,7 +1688,7 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) (void)u_save_cursor(); // errors are ignored! vr_lines_changed++; } - ml_replace(curwin->w_cursor.lnum, (char *)p_extra, true); + ml_replace(curwin->w_cursor.lnum, p_extra, true); changed_bytes(curwin->w_cursor.lnum, 0); // TODO(vigoux): extmark_splice_cols here?? curwin->w_cursor.lnum--; @@ -1702,7 +1753,7 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) if (trunc_line && !(flags & OPENLINE_KEEPTRAIL)) { truncate_spaces(saved_line); } - ml_replace(curwin->w_cursor.lnum, (char *)saved_line, false); + ml_replace(curwin->w_cursor.lnum, saved_line, false); int new_len = (int)STRLEN(saved_line); @@ -1787,10 +1838,10 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) // stuff onto the replace stack (via ins_char()). if (State & VREPLACE_FLAG) { // Put new line in p_extra - p_extra = vim_strsave(get_cursor_line_ptr()); + p_extra = (char *)vim_strsave(get_cursor_line_ptr()); // Put back original line - ml_replace(curwin->w_cursor.lnum, (char *)next_line, false); + ml_replace(curwin->w_cursor.lnum, next_line, false); // Insert new stuff into line again curwin->w_cursor.col = 0; @@ -1814,16 +1865,16 @@ theend: /// If "fixpos" is true fix the cursor position when done. void truncate_line(int fixpos) { - char_u *newp; + char *newp; linenr_T lnum = curwin->w_cursor.lnum; colnr_T col = curwin->w_cursor.col; if (col == 0) { - newp = vim_strsave((char_u *)""); + newp = xstrdup(""); } else { - newp = vim_strnsave(ml_get(lnum), (size_t)col); + newp = (char *)vim_strnsave(ml_get(lnum), (size_t)col); } - ml_replace(lnum, (char *)newp, false); + ml_replace(lnum, newp, false); // mark the buffer as changed and prepare for displaying changed_bytes(lnum, curwin->w_cursor.col); @@ -1900,7 +1951,7 @@ int get_leader_len(char *line, char **flags, bool backward, bool include_space) while (line[i] != NUL) { // scan through the 'comments' option for a match int found_one = false; - for (list = (char *)curbuf->b_p_com; *list;) { + for (list = curbuf->b_p_com; *list;) { // Get one option part into part_buf[]. Advance "list" to next // one. Put "string" at start of string. if (!got_com && flags != NULL) { @@ -2037,7 +2088,7 @@ int get_last_leader_offset(char *line, char **flags) while (--i >= lower_check_bound) { // scan through the 'comments' option for a match int found_one = false; - for (list = (char *)curbuf->b_p_com; *list;) { + for (list = curbuf->b_p_com; *list;) { char *flags_save = list; // Get one option part into part_buf[]. Advance list to next one. @@ -2122,7 +2173,7 @@ int get_last_leader_offset(char *line, char **flags) } len1 = (int)STRLEN(com_leader); - for (list = (char *)curbuf->b_p_com; *list;) { + for (list = curbuf->b_p_com; *list;) { char *flags_save = list; (void)copy_option_part(&list, part_buf2, COM_MAX_LEN, ","); diff --git a/src/nvim/channel.c b/src/nvim/channel.c index 20fae3a206..5910053025 100644 --- a/src/nvim/channel.c +++ b/src/nvim/channel.c @@ -4,14 +4,15 @@ #include "nvim/api/private/converter.h" #include "nvim/api/private/helpers.h" #include "nvim/api/ui.h" +#include "nvim/autocmd.h" #include "nvim/channel.h" #include "nvim/eval.h" #include "nvim/eval/encode.h" #include "nvim/event/socket.h" -#include "nvim/fileio.h" #include "nvim/lua/executor.h" #include "nvim/msgpack_rpc/channel.h" #include "nvim/msgpack_rpc/server.h" +#include "nvim/os/fs.h" #include "nvim/os/shell.h" #ifdef WIN32 # include "nvim/os/os_win_console.h" @@ -314,8 +315,6 @@ Channel *channel_job_start(char **argv, CallbackReader on_stdout, CallbackReader ChannelStdinMode stdin_mode, const char *cwd, uint16_t pty_width, uint16_t pty_height, dict_T *env, varnumber_T *status_out) { - assert(cwd == NULL || os_isdir_executable(cwd)); - Channel *chan = channel_alloc(kChannelStreamProc); chan->on_data = on_stdout; chan->on_stderr = on_stderr; diff --git a/src/nvim/charset.c b/src/nvim/charset.c index a26a7f6aaf..4efd8a4ad1 100644 --- a/src/nvim/charset.c +++ b/src/nvim/charset.c @@ -126,7 +126,7 @@ int buf_init_chartab(buf_T *buf, int global) } // Init word char flags all to false - memset(buf->b_chartab, 0, (size_t)32); + CLEAR_FIELD(buf->b_chartab); // In lisp mode the '-' character is included in keywords. if (buf->b_p_lisp) { @@ -140,16 +140,16 @@ int buf_init_chartab(buf_T *buf, int global) const char_u *p; if (i == 0) { // first round: 'isident' - p = p_isi; + p = (char_u *)p_isi; } else if (i == 1) { // second round: 'isprint' - p = p_isp; + p = (char_u *)p_isp; } else if (i == 2) { // third round: 'isfname' - p = p_isf; + p = (char_u *)p_isf; } else { // i == 3 // fourth round: 'iskeyword' - p = buf->b_p_isk; + p = (char_u *)buf->b_p_isk; } while (*p) { @@ -246,7 +246,7 @@ int buf_init_chartab(buf_T *buf, int global) } c = *p; - p = skip_to_option_part(p); + p = (char_u *)skip_to_option_part((char *)p); if ((c == ',') && (*p == NUL)) { // Trailing comma is not allowed. @@ -313,7 +313,7 @@ size_t transstr_len(const char *const s, bool untab) const size_t l = (size_t)utfc_ptr2len(p); if (l > 1) { int pcc[MAX_MCO + 1]; - pcc[0] = utfc_ptr2char((const char_u *)p, &pcc[1]); + pcc[0] = utfc_ptr2char(p, &pcc[1]); if (vim_isprintc(pcc[0])) { len += l; @@ -359,7 +359,7 @@ size_t transstr_buf(const char *const s, char *const buf, const size_t len, bool break; // Exceeded `buf` size. } int pcc[MAX_MCO + 1]; - pcc[0] = utfc_ptr2char((const char_u *)p, &pcc[1]); + pcc[0] = utfc_ptr2char(p, &pcc[1]); if (vim_isprintc(pcc[0])) { memmove(buf_p, p, l); @@ -709,7 +709,7 @@ int ptr2cells(const char *p_in) /// @return number of character cells. int vim_strsize(char *s) { - return vim_strnsize((char_u *)s, MAXCOL); + return vim_strnsize(s, MAXCOL); } /// Return the number of character cells string "s[len]" will take on the @@ -721,13 +721,13 @@ int vim_strsize(char *s) /// @param len /// /// @return Number of character cells. -int vim_strnsize(char_u *s, int len) +int vim_strnsize(char *s, int len) { assert(s != NULL); int size = 0; while (*s != NUL && --len >= 0) { - int l = utfc_ptr2len((char *)s); - size += ptr2cells((char *)s); + int l = utfc_ptr2len(s); + size += ptr2cells(s); s += l; len -= l - 1; } @@ -930,14 +930,18 @@ void getvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, colnr_T *en posptr -= utf_head_off(line, posptr); } + chartabsize_T cts; + init_chartabsize_arg(&cts, wp, pos->lnum, 0, line, line); + // This function is used very often, do some speed optimizations. // When 'list', 'linebreak', 'showbreak' and 'breakindent' are not set - // use a simple loop. + // and there are no virtual text use a simple loop. // Also use this when 'list' is set but tabs take their normal size. if ((!wp->w_p_list || (wp->w_p_lcs_chars.tab1 != NUL)) && !wp->w_p_lbr && *get_showbreak_value(wp) == NUL - && !wp->w_p_bri) { + && !wp->w_p_bri + && !cts.cts_has_virt_text) { for (;;) { head = 0; int c = *ptr; @@ -984,25 +988,29 @@ void getvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, colnr_T *en } else { for (;;) { // A tab gets expanded, depending on the current column + // Other things also take up space. head = 0; - incr = win_lbr_chartabsize(wp, line, ptr, vcol, &head); + incr = win_lbr_chartabsize(&cts, &head); // make sure we don't go past the end of the line - if (*ptr == NUL) { + if (*cts.cts_ptr == NUL) { // NUL at end of line only takes one column incr = 1; break; } - if ((posptr != NULL) && (ptr >= posptr)) { + if ((posptr != NULL) && ((char_u *)cts.cts_ptr >= posptr)) { // character at pos->col break; } - vcol += incr; - MB_PTR_ADV(ptr); + cts.cts_vcol += incr; + MB_PTR_ADV(cts.cts_ptr); } + vcol = cts.cts_vcol; + ptr = (char_u *)cts.cts_ptr; } + clear_chartabsize_arg(&cts); if (start != NULL) { *start = vcol + head; @@ -1013,6 +1021,8 @@ void getvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, colnr_T *en } if (cursor != NULL) { + // cursor is after inserted text + vcol += cts.cts_cur_text_width; if ((*ptr == TAB) && (State & MODE_NORMAL) && !wp->w_p_list @@ -1147,7 +1157,7 @@ char *skipwhite(const char *const p) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET { - return (char *)skipwhite_len((char_u *)p, STRLEN(p)); + return skipwhite_len(p, STRLEN(p)); } /// Like `skipwhite`, but skip up to `len` characters. @@ -1158,27 +1168,27 @@ char *skipwhite(const char *const p) /// /// @return Pointer to character after the skipped whitespace, or the `len`-th /// character in the string. -char_u *skipwhite_len(const char_u *p, size_t len) +char *skipwhite_len(const char *p, size_t len) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET { for (; len > 0 && ascii_iswhite(*p); len--) { p++; } - return (char_u *)p; + return (char *)p; } // getwhitecols: return the number of whitespace // columns (bytes) at the start of a given line intptr_t getwhitecols_curline(void) { - return getwhitecols(get_cursor_line_ptr()); + return getwhitecols((char *)get_cursor_line_ptr()); } -intptr_t getwhitecols(const char_u *p) +intptr_t getwhitecols(const char *p) FUNC_ATTR_PURE { - return (char_u *)skipwhite((char *)p) - p; + return skipwhite(p) - p; } /// Skip over digits @@ -1222,10 +1232,10 @@ const char *skipbin(const char *q) /// /// @return Pointer to the character after the skipped digits and hex /// characters. -char_u *skiphex(char_u *q) +char *skiphex(char *q) FUNC_ATTR_PURE { - char_u *p = q; + char *p = q; while (ascii_isxdigit(*p)) { // skip to next non-digit p++; @@ -1238,10 +1248,10 @@ char_u *skiphex(char_u *q) /// @param q /// /// @return Pointer to the digit or (NUL after the string). -char_u *skiptodigit(char_u *q) +char *skiptodigit(char *q) FUNC_ATTR_PURE { - char_u *p = q; + char *p = q; while (*p != NUL && !ascii_isdigit(*p)) { // skip to next digit p++; @@ -1272,10 +1282,10 @@ const char *skiptobin(const char *q) /// @param q /// /// @return Pointer to the hex character or (NUL after the string). -char_u *skiptohex(char_u *q) +char *skiptohex(char *q) FUNC_ATTR_PURE { - char_u *p = q; + char *p = q; while (*p != NUL && !ascii_isxdigit(*p)) { // skip to next digit p++; @@ -1288,13 +1298,13 @@ char_u *skiptohex(char_u *q) /// @param[in] p Text to skip over. /// /// @return Pointer to the next whitespace or NUL character. -char_u *skiptowhite(const char_u *p) +char *skiptowhite(const char *p) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE { while (*p != ' ' && *p != '\t' && *p != NUL) { p++; } - return (char_u *)p; + return (char *)p; } /// skiptowhite_esc: Like skiptowhite(), but also skip escaped chars @@ -1319,11 +1329,11 @@ char *skiptowhite_esc(char *p) /// @param[in] p Text to skip over. /// /// @return Pointer to the next '\n' or NUL character. -char_u *skip_to_newline(const char_u *const p) +char *skip_to_newline(const char *const p) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET { - return (char_u *)xstrchrnul((const char *)p, NL); + return xstrchrnul(p, NL); } /// Gets a number from a string and skips over it, signalling overflow. @@ -1412,10 +1422,10 @@ int32_t getdigits_int32(char **pp, bool strict, long def) /// Check that "lbuf" is empty or only contains blanks. /// /// @param lbuf line buffer to check -bool vim_isblankline(char_u *lbuf) +bool vim_isblankline(char *lbuf) FUNC_ATTR_PURE { - char_u *p = (char_u *)skipwhite((char *)lbuf); + char *p = skipwhite(lbuf); return *p == NUL || *p == '\r' || *p == '\n'; } @@ -1654,8 +1664,9 @@ int hex2nr(int c) } /// Convert two hex characters to a byte. -/// Return -1 if one of the characters is not hex. -int hexhex2nr(char_u *p) +/// +/// @return -1 if one of the characters is not hex. +int hexhex2nr(const char *p) FUNC_ATTR_PURE { if (!ascii_isxdigit(p[0]) || !ascii_isxdigit(p[1])) { @@ -1677,12 +1688,12 @@ int hexhex2nr(char_u *p) /// characters. /// /// @param str file path string to check -bool rem_backslash(const char_u *str) +bool rem_backslash(const char *str) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { #ifdef BACKSLASH_IN_FILENAME return str[0] == '\\' - && str[1] < 0x80 + && (uint8_t)str[1] < 0x80 && (str[1] == ' ' || (str[1] != NUL && str[1] != '*' @@ -1697,7 +1708,7 @@ bool rem_backslash(const char_u *str) /// Halve the number of backslashes in a file name argument. /// /// @param p -void backslash_halve(char_u *p) +void backslash_halve(char *p) { for (; *p; p++) { if (rem_backslash(p)) { @@ -1711,11 +1722,11 @@ void backslash_halve(char_u *p) /// @param p /// /// @return String with the number of backslashes halved. -char_u *backslash_halve_save(const char_u *p) +char *backslash_halve_save(const char *p) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET { // TODO(philix): simplify and improve backslash_halve_save algorithm - char_u *res = vim_strsave(p); + char *res = xstrdup(p); backslash_halve(res); return res; } diff --git a/src/nvim/cmdexpand.c b/src/nvim/cmdexpand.c new file mode 100644 index 0000000000..5c54404aab --- /dev/null +++ b/src/nvim/cmdexpand.c @@ -0,0 +1,2908 @@ +// 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 + +// cmdexpand.c: functions for command-line completion + +#include "nvim/api/private/helpers.h" +#include "nvim/arglist.h" +#include "nvim/ascii.h" +#include "nvim/buffer.h" +#include "nvim/charset.h" +#include "nvim/cmdexpand.h" +#include "nvim/cmdhist.h" +#include "nvim/drawscreen.h" +#include "nvim/eval.h" +#include "nvim/eval/funcs.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/help.h" +#include "nvim/highlight_group.h" +#include "nvim/if_cscope.h" +#include "nvim/locale.h" +#include "nvim/lua/executor.h" +#include "nvim/mapping.h" +#include "nvim/menu.h" +#include "nvim/option.h" +#include "nvim/os/os.h" +#include "nvim/popupmenu.h" +#include "nvim/profile.h" +#include "nvim/regexp.h" +#include "nvim/screen.h" +#include "nvim/search.h" +#include "nvim/sign.h" +#include "nvim/strings.h" +#include "nvim/syntax.h" +#include "nvim/tag.h" +#include "nvim/types.h" +#include "nvim/ui.h" +#include "nvim/usercmd.h" +#include "nvim/vim.h" +#include "nvim/window.h" + +/// Type used by ExpandGeneric() +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 *); + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "cmdexpand.c.generated.h" +#endif + +static int cmd_showtail; ///< Only show path tail in lists ? + +/// "compl_match_array" points the currently displayed list of entries in the +/// popup menu. It is NULL when there is no popup menu. +static pumitem_T *compl_match_array = NULL; +static int compl_match_arraysize; +/// First column in cmdline of the matched item for completion. +static int compl_startcol; +static int compl_selected; + +static int sort_func_compare(const void *s1, const void *s2) +{ + char_u *p1 = *(char_u **)s1; + char_u *p2 = *(char_u **)s2; + + if (*p1 != '<' && *p2 == '<') { + return -1; + } + if (*p1 == '<' && *p2 != '<') { + return 1; + } + return STRCMP(p1, p2); +} + +static void ExpandEscape(expand_T *xp, char_u *str, int numfiles, char **files, int options) +{ + int i; + char_u *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 defined(BACKSLASH_IN_FILENAME) + p = vim_strsave_escaped(files[i], (char_u *)" "); + xfree(files[i]); + files[i] = p; +#endif + } +#ifdef BACKSLASH_IN_FILENAME + p = (char_u *)vim_strsave_fnameescape((const char *)files[i], vse_what); +#else + p = (char_u *)vim_strsave_fnameescape((const char *)files[i], + xp->xp_shell ? VSE_SHELL : vse_what); +#endif + xfree(files[i]); + files[i] = (char *)p; + + // 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; + + // 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; + } + } + } +} + +/// 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 +/// normal character (instead of being expanded). This allows :s/^I^D etc. +/// +/// @param options extra options for ExpandOne() +/// @param escape if true, escape the returned matches +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; + int difflen; + + if (xp->xp_numfiles == -1) { + set_expand_context(xp); + cmd_showtail = expand_showtail(xp); + } + + if (xp->xp_context == EXPAND_UNSUCCESSFUL) { + beep_flush(); + return OK; // Something illegal on command line + } + if (xp->xp_context == EXPAND_NOTHING) { + // Caller can use the character as a normal char instead + return FAIL; + } + + if (!(ui_has(kUICmdline) || ui_has(kUIWildmenu))) { + msg_puts("..."); // show that we are busy + ui_flush(); + } + + i = (int)((char_u *)xp->xp_pattern - ccline->cmdbuff); + assert(ccline->cmdpos >= i); + xp->xp_pattern_len = (size_t)ccline->cmdpos - (size_t)i; + + if (type == WILD_NEXT || type == WILD_PREV) { + // Get next/previous match for a previous expanded pattern. + p2 = ExpandOne(xp, NULL, NULL, 0, type); + } else { + // Translate string into pattern and expand it. + p1 = addstar((char_u *)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 = ExpandOne(xp, p1, vim_strnsave(&ccline->cmdbuff[i], xp->xp_pattern_len), + use_options, type); + xfree(p1); + + // xp->xp_pattern might have been modified by ExpandOne (for example, + // in lua completion), so recompute the pattern index and length + i = (int)((char_u *)xp->xp_pattern - ccline->cmdbuff); + xp->xp_pattern_len = (size_t)ccline->cmdpos - (size_t)i; + + // Longest match: make sure it is not shorter, happens with :help. + if (p2 != NULL && type == WILD_LONGEST) { + for (j = 0; (size_t)j < xp->xp_pattern_len; j++) { + if (ccline->cmdbuff[i + j] == '*' + || ccline->cmdbuff[i + j] == '?') { + break; + } + } + if ((int)STRLEN(p2) < j) { + XFREE_CLEAR(p2); + } + } + } + + if (p2 != NULL && !got_int) { + 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 = (char *)ccline->cmdbuff + i; + } + assert(ccline->cmdpos <= ccline->cmdlen); + 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)); + ccline->cmdlen += difflen; + ccline->cmdpos += difflen; + } + xfree(p2); + + redrawcmd(); + cursorcmd(); + + // When expanding a ":map" command and no matches are found, assume that + // the key is supposed to be inserted literally + if (xp->xp_context == EXPAND_MAPPINGS && p2 == NULL) { + return FAIL; + } + + if (xp->xp_numfiles <= 0 && p2 == NULL) { + beep_flush(); + } else if (xp->xp_numfiles == 1) { + // free expanded pattern + (void)ExpandOne(xp, NULL, NULL, 0, WILD_FREE); + } + + return OK; +} + +void cmdline_pum_display(bool changed_array) +{ + pum_display(compl_match_array, compl_match_arraysize, compl_selected, + changed_array, compl_startcol); +} + +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 +void cmdline_pum_remove(void) +{ + pum_undisplay(true); + XFREE_CLEAR(compl_match_array); +} + +void cmdline_pum_cleanup(CmdlineInfo *cclp) +{ + cmdline_pum_remove(); + wildmenu_cleanup(cclp); +} + +/// Get the next or prev cmdline completion match. The index of the match is set +/// in "p_findex" +static char_u *get_next_or_prev_match(int mode, expand_T *xp, int *p_findex, char_u *orig_save) +{ + if (xp->xp_numfiles <= 0) { + return NULL; + } + + int findex = *p_findex; + + if (mode == WILD_PREV) { + if (findex == -1) { + findex = xp->xp_numfiles; + } + findex--; + } else { // mode == WILD_NEXT + findex++; + } + + // When wrapping around, return the original string, set findex to -1. + if (findex < 0) { + if (orig_save == NULL) { + findex = xp->xp_numfiles - 1; + } else { + findex = -1; + } + } + if (findex >= xp->xp_numfiles) { + if (orig_save == NULL) { + findex = 0; + } else { + findex = -1; + } + } + if (compl_match_array) { + compl_selected = findex; + cmdline_pum_display(false); + } else if (p_wmnu) { + redraw_wildmenu(xp, xp->xp_numfiles, xp->xp_files, findex, cmd_showtail); + } + *p_findex = findex; + + return vim_strsave(findex == -1 ? orig_save : (char_u *)xp->xp_files[findex]); +} + +/// Start the command-line expansion and get the matches. +static char_u *ExpandOne_start(int mode, expand_T *xp, char_u *str, int options) +{ + int non_suf_match; // number without matching suffix + char_u *ss = NULL; + + // Do the expansion. + if (ExpandFromContext(xp, str, &xp->xp_numfiles, &xp->xp_files, 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, + // causing the pattern to be added, which has illegal characters. + if (!(options & WILD_SILENT) && (options & WILD_LIST_NOTFOUND)) { + semsg(_(e_nomatch2), str); + } +#endif + } else if (xp->xp_numfiles == 0) { + if (!(options & WILD_SILENT)) { + semsg(_(e_nomatch2), str); + } + } else { + // Escape the matches for use on the command line. + 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 (xp->xp_numfiles) { + non_suf_match = xp->xp_numfiles; + } else { + non_suf_match = 1; + } + if ((xp->xp_context == EXPAND_FILES + || xp->xp_context == EXPAND_DIRECTORIES) + && xp->xp_numfiles > 1) { + // More than one match; check suffix. + // The files will have been sorted on matching suffix in + // expand_wildcards, only need to check the first two. + non_suf_match = 0; + for (int i = 0; i < 2; i++) { + if (match_suffix((char_u *)xp->xp_files[i])) { + non_suf_match++; + } + } + } + if (non_suf_match != 1) { + // Can we ever get here unless it's while expanding + // interactively? If not, we can get rid of this all + // together. Don't really want to wait for this message + // (and possibly have to hit return to continue!). + if (!(options & WILD_SILENT)) { + emsg(_(e_toomany)); + } else if (!(options & WILD_NO_BEEP)) { + beep_flush(); + } + } + if (!(non_suf_match != 1 && mode == WILD_EXPAND_FREE)) { + ss = vim_strsave((char_u *)xp->xp_files[0]); + } + } + } + + return ss; +} + +/// Return the longest common part in the list of cmdline completion matches. +static char *find_longest_match(expand_T *xp, int options) +{ + size_t len = 0; + + for (size_t mb_len; xp->xp_files[0][len]; len += mb_len) { + mb_len = (size_t)utfc_ptr2len(&xp->xp_files[0][len]); + int c0 = utf_ptr2char(&xp->xp_files[0][len]); + int i; + for (i = 1; i < xp->xp_numfiles; i++) { + int ci = utf_ptr2char(&xp->xp_files[i][len]); + + if (p_fic && (xp->xp_context == EXPAND_DIRECTORIES + || xp->xp_context == EXPAND_FILES + || xp->xp_context == EXPAND_SHELLCMD + || xp->xp_context == EXPAND_BUFFERS)) { + if (mb_tolower(c0) != mb_tolower(ci)) { + break; + } + } else if (c0 != ci) { + break; + } + } + if (i < xp->xp_numfiles) { + if (!(options & WILD_NO_BEEP)) { + vim_beep(BO_WILD); + } + break; + } + } + + return xstrndup(xp->xp_files[0], len); +} + +/// 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. +/// +/// "orig" is the originally expanded string, copied to allocated memory. It +/// should either be kept in orig_save or freed. When "mode" is WILD_NEXT or +/// WILD_PREV "orig" should be NULL. +/// +/// Results are cached in xp->xp_files and xp->xp_numfiles, except when "mode" +/// is WILD_EXPAND_FREE or WILD_ALL. +/// +/// mode = WILD_FREE: just free previously expanded matches +/// mode = WILD_EXPAND_FREE: normal expansion, do not keep matches +/// mode = WILD_EXPAND_KEEP: normal expansion, keep matches +/// mode = WILD_NEXT: use next match in multiple match, wrap to first +/// mode = WILD_PREV: use previous match in multiple match, wrap to first +/// mode = WILD_ALL: return all matches concatenated +/// mode = WILD_LONGEST: return longest matched part +/// mode = WILD_ALL_KEEP: get all matches, keep matches +/// mode = WILD_APPLY: apply the item selected in the cmdline completion +/// popup menu and close the menu. +/// mode = WILD_CANCEL: cancel and close the cmdline completion popup and +/// use the original text. +/// +/// options = WILD_LIST_NOTFOUND: list entries without a match +/// options = WILD_HOME_REPLACE: do home_replace() for buffer names +/// options = WILD_USE_NL: Use '\n' for WILD_ALL +/// options = WILD_NO_BEEP: Don't beep for multiple matches +/// options = WILD_ADD_SLASH: add a slash after directory names +/// options = WILD_KEEP_ALL: don't remove 'wildignore' entries +/// options = WILD_SILENT: don't print warning messages +/// options = WILD_ESCAPE: put backslash before special chars +/// options = WILD_ICASE: ignore case for files +/// +/// The variables xp->xp_context and xp->xp_backslash must have been set! +/// +/// @param orig allocated copy of original of expanded string +char_u *ExpandOne(expand_T *xp, char_u *str, char_u *orig, int options, int mode) +{ + char_u *ss = NULL; + static int findex; + static char_u *orig_save = NULL; // kept value of orig + int orig_saved = false; + int i; + + // first handle the case of using an old match + if (mode == WILD_NEXT || mode == WILD_PREV) { + return get_next_or_prev_match(mode, xp, &findex, orig_save); + } + + if (mode == WILD_CANCEL) { + ss = vim_strsave(orig_save ? orig_save : (char_u *)""); + } else if (mode == WILD_APPLY) { + ss = vim_strsave(findex == -1 ? (orig_save ? orig_save : (char_u *)"") + : (char_u *)xp->xp_files[findex]); + } + + // free old names + if (xp->xp_numfiles != -1 && mode != WILD_ALL && mode != WILD_LONGEST) { + FreeWild(xp->xp_numfiles, xp->xp_files); + xp->xp_numfiles = -1; + XFREE_CLEAR(orig_save); + } + findex = 0; + + if (mode == WILD_FREE) { // only release file name + return NULL; + } + + if (xp->xp_numfiles == -1 && mode != WILD_APPLY && mode != WILD_CANCEL) { + xfree(orig_save); + orig_save = orig; + orig_saved = true; + + ss = ExpandOne_start(mode, xp, str, options); + } + + // Find longest common part + if (mode == WILD_LONGEST && xp->xp_numfiles > 0) { + ss = (char_u *)find_longest_match(xp, options); + findex = -1; // next p_wc gets first one + } + + // Concatenate all matching names. Unless interrupted, this can be slow + // and the result probably won't be used. + // TODO(philix): use xstpcpy instead of strcat in a loop (ExpandOne) + if (mode == WILD_ALL && xp->xp_numfiles > 0 && !got_int) { + size_t len = 0; + for (i = 0; i < xp->xp_numfiles; i++) { + len += STRLEN(xp->xp_files[i]) + 1; + } + ss = xmalloc(len); + *ss = NUL; + for (i = 0; i < xp->xp_numfiles; i++) { + STRCAT(ss, xp->xp_files[i]); + if (i != xp->xp_numfiles - 1) { + STRCAT(ss, (options & WILD_USE_NL) ? "\n" : " "); + } + } + } + + if (mode == WILD_EXPAND_FREE || mode == WILD_ALL) { + ExpandCleanup(xp); + } + + // Free "orig" if it wasn't stored in "orig_save". + if (!orig_saved) { + xfree(orig); + } + + return ss; +} + +/// Prepare an expand structure for use. +void ExpandInit(expand_T *xp) + FUNC_ATTR_NONNULL_ALL +{ + CLEAR_POINTER(xp); + xp->xp_backslash = XP_BS_NONE; + xp->xp_numfiles = -1; +} + +/// Cleanup an expand structure after use. +void ExpandCleanup(expand_T *xp) +{ + if (xp->xp_numfiles >= 0) { + FreeWild(xp->xp_numfiles, xp->xp_files); + xp->xp_numfiles = -1; + } +} + +/// 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 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, ccline->cmdbuff, ccline->cmdpos, + &num_files, &files_found); + showtail = expand_showtail(xp); + if (i != EXPAND_OK) { + return i; + } + } else { + num_files = xp->xp_numfiles; + files_found = xp->xp_files; + showtail = cmd_showtail; + } + + bool compl_use_pum = (ui_has(kUICmdline) + ? ui_has(kUIPopupmenu) + : wildmenu && (wop_flags & WOP_PUM)) + || 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_u *endpos = (char_u *)(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; + } + + if (!wildmenu) { + msg_didany = false; // lines_left will be set + msg_start(); // prepare for paging + msg_putchar('\n'); + ui_flush(); + cmdline_row = msg_row; + msg_didany = false; // lines_left will be set again + msg_start(); // prepare for paging + } + + 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); + } else { + // find the length of the longest file name + maxlen = 0; + for (i = 0; i < num_files; 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); + } else { + j = vim_strsize(L_SHOWFILE(i)); + } + if (j > maxlen) { + maxlen = j; + } + } + + if (xp->xp_context == EXPAND_TAGS_LISTFILES) { + lines = num_files; + } else { + // compute the number of columns and lines for the listing + maxlen += 2; // two spaces between file names + columns = (Columns + 2) / maxlen; + if (columns < 1) { + columns = 1; + } + lines = (num_files + columns - 1) / columns; + } + + attr = HL_ATTR(HLF_D); // find out highlighting for directories + + if (xp->xp_context == EXPAND_TAGS_LISTFILES) { + msg_puts_attr(_("tagname"), HL_ATTR(HLF_T)); + msg_clr_eos(); + msg_advance(maxlen - 3); + msg_puts_attr(_(" kind file\n"), HL_ATTR(HLF_T)); + } + + // 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(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'); + } + ui_flush(); // show one line at a time + if (got_int) { + got_int = false; + break; + } + } + + // we redraw the command below the lines that we have just listed + // This is a bit tricky, but it saves a lot of screen updating. + cmdline_row = msg_row; // will put it back later + } + + if (xp->xp_numfiles == -1) { + FreeWild(num_files, files_found); + } + + 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) +{ + char_u *p; + char_u *t = (char_u *)s; + bool had_sep = false; + + for (p = (char_u *)s; *p != NUL;) { + if (vim_ispathsep(*p) +#ifdef BACKSLASH_IN_FILENAME + && !rem_backslash(p) +#endif + ) { + if (eager) { + t = p + 1; + } else { + had_sep = true; + } + } else if (had_sep) { + t = p; + had_sep = false; + } + MB_PTR_ADV(p); + } + return (char *)t; +} + +/// Return true if we only need to show the tail of completion matches. +/// When not completing file names or there is a wildcard in the path false is +/// returned. +static bool expand_showtail(expand_T *xp) +{ + char_u *s; + char_u *end; + + // When not completing file names a "/" may mean something different. + if (xp->xp_context != EXPAND_FILES + && xp->xp_context != EXPAND_SHELLCMD + && xp->xp_context != EXPAND_DIRECTORIES) { + return false; + } + + end = (char_u *)path_tail(xp->xp_pattern); + if (end == (char_u *)xp->xp_pattern) { // there is no path separator + return false; + } + + for (s = (char_u *)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)) { + s++; + } else if (vim_strchr("*?[", *s) != NULL) { + return false; + } + } + return true; +} + +/// Prepare a string for expansion. +/// +/// When expanding file names: The string will be used with expand_wildcards(). +/// Copy "fname[len]" into allocated memory and add a '*' at the end. +/// When expanding other names: The string will be used with regcomp(). Copy +/// the name into allocated memory and prepend "^". +/// +/// @param context EXPAND_FILES etc. +char_u *addstar(char_u *fname, size_t len, int context) + FUNC_ATTR_NONNULL_RET +{ + char_u *retval; + size_t i, j; + size_t new_len; + char_u *tail; + int ends_in_star; + + if (context != EXPAND_FILES + && context != EXPAND_FILES_IN_PATH + && context != EXPAND_SHELLCMD + && context != EXPAND_DIRECTORIES) { + // Matching will be done internally (on something other than files). + // So we convert the file-matching-type wildcards into our kind for + // use with vim_regcomp(). First work out how long it will be: + + // For help tags the translation is done in find_help_tags(). + // For a tag pattern starting with "/" no translation is needed. + if (context == EXPAND_HELP + || context == EXPAND_CHECKHEALTH + || context == EXPAND_COLORS + || context == EXPAND_COMPILER + || context == EXPAND_OWNSYNTAX + || context == EXPAND_FILETYPE + || context == EXPAND_PACKADD + || ((context == EXPAND_TAGS_LISTFILES || context == EXPAND_TAGS) + && fname[0] == '/')) { + retval = vim_strnsave(fname, len); + } else { + new_len = len + 2; // +2 for '^' at start, NUL at end + for (i = 0; i < len; i++) { + if (fname[i] == '*' || fname[i] == '~') { + new_len++; // '*' needs to be replaced by ".*" + // '~' needs to be replaced by "\~" + } + // Buffer names are like file names. "." should be literal + if (context == EXPAND_BUFFERS && fname[i] == '.') { + new_len++; // "." becomes "\." + } + // Custom expansion takes care of special things, match + // backslashes literally (perhaps also for other types?) + if ((context == EXPAND_USER_DEFINED + || context == EXPAND_USER_LIST) && fname[i] == '\\') { + new_len++; // '\' becomes "\\" + } + } + retval = xmalloc(new_len); + { + retval[0] = '^'; + j = 1; + for (i = 0; i < len; i++, j++) { + // Skip backslash. But why? At least keep it for custom + // expansion. + if (context != EXPAND_USER_DEFINED + && context != EXPAND_USER_LIST + && fname[i] == '\\' + && ++i == len) { + break; + } + + switch (fname[i]) { + case '*': + retval[j++] = '.'; + break; + case '~': + retval[j++] = '\\'; + break; + case '?': + retval[j] = '.'; + continue; + case '.': + if (context == EXPAND_BUFFERS) { + retval[j++] = '\\'; + } + break; + case '\\': + if (context == EXPAND_USER_DEFINED + || context == EXPAND_USER_LIST) { + retval[j++] = '\\'; + } + break; + } + retval[j] = fname[i]; + } + retval[j] = NUL; + } + } + } else { + retval = xmalloc(len + 4); + STRLCPY(retval, fname, len + 1); + + // Don't add a star to *, ~, ~user, $var or `cmd`. + // * would become **, which walks the whole tree. + // ~ would be at the start of the file name, but not the tail. + // $ could be anywhere in the tail. + // ` could be anywhere in the file name. + // When the name ends in '$' don't add a star, remove the '$'. + tail = (char_u *)path_tail((char *)retval); + ends_in_star = (len > 0 && retval[len - 1] == '*'); +#ifndef BACKSLASH_IN_FILENAME + for (ssize_t k = (ssize_t)len - 2; k >= 0; k--) { + if (retval[k] != '\\') { + break; + } + ends_in_star = !ends_in_star; + } +#endif + if ((*retval != '~' || tail != retval) + && !ends_in_star + && vim_strchr((char *)tail, '$') == NULL + && vim_strchr((char *)retval, '`') == NULL) { + retval[len++] = '*'; + } else if (len > 0 && retval[len - 1] == '$') { + len--; + } + retval[len] = NUL; + } + return retval; +} + +/// Must parse the command line so far to work out what context we are in. +/// Completion can then be done based on that context. +/// This routine sets the variables: +/// xp->xp_pattern The start of the pattern to be expanded within +/// the command line (ends at the cursor). +/// xp->xp_context The type of thing to expand. Will be one of: +/// +/// EXPAND_UNSUCCESSFUL Used sometimes when there is something illegal on +/// the command line, like an unknown command. Caller +/// should beep. +/// EXPAND_NOTHING Unrecognised context for completion, use char like +/// a normal char, rather than for completion. eg +/// :s/^I/ +/// EXPAND_COMMANDS Cursor is still touching the command, so complete +/// it. +/// EXPAND_BUFFERS Complete file names for :buf and :sbuf commands. +/// EXPAND_FILES After command with EX_XFILE set, or after setting +/// with P_EXPAND set. eg :e ^I, :w>>^I +/// EXPAND_DIRECTORIES In some cases this is used instead of the latter +/// when we know only directories are of interest. eg +/// :set dir=^I +/// EXPAND_SHELLCMD After ":!cmd", ":r !cmd" or ":w !cmd". +/// EXPAND_SETTINGS Complete variable names. eg :set d^I +/// EXPAND_BOOL_SETTINGS Complete boolean variables only, eg :set no^I +/// EXPAND_TAGS Complete tags from the files in p_tags. eg :ta a^I +/// EXPAND_TAGS_LISTFILES As above, but list filenames on ^D, after :tselect +/// EXPAND_HELP Complete tags from the file 'helpfile'/tags +/// EXPAND_EVENTS Complete event names +/// EXPAND_SYNTAX Complete :syntax command arguments +/// EXPAND_HIGHLIGHT Complete highlight (syntax) group names +/// EXPAND_AUGROUP Complete autocommand group names +/// EXPAND_USER_VARS Complete user defined variable names, eg :unlet a^I +/// EXPAND_MAPPINGS Complete mapping and abbreviation names, +/// eg :unmap a^I , :cunab x^I +/// EXPAND_FUNCTIONS Complete internal or user defined function names, +/// eg :call sub^I +/// EXPAND_USER_FUNC Complete user defined function names, eg :delf F^I +/// EXPAND_EXPRESSION Complete internal or user defined function/variable +/// names in expressions, eg :while s^I +/// EXPAND_ENV_VARS Complete environment variable names +/// EXPAND_USER Complete user names +void set_expand_context(expand_T *xp) +{ + CmdlineInfo *const ccline = get_cmdline_info(); + + // only expansion for ':', '>' and '=' command-lines + if (ccline->cmdfirstc != ':' + && ccline->cmdfirstc != '>' && ccline->cmdfirstc != '=' + && !ccline->input_fn) { + xp->xp_context = EXPAND_NOTHING; + return; + } + 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. +/// For user defined commands, the completion context is set in "xp" and the +/// completion flags in "complp". +/// +/// @return a pointer to the text after the command or NULL for failure. +static const char *set_cmd_index(const char *cmd, exarg_T *eap, expand_T *xp, int *complp) +{ + const char *p = NULL; + size_t len = 0; + + // 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 's' command can be followed directly by 'c', 'g', 'i', 'I' or 'r' + if (*cmd == 'k' && cmd[1] != 'e') { + eap->cmdidx = CMD_k; + p = cmd + 1; + } else { + p = cmd; + while (ASCII_ISALPHA(*p) || *p == '*') { // Allow * wild card + p++; + } + // a user command may contain digits + if (ASCII_ISUPPER(cmd[0])) { + while (ASCII_ISALNUM(*p) || *p == '*') { + p++; + } + } + // for python 3.x: ":py3*" commands completion + if (cmd[0] == 'p' && cmd[1] == 'y' && p == cmd + 2 && *p == '3') { + p++; + while (ASCII_ISALPHA(*p) || *p == '*') { + p++; + } + } + // check for non-alpha command + if (p == cmd && vim_strchr("@*!=><&~#", *p) != NULL) { + p++; + } + len = (size_t)(p - cmd); + + if (len == 0) { + xp->xp_context = EXPAND_UNSUCCESSFUL; + return NULL; + } + + eap->cmdidx = excmd_get_cmdidx(cmd, len); + + if (cmd[0] >= 'A' && cmd[0] <= 'Z') { + while (ASCII_ISALNUM(*p) || *p == '*') { // Allow * wild card + p++; + } + } + } + + // If the cursor is touching the command, and it ends in an alphanumeric + // character, complete the command name. + if (*p == NUL && ASCII_ISALNUM(p[-1])) { + return NULL; + } + + if (eap->cmdidx == CMD_SIZE) { + if (*cmd == 's' && vim_strchr("cgriI", cmd[1]) != NULL) { + eap->cmdidx = CMD_substitute; + p = cmd + 1; + } else if (cmd[0] >= 'A' && cmd[0] <= 'Z') { + eap->cmd = (char *)cmd; + p = (const char *)find_ucmd(eap, (char *)p, NULL, xp, complp); + if (p == NULL) { + eap->cmdidx = CMD_SIZE; // Ambiguous user command. + } + } + } + if (eap->cmdidx == CMD_SIZE) { + // Not still touching the command and it was an illegal one + xp->xp_context = EXPAND_UNSUCCESSFUL; + return NULL; + } + + return p; +} + +/// Set the completion context for a command argument with wild card characters. +static void set_context_for_wildcard_arg(exarg_T *eap, const char *arg, bool usefilter, + expand_T *xp, int *complp) +{ + bool in_quote = false; + const char *bow = NULL; // Beginning of word. + size_t len = 0; + + // Allow spaces within back-quotes to count as part of the argument + // being expanded. + xp->xp_pattern = skipwhite(arg); + const char *p = (const char *)xp->xp_pattern; + while (*p != NUL) { + int c = utf_ptr2char(p); + if (c == '\\' && p[1] != NUL) { + p++; + } else if (c == '`') { + if (!in_quote) { + xp->xp_pattern = (char *)p; + bow = p + 1; + } + in_quote = !in_quote; + // An argument can contain just about everything, except + // characters that end the command and white space. + } else if (c == '|' || c == '\n' || c == '"' || ascii_iswhite(c)) { + len = 0; // avoid getting stuck when space is in 'isfname' + while (*p != NUL) { + c = utf_ptr2char(p); + if (c == '`' || vim_isfilec_or_wc(c)) { + break; + } + len = (size_t)utfc_ptr2len(p); + MB_PTR_ADV(p); + } + if (in_quote) { + bow = p; + } else { + xp->xp_pattern = (char *)p; + } + p -= len; + } + MB_PTR_ADV(p); + } + + // If we are still inside the quotes, and we passed a space, just + // expand from there. + if (bow != NULL && in_quote) { + xp->xp_pattern = (char *)bow; + } + xp->xp_context = EXPAND_FILES; + + // For a shell command more chars need to be escaped. + if (usefilter || eap->cmdidx == CMD_bang || eap->cmdidx == CMD_terminal) { +#ifndef BACKSLASH_IN_FILENAME + xp->xp_shell = true; +#endif + // When still after the command name expand executables. + if (xp->xp_pattern == skipwhite(arg)) { + xp->xp_context = EXPAND_SHELLCMD; + } + } + + // Check for environment variable. + if (*xp->xp_pattern == '$') { + for (p = (const char *)xp->xp_pattern + 1; *p != NUL; p++) { + if (!vim_isIDc((uint8_t)(*p))) { + break; + } + } + if (*p == NUL) { + xp->xp_context = EXPAND_ENV_VARS; + xp->xp_pattern++; + // Avoid that the assignment uses EXPAND_FILES again. + if (*complp != EXPAND_USER_DEFINED && *complp != EXPAND_USER_LIST) { + *complp = EXPAND_ENV_VARS; + } + } + } + // Check for user names. + if (*xp->xp_pattern == '~') { + for (p = (const char *)xp->xp_pattern + 1; *p != NUL && *p != '/'; p++) {} + // Complete ~user only if it partially matches a user name. + // A full match ~user<Tab> will be replaced by user's home + // directory i.e. something like ~user<Tab> -> /home/user/ + if (*p == NUL && p > (const char *)xp->xp_pattern + 1 + && match_user((char_u *)xp->xp_pattern + 1) >= 1) { + xp->xp_context = EXPAND_USER; + xp->xp_pattern++; + } + } +} + +/// 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) +{ + const char *p; + + switch (cmdidx) { + case CMD_find: + case CMD_sfind: + case CMD_tabfind: + if (xp->xp_context == EXPAND_FILES) { + xp->xp_context = EXPAND_FILES_IN_PATH; + } + break; + case CMD_cd: + case CMD_chdir: + case CMD_lcd: + case CMD_lchdir: + case CMD_tcd: + case CMD_tchdir: + if (xp->xp_context == EXPAND_FILES) { + xp->xp_context = EXPAND_DIRECTORIES; + } + break; + case CMD_help: + xp->xp_context = EXPAND_HELP; + xp->xp_pattern = (char *)arg; + break; + + // Command modifiers: return the argument. + // Also for commands with an argument that is a command. + case CMD_aboveleft: + case CMD_argdo: + case CMD_belowright: + case CMD_botright: + case CMD_browse: + case CMD_bufdo: + case CMD_cdo: + case CMD_cfdo: + case CMD_confirm: + case CMD_debug: + case CMD_folddoclosed: + case CMD_folddoopen: + case CMD_hide: + case CMD_keepalt: + case CMD_keepjumps: + case CMD_keepmarks: + case CMD_keeppatterns: + case CMD_ldo: + case CMD_leftabove: + case CMD_lfdo: + case CMD_lockmarks: + case CMD_noautocmd: + case CMD_noswapfile: + case CMD_rightbelow: + case CMD_sandbox: + case CMD_silent: + case CMD_tab: + case CMD_tabdo: + case CMD_topleft: + case CMD_verbose: + case CMD_vertical: + case CMD_windo: + 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); + + 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); + + // All completion for the +cmdline_compl feature goes here. + + case CMD_command: + return set_context_in_user_cmd(xp, arg); + + case CMD_delcommand: + xp->xp_context = EXPAND_USER_COMMANDS; + xp->xp_pattern = (char *)arg; + 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_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_isearch: + case CMD_dsearch: + case CMD_ilist: + case CMD_dlist: + case CMD_ijump: + case CMD_psearch: + 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; + case CMD_autocmd: + return (const char *)set_context_in_autocmd(xp, (char *)arg, false); + + case CMD_doautocmd: + 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); + break; + case CMD_setglobal: + set_context_in_set_cmd(xp, (char_u *)arg, OPT_GLOBAL); + break; + case CMD_setlocal: + set_context_in_set_cmd(xp, (char_u *)arg, OPT_LOCAL); + break; + case CMD_tag: + case CMD_stag: + case CMD_ptag: + case CMD_ltag: + case CMD_tselect: + case CMD_stselect: + case CMD_ptselect: + case CMD_tjump: + case CMD_stjump: + case CMD_ptjump: + if (wop_flags & WOP_TAGFILE) { + xp->xp_context = EXPAND_TAGS_LISTFILES; + } else { + xp->xp_context = EXPAND_TAGS; + } + xp->xp_pattern = (char *)arg; + break; + case CMD_augroup: + xp->xp_context = EXPAND_AUGROUP; + xp->xp_pattern = (char *)arg; + break; + case CMD_syntax: + set_context_in_syntax_cmd(xp, arg); + break; + case CMD_const: + case CMD_let: + case CMD_if: + case CMD_elseif: + case CMD_while: + case CMD_for: + case CMD_echo: + case CMD_echon: + case CMD_execute: + case CMD_echomsg: + case CMD_echoerr: + case CMD_call: + case CMD_return: + case CMD_cexpr: + case CMD_caddexpr: + case CMD_cgetexpr: + case CMD_lexpr: + case CMD_laddexpr: + case CMD_lgetexpr: + set_context_for_expression(xp, (char *)arg, cmdidx); + 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; + + case CMD_function: + case CMD_delfunction: + xp->xp_context = EXPAND_USER_FUNC; + xp->xp_pattern = (char *)arg; + break; + + case CMD_echohl: + set_context_in_echohl_cmd(xp, arg); + break; + 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_u *)arg); + break; + case CMD_bdelete: + case CMD_bwipeout: + case CMD_bunload: + while ((xp->xp_pattern = strchr(arg, ' ')) != NULL) { + arg = (const char *)xp->xp_pattern + 1; + } + FALLTHROUGH; + case CMD_buffer: + case CMD_sbuffer: + case CMD_checktime: + xp->xp_context = EXPAND_BUFFERS; + xp->xp_pattern = (char *)arg; + break; + case CMD_diffget: + case CMD_diffput: + // If current buffer is in diff mode, complete buffer names + // which are in diff mode, and different than current buffer. + xp->xp_context = EXPAND_DIFF_BUFFERS; + xp->xp_pattern = (char *)arg; + break; + + 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; + + case CMD_map: + case CMD_noremap: + case CMD_nmap: + case CMD_nnoremap: + case CMD_vmap: + case CMD_vnoremap: + case CMD_omap: + case CMD_onoremap: + case CMD_imap: + case CMD_inoremap: + case CMD_cmap: + case CMD_cnoremap: + case CMD_lmap: + case CMD_lnoremap: + case CMD_smap: + 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, + false, cmdidx); + case CMD_unmap: + case CMD_nunmap: + case CMD_vunmap: + case CMD_ounmap: + case CMD_iunmap: + case CMD_cunmap: + 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, + true, cmdidx); + case CMD_mapclear: + case CMD_nmapclear: + case CMD_vmapclear: + case CMD_omapclear: + case CMD_imapclear: + case CMD_cmapclear: + case CMD_lmapclear: + case CMD_smapclear: + case CMD_xmapclear: + xp->xp_context = EXPAND_MAPCLEAR; + xp->xp_pattern = (char *)arg; + break; + + case CMD_abbreviate: + case CMD_noreabbrev: + case CMD_cabbrev: + 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, + 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, + true, cmdidx); + case CMD_menu: + case CMD_noremenu: + case CMD_unmenu: + case CMD_amenu: + case CMD_anoremenu: + case CMD_aunmenu: + case CMD_nmenu: + case CMD_nnoremenu: + case CMD_nunmenu: + case CMD_vmenu: + case CMD_vnoremenu: + case CMD_vunmenu: + case CMD_omenu: + case CMD_onoremenu: + case CMD_ounmenu: + case CMD_imenu: + case CMD_inoremenu: + case CMD_iunmenu: + case CMD_cmenu: + case CMD_cnoremenu: + case CMD_cunmenu: + case CMD_tlmenu: + case CMD_tlnoremenu: + case CMD_tlunmenu: + case CMD_tmenu: + case CMD_tunmenu: + case CMD_popup: + case CMD_emenu: + return (const char *)set_context_in_menu_cmd(xp, cmd, (char *)arg, forceit); + + case CMD_colorscheme: + xp->xp_context = EXPAND_COLORS; + xp->xp_pattern = (char *)arg; + break; + + case CMD_compiler: + xp->xp_context = EXPAND_COMPILER; + xp->xp_pattern = (char *)arg; + break; + + case CMD_ownsyntax: + xp->xp_context = EXPAND_OWNSYNTAX; + xp->xp_pattern = (char *)arg; + break; + + case CMD_setfiletype: + xp->xp_context = EXPAND_FILETYPE; + xp->xp_pattern = (char *)arg; + break; + + case CMD_packadd: + xp->xp_context = EXPAND_PACKADD; + xp->xp_pattern = (char *)arg; + break; + +#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; +#endif + case CMD_profile: + set_context_in_profile_cmd(xp, arg); + break; + case CMD_checkhealth: + xp->xp_context = EXPAND_CHECKHEALTH; + xp->xp_pattern = (char *)arg; + break; + case CMD_behave: + xp->xp_context = EXPAND_BEHAVE; + xp->xp_pattern = (char *)arg; + break; + + case CMD_messages: + xp->xp_context = EXPAND_MESSAGES; + xp->xp_pattern = (char *)arg; + break; + + case CMD_history: + xp->xp_context = EXPAND_HISTORY; + xp->xp_pattern = (char *)arg; + break; + case CMD_syntime: + xp->xp_context = EXPAND_SYNTIME; + xp->xp_pattern = (char *)arg; + break; + + case CMD_argdelete: + while ((xp->xp_pattern = vim_strchr(arg, ' ')) != NULL) { + arg = (const char *)(xp->xp_pattern + 1); + } + xp->xp_context = EXPAND_ARGLIST; + xp->xp_pattern = (char *)arg; + break; + + case CMD_lua: + xp->xp_context = EXPAND_LUA; + break; + + default: + break; + } + return NULL; +} + +/// This is all pretty much copied from do_one_cmd(), with all the extra stuff +/// we don't need/want deleted. Maybe this could be done better if we didn't +/// repeat all this stuff. The only problem is that they may not stay +/// perfectly compatible with each other, but then the command line syntax +/// probably won't change that much -- webb. +/// +/// @param buff buffer for command string +static const char *set_one_cmd_context(expand_T *xp, const char *buff) +{ + size_t len = 0; + exarg_T ea; + int context = EXPAND_NOTHING; + bool forceit = false; + bool usefilter = false; // Filter instead of file name. + + ExpandInit(xp); + xp->xp_pattern = (char *)buff; + xp->xp_line = (char *)buff; + xp->xp_context = EXPAND_COMMANDS; // Default until we get past command + ea.argt = 0; + + // 1. skip comment lines and leading space, colons or bars + const char *cmd; + for (cmd = buff; vim_strchr(" \t:|", *cmd) != NULL; cmd++) {} + xp->xp_pattern = (char *)cmd; + + if (*cmd == NUL) { + return NULL; + } + if (*cmd == '"') { // ignore comment lines + xp->xp_context = EXPAND_NOTHING; + return NULL; + } + + // 3. skip over a range specifier of the form: addr [,addr] [;addr] .. + cmd = (const char *)skip_range(cmd, &xp->xp_context); + xp->xp_pattern = (char *)cmd; + if (*cmd == NUL) { + return NULL; + } + if (*cmd == '"') { + xp->xp_context = EXPAND_NOTHING; + return NULL; + } + + if (*cmd == '|' || *cmd == '\n') { + return cmd + 1; // There's another command + } + + // Get the command index. + const char *p = set_cmd_index(cmd, &ea, xp, &context); + if (p == NULL) { + return NULL; + } + + xp->xp_context = EXPAND_NOTHING; // Default now that we're past command + + if (*p == '!') { // forced commands + forceit = true; + p++; + } + + // 6. parse arguments + if (!IS_USER_CMDIDX(ea.cmdidx)) { + ea.argt = excmd_get_argt(ea.cmdidx); + } + + const char *arg = (const char *)skipwhite(p); + + // Skip over ++argopt argument + if ((ea.argt & EX_ARGOPT) && *arg != NUL && strncmp(arg, "++", 2) == 0) { + p = arg; + while (*p && !ascii_isspace(*p)) { + MB_PTR_ADV(p); + } + arg = (const char *)skipwhite(p); + } + + if (ea.cmdidx == CMD_write || ea.cmdidx == CMD_update) { + if (*arg == '>') { // append + if (*++arg == '>') { + arg++; + } + arg = (const char *)skipwhite(arg); + } else if (*arg == '!' && ea.cmdidx == CMD_write) { // :w !filter + arg++; + usefilter = true; + } + } + + if (ea.cmdidx == CMD_read) { + usefilter = forceit; // :r! filter if forced + if (*arg == '!') { // :r !filter + arg++; + usefilter = true; + } + } + + if (ea.cmdidx == CMD_lshift || ea.cmdidx == CMD_rshift) { + while (*arg == *cmd) { // allow any number of '>' or '<' + arg++; + } + arg = (const char *)skipwhite(arg); + } + + // Does command allow "+command"? + if ((ea.argt & EX_CMDARG) && !usefilter && *arg == '+') { + // Check if we're in the +command + p = arg + 1; + arg = (const char *)skip_cmd_arg((char *)arg, false); + + // Still touching the command after '+'? + if (*arg == NUL) { + return p; + } + + // Skip space(s) after +command to get to the real argument. + arg = (const char *)skipwhite(arg); + } + + // Check for '|' to separate commands and '"' to start comments. + // Don't do this for ":read !cmd" and ":write !cmd". + if ((ea.argt & EX_TRLBAR) && !usefilter) { + p = arg; + // ":redir @" is not the start of a comment + if (ea.cmdidx == CMD_redir && p[0] == '@' && p[1] == '"') { + p += 2; + } + while (*p) { + if (*p == Ctrl_V) { + if (p[1] != NUL) { + p++; + } + } else if ((*p == '"' && !(ea.argt & EX_NOTRLCOM)) + || *p == '|' + || *p == '\n') { + if (*(p - 1) != '\\') { + if (*p == '|' || *p == '\n') { + return p + 1; + } + return NULL; // It's a comment + } + } + MB_PTR_ADV(p); + } + } + + if (!(ea.argt & EX_EXTRA) && *arg != NUL && strchr("|\"", *arg) == NULL) { + // no arguments allowed but there is something + return NULL; + } + + // Find start of last argument (argument just before cursor): + p = buff; + xp->xp_pattern = (char *)p; + len = strlen(buff); + while (*p && p < buff + len) { + if (*p == ' ' || *p == TAB) { + // argument starts after a space + xp->xp_pattern = (char *)++p; + } else { + if (*p == '\\' && *(p + 1) != NUL) { + p++; // skip over escaped character + } + MB_PTR_ADV(p); + } + } + + if (ea.argt & EX_XFILE) { + set_context_for_wildcard_arg(&ea, arg, usefilter, xp, &context); + } + + // Switch on command name. + return set_context_by_cmdname(cmd, ea.cmdidx, arg, ea.argt, context, xp, forceit); +} + +/// @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) +{ + CmdlineInfo *const ccline = get_cmdline_info(); + char_u old_char = NUL; + + // Avoid a UMR warning from Purify, only save the character if it has been + // written before. + if (col < len) { + old_char = str[col]; + } + str[col] = NUL; + const char *nextcomm = (const char *)str; + + if (use_ccline && ccline->cmdfirstc == '=') { + // pass CMD_SIZE because there is no real command + set_context_for_expression(xp, (char *)str, CMD_SIZE); + } else if (use_ccline && ccline->input_fn) { + xp->xp_context = ccline->xp_context; + xp->xp_pattern = (char *)ccline->cmdbuff; + xp->xp_arg = (char *)ccline->xp_arg; + } else { + while (nextcomm != NULL) { + nextcomm = set_one_cmd_context(xp, nextcomm); + } + } + + // Store the string here so that call_user_expand_func() can get to them + // easily. + xp->xp_line = (char *)str; + xp->xp_col = col; + + str[col] = old_char; +} + +/// Expand the command line "str" from context "xp". +/// "xp" must have been set by set_cmd_context(). +/// xp->xp_pattern points into "str", to where the text that is to be expanded +/// starts. +/// Returns EXPAND_UNSUCCESSFUL when there is something illegal before the +/// cursor. +/// Returns EXPAND_NOTHING when there is nothing to expand, might insert the +/// key that triggered expansion literally. +/// Returns EXPAND_OK otherwise. +/// +/// @param str start of command line +/// @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) +{ + char_u *file_str = NULL; + int options = WILD_ADD_SLASH|WILD_SILENT; + + if (xp->xp_context == EXPAND_UNSUCCESSFUL) { + beep_flush(); + return EXPAND_UNSUCCESSFUL; // Something illegal on command line + } + if (xp->xp_context == EXPAND_NOTHING) { + // Caller can use the character as a normal char instead + return EXPAND_NOTHING; + } + + // 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 = addstar((char_u *)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) { + *matchcount = 0; + *matches = NULL; + } + xfree(file_str); + + return EXPAND_OK; +} + +/// Expand file or directory names. +static int expand_files_and_dirs(expand_T *xp, char_u *pat, char ***file, int *num_file, int flags, + int options) +{ + bool free_pat = false; + + // for ":set path=" and ":set tags=" halve backslashes for escaped space + if (xp->xp_backslash != XP_BS_NONE) { + free_pat = true; + pat = vim_strsave(pat); + for (int i = 0; pat[i]; i++) { + if (pat[i] == '\\') { + if (xp->xp_backslash == XP_BS_THREE + && pat[i + 1] == '\\' + && pat[i + 2] == '\\' + && pat[i + 3] == ' ') { + STRMOVE(pat + i, pat + i + 3); + } + if (xp->xp_backslash == XP_BS_ONE + && pat[i + 1] == ' ') { + STRMOVE(pat + i, pat + i + 1); + } + } + } + } + + if (xp->xp_context == EXPAND_FILES) { + flags |= EW_FILE; + } else if (xp->xp_context == EXPAND_FILES_IN_PATH) { + flags |= (EW_FILE | EW_PATH); + } else { + flags = (flags | EW_DIR) & ~EW_FILE; + } + if (options & WILD_ICASE) { + flags |= EW_ICASE; + } + + // Expand wildcards, supporting %:h and the like. + int ret = expand_wildcards_eval(&pat, num_file, file, 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]; + while (*ptr != NUL) { + if (p_csl[0] == 's' && *ptr == '\\') { + *ptr = '/'; + } else if (p_csl[0] == 'b' && *ptr == '/') { + *ptr = '\\'; + } + ptr += utfc_ptr2len(ptr); + } + } + } +#endif + return ret; +} + +/// Function given to ExpandGeneric() to obtain the possible arguments of the +/// ":behave {mswin,xterm}" command. +static char *get_behave_arg(expand_T *xp FUNC_ATTR_UNUSED, int idx) +{ + if (idx == 0) { + return "mswin"; + } + if (idx == 1) { + return "xterm"; + } + return NULL; +} + +/// 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) +{ + if (idx == 0) { + return "clear"; + } + return NULL; +} + +static char *get_mapclear_arg(expand_T *xp FUNC_ATTR_UNUSED, int idx) +{ + if (idx == 0) { + return "<buffer>"; + } + return NULL; +} + +/// Completion for |:checkhealth| command. +/// +/// Given to ExpandGeneric() to obtain all available heathcheck names. +/// @param[in] idx Index of the healthcheck item. +/// @param[in] xp Not used. +static char *get_healthcheck_names(expand_T *xp FUNC_ATTR_UNUSED, int idx) +{ + static Object names = OBJECT_INIT; + static unsigned last_gen = 0; + + if (last_gen != get_cmdline_last_prompt_id() || last_gen == 0) { + Array a = ARRAY_DICT_INIT; + Error err = ERROR_INIT; + Object res = nlua_exec(STATIC_CSTR_AS_STRING("return vim.health._complete()"), a, &err); + api_clear_error(&err); + api_free_object(names); + names = res; + last_gen = get_cmdline_last_prompt_id(); + } + + if (names.type == kObjectTypeArray && idx < (int)names.data.array.size) { + return names.data.array.items[idx].data.string.data; + } + return NULL; +} + +/// Do the expansion based on xp->xp_context and "rmp". +static int ExpandOther(expand_T *xp, regmatch_T *rmp, int *num_file, char ***file) +{ + typedef CompleteListItemGetter ExpandFunc; + static struct expgen { + int context; + ExpandFunc func; + int ic; + int escaped; + } tab[] = { + { EXPAND_COMMANDS, get_command_name, false, true }, + { EXPAND_BEHAVE, get_behave_arg, true, true }, + { EXPAND_MAPCLEAR, get_mapclear_arg, true, true }, + { EXPAND_MESSAGES, get_messages_arg, true, true }, + { EXPAND_HISTORY, get_history_arg, true, true }, + { EXPAND_USER_COMMANDS, get_user_commands, false, true }, + { EXPAND_USER_ADDR_TYPE, get_user_cmd_addr_type, false, true }, + { EXPAND_USER_CMD_FLAGS, get_user_cmd_flags, false, true }, + { EXPAND_USER_NARGS, get_user_cmd_nargs, false, true }, + { EXPAND_USER_COMPLETE, get_user_cmd_complete, false, true }, + { EXPAND_USER_VARS, get_user_var_name, false, true }, + { EXPAND_FUNCTIONS, get_function_name, false, true }, + { EXPAND_USER_FUNC, get_user_func_name, false, true }, + { EXPAND_EXPRESSION, get_expr_name, false, true }, + { EXPAND_MENUS, get_menu_name, false, true }, + { EXPAND_MENUNAMES, get_menu_names, false, true }, + { EXPAND_SYNTAX, get_syntax_name, true, true }, + { EXPAND_SYNTIME, get_syntime_arg, true, true }, + { 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 + { EXPAND_LANGUAGE, get_lang_arg, true, false }, + { EXPAND_LOCALES, get_locales, true, false }, +#endif + { EXPAND_ENV_VARS, get_env_name, true, true }, + { EXPAND_USER, get_users, true, false }, + { EXPAND_ARGLIST, get_arglist_name, true, false }, + { EXPAND_CHECKHEALTH, get_healthcheck_names, true, false }, + }; + int ret = FAIL; + + // Find a context in the table and call the ExpandGeneric() with the + // right function to do the expansion. + for (int i = 0; i < (int)ARRAY_SIZE(tab); i++) { + if (xp->xp_context == tab[i].context) { + if (tab[i].ic) { + rmp->rm_ic = true; + } + ExpandGeneric(xp, rmp, num_file, file, tab[i].func, tab[i].escaped); + ret = OK; + break; + } + } + + 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) +{ + regmatch_T regmatch; + int ret; + int flags; + + flags = EW_DIR; // include directories + if (options & WILD_LIST_NOTFOUND) { + flags |= EW_NOTFOUND; + } + if (options & WILD_ADD_SLASH) { + flags |= EW_ADDSLASH; + } + if (options & WILD_KEEP_ALL) { + flags |= EW_KEEPALL; + } + if (options & WILD_SILENT) { + flags |= EW_SILENT; + } + if (options & WILD_NOERROR) { + flags |= EW_NOERROR; + } + if (options & WILD_ALLLINKS) { + flags |= EW_ALLLINKS; + } + + if (xp->xp_context == EXPAND_FILES + || xp->xp_context == EXPAND_DIRECTORIES + || xp->xp_context == EXPAND_FILES_IN_PATH) { + return expand_files_and_dirs(xp, pat, file, num_file, flags, options); + } + + *file = NULL; + *num_file = 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); + return OK; + } + return FAIL; + } + + if (xp->xp_context == EXPAND_SHELLCMD) { + *file = NULL; + expand_shellcmd(pat, num_file, file, flags); + return OK; + } + if (xp->xp_context == EXPAND_OLD_SETTING) { + ExpandOldSetting(num_file, file); + return OK; + } + if (xp->xp_context == EXPAND_BUFFERS) { + return ExpandBufnames((char *)pat, num_file, file, options); + } + if (xp->xp_context == EXPAND_DIFF_BUFFERS) { + return ExpandBufnames((char *)pat, num_file, file, 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); + } + if (xp->xp_context == EXPAND_COLORS) { + char *directories[] = { "colors", NULL }; + return ExpandRTDir((char *)pat, DIP_START + DIP_OPT + DIP_LUA, num_file, file, directories); + } + if (xp->xp_context == EXPAND_COMPILER) { + char *directories[] = { "compiler", NULL }; + return ExpandRTDir((char *)pat, DIP_LUA, num_file, file, directories); + } + if (xp->xp_context == EXPAND_OWNSYNTAX) { + char *directories[] = { "syntax", NULL }; + return ExpandRTDir((char *)pat, 0, num_file, file, directories); + } + if (xp->xp_context == EXPAND_FILETYPE) { + char *directories[] = { "syntax", "indent", "ftplugin", NULL }; + return ExpandRTDir((char *)pat, DIP_LUA, num_file, file, directories); + } + if (xp->xp_context == EXPAND_USER_LIST) { + return ExpandUserList(xp, num_file, file); + } + if (xp->xp_context == EXPAND_USER_LUA) { + return ExpandUserLua(xp, num_file, file); + } + if (xp->xp_context == EXPAND_PACKADD) { + return ExpandPackAddDir((char *)pat, num_file, file); + } + + // 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; + + tofree = xmalloc(len); + snprintf(tofree, len, "^<SNR>\\d\\+_%s", pat + 3); + pat = (char_u *)tofree; + } + + if (xp->xp_context == EXPAND_LUA) { + ILOG("PAT %s", pat); + return nlua_expand_pat(xp, pat, num_file, file); + } + + regmatch.regprog = vim_regcomp((char *)pat, p_magic ? 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); + + if (xp->xp_context == EXPAND_SETTINGS + || xp->xp_context == EXPAND_BOOL_SETTINGS) { + ret = ExpandSettings(xp, ®match, num_file, file); + } else if (xp->xp_context == EXPAND_MAPPINGS) { + ret = ExpandMappings(®match, num_file, file); + } else if (xp->xp_context == EXPAND_USER_DEFINED) { + ret = ExpandUserDefined(xp, ®match, num_file, file); + } else { + ret = ExpandOther(xp, ®match, num_file, file); + } + + vim_regfree(regmatch.regprog); + xfree(tofree); + + return ret; +} + +/// Expand a list of names. +/// +/// Generic function for command line completion. It calls a function to +/// obtain strings, one by one. The strings are matched against a regexp +/// 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) +{ + int i; + size_t count = 0; + char_u *str; + + // count the number of matching names + for (i = 0;; i++) { + str = (char_u *)(*func)(xp, i); + if (str == NULL) { // end of list + break; + } + if (*str == NUL) { // skip empty strings + continue; + } + if (vim_regexec(regmatch, (char *)str, (colnr_T)0)) { + count++; + } + } + if (count == 0) { + return; + } + 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 = (char_u *)(*func)(xp, i); + if (str == NULL) { // End of list. + break; + } + if (*str == NUL) { // Skip empty strings. + continue; + } + if (vim_regexec(regmatch, (char *)str, (colnr_T)0)) { + if (escaped) { + str = vim_strsave_escaped(str, (char_u *)" \t\\."); + } else { + str = vim_strsave(str); + } + (*file)[count++] = (char *)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) { + // <SNR> functions should be sorted to the end. + qsort((void *)(*file), (size_t)(*num_file), sizeof(char_u *), + sort_func_compare); + } else { + sort_strings(*file, *num_file); + } + } + + // 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(); +} + +/// 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_u *filepat, int *num_file, char ***file, int flagsarg) + FUNC_ATTR_NONNULL_ALL +{ + char_u *pat; + int i; + char_u *path = NULL; + garray_T ga; + char *buf = xmalloc(MAXPATHL); + size_t l; + char_u *s, *e; + int flags = flagsarg; + int ret; + bool did_curdir = false; + + // for ":set path=" and ":set tags=" halve backslashes for escaped space + pat = vim_strsave(filepat); + for (i = 0; pat[i]; i++) { + if (pat[i] == '\\' && pat[i + 1] == ' ') { + STRMOVE(pat + i, pat + i + 1); + } + } + + flags |= EW_FILE | EW_EXEC | EW_SHELLCMD; + + bool mustfree = false; // Track memory allocation for *path. + if (pat[0] == '.' && (vim_ispathsep(pat[1]) + || (pat[1] == '.' && vim_ispathsep(pat[2])))) { + path = (char_u *)"."; + } else { + // For an absolute name we don't use $PATH. + if (!path_is_absolute(pat)) { + path = (char_u *)vim_getenv("PATH"); + } + if (path == NULL) { + path = (char_u *)""; + } else { + mustfree = true; + } + } + + // 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 + // current directory, to find "subdir/cmd". + ga_init(&ga, (int)sizeof(char *), 10); + hashtab_T found_ht; + hash_init(&found_ht); + for (s = path;; s = e) { + e = (char_u *)vim_strchr((char *)s, ENV_SEPCHAR); + if (e == NULL) { + e = s + STRLEN(s); + } + + if (*s == NUL) { + if (did_curdir) { + break; + } + // Find directories in the current directory, path is empty. + did_curdir = true; + flags |= EW_DIR; + } else if (STRNCMP(s, ".", e - s) == 0) { + did_curdir = true; + flags |= EW_DIR; + } else { + // Do not match directories inside a $PATH item. + flags &= ~EW_DIR; + } + + l = (size_t)(e - s); + 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); + } + } + if (*e != NUL) { + e++; + } + } + *file = ga.ga_data; + *num_file = ga.ga_len; + + xfree(buf); + xfree(pat); + if (mustfree) { + xfree(path); + } + hash_clear(&found_ht); +} + +/// 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) + FUNC_ATTR_NONNULL_ALL +{ + CmdlineInfo *const ccline = get_cmdline_info(); + char_u keep = 0; + typval_T args[4]; + char_u *pat = NULL; + const sctx_T save_current_sctx = current_sctx; + + 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]; + ccline->cmdbuff[ccline->cmdlen] = 0; + } + + pat = vim_strnsave((char_u *)xp->xp_pattern, xp->xp_pattern_len); + args[0].v_type = VAR_STRING; + args[1].v_type = VAR_STRING; + args[2].v_type = VAR_NUMBER; + args[3].v_type = VAR_UNKNOWN; + args[0].vval.v_string = (char *)pat; + args[1].vval.v_string = xp->xp_line; + args[2].vval.v_number = xp->xp_col; + + current_sctx = xp->xp_script_ctx; + + void *const ret = user_expand_func((char_u *)xp->xp_arg, 3, args); + + current_sctx = save_current_sctx; + if (ccline->cmdbuff != NULL) { + ccline->cmdbuff[ccline->cmdlen] = keep; + } + + xfree(pat); + 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) +{ + char_u *e; + garray_T ga; + + char_u *const retstr = call_user_expand_func((user_expand_func_T)call_func_retstr, xp, num_file, + file); + + if (retstr == NULL) { + return FAIL; + } + + ga_init(&ga, (int)sizeof(char *), 3); + for (char_u *s = retstr; *s != NUL; s = e) { + e = (char_u *)vim_strchr((char *)s, '\n'); + if (e == NULL) { + e = s + STRLEN(s); + } + const char_u keep = *e; + *e = NUL; + + const bool skip = xp->xp_pattern[0] + && vim_regexec(regmatch, (char *)s, (colnr_T)0) == 0; + *e = keep; + if (!skip) { + GA_APPEND(char_u *, &ga, vim_strnsave(s, (size_t)(e - s))); + } + + if (*e != NUL) { + e++; + } + } + xfree(retstr); + *file = ga.ga_data; + *num_file = 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) +{ + list_T *const retlist = call_user_expand_func((user_expand_func_T)call_func_retlist, xp, num_file, + file); + if (retlist == NULL) { + return FAIL; + } + + garray_T ga; + ga_init(&ga, (int)sizeof(char *), 3); + // Loop over the items in the list. + TV_LIST_ITER_CONST(retlist, li, { + if (TV_LIST_ITEM_TV(li)->v_type != VAR_STRING + || TV_LIST_ITEM_TV(li)->vval.v_string == NULL) { + continue; // Skip non-string items and empty strings. + } + + GA_APPEND(char *, &ga, xstrdup((const char *)TV_LIST_ITEM_TV(li)->vval.v_string)); + }); + tv_list_unref(retlist); + + *file = ga.ga_data; + *num_file = ga.ga_len; + return OK; +} + +static int ExpandUserLua(expand_T *xp, int *num_file, char ***file) +{ + typval_T rettv; + nlua_call_user_expand_func(xp, &rettv); + if (rettv.v_type != VAR_LIST) { + tv_clear(&rettv); + return FAIL; + } + + list_T *const retlist = rettv.vval.v_list; + + garray_T ga; + ga_init(&ga, (int)sizeof(char *), 3); + // Loop over the items in the list. + TV_LIST_ITER_CONST(retlist, li, { + if (TV_LIST_ITEM_TV(li)->v_type != VAR_STRING + || TV_LIST_ITEM_TV(li)->vval.v_string == NULL) { + continue; // Skip non-string items and empty strings. + } + + GA_APPEND(char *, &ga, xstrdup((const char *)TV_LIST_ITEM_TV(li)->vval.v_string)); + }); + tv_list_unref(retlist); + + *file = ga.ga_data; + *num_file = ga.ga_len; + return OK; +} + +/// Expand `file` for all comma-separated directories in `path`. +/// Adds matches to `ga`. +void globpath(char *path, char *file, garray_T *ga, int expand_options) +{ + expand_T xpc; + ExpandInit(&xpc); + xpc.xp_context = EXPAND_FILES; + + char_u *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); + STRCAT(buf, file); // NOLINT + + char **p; + int num_p = 0; + (void)ExpandFromContext(&xpc, buf, &num_p, &p, + WILD_SILENT | expand_options); + if (num_p > 0) { + ExpandEscape(&xpc, buf, num_p, p, WILD_SILENT | expand_options); + + // Concatenate new results to previous ones. + ga_grow(ga, num_p); + // take over the pointers and put them in "ga" + for (int i = 0; i < num_p; i++) { + ((char_u **)ga->ga_data)[ga->ga_len] = (char_u *)p[i]; + ga->ga_len++; + } + xfree(p); + } + } + } + + xfree(buf); +} + +/// Translate some keys pressed when 'wildmenu' is used. +int wildmenu_translate_key(CmdlineInfo *cclp, int key, expand_T *xp, int did_wild_list) +{ + int c = key; + + if (did_wild_list) { + if (c == K_LEFT) { + c = Ctrl_P; + } else if (c == K_RIGHT) { + c = Ctrl_N; + } + } + + // Hitting CR after "emenu Name.": complete submenu + if (xp->xp_context == EXPAND_MENUNAMES + && cclp->cmdpos > 1 + && cclp->cmdbuff[cclp->cmdpos - 1] == '.' + && cclp->cmdbuff[cclp->cmdpos - 2] != '\\' + && (c == '\n' || c == '\r' || c == K_KENTER)) { + c = K_DOWN; + } + + return c; +} + +/// Delete characters on the command line, from "from" to the current position. +static void cmdline_del(CmdlineInfo *cclp, int from) +{ + assert(cclp->cmdpos <= cclp->cmdlen); + memmove(cclp->cmdbuff + from, cclp->cmdbuff + cclp->cmdpos, + (size_t)cclp->cmdlen - (size_t)cclp->cmdpos + 1); + cclp->cmdlen -= cclp->cmdpos - from; + cclp->cmdpos = from; +} + +/// Handle a key pressed when wild menu is displayed +int wildmenu_process_key(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)((char_u *)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; + } 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 (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)((char_u *)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 + } + } else if (c == K_UP) { + // go up a directory + int found = false; + + int j = cclp->cmdpos - 1; + int i = (int)((char_u *)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 +#endif + ) { + 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); + } + + // Now complete in the new directory. Set KeyTyped in case the + // Up key came from a mapping. + c = (int)p_wc; + KeyTyped = true; + } + } + + return c; +} + +/// Free expanded names when finished walking through the matches +void wildmenu_cleanup(CmdlineInfo *cclp) +{ + if (!p_wmnu || wild_menu_showing == 0) { + return; + } + + const bool skt = KeyTyped; + const int old_RedrawingDisabled = RedrawingDisabled; + + if (cclp->input_fn) { + RedrawingDisabled = 0; + } + + if (wild_menu_showing == WM_SCROLLED) { + // Entered command line, move it up + cmdline_row--; + redrawcmd(); + wild_menu_showing = 0; + } else if (save_p_ls != -1) { + // restore 'laststatus' and 'winminheight' + p_ls = save_p_ls; + p_wmh = save_p_wmh; + last_status(false); + update_screen(UPD_VALID); // redraw the screen NOW + redrawcmd(); + save_p_ls = -1; + wild_menu_showing = 0; + // don't redraw statusline if WM_LIST is showing + } else if (wild_menu_showing != WM_LIST) { + win_redraw_last_status(topframe); + wild_menu_showing = 0; // must be before redraw_statuslines #8385 + redraw_statuslines(); + } else { + wild_menu_showing = 0; + } + KeyTyped = skt; + if (cclp->input_fn) { + RedrawingDisabled = old_RedrawingDisabled; + } +} + +/// "getcompletion()" function +void f_getcompletion(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + char_u *pat; + expand_T xpc; + bool filtered = false; + int options = WILD_SILENT | WILD_USE_NL | WILD_ADD_SLASH + | WILD_NO_BEEP | WILD_HOME_REPLACE; + + if (argvars[1].v_type != VAR_STRING) { + semsg(_(e_invarg2), "type must be a string"); + return; + } + const char *const type = tv_get_string(&argvars[1]); + + if (argvars[2].v_type != VAR_UNKNOWN) { + filtered = (bool)tv_get_number_chk(&argvars[2], NULL); + } + + if (p_wic) { + options |= WILD_ICASE; + } + + // For filtered results, 'wildignore' is used + if (!filtered) { + options |= WILD_KEEP_ALL; + } + + if (argvars[0].v_type != VAR_STRING) { + emsg(_(e_invarg)); + return; + } + const char *pattern = tv_get_string(&argvars[0]); + + if (strcmp(type, "cmdline") == 0) { + set_one_cmd_context(&xpc, pattern); + xpc.xp_pattern_len = STRLEN(xpc.xp_pattern); + xpc.xp_col = (int)STRLEN(pattern); + goto theend; + } + + ExpandInit(&xpc); + xpc.xp_pattern = (char *)pattern; + xpc.xp_pattern_len = STRLEN(xpc.xp_pattern); + xpc.xp_context = cmdcomplete_str_to_type(type); + if (xpc.xp_context == EXPAND_NOTHING) { + semsg(_(e_invarg2), type); + return; + } + + if (xpc.xp_context == EXPAND_MENUS) { + set_context_in_menu_cmd(&xpc, "menu", xpc.xp_pattern, false); + 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, (char_u *)xpc.xp_pattern); + xpc.xp_pattern_len = STRLEN(xpc.xp_pattern); + } + +theend: + pat = addstar((char_u *)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++) { + tv_list_append_string(rettv->vval.v_list, (const char *)xpc.xp_files[i], + -1); + } + xfree(pat); + ExpandCleanup(&xpc); +} diff --git a/src/nvim/cmdexpand.h b/src/nvim/cmdexpand.h new file mode 100644 index 0000000000..93e91af169 --- /dev/null +++ b/src/nvim/cmdexpand.h @@ -0,0 +1,44 @@ +#ifndef NVIM_CMDEXPAND_H +#define NVIM_CMDEXPAND_H + +#include "nvim/eval/typval.h" +#include "nvim/ex_getln.h" +#include "nvim/garray.h" +#include "nvim/types.h" + +// Values for nextwild() and ExpandOne(). See ExpandOne() for meaning. + +enum { + WILD_FREE = 1, + WILD_EXPAND_FREE = 2, + WILD_EXPAND_KEEP = 3, + WILD_NEXT = 4, + WILD_PREV = 5, + WILD_ALL = 6, + WILD_LONGEST = 7, + WILD_ALL_KEEP = 8, + WILD_CANCEL = 9, + WILD_APPLY = 10, +}; + +enum { + WILD_LIST_NOTFOUND = 0x01, + WILD_HOME_REPLACE = 0x02, + WILD_USE_NL = 0x04, + WILD_NO_BEEP = 0x08, + WILD_ADD_SLASH = 0x10, + WILD_KEEP_ALL = 0x20, + WILD_SILENT = 0x40, + WILD_ESCAPE = 0x80, + WILD_ICASE = 0x100, + WILD_ALLLINKS = 0x200, + WILD_IGNORE_COMPLETESLASH = 0x400, + WILD_NOERROR = 0x800, ///< sets EW_NOERROR + WILD_BUFLASTUSED = 0x1000, + BUF_DIFF_FILTER = 0x2000, +}; + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "cmdexpand.h.generated.h" +#endif +#endif // NVIM_CMDEXPAND_H diff --git a/src/nvim/cmdhist.c b/src/nvim/cmdhist.c new file mode 100644 index 0000000000..1426054d0b --- /dev/null +++ b/src/nvim/cmdhist.c @@ -0,0 +1,744 @@ +// 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 + +// cmdhist.c: Functions for the history of the command-line. + +#include "nvim/ascii.h" +#include "nvim/charset.h" +#include "nvim/cmdhist.h" +#include "nvim/ex_cmds.h" +#include "nvim/ex_getln.h" +#include "nvim/regexp.h" +#include "nvim/strings.h" +#include "nvim/ui.h" +#include "nvim/vim.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "cmdhist.c.generated.h" +#endif + +static histentry_T *(history[HIST_COUNT]) = { NULL, NULL, NULL, NULL, NULL }; +static int hisidx[HIST_COUNT] = { -1, -1, -1, -1, -1 }; ///< lastused entry +/// identifying (unique) number of newest history entry +static int hisnum[HIST_COUNT] = { 0, 0, 0, 0, 0 }; +static int hislen = 0; ///< actual length of history tables + +/// Return the length of the history tables +int get_hislen(void) +{ + return hislen; +} + +/// Return a pointer to a specified history table +histentry_T *get_histentry(int hist_type) +{ + return history[hist_type]; +} + +void set_histentry(int hist_type, histentry_T *entry) +{ + history[hist_type] = entry; +} + +int *get_hisidx(int hist_type) +{ + return &hisidx[hist_type]; +} + +int *get_hisnum(int hist_type) +{ + return &hisnum[hist_type]; +} + +/// Translate a history character to the associated type number +HistoryType hist_char2type(const int c) + FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT +{ + switch (c) { + case ':': + return HIST_CMD; + case '=': + return HIST_EXPR; + case '@': + return HIST_INPUT; + case '>': + return HIST_DEBUG; + case NUL: + case '/': + case '?': + return HIST_SEARCH; + default: + return HIST_INVALID; + } + // Silence -Wreturn-type + return 0; +} + +/// Table of history names. +/// These names are used in :history and various hist...() functions. +/// It is sufficient to give the significant prefix of a history name. +static char *(history_names[]) = { + "cmd", + "search", + "expr", + "input", + "debug", + NULL +}; + +/// Function given to ExpandGeneric() to obtain the possible first +/// arguments of the ":history command. +char *get_history_arg(expand_T *xp, int idx) +{ + const char *short_names = ":=@>?/"; + const int short_names_count = (int)STRLEN(short_names); + const int history_name_count = ARRAY_SIZE(history_names) - 1; + + if (idx < short_names_count) { + xp->xp_buf[0] = short_names[idx]; + xp->xp_buf[1] = NUL; + return xp->xp_buf; + } + if (idx < short_names_count + history_name_count) { + return history_names[idx - short_names_count]; + } + if (idx == short_names_count + history_name_count) { + return "all"; + } + return NULL; +} + +/// Initialize command line history. +/// Also used to re-allocate history tables when size changes. +void init_history(void) +{ + assert(p_hi >= 0 && p_hi <= INT_MAX); + int newlen = (int)p_hi; + int oldlen = hislen; + + // 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); + } + } + + // 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)); + } + + hisidx[type] = l3 - 1; + xfree(history[type]); + history[type] = temp; + } + hislen = newlen; + } +} + +static inline void hist_free_entry(histentry_T *hisptr) + FUNC_ATTR_NONNULL_ALL +{ + xfree(hisptr->hisstr); + tv_list_unref(hisptr->additional_elements); + clear_hist_entry(hisptr); +} + +static inline void clear_hist_entry(histentry_T *hisptr) + FUNC_ATTR_NONNULL_ALL +{ + CLEAR_POINTER(hisptr); +} + +/// Check if command line 'str' is already in history. +/// If 'move_to_front' is true, matching entry is moved to end of history. +/// +/// @param move_to_front Move the entry to the front if it exists +static int in_history(int type, char_u *str, int move_to_front, int sep) +{ + int last_i = -1; + + if (hisidx[type] < 0) { + return false; + } + int i = hisidx[type]; + do { + if (history[type][i].hisstr == NULL) { + return false; + } + + // For search history, check that the separator character matches as + // well. + char_u *p = history[type][i].hisstr; + if (STRCMP(str, p) == 0 + && (type != HIST_SEARCH || sep == p[STRLEN(p) + 1])) { + if (!move_to_front) { + return true; + } + last_i = i; + break; + } + if (--i < 0) { + i = hislen - 1; + } + } 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; + } + 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; +} + +/// Convert history name to its HIST_ equivalent +/// +/// Names are taken from the table above. When `name` is empty returns currently +/// active history or HIST_DEFAULT, depending on `return_default` argument. +/// +/// @param[in] name Converted name. +/// @param[in] len Name length. +/// @param[in] return_default Determines whether HIST_DEFAULT should be +/// returned or value based on `ccline.cmdfirstc`. +/// +/// @return Any value from HistoryType enum, including HIST_INVALID. May not +/// return HIST_DEFAULT unless return_default is true. +static HistoryType get_histtype(const char *const name, const size_t len, const bool return_default) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ + // No argument: use current history. + if (len == 0) { + return return_default ? HIST_DEFAULT : hist_char2type(get_cmdline_firstc()); + } + + for (HistoryType i = 0; history_names[i] != NULL; i++) { + if (STRNICMP(name, history_names[i], len) == 0) { + return i; + } + } + + if (vim_strchr(":=@>?/", name[0]) != NULL && len == 1) { + return hist_char2type(name[0]); + } + + return HIST_INVALID; +} + +static int last_maptick = -1; // last seen maptick + +/// Add the given string to the given history. If the string is already in the +/// history then it is moved to the front. +/// +/// @param histype may be one of the HIST_ values. +/// @param in_map consider maptick when inside a mapping +/// @param sep separator character used (search hist) +void add_to_history(int histype, char_u *new_entry, int in_map, int sep) +{ + histentry_T *hisptr; + + if (hislen == 0 || histype == HIST_INVALID) { // no history + return; + } + assert(histype != HIST_DEFAULT); + + if ((cmdmod.cmod_flags & CMOD_KEEPPATTERNS) && histype == HIST_SEARCH) { + return; + } + + // Searches inside the same mapping overwrite each other, so that only + // the last line is kept. Be careful not to remove a line that was moved + // down, only lines that were added. + if (histype == HIST_SEARCH && in_map) { + if (maptick == last_maptick && hisidx[HIST_SEARCH] >= 0) { + // Current line is from the same mapping, remove it + hisptr = &history[HIST_SEARCH][hisidx[HIST_SEARCH]]; + hist_free_entry(hisptr); + hisnum[histype]--; + if (--hisidx[HIST_SEARCH] < 0) { + hisidx[HIST_SEARCH] = hislen - 1; + } + } + 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 = vim_strnsave(new_entry, len + 2); + hisptr->timestamp = os_time(); + hisptr->additional_elements = NULL; + hisptr->hisstr[len + 1] = (char_u)sep; + + hisptr->hisnum = ++hisnum[histype]; + if (histype == HIST_SEARCH && in_map) { + last_maptick = maptick; + } + } +} + +/// Get identifier of newest history entry. +/// +/// @param histype may be one of the HIST_ values. +static int get_history_idx(int histype) +{ + if (hislen == 0 || histype < 0 || histype >= HIST_COUNT + || hisidx[histype] < 0) { + return -1; + } + + return history[histype][hisidx[histype]].hisnum; +} + +/// Calculate history index from a number: +/// +/// @param num > 0: seen as identifying number of a history entry +/// < 0: relative position in history wrt newest entry +/// @param histype may be one of the HIST_ values. +static int calc_hist_idx(int histype, int num) +{ + int i; + int wrapped = false; + + if (hislen == 0 || histype < 0 || histype >= HIST_COUNT + || (i = hisidx[histype]) < 0 || num == 0) { + return -1; + } + + histentry_T *hist = history[histype]; + if (num > 0) { + while (hist[i].hisnum > num) { + if (--i < 0) { + if (wrapped) { + break; + } + i += hislen; + wrapped = true; + } + } + if (i >= 0 && hist[i].hisnum == num && hist[i].hisstr != NULL) { + return i; + } + } else if (-num <= hislen) { + i += num + 1; + if (i < 0) { + i += hislen; + } + if (hist[i].hisstr != NULL) { + return i; + } + } + return -1; +} + +/// Get a history entry by its index. +/// +/// @param histype may be one of the HIST_ values. +static char_u *get_history_entry(int histype, int idx) +{ + idx = calc_hist_idx(histype, idx); + if (idx >= 0) { + return history[histype][idx].hisstr; + } else { + return (char_u *)""; + } +} + +/// Clear all entries in a history +/// +/// @param[in] histype One of the HIST_ values. +/// +/// @return OK if there was something to clean and histype was one of HIST_ +/// values, FAIL otherwise. +int clr_history(const int histype) +{ + if (hislen != 0 && histype >= 0 && histype < HIST_COUNT) { + histentry_T *hisptr = history[histype]; + for (int i = hislen; i--; hisptr++) { + hist_free_entry(hisptr); + } + hisidx[histype] = -1; // mark history as cleared + hisnum[histype] = 0; // reset identifier counter + return OK; + } + return FAIL; +} + +/// 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) +{ + regmatch_T regmatch; + histentry_T *hisptr; + int idx; + int i; + int last; + bool found = 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, (char *)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; + } + } + if (--i < 0) { + i += hislen; + } + } while (i != idx); + if (history[histype][idx].hisstr == NULL) { + hisidx[histype] = -1; + } + } + vim_regfree(regmatch.regprog); + return found; +} + +/// Remove an indexed entry from a history. +/// +/// @param histype may be one of the HIST_ values. +static int del_history_idx(int histype, int idx) +{ + int i = calc_hist_idx(histype, idx); + if (i < 0) { + return false; + } + idx = hisidx[histype]; + hist_free_entry(&history[histype][i]); + + // When deleting the last added search string in a mapping, reset + // last_maptick, so that the last added search string isn't deleted again. + if (histype == HIST_SEARCH && maptick == last_maptick && i == idx) { + last_maptick = -1; + } + + while (i != idx) { + int j = (i + 1) % hislen; + history[histype][i] = history[histype][j]; + i = j; + } + clear_hist_entry(&history[histype][idx]); + if (--i < 0) { + i += hislen; + } + hisidx[histype] = i; + return true; +} + +/// "histadd()" function +void f_histadd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + HistoryType histype; + + rettv->vval.v_number = false; + if (check_secure()) { + return; + } + 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_u *)str, false, NUL); + rettv->vval.v_number = true; + return; + } + } +} + +/// "histdel()" function +void f_histdel(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + int n; + const char *const str = tv_get_string_chk(&argvars[0]); // NULL on type error + if (str == NULL) { + n = 0; + } else if (argvars[1].v_type == VAR_UNKNOWN) { + // only one argument: clear entire history + n = clr_history(get_histtype(str, strlen(str), false)); + } else if (argvars[1].v_type == VAR_NUMBER) { + // index given: remove that entry + n = del_history_idx(get_histtype(str, strlen(str), false), + (int)tv_get_number(&argvars[1])); + } else { + // 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)); + } + rettv->vval.v_number = n; +} + +/// "histget()" function +void f_histget(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + HistoryType type; + int idx; + + const char *const str = tv_get_string_chk(&argvars[0]); // NULL on type error + if (str == NULL) { + rettv->vval.v_string = NULL; + } else { + type = get_histtype(str, strlen(str), false); + if (argvars[1].v_type == VAR_UNKNOWN) { + idx = get_history_idx(type); + } else { + idx = (int)tv_get_number_chk(&argvars[1], NULL); + } + // -1 on type error + rettv->vval.v_string = (char *)vim_strsave(get_history_entry(type, idx)); + } + rettv->v_type = VAR_STRING; +} + +/// "histnr()" function +void f_histnr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + const char *const histname = tv_get_string_chk(&argvars[0]); + HistoryType i = histname == NULL + ? HIST_INVALID + : get_histtype(histname, strlen(histname), false); + if (i != HIST_INVALID) { + i = get_history_idx(i); + } + rettv->vval.v_number = i; +} + +/// :history command - print a history +void ex_history(exarg_T *eap) +{ + histentry_T *hist; + int histype1 = HIST_CMD; + int histype2 = HIST_CMD; + int hisidx1 = 1; + int hisidx2 = -1; + int idx; + int i, j, k; + char *end; + char_u *arg = (char_u *)eap->arg; + + if (hislen == 0) { + msg(_("'history' option is zero")); + return; + } + + if (!(ascii_isdigit(*arg) || *arg == '-' || *arg == ',')) { + end = (char *)arg; + while (ASCII_ISALPHA(*end) + || vim_strchr(":=@>/?", *end) != NULL) { + end++; + } + histype1 = get_histtype((const char *)arg, (size_t)(end - (char *)arg), false); + if (histype1 == HIST_INVALID) { + if (STRNICMP(arg, "all", end - (char *)arg) == 0) { + histype1 = 0; + histype2 = HIST_COUNT - 1; + } else { + semsg(_(e_trailing_arg), arg); + return; + } + } else { + histype2 = histype1; + } + } else { + end = (char *)arg; + } + if (!get_list_range(&end, &hisidx1, &hisidx2) || *end != NUL) { + semsg(_(e_trailing_arg), end); + return; + } + + for (; !got_int && histype1 <= histype2; histype1++) { + STRCPY(IObuff, "\n # "); + assert(history_names[histype1] != NULL); + STRCAT(STRCAT(IObuff, history_names[histype1]), " history"); + msg_puts_title((char *)IObuff); + idx = hisidx[histype1]; + hist = history[histype1]; + j = hisidx1; + k = hisidx2; + if (j < 0) { + j = (-j > hislen) ? 0 : hist[(hislen + j + idx + 1) % hislen].hisnum; + } + if (k < 0) { + k = (-k > hislen) ? 0 : hist[(hislen + k + idx + 1) % hislen].hisnum; + } + if (idx >= 0 && j <= k) { + for (i = idx + 1; !got_int; i++) { + if (i == hislen) { + i = 0; + } + if (hist[i].hisstr != NULL + && hist[i].hisnum >= j && hist[i].hisnum <= k) { + msg_putchar('\n'); + snprintf((char *)IObuff, IOSIZE, "%c%6d ", i == idx ? '>' : ' ', + hist[i].hisnum); + if (vim_strsize((char *)hist[i].hisstr) > Columns - 10) { + trunc_string((char *)hist[i].hisstr, (char *)IObuff + STRLEN(IObuff), + Columns - 10, IOSIZE - (int)STRLEN(IObuff)); + } else { + STRCAT(IObuff, hist[i].hisstr); + } + msg_outtrans((char *)IObuff); + ui_flush(); + } + if (i == idx) { + break; + } + } + } + } +} + +/// Iterate over history items +/// +/// @warning No history-editing functions must be run while iteration is in +/// progress. +/// +/// @param[in] iter Pointer to the last history entry. +/// @param[in] history_type Type of the history (HIST_*). Ignored if iter +/// parameter is not NULL. +/// @param[in] zero If true then zero (but not free) returned items. +/// +/// @warning When using this parameter user is +/// responsible for calling clr_history() +/// itself after iteration is over. If +/// clr_history() is not called behaviour is +/// undefined. No functions that work with +/// history must be called during iteration +/// in this case. +/// @param[out] hist Next history entry. +/// +/// @return Pointer used in next iteration or NULL to indicate that iteration +/// was finished. +const void *hist_iter(const void *const iter, const uint8_t history_type, const bool zero, + histentry_T *const hist) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(4) +{ + *hist = (histentry_T) { + .hisstr = NULL + }; + if (hisidx[history_type] == -1) { + return NULL; + } + histentry_T *const hstart = &(history[history_type][0]); + histentry_T *const hlast = &(history[history_type][hisidx[history_type]]); + const histentry_T *const hend = &(history[history_type][hislen - 1]); + histentry_T *hiter; + if (iter == NULL) { + histentry_T *hfirst = hlast; + do { + hfirst++; + if (hfirst > hend) { + hfirst = hstart; + } + if (hfirst->hisstr != NULL) { + break; + } + } while (hfirst != hlast); + hiter = hfirst; + } else { + hiter = (histentry_T *)iter; + } + if (hiter == NULL) { + return NULL; + } + *hist = *hiter; + if (zero) { + CLEAR_POINTER(hiter); + } + if (hiter == hlast) { + return NULL; + } + hiter++; + return (const void *)((hiter > hend) ? hstart : hiter); +} + +/// Get array of history items +/// +/// @param[in] history_type Type of the history to get array for. +/// @param[out] new_hisidx Location where last index in the new array should +/// be saved. +/// @param[out] new_hisnum Location where last history number in the new +/// history should be saved. +/// +/// @return Pointer to the array or NULL. +histentry_T *hist_get_array(const uint8_t history_type, int **const new_hisidx, + int **const new_hisnum) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL +{ + init_history(); + *new_hisidx = &(hisidx[history_type]); + *new_hisnum = &(hisnum[history_type]); + return history[history_type]; +} diff --git a/src/nvim/cmdhist.h b/src/nvim/cmdhist.h new file mode 100644 index 0000000000..797b79a5f0 --- /dev/null +++ b/src/nvim/cmdhist.h @@ -0,0 +1,33 @@ +#ifndef NVIM_CMDHIST_H +#define NVIM_CMDHIST_H + +#include "nvim/eval/typval.h" +#include "nvim/ex_cmds_defs.h" +#include "nvim/os/time.h" + +/// Present history tables +typedef enum { + HIST_DEFAULT = -2, ///< Default (current) history. + HIST_INVALID = -1, ///< Unknown history. + HIST_CMD = 0, ///< Colon commands. + HIST_SEARCH, ///< Search commands. + HIST_EXPR, ///< Expressions (e.g. from entering = register). + HIST_INPUT, ///< input() lines. + HIST_DEBUG, ///< Debug commands. +} HistoryType; + +/// Number of history tables +#define HIST_COUNT (HIST_DEBUG + 1) + +/// History entry definition +typedef struct hist_entry { + int hisnum; ///< Entry identifier number. + char_u *hisstr; ///< Actual entry, separator char after the NUL. + Timestamp timestamp; ///< Time when entry was added. + list_T *additional_elements; ///< Additional entries from ShaDa file. +} histentry_T; + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "cmdhist.h.generated.h" +#endif +#endif // NVIM_CMDHIST_H diff --git a/src/nvim/context.c b/src/nvim/context.c index db26667009..34692cdf64 100644 --- a/src/nvim/context.c +++ b/src/nvim/context.c @@ -9,6 +9,7 @@ #include "nvim/api/vimscript.h" #include "nvim/context.h" #include "nvim/eval/encode.h" +#include "nvim/eval/userfunc.h" #include "nvim/ex_docmd.h" #include "nvim/option.h" #include "nvim/shada.h" @@ -249,7 +250,7 @@ static inline void ctx_save_funcs(Context *ctx, bool scriptonly) ctx->funcs = (Array)ARRAY_DICT_INIT; Error err = ERROR_INIT; - HASHTAB_ITER(&func_hashtab, hi, { + 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); @@ -344,7 +345,7 @@ Dictionary ctx_to_dict(Context *ctx) PUT(rv, "jumps", ARRAY_OBJ(sbuf_to_array(ctx->jumps))); PUT(rv, "bufs", ARRAY_OBJ(sbuf_to_array(ctx->bufs))); PUT(rv, "gvars", ARRAY_OBJ(sbuf_to_array(ctx->gvars))); - PUT(rv, "funcs", ARRAY_OBJ(copy_array(ctx->funcs))); + PUT(rv, "funcs", ARRAY_OBJ(copy_array(ctx->funcs, NULL))); return rv; } @@ -380,7 +381,7 @@ int ctx_from_dict(Dictionary dict, Context *ctx) ctx->gvars = array_to_sbuf(item.value.data.array); } else if (strequal(item.key.data, "funcs")) { types |= kCtxFuncs; - ctx->funcs = copy_object(item.value).data.array; + ctx->funcs = copy_object(item.value, NULL).data.array; } } diff --git a/src/nvim/cursor.c b/src/nvim/cursor.c index 1446257f7e..ed0488cf76 100644 --- a/src/nvim/cursor.c +++ b/src/nvim/cursor.c @@ -9,6 +9,7 @@ #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/mark.h" @@ -17,7 +18,6 @@ #include "nvim/move.h" #include "nvim/option.h" #include "nvim/plines.h" -#include "nvim/screen.h" #include "nvim/state.h" #include "nvim/vim.h" @@ -137,14 +137,18 @@ static int coladvance2(pos_T *pos, bool addspaces, bool finetune, colnr_T wcol_a } } - char_u *ptr = line; - while (col <= wcol && *ptr != NUL) { + chartabsize_T cts; + 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(curwin, line, ptr, col, &head); - MB_PTR_ADV(ptr); - col += csize; + csize = win_lbr_chartabsize(&cts, &head); + MB_PTR_ADV(cts.cts_ptr); + cts.cts_vcol += csize; } - idx = (int)(ptr - line); + col = cts.cts_vcol; + idx = (int)(cts.cts_ptr - (char *)line); + clear_chartabsize_arg(&cts); + // Handle all the special cases. The virtual_active() check // is needed to ensure that a virtual position off the end of // a line has the correct indexing. The one_more comparison @@ -471,7 +475,7 @@ bool leftcol_changed(void) if (retval) { curwin->w_set_curswant = true; } - redraw_later(curwin, NOT_VALID); + redraw_later(curwin, UPD_NOT_VALID); return retval; } diff --git a/src/nvim/cursor_shape.c b/src/nvim/cursor_shape.c index 9c33b1a806..73ad99876a 100644 --- a/src/nvim/cursor_shape.c +++ b/src/nvim/cursor_shape.c @@ -120,7 +120,7 @@ char *parse_shape_opt(int what) } } // Repeat for all comma separated parts. - char *modep = (char *)p_guicursor; + char *modep = p_guicursor; while (modep != NULL && *modep != NUL) { colonp = vim_strchr(modep, ':'); commap = vim_strchr(modep, ','); diff --git a/src/nvim/debugger.c b/src/nvim/debugger.c index 0eaff06833..36df62b502 100644 --- a/src/nvim/debugger.c +++ b/src/nvim/debugger.c @@ -8,6 +8,7 @@ #include "nvim/ascii.h" #include "nvim/charset.h" #include "nvim/debugger.h" +#include "nvim/drawscreen.h" #include "nvim/eval.h" #include "nvim/ex_docmd.h" #include "nvim/ex_getln.h" @@ -17,7 +18,7 @@ #include "nvim/os/os.h" #include "nvim/pos.h" #include "nvim/regexp.h" -#include "nvim/screen.h" +#include "nvim/runtime.h" #include "nvim/types.h" #include "nvim/vim.h" @@ -46,7 +47,7 @@ struct debuggy { /// Debug mode. Repeatedly get Ex commands, until told to continue normal /// execution. -void do_debug(char_u *cmd) +void do_debug(char *cmd) { int save_msg_scroll = msg_scroll; int save_State = State; @@ -98,14 +99,17 @@ void do_debug(char_u *cmd) xfree(debug_newval); debug_newval = NULL; } - if (sourcing_name != NULL) { - msg(sourcing_name); + char *sname = estack_sfile(ESTACK_NONE); + if (sname != NULL) { + msg(sname); } - if (sourcing_lnum != 0) { - smsg(_("line %" PRId64 ": %s"), (int64_t)sourcing_lnum, cmd); + xfree(sname); + if (SOURCING_LNUM != 0) { + smsg(_("line %" PRId64 ": %s"), (int64_t)SOURCING_LNUM, cmd); } else { smsg(_("cmd: %s"), cmd); } + // Repeat getting a command and executing it. for (;;) { msg_scroll = true; @@ -235,11 +239,11 @@ void do_debug(char_u *cmd) last_cmd = CMD_STEP; break; case CMD_BACKTRACE: - do_showbacktrace(cmd); + do_showbacktrace((char_u *)cmd); continue; case CMD_FRAME: if (*p == NUL) { - do_showbacktrace(cmd); + do_showbacktrace((char_u *)cmd); } else { p = skipwhite(p); do_setdebugtracelevel((char_u *)p); @@ -271,7 +275,7 @@ void do_debug(char_u *cmd) RedrawingDisabled--; no_wait_return--; - redraw_all_later(NOT_VALID); + redraw_all_later(UPD_NOT_VALID); need_wait_return = false; msg_scroll = save_msg_scroll; lines_left = Rows - 1; @@ -287,12 +291,12 @@ void do_debug(char_u *cmd) debug_did_msg = true; } -static int get_maxbacktrace_level(void) +static int get_maxbacktrace_level(char *sname) { int maxbacktrace = 0; - if (sourcing_name != NULL) { - char *p = sourcing_name; + if (sname != NULL) { + char *p = sname; char *q; while ((q = strstr(p, "..")) != NULL) { p = q + 2; @@ -320,20 +324,24 @@ static void do_checkbacktracelevel(void) debug_backtrace_level = 0; msg(_("frame is zero")); } else { - int max = get_maxbacktrace_level(); + char *sname = estack_sfile(ESTACK_NONE); + int max = get_maxbacktrace_level(sname); + if (debug_backtrace_level > max) { debug_backtrace_level = max; smsg(_("frame at highest level: %d"), max); } + xfree(sname); } } static void do_showbacktrace(char_u *cmd) { - if (sourcing_name != NULL) { + char *sname = estack_sfile(ESTACK_NONE); + int max = get_maxbacktrace_level(sname); + if (sname != NULL) { int i = 0; - int max = get_maxbacktrace_level(); - char *cur = sourcing_name; + char *cur = sname; while (!got_int) { char *next = strstr(cur, ".."); if (next != NULL) { @@ -351,9 +359,11 @@ static void do_showbacktrace(char_u *cmd) *next = '.'; cur = next + 2; } + xfree(sname); } - if (sourcing_lnum != 0) { - smsg(_("line %" PRId64 ": %s"), (int64_t)sourcing_lnum, cmd); + + if (SOURCING_LNUM != 0) { + smsg(_("line %" PRId64 ": %s"), (int64_t)SOURCING_LNUM, cmd); } else { smsg(_("cmd: %s"), cmd); } @@ -404,7 +414,7 @@ void dbg_check_breakpoint(exarg_T *eap) debug_breakpoint_name + (*p == NUL ? 0 : 3), (int64_t)debug_breakpoint_lnum); debug_breakpoint_name = NULL; - do_debug((char_u *)eap->cmd); + do_debug(eap->cmd); } else { debug_skipped = true; debug_skipped_name = debug_breakpoint_name; @@ -412,7 +422,7 @@ void dbg_check_breakpoint(exarg_T *eap) } } else if (ex_nesting_level <= debug_break_level) { if (!eap->skip) { - do_debug((char_u *)eap->cmd); + do_debug(eap->cmd); } else { debug_skipped = true; debug_skipped_name = NULL; @@ -693,7 +703,7 @@ void ex_breaklist(exarg_T *eap) 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, + bp->dbg_type == DBG_FUNC ? bp->dbg_name : (char_u *)NameBuff, (int64_t)bp->dbg_lnum); } else { smsg(_("%3d expr %s"), bp->dbg_nr, bp->dbg_name); @@ -708,9 +718,9 @@ void ex_breaklist(exarg_T *eap) /// @param file true for a file, false for a function /// @param fname file or function name /// @param after after this line number -linenr_T dbg_find_breakpoint(bool file, char_u *fname, linenr_T after) +linenr_T dbg_find_breakpoint(bool file, char *fname, linenr_T after) { - return debuggy_find(file, fname, after, &dbg_breakp, NULL); + return debuggy_find(file, (char_u *)fname, after, &dbg_breakp, NULL); } /// @param file true for a file, false for a function @@ -718,9 +728,9 @@ linenr_T dbg_find_breakpoint(bool file, char_u *fname, linenr_T after) /// @param fp[out] forceit /// /// @returns true if profiling is on for a function or sourced file. -bool has_profiling(bool file, char_u *fname, bool *fp) +bool has_profiling(bool file, char *fname, bool *fp) { - return debuggy_find(file, fname, (linenr_T)0, &prof_ga, fp) + return debuggy_find(file, (char_u *)fname, (linenr_T)0, &prof_ga, fp) != (linenr_T)0; } @@ -815,9 +825,9 @@ static linenr_T debuggy_find(bool file, char_u *fname, linenr_T after, garray_T } /// Called when a breakpoint was encountered. -void dbg_breakpoint(char_u *name, linenr_T lnum) +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 = name; + debug_breakpoint_name = (char_u *)name; debug_breakpoint_lnum = lnum; } diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index e7c76fe38e..9f3e5a8638 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -4,12 +4,12 @@ #include "nvim/api/ui.h" #include "nvim/buffer.h" #include "nvim/decoration.h" +#include "nvim/drawscreen.h" #include "nvim/extmark.h" #include "nvim/highlight.h" #include "nvim/highlight_group.h" #include "nvim/lua/executor.h" #include "nvim/move.h" -#include "nvim/screen.h" #include "nvim/vim.h" #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -358,7 +358,8 @@ next_mark: return attr; } -void decor_redraw_signs(buf_T *buf, int row, int *num_signs, sign_attrs_T sattrs[]) +void decor_redraw_signs(buf_T *buf, int row, int *num_signs, SignTextAttrs sattrs[], + HlPriAttr *num_attrs, HlPriAttr *line_attrs, HlPriAttr *cul_attrs) { if (!buf->b_signs) { return; @@ -383,30 +384,37 @@ void decor_redraw_signs(buf_T *buf, int row, int *num_signs, sign_attrs_T sattrs goto next_mark; } - int j; - for (j = (*num_signs); j > 0; j--) { - if (sattrs[j].sat_prio <= decor->priority) { - break; - } - sattrs[j] = sattrs[j - 1]; - } - if (j < SIGN_SHOW_MAX) { - memset(&sattrs[j], 0, sizeof(sign_attrs_T)); - sattrs[j].sat_text = decor->sign_text; - if (decor->sign_hl_id != 0) { - sattrs[j].sat_texthl = syn_id2attr(decor->sign_hl_id); - } - if (decor->number_hl_id != 0) { - sattrs[j].sat_numhl = syn_id2attr(decor->number_hl_id); + if (decor->sign_text) { + int j; + for (j = (*num_signs); j > 0; j--) { + if (sattrs[j - 1].priority >= decor->priority) { + break; + } + sattrs[j] = sattrs[j - 1]; } - if (decor->line_hl_id != 0) { - sattrs[j].sat_linehl = syn_id2attr(decor->line_hl_id); + if (j < SIGN_SHOW_MAX) { + sattrs[j] = (SignTextAttrs) { + .text = decor->sign_text, + .hl_attr_id = decor->sign_hl_id == 0 ? 0 : syn_id2attr(decor->sign_hl_id), + .priority = decor->priority + }; + (*num_signs)++; } - if (decor->cursorline_hl_id != 0) { - sattrs[j].sat_culhl = syn_id2attr(decor->cursorline_hl_id); + } + + struct { HlPriAttr *dest; int hl; } cattrs[] = { + { line_attrs, decor->line_hl_id }, + { num_attrs, decor->number_hl_id }, + { cul_attrs, decor->cursorline_hl_id }, + { NULL, -1 }, + }; + for (int i = 0; cattrs[i].dest; i++) { + if (cattrs[i].hl != 0 && decor->priority >= cattrs[i].dest->priority) { + *cattrs[i].dest = (HlPriAttr) { + .attr_id = syn_id2attr(cattrs[i].hl), + .priority = decor->priority + }; } - sattrs[j].sat_prio = decor->priority; - (*num_signs)++; } next_mark: @@ -539,7 +547,7 @@ int decor_virt_lines(win_T *wp, linenr_T lnum, VirtLines *lines) } int virt_lines = 0; - int row = (int)MAX(lnum - 2, 0); + int row = MAX(lnum - 2, 0); int end_row = (int)lnum; MarkTreeIter itr[1] = { 0 }; marktree_itr_get(buf->b_marktree, row, 0, itr); @@ -550,7 +558,7 @@ int decor_virt_lines(win_T *wp, linenr_T lnum, VirtLines *lines) } else if (marktree_decor_level(mark) < kDecorLevelVirtLine) { goto next_mark; } - bool above = mark.pos.row > (int)(lnum - 2); + bool above = mark.pos.row > (lnum - 2); Decoration *decor = mark.decor_full; if (decor && decor->virt_lines_above == above) { virt_lines += (int)kv_size(decor->virt_lines); diff --git a/src/nvim/decoration_provider.c b/src/nvim/decoration_provider.c index 04d875c4e3..14c1238fa4 100644 --- a/src/nvim/decoration_provider.c +++ b/src/nvim/decoration_provider.c @@ -14,7 +14,7 @@ static kvec_t(DecorProvider) decor_providers = KV_INITIAL_VALUE; #define DECORATION_PROVIDER_INIT(ns_id) (DecorProvider) \ { ns_id, false, LUA_NOREF, LUA_NOREF, \ LUA_NOREF, LUA_NOREF, LUA_NOREF, \ - LUA_NOREF, -1 } + LUA_NOREF, -1, false } static bool decor_provider_invoke(NS ns_id, const char *name, LuaRef ref, Array args, bool default_true, char **perr) @@ -107,8 +107,6 @@ void decor_providers_invoke_win(win_T *wp, DecorProviders *providers, } } } - - win_check_ns_hl(wp); } /// For each provider invoke the 'line' callback for a given window row. @@ -135,7 +133,7 @@ void providers_invoke_line(win_T *wp, DecorProviders *providers, int row, bool * kv_A(*providers, k) = NULL; } - win_check_ns_hl(wp); + hl_check_ns(); } } } @@ -176,6 +174,7 @@ void decor_providers_invoke_end(DecorProviders *providers, char **err) DecorProvider *get_decor_provider(NS ns_id, bool force) { + assert(ns_id > 0); size_t i; size_t len = kv_size(decor_providers); for (i = 0; i < len; i++) { diff --git a/src/nvim/decoration_provider.h b/src/nvim/decoration_provider.h index 3ec7c80357..dd1ed6c581 100644 --- a/src/nvim/decoration_provider.h +++ b/src/nvim/decoration_provider.h @@ -13,6 +13,7 @@ typedef struct { LuaRef redraw_end; LuaRef hl_def; int hl_valid; + bool hl_cached; } DecorProvider; typedef kvec_withinit_t(DecorProvider *, 4) DecorProviders; diff --git a/src/nvim/diff.c b/src/nvim/diff.c index 849204f789..1cfb535fe8 100644 --- a/src/nvim/diff.c +++ b/src/nvim/diff.c @@ -14,11 +14,13 @@ #include <stdbool.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/diff.h" +#include "nvim/drawscreen.h" #include "nvim/eval.h" #include "nvim/ex_cmds.h" #include "nvim/ex_docmd.h" @@ -32,10 +34,10 @@ #include "nvim/move.h" #include "nvim/normal.h" #include "nvim/option.h" +#include "nvim/optionstr.h" #include "nvim/os/os.h" #include "nvim/os/shell.h" #include "nvim/path.h" -#include "nvim/screen.h" #include "nvim/strings.h" #include "nvim/ui.h" #include "nvim/undo.h" @@ -118,7 +120,7 @@ void diff_buf_delete(buf_T *buf) // don't redraw right away, more might change or buffer state // is invalid right now need_diff_redraw = true; - redraw_later(curwin, VALID); + redraw_later(curwin, UPD_VALID); } } } @@ -571,9 +573,9 @@ static void diff_check_unchanged(tabpage_T *tp, diff_T *dp) if (dir == BACKWARD) { off_org = dp->df_count[i_org] - 1; } - char_u *line_org = vim_strsave(ml_get_buf(tp->tp_diffbuf[i_org], - dp->df_lnum[i_org] + off_org, - false)); + char *line_org = (char *)vim_strsave(ml_get_buf(tp->tp_diffbuf[i_org], + dp->df_lnum[i_org] + off_org, + false)); int i_new; for (i_new = i_org + 1; i_new < DB_COUNT; i_new++) { @@ -590,9 +592,9 @@ static void diff_check_unchanged(tabpage_T *tp, diff_T *dp) break; } - if (diff_cmp(line_org, ml_get_buf(tp->tp_diffbuf[i_new], - dp->df_lnum[i_new] + off_new, - false)) != 0) { + if (diff_cmp((char_u *)line_org, ml_get_buf(tp->tp_diffbuf[i_new], + dp->df_lnum[i_new] + off_new, + false)) != 0) { break; } } @@ -657,7 +659,7 @@ void diff_redraw(bool dofold) continue; } - redraw_later(wp, SOME_VALID); + redraw_later(wp, UPD_SOME_VALID); if (wp != curwin) { wp_other = wp; } @@ -795,8 +797,8 @@ static int diff_write(buf_T *buf, diffin_T *din) } // Always use 'fileformat' set to "unix". - char_u *save_ff = buf->b_p_ff; - buf->b_p_ff = vim_strsave((char_u *)FF_UNIX); + char *save_ff = buf->b_p_ff; + buf->b_p_ff = xstrdup(FF_UNIX); const bool save_cmod_flags = cmdmod.cmod_flags; // Writing the buffer is an implementation detail of performing the diff, // so it shouldn't update the '[ and '] marks. @@ -956,13 +958,13 @@ void ex_diffupdate(exarg_T *eap) // Only use the internal method if it did not fail for one of the buffers. diffio_T diffio; - memset(&diffio, 0, sizeof(diffio)); + CLEAR_FIELD(diffio); diffio.dio_internal = diff_internal() && !diff_internal_failed(); diff_try_update(&diffio, idx_orig, eap); if (diffio.dio_internal && diff_internal_failed()) { // Internal diff failed, use external diff instead. - memset(&diffio, 0, sizeof(diffio)); + CLEAR_FIELD(diffio); diff_try_update(&diffio, idx_orig, eap); } @@ -1075,9 +1077,9 @@ static int diff_file_internal(diffio_T *diffio) xdemitconf_t emit_cfg; xdemitcb_t emit_cb; - memset(¶m, 0, sizeof(param)); - memset(&emit_cfg, 0, sizeof(emit_cfg)); - memset(&emit_cb, 0, sizeof(emit_cb)); + CLEAR_FIELD(param); + CLEAR_FIELD(emit_cfg); + CLEAR_FIELD(emit_cb); param.flags = (unsigned long)diff_algorithm; @@ -1146,7 +1148,7 @@ static int diff_file(diffio_T *dio) (diff_flags & DIFF_IBLANK) ? "-B " : "", (diff_flags & DIFF_ICASE) ? "-i " : "", tmp_orig, tmp_new); - append_redir(cmd, len, (char *)p_srr, tmp_diff); + append_redir(cmd, len, p_srr, tmp_diff); block_autocmds(); // Avoid ShellCmdPost stuff (void)call_shell((char_u *)cmd, kShellOptFilter | kShellOptSilent | kShellOptDoOut, @@ -1372,7 +1374,7 @@ static void set_diff_option(win_T *wp, int value) curwin = wp; curbuf = curwin->w_buffer; curbuf->b_ro_locked++; - set_option_value("diff", (long)value, NULL, OPT_LOCAL); + set_option_value_give_err("diff", (long)value, NULL, OPT_LOCAL); curbuf->b_ro_locked--; curwin = old_curwin; curbuf = curwin->w_buffer; @@ -1406,18 +1408,14 @@ void diff_win_options(win_T *wp, int addbuf) } wp->w_p_wrap = false; } - curwin = wp; // -V519 - curbuf = curwin->w_buffer; if (!wp->w_p_diff) { if (wp->w_p_diff_saved) { free_string_option(wp->w_p_fdm_save); } - wp->w_p_fdm_save = vim_strsave(wp->w_p_fdm); + wp->w_p_fdm_save = xstrdup(wp->w_p_fdm); } - set_string_option_direct("fdm", -1, "diff", OPT_LOCAL | OPT_FREE, 0); - curwin = old_curwin; - curbuf = curwin->w_buffer; + set_string_option_direct_in_win(wp, "fdm", -1, "diff", OPT_LOCAL | OPT_FREE, 0); if (!wp->w_p_diff) { wp->w_p_fen_save = wp->w_p_fen; @@ -1426,12 +1424,12 @@ void diff_win_options(win_T *wp, int addbuf) if (wp->w_p_diff_saved) { free_string_option(wp->w_p_fdc_save); } - wp->w_p_fdc_save = vim_strsave(wp->w_p_fdc); + wp->w_p_fdc_save = xstrdup(wp->w_p_fdc); } free_string_option(wp->w_p_fdc); - wp->w_p_fdc = (char_u *)xstrdup("2"); + wp->w_p_fdc = xstrdup("2"); assert(diff_foldcolumn >= 0 && diff_foldcolumn <= 9); - snprintf((char *)wp->w_p_fdc, STRLEN(wp->w_p_fdc) + 1, "%d", diff_foldcolumn); + snprintf(wp->w_p_fdc, STRLEN(wp->w_p_fdc) + 1, "%d", diff_foldcolumn); wp->w_p_fen = true; wp->w_p_fdl = 0; foldUpdateAll(wp); @@ -1450,7 +1448,7 @@ void diff_win_options(win_T *wp, int addbuf) if (addbuf) { diff_buf_add(wp->w_buffer); } - redraw_later(wp, NOT_VALID); + redraw_later(wp, UPD_NOT_VALID); } /// Set options not to show diffs. For the current window or all windows. @@ -1482,11 +1480,9 @@ void ex_diffoff(exarg_T *eap) } } free_string_option(wp->w_p_fdm); - wp->w_p_fdm = vim_strsave(*wp->w_p_fdm_save - ? wp->w_p_fdm_save - : (char_u *)"manual"); + 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 = vim_strsave(wp->w_p_fdc_save); + wp->w_p_fdc = xstrdup(wp->w_p_fdc_save); if (wp->w_p_fdl == 0) { wp->w_p_fdl = wp->w_p_fdl_save; @@ -1543,8 +1539,8 @@ static void diff_read(int idx_orig, int idx_new, diffio_T *dio) diff_T *dp = curtab->tp_first_diff; diff_T *dn, *dpl; diffout_T *dout = &dio->dio_diff; - char_u linebuf[LBUFLEN]; // only need to hold the diff line - char_u *line; + 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 @@ -1580,9 +1576,9 @@ static void diff_read(int idx_orig, int idx_new, diffio_T *dio) if (line_idx >= dout->dout_ga.ga_len) { break; // did last line } - line = ((char_u **)dout->dout_ga.ga_data)[line_idx++]; + line = ((char **)dout->dout_ga.ga_data)[line_idx++]; } else { - if (vim_fgets(linebuf, LBUFLEN, fd)) { + if (vim_fgets((char_u *)linebuf, LBUFLEN, fd)) { break; // end of file } line = linebuf; @@ -1604,9 +1600,9 @@ static void diff_read(int idx_orig, int idx_new, diffio_T *dio) } else if ((STRNCMP(line, "@@ ", 3) == 0)) { diffstyle = DIFF_UNIFIED; } else if ((STRNCMP(line, "--- ", 4) == 0) // -V501 - && (vim_fgets(linebuf, LBUFLEN, fd) == 0) // -V501 + && (vim_fgets((char_u *)linebuf, LBUFLEN, fd) == 0) // -V501 && (STRNCMP(line, "+++ ", 4) == 0) - && (vim_fgets(linebuf, LBUFLEN, fd) == 0) // -V501 + && (vim_fgets((char_u *)linebuf, LBUFLEN, fd) == 0) // -V501 && (STRNCMP(line, "@@ ", 3) == 0)) { diffstyle = DIFF_UNIFIED; } else { @@ -1620,7 +1616,7 @@ static void diff_read(int idx_orig, int idx_new, diffio_T *dio) if (!isdigit(*line)) { continue; // not the start of a diff block } - if (parse_diff_ed(line, hunk) == FAIL) { + if (parse_diff_ed((char_u *)line, hunk) == FAIL) { continue; } } else { @@ -1628,7 +1624,7 @@ static void diff_read(int idx_orig, int idx_new, diffio_T *dio) if (STRNCMP(line, "@@ ", 3) != 0) { continue; // not the start of a diff block } - if (parse_diff_unified(line, hunk) == FAIL) { + if (parse_diff_unified((char_u *)line, hunk) == FAIL) { continue; } } @@ -1924,11 +1920,11 @@ static bool diff_equal_entry(diff_T *dp, int idx1, int idx2) } for (int i = 0; i < dp->df_count[idx1]; i++) { - char_u *line = vim_strsave(ml_get_buf(curtab->tp_diffbuf[idx1], - dp->df_lnum[idx1] + i, false)); + char *line = (char *)vim_strsave(ml_get_buf(curtab->tp_diffbuf[idx1], + dp->df_lnum[idx1] + i, false)); - int cmp = diff_cmp(line, ml_get_buf(curtab->tp_diffbuf[idx2], - dp->df_lnum[idx2] + i, false)); + int cmp = diff_cmp((char_u *)line, ml_get_buf(curtab->tp_diffbuf[idx2], + dp->df_lnum[idx2] + i, false)); xfree(line); if (cmp != 0) { @@ -2143,14 +2139,14 @@ int diffopt_changed(void) long diff_algorithm_new = 0; long diff_indent_heuristic = 0; - char_u *p = p_dip; + char *p = p_dip; while (*p != NUL) { if (STRNCMP(p, "filler", 6) == 0) { p += 6; diff_flags_new |= DIFF_FILLER; } else if ((STRNCMP(p, "context:", 8) == 0) && ascii_isdigit(p[8])) { p += 8; - diff_context_new = getdigits_int((char **)&p, false, diff_context_new); + diff_context_new = getdigits_int(&p, false, diff_context_new); } else if (STRNCMP(p, "iblank", 6) == 0) { p += 6; diff_flags_new |= DIFF_IBLANK; @@ -2174,7 +2170,7 @@ int diffopt_changed(void) diff_flags_new |= DIFF_VERTICAL; } else if ((STRNCMP(p, "foldcolumn:", 11) == 0) && ascii_isdigit(p[11])) { p += 11; - diff_foldcolumn_new = getdigits_int((char **)&p, false, diff_foldcolumn_new); + diff_foldcolumn_new = getdigits_int(&p, false, diff_foldcolumn_new); } else if (STRNCMP(p, "hiddenoff", 9) == 0) { p += 9; diff_flags_new |= DIFF_HIDDEN_OFF; @@ -2285,7 +2281,7 @@ 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_u *line_new; + char *line_new; int si_org; int si_new; int ei_org; @@ -2294,7 +2290,7 @@ bool diff_find_change(win_T *wp, linenr_T lnum, int *startp, int *endp) int l; // Make a copy of the line, the next ml_get() will invalidate it. - char_u *line_org = vim_strsave(ml_get_buf(wp->w_buffer, lnum, false)); + char *line_org = (char *)vim_strsave(ml_get_buf(wp->w_buffer, lnum, false)); int idx = diff_buf_idx(wp->w_buffer); if (idx == DB_COUNT) { @@ -2326,8 +2322,8 @@ 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); + line_new = (char *)ml_get_buf(curtab->tp_diffbuf[i], + dp->df_lnum[i] + off, false); // Search for start of difference si_org = si_new = 0; @@ -2339,10 +2335,10 @@ bool diff_find_change(win_T *wp, linenr_T lnum, int *startp, int *endp) || ((diff_flags & DIFF_IWHITEALL) && (ascii_iswhite(line_org[si_org]) || ascii_iswhite(line_new[si_new])))) { - si_org = (int)((char_u *)skipwhite((char *)line_org + si_org) - line_org); - si_new = (int)((char_u *)skipwhite((char *)line_new + si_new) - line_new); + 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(line_org + si_org, line_new + si_new, &l)) { + if (!diff_equal_char((char_u *)line_org + si_org, (char_u *)line_new + si_new, &l)) { break; } si_org += l; @@ -2352,8 +2348,8 @@ bool diff_find_change(win_T *wp, linenr_T lnum, int *startp, int *endp) // Move back to first byte of character in both lines (may // have "nn^" in line_org and "n^ in line_new). - si_org -= utf_head_off(line_org, line_org + si_org); - si_new -= utf_head_off(line_new, line_new + si_new); + si_org -= utf_head_off((char_u *)line_org, (char_u *)line_org + si_org); + si_new -= utf_head_off((char_u *)line_new, (char_u *)line_new + si_new); if (*startp > si_org) { *startp = si_org; @@ -2382,11 +2378,11 @@ bool diff_find_change(win_T *wp, linenr_T lnum, int *startp, int *endp) ei_new--; } } else { - const char_u *p1 = line_org + ei_org; - const char_u *p2 = line_new + ei_new; + const char_u *p1 = (char_u *)line_org + ei_org; + const char_u *p2 = (char_u *)line_new + ei_new; - p1 -= utf_head_off(line_org, p1); - p2 -= utf_head_off(line_new, p2); + p1 -= utf_head_off((char_u *)line_org, p1); + p2 -= utf_head_off((char_u *)line_new, p2); if (!diff_equal_char(p1, p2, &l)) { break; @@ -2515,7 +2511,7 @@ void ex_diffgetput(exarg_T *eap) diff_T *dfree; int i; int added; - char_u *p; + char *p; aco_save_T aco; buf_T *buf; linenr_T start_skip; @@ -2569,18 +2565,18 @@ void ex_diffgetput(exarg_T *eap) } } else { // Buffer number or pattern given. Ignore trailing white space. - p = (char_u *)eap->arg + STRLEN(eap->arg); - while (p > (char_u *)eap->arg && ascii_iswhite(p[-1])) { + p = eap->arg + STRLEN(eap->arg); + while (p > eap->arg && ascii_iswhite(p[-1])) { p--; } - for (i = 0; ascii_isdigit(eap->arg[i]) && (char_u *)eap->arg + i < p; i++) {} + for (i = 0; ascii_isdigit(eap->arg[i]) && eap->arg + i < p; i++) {} - if ((char_u *)eap->arg + i == p) { + if (eap->arg + i == p) { // digits only i = (int)atol(eap->arg); } else { - i = buflist_findpat(eap->arg, (char *)p, false, true, false); + i = buflist_findpat(eap->arg, p, false, true, false); if (i < 0) { // error message already given @@ -2722,8 +2718,8 @@ void ex_diffgetput(exarg_T *eap) if (nr > curtab->tp_diffbuf[idx_from]->b_ml.ml_line_count) { break; } - p = vim_strsave(ml_get_buf(curtab->tp_diffbuf[idx_from], nr, false)); - ml_append(lnum + i - 1, (char *)p, 0, false); + p = (char *)vim_strsave(ml_get_buf(curtab->tp_diffbuf[idx_from], nr, false)); + ml_append(lnum + i - 1, p, 0, false); xfree(p); added++; if (buf_empty && (curbuf->b_ml.ml_line_count == 2)) { diff --git a/src/nvim/digraph.c b/src/nvim/digraph.c index 733b3d3d5d..e4528f9038 100644 --- a/src/nvim/digraph.c +++ b/src/nvim/digraph.c @@ -12,8 +12,8 @@ #include "nvim/ascii.h" #include "nvim/charset.h" #include "nvim/digraph.h" +#include "nvim/drawscreen.h" #include "nvim/eval/typval.h" -#include "nvim/ex_cmds2.h" #include "nvim/ex_docmd.h" #include "nvim/ex_getln.h" #include "nvim/garray.h" @@ -24,7 +24,7 @@ #include "nvim/message.h" #include "nvim/normal.h" #include "nvim/os/input.h" -#include "nvim/screen.h" +#include "nvim/runtime.h" #include "nvim/strings.h" #include "nvim/vim.h" @@ -1695,7 +1695,7 @@ static void digraph_header(const char *msg) if (msg_col > 0) { msg_putchar('\n'); } - msg_outtrans_attr((const char_u *)msg, HL_ATTR(HLF_CM)); + msg_outtrans_attr(msg, HL_ATTR(HLF_CM)); msg_putchar('\n'); } @@ -1856,7 +1856,7 @@ static void printdigraph(const digr_T *dp, result_T *previous) p += utf_char2bytes(dp->result, (char *)p); *p = NUL; - msg_outtrans_attr(buf, HL_ATTR(HLF_8)); + msg_outtrans_attr((char *)buf, HL_ATTR(HLF_8)); p = buf; if (char2cells(dp->result) == 1) { *p++ = ' '; @@ -1917,7 +1917,7 @@ static bool digraph_set_common(const typval_T *argchars, const typval_T *argdigr } /// "digraph_get()" function -void f_digraph_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_digraph_get(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_STRING; rettv->vval.v_string = NULL; // Return empty string for failure @@ -1938,7 +1938,7 @@ void f_digraph_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "digraph_getlist()" function -void f_digraph_getlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_digraph_getlist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { bool flag_list_all; @@ -1957,7 +1957,7 @@ void f_digraph_getlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "digraph_set()" function -void f_digraph_set(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_digraph_set(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_BOOL; rettv->vval.v_bool = kBoolVarFalse; @@ -1970,7 +1970,7 @@ void f_digraph_set(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "digraph_setlist()" function -void f_digraph_setlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_digraph_setlist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_BOOL; rettv->vval.v_bool = kBoolVarFalse; @@ -2096,10 +2096,10 @@ void ex_loadkeymap(exarg_T *eap) if ((*p != '"') && (*p != NUL)) { kmap_T *kp = GA_APPEND_VIA_PTR(kmap_T, &curbuf->b_kmap_ga); - s = skiptowhite(p); + s = (char_u *)skiptowhite((char *)p); kp->from = vim_strnsave(p, (size_t)(s - p)); p = (char_u *)skipwhite((char *)s); - s = skiptowhite(p); + s = (char_u *)skiptowhite((char *)p); kp->to = vim_strnsave(p, (size_t)(s - p)); if ((STRLEN(kp->from) + STRLEN(kp->to) >= KMAP_LLEN) diff --git a/src/nvim/drawline.c b/src/nvim/drawline.c new file mode 100644 index 0000000000..b6c4400c60 --- /dev/null +++ b/src/nvim/drawline.c @@ -0,0 +1,2680 @@ +// 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 + +// drawline.c: Functions for drawing window lines on the screen. +// 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 <stdbool.h> +#include <string.h> + +#include "nvim/arabic.h" +#include "nvim/buffer.h" +#include "nvim/charset.h" +#include "nvim/cursor.h" +#include "nvim/cursor_shape.h" +#include "nvim/diff.h" +#include "nvim/drawline.h" +#include "nvim/fold.h" +#include "nvim/grid.h" +#include "nvim/highlight.h" +#include "nvim/highlight_group.h" +#include "nvim/indent.h" +#include "nvim/match.h" +#include "nvim/move.h" +#include "nvim/option.h" +#include "nvim/plines.h" +#include "nvim/quickfix.h" +#include "nvim/search.h" +#include "nvim/sign.h" +#include "nvim/spell.h" +#include "nvim/state.h" +#include "nvim/syntax.h" +#include "nvim/undo.h" +#include "nvim/window.h" + +#define MB_FILLER_CHAR '<' // character used when a double-width character + // doesn't fit. + +/// for line_putchar. Contains the state that needs to be remembered from +/// putting one character to the next. +typedef struct { + const char *p; + int prev_c; ///< previous Arabic character + int prev_c1; ///< first composing char for prev_c +} LineState; +#define LINE_STATE(p) { p, 0, 0 } + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "drawline.c.generated.h" +#endif + +/// Advance **color_cols +/// +/// @return true when there are columns to draw. +static bool advance_color_col(int vcol, int **color_cols) +{ + while (**color_cols >= 0 && vcol > **color_cols) { + (*color_cols)++; + } + return **color_cols >= 0; +} + +/// Used when 'cursorlineopt' contains "screenline": compute the margins between +/// which the highlighting is used. +static void margin_columns_win(win_T *wp, int *left_col, int *right_col) +{ + // cache previous calculations depending on w_virtcol + static int saved_w_virtcol; + static win_T *prev_wp; + static int prev_left_col; + static int prev_right_col; + static int prev_col_off; + + int cur_col_off = win_col_off(wp); + int width1; + int width2; + + if (saved_w_virtcol == wp->w_virtcol && prev_wp == wp + && prev_col_off == cur_col_off) { + *right_col = prev_right_col; + *left_col = prev_left_col; + return; + } + + width1 = wp->w_width - cur_col_off; + width2 = width1 + win_col_off2(wp); + + *left_col = 0; + *right_col = width1; + + if (wp->w_virtcol >= (colnr_T)width1) { + *right_col = width1 + ((wp->w_virtcol - width1) / width2 + 1) * width2; + } + if (wp->w_virtcol >= (colnr_T)width1 && width2 > 0) { + *left_col = (wp->w_virtcol - width1) / width2 * width2 + width1; + } + + // cache values + prev_left_col = *left_col; + prev_right_col = *right_col; + prev_wp = wp; + saved_w_virtcol = wp->w_virtcol; + prev_col_off = cur_col_off; +} + +/// Put a single char from an UTF-8 buffer into a line buffer. +/// +/// 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); + int u8c, u8cc[MAX_MCO]; + if (cells > maxcells) { + return -1; + } + u8c = utfc_ptr2char((char *)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)); + 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; + + // 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); + s->prev_c1 = u8cc[0]; + } else { + pc = utfc_ptr2char((char *)p + c_len, pcc); + nc = s->prev_c; + pc1 = pcc[0]; + } + s->prev_c = u8c; + + u8c = arabic_shape(u8c, &firstbyte, &u8cc[0], pc, pc1, nc); + } else { + s->prev_c = u8c; + } + schar_from_cc(dest[0], u8c, u8cc); + } + if (cells > 1) { + dest[1][0] = 0; + } +done: + s->p += c_len; + return cells; +} + +static inline void provider_err_virt_text(linenr_T lnum, char *err) +{ + Decoration err_decor = DECORATION_INIT; + int hl_err = syn_check_group(S_LEN("ErrorMsg")); + kv_push(err_decor.virt_text, + ((VirtTextChunk){ .text = err, + .hl_id = hl_err })); + err_decor.virt_text_width = (int)mb_string2cells(err); + decor_add_ephemeral(lnum - 1, 0, lnum - 1, 0, &err_decor, 0, 0); +} + +static void draw_virt_text(win_T *wp, buf_T *buf, int col_off, int *end_col, int max_col, + int win_row) +{ + DecorState *state = &decor_state; + int right_pos = max_col; + bool do_eol = state->eol_col > -1; + for (size_t i = 0; i < kv_size(state->active); i++) { + DecorRange *item = &kv_A(state->active, i); + if (!(item->start_row == state->row + && (kv_size(item->decor.virt_text) || item->decor.ui_watched))) { + continue; + } + if (item->win_col == -1) { + if (item->decor.virt_text_pos == kVTRightAlign) { + right_pos -= item->decor.virt_text_width; + item->win_col = right_pos; + } else if (item->decor.virt_text_pos == kVTEndOfLine && do_eol) { + item->win_col = state->eol_col; + } else if (item->decor.virt_text_pos == kVTWinCol) { + item->win_col = MAX(item->decor.col + col_off, 0); + } + } + if (item->win_col < 0) { + continue; + } + int col; + if (item->decor.ui_watched) { + // send mark position to UI + col = item->win_col; + WinExtmark m = { (NS)item->ns_id, item->mark_id, win_row, col }; + kv_push(win_extmark_arr, m); + } + if (kv_size(item->decor.virt_text)) { + col = draw_virt_text_item(buf, item->win_col, item->decor.virt_text, + item->decor.hl_mode, max_col, item->win_col - col_off); + } + item->win_col = -2; // deactivate + if (item->decor.virt_text_pos == kVTEndOfLine && do_eol) { + state->eol_col = col + 1; + } + + *end_col = MAX(*end_col, col); + } +} + +static int draw_virt_text_item(buf_T *buf, int col, VirtText vt, HlMode hl_mode, int max_col, + int vcol) +{ + LineState s = LINE_STATE(""); + int virt_attr = 0; + size_t virt_pos = 0; + + while (col < max_col) { + if (!*s.p) { + if (virt_pos >= kv_size(vt)) { + break; + } + virt_attr = 0; + do { + s.p = kv_A(vt, virt_pos).text; + int hl_id = kv_A(vt, virt_pos).hl_id; + virt_attr = hl_combine_attr(virt_attr, + hl_id > 0 ? syn_id2attr(hl_id) : 0); + virt_pos++; + } while (!s.p && virt_pos < kv_size(vt)); + if (!s.p) { + break; + } + } + if (!*s.p) { + continue; + } + int attr; + bool through = false; + if (hl_mode == kHlModeCombine) { + attr = hl_combine_attr(linebuf_attr[col], virt_attr); + } else if (hl_mode == kHlModeBlend) { + through = (*s.p == ' '); + attr = hl_blend_attrs(linebuf_attr[col], virt_attr, &through); + } else { + attr = virt_attr; + } + schar_T dummy[2]; + int cells = line_putchar(buf, &s, through ? dummy : &linebuf_char[col], + max_col - col, false, vcol); + // if we failed to emit a char, we still need to advance + cells = MAX(cells, 1); + + for (int c = 0; c < cells; c++) { + linebuf_attr[col++] = attr; + } + vcol += cells; + } + return col; +} + +/// Return true if CursorLineSign highlight is to be used. +static bool use_cursor_line_sign(win_T *wp, linenr_T lnum) +{ + return wp->w_p_cul + && lnum == wp->w_cursor.lnum + && (wp->w_p_culopt_flags & CULOPT_NBR); +} + +// Get information needed to display the sign in line 'lnum' in window 'wp'. +// If 'nrcol' is true, the sign is going to be displayed in the number column. +// Otherwise the sign is going to be displayed in the sign column. +// +// @param count max number of signs +// @param[out] n_extrap number of characters from pp_extra to display +// @param 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 cul_attr) +{ + // Draw cells with the sign value or blank. + *c_extrap = ' '; + *c_finalp = NUL; + if (nrcol) { + *n_extrap = number_width(wp) + 1; + } else { + if (use_cursor_line_sign(wp, lnum)) { + *char_attrp = win_hl_attr(wp, HLF_CLS); + } else { + *char_attrp = win_hl_attr(wp, HLF_SC); + } + *n_extrap = win_signcol_width(wp); + } + + if (row == startrow + filler_lines && filler_todo <= 0) { + SignTextAttrs *sattr = sign_get_attr(sign_idx, sattrs, wp->w_scwidth); + if (sattr != NULL) { + *pp_extra = sattr->text; + if (*pp_extra != NULL) { + *c_extrap = NUL; + *c_finalp = NUL; + + if (nrcol) { + int n, width = number_width(wp) - 2; + for (n = 0; n < width; n++) { + extra[n] = ' '; + } + extra[n] = NUL; + STRCAT(extra, *pp_extra); + STRCAT(extra, " "); + *pp_extra = extra; + *n_extrap = (int)STRLEN(*pp_extra); + } else { + 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)); + + assert(extra_size > symbol_blen); + memset(extra, ' ', extra_size); + memcpy(extra, *pp_extra, symbol_blen); + + *pp_extra = extra; + (*pp_extra)[*n_extrap] = NUL; + } + } + + if (use_cursor_line_sign(wp, lnum) && cul_attr > 0) { + *char_attrp = cul_attr; + } else { + *char_attrp = sattr->hl_attr_id; + } + } + } +} + +static int get_sign_attrs(buf_T *buf, linenr_T lnum, SignTextAttrs *sattrs, int *line_attr, + int *num_attr, int *cul_attr) +{ + HlPriAttr line_attrs = { *line_attr, 0 }; + HlPriAttr num_attrs = { *num_attr, 0 }; + HlPriAttr cul_attrs = { *cul_attr, 0 }; + + // TODO(bfredl, vigoux): line_attr should not take priority over decoration! + int num_signs = buf_get_signattrs(buf, lnum, sattrs, &num_attrs, &line_attrs, &cul_attrs); + decor_redraw_signs(buf, lnum - 1, &num_signs, sattrs, &num_attrs, &line_attrs, &cul_attrs); + + *line_attr = line_attrs.attr_id; + *num_attr = num_attrs.attr_id; + *cul_attr = cul_attrs.attr_id; + + return num_signs; +} + +/// Return true if CursorLineNr highlight is to be used for the number column. +/// +/// - 'cursorline' must be set +/// - lnum must be the cursor line +/// - 'cursorlineopt' has "number" +/// - don't highlight filler lines (when in diff mode) +/// - When line is wrapped and 'cursorlineopt' does not have "line", only highlight the line number +/// itself on the first screenline of the wrapped line, otherwise highlight the number column of +/// all screenlines of the wrapped line. +static bool use_cursor_line_nr(win_T *wp, linenr_T lnum, int row, int startrow, int filler_lines) +{ + return wp->w_p_cul + && lnum == wp->w_cursor.lnum + && (wp->w_p_culopt_flags & CULOPT_NBR) + && (row == startrow + filler_lines + || (row > startrow + filler_lines + && (wp->w_p_culopt_flags & CULOPT_LINE))); +} + +static inline void get_line_number_str(win_T *wp, linenr_T lnum, char_u *buf, size_t buf_len) +{ + long num; + char *fmt = "%*ld "; + + if (wp->w_p_nu && !wp->w_p_rnu) { + // 'number' + 'norelativenumber' + num = (long)lnum; + } else { + // 'relativenumber', don't use negative numbers + num = labs((long)get_cursor_rel_lnum(wp, lnum)); + if (num == 0 && wp->w_p_nu && wp->w_p_rnu) { + // 'number' + 'relativenumber' + num = lnum; + fmt = "%-*ld "; + } + } + + snprintf((char *)buf, buf_len, fmt, number_width(wp), num); +} + +static int get_line_number_attr(win_T *wp, linenr_T lnum, int row, int startrow, int filler_lines) +{ + if (wp->w_p_rnu) { + if (lnum < wp->w_cursor.lnum) { + // Use LineNrAbove + return win_hl_attr(wp, HLF_LNA); + } + if (lnum > wp->w_cursor.lnum) { + // Use LineNrBelow + return win_hl_attr(wp, HLF_LNB); + } + } + + if (use_cursor_line_nr(wp, lnum, row, startrow, filler_lines)) { + // TODO(vim): Can we use CursorLine instead of CursorLineNr + // when CursorLineNr isn't set? + return win_hl_attr(wp, HLF_CLN); + } + + return win_hl_attr(wp, HLF_N); +} + +static void apply_cursorline_highlight(win_T *wp, linenr_T lnum, int *line_attr, int *cul_attr, + int *line_attr_lowprio) +{ + *cul_attr = win_hl_attr(wp, HLF_CUL); + HlAttrs ae = syn_attr2entry(*cul_attr); + // We make a compromise here (#7383): + // * low-priority CursorLine if fg is not set + // * high-priority ("same as Vim" priority) CursorLine if fg is set + if (ae.rgb_fg_color == -1 && ae.cterm_fg_color == 0) { + *line_attr_lowprio = *cul_attr; + } else { + if (!(State & MODE_INSERT) && bt_quickfix(wp->w_buffer) + && qf_current_entry(wp) == lnum) { + *line_attr = hl_combine_attr(*cul_attr, *line_attr); + } else { + *line_attr = *cul_attr; + } + } +} + +static bool check_mb_utf8(int *c, int *u8cc) +{ + if (utf_char2len(*c) > 1) { + *u8cc = 0; + *c = 0xc0; + return true; + } + return false; +} + +/// Display line "lnum" of window 'wp' on the screen. +/// wp->w_virtcol needs to be valid. +/// +/// @param lnum line to display +/// @param startrow first row relative to window grid +/// @param endrow last grid row to be redrawn +/// @param nochange not updating for changed text +/// @param number_only only update the number column +/// @param foldinfo fold info for this line +/// @param[in, out] providers decoration providers active this line +/// items will be disables if they cause errors +/// or explicitly return `false`. +/// +/// @return the number of last row the line occupies. +int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, bool number_only, + foldinfo_T foldinfo, DecorProviders *providers, char **provider_err) +{ + int c = 0; // init for GCC + 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" + 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 + // 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 + 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 + 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; + int saved_c_extra = 0; + int saved_c_final = 0; + int saved_char_attr = 0; + + int n_attr = 0; // chars with special attr + int saved_attr2 = 0; // char_attr saved for n_attr + int n_attr3 = 0; // chars with overruling special attr + int saved_attr3 = 0; // char_attr saved for n_attr3 + + int n_skip = 0; // nr of chars to skip for 'nowrap' + + int fromcol = -10; // start of inverting + int tocol = MAXCOL; // end of inverting + int fromcol_prev = -2; // start of inverting after cursor + bool noinvcur = false; // don't invert the cursor + bool lnum_in_visual_area = false; + pos_T pos; + long v; + + 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 area_attr = 0; // attributes desired by highlighting + int search_attr = 0; // attributes desired by 'hlsearch' + int vcol_save_attr = 0; // saved attr for 'cursorcolumn' + int syntax_attr = 0; // attributes desired by syntax + bool has_syntax = false; // this buffer has syntax highl. + int save_did_emsg; + int eol_hl_off = 0; // 1 if highlighted char after EOL + bool draw_color_col = false; // highlight colorcolumn + 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 + int nextlinecol = 0; // column where nextline[] starts + int nextline_idx = 0; // index in nextline[] where next line + // starts + int spell_attr = 0; // attributes desired by spelling + int word_end = 0; // last byte with same spell_attr + static linenr_T checked_lnum = 0; // line number for "checked_col" + static int checked_col = 0; // column in "checked_lnum" up to which + // there are no spell errors + static int cap_col = -1; // column to check for Cap word + static linenr_T capcol_lnum = 0; // line number where "cap_col" + int cur_checked_col = 0; // checked column for current line + int extra_check = 0; // has syntax or linebreak + int multi_attr = 0; // attributes desired by multibyte + int mb_l = 1; // multi-byte byte length + int mb_c = 0; // decoded multi-byte character + bool mb_utf8 = false; // screen char is UTF-8 char + int u8cc[MAX_MCO]; // composing UTF-8 chars + int filler_lines; // nr of filler lines to be drawn + int filler_todo; // nr of filler lines still to do + 1 + hlf_T diff_hlf = (hlf_T)0; // type of diff highlighting + int change_start = MAXCOL; // first col of changed area + int change_end = -1; // last col of changed area + colnr_T trailcol = MAXCOL; // start of trailing spaces + colnr_T leadcol = 0; // start of leading spaces + bool in_multispace = false; // in multiple consecutive spaces + int multispace_pos = 0; // position in lcs-multispace string + bool need_showbreak = false; // overlong line, skip first x chars + int line_attr = 0; // attribute for the whole line + int line_attr_save; + int line_attr_lowprio = 0; // low-priority attribute for the line + int line_attr_lowprio_save; + int prev_c = 0; // previous Arabic character + int prev_c1 = 0; // first composing char for prev_c + + bool search_attr_from_match = false; // if search_attr is from :match + 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 + + bool area_active = false; + + int cul_attr = 0; // set when 'cursorline' active + // 'cursorlineopt' has "screenline" and cursor is in this line + bool cul_screenline = false; + // margin columns for the screen line, needed for when 'cursorlineopt' + // contains "screenline" + 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 + + int syntax_flags = 0; + int syntax_seqnr = 0; + int prev_syntax_id = 0; + int conceal_attr = win_hl_attr(wp, HLF_CONCEAL); + bool is_concealing = false; + int boguscols = 0; ///< nonexistent columns added to + ///< 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 \ + { \ + n_extra += vcol_off; \ + vcol -= vcol_off; \ + vcol_off = 0; \ + col -= boguscols; \ + old_boguscols = boguscols; \ + boguscols = 0; \ + } + + if (startrow > endrow) { // past the end already! + return startrow; + } + + row = startrow; + + buf_T *buf = wp->w_buffer; + bool end_fill = (lnum == buf->b_ml.ml_line_count + 1); + + if (!number_only) { + // To speed up the loop below, set extra_check when there is linebreak, + // trailing white space and/or syntax processing to be done. + extra_check = wp->w_p_lbr; + if (syntax_present(wp) && !wp->w_s->b_syn_error && !wp->w_s->b_syn_slow + && !has_fold && !end_fill) { + // Prepare for syntax highlighting in this line. When there is an + // error, stop syntax highlighting. + save_did_emsg = did_emsg; + did_emsg = false; + syntax_start(wp, lnum); + if (did_emsg) { + wp->w_s->b_syn_error = true; + } else { + did_emsg = save_did_emsg; + if (!wp->w_s->b_syn_slow) { + has_syntax = true; + extra_check = true; + } + } + } + + has_decor = decor_redraw_line(buf, lnum - 1, &decor_state); + + providers_invoke_line(wp, providers, lnum - 1, &has_decor, provider_err); + + if (*provider_err) { + provider_err_virt_text(lnum, *provider_err); + has_decor = true; + *provider_err = NULL; + } + + if (has_decor) { + extra_check = true; + } + + // Check for columns to display for 'colorcolumn'. + color_cols = wp->w_buffer->terminal ? NULL : wp->w_p_cc_cols; + if (color_cols != NULL) { + draw_color_col = advance_color_col((int)VCOL_HLC, &color_cols); + } + + if (wp->w_p_spell + && !has_fold + && !end_fill + && *wp->w_s->b_p_spl != NUL + && !GA_EMPTY(&wp->w_s->b_langp) + && *(char **)(wp->w_s->b_langp.ga_data) != NULL) { + // Prepare for spell checking. + has_spell = true; + extra_check = true; + + // Get the start of the next line, so that words that wrap to the next + // line are found too: "et<line-break>al.". + // Trick: skip a few chars for C/shell/Vim comments + nextline[SPWORDLEN] = NUL; + if (lnum < wp->w_buffer->b_ml.ml_line_count) { + line = ml_get_buf(wp->w_buffer, lnum + 1, false); + spell_cat_line(nextline + SPWORDLEN, line, SPWORDLEN); + } + + // When a word wrapped from the previous line the start of the current + // line is valid. + if (lnum == checked_lnum) { + cur_checked_col = checked_col; + } + checked_lnum = 0; + + // When there was a sentence end in the previous line may require a + // word starting with capital in this line. In line 1 always check + // the first word. + if (lnum != capcol_lnum) { + cap_col = -1; + } + if (lnum == 1) { + cap_col = 0; + } + capcol_lnum = 0; + } + + // handle Visual active in this window + if (VIsual_active && wp->w_buffer == curwin->w_buffer) { + pos_T *top, *bot; + + if (ltoreq(curwin->w_cursor, VIsual)) { + // Visual is after curwin->w_cursor + top = &curwin->w_cursor; + bot = &VIsual; + } else { + // Visual is before curwin->w_cursor + top = &VIsual; + bot = &curwin->w_cursor; + } + lnum_in_visual_area = (lnum >= top->lnum && lnum <= bot->lnum); + if (VIsual_mode == Ctrl_V) { + // block mode + if (lnum_in_visual_area) { + fromcol = wp->w_old_cursor_fcol; + tocol = wp->w_old_cursor_lcol; + } + } else { + // non-block mode + if (lnum > top->lnum && lnum <= bot->lnum) { + fromcol = 0; + } else if (lnum == top->lnum) { + if (VIsual_mode == 'V') { // linewise + fromcol = 0; + } else { + getvvcol(wp, top, (colnr_T *)&fromcol, NULL, NULL); + if (gchar_pos(top) == NUL) { + tocol = fromcol + 1; + } + } + } + if (VIsual_mode != 'V' && lnum == bot->lnum) { + if (*p_sel == 'e' && bot->col == 0 + && bot->coladd == 0) { + fromcol = -10; + tocol = MAXCOL; + } else if (bot->col == MAXCOL) { + tocol = MAXCOL; + } else { + pos = *bot; + if (*p_sel == 'e') { + getvvcol(wp, &pos, (colnr_T *)&tocol, NULL, NULL); + } else { + getvvcol(wp, &pos, NULL, NULL, (colnr_T *)&tocol); + tocol++; + } + } + } + } + + // Check if the char under the cursor should be inverted (highlighted). + if (!highlight_match && lnum == curwin->w_cursor.lnum && wp == curwin + && cursor_is_block_during_visual(*p_sel == 'e')) { + noinvcur = true; + } + + // if inverting in this line set area_highlighting + if (fromcol >= 0) { + area_highlighting = true; + attr = win_hl_attr(wp, HLF_V); + } + // handle 'incsearch' and ":s///c" highlighting + } else if (highlight_match + && wp == curwin + && !has_fold + && lnum >= curwin->w_cursor.lnum + && lnum <= curwin->w_cursor.lnum + search_match_lines) { + if (lnum == curwin->w_cursor.lnum) { + getvcol(curwin, &(curwin->w_cursor), + (colnr_T *)&fromcol, NULL, NULL); + } else { + fromcol = 0; + } + if (lnum == curwin->w_cursor.lnum + search_match_lines) { + pos.lnum = lnum; + pos.col = search_match_endcol; + getvcol(curwin, &pos, (colnr_T *)&tocol, NULL, NULL); + } + // do at least one character; happens when past end of line + if (fromcol == tocol && search_match_endcol) { + tocol = fromcol + 1; + } + area_highlighting = true; + 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) { + if (diff_find_change(wp, lnum, &change_start, &change_end)) { + diff_hlf = HLF_ADD; // added line + } else if (change_start == 0) { + diff_hlf = HLF_TXD; // changed text + } else { + diff_hlf = HLF_CHD; // changed line + } + } else { + diff_hlf = HLF_ADD; // added line + } + filler_lines = 0; + area_highlighting = true; + } + VirtLines virt_lines = KV_INITIAL_VALUE; + int n_virt_lines = decor_virt_lines(wp, lnum, &virt_lines); + filler_lines += n_virt_lines; + if (lnum == wp->w_topline) { + filler_lines = wp->w_topfill; + n_virt_lines = MIN(n_virt_lines, filler_lines); + } + filler_todo = filler_lines; + + // Cursor line highlighting for 'cursorline' in the current window. + if (lnum == wp->w_cursor.lnum) { + // Do not show the cursor line in the text when Visual mode is active, + // because it's not clear what is selected then. + if (wp->w_p_cul && !(wp == curwin && VIsual_active) + && wp->w_p_culopt_flags != CULOPT_NBR) { + cul_screenline = (wp->w_p_wrap + && (wp->w_p_culopt_flags & CULOPT_SCRLINE)); + if (!cul_screenline) { + apply_cursorline_highlight(wp, lnum, &line_attr, &cul_attr, &line_attr_lowprio); + } else { + margin_columns_win(wp, &left_curline_col, &right_curline_col); + } + area_highlighting = true; + } + } + + SignTextAttrs sattrs[SIGN_SHOW_MAX]; // sign attributes for the sign column + int sign_num_attr = 0; // sign attribute for the number column + int sign_cul_attr = 0; // sign attribute for cursorline + CLEAR_FIELD(sattrs); + int num_signs = get_sign_attrs(buf, lnum, sattrs, &line_attr, &sign_num_attr, &sign_cul_attr); + + // Highlight the current line in the quickfix window. + if (bt_quickfix(wp->w_buffer) && qf_current_entry(wp) == lnum) { + line_attr = win_hl_attr(wp, HLF_QFL); + } + + if (line_attr_lowprio || line_attr) { + area_highlighting = true; + } + + if (cul_screenline) { + line_attr_save = line_attr; + line_attr_lowprio_save = line_attr_lowprio; + } + + line = end_fill ? (char_u *)"" : 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); + } + + // To be able to spell-check over line boundaries copy the end of the + // current line into nextline[]. Above the start of the next line was + // copied to nextline[SPWORDLEN]. + if (nextline[SPWORDLEN] == NUL) { + // No next line or it is empty. + nextlinecol = MAXCOL; + nextline_idx = 0; + } else { + v = (long)STRLEN(line); + if (v < SPWORDLEN) { + // Short line, use it completely and append the start of the + // next line. + nextlinecol = 0; + memmove(nextline, line, (size_t)v); + STRMOVE(nextline + v, nextline + SPWORDLEN); + nextline_idx = (int)v + 1; + } else { + // Long line, use only the last SPWORDLEN bytes. + nextlinecol = (int)v - SPWORDLEN; + memmove(nextline, line + nextlinecol, SPWORDLEN); // -V512 + nextline_idx = SPWORDLEN + 1; + } + } + } + + if (wp->w_p_list && !has_fold && !end_fill) { + if (wp->w_p_lcs_chars.space + || wp->w_p_lcs_chars.multispace != NULL + || wp->w_p_lcs_chars.leadmultispace != NULL + || wp->w_p_lcs_chars.trail + || wp->w_p_lcs_chars.lead + || wp->w_p_lcs_chars.nbsp) { + extra_check = true; + } + // find start of trailing whitespace + if (wp->w_p_lcs_chars.trail) { + trailcol = (colnr_T)STRLEN(ptr); + while (trailcol > (colnr_T)0 && ascii_iswhite(ptr[trailcol - 1])) { + trailcol--; + } + trailcol += (colnr_T)(ptr - line); + } + // find end of leading whitespace + if (wp->w_p_lcs_chars.lead || wp->w_p_lcs_chars.leadmultispace != NULL) { + leadcol = 0; + while (ascii_iswhite(ptr[leadcol])) { + leadcol++; + } + if (ptr[leadcol] == NUL) { + // in a line full of spaces all of them are treated as trailing + leadcol = (colnr_T)0; + } else { + // keep track of the first column not filled with spaces + leadcol += (colnr_T)(ptr - line) + 1; + } + } + } + + // 'nowrap' or 'wrap' and a single line that doesn't fit: Advance to the + // first character to be displayed. + if (wp->w_p_wrap) { + v = wp->w_skipcol; + } else { + v = wp->w_leftcol; + } + if (v > 0 && !number_only) { + char_u *prev_ptr = ptr; + chartabsize_T cts; + int charsize; + + 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; + MB_PTR_ADV(cts.cts_ptr); + } + vcol = cts.cts_vcol; + ptr = (char_u *)cts.cts_ptr; + clear_chartabsize_arg(&cts); + + // When: + // - 'cuc' is set, or + // - 'colorcolumn' is set, or + // - 'virtualedit' is set, or + // - the visual mode is active, + // the end of the line may be before the start of the displayed part. + if (vcol < v && (wp->w_p_cuc + || draw_color_col + || virtual_active() + || (VIsual_active && wp->w_buffer == curwin->w_buffer))) { + vcol = v; + } + + // Handle a character that's not completely on the screen: Put ptr at + // that character but skip the first few screen characters. + if (vcol > v) { + vcol -= charsize; + 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) { + n_skip = (int)(v - vcol); + } + } + + // Adjust for when the inverted text is before the screen, + // and when the start of the inverted text is before the screen. + if (tocol <= vcol) { + fromcol = 0; + } else if (fromcol >= 0 && fromcol < vcol) { + fromcol = (int)vcol; + } + + // When w_skipcol is non-zero, first line needs 'showbreak' + if (wp->w_p_wrap) { + need_showbreak = true; + } + // When spell checking a word we need to figure out the start of the + // word and if it's badly spelled or not. + if (has_spell) { + size_t len; + colnr_T linecol = (colnr_T)(ptr - line); + hlf_T spell_hlf = HLF_COUNT; + + pos = wp->w_cursor; + wp->w_cursor.lnum = lnum; + wp->w_cursor.col = linecol; + len = spell_move_to(wp, FORWARD, true, true, &spell_hlf); + + // spell_move_to() may call ml_get() and make "line" invalid + line = ml_get_buf(wp->w_buffer, lnum, false); + ptr = line + linecol; + + if (len == 0 || (int)wp->w_cursor.col > ptr - line) { + // no bad word found at line start, don't check until end of a + // word + spell_hlf = HLF_COUNT; + word_end = (int)(spell_to_word_end(ptr, wp) - line + 1); + } else { + // bad word found, use attributes until end of word + assert(len <= INT_MAX); + word_end = wp->w_cursor.col + (int)len + 1; + + // Turn index into actual attributes. + if (spell_hlf != HLF_COUNT) { + spell_attr = highlight_attr[spell_hlf]; + } + } + wp->w_cursor = pos; + + // Need to restart syntax highlighting for this line. + if (has_syntax) { + syntax_start(wp, lnum); + } + } + } + + // Correct highlighting for cursor that can't be disabled. + // Avoids having to check this for each character. + if (fromcol >= 0) { + if (noinvcur) { + if ((colnr_T)fromcol == wp->w_virtcol) { + // highlighting starts at cursor, let it start just after the + // cursor + fromcol_prev = fromcol; + fromcol = -1; + } else if ((colnr_T)fromcol < wp->w_virtcol) { + // restart highlighting after the cursor + fromcol_prev = wp->w_virtcol; + } + } + if (fromcol >= tocol) { + fromcol = -1; + } + } + + if (!number_only && !has_fold && !end_fill) { + v = ptr - line; + area_highlighting |= prepare_search_hl_line(wp, lnum, (colnr_T)v, + &line, &screen_search_hl, &search_attr, + &search_attr_from_match); + ptr = line + v; // "line" may have been updated + } + + int off = 0; // Offset relative start of line + int col = 0; // Visual column on screen. + if (wp->w_p_rl) { + // Rightleft window: process the text in the normal direction, but put + // it in linebuf_char[off] from right to left. Start at the + // rightmost column of the window. + col = grid->cols - 1; + off += col; + } + + // won't highlight after TERM_ATTRS_MAX columns + int term_attrs[TERM_ATTRS_MAX] = { 0 }; + if (wp->w_buffer->terminal) { + terminal_get_line_attributes(wp->w_buffer->terminal, wp, lnum, term_attrs); + extra_check = true; + } + + int sign_idx = 0; + // Repeat for the whole displayed line. + for (;;) { + int has_match_conc = 0; ///< match wants to conceal + int decor_conceal = 0; + + bool did_decrement_ptr = false; + + // Skip this quickly when working on the text. + if (draw_state != WL_LINE) { + if (cul_screenline) { + cul_attr = 0; + line_attr = line_attr_save; + line_attr_lowprio = line_attr_lowprio_save; + } + + if (draw_state == WL_CMDLINE - 1 && n_extra == 0) { + draw_state = WL_CMDLINE; + if (cmdwin_type != 0 && wp == curwin) { + // Draw the cmdline character. + n_extra = 1; + c_extra = cmdwin_type; + c_final = NUL; + char_attr = win_hl_attr(wp, HLF_AT); + } + } + + 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 + // already be in use. + xfree(p_extra_free); + p_extra_free = xmalloc(MAX_MCO * (size_t)fdc + 1); + n_extra = (int)fill_foldcolumn(p_extra_free, wp, foldinfo, lnum); + p_extra_free[n_extra] = NUL; + p_extra = p_extra_free; + c_extra = NUL; + c_final = NUL; + if (use_cursor_line_sign(wp, lnum)) { + char_attr = win_hl_attr(wp, HLF_CLF); + } else { + char_attr = win_hl_attr(wp, HLF_FC); + } + } + } + + // sign column, this is hit until sign_idx reaches count + if (draw_state == WL_SIGN - 1 && n_extra == 0) { + draw_state = WL_SIGN; + // Show the sign column when there are any signs in this buffer + if (wp->w_scwidth > 0) { + get_sign_display_info(false, wp, lnum, sattrs, row, + startrow, filler_lines, filler_todo, + &c_extra, &c_final, extra, sizeof(extra), + &p_extra, &n_extra, &char_attr, sign_idx, + sign_cul_attr); + sign_idx++; + if (sign_idx < wp->w_scwidth) { + draw_state = WL_SIGN - 1; + } else { + sign_idx = 0; + } + } + } + + if (draw_state == WL_NR - 1 && n_extra == 0) { + draw_state = WL_NR; + // Display the absolute or relative line number. After the + // first fill with blanks when the 'n' flag isn't in 'cpo' + if ((wp->w_p_nu || wp->w_p_rnu) + && (row == startrow + filler_lines + || vim_strchr(p_cpo, CPO_NUMCOL) == NULL)) { + // If 'signcolumn' is set to 'number' and a sign is present + // in 'lnum', then display the sign instead of the line + // number. + if (*wp->w_p_scl == 'n' && *(wp->w_p_scl + 1) == 'u' && num_signs > 0) { + get_sign_display_info(true, wp, lnum, sattrs, row, + startrow, filler_lines, filler_todo, + &c_extra, &c_final, extra, sizeof(extra), + &p_extra, &n_extra, &char_attr, sign_idx, + sign_cul_attr); + } 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)); + if (wp->w_skipcol > 0) { + for (p_extra = extra; *p_extra == ' '; p_extra++) { + *p_extra = '-'; + } + } + 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; + *p1 = *p2; + *p2 = t; + } + } + p_extra = extra; + c_extra = NUL; + } else { + c_extra = ' '; + } + c_final = NUL; + n_extra = number_width(wp) + 1; + if (sign_num_attr > 0) { + char_attr = sign_num_attr; + } else { + char_attr = get_line_number_attr(wp, lnum, row, startrow, filler_lines); + } + } + } + } + + if (draw_state == WL_NR && n_extra == 0) { + win_col_offset = off; + } + + if (wp->w_briopt_sbr && draw_state == WL_BRI - 1 + && n_extra == 0 && *get_showbreak_value(wp) != NUL) { + // draw indent after showbreak value + draw_state = WL_BRI; + } else if (wp->w_briopt_sbr && draw_state == WL_SBR && n_extra == 0) { + // after the showbreak, draw the breakindent + draw_state = WL_BRI - 1; + } + + // draw 'breakindent': indent wrapped text accordingly + if (draw_state == WL_BRI - 1 && n_extra == 0) { + draw_state = WL_BRI; + // if need_showbreak is set, breakindent also applies + if (wp->w_p_bri && (row != startrow || need_showbreak) + && filler_lines == 0) { + char_attr = 0; + + if (diff_hlf != (hlf_T)0) { + char_attr = win_hl_attr(wp, (int)diff_hlf); + } + p_extra = NULL; + c_extra = ' '; + c_final = NUL; + n_extra = + 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) { + n_extra = 0; + } + } + if (wp->w_skipcol > 0 && wp->w_p_wrap && wp->w_briopt_sbr) { + need_showbreak = false; + } + // Correct end of highlighted area for 'breakindent', + // required wen 'linebreak' is also set. + if (tocol == vcol) { + tocol += n_extra; + } + } + } + + if (draw_state == WL_SBR - 1 && n_extra == 0) { + draw_state = WL_SBR; + if (filler_todo > filler_lines - n_virt_lines) { + // TODO(bfredl): check this doesn't inhibit TUI-style + // clear-to-end-of-line. + c_extra = ' '; + c_final = NUL; + if (wp->w_p_rl) { + n_extra = col + 1; + } else { + n_extra = grid->cols - col; + } + char_attr = 0; + } else if (filler_todo > 0) { + // Draw "deleted" diff line(s) + if (char2cells(wp->w_p_fcs_chars.diff) > 1) { + c_extra = '-'; + c_final = NUL; + } else { + c_extra = wp->w_p_fcs_chars.diff; + c_final = NUL; + } + if (wp->w_p_rl) { + n_extra = col + 1; + } else { + n_extra = grid->cols - col; + } + char_attr = win_hl_attr(wp, HLF_DED); + } + char_u *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); + char_attr = win_hl_attr(wp, HLF_AT); + if (wp->w_skipcol == 0 || !wp->w_p_wrap) { + need_showbreak = false; + } + vcol_sbr = vcol + mb_charlen(sbr); + // Correct end of highlighted area for 'showbreak', + // required when 'linebreak' is also set. + if (tocol == vcol) { + tocol += n_extra; + } + // Combine 'showbreak' with 'cursorline', prioritizing 'showbreak'. + if (cul_attr) { + char_attr = hl_combine_attr(cul_attr, char_attr); + } + } + } + + if (draw_state == WL_LINE - 1 && n_extra == 0) { + sign_idx = 0; + draw_state = WL_LINE; + + if (has_decor && row == startrow + filler_lines) { + // hide virt_text on text hidden by 'nowrap' + decor_redraw_col(wp->w_buffer, (int)vcol, off, true, &decor_state); + } + + if (saved_n_extra) { + // Continue item from end of wrapped line. + n_extra = saved_n_extra; + c_extra = saved_c_extra; + c_final = saved_c_final; + p_extra = saved_p_extra; + char_attr = saved_char_attr; + } else { + char_attr = 0; + } + } + } + + if (cul_screenline && draw_state == WL_LINE + && vcol >= left_curline_col + && vcol < right_curline_col) { + apply_cursorline_highlight(wp, lnum, &line_attr, &cul_attr, &line_attr_lowprio); + } + + // When still displaying '$' of change command, stop at cursor + if (((dollar_vcol >= 0 + && wp == curwin + && lnum == wp->w_cursor.lnum + && vcol >= (long)wp->w_virtcol) + || (number_only && draw_state > WL_NR)) + && 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); + // Pretend we have finished updating the window. Except when + // 'cursorcolumn' is set. + if (wp->w_p_cuc) { + row = wp->w_cline_row + wp->w_cline_height; + } else { + row = grid->rows; + } + break; + } + + if (draw_state == WL_LINE + && has_fold + && col == win_col_offset + && n_extra == 0 + && row == startrow) { + char_attr = win_hl_attr(wp, HLF_FL); + + linenr_T lnume = lnum + foldinfo.fi_lines - 1; + memset(buf_fold, ' ', FOLD_TEXT_LEN); + 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); + p_extra_free = p_extra; + } + c_extra = NUL; + c_final = NUL; + p_extra[n_extra] = NUL; + } + + if (draw_state == WL_LINE + && has_fold + && col < grid->cols + && n_extra == 0 + && row == startrow) { + // fill rest of line with 'fold' + c_extra = wp->w_p_fcs_chars.fold; + c_final = NUL; + + n_extra = wp->w_p_rl ? (col + 1) : (grid->cols - col); + } + + if (draw_state == WL_LINE + && has_fold + && col >= grid->cols + && n_extra != 0 + && row == startrow) { + // Truncate the folding. + n_extra = 0; + } + + if (draw_state == WL_LINE && (area_highlighting || has_spell)) { + // handle Visual or match highlighting in this line + if (vcol == fromcol + || (vcol + 1 == fromcol && n_extra == 0 + && utf_ptr2cells((char *)ptr) > 1) + || ((int)vcol_prev == fromcol_prev + && vcol_prev < vcol // not at margin + && vcol < tocol)) { + area_attr = attr; // start highlighting + if (area_highlighting) { + area_active = true; + } + } else if (area_attr != 0 && (vcol == tocol + || (noinvcur + && (colnr_T)vcol == wp->w_virtcol))) { + area_attr = 0; // stop highlighting + area_active = false; + } + + if (!n_extra) { + // Check for start/end of 'hlsearch' and other matches. + // After end, check for start/end of next match. + // When another match, have to check for start again. + v = (ptr - line); + 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); + ptr = line + v; // "line" may have been changed + + // Do not allow a conceal over EOL otherwise EOL will be missed + // and bad things happen. + if (*ptr == NUL) { + has_match_conc = 0; + } + } + + if (diff_hlf != (hlf_T)0) { + if (diff_hlf == HLF_CHD && ptr - line >= change_start + && n_extra == 0) { + diff_hlf = HLF_TXD; // changed text + } + if (diff_hlf == HLF_TXD && ptr - line > change_end + && n_extra == 0) { + diff_hlf = HLF_CHD; // changed line + } + line_attr = win_hl_attr(wp, (int)diff_hlf); + // Overlay CursorLine onto diff-mode highlight. + if (cul_attr) { + line_attr = 0 != line_attr_lowprio // Low-priority CursorLine + ? hl_combine_attr(hl_combine_attr(cul_attr, line_attr), + hl_get_underline()) + : hl_combine_attr(line_attr, cul_attr); + } + } + + // Decide which of the highlight attributes to use. + attr_pri = true; + + if (area_attr != 0) { + char_attr = hl_combine_attr(line_attr, area_attr); + if (!highlight_match) { + // let search highlight show in Visual area if possible + char_attr = hl_combine_attr(search_attr, char_attr); + } + } else if (search_attr != 0) { + char_attr = hl_combine_attr(line_attr, search_attr); + } else if (line_attr != 0 && ((fromcol == -10 && tocol == MAXCOL) + || vcol < fromcol || vcol_prev < fromcol_prev + || vcol >= tocol)) { + // Use line_attr when not in the Visual or 'incsearch' area + // (area_attr may be 0 when "noinvcur" is set). + char_attr = line_attr; + } else { + attr_pri = false; + if (has_syntax) { + char_attr = syntax_attr; + } else { + char_attr = 0; + } + } + } + + // Get the next character to put on the screen. + // + // The "p_extra" points to the extra stuff that is inserted to + // represent special characters (non-printable stuff) and other + // things. When all characters are the same, c_extra is used. + // If c_final is set, it will compulsorily be used at the end. + // "p_extra" must end in a NUL to avoid utfc_ptr2len() reads past + // "p_extra[n_extra]". + // For the '$' of the 'list' option, n_extra == 1, p_extra == "". + if (n_extra > 0) { + if (c_extra != NUL || (n_extra == 1 && c_final != NUL)) { + c = (n_extra == 1 && c_final != NUL) ? c_final : c_extra; + mb_c = c; // doesn't handle non-utf-8 multi-byte! + mb_utf8 = check_mb_utf8(&c, u8cc); + } else { + assert(p_extra != NULL); + c = *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_utf8 = false; + if (mb_l > n_extra) { + mb_l = 1; + } else if (mb_l > 1) { + mb_c = utfc_ptr2char((char *)p_extra, u8cc); + mb_utf8 = true; + c = 0xc0; + } + if (mb_l == 0) { // at the NUL at end-of-line + mb_l = 1; + } + + // If a double-width char doesn't fit display a '>' in the last column. + if ((wp->w_p_rl ? (col <= 0) : (col >= grid->cols - 1)) + && utf_char2cells(mb_c) == 2) { + c = '>'; + mb_c = c; + mb_l = 1; + (void)mb_l; + multi_attr = win_hl_attr(wp, HLF_AT); + + if (cul_attr) { + multi_attr = 0 != line_attr_lowprio + ? hl_combine_attr(cul_attr, multi_attr) + : hl_combine_attr(multi_attr, cul_attr); + } + + // put the pointer back to output the double-width + // character at the start of the next line. + n_extra++; + p_extra--; + } else { + n_extra -= mb_l - 1; + p_extra += mb_l - 1; + } + p_extra++; + } + n_extra--; + } else if (foldinfo.fi_lines > 0) { + // skip writing the buffer line itself + c = NUL; + XFREE_CLEAR(p_extra_free); + } else { + int c0; + + XFREE_CLEAR(p_extra_free); + + // Get a character from the line itself. + c0 = c = *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_utf8 = false; + if (mb_l > 1) { + mb_c = utfc_ptr2char((char *)ptr, u8cc); + // Overlong encoded ASCII or ASCII with composing char + // is displayed normally, except a NUL. + if (mb_c < 0x80) { + c0 = c = mb_c; + } + mb_utf8 = true; + + // At start of the line we can have a composing char. + // Draw it as a space with a composing char. + if (utf_iscomposing(mb_c)) { + int i; + + for (i = MAX_MCO - 1; i > 0; i--) { + u8cc[i] = u8cc[i - 1]; + } + u8cc[0] = mb_c; + mb_c = ' '; + } + } + + if ((mb_l == 1 && c >= 0x80) + || (mb_l >= 1 && mb_c == 0) + || (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); + 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); + mb_utf8 = (c >= 0x80); + n_extra = (int)STRLEN(p_extra); + c_extra = NUL; + c_final = NUL; + if (area_attr == 0 && search_attr == 0) { + n_attr = n_extra + 1; + extra_attr = win_hl_attr(wp, HLF_8); + saved_attr2 = char_attr; // save current attr + } + } else if (mb_l == 0) { // at the NUL at end-of-line + mb_l = 1; + } else if (p_arshape && !p_tbidi && ARABIC_CHAR(mb_c)) { + // Do Arabic shaping. + int pc, pc1, nc; + int pcc[MAX_MCO]; + + // The idea of what is the previous and next + // character depends on 'rightleft'. + if (wp->w_p_rl) { + pc = prev_c; + pc1 = prev_c1; + nc = utf_ptr2char((char *)ptr + mb_l); + prev_c1 = u8cc[0]; + } else { + pc = utfc_ptr2char((char *)ptr + mb_l, pcc); + nc = prev_c; + pc1 = pcc[0]; + } + prev_c = mb_c; + + mb_c = arabic_shape(mb_c, &c, &u8cc[0], pc, pc1, nc); + } else { + prev_c = mb_c; + } + // If a double-width char doesn't fit display a '>' in the + // last column; the character is displayed at the start of the + // next line. + if ((wp->w_p_rl ? (col <= 0) : + (col >= grid->cols - 1)) + && utf_char2cells(mb_c) == 2) { + c = '>'; + mb_c = c; + mb_utf8 = false; + mb_l = 1; + multi_attr = win_hl_attr(wp, HLF_AT); + // Put pointer back so that the character will be + // displayed at the start of the next line. + ptr--; + did_decrement_ptr = true; + } else if (*ptr != NUL) { + ptr += mb_l - 1; + } + + // If a double-width char doesn't fit at the left side display a '<' in + // the first column. Don't do this for unprintable characters. + if (n_skip > 0 && mb_l > 1 && n_extra == 0) { + n_extra = 1; + c_extra = MB_FILLER_CHAR; + c_final = NUL; + c = ' '; + if (area_attr == 0 && search_attr == 0) { + n_attr = n_extra + 1; + extra_attr = win_hl_attr(wp, HLF_AT); + saved_attr2 = char_attr; // save current attr + } + mb_c = c; + mb_utf8 = false; + mb_l = 1; + } + ptr++; + + if (extra_check) { + bool can_spell = true; + + // Get syntax attribute, unless still at the start of the line + // (double-wide char that doesn't fit). + v = (ptr - line); + if (has_syntax && v > 0) { + // Get the syntax attribute for the character. If there + // is an error, disable syntax highlighting. + save_did_emsg = did_emsg; + did_emsg = false; + + syntax_attr = get_syntax_attr((colnr_T)v - 1, + has_spell ? &can_spell : NULL, false); + + if (did_emsg) { + wp->w_s->b_syn_error = true; + has_syntax = false; + } else { + did_emsg = save_did_emsg; + } + + if (wp->w_s->b_syn_slow) { + has_syntax = false; + } + + // Need to get the line again, a multi-line regexp may + // have made it invalid. + line = ml_get_buf(wp->w_buffer, lnum, false); + ptr = line + v; + + if (!attr_pri) { + if (cul_attr) { + char_attr = 0 != line_attr_lowprio + ? hl_combine_attr(cul_attr, syntax_attr) + : hl_combine_attr(syntax_attr, cul_attr); + } else { + char_attr = syntax_attr; + } + } else { + char_attr = hl_combine_attr(syntax_attr, char_attr); + } + // no concealing past the end of the line, it interferes + // with line highlighting. + if (c == NUL) { + syntax_flags = 0; + } else { + syntax_flags = get_syntax_info(&syntax_seqnr); + } + } else if (!attr_pri) { + char_attr = 0; + } + + // Check spelling (unless at the end of the line). + // Only do this when there is no syntax highlighting, the + // @Spell cluster is not used or the current syntax item + // contains the @Spell cluster. + v = (ptr - line); + if (has_spell && v >= word_end && v > cur_checked_col) { + spell_attr = 0; + if (!attr_pri) { + char_attr = syntax_attr; + } + if (c != 0 && (!has_syntax || can_spell)) { + char_u *prev_ptr; + char_u *p; + int len; + hlf_T spell_hlf = HLF_COUNT; + prev_ptr = ptr - mb_l; + v -= mb_l - 1; + + // Use nextline[] if possible, it has the start of the + // next line concatenated. + if ((prev_ptr - line) - nextlinecol >= 0) { + p = nextline + ((prev_ptr - line) - nextlinecol); + } else { + p = prev_ptr; + } + cap_col -= (int)(prev_ptr - line); + size_t tmplen = spell_check(wp, p, &spell_hlf, &cap_col, nochange); + assert(tmplen <= INT_MAX); + len = (int)tmplen; + word_end = (int)v + len; + + // In Insert mode only highlight a word that + // doesn't touch the cursor. + if (spell_hlf != HLF_COUNT + && (State & MODE_INSERT) + && wp->w_cursor.lnum == lnum + && wp->w_cursor.col >= + (colnr_T)(prev_ptr - line) + && wp->w_cursor.col < (colnr_T)word_end) { + spell_hlf = HLF_COUNT; + spell_redraw_lnum = lnum; + } + + if (spell_hlf == HLF_COUNT && p != prev_ptr + && (p - nextline) + len > nextline_idx) { + // Remember that the good word continues at the + // start of the next line. + checked_lnum = lnum + 1; + checked_col = (int)((p - nextline) + len - nextline_idx); + } + + // Turn index into actual attributes. + if (spell_hlf != HLF_COUNT) { + spell_attr = highlight_attr[spell_hlf]; + } + + if (cap_col > 0) { + if (p != prev_ptr + && (p - nextline) + cap_col >= nextline_idx) { + // Remember that the word in the next line + // must start with a capital. + capcol_lnum = lnum + 1; + cap_col = (int)((p - nextline) + cap_col + - nextline_idx); + } else { + // Compute the actual column. + cap_col += (int)(prev_ptr - line); + } + } + } + } + if (spell_attr != 0) { + if (!attr_pri) { + char_attr = hl_combine_attr(char_attr, spell_attr); + } else { + char_attr = hl_combine_attr(spell_attr, char_attr); + } + } + + if (wp->w_buffer->terminal) { + char_attr = hl_combine_attr(term_attrs[vcol], char_attr); + } + + if (has_decor && v > 0) { + bool selected = (area_active || (area_highlighting && noinvcur + && (colnr_T)vcol == wp->w_virtcol)); + int extmark_attr = decor_redraw_col(wp->w_buffer, (colnr_T)v - 1, off, + selected, &decor_state); + if (extmark_attr != 0) { + if (!attr_pri) { + char_attr = hl_combine_attr(char_attr, extmark_attr); + } else { + char_attr = hl_combine_attr(extmark_attr, char_attr); + } + } + + decor_conceal = decor_state.conceal; + if (decor_conceal && decor_state.conceal_char) { + decor_conceal = 2; // really?? + } + } + + // Found last space before word: check for line break. + if (wp->w_p_lbr && c0 == c && vim_isbreak(c) + && !vim_isbreak((int)(*ptr))) { + int mb_off = utf_head_off(line, ptr - 1); + char_u *p = ptr - (mb_off + 1); + chartabsize_T cts; + + 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 + // space for it again. + if (vcol == vcol_sbr) { + n_extra -= mb_charlen(get_showbreak_value(wp)); + if (n_extra < 0) { + n_extra = 0; + } + } + + if (c == TAB && n_extra + col > grid->cols) { + n_extra = tabstop_padding((colnr_T)vcol, wp->w_buffer->b_p_ts, + wp->w_buffer->b_p_vts_array) - 1; + } + c_extra = mb_off > 0 ? MB_FILLER_CHAR : ' '; + c_final = NUL; + if (ascii_iswhite(c)) { + if (c == TAB) { + // See "Tab alignment" below. + FIX_FOR_BOGUSCOLS; + } + if (!wp->w_p_list) { + c = ' '; + } + } + clear_chartabsize_arg(&cts); + } + + in_multispace = c == ' ' && ((ptr > line + 1 && ptr[-2] == ' ') || *ptr == ' '); + if (!in_multispace) { + multispace_pos = 0; + } + + // 'list': Change char 160 to 'nbsp' and space to 'space'. + // But not when the character is followed by a composing + // character (use mb_l to check that). + if (wp->w_p_list + && ((((c == 160 && mb_l == 1) + || (mb_utf8 + && ((mb_c == 160 && mb_l == 2) + || (mb_c == 0x202f && mb_l == 3)))) + && wp->w_p_lcs_chars.nbsp) + || (c == ' ' + && mb_l == 1 + && (wp->w_p_lcs_chars.space + || (in_multispace && wp->w_p_lcs_chars.multispace != NULL)) + && ptr - line >= leadcol + && ptr - line <= trailcol))) { + if (in_multispace && wp->w_p_lcs_chars.multispace != NULL) { + c = wp->w_p_lcs_chars.multispace[multispace_pos++]; + if (wp->w_p_lcs_chars.multispace[multispace_pos] == NUL) { + multispace_pos = 0; + } + } else { + c = (c == ' ') ? wp->w_p_lcs_chars.space : wp->w_p_lcs_chars.nbsp; + } + n_attr = 1; + extra_attr = win_hl_attr(wp, HLF_0); + saved_attr2 = char_attr; // save current attr + mb_c = c; + mb_utf8 = check_mb_utf8(&c, u8cc); + } + + if (c == ' ' && ((trailcol != MAXCOL && ptr > line + trailcol) + || (leadcol != 0 && ptr < line + leadcol))) { + if (leadcol != 0 && in_multispace && ptr < line + leadcol + && wp->w_p_lcs_chars.leadmultispace != NULL) { + c = wp->w_p_lcs_chars.leadmultispace[multispace_pos++]; + if (wp->w_p_lcs_chars.leadmultispace[multispace_pos] == NUL) { + multispace_pos = 0; + } + } else if (ptr > line + trailcol && wp->w_p_lcs_chars.trail) { + c = wp->w_p_lcs_chars.trail; + } else if (ptr < line + leadcol && wp->w_p_lcs_chars.lead) { + c = wp->w_p_lcs_chars.lead; + } else if (leadcol != 0 && wp->w_p_lcs_chars.space) { + c = wp->w_p_lcs_chars.space; + } + + n_attr = 1; + extra_attr = win_hl_attr(wp, HLF_0); + saved_attr2 = char_attr; // save current attr + mb_c = c; + mb_utf8 = check_mb_utf8(&c, u8cc); + } + } + + // Handling of non-printable characters. + if (!vim_isprintc(c)) { + // when getting a character from the file, we may have to + // turn it into something else on the way to putting it on the screen. + 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); + + // Only adjust the tab_len, when at the first column after the + // showbreak value was drawn. + if (*sbr != NUL && vcol == vcol_sbr && wp->w_p_wrap) { + vcol_adjusted = vcol - mb_charlen(sbr); + } + // tab amount depends on current column + tab_len = tabstop_padding((colnr_T)vcol_adjusted, + wp->w_buffer->b_p_ts, + wp->w_buffer->b_p_vts_array) - 1; + + if (!wp->w_p_lbr || !wp->w_p_list) { + n_extra = tab_len; + } else { + char_u *p; + int i; + int saved_nextra = n_extra; + + if (vcol_off > 0) { + // there are characters to conceal + tab_len += vcol_off; + } + // boguscols before FIX_FOR_BOGUSCOLS macro from above. + if (wp->w_p_lcs_chars.tab1 && old_boguscols > 0 + && n_extra > tab_len) { + tab_len += n_extra - tab_len; + } + + // If n_extra > 0, it gives the number of chars + // to use for a tab, else we need to calculate the width + // for a tab. + int len = (tab_len * utf_char2len(wp->w_p_lcs_chars.tab2)); + if (wp->w_p_lcs_chars.tab3) { + len += utf_char2len(wp->w_p_lcs_chars.tab3); + } + if (n_extra > 0) { + len += n_extra - tab_len; + } + c = wp->w_p_lcs_chars.tab1; + p = xmalloc((size_t)len + 1); + memset(p, ' ', (size_t)len); + p[len] = NUL; + xfree(p_extra_free); + p_extra_free = p; + for (i = 0; i < tab_len; i++) { + if (*p == NUL) { + tab_len = i; + break; + } + int lcs = wp->w_p_lcs_chars.tab2; + + // if tab3 is given, use it for the last char + if (wp->w_p_lcs_chars.tab3 && i == tab_len - 1) { + lcs = wp->w_p_lcs_chars.tab3; + } + p += utf_char2bytes(lcs, (char *)p); + n_extra += utf_char2len(lcs) - (saved_nextra > 0 ? 1 : 0); + } + p_extra = p_extra_free; + + // n_extra will be increased by FIX_FOX_BOGUSCOLS + // macro below, so need to adjust for that here + if (vcol_off > 0) { + n_extra -= vcol_off; + } + } + + { + int vc_saved = vcol_off; + + // Tab alignment should be identical regardless of + // 'conceallevel' value. So tab compensates of all + // previous concealed characters, and thus resets + // vcol_off and boguscols accumulated so far in the + // line. Note that the tab can be longer than + // 'tabstop' when there are concealed characters. + FIX_FOR_BOGUSCOLS; + + // Make sure, the highlighting for the tab char will be + // correctly set further below (effectively reverts the + // FIX_FOR_BOGSUCOLS macro). + if (n_extra == tab_len + vc_saved && wp->w_p_list + && wp->w_p_lcs_chars.tab1) { + tab_len += vc_saved; + } + } + + mb_utf8 = false; // don't draw as UTF-8 + if (wp->w_p_list) { + c = (n_extra == 0 && wp->w_p_lcs_chars.tab3) + ? wp->w_p_lcs_chars.tab3 + : wp->w_p_lcs_chars.tab1; + if (wp->w_p_lbr) { + c_extra = NUL; // using p_extra from above + } else { + c_extra = wp->w_p_lcs_chars.tab2; + } + c_final = wp->w_p_lcs_chars.tab3; + n_attr = tab_len + 1; + extra_attr = win_hl_attr(wp, HLF_0); + saved_attr2 = char_attr; // save current attr + mb_c = c; + mb_utf8 = check_mb_utf8(&c, u8cc); + } else { + c_final = NUL; + c_extra = ' '; + c = ' '; + } + } else if (c == NUL + && (wp->w_p_list + || ((fromcol >= 0 || fromcol_prev >= 0) + && tocol > vcol + && VIsual_mode != Ctrl_V + && (wp->w_p_rl ? (col >= 0) : (col < grid->cols)) + && !(noinvcur + && lnum == wp->w_cursor.lnum + && (colnr_T)vcol == wp->w_virtcol))) + && lcs_eol_one > 0) { + // Display a '$' after the line or highlight an extra + // character if the line break is included. + // For a diff line the highlighting continues after the "$". + if (diff_hlf == (hlf_T)0 + && line_attr == 0 + && line_attr_lowprio == 0) { + // In virtualedit, visual selections may extend beyond end of line + if (area_highlighting && virtual_active() + && tocol != MAXCOL && vcol < tocol) { + n_extra = 0; + } else { + p_extra = at_end_str; + n_extra = 1; + c_extra = NUL; + c_final = NUL; + } + } + if (wp->w_p_list && wp->w_p_lcs_chars.eol > 0) { + c = wp->w_p_lcs_chars.eol; + } else { + c = ' '; + } + lcs_eol_one = -1; + ptr--; // put it back at the NUL + extra_attr = win_hl_attr(wp, HLF_AT); + n_attr = 1; + mb_c = c; + mb_utf8 = check_mb_utf8(&c, u8cc); + } else if (c != NUL) { + p_extra = transchar_buf(wp->w_buffer, c); + if (n_extra == 0) { + n_extra = byte2cells(c) - 1; + } + if ((dy_flags & DY_UHEX) && wp->w_p_rl) { + rl_mirror(p_extra); // reverse "<12>" + } + c_extra = NUL; + c_final = NUL; + if (wp->w_p_lbr) { + char_u *p; + + c = *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) + p[n_extra] = NUL; + xfree(p_extra_free); + p_extra_free = p_extra = p; + } else { + n_extra = byte2cells(c) - 1; + c = *p_extra++; + } + n_attr = n_extra + 1; + extra_attr = win_hl_attr(wp, HLF_8); + saved_attr2 = char_attr; // save current attr + mb_utf8 = false; // don't draw as UTF-8 + } else if (VIsual_active + && (VIsual_mode == Ctrl_V || VIsual_mode == 'v') + && virtual_active() + && tocol != MAXCOL + && vcol < tocol + && (wp->w_p_rl ? (col >= 0) : (col < grid->cols))) { + c = ' '; + ptr--; // put it back at the NUL + } + } + + if (wp->w_p_cole > 0 + && (wp != curwin || lnum != wp->w_cursor.lnum || conceal_cursor_line(wp)) + && ((syntax_flags & HL_CONCEAL) != 0 || has_match_conc > 0 || decor_conceal > 0) + && !(lnum_in_visual_area && vim_strchr(wp->w_p_cocu, 'v') == NULL)) { + char_attr = conceal_attr; + if (((prev_syntax_id != syntax_seqnr && (syntax_flags & HL_CONCEAL) != 0) + || has_match_conc > 1 || decor_conceal > 1) + && (syn_get_sub_char() != NUL + || (has_match_conc && match_conc) + || (decor_conceal && decor_state.conceal_char) + || wp->w_p_cole == 1) + && wp->w_p_cole != 3) { + // First time at this concealed item: display one + // character. + if (has_match_conc && match_conc) { + c = match_conc; + } else if (decor_conceal && decor_state.conceal_char) { + c = decor_state.conceal_char; + if (decor_state.conceal_attr) { + char_attr = decor_state.conceal_attr; + } + } else if (syn_get_sub_char() != NUL) { + c = syn_get_sub_char(); + } else if (wp->w_p_lcs_chars.conceal != NUL) { + c = wp->w_p_lcs_chars.conceal; + } else { + c = ' '; + } + + prev_syntax_id = syntax_seqnr; + + if (n_extra > 0) { + vcol_off += n_extra; + } + vcol += n_extra; + if (wp->w_p_wrap && n_extra > 0) { + if (wp->w_p_rl) { + col -= n_extra; + boguscols -= n_extra; + } else { + boguscols += n_extra; + col += n_extra; + } + } + n_extra = 0; + n_attr = 0; + } else if (n_skip == 0) { + is_concealing = true; + n_skip = 1; + } + mb_c = c; + mb_utf8 = check_mb_utf8(&c, u8cc); + } else { + prev_syntax_id = 0; + is_concealing = false; + } + + if (n_skip > 0 && did_decrement_ptr) { + // not showing the '>', put pointer back to avoid getting stuck + ptr++; + } + } // end of printing from buffer content + + // In the cursor line and we may be concealing characters: correct + // the cursor column when we reach its position. + if (!did_wcol && draw_state == WL_LINE + && wp == curwin && lnum == wp->w_cursor.lnum + && conceal_cursor_line(wp) + && (int)wp->w_virtcol <= vcol + n_skip) { + if (wp->w_p_rl) { + wp->w_wcol = grid->cols - col + boguscols - 1; + } else { + wp->w_wcol = col - boguscols; + } + wp->w_wrow = row; + did_wcol = true; + wp->w_valid |= VALID_WCOL|VALID_WROW|VALID_VIRTCOL; + } + + // Don't override visual selection highlighting. + if (n_attr > 0 && draw_state == WL_LINE && !search_attr_from_match) { + char_attr = hl_combine_attr(char_attr, extra_attr); + } + + // Handle the case where we are in column 0 but not on the first + // character of the line and the user wants us to show us a + // special character (via 'listchars' option "precedes:<char>". + if (lcs_prec_todo != NUL + && 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 + && c != NUL) { + c = wp->w_p_lcs_chars.prec; + lcs_prec_todo = NUL; + if (utf_char2cells(mb_c) > 1) { + // Double-width character being overwritten by the "precedes" + // character, need to fill up half the character. + c_extra = MB_FILLER_CHAR; + c_final = NUL; + n_extra = 1; + n_attr = 2; + extra_attr = win_hl_attr(wp, HLF_AT); + } + mb_c = c; + mb_utf8 = check_mb_utf8(&c, u8cc); + saved_attr3 = char_attr; // save current attr + char_attr = win_hl_attr(wp, HLF_AT); // overwriting char_attr + n_attr3 = 1; + } + + // At end of the text line or just after the last character. + if (c == NUL && eol_hl_off == 0) { + // 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); + + // Invert at least one char, used for Visual and empty line or + // highlight match at end of line. If it's beyond the last + // char on the screen, just overwrite that one (tricky!) Not + // needed when a '$' was displayed for 'list'. + if (wp->w_p_lcs_chars.eol == lcs_eol_one + && ((area_attr != 0 && vcol == fromcol + && (VIsual_mode != Ctrl_V + || lnum == VIsual.lnum + || lnum == curwin->w_cursor.lnum)) + // highlight 'hlsearch' match at end of line + || prevcol_hl_flag)) { + int n = 0; + + if (wp->w_p_rl) { + if (col < 0) { + n = 1; + } + } else { + if (col >= grid->cols) { + n = -1; + } + } + if (n != 0) { + // At the window boundary, highlight the last character + // instead (better than nothing). + off += n; + col += n; + } else { + // Add a blank character to highlight. + schar_from_ascii(linebuf_char[off], ' '); + } + 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); + } + + int eol_attr = char_attr; + if (cul_attr) { + eol_attr = hl_combine_attr(cul_attr, eol_attr); + } + linebuf_attr[off] = eol_attr; + if (wp->w_p_rl) { + col--; + off--; + } else { + col++; + off++; + } + vcol++; + eol_hl_off = 1; + } + // Highlight 'cursorcolumn' & 'colorcolumn' past end of the line. + if (wp->w_p_wrap) { + v = wp->w_skipcol; + } else { + v = wp->w_leftcol; + } + + // check if line ends before left margin + if (vcol < v + col - win_col_off(wp)) { + vcol = v + col - win_col_off(wp); + } + // Get rid of the boguscols now, we want to draw until the right + // edge for 'cursorcolumn'. + col -= boguscols; + // boguscols = 0; // Disabled because value never read after this + + if (draw_color_col) { + draw_color_col = advance_color_col((int)VCOL_HLC, &color_cols); + } + + bool has_virttext = false; + // Make sure alignment is the same regardless + // if listchars=eol:X is used or not. + int eol_skip = (wp->w_p_lcs_chars.eol == lcs_eol_one && eol_hl_off == 0 + ? 1 : 0); + + if (has_decor) { + has_virttext = decor_redraw_eol(wp->w_buffer, &decor_state, &line_attr, + col + eol_skip); + } + + if (((wp->w_p_cuc + && (int)wp->w_virtcol >= VCOL_HLC - eol_hl_off + && (int)wp->w_virtcol < + (long)grid->cols * (row - startrow + 1) + v + && lnum != wp->w_cursor.lnum) + || draw_color_col || line_attr_lowprio || line_attr + || diff_hlf != (hlf_T)0 || has_virttext)) { + int rightmost_vcol = 0; + int i; + + if (wp->w_p_cuc) { + rightmost_vcol = wp->w_virtcol; + } + + if (draw_color_col) { + // determine rightmost colorcolumn to possibly draw + for (i = 0; color_cols[i] >= 0; i++) { + if (rightmost_vcol < color_cols[i]) { + rightmost_vcol = color_cols[i]; + } + } + } + + int cuc_attr = win_hl_attr(wp, HLF_CUC); + int mc_attr = win_hl_attr(wp, HLF_MC); + + int diff_attr = 0; + if (diff_hlf == HLF_TXD) { + diff_hlf = HLF_CHD; + } + if (diff_hlf != 0) { + diff_attr = win_hl_attr(wp, (int)diff_hlf); + } + + int base_attr = hl_combine_attr(line_attr_lowprio, diff_attr); + if (base_attr || line_attr || has_virttext) { + rightmost_vcol = INT_MAX; + } + + int col_stride = wp->w_p_rl ? -1 : 1; + + while (wp->w_p_rl ? col >= 0 : col < grid->cols) { + schar_from_ascii(linebuf_char[off], ' '); + col += col_stride; + if (draw_color_col) { + draw_color_col = advance_color_col((int)VCOL_HLC, &color_cols); + } + + int col_attr = base_attr; + + if (wp->w_p_cuc && VCOL_HLC == (long)wp->w_virtcol) { + col_attr = cuc_attr; + } else if (draw_color_col && VCOL_HLC == *color_cols) { + col_attr = mc_attr; + } + + col_attr = hl_combine_attr(col_attr, line_attr); + + linebuf_attr[off] = col_attr; + off += col_stride; + + if (VCOL_HLC >= rightmost_vcol) { + break; + } + + vcol += 1; + } + } + + // TODO(bfredl): integrate with the common beyond-the-end-loop + if (wp->w_buffer->terminal) { + // terminal buffers may need to highlight beyond the end of the + // logical line + int n = wp->w_p_rl ? -1 : 1; + while (col >= 0 && col < grid->cols) { + schar_from_ascii(linebuf_char[off], ' '); + linebuf_attr[off] = vcol >= TERM_ATTRS_MAX ? 0 : term_attrs[vcol]; + off += n; + vcol += n; + col += n; + } + } + + 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); + row++; + + // Update w_cline_height and w_cline_folded if the cursor line was + // updated (saves a call to plines_win() later). + if (wp == curwin && lnum == curwin->w_cursor.lnum) { + curwin->w_cline_row = startrow; + curwin->w_cline_height = row - startrow; + curwin->w_cline_folded = foldinfo.fi_lines > 0; + curwin->w_valid |= (VALID_CHEIGHT|VALID_CROW); + conceal_cursor_used = conceal_cursor_line(curwin); + } + break; + } + + // Show "extends" character from 'listchars' if beyond the line end and + // 'list' is set. + if (wp->w_p_lcs_chars.ext != NUL + && draw_state == WL_LINE + && wp->w_p_list + && !wp->w_p_wrap + && filler_todo <= 0 + && (wp->w_p_rl ? col == 0 : col == grid->cols - 1) + && !has_fold + && (*ptr != NUL + || lcs_eol_one > 0 + || (n_extra && (c_extra != NUL || *p_extra != NUL)))) { + c = wp->w_p_lcs_chars.ext; + char_attr = win_hl_attr(wp, HLF_AT); + mb_c = c; + mb_utf8 = check_mb_utf8(&c, u8cc); + } + + // advance to the next 'colorcolumn' + if (draw_color_col) { + draw_color_col = advance_color_col((int)VCOL_HLC, &color_cols); + } + + // Highlight the cursor column if 'cursorcolumn' is set. But don't + // highlight the cursor position itself. + // Also highlight the 'colorcolumn' if it is different than + // 'cursorcolumn' + // Also highlight the 'colorcolumn' if 'breakindent' and/or 'showbreak' + // options are set + vcol_save_attr = -1; + if ((draw_state == WL_LINE + || draw_state == WL_BRI + || draw_state == WL_SBR) + && !lnum_in_visual_area + && search_attr == 0 + && area_attr == 0 + && filler_todo <= 0) { + if (wp->w_p_cuc && VCOL_HLC == (long)wp->w_virtcol + && lnum != wp->w_cursor.lnum) { + vcol_save_attr = char_attr; + char_attr = hl_combine_attr(win_hl_attr(wp, HLF_CUC), char_attr); + } else if (draw_color_col && VCOL_HLC == *color_cols) { + vcol_save_attr = char_attr; + char_attr = hl_combine_attr(win_hl_attr(wp, HLF_MC), char_attr); + } + } + + // Apply lowest-priority line attr now, so everything can override it. + if (draw_state == WL_LINE) { + char_attr = hl_combine_attr(line_attr_lowprio, char_attr); + } + + // Store character to be displayed. + // Skip characters that are left of the screen for 'nowrap'. + vcol_prev = vcol; + if (draw_state < WL_LINE || n_skip <= 0) { + // + // Store the character. + // + if (wp->w_p_rl && utf_char2cells(mb_c) > 1) { + // A double-wide character is: put first half in left cell. + off--; + col--; + } + if (mb_utf8) { + schar_from_cc(linebuf_char[off], mb_c, u8cc); + } else { + schar_from_ascii(linebuf_char[off], (char)c); + } + if (multi_attr) { + linebuf_attr[off] = multi_attr; + multi_attr = 0; + } else { + linebuf_attr[off] = char_attr; + } + + if (utf_char2cells(mb_c) > 1) { + // Need to fill two screen columns. + off++; + col++; + // UTF-8: Put a 0 in the second screen char. + linebuf_char[off][0] = 0; + if (draw_state > WL_NR && filler_todo <= 0) { + vcol++; + } + // When "tocol" is halfway through a character, set it to the end of + // the character, otherwise highlighting won't stop. + if (tocol == vcol) { + tocol++; + } + if (wp->w_p_rl) { + // now it's time to backup one cell + off--; + col--; + } + } + if (wp->w_p_rl) { + off--; + col--; + } else { + off++; + col++; + } + } else if (wp->w_p_cole > 0 && is_concealing) { + n_skip--; + vcol_off++; + if (n_extra > 0) { + vcol_off += n_extra; + } + if (wp->w_p_wrap) { + // Special voodoo required if 'wrap' is on. + // + // Advance the column indicator to force the line + // drawing to wrap early. This will make the line + // take up the same screen space when parts are concealed, + // so that cursor line computations aren't messed up. + // + // To avoid the fictitious advance of 'col' causing + // trailing junk to be written out of the screen line + // we are building, 'boguscols' keeps track of the number + // of bad columns we have advanced. + if (n_extra > 0) { + vcol += n_extra; + if (wp->w_p_rl) { + col -= n_extra; + boguscols -= n_extra; + } else { + col += n_extra; + boguscols += n_extra; + } + n_extra = 0; + n_attr = 0; + } + + if (utf_char2cells(mb_c) > 1) { + // Need to fill two screen columns. + if (wp->w_p_rl) { + boguscols--; + col--; + } else { + boguscols++; + col++; + } + } + + if (wp->w_p_rl) { + boguscols--; + col--; + } else { + boguscols++; + col++; + } + } else { + if (n_extra > 0) { + vcol += n_extra; + n_extra = 0; + n_attr = 0; + } + } + } else { + n_skip--; + } + + // Only advance the "vcol" when after the 'number' or 'relativenumber' + // column. + if (draw_state > WL_NR + && filler_todo <= 0) { + vcol++; + } + + if (vcol_save_attr >= 0) { + char_attr = vcol_save_attr; + } + + // restore attributes after "predeces" in 'listchars' + if (draw_state > WL_NR && n_attr3 > 0 && --n_attr3 == 0) { + char_attr = saved_attr3; + } + + // restore attributes after last 'listchars' or 'number' char + if (n_attr > 0 && draw_state == WL_LINE && --n_attr == 0) { + char_attr = saved_attr2; + } + + // 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 + && (draw_state != WL_LINE + || *ptr != NUL + || filler_todo > 0 + || (wp->w_p_list && wp->w_p_lcs_chars.eol != NUL + && p_extra != at_end_str) + || (n_extra != 0 + && (c_extra != NUL || *p_extra != NUL)))) { + bool wrap = wp->w_p_wrap // Wrapping enabled. + && filler_todo <= 0 // Not drawing diff filler lines. + && lcs_eol_one != -1 // Haven't printed the lcs_eol character. + && row != endrow - 1 // Not the last line being displayed. + && (grid->cols == Columns // Window spans the width of the screen, + || ui_has(kUIMultigrid)) // or has dedicated grid. + && !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, offset); + } + } else { + draw_virt_text(wp, buf, win_col_offset, &draw_col, grid->cols, row); + } + + grid_put_linebuf(grid, row, 0, draw_col, grid->cols, wp->w_p_rl, wp, bg_attr, wrap); + if (wrap) { + ScreenGrid *current_grid = grid; + int current_row = row, dummy_col = 0; // dummy_col unused + grid_adjust(¤t_grid, ¤t_row, &dummy_col); + + // Force a redraw of the first column of the next line. + current_grid->attrs[current_grid->line_offset[current_row + 1]] = -1; + + // Remember that the line wraps, used for modeless copy. + current_grid->line_wraps[current_row] = true; + } + + boguscols = 0; + row++; + + // When not wrapping and finished diff lines, or when displayed + // '$' and highlighting until last column, break here. + if ((!wp->w_p_wrap && filler_todo <= 0) || lcs_eol_one == -1) { + break; + } + + // When the window is too narrow draw all "@" lines. + if (draw_state != WL_LINE && filler_todo <= 0) { + win_draw_end(wp, '@', ' ', true, row, wp->w_grid.rows, HLF_AT); + row = endrow; + } + + // When line got too long for screen break here. + if (row == endrow) { + row++; + break; + } + + col = 0; + off = 0; + if (wp->w_p_rl) { + col = grid->cols - 1; // col is not used if breaking! + off += col; + } + + // reset the drawing state for the start of a wrapped line + draw_state = WL_START; + saved_n_extra = n_extra; + saved_p_extra = p_extra; + saved_c_extra = c_extra; + saved_c_final = c_final; + saved_char_attr = char_attr; + n_extra = 0; + lcs_prec_todo = wp->w_p_lcs_chars.prec; + if (filler_todo <= 0) { + need_showbreak = true; + } + filler_todo--; + // 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)) { + break; + } + } + } // for every character in the line + + // After an empty line check first word for capital. + if (*skipwhite((char *)line) == NUL) { + capcol_lnum = lnum + 1; + cap_col = 0; + } + + kv_destroy(virt_lines); + xfree(p_extra_free); + return row; +} diff --git a/src/nvim/drawline.h b/src/nvim/drawline.h new file mode 100644 index 0000000000..e50969983e --- /dev/null +++ b/src/nvim/drawline.h @@ -0,0 +1,24 @@ +#ifndef NVIM_DRAWLINE_H +#define NVIM_DRAWLINE_H + +#include "nvim/decoration_provider.h" +#include "nvim/fold.h" +#include "nvim/screen.h" + +// Maximum columns for terminal highlight attributes +#define TERM_ATTRS_MAX 1024 + +typedef struct { + NS ns_id; + uint64_t mark_id; + int win_row; + int win_col; +} WinExtmark; +EXTERN kvec_t(WinExtmark) win_extmark_arr INIT(= KV_INITIAL_VALUE); + +EXTERN bool conceal_cursor_used INIT(= false); + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "drawline.h.generated.h" +#endif +#endif // NVIM_DRAWLINE_H diff --git a/src/nvim/drawscreen.c b/src/nvim/drawscreen.c new file mode 100644 index 0000000000..29ff261461 --- /dev/null +++ b/src/nvim/drawscreen.c @@ -0,0 +1,2176 @@ +// 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 + +// drawscreen.c: Code for updating all the windows on the screen. +// This is the top level, drawline.c is the middle and grid.c/screen.c the lower level. + +// update_screen() is the function that updates all windows and status lines. +// It is called from the main loop when must_redraw is non-zero. It may be +// called from other places when an immediate screen update is needed. +// +// The part of the buffer that is displayed in a window is set with: +// - w_topline (first buffer line in window) +// - w_topfill (filler lines above the first line) +// - w_leftcol (leftmost window cell in window), +// - w_skipcol (skipped window cells of first line) +// +// Commands that only move the cursor around in a window, do not need to take +// action to update the display. The main loop will check if w_topline is +// valid and update it (scroll the window) when needed. +// +// Commands that scroll a window change w_topline and must call +// check_cursor() to move the cursor into the visible part of the window, and +// call redraw_later(wp, UPD_VALID) to have the window displayed by update_screen() +// later. +// +// Commands that change text in the buffer must call changed_bytes() or +// changed_lines() to mark the area that changed and will require updating +// later. The main loop will call update_screen(), which will update each +// window that shows the changed buffer. This assumes text above the change +// can remain displayed as it is. Text after the change may need updating for +// scrolling, folding and syntax highlighting. +// +// Commands that change how a window is displayed (e.g., setting 'list') or +// invalidate the contents of a window in another way (e.g., change fold +// settings), must call redraw_later(wp, UPD_NOT_VALID) to have the whole window +// redisplayed by update_screen() later. +// +// Commands that change how a buffer is displayed (e.g., setting 'tabstop') +// must call redraw_curbuf_later(UPD_NOT_VALID) to have all the windows for the +// buffer redisplayed by update_screen() later. +// +// Commands that change highlighting and possibly cause a scroll too must call +// redraw_later(wp, UPD_SOME_VALID) to update the whole window but still use +// scrolling to avoid redrawing everything. But the length of displayed lines +// must not change, use UPD_NOT_VALID then. +// +// Commands that move the window position must call redraw_later(wp, UPD_NOT_VALID). +// TODO(neovim): should minimize redrawing by scrolling when possible. +// +// Commands that change everything (e.g., resizing the screen) must call +// redraw_all_later(UPD_NOT_VALID) or redraw_all_later(UPD_CLEAR). +// +// Things that are handled indirectly: +// - When messages scroll the screen up, msg_scrolled will be set and +// update_screen() called to redraw. + +#include <assert.h> +#include <inttypes.h> +#include <stdbool.h> +#include <string.h> + +#include "nvim/buffer.h" +#include "nvim/charset.h" +#include "nvim/cmdexpand.h" +#include "nvim/diff.h" +#include "nvim/drawscreen.h" +#include "nvim/ex_getln.h" +#include "nvim/grid.h" +#include "nvim/highlight.h" +#include "nvim/highlight_group.h" +#include "nvim/insexpand.h" +#include "nvim/match.h" +#include "nvim/move.h" +#include "nvim/option.h" +#include "nvim/plines.h" +#include "nvim/popupmenu.h" +#include "nvim/profile.h" +#include "nvim/regexp.h" +#include "nvim/statusline.h" +#include "nvim/syntax.h" +#include "nvim/ui_compositor.h" +#include "nvim/undo.h" +#include "nvim/version.h" +#include "nvim/window.h" + +/// corner value flags for hsep_connected and vsep_connected +typedef enum { + WC_TOP_LEFT = 0, + WC_TOP_RIGHT, + WC_BOTTOM_LEFT, + WC_BOTTOM_RIGHT, +} WindowCorner; + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "drawscreen.c.generated.h" +#endif + +static bool redraw_popupmenu = false; +static bool msg_grid_invalid = false; +static bool resizing = false; + +static char *provider_err = NULL; + +/// Check if the cursor line needs to be redrawn because of 'concealcursor'. +/// +/// When cursor is moved at the same time, both lines will be redrawn regardless. +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); + } +} + +/// Resize the screen to Rows and Columns. +/// +/// Allocate default_grid.chars[] and other grid arrays. +/// +/// There may be some time between setting Rows and Columns and (re)allocating +/// default_grid arrays. This happens when starting up and when +/// (manually) changing the screen size. Always use default_grid.rows and +/// 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. +void screenalloc(void) +{ + // It's possible that we produce an out-of-memory message below, which + // will cause this function to be called again. To break the loop, just + // return here. + if (resizing) { + return; + } + resizing = true; + + int retry_count = 0; + +retry: + // Allocation of the screen buffers is done only when the size changes and + // when Rows and Columns have been set and we have started doing full + // screen stuff. + if ((default_grid.chars != NULL + && Rows == default_grid.rows + && Columns == default_grid.cols) + || Rows == 0 + || Columns == 0 + || (!full_screen && default_grid.chars == NULL)) { + resizing = false; + return; + } + + // Note that the window sizes are updated before reallocating the arrays, + // thus we must not redraw here! + RedrawingDisabled++; + + // 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; + } + + win_new_screensize(); // fit the windows in the new sized screen + + comp_col(); // recompute columns for shown command and ruler + + // We're changing the size of the screen. + // - Allocate new arrays for default_grid + // - Move lines from the old arrays into the new arrays, clear extra + // lines (unless the screen is going to be cleared). + // - Free the old arrays. + // + // If anything fails, make grid arrays NULL, so we don't do anything! + // Continuing with the old arrays may result in a crash, because the + // 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; + + default_grid.comp_height = Rows; + default_grid.comp_width = Columns; + + default_grid.row_offset = 0; + default_grid.col_offset = 0; + default_grid.handle = DEFAULT_GRID_HANDLE; + + must_redraw = UPD_CLEAR; // need to clear the screen later + + RedrawingDisabled--; + + // Do not apply autocommands more than 3 times to avoid an endless loop + // in case applying autocommands always changes Rows or Columns. + if (starting == 0 && ++retry_count <= 3) { + apply_autocmds(EVENT_VIMRESIZED, NULL, NULL, false, curbuf); + // In rare cases, autocommands may have altered Rows or Columns, + // jump back to check if we need to allocate the screen again. + goto retry; + } + + resizing = false; +} + +void screenclear(void) +{ + check_for_delay(false); + screenalloc(); // allocate screen buffers if size changed + + int i; + + if (starting == NO_SCREEN || default_grid.chars == NULL) { + return; + } + + // blank out the default grid + for (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; + } + + ui_call_grid_clear(1); // clear the display + ui_comp_set_screen_valid(true); + + ns_hl_fast = -1; + + clear_cmdline = false; + mode_displayed = false; + + redraw_all_later(UPD_NOT_VALID); + redraw_cmdline = true; + redraw_tabline = true; + redraw_popupmenu = true; + pum_invalidate(); + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_floating) { + wp->w_redr_type = UPD_CLEAR; + } + } + if (must_redraw == UPD_CLEAR) { + must_redraw = UPD_NOT_VALID; // no need to clear again + } + compute_cmdrow(); + msg_row = cmdline_row; // put cursor on last line for messages + msg_col = 0; + msg_scrolled = 0; // can't scroll back + msg_didany = false; + msg_didout = false; + if (HL_ATTR(HLF_MSG) > 0 && msg_use_grid() && msg_grid.chars) { + grid_invalidate(&msg_grid); + msg_grid_validate(); + msg_grid_invalid = false; + clear_cmdline = true; + } +} + +/// Set dimensions of the Nvim application "screen". +void screen_resize(int width, int height) +{ + // Avoid recursiveness, can happen when setting the window size causes + // another window-changed signal. + if (updating_screen || resizing_screen) { + return; + } + + if (width < 0 || height < 0) { // just checking... + return; + } + + if (State == MODE_HITRETURN || State == MODE_SETWSIZE) { + // postpone the resizing + State = MODE_SETWSIZE; + return; + } + + // curwin->w_buffer can be NULL when we are closing a window and the + // buffer has already been closed and removing a scrollbar causes a resize + // event. Don't resize then, it will happen after entering another buffer. + if (curwin->w_buffer == NULL) { + return; + } + + resizing_screen = true; + + Rows = height; + Columns = width; + check_screensize(); + int max_p_ch = Rows - min_rows() + 1; + if (!ui_has(kUIMessages) && p_ch > 0 && p_ch > max_p_ch) { + p_ch = max_p_ch ? max_p_ch : 1; + } + height = Rows; + width = Columns; + p_lines = Rows; + p_columns = Columns; + ui_call_grid_resize(1, width, height); + + /// The window layout used to be adjusted here, but it now happens in + /// screenalloc() (also invoked from screenclear()). That is because the + /// recursize "resizing_screen" check above may skip this, but not screenalloc(). + + if (State != MODE_ASKMORE && State != MODE_EXTERNCMD && State != MODE_CONFIRM) { + screenclear(); + } + + if (starting != NO_SCREEN) { + maketitle(); + + changed_line_abv_curs(); + invalidate_botline(); + + // We only redraw when it's needed: + // - While at the more prompt or executing an external command, don't + // redraw, but position the cursor. + // - While editing the command line, only redraw that. + // - in Ex mode, don't redraw anything. + // - Otherwise, redraw right now, and position the cursor. + // Always need to call update_screen() or screenalloc(), to make + // sure Rows/Columns and the size of the screen is correct! + if (State == MODE_ASKMORE || State == MODE_EXTERNCMD || State == MODE_CONFIRM + || exmode_active) { + screenalloc(); + if (msg_grid.chars) { + msg_grid_validate(); + } + // TODO(bfredl): sometimes messes up the output. Implement clear+redraw + // also for the pager? (or: what if the pager was just a modal window?) + ui_comp_set_screen_valid(true); + repeat_message(); + } else { + if (curwin->w_p_scb) { + do_check_scrollbind(true); + } + if (State & MODE_CMDLINE) { + redraw_popupmenu = false; + update_screen(UPD_NOT_VALID); + redrawcmdline(); + if (pum_drawn()) { + cmdline_pum_display(false); + } + } else { + update_topline(curwin); + if (pum_drawn()) { + // TODO(bfredl): ins_compl_show_pum wants to redraw the screen first. + // For now make sure the nested update_screen(0) won't redraw the + // pum at the old position. Try to untangle this later. + redraw_popupmenu = false; + ins_compl_show_pum(); + } + update_screen(UPD_NOT_VALID); + if (redrawing()) { + setcursor(); + } + } + } + ui_flush(); + } + resizing_screen = false; +} + +/// Redraw the parts of the screen that is marked for redraw. +/// +/// Most code shouldn't call this directly, rather use redraw_later() and +/// and redraw_all_later() to mark parts of the screen as needing a redraw. +/// +/// @param type set to a UPD_NOT_VALID to force redraw of entire screen +int update_screen(int type) +{ + static bool did_intro = false; + bool is_stl_global = global_stl_height() > 0; + + // Don't do anything if the screen structures are (not yet) valid. + // A VimResized autocmd can invoke redrawing in the middle of a resize, + // which would bypass the checks in screen_resize for popupmenu etc. + if (!default_grid.chars || resizing) { + return FAIL; + } + + // May have postponed updating diffs. + if (need_diff_redraw) { + diff_redraw(true); + } + + if (must_redraw) { + if (type < must_redraw) { // use maximal type + type = must_redraw; + } + + // must_redraw is reset here, so that when we run into some weird + // reason to redraw while busy redrawing (e.g., asynchronous + // scrolling), or update_topline() in win_update() will cause a + // scroll, or a decoration provider requires a redraw, the screen + // will be redrawn later or in win_update(). + must_redraw = 0; + } + + // Need to update w_lines[]. + if (curwin->w_lines_valid == 0 && type < UPD_NOT_VALID) { + type = UPD_NOT_VALID; + } + + // Postpone the redrawing when it's not needed and when being called + // recursively. + if (!redrawing() || updating_screen) { + must_redraw = type; + if (type > UPD_INVERTED_ALL) { + curwin->w_lines_valid = 0; // don't use w_lines[].wl_size now + } + return FAIL; + } + updating_screen = 1; + + display_tick++; // let syntax code know we're in a next round of + // display updating + + // Tricky: vim code can reset msg_scrolled behind our back, so need + // separate bookkeeping for now. + if (msg_did_scroll) { + msg_did_scroll = false; + msg_scrolled_at_flush = 0; + } + + if (type >= UPD_CLEAR || !default_grid.valid) { + ui_comp_set_screen_valid(false); + } + + // if the screen was scrolled up when displaying a message, scroll it down + if (msg_scrolled || msg_grid_invalid) { + clear_cmdline = true; + int valid = MAX(Rows - msg_scrollsize(), 0); + if (msg_grid.chars) { + // non-displayed part of msg_grid is considered invalid. + for (int i = 0; i < MIN(msg_scrollsize(), msg_grid.rows); i++) { + grid_clear_line(&msg_grid, msg_grid.line_offset[i], + msg_grid.cols, false); + } + } + if (msg_use_msgsep()) { + msg_grid.throttled = false; + // UPD_CLEAR is already handled + if (type == UPD_NOT_VALID && !ui_has(kUIMultigrid) && msg_scrolled) { + ui_comp_set_screen_valid(false); + for (int i = valid; i < Rows - p_ch; i++) { + grid_clear_line(&default_grid, default_grid.line_offset[i], + Columns, false); + } + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_floating) { + continue; + } + if (W_ENDROW(wp) > valid) { + wp->w_redr_type = MAX(wp->w_redr_type, UPD_NOT_VALID); + } + if (!is_stl_global && W_ENDROW(wp) + wp->w_status_height > valid) { + wp->w_redr_status = true; + } + } + if (is_stl_global && Rows - p_ch - 1 > valid) { + curwin->w_redr_status = true; + } + } + msg_grid_set_pos(Rows - (int)p_ch, false); + msg_grid_invalid = false; + } else if (type != UPD_CLEAR) { + if (msg_scrolled > Rows - 5) { // redrawing is faster + type = UPD_NOT_VALID; + } else { + check_for_delay(false); + grid_ins_lines(&default_grid, 0, msg_scrolled, Rows, 0, Columns); + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_floating) { + continue; + } + if (wp->w_winrow < msg_scrolled) { + if (W_ENDROW(wp) > msg_scrolled + && wp->w_redr_type < UPD_REDRAW_TOP + && wp->w_lines_valid > 0 + && wp->w_topline == wp->w_lines[0].wl_lnum) { + wp->w_upd_rows = msg_scrolled - wp->w_winrow; + wp->w_redr_type = UPD_REDRAW_TOP; + } else { + wp->w_redr_type = UPD_NOT_VALID; + if (wp->w_winrow + wp->w_winbar_height <= msg_scrolled) { + wp->w_redr_status = true; + } + } + } + } + if (is_stl_global && Rows - p_ch - 1 <= msg_scrolled) { + curwin->w_redr_status = true; + } + redraw_cmdline = true; + redraw_tabline = true; + } + } + msg_scrolled = 0; + msg_scrolled_at_flush = 0; + need_wait_return = false; + } + + win_ui_flush(); + msg_ext_check_clear(); + + // reset cmdline_row now (may have been changed temporarily) + compute_cmdrow(); + + bool hl_changed = false; + // Check for changed highlighting + if (need_highlight_changed) { + highlight_changed(); + hl_changed = true; + } + + if (type == UPD_CLEAR) { // first clear screen + screenclear(); // will reset clear_cmdline + cmdline_screen_cleared(); // clear external cmdline state + type = UPD_NOT_VALID; + // must_redraw may be set indirectly, avoid another redraw later + must_redraw = 0; + } else if (!default_grid.valid) { + grid_invalidate(&default_grid); + default_grid.valid = true; + } + + // After disabling msgsep the grid might not have been deallocated yet, + // hence we also need to check msg_grid.chars + if (type == UPD_NOT_VALID && (msg_use_grid() || msg_grid.chars)) { + grid_fill(&default_grid, Rows - (int)p_ch, Rows, 0, Columns, ' ', ' ', 0); + } + + ui_comp_set_screen_valid(true); + + DecorProviders providers; + decor_providers_start(&providers, type, &provider_err); + + // "start" callback could have changed highlights for global elements + if (win_check_ns_hl(NULL)) { + redraw_cmdline = true; + redraw_tabline = true; + } + + if (clear_cmdline) { // going to clear cmdline (done below) + check_for_delay(false); + } + + // Force redraw when width of 'number' or 'relativenumber' column + // changes. + if (curwin->w_redr_type < UPD_NOT_VALID + && curwin->w_nrwidth != ((curwin->w_p_nu || curwin->w_p_rnu) + ? number_width(curwin) : 0)) { + curwin->w_redr_type = UPD_NOT_VALID; + } + + // Only start redrawing if there is really something to do. + if (type == UPD_INVERTED) { + update_curswant(); + } + if (curwin->w_redr_type < type + && !((type == UPD_VALID + && curwin->w_lines[0].wl_valid + && curwin->w_topfill == curwin->w_old_topfill + && curwin->w_botfill == curwin->w_old_botfill + && curwin->w_topline == curwin->w_lines[0].wl_lnum) + || (type == UPD_INVERTED + && VIsual_active + && curwin->w_old_cursor_lnum == curwin->w_cursor.lnum + && curwin->w_old_visual_mode == VIsual_mode + && (curwin->w_valid & VALID_VIRTCOL) + && curwin->w_old_curswant == curwin->w_curswant))) { + curwin->w_redr_type = type; + } + + // Redraw the tab pages line if needed. + if (redraw_tabline || type >= UPD_NOT_VALID) { + update_window_hl(curwin, type >= UPD_NOT_VALID); + FOR_ALL_TABS(tp) { + if (tp != curtab) { + update_window_hl(tp->tp_curwin, type >= UPD_NOT_VALID); + } + } + draw_tabline(); + } + + // Correct stored syntax highlighting info for changes in each displayed + // buffer. Each buffer must only be done once. + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + update_window_hl(wp, type >= UPD_NOT_VALID || hl_changed); + + buf_T *buf = wp->w_buffer; + if (buf->b_mod_set) { + if (buf->b_mod_tick_syn < display_tick + && syntax_present(wp)) { + syn_stack_apply_changes(buf); + buf->b_mod_tick_syn = display_tick; + } + + if (buf->b_mod_tick_decor < display_tick) { + decor_providers_invoke_buf(buf, &providers, &provider_err); + buf->b_mod_tick_decor = display_tick; + } + } + } + + // Go from top to bottom through the windows, redrawing the ones that need it. + bool did_one = false; + screen_search_hl.rm.regprog = NULL; + + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_redr_type == UPD_CLEAR && wp->w_floating && wp->w_grid_alloc.chars) { + grid_invalidate(&wp->w_grid_alloc); + wp->w_redr_type = UPD_NOT_VALID; + } + + win_check_ns_hl(wp); + + // reallocate grid if needed. + win_grid_alloc(wp); + + if (wp->w_redr_border || wp->w_redr_type >= UPD_NOT_VALID) { + win_redr_border(wp); + } + + if (wp->w_redr_type != 0) { + if (!did_one) { + did_one = true; + start_search_hl(); + } + win_update(wp, &providers); + } + + // redraw status line and window bar after the window to minimize cursor movement + if (wp->w_redr_status) { + win_redr_winbar(wp); + win_redr_status(wp); + } + } + + end_search_hl(); + + // May need to redraw the popup menu. + if (pum_drawn() && must_redraw_pum) { + win_check_ns_hl(curwin); + pum_redraw(); + } + + win_check_ns_hl(NULL); + + // Reset b_mod_set flags. Going through all windows is probably faster + // than going through all buffers (there could be many buffers). + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + wp->w_buffer->b_mod_set = false; + } + + updating_screen = 0; + + // Clear or redraw the command line. Done last, because scrolling may + // mess up the command line. + if (clear_cmdline || redraw_cmdline || redraw_mode) { + showmode(); + } + + // May put up an introductory message when not editing a file + if (!did_intro) { + maybe_intro_message(); + } + did_intro = true; + + decor_providers_invoke_end(&providers, &provider_err); + kvi_destroy(providers); + + // either cmdline is cleared, not drawn or mode is last drawn + cmdline_was_last_drawn = false; + return OK; +} + +static void win_redr_border(win_T *wp) +{ + wp->w_redr_border = false; + if (!(wp->w_floating && wp->w_float_config.border)) { + return; + } + + ScreenGrid *grid = &wp->w_grid_alloc; + + schar_T *chars = wp->w_float_config.border_chars; + int *attrs = wp->w_float_config.border_attr; + + int *adj = wp->w_border_adj; + int irow = wp->w_height_inner + wp->w_winbar_height, icol = wp->w_width_inner; + + if (adj[0]) { + grid_puts_line_start(grid, 0); + 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 (adj[1]) { + grid_put_schar(grid, 0, icol + adj[3], chars[2], attrs[2]); + } + grid_puts_line_flush(false); + } + + for (int i = 0; i < irow; i++) { + if (adj[3]) { + grid_puts_line_start(grid, i + adj[0]); + grid_put_schar(grid, i + adj[0], 0, chars[7], attrs[7]); + grid_puts_line_flush(false); + } + if (adj[1]) { + int ic = (i == 0 && !adj[0] && chars[2][0]) ? 2 : 3; + grid_puts_line_start(grid, i + adj[0]); + grid_put_schar(grid, i + adj[0], icol + adj[3], chars[ic], attrs[ic]); + grid_puts_line_flush(false); + } + } + + if (adj[2]) { + grid_puts_line_start(grid, irow + adj[0]); + if (adj[3]) { + grid_put_schar(grid, irow + adj[0], 0, chars[6], attrs[6]); + } + for (int i = 0; i < icol; i++) { + int ic = (i == 0 && !adj[3] && chars[6][0]) ? 6 : 5; + grid_put_schar(grid, irow + adj[0], i + adj[3], chars[ic], attrs[ic]); + } + if (adj[1]) { + grid_put_schar(grid, irow + adj[0], icol + adj[3], chars[4], attrs[4]); + } + grid_puts_line_flush(false); + } +} + +/// Show current cursor info in ruler and various other places +/// +/// @param always if false, only show ruler if position has changed. +void show_cursor_info(bool always) +{ + if (!always && !redrawing()) { + return; + } + + win_check_ns_hl(curwin); + if ((*p_stl != NUL || *curwin->w_p_stl != NUL) + && (curwin->w_status_height || global_stl_height())) { + redraw_custom_statusline(curwin); + } else { + win_redr_ruler(curwin, always); + } + if (*p_wbr != NUL || *curwin->w_p_wbr != NUL) { + win_redr_winbar(curwin); + } + + if (need_maketitle + || (p_icon && (stl_syntax & STL_IN_ICON)) + || (p_title && (stl_syntax & STL_IN_TITLE))) { + maketitle(); + } + + win_check_ns_hl(NULL); + // Redraw the tab pages line if needed. + if (redraw_tabline) { + draw_tabline(); + } +} + +static void redraw_win_signcol(win_T *wp) +{ + // If we can compute a change in the automatic sizing of the sign column + // under 'signcolumn=auto:X' and signs currently placed in the buffer, better + // figuring it out here so we can redraw the entire screen for it. + int scwidth = wp->w_scwidth; + wp->w_scwidth = win_signcol_count(wp); + if (wp->w_scwidth != scwidth) { + changed_line_abv_curs_win(wp); + } +} + +/// Check if horizontal separator of window "wp" at specified window corner is connected to the +/// horizontal separator of another window +/// Assumes global statusline is enabled +static bool hsep_connected(win_T *wp, WindowCorner corner) +{ + bool before = (corner == WC_TOP_LEFT || corner == WC_BOTTOM_LEFT); + int sep_row = (corner == WC_TOP_LEFT || corner == WC_TOP_RIGHT) + ? wp->w_winrow - 1 : W_ENDROW(wp); + frame_T *fr = wp->w_frame; + + while (fr->fr_parent != NULL) { + if (fr->fr_parent->fr_layout == FR_ROW && (before ? fr->fr_prev : fr->fr_next) != NULL) { + fr = before ? fr->fr_prev : fr->fr_next; + break; + } + fr = fr->fr_parent; + } + if (fr->fr_parent == NULL) { + return false; + } + while (fr->fr_layout != FR_LEAF) { + fr = fr->fr_child; + if (fr->fr_parent->fr_layout == FR_ROW && before) { + while (fr->fr_next != NULL) { + fr = fr->fr_next; + } + } else { + while (fr->fr_next != NULL && frame2win(fr)->w_winrow + fr->fr_height < sep_row) { + fr = fr->fr_next; + } + } + } + + return (sep_row == fr->fr_win->w_winrow - 1 || sep_row == W_ENDROW(fr->fr_win)); +} + +/// Check if vertical separator of window "wp" at specified window corner is connected to the +/// vertical separator of another window +static bool vsep_connected(win_T *wp, WindowCorner corner) +{ + bool before = (corner == WC_TOP_LEFT || corner == WC_TOP_RIGHT); + int sep_col = (corner == WC_TOP_LEFT || corner == WC_BOTTOM_LEFT) + ? wp->w_wincol - 1 : W_ENDCOL(wp); + frame_T *fr = wp->w_frame; + + while (fr->fr_parent != NULL) { + if (fr->fr_parent->fr_layout == FR_COL && (before ? fr->fr_prev : fr->fr_next) != NULL) { + fr = before ? fr->fr_prev : fr->fr_next; + break; + } + fr = fr->fr_parent; + } + if (fr->fr_parent == NULL) { + return false; + } + while (fr->fr_layout != FR_LEAF) { + fr = fr->fr_child; + if (fr->fr_parent->fr_layout == FR_COL && before) { + while (fr->fr_next != NULL) { + fr = fr->fr_next; + } + } else { + while (fr->fr_next != NULL && frame2win(fr)->w_wincol + fr->fr_width < sep_col) { + fr = fr->fr_next; + } + } + } + + return (sep_col == fr->fr_win->w_wincol - 1 || sep_col == W_ENDCOL(fr->fr_win)); +} + +/// 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); + } +} + +/// 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); + } +} + +/// Get the separator connector for specified window corner of window "wp" +static int get_corner_sep_connector(win_T *wp, WindowCorner corner) +{ + // It's impossible for windows to be connected neither vertically nor horizontally + // So if they're not vertically connected, assume they're horizontally connected + if (vsep_connected(wp, corner)) { + if (hsep_connected(wp, corner)) { + return wp->w_p_fcs_chars.verthoriz; + } else if (corner == WC_TOP_LEFT || corner == WC_BOTTOM_LEFT) { + return wp->w_p_fcs_chars.vertright; + } else { + return wp->w_p_fcs_chars.vertleft; + } + } else if (corner == WC_TOP_LEFT || corner == WC_TOP_RIGHT) { + return wp->w_p_fcs_chars.horizdown; + } else { + return wp->w_p_fcs_chars.horizup; + } +} + +/// Draw separator connecting characters on the corners of window "wp" +static void draw_sep_connectors_win(win_T *wp) +{ + // Don't draw separator connectors unless global statusline is enabled and the window has + // either a horizontal or vertical separator + if (global_stl_height() == 0 || !(wp->w_hsep_height == 1 || wp->w_vsep_width == 1)) { + return; + } + + int hl = win_hl_attr(wp, HLF_C); + + // Determine which edges of the screen the window is located on so we can avoid drawing separators + // on corners contained in those edges + bool win_at_top; + bool win_at_bottom = wp->w_hsep_height == 0; + bool win_at_left; + bool win_at_right = wp->w_vsep_width == 0; + frame_T *frp; + + for (frp = wp->w_frame; frp->fr_parent != NULL; frp = frp->fr_parent) { + if (frp->fr_parent->fr_layout == FR_COL && frp->fr_prev != NULL) { + break; + } + } + win_at_top = frp->fr_parent == NULL; + for (frp = wp->w_frame; frp->fr_parent != NULL; frp = frp->fr_parent) { + if (frp->fr_parent->fr_layout == FR_ROW && frp->fr_prev != NULL) { + break; + } + } + win_at_left = frp->fr_parent == NULL; + + // Draw the appropriate separator connector in every corner where drawing them is necessary + if (!(win_at_top || win_at_left)) { + grid_putchar(&default_grid, get_corner_sep_connector(wp, WC_TOP_LEFT), + wp->w_winrow - 1, wp->w_wincol - 1, hl); + } + if (!(win_at_top || win_at_right)) { + grid_putchar(&default_grid, get_corner_sep_connector(wp, WC_TOP_RIGHT), + wp->w_winrow - 1, W_ENDCOL(wp), hl); + } + if (!(win_at_bottom || win_at_left)) { + grid_putchar(&default_grid, get_corner_sep_connector(wp, WC_BOTTOM_LEFT), + W_ENDROW(wp), wp->w_wincol - 1, hl); + } + if (!(win_at_bottom || win_at_right)) { + grid_putchar(&default_grid, get_corner_sep_connector(wp, WC_BOTTOM_RIGHT), + W_ENDROW(wp), W_ENDCOL(wp), hl); + } +} + +/// Update a single window. +/// +/// This may cause the windows below it also to be redrawn (when clearing the +/// screen or scrolling lines). +/// +/// How the window is redrawn depends on wp->w_redr_type. Each type also +/// implies the one below it. +/// UPD_NOT_VALID redraw the whole window +/// UPD_SOME_VALID redraw the whole window but do scroll when possible +/// UPD_REDRAW_TOP redraw the top w_upd_rows window lines, otherwise like UPD_VALID +/// UPD_INVERTED redraw the changed part of the Visual area +/// UPD_INVERTED_ALL redraw the whole Visual area +/// UPD_VALID 1. scroll up/down to adjust for a changed w_topline +/// 2. update lines at the top when scrolled down +/// 3. redraw changed text: +/// - if wp->w_buffer->b_mod_set set, update lines between +/// b_mod_top and b_mod_bot. +/// - if wp->w_redraw_top non-zero, redraw lines between +/// wp->w_redraw_top and wp->w_redr_bot. +/// - continue redrawing when syntax status is invalid. +/// 4. if scrolled up, update lines at the bottom. +/// This results in three areas that may need updating: +/// top: from first row to top_end (when scrolled down) +/// mid: from mid_start to mid_end (update inversion or changed text) +/// bot: from bot_start to last row (when scrolled up) +static void win_update(win_T *wp, 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 + // updating. 999 when no mid area updating. + int mid_end = 0; // Below last row of the mid area that needs + // updating. 0 when no mid area updating. + int bot_start = 999; // first row of the bot area that needs + // updating. 999 when no bot area updating + bool scrolled_down = false; // true when scrolled down when w_topline got smaller a bit + bool top_to_mod = false; // redraw above mod_top + + 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; + 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; + + if (type >= UPD_NOT_VALID) { + wp->w_redr_status = true; + wp->w_lines_valid = 0; + } + + // Window is zero-height: Only need to draw the separator + if (wp->w_grid.rows == 0) { + // draw the horizontal separator below this window + draw_hsep_win(wp); + draw_sep_connectors_win(wp); + wp->w_redr_type = 0; + return; + } + + // Window is zero-width: Only need to draw the separator. + if (wp->w_grid.cols == 0) { + // draw the vertical separator right of this window + draw_vsep_win(wp); + draw_sep_connectors_win(wp); + wp->w_redr_type = 0; + return; + } + + 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) { + type = UPD_NOT_VALID; + wp->w_nrwidth = i; + + if (buf->terminal) { + terminal_check_size(buf->terminal); + } + } else if (buf->b_mod_set + && buf->b_mod_xlines != 0 + && wp->w_redraw_top != 0) { + // When there are both inserted/deleted lines and specific lines to be + // redrawn, w_redraw_top and w_redraw_bot may be invalid, just redraw + // everything (only happens when redrawing is off for while). + type = UPD_NOT_VALID; + } else { + // Set mod_top to the first line that needs displaying because of + // changes. Set mod_bot to the first line after the changes. + mod_top = wp->w_redraw_top; + if (wp->w_redraw_bot != 0) { + mod_bot = wp->w_redraw_bot + 1; + } else { + mod_bot = 0; + } + if (buf->b_mod_set) { + if (mod_top == 0 || mod_top > buf->b_mod_top) { + mod_top = buf->b_mod_top; + // Need to redraw lines above the change that may be included + // in a pattern match. + if (syntax_present(wp)) { + mod_top -= buf->b_s.b_syn_sync_linebreaks; + if (mod_top < 1) { + mod_top = 1; + } + } + } + if (mod_bot == 0 || mod_bot < buf->b_mod_bot) { + mod_bot = buf->b_mod_bot; + } + + // When 'hlsearch' is on and using a multi-line search pattern, a + // change in one line may make the Search highlighting in a + // previous line invalid. Simple solution: redraw all visible + // lines above the change. + // Same for a match pattern. + if (screen_search_hl.rm.regprog != NULL + && re_multiline(screen_search_hl.rm.regprog)) { + top_to_mod = true; + } else { + const matchitem_T *cur = wp->w_match_head; + while (cur != NULL) { + if (cur->match.regprog != NULL + && re_multiline(cur->match.regprog)) { + top_to_mod = true; + break; + } + cur = cur->next; + } + } + } + if (mod_top != 0 && hasAnyFolding(wp)) { + linenr_T lnumt, lnumb; + + // A change in a line can cause lines above it to become folded or + // unfolded. Find the top most buffer line that may be affected. + // If the line was previously folded and displayed, get the first + // line of that fold. If the line is folded now, get the first + // folded line. Use the minimum of these two. + + // Find last valid w_lines[] entry above mod_top. Set lnumt to + // the line below it. If there is no valid entry, use w_topline. + // Find the first valid w_lines[] entry below mod_bot. Set lnumb + // 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++) { + if (wp->w_lines[i].wl_valid) { + if (wp->w_lines[i].wl_lastlnum < mod_top) { + lnumt = wp->w_lines[i].wl_lastlnum + 1; + } + if (lnumb == MAXLNUM && wp->w_lines[i].wl_lnum >= mod_bot) { + lnumb = wp->w_lines[i].wl_lnum; + // When there is a fold column it might need updating + // in the next line ("J" just above an open fold). + if (compute_foldcolumn(wp, 0) > 0) { + lnumb++; + } + } + } + } + + (void)hasFoldingWin(wp, mod_top, &mod_top, NULL, true, NULL); + if (mod_top > lnumt) { + mod_top = lnumt; + } + + // Now do the same for the bottom line (one above mod_bot). + mod_bot--; + (void)hasFoldingWin(wp, mod_bot, NULL, &mod_bot, true, NULL); + mod_bot++; + if (mod_bot < lnumb) { + mod_bot = lnumb; + } + } + + // When a change starts above w_topline and the end is below + // w_topline, start redrawing at w_topline. + // If the end of the change is above w_topline: do like no change was + // made, but redraw the first line to find changes in syntax. + if (mod_top != 0 && mod_top < wp->w_topline) { + if (mod_bot > wp->w_topline) { + mod_top = wp->w_topline; + } else if (syntax_present(wp)) { + top_end = 1; + } + } + + // When line numbers are displayed need to redraw all lines below + // inserted/deleted lines. + if (mod_top != 0 && buf->b_mod_xlines != 0 && wp->w_p_nu) { + mod_bot = MAXLNUM; + } + } + wp->w_redraw_top = 0; // reset for next time + wp->w_redraw_bot = 0; + + // 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++) { + j += wp->w_lines[i].wl_size; + if (j >= wp->w_upd_rows) { + top_end = (int)j; + break; + } + } + if (top_end == 0) { + // not found (cannot happen?): redraw everything + type = UPD_NOT_VALID; + } else { + // top area defined, the rest is UPD_VALID + type = UPD_VALID; + } + } + + // If there are no changes on the screen that require a complete redraw, + // handle three cases: + // 1: we are off the top of the screen by a few lines: scroll down + // 2: wp->w_topline is below wp->w_lines[0].wl_lnum: may scroll up + // 3: wp->w_topline is wp->w_lines[0].wl_lnum: find first entry in + // w_lines[] that needs updating. + if ((type == UPD_VALID || type == UPD_SOME_VALID + || type == UPD_INVERTED || type == UPD_INVERTED_ALL) + && !wp->w_botfill && !wp->w_old_botfill) { + if (mod_top != 0 + && wp->w_topline == mod_top + && (!wp->w_lines[0].wl_valid + || wp->w_topline == wp->w_lines[0].wl_lnum)) { + // w_topline is the first changed line and window is not scrolled, + // the scrolling from changed lines will be done further down. + } else if (wp->w_lines[0].wl_valid + && (wp->w_topline < wp->w_lines[0].wl_lnum + || (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. + if (hasAnyFolding(wp)) { + linenr_T ln; + + // count the number of lines we are off, counting a sequence + // of folded lines as one + j = 0; + for (ln = wp->w_topline; ln < wp->w_lines[0].wl_lnum; ln++) { + j++; + if (j >= wp->w_grid.rows - 2) { + break; + } + (void)hasFoldingWin(wp, ln, NULL, &ln, true, NULL); + } + } else { + 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); + // 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; + } + if (i != 0 && i < wp->w_grid.rows - 2) { // less than a screen off + // Try to insert the correct number of lines. + // If not the last window, delete the lines at the bottom. + // win_ins_lines may fail when the terminal can't do it. + win_scroll_lines(wp, 0, i); + if (wp->w_lines_valid != 0) { + // Need to update rows that are new, stop at the + // first one that scrolled down. + top_end = i; + scrolled_down = true; + + // Move the entries that were scrolled, disable + // the entries for the lines to be redrawn. + if ((wp->w_lines_valid += (linenr_T)j) > wp->w_grid.rows) { + wp->w_lines_valid = wp->w_grid.rows; + } + for (idx = wp->w_lines_valid; idx - j >= 0; idx--) { + wp->w_lines[idx] = wp->w_lines[idx - j]; + } + while (idx >= 0) { + wp->w_lines[idx--].wl_valid = false; + } + } + } else { + mid_start = 0; // redraw all lines + } + } else { + mid_start = 0; // redraw all lines + } + } else { + // New topline is at or below old topline: May scroll up. + // When topline didn't change, find first entry in w_lines[] that + // 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++) { + if (wp->w_lines[i].wl_valid + && wp->w_lines[i].wl_lnum == wp->w_topline) { + j = i; + break; + } + row += wp->w_lines[i].wl_size; + } + if (j == -1) { + // if wp->w_topline is not in wp->w_lines[].wl_lnum redraw all + // lines + mid_start = 0; + } else { + // Try to delete the correct number of lines. + // wp->w_topline is at wp->w_lines[i].wl_lnum. + + // If the topline didn't change, delete old filler lines, + // otherwise delete filler lines of the new topline... + if (wp->w_lines[0].wl_lnum == wp->w_topline) { + row += wp->w_old_topfill; + } else { + row += win_get_fill(wp, wp->w_topline); + } + // ... but don't delete new filler lines. + row -= wp->w_topfill; + if (row > 0) { + win_scroll_lines(wp, 0, -row); + bot_start = wp->w_grid.rows - row; + } + if ((row == 0 || bot_start < 999) && wp->w_lines_valid != 0) { + // Skip the lines (below the deleted lines) that are still + // valid and don't need redrawing. Copy their info + // upwards, to compensate for the deleted lines. Set + // bot_start to the first row that needs redrawing. + bot_start = 0; + idx = 0; + for (;;) { + wp->w_lines[idx] = wp->w_lines[j]; + // stop at line that didn't fit, unless it is still + // valid (no lines deleted) + if (row > 0 && bot_start + row + + (int)wp->w_lines[j].wl_size > wp->w_grid.rows) { + wp->w_lines_valid = idx + 1; + break; + } + bot_start += wp->w_lines[idx++].wl_size; + + // stop at the last valid entry in w_lines[].wl_size + if (++j >= wp->w_lines_valid) { + wp->w_lines_valid = idx; + break; + } + } + + // Correct the first entry for filler lines at the top + // when it won't get updated below. + if (win_may_fill(wp) && bot_start > 0) { + wp->w_lines[0].wl_size = (uint16_t)(plines_win_nofill(wp, wp->w_topline, true) + + wp->w_topfill); + } + } + } + } + + // When starting redraw in the first line, redraw all lines. + if (mid_start == 0) { + mid_end = wp->w_grid.rows; + } + } else { + // Not UPD_VALID or UPD_INVERTED: redraw all lines. + mid_start = 0; + mid_end = wp->w_grid.rows; + } + + if (type == UPD_SOME_VALID) { + // UPD_SOME_VALID: redraw all lines. + mid_start = 0; + mid_end = wp->w_grid.rows; + type = UPD_NOT_VALID; + } + + // check if we are updating or removing the inverted part + if ((VIsual_active && buf == curwin->w_buffer) + || (wp->w_old_cursor_lnum != 0 && type != UPD_NOT_VALID)) { + linenr_T from, to; + + if (VIsual_active) { + if (VIsual_mode != wp->w_old_visual_mode || type == UPD_INVERTED_ALL) { + // If the type of Visual selection changed, redraw the whole + // selection. Also when the ownership of the X selection is + // gained or lost. + if (curwin->w_cursor.lnum < VIsual.lnum) { + from = curwin->w_cursor.lnum; + to = VIsual.lnum; + } else { + from = VIsual.lnum; + to = curwin->w_cursor.lnum; + } + // redraw more when the cursor moved as well + if (wp->w_old_cursor_lnum < from) { + from = wp->w_old_cursor_lnum; + } + if (wp->w_old_cursor_lnum > to) { + to = wp->w_old_cursor_lnum; + } + if (wp->w_old_visual_lnum < from) { + from = wp->w_old_visual_lnum; + } + if (wp->w_old_visual_lnum > to) { + to = wp->w_old_visual_lnum; + } + } else { + // Find the line numbers that need to be updated: The lines + // between the old cursor position and the current cursor + // position. Also check if the Visual position changed. + if (curwin->w_cursor.lnum < wp->w_old_cursor_lnum) { + from = curwin->w_cursor.lnum; + to = wp->w_old_cursor_lnum; + } else { + from = wp->w_old_cursor_lnum; + to = curwin->w_cursor.lnum; + if (from == 0) { // Visual mode just started + from = to; + } + } + + if (VIsual.lnum != wp->w_old_visual_lnum + || VIsual.col != wp->w_old_visual_col) { + if (wp->w_old_visual_lnum < from + && wp->w_old_visual_lnum != 0) { + from = wp->w_old_visual_lnum; + } + if (wp->w_old_visual_lnum > to) { + to = wp->w_old_visual_lnum; + } + if (VIsual.lnum < from) { + from = VIsual.lnum; + } + if (VIsual.lnum > to) { + to = VIsual.lnum; + } + } + } + + // If in block mode and changed column or curwin->w_curswant: + // update all lines. + // First compute the actual start and end column. + if (VIsual_mode == Ctrl_V) { + colnr_T fromc, toc; + unsigned int save_ve_flags = curwin->w_ve_flags; + + if (curwin->w_p_lbr) { + curwin->w_ve_flags = VE_ALL; + } + + getvcols(wp, &VIsual, &curwin->w_cursor, &fromc, &toc); + toc++; + curwin->w_ve_flags = save_ve_flags; + // Highlight to the end of the line, unless 'virtualedit' has + // "block". + if (curwin->w_curswant == MAXCOL) { + if (get_ve_flags() & VE_BLOCK) { + pos_T pos; + int cursor_above = curwin->w_cursor.lnum < VIsual.lnum; + + // Need to find the longest line. + toc = 0; + pos.coladd = 0; + for (pos.lnum = curwin->w_cursor.lnum; + cursor_above ? pos.lnum <= VIsual.lnum : pos.lnum >= VIsual.lnum; + pos.lnum += cursor_above ? 1 : -1) { + colnr_T t; + + pos.col = (colnr_T)STRLEN(ml_get_buf(wp->w_buffer, pos.lnum, false)); + getvvcol(wp, &pos, NULL, NULL, &t); + if (toc < t) { + toc = t; + } + } + toc++; + } else { + toc = MAXCOL; + } + } + + if (fromc != wp->w_old_cursor_fcol + || toc != wp->w_old_cursor_lcol) { + if (from > VIsual.lnum) { + from = VIsual.lnum; + } + if (to < VIsual.lnum) { + to = VIsual.lnum; + } + } + wp->w_old_cursor_fcol = fromc; + wp->w_old_cursor_lcol = toc; + } + } else { + // Use the line numbers of the old Visual area. + if (wp->w_old_cursor_lnum < wp->w_old_visual_lnum) { + from = wp->w_old_cursor_lnum; + to = wp->w_old_visual_lnum; + } else { + from = wp->w_old_visual_lnum; + to = wp->w_old_cursor_lnum; + } + } + + // There is no need to update lines above the top of the window. + if (from < wp->w_topline) { + from = wp->w_topline; + } + + // If we know the value of w_botline, use it to restrict the update to + // the lines that are visible in the window. + if (wp->w_valid & VALID_BOTLINE) { + if (from >= wp->w_botline) { + from = wp->w_botline - 1; + } + if (to >= wp->w_botline) { + to = wp->w_botline - 1; + } + } + + // Find the minimal part to be updated. + // Watch out for scrolling that made entries in w_lines[] invalid. + // E.g., CTRL-U makes the first half of w_lines[] invalid and sets + // top_end; need to redraw from top_end to the "to" line. + // A middle mouse click with a Visual selection may change the text + // 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; + if (scrolled_down) { + mid_start = top_end; + } else { + mid_start = 0; + } + while (lnum < from && idx < wp->w_lines_valid) { // find start + if (wp->w_lines[idx].wl_valid) { + mid_start += wp->w_lines[idx].wl_size; + } else if (!scrolled_down) { + srow += wp->w_lines[idx].wl_size; + } + idx++; + if (idx < wp->w_lines_valid && wp->w_lines[idx].wl_valid) { + lnum = wp->w_lines[idx].wl_lnum; + } else { + lnum++; + } + } + srow += mid_start; + mid_end = wp->w_grid.rows; + for (; idx < wp->w_lines_valid; idx++) { // find end + if (wp->w_lines[idx].wl_valid + && wp->w_lines[idx].wl_lnum >= to + 1) { + // Only update until first row of this line + mid_end = srow; + break; + } + srow += wp->w_lines[idx].wl_size; + } + } + } + + if (VIsual_active && buf == curwin->w_buffer) { + wp->w_old_visual_mode = (char)VIsual_mode; + wp->w_old_cursor_lnum = curwin->w_cursor.lnum; + wp->w_old_visual_lnum = VIsual.lnum; + wp->w_old_visual_col = VIsual.col; + wp->w_old_curswant = curwin->w_curswant; + } else { + wp->w_old_visual_mode = 0; + wp->w_old_cursor_lnum = 0; + wp->w_old_visual_lnum = 0; + 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); + + 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) + if (row == wp->w_grid.rows) { + didline = true; + break; + } + + // stop updating when hit the end of the file + if (lnum > buf->b_ml.ml_line_count) { + eof = true; + break; + } + + // Remember the starting row of the line that is going to be dealt + // with. It is used further down when the line doesn't fit. + srow = row; + + // Update a line when it is in an area that needs updating, when it + // has changes or w_lines[idx] is invalid. + // "bot_start" may be halfway a wrapped line after using + // win_scroll_lines(), check if the current line includes it. + // When syntax folding is being used, the saved syntax states will + // already have been updated, we can't see where the syntax state is + // the same again, just update until the end of the window. + if (row < top_end + || (row >= mid_start && row < mid_end) + || top_to_mod + || idx >= wp->w_lines_valid + || (row + wp->w_lines[idx].wl_size > bot_start) + || (mod_top != 0 + && (lnum == mod_top + || (lnum >= mod_top + && (lnum < mod_bot + || did_update == DID_FOLD + || (did_update == DID_LINE + && syntax_present(wp) + && ((foldmethodIsSyntax(wp) + && hasAnyFolding(wp)) + || syntax_check_changed(lnum))) + // match in fixed position might need redraw + // if lines were inserted or deleted + || (wp->w_match_head != NULL + && buf->b_mod_xlines != 0))))) + || (cursorline_standout && lnum == wp->w_cursor.lnum) + || lnum == wp->w_last_cursorline) { + if (lnum == mod_top) { + top_to_mod = false; + } + + // When at start of changed lines: May scroll following lines + // up or down to minimize redrawing. + // Don't do this when the change continues until the end. + // Don't scroll when dollar_vcol >= 0, keep the "$". + // Don't scroll when redrawing the top, scrolled already above. + if (lnum == mod_top + && mod_bot != MAXLNUM + && !(dollar_vcol >= 0 && mod_bot == mod_top + 1) + && row >= top_end) { + int old_rows = 0; + int new_rows = 0; + int xtra_rows; + linenr_T l; + + // Count the old number of window rows, using w_lines[], which + // should still contain the sizes for the lines as they are + // currently displayed. + for (i = idx; i < wp->w_lines_valid; i++) { + // Only valid lines have a meaningful wl_lnum. Invalid + // lines are part of the changed area. + if (wp->w_lines[i].wl_valid + && wp->w_lines[i].wl_lnum == mod_bot) { + break; + } + old_rows += wp->w_lines[i].wl_size; + if (wp->w_lines[i].wl_valid + && wp->w_lines[i].wl_lastlnum + 1 == mod_bot) { + // Must have found the last valid entry above mod_bot. + // Add following invalid entries. + i++; + while (i < wp->w_lines_valid + && !wp->w_lines[i].wl_valid) { + old_rows += wp->w_lines[i++].wl_size; + } + break; + } + } + + if (i >= wp->w_lines_valid) { + // We can't find a valid line below the changed lines, + // need to redraw until the end of the window. + // Inserting/deleting lines has no use. + bot_start = 0; + } else { + // Able to count old number of rows: Count new window + // rows, and may insert/delete lines + j = idx; + for (l = lnum; l < mod_bot; l++) { + if (hasFoldingWin(wp, l, NULL, &l, true, NULL)) { + new_rows++; + } else if (l == wp->w_topline) { + new_rows += plines_win_nofill(wp, l, true) + wp->w_topfill; + } else { + new_rows += plines_win(wp, l, true); + } + j++; + if (new_rows > wp->w_grid.rows - row - 2) { + // it's getting too much, must redraw the rest + new_rows = 9999; + break; + } + } + xtra_rows = new_rows - old_rows; + if (xtra_rows < 0) { + // May scroll text up. If there is not enough + // remaining text or scrolling fails, must redraw the + // rest. If scrolling works, must redraw the text + // below the scrolled text. + if (row - xtra_rows >= wp->w_grid.rows - 2) { + mod_bot = MAXLNUM; + } else { + win_scroll_lines(wp, row, xtra_rows); + bot_start = wp->w_grid.rows + xtra_rows; + } + } else if (xtra_rows > 0) { + // May scroll text down. If there is not enough + // remaining text of scrolling fails, must redraw the + // rest. + if (row + xtra_rows >= wp->w_grid.rows - 2) { + mod_bot = MAXLNUM; + } else { + win_scroll_lines(wp, row + old_rows, xtra_rows); + if (top_end > row + old_rows) { + // Scrolled the part at the top that requires + // updating down. + top_end += xtra_rows; + } + } + } + + // When not updating the rest, may need to move w_lines[] + // entries. + if (mod_bot != MAXLNUM && i != j) { + if (j < i) { + int x = row + new_rows; + + // move entries in w_lines[] upwards + for (;;) { + // stop at last valid entry in w_lines[] + if (i >= wp->w_lines_valid) { + wp->w_lines_valid = (int)j; + break; + } + wp->w_lines[j] = wp->w_lines[i]; + // stop at a line that won't fit + if (x + (int)wp->w_lines[j].wl_size + > wp->w_grid.rows) { + wp->w_lines_valid = (int)j + 1; + break; + } + x += wp->w_lines[j++].wl_size; + i++; + } + if (bot_start > x) { + bot_start = x; + } + } else { // j > i + // move entries in w_lines[] downwards + j -= i; + wp->w_lines_valid += (linenr_T)j; + if (wp->w_lines_valid > wp->w_grid.rows) { + wp->w_lines_valid = wp->w_grid.rows; + } + for (i = wp->w_lines_valid; i - j >= idx; i--) { + wp->w_lines[i] = wp->w_lines[i - j]; + } + + // The w_lines[] entries for inserted lines are + // now invalid, but wl_size may be used above. + // Reset to zero. + while (i >= idx) { + wp->w_lines[i].wl_size = 0; + wp->w_lines[i--].wl_valid = false; + } + } + } + } + } + + // When lines are folded, display one line for all of them. + // Otherwise, display normally (can be several display lines when + // 'wrap' is on). + foldinfo_T foldinfo = fold_info(wp, lnum); + + if (foldinfo.fi_lines == 0 + && idx < wp->w_lines_valid + && wp->w_lines[idx].wl_valid + && wp->w_lines[idx].wl_lnum == lnum + && lnum > wp->w_topline + && !(dy_flags & (DY_LASTLINE | DY_TRUNCATE)) + && srow + wp->w_lines[idx].wl_size > wp->w_grid.rows + && win_get_fill(wp, lnum) == 0) { + // This line is not going to fit. Don't draw anything here, + // will draw "@ " lines below. + row = wp->w_grid.rows + 1; + } else { + prepare_search_hl(wp, &screen_search_hl, lnum); + // Let the syntax stuff know we skipped a few lines. + if (syntax_last_parsed != 0 && syntax_last_parsed + 1 < lnum + && syntax_present(wp)) { + syntax_end_parsing(syntax_last_parsed + 1); + } + + // Display one line + row = win_line(wp, lnum, srow, + foldinfo.fi_lines ? srow : wp->w_grid.rows, + mod_top == 0, false, foldinfo, &line_providers, &provider_err); + + if (foldinfo.fi_lines == 0) { + wp->w_lines[idx].wl_folded = false; + wp->w_lines[idx].wl_lastlnum = lnum; + did_update = DID_LINE; + syntax_last_parsed = lnum; + } else { + foldinfo.fi_lines--; + wp->w_lines[idx].wl_folded = true; + wp->w_lines[idx].wl_lastlnum = lnum + foldinfo.fi_lines; + did_update = DID_FOLD; + } + } + + wp->w_lines[idx].wl_lnum = lnum; + wp->w_lines[idx].wl_valid = true; + + if (row > wp->w_grid.rows) { // past end of grid + // we may need the size of that too long line later on + if (dollar_vcol == -1) { + wp->w_lines[idx].wl_size = (uint16_t)plines_win(wp, lnum, true); + } + idx++; + break; + } + if (dollar_vcol == -1) { + wp->w_lines[idx].wl_size = (uint16_t)(row - srow); + } + idx++; + lnum += foldinfo.fi_lines + 1; + } else { + if (wp->w_p_rnu && wp->w_last_cursor_lnum_rnu != wp->w_cursor.lnum) { + // 'relativenumber' set and cursor moved vertically: The + // text doesn't need to be drawn, but the number column does. + foldinfo_T info = fold_info(wp, lnum); + (void)win_line(wp, lnum, srow, wp->w_grid.rows, true, true, + info, &line_providers, &provider_err); + } + + // This line does not need to be drawn, advance to the next one. + row += wp->w_lines[idx++].wl_size; + if (row > wp->w_grid.rows) { // past end of screen + break; + } + lnum = wp->w_lines[idx - 1].wl_lastlnum + 1; + did_update = DID_NONE; + } + + if (lnum > buf->b_ml.ml_line_count) { + eof = true; + break; + } + } + // End of loop over all window lines. + + // Now that the window has been redrawn with the old and new cursor line, + // update w_last_cursorline. + wp->w_last_cursorline = cursorline_standout ? wp->w_cursor.lnum : 0; + + wp->w_last_cursor_lnum_rnu = wp->w_p_rnu ? wp->w_cursor.lnum : 0; + + if (idx > wp->w_lines_valid) { + wp->w_lines_valid = idx; + } + + // Let the syntax stuff know we stop parsing here. + if (syntax_last_parsed != 0 && syntax_present(wp)) { + syntax_end_parsing(syntax_last_parsed + 1); + } + + // 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; + wp->w_filler_rows = 0; + if (!eof && !didline) { + int at_attr = hl_combine_attr(win_bg_attr(wp), win_hl_attr(wp, HLF_AT)); + if (lnum == wp->w_topline) { + // Single line that does not fit! + // Don't overwrite it, it can be edited. + wp->w_botline = lnum + 1; + } else if (win_get_fill(wp, lnum) >= wp->w_grid.rows - srow) { + // Window ends in filler lines. + wp->w_botline = lnum; + wp->w_filler_rows = wp->w_grid.rows - srow; + } else if (dy_flags & DY_TRUNCATE) { // 'display' has "truncate" + int scr_row = wp->w_grid.rows - 1; + + // Last line isn't finished: Display "@@@" in the last screen line. + grid_puts_len(&wp->w_grid, "@@", MIN(wp->w_grid.cols, 2), scr_row, 0, at_attr); + + grid_fill(&wp->w_grid, scr_row, scr_row + 1, 2, wp->w_grid.cols, + '@', ' ', at_attr); + set_empty_rows(wp, srow); + wp->w_botline = lnum; + } else if (dy_flags & DY_LASTLINE) { // 'display' has "lastline" + int start_col = wp->w_grid.cols - 3; + + // Last line isn't finished: Display "@@@" at the end. + grid_fill(&wp->w_grid, wp->w_grid.rows - 1, wp->w_grid.rows, + MAX(start_col, 0), wp->w_grid.cols, '@', '@', at_attr); + set_empty_rows(wp, srow); + wp->w_botline = lnum; + } else { + win_draw_end(wp, '@', ' ', true, srow, wp->w_grid.rows, HLF_AT); + wp->w_botline = lnum; + } + } 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); + 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; + row = win_line(wp, wp->w_botline, row, wp->w_grid.rows, + false, false, info, &line_providers, &provider_err); + } + } else if (dollar_vcol == -1) { + wp->w_botline = lnum; + } + + // Make sure the rest of the screen is blank. + // write the "eob" character from 'fillchars' to rows that aren't part + // of the file. + win_draw_end(wp, wp->w_p_fcs_chars.eob, ' ', false, row, wp->w_grid.rows, + HLF_EOB); + } + + kvi_destroy(line_providers); + + if (wp->w_redr_type >= UPD_REDRAW_TOP) { + draw_vsep_win(wp); + draw_hsep_win(wp); + draw_sep_connectors_win(wp); + } + syn_set_timeout(NULL); + + // Reset the type of redrawing required, the window has been updated. + wp->w_redr_type = 0; + wp->w_old_topfill = wp->w_topfill; + wp->w_old_botfill = wp->w_botfill; + + // Send win_extmarks if needed + for (size_t n = 0; n < kv_size(win_extmark_arr); n++) { + ui_call_win_extmark(wp->w_grid_alloc.handle, wp->handle, + kv_A(win_extmark_arr, n).ns_id, (Integer)kv_A(win_extmark_arr, n).mark_id, + kv_A(win_extmark_arr, n).win_row, kv_A(win_extmark_arr, n).win_col); + } + + if (dollar_vcol == -1) { + // There is a trick with w_botline. If we invalidate it on each + // change that might modify it, this will cause a lot of expensive + // calls to plines_win() in update_topline() each time. Therefore the + // value of w_botline is often approximated, and this value is used to + // compute the value of w_topline. If the value of w_botline was + // wrong, check that the value of w_topline is correct (cursor is on + // the visible part of the text). If it's not, we need to redraw + // again. Mostly this just means scrolling up a few lines, so it + // doesn't look too bad. Only do this for the current window (where + // changes are relevant). + wp->w_valid |= VALID_BOTLINE; + wp->w_viewport_invalid = true; + if (wp == curwin && wp->w_botline != old_botline && !recursive) { + recursive = true; + curwin->w_valid &= ~VALID_TOPLINE; + 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; + curbuf->b_mod_set = false; + win_update(curwin, providers); + must_redraw = 0; + curbuf->b_mod_set = i; + } + recursive = false; + } + } + + // restore got_int, unless CTRL-C was hit while redrawing + if (!got_int) { + got_int = save_got_int; + } +} + +/// Redraw a window later, with update_screen(type). +/// +/// Set must_redraw only if not already set to a higher value. +/// e.g. if must_redraw is UPD_CLEAR, type UPD_NOT_VALID will do nothing. +void redraw_later(win_T *wp, int type) + FUNC_ATTR_NONNULL_ALL +{ + if (!exiting && wp->w_redr_type < type) { + wp->w_redr_type = type; + if (type >= UPD_NOT_VALID) { + wp->w_lines_valid = 0; + } + if (must_redraw < type) { // must_redraw is the maximum of all windows + must_redraw = type; + } + } +} + +/// Mark all windows to be redrawn later. +void redraw_all_later(int type) +{ + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + redraw_later(wp, type); + } + // This may be needed when switching tabs. + if (must_redraw < type) { + must_redraw = type; + } +} + +void screen_invalidate_highlights(void) +{ + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + redraw_later(wp, UPD_NOT_VALID); + wp->w_grid_alloc.valid = false; + } +} + +/// Mark all windows that are editing the current buffer to be updated later. +void redraw_curbuf_later(int type) +{ + redraw_buf_later(curbuf, type); +} + +void redraw_buf_later(buf_T *buf, int type) +{ + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_buffer == buf) { + redraw_later(wp, type); + } + } +} + +void redraw_buf_line_later(buf_T *buf, linenr_T line) +{ + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_buffer == buf + && line >= wp->w_topline && line < wp->w_botline) { + redrawWinline(wp, line); + } + } +} + +void redraw_buf_range_later(buf_T *buf, linenr_T firstline, linenr_T lastline) +{ + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_buffer == buf + && lastline >= wp->w_topline && firstline < wp->w_botline) { + if (wp->w_redraw_top == 0 || wp->w_redraw_top > firstline) { + wp->w_redraw_top = firstline; + } + if (wp->w_redraw_bot == 0 || wp->w_redraw_bot < lastline) { + wp->w_redraw_bot = lastline; + } + redraw_later(wp, UPD_VALID); + } + } +} + +/// called when the status bars for the buffer 'buf' need to be updated +void redraw_buf_status_later(buf_T *buf) +{ + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_buffer == buf + && (wp->w_status_height + || (wp == curwin && global_stl_height()) + || wp->w_winbar_height)) { + wp->w_redr_status = true; + if (must_redraw < UPD_VALID) { + must_redraw = UPD_VALID; + } + } + } +} + +/// Mark all status lines and window bars for redraw; used after first :cd +void status_redraw_all(void) +{ + bool is_stl_global = global_stl_height() != 0; + + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if ((!is_stl_global && wp->w_status_height) || (is_stl_global && wp == curwin) + || wp->w_winbar_height) { + wp->w_redr_status = true; + redraw_later(wp, UPD_VALID); + } + } +} + +/// Marks all status lines and window bars of the current buffer for redraw. +void status_redraw_curbuf(void) +{ + status_redraw_buf(curbuf); +} + +/// Marks all status lines and window bars of the given buffer for redraw. +void status_redraw_buf(buf_T *buf) +{ + bool is_stl_global = global_stl_height() != 0; + + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_buffer == buf && ((!is_stl_global && wp->w_status_height) + || (is_stl_global && wp == curwin) || wp->w_winbar_height)) { + wp->w_redr_status = true; + redraw_later(wp, UPD_VALID); + } + } +} + +/// Redraw all status lines that need to be redrawn. +void redraw_statuslines(void) +{ + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_redr_status) { + win_check_ns_hl(wp); + win_redr_winbar(wp); + win_redr_status(wp); + } + } + + win_check_ns_hl(NULL); + if (redraw_tabline) { + draw_tabline(); + } +} + +/// Redraw all status lines at the bottom of frame "frp". +void win_redraw_last_status(const frame_T *frp) + FUNC_ATTR_NONNULL_ARG(1) +{ + if (frp->fr_layout == FR_LEAF) { + frp->fr_win->w_redr_status = true; + } else if (frp->fr_layout == FR_ROW) { + FOR_ALL_FRAMES(frp, frp->fr_child) { + win_redraw_last_status(frp); + } + } else { + assert(frp->fr_layout == FR_COL); + frp = frp->fr_child; + while (frp->fr_next != NULL) { + frp = frp->fr_next; + } + win_redraw_last_status(frp); + } +} + +/// Changed something in the current window, at buffer line "lnum", that +/// requires that line and possibly other lines to be redrawn. +/// Used when entering/leaving Insert mode with the cursor on a folded line. +/// Used to remove the "$" from a change command. +/// Note that when also inserting/deleting lines w_redraw_top and w_redraw_bot +/// may become invalid and the whole window will have to be redrawn. +void redrawWinline(win_T *wp, linenr_T lnum) + FUNC_ATTR_NONNULL_ALL +{ + if (lnum >= wp->w_topline + && lnum < wp->w_botline) { + if (wp->w_redraw_top == 0 || wp->w_redraw_top > lnum) { + wp->w_redraw_top = lnum; + } + if (wp->w_redraw_bot == 0 || wp->w_redraw_bot < lnum) { + wp->w_redraw_bot = lnum; + } + redraw_later(wp, UPD_VALID); + } +} diff --git a/src/nvim/drawscreen.h b/src/nvim/drawscreen.h new file mode 100644 index 0000000000..ef99899581 --- /dev/null +++ b/src/nvim/drawscreen.h @@ -0,0 +1,25 @@ +#ifndef NVIM_DRAWSCREEN_H +#define NVIM_DRAWSCREEN_H + +#include "nvim/drawline.h" + +/// flags for update_screen() +/// The higher the value, the higher the priority +enum { + UPD_VALID = 10, ///< buffer not changed, or changes marked with b_mod_* + UPD_INVERTED = 20, ///< redisplay inverted part that changed + UPD_INVERTED_ALL = 25, ///< redisplay whole inverted part + UPD_REDRAW_TOP = 30, ///< display first w_upd_rows screen lines + UPD_SOME_VALID = 35, ///< like UPD_NOT_VALID but may scroll + UPD_NOT_VALID = 40, ///< buffer needs complete redraw + UPD_CLEAR = 50, ///< screen messed up, clear it +}; + +/// While redrawing the screen this flag is set. It means the screen size +/// ('lines' and 'rows') must not be changed. +EXTERN bool updating_screen INIT(= 0); + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "drawscreen.h.generated.h" +#endif +#endif // NVIM_DRAWSCREEN_H diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 5861e4c52b..aee8389900 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -16,6 +16,7 @@ #include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/digraph.h" +#include "nvim/drawscreen.h" #include "nvim/edit.h" #include "nvim/eval.h" #include "nvim/event/loop.h" @@ -25,6 +26,7 @@ #include "nvim/fileio.h" #include "nvim/fold.h" #include "nvim/getchar.h" +#include "nvim/grid.h" #include "nvim/highlight_group.h" #include "nvim/indent.h" #include "nvim/indent_c.h" @@ -46,15 +48,16 @@ #include "nvim/os/time.h" #include "nvim/path.h" #include "nvim/plines.h" -#include "nvim/popupmnu.h" +#include "nvim/popupmenu.h" #include "nvim/quickfix.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/ui.h" #include "nvim/undo.h" #include "nvim/vim.h" @@ -91,6 +94,10 @@ typedef struct insert_state { #define BACKSPACE_WORD_NOT_SPACE 3 #define BACKSPACE_LINE 4 +/// Set when doing something for completion that may call edit() recursively, +/// which is not allowed. +static bool compl_busy = false; + 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 @@ -103,8 +110,6 @@ static int did_restart_edit; // "restart_edit" when calling edit() static bool can_cindent; // may do cindenting on this line -static int old_indent = 0; // for ^^D command in insert mode - static int revins_on; // reverse insert mode on static int revins_chars; // how much to skip after edit static int revins_legal; // was the last char 'legal'? @@ -114,8 +119,6 @@ static bool ins_need_undo; // call u_save() before inserting a // char. Set when edit() is called. // after that arrow_used is used. -static bool did_add_space = false; // auto_format() added an extra space - // under the cursor static TriState dont_sync_undo = kFalse; // CTRL-G U prevents syncing undo // for the next left/right cursor key @@ -447,7 +450,7 @@ static int insert_check(VimState *state) && (curwin->w_cursor.lnum != curwin->w_topline || curwin->w_topfill > 0)) { if (curwin->w_topfill > 0) { - --curwin->w_topfill; + curwin->w_topfill--; } else if (hasFolding(curwin->w_topline, NULL, &s->old_topline)) { set_topline(curwin, s->old_topline + 1); } else { @@ -623,16 +626,17 @@ static int insert_execute(VimState *state, int key) } if (cindent_on() && ctrl_x_mode_none()) { + s->line_is_white = inindent(0); // A key name preceded by a bang means this key is not to be // inserted. Skip ahead to the re-indenting below. - // A key name preceded by a star means that indenting has to be - // done before inserting the key. - s->line_is_white = inindent(0); - if (in_cinkeys(s->c, '!', s->line_is_white)) { - insert_do_cindent(s); + if (in_cinkeys(s->c, '!', s->line_is_white) + && stop_arrow() == OK) { + do_c_expr_indent(); return 1; // continue } + // A key name preceded by a star means that indenting has to be + // done before inserting the key. if (can_cindent && in_cinkeys(s->c, '*', s->line_is_white) && stop_arrow() == OK) { do_c_expr_indent(); @@ -1089,7 +1093,7 @@ check_pum: // but it is under other ^X modes if (*curbuf->b_p_cpt == NUL && (ctrl_x_mode_normal() || ctrl_x_mode_whole_line()) - && !(compl_cont_status & CONT_LOCAL)) { + && !compl_status_local()) { goto normalchar; } @@ -1175,9 +1179,11 @@ normalchar: static void insert_do_complete(InsertState *s) { compl_busy = true; + disable_fold_update++; // don't redraw folds here if (ins_complete(s->c, true) == FAIL) { - compl_cont_status = 0; + compl_status_clear(); } + disable_fold_update--; compl_busy = false; can_si = may_do_si(); // allow smartindenting } @@ -1348,7 +1354,7 @@ void ins_redraw(bool ready) } else if (clear_cmdline || redraw_cmdline) { showmode(); // clear cmdline and show mode } - showruler(false); + show_cursor_info(false); setcursor(); emsg_on_display = false; // may remove error message now } @@ -1517,8 +1523,7 @@ void edit_unputchar(void) if (pc_status == PC_STATUS_RIGHT || pc_status == PC_STATUS_LEFT) { redrawWinline(curwin, curwin->w_cursor.lnum); } else { - grid_puts(&curwin->w_grid, pc_bytes, pc_row - msg_scrolled, pc_col, - pc_attr); + grid_puts(&curwin->w_grid, (char *)pc_bytes, pc_row - msg_scrolled, pc_col, pc_attr); } } } @@ -1553,7 +1558,7 @@ void display_dollar(colnr_T col) * Call this function before moving the cursor from the normal insert position * in insert mode. */ -static void undisplay_dollar(void) +void undisplay_dollar(void) { if (dollar_vcol >= 0) { dollar_vcol = -1; @@ -1567,7 +1572,7 @@ static void undisplay_dollar(void) /// type == INDENT_DEC decrease indent (for CTRL-D) /// type == INDENT_SET set indent to "amount" /// -/// @param round if TRUE, round the indent to 'shiftwidth' (only with _INC and _Dec). +/// @param round if true, round the indent to 'shiftwidth' (only with _INC and _Dec). /// @param replaced replaced character, put on replace stack /// @param call_changed_bytes call changed_bytes() void change_indent(int type, int amount, int round, int replaced, int call_changed_bytes) @@ -1591,7 +1596,7 @@ void change_indent(int type, int amount, int round, int replaced, int call_chang // for the following tricks we don't want list mode save_p_list = curwin->w_p_list; - curwin->w_p_list = FALSE; + curwin->w_p_list = false; vc = getvcol_nolist(&curwin->w_cursor); vcol = vc; @@ -1659,28 +1664,28 @@ void change_indent(int type, int amount, int round, int replaced, int call_chang } else if (!(State & MODE_INSERT)) { new_cursor_col = curwin->w_cursor.col; } else { - /* - * Compute the screen column where the cursor should be. - */ + // Compute the screen column where the cursor should be. vcol = get_indent() - vcol; curwin->w_virtcol = (colnr_T)((vcol < 0) ? 0 : vcol); - /* - * Advance the cursor until we reach the right screen column. - */ - vcol = last_vcol = 0; - new_cursor_col = -1; + // Advance the cursor until we reach the right screen column. + last_vcol = 0; ptr = get_cursor_line_ptr(); - while (vcol <= (int)curwin->w_virtcol) { - last_vcol = vcol; - if (new_cursor_col >= 0) { - new_cursor_col += utfc_ptr2len((char *)ptr + new_cursor_col); - } else { - new_cursor_col++; + chartabsize_T cts; + 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) { + MB_PTR_ADV(cts.cts_ptr); + } + if (*cts.cts_ptr == NUL) { + break; } - vcol += lbr_chartabsize(ptr, ptr + new_cursor_col, (colnr_T)vcol); + cts.cts_vcol += lbr_chartabsize(&cts); } vcol = last_vcol; + new_cursor_col = (int)(cts.cts_ptr - cts.cts_line); + clear_chartabsize_arg(&cts); /* * May need to insert spaces to be able to position the cursor on @@ -1692,7 +1697,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(ptr); + ins_str((char *)ptr); xfree(ptr); } @@ -1710,7 +1715,7 @@ void change_indent(int type, int amount, int round, int replaced, int call_chang } else { curwin->w_cursor.col = (colnr_T)new_cursor_col; } - curwin->w_set_curswant = TRUE; + curwin->w_set_curswant = true; changed_cline_bef_curs(); /* @@ -1747,7 +1752,7 @@ void change_indent(int type, int amount, int round, int replaced, int call_chang replace_push(replaced); replaced = NUL; } - ++start_col; + start_col++; } } @@ -1772,7 +1777,7 @@ void change_indent(int type, int amount, int round, int replaced, int call_chang backspace_until_column(0); // Insert new stuff into line again - ins_bytes(new_line); + ins_bytes((char *)new_line); xfree(new_line); @@ -1791,7 +1796,7 @@ void change_indent(int type, int amount, int round, int replaced, int call_chang /// Truncate the space at the end of a line. This is to be used only in an /// insert mode. It handles fixing the replace stack for MODE_REPLACE and /// MODE_VREPLACE modes. -void truncate_spaces(char_u *line) +void truncate_spaces(char *line) { int i; @@ -1913,7 +1918,7 @@ int get_literal(bool no_simplify) cc = cc * 10 + nc - '0'; } - ++i; + i++; } if (cc > 255 @@ -1948,7 +1953,7 @@ int get_literal(bool no_simplify) cc = '\n'; } - --no_mapping; + no_mapping--; if (nc) { vungetc(nc); // A character typed with i_CTRL-V_digit cannot have modifiers. @@ -1970,7 +1975,7 @@ static void insert_special(int c, int allow_modmask, int ctrlv) // inserted with ins_str(), so as not to replace characters in replace // mode. // Only use mod_mask for special keys, to avoid things like <S-Space>, - // unless 'allow_modmask' is TRUE. + // unless 'allow_modmask' is true. if (mod_mask & MOD_MASK_CMD) { // Command-key never produces a normal key. allow_modmask = true; } @@ -1983,7 +1988,7 @@ static void insert_special(int c, int allow_modmask, int ctrlv) return; } p[len - 1] = NUL; - ins_str(p); + ins_str((char *)p); AppendToRedobuffLit((char *)p, -1); ctrlv = false; } @@ -2004,9 +2009,6 @@ static void insert_special(int c, int allow_modmask, int ctrlv) */ #define ISSPECIAL(c) ((c) < ' ' || (c) >= DEL || (c) == '0' || (c) == '^') -#define WHITECHAR(cc) (ascii_iswhite(cc) \ - && !utf_iscomposing(utf_ptr2char((char *)get_cursor_pos_ptr() + 1))) - /// /// "flags": INSCHAR_FORMAT - force formatting /// INSCHAR_CTRLV - char typed just after CTRL-V @@ -2022,7 +2024,7 @@ static void insert_special(int c, int allow_modmask, int ctrlv) /// @param second_indent indent for second line if >= 0 void insertchar(int c, int flags, int second_indent) { - char_u *p; + char *p; int force_format = flags & INSCHAR_FORMAT; const int textwidth = comp_textwidth(force_format); @@ -2081,13 +2083,13 @@ 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 = get_cursor_line_ptr(); - int i = get_leader_len((char *)line, (char **)&p, false, true); - if (i > 0 && vim_strchr((char *)p, COM_MIDDLE) != NULL) { // Just checking + int i = get_leader_len((char *)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 p++; } - int middle_len = (int)copy_option_part((char **)&p, (char *)lead_end, COM_MAX_LEN, ","); + int middle_len = (int)copy_option_part(&p, (char *)lead_end, COM_MAX_LEN, ","); // Don't count trailing white space for middle_len while (middle_len > 0 && ascii_iswhite(lead_end[middle_len - 1])) { middle_len--; @@ -2097,7 +2099,7 @@ void insertchar(int c, int flags, int second_indent) while (*p && p[-1] != ':') { // find end of end flags p++; } - int end_len = (int)copy_option_part((char **)&p, (char *)lead_end, COM_MAX_LEN, ","); + int end_len = (int)copy_option_part(&p, (char *)lead_end, COM_MAX_LEN, ","); // Skip white space before the cursor i = curwin->w_cursor.col; @@ -2114,7 +2116,7 @@ void insertchar(int c, int flags, int second_indent) // Insert the end-comment string, except for the last // character, which will get inserted as normal later. - ins_bytes_len(lead_end, (size_t)(end_len - 1)); + ins_bytes_len((char *)lead_end, (size_t)(end_len - 1)); } } } @@ -2174,7 +2176,7 @@ void insertchar(int c, int flags, int second_indent) do_digraph(-1); // clear digraphs do_digraph(buf[i - 1]); // may be the start of a digraph buf[i] = NUL; - ins_str(buf); + ins_str((char *)buf); if (flags & INSCHAR_CTRLV) { redo_literal(*buf); i = 1; @@ -2192,7 +2194,7 @@ void insertchar(int c, int flags, int second_indent) utf_char2bytes(c, (char *)buf); buf[cc] = NUL; - ins_char_bytes((char_u *)buf, (size_t)cc); + ins_char_bytes(buf, (size_t)cc); AppendCharToRedobuff(c); } else { ins_char(c); @@ -2205,597 +2207,6 @@ void insertchar(int c, int flags, int second_indent) } } -/// Format text at the current insert position. -/// -/// If the INSCHAR_COM_LIST flag is present, then the value of second_indent -/// will be the comment leader length sent to open_line(). -/// -/// @param c character to be inserted (can be NUL) -static void internal_format(int textwidth, int second_indent, int flags, int format_only, int c) -{ - int cc; - int save_char = NUL; - bool haveto_redraw = false; - const bool fo_ins_blank = has_format_option(FO_INS_BLANK); - const bool fo_multibyte = has_format_option(FO_MBYTE_BREAK); - const bool fo_rigor_tw = has_format_option(FO_RIGOROUS_TW); - const bool fo_white_par = has_format_option(FO_WHITE_PAR); - bool first_line = true; - colnr_T leader_len; - bool no_leader = false; - int do_comments = (flags & INSCHAR_DO_COM); - int has_lbr = curwin->w_p_lbr; - - // make sure win_lbr_chartabsize() counts correctly - curwin->w_p_lbr = false; - - /* - * When 'ai' is off we don't want a space under the cursor to be - * deleted. Replace it with an 'x' temporarily. - */ - if (!curbuf->b_p_ai - && !(State & VREPLACE_FLAG)) { - cc = gchar_cursor(); - if (ascii_iswhite(cc)) { - save_char = cc; - pchar_cursor('x'); - } - } - - /* - * Repeat breaking lines, until the current line is not too long. - */ - while (!got_int) { - int startcol; // Cursor column at entry - int wantcol; // column at textwidth border - int foundcol; // column for start of spaces - int end_foundcol = 0; // column for start of word - colnr_T len; - colnr_T virtcol; - int orig_col = 0; - char_u *saved_text = NULL; - colnr_T col; - colnr_T end_col; - bool did_do_comment = false; - - virtcol = get_nolist_virtcol() - + char2cells(c != NUL ? c : gchar_cursor()); - if (virtcol <= (colnr_T)textwidth) { - break; - } - - if (no_leader) { - do_comments = false; - } else if (!(flags & INSCHAR_FORMAT) - && has_format_option(FO_WRAP_COMS)) { - do_comments = true; - } - - // Don't break until after the comment leader - if (do_comments) { - char_u *line = get_cursor_line_ptr(); - leader_len = get_leader_len((char *)line, NULL, false, true); - if (leader_len == 0 && curbuf->b_p_cin) { - // Check for a line comment after code. - int comment_start = check_linecomment(line); - if (comment_start != MAXCOL) { - leader_len = get_leader_len((char *)line + comment_start, NULL, false, true); - if (leader_len != 0) { - leader_len += comment_start; - } - } - } - } else { - leader_len = 0; - } - - // If the line doesn't start with a comment leader, then don't - // start one in a following broken line. Avoids that a %word - // moved to the start of the next line causes all following lines - // to start with %. - if (leader_len == 0) { - no_leader = true; - } - if (!(flags & INSCHAR_FORMAT) - && leader_len == 0 - && !has_format_option(FO_WRAP)) { - break; - } - if ((startcol = curwin->w_cursor.col) == 0) { - break; - } - - // find column of textwidth border - coladvance((colnr_T)textwidth); - wantcol = curwin->w_cursor.col; - - curwin->w_cursor.col = startcol; - foundcol = 0; - int skip_pos = 0; - - /* - * Find position to break at. - * Stop at first entered white when 'formatoptions' has 'v' - */ - while ((!fo_ins_blank && !has_format_option(FO_INS_VI)) - || (flags & INSCHAR_FORMAT) - || curwin->w_cursor.lnum != Insstart.lnum - || curwin->w_cursor.col >= Insstart.col) { - if (curwin->w_cursor.col == startcol && c != NUL) { - cc = c; - } else { - cc = gchar_cursor(); - } - if (WHITECHAR(cc)) { - // remember position of blank just before text - end_col = curwin->w_cursor.col; - - // find start of sequence of blanks - int wcc = 0; // counter for whitespace chars - while (curwin->w_cursor.col > 0 && WHITECHAR(cc)) { - dec_cursor(); - cc = gchar_cursor(); - - // Increment count of how many whitespace chars in this - // group; we only need to know if it's more than one. - if (wcc < 2) { - wcc++; - } - } - if (curwin->w_cursor.col == 0 && WHITECHAR(cc)) { - break; // only spaces in front of text - } - - // Don't break after a period when 'formatoptions' has 'p' and - // there are less than two spaces. - if (has_format_option(FO_PERIOD_ABBR) && cc == '.' && wcc < 2) { - continue; - } - - // Don't break until after the comment leader - if (curwin->w_cursor.col < leader_len) { - break; - } - - if (has_format_option(FO_ONE_LETTER)) { - // do not break after one-letter words - if (curwin->w_cursor.col == 0) { - break; // one-letter word at begin - } - // do not break "#a b" when 'tw' is 2 - if (curwin->w_cursor.col <= leader_len) { - break; - } - col = curwin->w_cursor.col; - dec_cursor(); - cc = gchar_cursor(); - - if (WHITECHAR(cc)) { - continue; // one-letter, continue - } - curwin->w_cursor.col = col; - } - - inc_cursor(); - - end_foundcol = end_col + 1; - foundcol = curwin->w_cursor.col; - if (curwin->w_cursor.col <= (colnr_T)wantcol) { - break; - } - } else if ((cc >= 0x100 || !utf_allow_break_before(cc)) && fo_multibyte) { - int ncc; - bool allow_break; - - // Break after or before a multi-byte character. - if (curwin->w_cursor.col != startcol) { - // Don't break until after the comment leader - if (curwin->w_cursor.col < leader_len) { - break; - } - col = curwin->w_cursor.col; - inc_cursor(); - ncc = gchar_cursor(); - allow_break = utf_allow_break(cc, ncc); - - // If we have already checked this position, skip! - if (curwin->w_cursor.col != skip_pos && allow_break) { - foundcol = curwin->w_cursor.col; - end_foundcol = foundcol; - if (curwin->w_cursor.col <= (colnr_T)wantcol) { - break; - } - } - curwin->w_cursor.col = col; - } - - if (curwin->w_cursor.col == 0) { - break; - } - - ncc = cc; - col = curwin->w_cursor.col; - - dec_cursor(); - cc = gchar_cursor(); - - if (WHITECHAR(cc)) { - continue; // break with space - } - // Don't break until after the comment leader. - if (curwin->w_cursor.col < leader_len) { - break; - } - - curwin->w_cursor.col = col; - skip_pos = curwin->w_cursor.col; - - allow_break = utf_allow_break(cc, ncc); - - // Must handle this to respect line break prohibition. - if (allow_break) { - foundcol = curwin->w_cursor.col; - end_foundcol = foundcol; - } - if (curwin->w_cursor.col <= (colnr_T)wantcol) { - const bool ncc_allow_break = utf_allow_break_before(ncc); - - if (allow_break) { - break; - } - if (!ncc_allow_break && !fo_rigor_tw) { - // Enable at most 1 punct hang outside of textwidth. - if (curwin->w_cursor.col == startcol) { - // We are inserting a non-breakable char, postpone - // line break check to next insert. - end_foundcol = foundcol = 0; - break; - } - - // Neither cc nor ncc is NUL if we are here, so - // it's safe to inc_cursor. - col = curwin->w_cursor.col; - - inc_cursor(); - cc = ncc; - ncc = gchar_cursor(); - // handle insert - ncc = (ncc != NUL) ? ncc : c; - - allow_break = utf_allow_break(cc, ncc); - - if (allow_break) { - // Break only when we are not at end of line. - end_foundcol = foundcol = ncc == NUL? 0 : curwin->w_cursor.col; - break; - } - curwin->w_cursor.col = col; - } - } - } - if (curwin->w_cursor.col == 0) { - break; - } - dec_cursor(); - } - - if (foundcol == 0) { // no spaces, cannot break line - curwin->w_cursor.col = startcol; - break; - } - - // Going to break the line, remove any "$" now. - undisplay_dollar(); - - // Offset between cursor position and line break is used by replace - // stack functions. MODE_VREPLACE does not use this, and backspaces - // over the text instead. - if (State & VREPLACE_FLAG) { - orig_col = startcol; // Will start backspacing from here - } else { - replace_offset = startcol - end_foundcol; - } - - /* - * adjust startcol for spaces that will be deleted and - * characters that will remain on top line - */ - curwin->w_cursor.col = foundcol; - while ((cc = gchar_cursor(), WHITECHAR(cc)) - && (!fo_white_par || curwin->w_cursor.col < startcol)) { - inc_cursor(); - } - startcol -= curwin->w_cursor.col; - if (startcol < 0) { - startcol = 0; - } - - if (State & VREPLACE_FLAG) { - // In MODE_VREPLACE state, we will backspace over the text to be - // wrapped, so save a copy now to put on the next line. - saved_text = vim_strsave(get_cursor_pos_ptr()); - curwin->w_cursor.col = orig_col; - saved_text[startcol] = NUL; - - // Backspace over characters that will move to the next line - if (!fo_white_par) { - backspace_until_column(foundcol); - } - } else { - // put cursor after pos. to break line - if (!fo_white_par) { - curwin->w_cursor.col = foundcol; - } - } - - /* - * Split the line just before the margin. - * Only insert/delete lines, but don't really redraw the window. - */ - open_line(FORWARD, OPENLINE_DELSPACES + OPENLINE_MARKFIX - + (fo_white_par ? OPENLINE_KEEPTRAIL : 0) - + (do_comments ? OPENLINE_DO_COM : 0) - + OPENLINE_FORMAT - + ((flags & INSCHAR_COM_LIST) ? OPENLINE_COM_LIST : 0), - ((flags & INSCHAR_COM_LIST) ? second_indent : old_indent), - &did_do_comment); - if (!(flags & INSCHAR_COM_LIST)) { - old_indent = 0; - } - - // If a comment leader was inserted, may also do this on a following - // line. - if (did_do_comment) { - no_leader = false; - } - - replace_offset = 0; - if (first_line) { - if (!(flags & INSCHAR_COM_LIST)) { - // This section is for auto-wrap of numeric lists. When not - // in insert mode (i.e. format_lines()), the INSCHAR_COM_LIST - // flag will be set and open_line() will handle it (as seen - // above). The code here (and in get_number_indent()) will - // recognize comments if needed... - if (second_indent < 0 && has_format_option(FO_Q_NUMBER)) { - second_indent = get_number_indent(curwin->w_cursor.lnum - 1); - } - if (second_indent >= 0) { - if (State & VREPLACE_FLAG) { - change_indent(INDENT_SET, second_indent, false, NUL, true); - } else if (leader_len > 0 && second_indent - leader_len > 0) { - int padding = second_indent - leader_len; - - // We started at the first_line of a numbered list - // that has a comment. the open_line() function has - // inserted the proper comment leader and positioned - // the cursor at the end of the split line. Now we - // add the additional whitespace needed after the - // comment leader for the numbered list. - for (int i = 0; i < padding; i++) { - ins_str((char_u *)" "); - } - changed_bytes(curwin->w_cursor.lnum, leader_len); - } else { - (void)set_indent(second_indent, SIN_CHANGED); - } - } - } - first_line = false; - } - - if (State & VREPLACE_FLAG) { - // In MODE_VREPLACE state we have backspaced over the text to be - // moved, now we re-insert it into the new line. - ins_bytes(saved_text); - xfree(saved_text); - } else { - /* - * Check if cursor is not past the NUL off the line, cindent - * may have added or removed indent. - */ - curwin->w_cursor.col += startcol; - len = (colnr_T)STRLEN(get_cursor_line_ptr()); - if (curwin->w_cursor.col > len) { - curwin->w_cursor.col = len; - } - } - - haveto_redraw = true; - can_cindent = true; - // moved the cursor, don't autoindent or cindent now - did_ai = false; - did_si = false; - can_si = false; - can_si_back = false; - line_breakcheck(); - } - - if (save_char != NUL) { // put back space after cursor - pchar_cursor((char_u)save_char); - } - - curwin->w_p_lbr = has_lbr; - - if (!format_only && haveto_redraw) { - update_topline(curwin); - redraw_curbuf_later(VALID); - } -} - -/// Called after inserting or deleting text: When 'formatoptions' includes the -/// 'a' flag format from the current line until the end of the paragraph. -/// Keep the cursor at the same position relative to the text. -/// The caller must have saved the cursor line for undo, following ones will be -/// saved here. -/// -/// @param trailblank when true also format with trailing blank -/// @param prev_line may start in previous line -void auto_format(bool trailblank, bool prev_line) -{ - pos_T pos; - colnr_T len; - char_u *old; - char_u *new, *pnew; - int wasatend; - int cc; - - if (!has_format_option(FO_AUTO)) { - return; - } - - pos = curwin->w_cursor; - old = get_cursor_line_ptr(); - - // may remove added space - check_auto_format(false); - - // Don't format in Insert mode when the cursor is on a trailing blank, the - // user might insert normal text next. Also skip formatting when "1" is - // in 'formatoptions' and there is a single character before the cursor. - // Otherwise the line would be broken and when typing another non-white - // next they are not joined back together. - wasatend = (pos.col == (colnr_T)STRLEN(old)); - if (*old != NUL && !trailblank && wasatend) { - dec_cursor(); - cc = gchar_cursor(); - if (!WHITECHAR(cc) && curwin->w_cursor.col > 0 - && has_format_option(FO_ONE_LETTER)) { - dec_cursor(); - } - cc = gchar_cursor(); - if (WHITECHAR(cc)) { - curwin->w_cursor = pos; - return; - } - curwin->w_cursor = pos; - } - - // With the 'c' flag in 'formatoptions' and 't' missing: only format - // comments. - if (has_format_option(FO_WRAP_COMS) && !has_format_option(FO_WRAP) - && get_leader_len((char *)old, NULL, false, true) == 0) { - return; - } - - /* - * May start formatting in a previous line, so that after "x" a word is - * moved to the previous line if it fits there now. Only when this is not - * the start of a paragraph. - */ - if (prev_line && !paragraph_start(curwin->w_cursor.lnum)) { - --curwin->w_cursor.lnum; - if (u_save_cursor() == FAIL) { - return; - } - } - - /* - * Do the formatting and restore the cursor position. "saved_cursor" will - * be adjusted for the text formatting. - */ - saved_cursor = pos; - format_lines((linenr_T) - 1, false); - curwin->w_cursor = saved_cursor; - saved_cursor.lnum = 0; - - if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) { - // "cannot happen" - curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; - coladvance(MAXCOL); - } else { - check_cursor_col(); - } - - // Insert mode: If the cursor is now after the end of the line while it - // previously wasn't, the line was broken. Because of the rule above we - // need to add a space when 'w' is in 'formatoptions' to keep a paragraph - // formatted. - if (!wasatend && has_format_option(FO_WHITE_PAR)) { - new = get_cursor_line_ptr(); - len = (colnr_T)STRLEN(new); - if (curwin->w_cursor.col == len) { - pnew = vim_strnsave(new, (size_t)len + 2); - pnew[len] = ' '; - pnew[len + 1] = NUL; - ml_replace(curwin->w_cursor.lnum, (char *)pnew, false); - // remove the space later - did_add_space = true; - } else { - // may remove added space - check_auto_format(false); - } - } - - check_cursor(); -} - -/// When an extra space was added to continue a paragraph for auto-formatting, -/// delete it now. The space must be under the cursor, just after the insert -/// position. -/// -/// @param end_insert true when ending Insert mode -static void check_auto_format(bool end_insert) -{ - int c = ' '; - int cc; - - if (did_add_space) { - cc = gchar_cursor(); - if (!WHITECHAR(cc)) { - // Somehow the space was removed already. - did_add_space = false; - } else { - if (!end_insert) { - inc_cursor(); - c = gchar_cursor(); - dec_cursor(); - } - if (c != NUL) { - // The space is no longer at the end of the line, delete it. - del_char(false); - did_add_space = false; - } - } - } -} - -/// Find out textwidth to be used for formatting: -/// if 'textwidth' option is set, use it -/// else if 'wrapmargin' option is set, use curwin->w_width_inner-'wrapmargin' -/// if invalid value, use 0. -/// Set default to window width (maximum 79) for "gq" operator. -/// -/// @param ff force formatting (for "gq" command) -int comp_textwidth(bool ff) -{ - int textwidth = (int)curbuf->b_p_tw; - if (textwidth == 0 && curbuf->b_p_wm) { - // The width is the window width minus 'wrapmargin' minus all the - // things that add to the margin. - textwidth = curwin->w_width_inner - (int)curbuf->b_p_wm; - if (cmdwin_type != 0) { - textwidth -= 1; - } - textwidth -= win_fdccol_count(curwin); - textwidth -= win_signcol_count(curwin); - - if (curwin->w_p_nu || curwin->w_p_rnu) { - textwidth -= 8; - } - } - if (textwidth < 0) { - textwidth = 0; - } - if (ff && textwidth == 0) { - textwidth = curwin->w_width_inner - 1; - if (textwidth > 79) { - textwidth = 79; - } - } - return textwidth; -} - /* * Put a character in the redo buffer, for when just after a CTRL-V. */ @@ -2985,7 +2396,7 @@ static void stop_insert(pos_T *end_insert_pos, int esc, int nomove) check_cursor_col(); // make sure it is not past the line for (;;) { if (gchar_cursor() == NUL && curwin->w_cursor.col > 0) { - --curwin->w_cursor.col; + curwin->w_cursor.col--; } cc = gchar_cursor(); if (!ascii_iswhite(cc)) { @@ -3002,7 +2413,7 @@ static void stop_insert(pos_T *end_insert_pos, int esc, int nomove) tpos = curwin->w_cursor; tpos.col++; if (cc != NUL && gchar_pos(&tpos) == NUL) { - ++curwin->w_cursor.col; // put cursor back on the NUL + curwin->w_cursor.col++; // put cursor back on the NUL } } @@ -3074,11 +2485,11 @@ void beginline(int flags) char_u *ptr; for (ptr = get_cursor_line_ptr(); ascii_iswhite(*ptr) - && !((flags & BL_FIX) && ptr[1] == NUL); ++ptr) { - ++curwin->w_cursor.col; + && !((flags & BL_FIX) && ptr[1] == NUL); ptr++) { + curwin->w_cursor.col++; } } - curwin->w_set_curswant = TRUE; + curwin->w_set_curswant = true; } } @@ -3122,7 +2533,7 @@ int oneright(void) } curwin->w_cursor.col += l; - curwin->w_set_curswant = TRUE; + curwin->w_set_curswant = true; return OK; } @@ -3157,7 +2568,7 @@ int oneleft(void) } } - curwin->w_set_curswant = TRUE; + curwin->w_set_curswant = true; return OK; } @@ -3165,8 +2576,8 @@ int oneleft(void) return FAIL; } - curwin->w_set_curswant = TRUE; - --curwin->w_cursor.col; + curwin->w_set_curswant = true; + curwin->w_cursor.col--; // if the character on the left of the current cursor is a multi-byte // character, move to its first byte @@ -3174,7 +2585,7 @@ int oneleft(void) return OK; } -/// @oaram upd_topline When TRUE: update topline +/// @oaram upd_topline When true: update topline int cursor_up(long n, int upd_topline) { linenr_T lnum; @@ -3229,7 +2640,7 @@ int cursor_up(long n, int upd_topline) /// Cursor down a number of logical lines. /// -/// @param upd_topline When TRUE: update topline +/// @param upd_topline When true: update topline int cursor_down(long n, int upd_topline) { linenr_T lnum; @@ -3253,7 +2664,7 @@ int cursor_down(long n, int upd_topline) if (hasFolding(lnum, NULL, &last)) { lnum = last + 1; } else { - ++lnum; + lnum++; } if (lnum >= curbuf->b_ml.ml_line_count) { break; @@ -3287,12 +2698,12 @@ int cursor_down(long n, int upd_topline) /// @param no_esc Don't add an ESC at the end int stuff_inserted(int c, long count, int no_esc) { - char_u *esc_ptr; - char_u *ptr; - char_u *last_ptr; - char_u last = NUL; + char *esc_ptr; + char *ptr; + char *last_ptr; + char last = NUL; - ptr = get_last_insert(); + ptr = (char *)get_last_insert(); if (ptr == NULL) { emsg(_(e_noinstext)); return FAIL; @@ -3302,7 +2713,7 @@ int stuff_inserted(int c, long count, int no_esc) if (c != NUL) { stuffcharReadbuff(c); } - if ((esc_ptr = STRRCHR(ptr, ESC)) != NULL) { + if ((esc_ptr = strrchr(ptr, ESC)) != NULL) { // remove the ESC. *esc_ptr = NUL; } @@ -3433,20 +2844,19 @@ void replace_push(int c) memmove(p + 1, p, (size_t)replace_offset); } *p = (char_u)c; - ++replace_stack_nr; + replace_stack_nr++; } -/* - * Push a character onto the replace stack. Handles a multi-byte character in - * reverse byte order, so that the first byte is popped off first. - * Return the number of bytes done (includes composing characters). - */ -int replace_push_mb(char_u *p) +/// Push a character onto the replace stack. Handles a multi-byte character in +/// reverse byte order, so that the first byte is popped off first. +/// +/// @return the number of bytes done (includes composing characters). +int replace_push_mb(char *p) { - int l = utfc_ptr2len((char *)p); + int l = utfc_ptr2len(p); int j; - for (j = l - 1; j >= 0; --j) { + for (j = l - 1; j >= 0; j--) { replace_push(p[j]); } return l; @@ -3468,7 +2878,7 @@ static void replace_join(int off) { for (ssize_t i = replace_stack_nr; --i >= 0;) { if (replace_stack[i] == NUL && off-- <= 0) { - --replace_stack_nr; + replace_stack_nr--; memmove(replace_stack + i, replace_stack + i + 1, (size_t)(replace_stack_nr - i)); return; @@ -3507,7 +2917,7 @@ static void mb_replace_pop_ins(int cc) for (i = 1; i < n; i++) { buf[i] = (char_u)replace_pop(); } - ins_bytes_len(buf, (size_t)n); + ins_bytes_len((char *)buf, (size_t)n); } else { ins_char(cc); } @@ -3530,7 +2940,7 @@ static void mb_replace_pop_ins(int cc) buf[i] = (char_u)replace_pop(); } if (utf_iscomposing(utf_ptr2char((char *)buf))) { - ins_bytes_len(buf, (size_t)n); + ins_bytes_len((char *)buf, (size_t)n); } else { // Not a composing char, put it back. for (i = n - 1; i >= 0; i--) { @@ -3579,7 +2989,7 @@ static void replace_do_bs(int limit_col) // Get the number of screen cells used by the character we are // going to delete. getvcol(curwin, &curwin->w_cursor, NULL, &start_vcol, NULL); - orig_vcols = win_chartabsize(curwin, get_cursor_pos_ptr(), start_vcol); + orig_vcols = win_chartabsize(curwin, (char *)get_cursor_pos_ptr(), start_vcol); } (void)del_char_after_col(limit_col); if (l_State & VREPLACE_FLAG) { @@ -3594,7 +3004,7 @@ static void replace_do_bs(int limit_col) ins_len = (int)STRLEN(p) - orig_len; vcol = start_vcol; for (i = 0; i < ins_len; i++) { - vcol += win_chartabsize(curwin, p + i, vcol); + vcol += win_chartabsize(curwin, (char *)p + i, vcol); i += utfc_ptr2len((char *)p) - 1; } vcol -= start_vcol; @@ -3656,7 +3066,7 @@ void fix_indent(void) /// 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) -/// when == ' ': Only if key is not preceded with '*' (indent afterwards) +/// when == ' ': Only if key is not preceded with '*' or '!' (indent afterwards) /// /// "keytyped" can have a few special values: /// KEY_OPEN_FORW : @@ -3681,9 +3091,9 @@ bool in_cinkeys(int keytyped, int when, bool line_is_empty) } if (*curbuf->b_p_inde != NUL) { - look = curbuf->b_p_indk; // 'indentexpr' set: use 'indentkeys' + look = (char_u *)curbuf->b_p_indk; // 'indentexpr' set: use 'indentkeys' } else { - look = curbuf->b_p_cink; // 'indentexpr' empty: use 'cinkeys' + look = (char_u *)curbuf->b_p_cink; // 'indentexpr' empty: use 'cinkeys' } while (*look) { /* @@ -3696,7 +3106,7 @@ bool in_cinkeys(int keytyped, int when, bool line_is_empty) case '!': try_match = (*look == '!'); break; default: - try_match = (*look != '*'); break; + try_match = (*look != '*') && (*look != '!'); break; } if (*look == '*' || *look == '!') { look++; @@ -3794,12 +3204,9 @@ bool in_cinkeys(int keytyped, int when, bool line_is_empty) while (*look == '>') { look++; } - } - /* - * Is it a word: "=word"? - */ - else if (*look == '=' && look[1] != ',' && look[1] != NUL) { - ++look; + // Is it a word: "=word"? + } else if (*look == '=' && look[1] != ',' && look[1] != NUL) { + look++; if (*look == '~') { icase = true; look++; @@ -3873,10 +3280,8 @@ bool in_cinkeys(int keytyped, int when, bool line_is_empty) } } - /* - * Skip over ", ". - */ - look = skip_to_option_part(look); + // Skip over ", ". + look = (char_u *)skip_to_option_part((char *)look); } return false; } @@ -4002,13 +3407,13 @@ static void ins_reg(void) no_mapping++; allow_keys++; regname = plain_vgetc(); - LANGMAP_ADJUST(regname, TRUE); + LANGMAP_ADJUST(regname, true); if (regname == Ctrl_R || regname == Ctrl_O || regname == Ctrl_P) { // Get a third key for literal register insertion literally = regname; add_to_showcmd_c(literally); regname = plain_vgetc(); - LANGMAP_ADJUST(regname, TRUE); + LANGMAP_ADJUST(regname, true); } no_mapping--; allow_keys--; @@ -4046,7 +3451,7 @@ static void ins_reg(void) need_redraw = true; // remove the '"' } else if (stop_insert_mode) { // When the '=' register was used and a function was invoked that - // did ":stopinsert" then stuff_empty() returns FALSE but we won't + // did ":stopinsert" then stuff_empty() returns false but we won't // insert anything, need to remove the '"' need_redraw = true; } @@ -4197,7 +3602,7 @@ static bool ins_esc(long *count, int cmdchar, bool nomove) // Repeat the insert return false; } - stop_insert(&curwin->w_cursor, TRUE, nomove); + stop_insert(&curwin->w_cursor, true, nomove); undisplay_dollar(); } @@ -4251,7 +3656,7 @@ static bool ins_esc(long *count, int cmdchar, bool nomove) // Otherwise remove the mode message. if (reg_recording != 0 || restart_edit != NUL) { showmode(); - } else if (p_smd) { + } else if (p_smd && (got_int || !skip_showmode())) { msg(""); } // Exit Insert mode @@ -4266,7 +3671,7 @@ static void ins_ctrl_(void) { if (revins_on && revins_chars && revins_scol >= 0) { while (gchar_cursor() != NUL && revins_chars--) { - ++curwin->w_cursor.col; + curwin->w_cursor.col++; } } p_ri = !p_ri; @@ -4395,9 +3800,9 @@ static void ins_shift(int c, int lastc) if (lastc == '^') { old_indent = get_indent(); // remember curr. indent } - change_indent(INDENT_SET, 0, TRUE, 0, TRUE); + change_indent(INDENT_SET, 0, true, 0, true); } else { - change_indent(c == Ctrl_D ? INDENT_DEC : INDENT_INC, 0, TRUE, 0, TRUE); + change_indent(c == Ctrl_D ? INDENT_DEC : INDENT_INC, 0, true, 0, true); } if (did_ai && *skipwhite((char *)get_cursor_line_ptr()) != NUL) { @@ -4569,7 +3974,7 @@ static bool ins_bs(int c, int mode, int *inserted_space_p) } } - do_join(2, FALSE, FALSE, FALSE, false); + do_join(2, false, false, false, false); if (temp == NUL && gchar_cursor() != NUL) { inc_cursor(); } @@ -4671,7 +4076,7 @@ static bool ins_bs(int c, int mode, int *inserted_space_p) if (State & VREPLACE_FLAG) { ins_char(' '); } else { - ins_str((char_u *)" "); + ins_str(" "); if ((State & REPLACE_FLAG)) { replace_push(NUL); } @@ -4715,7 +4120,7 @@ static bool ins_bs(int c, int mode, int *inserted_space_p) } else { const int l_p_deco = p_deco; if (l_p_deco) { - (void)utfc_ptr2char(get_cursor_pos_ptr(), cpc); + (void)utfc_ptr2char((char *)get_cursor_pos_ptr(), cpc); } (void)del_char(false); // If there are combining characters and 'delcombine' is set @@ -4851,7 +4256,7 @@ static void ins_mousescroll(int dir) } } - curwin->w_redr_status = TRUE; + curwin->w_redr_status = true; curwin = old_curwin; curbuf = curwin->w_buffer; @@ -4882,7 +4287,7 @@ static void ins_left(void) revins_legal++; } revins_chars++; - } else if (vim_strchr((char *)p_ww, '[') != NULL && curwin->w_cursor.lnum > 1) { + } else if (vim_strchr(p_ww, '[') != NULL && curwin->w_cursor.lnum > 1) { // if 'whichwrap' set for cursor in insert mode may go to previous line. // always break undo when moving upwards/downwards, else undo may break start_arrow(&tpos); @@ -4975,7 +4380,7 @@ static void ins_right(void) if (revins_chars) { revins_chars--; } - } else if (vim_strchr((char *)p_ww, ']') != NULL + } else if (vim_strchr(p_ww, ']') != NULL && curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) { // if 'whichwrap' set for cursor in insert mode, may move the // cursor to the next line @@ -5019,13 +4424,13 @@ static void ins_up(bool startcol) undisplay_dollar(); tpos = curwin->w_cursor; - if (cursor_up(1L, TRUE) == OK) { + if (cursor_up(1L, true) == OK) { if (startcol) { coladvance(getvcol_nolist(&Insstart)); } if (old_topline != curwin->w_topline || old_topfill != curwin->w_topfill) { - redraw_later(curwin, VALID); + redraw_later(curwin, UPD_VALID); } start_arrow(&tpos); can_cindent = true; @@ -5067,13 +4472,13 @@ static void ins_down(bool startcol) undisplay_dollar(); tpos = curwin->w_cursor; - if (cursor_down(1L, TRUE) == OK) { + if (cursor_down(1L, true) == OK) { if (startcol) { coladvance(getvcol_nolist(&Insstart)); } if (old_topline != curwin->w_topline || old_topfill != curwin->w_topfill) { - redraw_later(curwin, VALID); + redraw_later(curwin, UPD_VALID); } start_arrow(&tpos); can_cindent = true; @@ -5177,7 +4582,7 @@ static bool ins_tab(void) if (State & VREPLACE_FLAG) { ins_char(' '); } else { - ins_str((char_u *)" "); + ins_str(" "); if (State & REPLACE_FLAG) { // no char replaced replace_push(NUL); } @@ -5219,8 +4624,8 @@ static bool ins_tab(void) // Find first white before the cursor fpos = curwin->w_cursor; while (fpos.col > 0 && ascii_iswhite(ptr[-1])) { - --fpos.col; - --ptr; + fpos.col--; + ptr--; } // In Replace mode, don't change characters before the insert point. @@ -5235,11 +4640,15 @@ static bool ins_tab(void) getvcol(curwin, &fpos, &vcol, NULL, NULL); getvcol(curwin, cursor, &want_vcol, NULL, NULL); + char_u *tab = (char_u *)"\t"; + chartabsize_T cts; + 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. while (ascii_iswhite(*ptr)) { - i = lbr_chartabsize(NULL, (char_u *)"\t", vcol); - if (vcol + i > want_vcol) { + i = lbr_chartabsize(&cts); + if (cts.cts_vcol + i > want_vcol) { break; } if (*ptr != TAB) { @@ -5252,21 +4661,26 @@ static bool ins_tab(void) } } } - ++fpos.col; - ++ptr; - vcol += i; + fpos.col++; + ptr++; + cts.cts_vcol += i; } + vcol = cts.cts_vcol; + clear_chartabsize_arg(&cts); if (change_col >= 0) { int repl_off = 0; - char_u *line = ptr; - // Skip over the spaces we need. - while (vcol < want_vcol && *ptr == ' ') { - vcol += lbr_chartabsize(line, ptr, vcol); - ++ptr; - ++repl_off; + 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; + vcol = cts.cts_vcol; + clear_chartabsize_arg(&cts); + if (vcol > want_vcol) { // Must have a char with 'showbreak' just before it. ptr--; @@ -5302,7 +4716,7 @@ static bool ins_tab(void) // Insert each char in saved_line from changed_col to // ptr-cursor - ins_bytes_len(saved_line + change_col, (size_t)(cursor->col - change_col)); + ins_bytes_len((char *)saved_line + change_col, (size_t)(cursor->col - change_col)); } } @@ -5402,7 +4816,7 @@ static int ins_digraph(void) if (IS_SPECIAL(c) || mod_mask) { // special key clear_showcmd(); - insert_special(c, TRUE, FALSE); + insert_special(c, true, false); return NUL; } if (c != ESC) { @@ -5446,7 +4860,6 @@ static int ins_digraph(void) int ins_copychar(linenr_T lnum) { int c; - int temp; char_u *ptr, *prev_ptr; char_u *line; @@ -5456,17 +4869,23 @@ int ins_copychar(linenr_T lnum) } // try to advance to the cursor column - temp = 0; - line = ptr = ml_get(lnum); - prev_ptr = ptr; + line = ml_get(lnum); + prev_ptr = line; validate_virtcol(); - while ((colnr_T)temp < curwin->w_virtcol && *ptr != NUL) { - prev_ptr = ptr; - temp += lbr_chartabsize_adv(line, &ptr, (colnr_T)temp); + + chartabsize_T cts; + 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; + cts.cts_vcol += lbr_chartabsize_adv(&cts); } - if ((colnr_T)temp > curwin->w_virtcol) { + + if (cts.cts_vcol > curwin->w_virtcol) { ptr = prev_ptr; + } else { + ptr = (char_u *)cts.cts_ptr; } + clear_chartabsize_arg(&cts); c = utf_ptr2char((char *)ptr); if (c == NUL) { @@ -5488,7 +4907,7 @@ static int ins_ctrl_ey(int tc) } else { scrollup_clamp(); } - redraw_later(curwin, VALID); + redraw_later(curwin, UPD_VALID); } else { c = ins_copychar(curwin->w_cursor.lnum + (c == Ctrl_Y ? -1 : 1)); if (c != NUL) { @@ -5503,7 +4922,7 @@ static int ins_ctrl_ey(int tc) } tw_save = curbuf->b_p_tw; curbuf->b_p_tw = -1; - insert_special(c, TRUE, FALSE); + insert_special(c, true, false); curbuf->b_p_tw = tw_save; revins_chars++; revins_legal++; @@ -5552,7 +4971,7 @@ static void ins_try_si(int c) i = get_indent(); curwin->w_cursor = old_pos; if (State & VREPLACE_FLAG) { - change_indent(INDENT_SET, i, FALSE, NUL, TRUE); + change_indent(INDENT_SET, i, false, NUL, true); } else { (void)set_indent(i, SIN_CHANGED); } @@ -5577,7 +4996,7 @@ static void ins_try_si(int c) curwin->w_cursor = old_pos; } if (temp) { - shift_line(TRUE, FALSE, 1, TRUE); + shift_line(true, false, 1, true); } } } @@ -5655,11 +5074,16 @@ static char_u *do_insert_char_pre(int c) return res; } -bool can_cindent_get(void) +bool get_can_cindent(void) { return can_cindent; } +void set_can_cindent(bool val) +{ + can_cindent = val; +} + /// Trigger "event" and take care of fixing undo. int ins_apply_autocmds(event_T event) { diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 5911a93099..2dbaa2f8ac 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -1,24 +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 -/* - * eval.c: Expression evaluation. - */ +// eval.c: Expression evaluation. #include <math.h> #include <stdlib.h> -#include "auto/config.h" - -#ifdef HAVE_LOCALE_H -# include <locale.h> -#endif - #include "nvim/ascii.h" +#include "nvim/autocmd.h" #include "nvim/buffer.h" #include "nvim/change.h" #include "nvim/channel.h" #include "nvim/charset.h" +#include "nvim/cmdhist.h" #include "nvim/cursor.h" #include "nvim/edit.h" #include "nvim/eval.h" @@ -28,23 +22,29 @@ #include "nvim/eval/typval.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_getln.h" #include "nvim/ex_session.h" -#include "nvim/fileio.h" #include "nvim/getchar.h" #include "nvim/highlight_group.h" +#include "nvim/locale.h" #include "nvim/lua/executor.h" #include "nvim/mark.h" #include "nvim/memline.h" #include "nvim/move.h" #include "nvim/ops.h" #include "nvim/option.h" +#include "nvim/optionstr.h" #include "nvim/os/input.h" #include "nvim/os/shell.h" #include "nvim/path.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" @@ -71,19 +71,15 @@ static char * const namespace_char = "abglstvw"; /// Variable used for g: static ScopeDictDictItem globvars_var; -/* - * Old Vim variables such as "v:version" are also available without the "v:". - * Also in functions. We need a special hashtable for them. - */ +/// Old Vim variables such as "v:version" are also available without the "v:". +/// Also in functions. We need a special hashtable for them. static hashtab_T compat_hashtab; /// Used for checking if local variables or arguments used in a lambda. bool *eval_lavars_used = NULL; -/* - * Array to hold the hashtab with variables local to each sourced script. - * Each item holds a variable (nameless) that points to the dict_T. - */ +/// Array to hold the hashtab with variables local to each sourced script. +/// Each item holds a variable (nameless) that points to the dict_T. typedef struct { ScopeDictDictItem sv_var; dict_T sv_dict; @@ -98,11 +94,9 @@ static int echo_attr = 0; // attributes used for ":echo" // The names of packages that once were loaded are remembered. static garray_T ga_loaded = { 0, 0, sizeof(char *), 4, NULL }; -/* - * Info used by a ":for" loop. - */ +/// Info used by a ":for" loop. typedef struct { - int fi_semicolon; // TRUE if ending in '; var]' + int fi_semicolon; // true if ending in '; var]' int fi_varcount; // nr of variables in the list listwatch_T fi_lw; // keep an eye on the item used. list_T *fi_list; // list being used @@ -356,8 +350,6 @@ void eval_init(void) { vimvars[VV_VERSION].vv_nr = VIM_VERSION_100; - struct vimvar *p; - init_var_dict(&globvardict, &globvars_var, VAR_DEF_SCOPE); init_var_dict(&vimvardict, &vimvars_var, VAR_SCOPE); vimvardict.dv_lock = VAR_FIXED; @@ -365,7 +357,7 @@ void eval_init(void) func_init(); for (size_t i = 0; i < ARRAY_SIZE(vimvars); i++) { - p = &vimvars[i]; + struct vimvar *p = &vimvars[i]; assert(STRLEN(p->vv_name) <= VIMVAR_KEY_LEN); STRCPY(p->vv_di.di_key, p->vv_name); if (p->vv_flags & VV_RO) { @@ -447,10 +439,8 @@ void eval_init(void) #if defined(EXITFREE) void eval_clear(void) { - struct vimvar *p; - for (size_t i = 0; i < ARRAY_SIZE(vimvars); i++) { - p = &vimvars[i]; + struct vimvar *p = &vimvars[i]; if (p->vv_di.di_tv.v_type == VAR_STRING) { XFREE_CLEAR(p->vv_str); } else if (p->vv_di.di_tv.v_type == VAR_LIST) { @@ -473,13 +463,13 @@ void eval_clear(void) // autoloaded script names ga_clear_strings(&ga_loaded); - /* Script-local variables. First clear all the variables and in a second - * loop free the scriptvar_T, because a variable in one script might hold - * a reference to the whole scope of another script. */ - for (int i = 1; i <= ga_scripts.ga_len; ++i) { + // Script-local variables. First clear all the variables and in a second + // loop free the scriptvar_T, because a variable in one script might hold + // a reference to the whole scope of another script. + for (int i = 1; i <= ga_scripts.ga_len; i++) { vars_clear(&SCRIPT_VARS(i)); } - for (int i = 1; i <= ga_scripts.ga_len; ++i) { + for (int i = 1; i <= ga_scripts.ga_len; i++) { xfree(SCRIPT_SV(i)); } ga_clear(&ga_scripts); @@ -518,10 +508,6 @@ static char *redir_varname = NULL; /// @return OK if successfully completed the setup. FAIL otherwise. int var_redir_start(char *name, int append) { - int save_emsg; - int err; - typval_T tv; - // Catch a bad name early. if (!eval_isnamec1(*name)) { emsg(_(e_invarg)); @@ -553,10 +539,11 @@ int var_redir_start(char *name, int append) return FAIL; } - /* check if we can write to the variable: set it to or append an empty - * string */ - save_emsg = did_emsg; - did_emsg = FALSE; + // check if we can write to the variable: set it to or append an empty + // string + int save_emsg = did_emsg; + did_emsg = false; + typval_T tv; tv.v_type = VAR_STRING; tv.vval.v_string = ""; if (append) { @@ -565,7 +552,7 @@ int var_redir_start(char *name, int append) set_var_lval(redir_lval, redir_endp, &tv, true, false, "="); } clear_lval(redir_lval); - err = did_emsg; + int err = did_emsg; did_emsg |= save_emsg; if (err) { redir_endp = NULL; // don't store a value, only cleanup @@ -585,12 +572,11 @@ int var_redir_start(char *name, int append) /// :redir END void var_redir_str(char *value, int value_len) { - int len; - if (redir_lval == NULL) { return; } + int len; if (value_len == -1) { len = (int)STRLEN(value); // Append the entire string } else { @@ -606,12 +592,11 @@ void var_redir_str(char *value, int value_len) /// Frees the allocated memory. void var_redir_stop(void) { - typval_T tv; - if (redir_lval != NULL) { // If there was no error: assign the text to the variable. if (redir_endp != NULL) { ga_append(&redir_ga, NUL); // Append the trailing NUL. + typval_T tv; tv.v_type = VAR_STRING; tv.vval.v_string = redir_ga.ga_data; // Call get_lval() again, if it's inside a Dict or List it may @@ -641,7 +626,7 @@ int eval_charconvert(const char *const enc_from, const char *const enc_to, set_vim_var_string(VV_CC_TO, enc_to, -1); set_vim_var_string(VV_FNAME_IN, fname_from, -1); set_vim_var_string(VV_FNAME_OUT, fname_to, -1); - if (eval_to_bool((char *)p_ccv, &err, NULL, false)) { + if (eval_to_bool(p_ccv, &err, NULL, false)) { err = true; } set_vim_var_string(VV_CC_FROM, NULL, -1); @@ -661,7 +646,7 @@ int eval_printexpr(const char *const fname, const char *const args) set_vim_var_string(VV_FNAME_IN, fname, -1); set_vim_var_string(VV_CMDARG, args, -1); - if (eval_to_bool((char *)p_pexpr, &err, NULL, false)) { + if (eval_to_bool(p_pexpr, &err, NULL, false)) { err = true; } set_vim_var_string(VV_FNAME_IN, NULL, -1); @@ -681,7 +666,7 @@ void eval_diff(const char *const origfile, const char *const newfile, const char set_vim_var_string(VV_FNAME_IN, origfile, -1); set_vim_var_string(VV_FNAME_NEW, newfile, -1); set_vim_var_string(VV_FNAME_OUT, outfile, -1); - (void)eval_to_bool((char *)p_dex, &err, NULL, false); + (void)eval_to_bool(p_dex, &err, NULL, false); set_vim_var_string(VV_FNAME_IN, NULL, -1); set_vim_var_string(VV_FNAME_NEW, NULL, -1); set_vim_var_string(VV_FNAME_OUT, NULL, -1); @@ -701,11 +686,11 @@ void eval_patch(const char *const origfile, const char *const difffile, const ch } /// Top level evaluation function, returning a boolean. -/// Sets "error" to TRUE if there was an error. +/// Sets "error" to true if there was an error. /// /// @param skip only parse, don't execute /// -/// @return TRUE or FALSE. +/// @return true or false. int eval_to_bool(char *arg, bool *error, char **nextcmd, int skip) { typval_T tv; @@ -905,7 +890,7 @@ char *eval_to_string(char *arg, char **nextcmd, bool convert) /// Call eval_to_string() without using current local variables and using /// textlock. /// -/// @param use_sandbox when TRUE, use the sandbox. +/// @param use_sandbox when true, use the sandbox. char *eval_to_string_safe(char *arg, char **nextcmd, int use_sandbox) { char *retval; @@ -935,7 +920,7 @@ varnumber_T eval_to_number(char *expr) varnumber_T retval; char *p = skipwhite(expr); - ++emsg_off; + emsg_off++; if (eval1(&p, &rettv, true) == FAIL) { retval = -1; @@ -943,7 +928,7 @@ varnumber_T eval_to_number(char *expr) retval = tv_get_number_chk(&rettv, NULL); tv_clear(&rettv); } - --emsg_off; + emsg_off--; return retval; } @@ -1000,11 +985,9 @@ void prepare_vimvar(int idx, typval_T *save_tv) /// When no longer defined, remove the variable from the v: hashtable. void restore_vimvar(int idx, typval_T *save_tv) { - hashitem_T *hi; - vimvars[idx].vv_tv = *save_tv; if (vimvars[idx].vv_type == VAR_UNKNOWN) { - hi = hash_find(&vimvarht, (char *)vimvars[idx].vv_di.di_key); + hashitem_T *hi = hash_find(&vimvarht, (char *)vimvars[idx].vv_di.di_key); if (HASHITEM_EMPTY(hi)) { internal_error("restore_vimvar()"); } else { @@ -1040,7 +1023,7 @@ list_T *eval_spell_expr(char *badword, char *expr) vimvars[VV_VAL].vv_type = VAR_STRING; vimvars[VV_VAL].vv_str = badword; if (p_verbose == 0) { - ++emsg_off; + emsg_off++; } if (eval1(&p, &rettv, true) == OK) { @@ -1052,7 +1035,7 @@ list_T *eval_spell_expr(char *badword, char *expr) } if (p_verbose == 0) { - --emsg_off; + emsg_off--; } restore_vimvar(VV_VAL, &save_val); @@ -1134,12 +1117,11 @@ varnumber_T call_func_retnr(const char *func, int argc, typval_T *argv) FUNC_ATTR_NONNULL_ALL { typval_T rettv; - varnumber_T retval; if (call_vim_function((char *)func, argc, argv, &rettv) == FAIL) { return -1; } - retval = tv_get_number_chk(&rettv, NULL); + varnumber_T retval = tv_get_number_chk(&rettv, NULL); tv_clear(&rettv); return retval; } @@ -1191,42 +1173,6 @@ void *call_func_retlist(const char *func, int argc, typval_T *argv) return rettv.vval.v_list; } -/// Prepare profiling for entering a child or something else that is not -/// counted for the script/function itself. -/// Should always be called in pair with prof_child_exit(). -/// -/// @param tm place to store waittime -void prof_child_enter(proftime_T *tm) -{ - funccall_T *fc = get_current_funccal(); - - if (fc != NULL && fc->func->uf_profiling) { - fc->prof_child = profile_start(); - } - - script_prof_save(tm); -} - -/// Take care of time spent in a child. -/// Should always be called after prof_child_enter(). -/// -/// @param tm where waittime was stored -void prof_child_exit(proftime_T *tm) -{ - funccall_T *fc = get_current_funccal(); - - if (fc != NULL && fc->func->uf_profiling) { - fc->prof_child = profile_end(fc->prof_child); - // don't count waiting time - fc->prof_child = profile_sub_wait(*tm, fc->prof_child); - fc->func->uf_tm_children = - profile_add(fc->func->uf_tm_children, fc->prof_child); - fc->func->uf_tml_children = - profile_add(fc->func->uf_tml_children, fc->prof_child); - } - script_prof_restore(tm); -} - /// Evaluate 'foldexpr'. Returns the foldlevel, and any character preceding /// it in "*cp". Doesn't give error messages. int eval_foldexpr(char *arg, int *cp) @@ -1235,11 +1181,11 @@ int eval_foldexpr(char *arg, int *cp) varnumber_T retval; int use_sandbox = was_set_insecurely(curwin, "foldexpr", OPT_LOCAL); - ++emsg_off; + emsg_off++; if (use_sandbox) { - ++sandbox; + sandbox++; } - ++textlock; + textlock++; *cp = NUL; if (eval0(arg, &tv, NULL, true) == FAIL) { retval = 0; @@ -1260,11 +1206,11 @@ int eval_foldexpr(char *arg, int *cp) } tv_clear(&tv); } - --emsg_off; + emsg_off--; if (use_sandbox) { - --sandbox; + sandbox--; } - --textlock; + textlock--; return (int)retval; } @@ -1298,16 +1244,11 @@ char *get_lval(char *const name, typval_T *const rettv, lval_T *const lp, const const bool skip, const int flags, const int fne_flags) FUNC_ATTR_NONNULL_ARG(1, 3) { - dictitem_T *v; - typval_T var1; - typval_T var2; - int empty1 = FALSE; - listitem_T *ni; - hashtab_T *ht = NULL; + bool empty1 = false; int quiet = flags & GLV_QUIET; // Clear everything in "lp". - memset(lp, 0, sizeof(lval_T)); + CLEAR_POINTER(lp); if (skip) { // When skipping just find the end of the name. @@ -1355,11 +1296,13 @@ char *get_lval(char *const name, typval_T *const rettv, lval_T *const lp, const return p; } + hashtab_T *ht = NULL; + // Only pass &ht when we would write to the variable, it prevents autoload // as well. - v = find_var(lp->ll_name, lp->ll_name_len, - (flags & GLV_READ_ONLY) ? NULL : &ht, - flags & GLV_NO_AUTOLOAD); + dictitem_T *v = find_var(lp->ll_name, lp->ll_name_len, + (flags & GLV_READ_ONLY) ? NULL : &ht, + flags & GLV_NO_AUTOLOAD); if (v == NULL && !quiet) { semsg(_("E121: Undefined variable: %.*s"), (int)lp->ll_name_len, lp->ll_name); @@ -1370,7 +1313,9 @@ char *get_lval(char *const name, typval_T *const rettv, lval_T *const lp, const // Loop until no more [idx] or .key is following. lp->ll_tv = &v->di_tv; + 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)) { if (!(lp->ll_tv->v_type == VAR_LIST && lp->ll_tv->vval.v_list != NULL) @@ -1612,7 +1557,7 @@ char *get_lval(char *const name, typval_T *const rettv, lval_T *const lp, const lp->ll_n2 = (long)tv_get_number(&var2); // Is number or string. tv_clear(&var2); if (lp->ll_n2 < 0) { - ni = tv_list_find(lp->ll_list, (int)lp->ll_n2); + listitem_T *ni = tv_list_find(lp->ll_list, (int)lp->ll_n2); if (ni == NULL) { if (!quiet) { semsg(_(e_listidx), (int64_t)lp->ll_n2); @@ -1765,9 +1710,7 @@ void set_var_lval(lval_T *lp, char *endp, typval_T *rettv, int copy, const bool ll_n1++; } - /* - * Assign the List values to the list items. - */ + // Assign the List values to the list items. for (ri = tv_list_first(rettv->vval.v_list); ri != NULL;) { if (op != NULL && *op != '=') { eexe_mod_op(TV_LIST_ITEM_TV(lp->ll_li), TV_LIST_ITEM_TV(ri), op); @@ -1864,7 +1807,7 @@ notify: /// Evaluate the expression used in a ":for var in expr" command. /// "arg" points to "var". /// -/// @param[out] *errp set to TRUE for an error, FALSE otherwise; +/// @param[out] *errp set to true for an error, false otherwise; /// /// @return a pointer that holds the info. Null when there is an error. void *eval_for_line(const char *arg, bool *errp, char **nextcmdp, int skip) @@ -1888,7 +1831,7 @@ void *eval_for_line(const char *arg, bool *errp, char **nextcmdp, int skip) } if (skip) { - ++emsg_skip; + emsg_skip++; } if (eval0(skipwhite(expr + 2), &tv, nextcmdp, !skip) == OK) { *errp = false; @@ -1930,7 +1873,7 @@ void *eval_for_line(const char *arg, bool *errp, char **nextcmdp, int skip) } } if (skip) { - --emsg_skip; + emsg_skip--; } return fi; @@ -2009,7 +1952,7 @@ void free_for_info(void *fi_void) void set_context_for_expression(expand_T *xp, char *arg, cmdidx_T cmdidx) FUNC_ATTR_NONNULL_ALL { - int got_eq = FALSE; + bool got_eq = false; int c; char *p; @@ -2035,7 +1978,7 @@ void set_context_for_expression(expand_T *xp, char *arg, cmdidx_T cmdidx) if (c == '&') { c = (uint8_t)xp->xp_pattern[1]; if (c == '&') { - ++xp->xp_pattern; + xp->xp_pattern++; xp->xp_context = cmdidx != CMD_let || got_eq ? EXPAND_EXPRESSION : EXPAND_NOTHING; } else if (c != ' ') { @@ -2048,7 +1991,7 @@ void set_context_for_expression(expand_T *xp, char *arg, cmdidx_T cmdidx) // environment variable xp->xp_context = EXPAND_ENV_VARS; } else if (c == '=') { - got_eq = TRUE; + got_eq = true; xp->xp_context = EXPAND_EXPRESSION; } else if (c == '#' && xp->xp_context == EXPAND_EXPRESSION) { @@ -2073,7 +2016,7 @@ void set_context_for_expression(expand_T *xp, char *arg, cmdidx_T cmdidx) xp->xp_context = EXPAND_NOTHING; } else if (c == '|') { if (xp->xp_pattern[1] == '|') { - ++xp->xp_pattern; + xp->xp_pattern++; xp->xp_context = EXPAND_EXPRESSION; } else { xp->xp_context = EXPAND_COMMANDS; @@ -2099,7 +2042,7 @@ void set_context_for_expression(expand_T *xp, char *arg, cmdidx_T cmdidx) || cmdidx == CMD_echomsg) && xp->xp_context == EXPAND_EXPRESSION) { for (;;) { - char *const n = (char *)skiptowhite((char_u *)arg); + char *const n = skiptowhite(arg); if (n == arg || ascii_iswhite_or_nul(*skipwhite(n))) { break; @@ -2123,11 +2066,9 @@ void del_menutrans_vars(void) hash_unlock(&globvarht); } -/* - * Local string buffer for the next two functions to store a variable name - * with its prefix. Allocated in cat_prefix_varname(), freed later in - * get_user_var_name(). - */ +/// Local string buffer for the next two functions to store a variable name +/// with its prefix. Allocated in cat_prefix_varname(), freed later in +/// get_user_var_name(). static char *varnamebuf = NULL; static size_t varnamebuflen = 0; @@ -2171,10 +2112,10 @@ char *get_user_var_name(expand_T *xp, int idx) if (gdone++ == 0) { hi = globvarht.ht_array; } else { - ++hi; + hi++; } while (HASHITEM_EMPTY(hi)) { - ++hi; + hi++; } if (STRNCMP("g:", xp->xp_pattern, 2) == 0) { return cat_prefix_varname('g', (char *)hi->hi_key); @@ -2188,10 +2129,10 @@ char *get_user_var_name(expand_T *xp, int idx) if (bdone++ == 0) { hi = ht->ht_array; } else { - ++hi; + hi++; } while (HASHITEM_EMPTY(hi)) { - ++hi; + hi++; } return cat_prefix_varname('b', (char *)hi->hi_key); } @@ -2202,10 +2143,10 @@ char *get_user_var_name(expand_T *xp, int idx) if (wdone++ == 0) { hi = ht->ht_array; } else { - ++hi; + hi++; } while (HASHITEM_EMPTY(hi)) { - ++hi; + hi++; } return cat_prefix_varname('w', (char *)hi->hi_key); } @@ -2216,10 +2157,10 @@ char *get_user_var_name(expand_T *xp, int idx) if (tdone++ == 0) { hi = ht->ht_array; } else { - ++hi; + hi++; } while (HASHITEM_EMPTY(hi)) { - ++hi; + hi++; } return cat_prefix_varname('t', (char *)hi->hi_key); } @@ -2238,7 +2179,7 @@ char *get_user_var_name(expand_T *xp, int idx) /// Does not use 'cpo' and always uses 'magic'. /// -/// @return TRUE if "pat" matches "text". +/// @return true if "pat" matches "text". int pattern_match(char *pat, char *text, bool ic) { int matches = 0; @@ -2246,7 +2187,7 @@ int pattern_match(char *pat, char *text, bool ic) // avoid 'l' flag in 'cpoptions' char *save_cpo = p_cpo; - p_cpo = ""; + p_cpo = empty_option; regmatch.regprog = vim_regcomp(pat, RE_MAGIC + RE_STRING); if (regmatch.regprog != NULL) { regmatch.rm_ic = ic; @@ -2291,7 +2232,7 @@ static int eval_func(char **const arg, char *const name, const int name_len, typ funcexe.evaluate = evaluate; funcexe.partial = partial; funcexe.basetv = basetv; - int ret = get_func_tv((char_u *)s, len, rettv, (char_u **)arg, &funcexe); + int ret = get_func_tv((char_u *)s, len, rettv, arg, &funcexe); xfree(s); @@ -2317,15 +2258,13 @@ static int eval_func(char **const arg, char *const name, const int name_len, typ // 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. - */ +/// 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. /// Handle zero level expression. /// This calls eval1() and handles error message and nextcmd. -/// Put the result in "rettv" when returning OK and "evaluate" is TRUE. +/// Put the result in "rettv" when returning OK and "evaluate" is true. /// Note: "rettv.v_lock" is not set. /// /// @return OK or FAIL. @@ -2372,18 +2311,16 @@ int eval0(char *arg, typval_T *rettv, char **nextcmd, int evaluate) /// @return OK or FAIL. int eval1(char **arg, typval_T *rettv, int evaluate) { - int result; + bool result; typval_T var2; - /* - * Get the first variable. - */ + // Get the first variable. if (eval2(arg, rettv, evaluate) == FAIL) { return FAIL; } if ((*arg)[0] == '?') { - result = FALSE; + result = false; if (evaluate) { bool error = false; @@ -2396,17 +2333,13 @@ int eval1(char **arg, typval_T *rettv, int evaluate) } } - /* - * Get the second variable. - */ + // Get the second variable. *arg = skipwhite(*arg + 1); if (eval1(arg, rettv, evaluate && result) == FAIL) { // recursive! return FAIL; } - /* - * Check for the ":". - */ + // Check for the ":". if ((*arg)[0] != ':') { emsg(_("E109: Missing ':' after '?'")); if (evaluate && result) { @@ -2415,9 +2348,7 @@ int eval1(char **arg, typval_T *rettv, int evaluate) return FAIL; } - /* - * Get the third variable. - */ + // Get the third variable. *arg = skipwhite(*arg + 1); if (eval1(arg, &var2, evaluate && !result) == FAIL) { // Recursive! if (evaluate && result) { @@ -2445,22 +2376,16 @@ int eval1(char **arg, typval_T *rettv, int evaluate) static int eval2(char **arg, typval_T *rettv, int evaluate) { typval_T var2; - long result; - int first; bool error = false; - /* - * Get the first variable. - */ + // Get the first variable. if (eval3(arg, rettv, evaluate) == FAIL) { return FAIL; } - /* - * Repeat until there is no following "||". - */ - first = TRUE; - result = FALSE; + // Repeat until there is no following "||". + bool first = true; + bool result = false; while ((*arg)[0] == '|' && (*arg)[1] == '|') { if (evaluate && first) { if (tv_get_number_chk(rettv, &error) != 0) { @@ -2473,17 +2398,13 @@ static int eval2(char **arg, typval_T *rettv, int evaluate) first = false; } - /* - * Get the second variable. - */ + // Get the second variable. *arg = skipwhite(*arg + 2); if (eval3(arg, &var2, evaluate && !result) == FAIL) { return FAIL; } - /* - * Compute the result. - */ + // Compute the result. if (evaluate && !result) { if (tv_get_number_chk(&var2, &error) != 0) { result = true; @@ -2514,22 +2435,16 @@ static int eval2(char **arg, typval_T *rettv, int evaluate) static int eval3(char **arg, typval_T *rettv, int evaluate) { typval_T var2; - long result; - int first; bool error = false; - /* - * Get the first variable. - */ + // Get the first variable. if (eval4(arg, rettv, evaluate) == FAIL) { return FAIL; } - /* - * Repeat until there is no following "&&". - */ - first = TRUE; - result = TRUE; + // Repeat until there is no following "&&". + bool first = true; + bool result = true; while ((*arg)[0] == '&' && (*arg)[1] == '&') { if (evaluate && first) { if (tv_get_number_chk(rettv, &error) == 0) { @@ -2542,17 +2457,13 @@ static int eval3(char **arg, typval_T *rettv, int evaluate) first = false; } - /* - * Get the second variable. - */ + // Get the second variable. *arg = skipwhite(*arg + 2); if (eval4(arg, &var2, evaluate && result) == FAIL) { return FAIL; } - /* - * Compute the result. - */ + // Compute the result. if (evaluate && result) { if (tv_get_number_chk(&var2, &error) == 0) { result = false; @@ -2597,9 +2508,7 @@ static int eval4(char **arg, typval_T *rettv, int evaluate) int len = 2; bool ic; - /* - * Get the first variable. - */ + // Get the first variable. if (eval5(arg, rettv, evaluate) == FAIL) { return FAIL; } @@ -2648,9 +2557,7 @@ static int eval4(char **arg, typval_T *rettv, int evaluate) break; } - /* - * If there is a comparative operator, use it. - */ + // If there is a comparative operator, use it. if (type != EXPR_UNKNOWN) { // extra question mark appended: ignore case if (p[len] == '?') { @@ -2701,16 +2608,12 @@ static int eval5(char **arg, typval_T *rettv, int evaluate) float_T f1 = 0, f2 = 0; char *p; - /* - * Get the first variable. - */ - if (eval6(arg, rettv, evaluate, FALSE) == FAIL) { + // Get the first variable. + if (eval6(arg, rettv, evaluate, false) == FAIL) { return FAIL; } - /* - * Repeat computing, until no '+', '-' or '.' is following. - */ + // Repeat computing, until no '+', '-' or '.' is following. for (;;) { op = (char_u)(**arg); if (op != '+' && op != '-' && op != '.') { @@ -2732,9 +2635,7 @@ static int eval5(char **arg, typval_T *rettv, int evaluate) } } - /* - * Get the second variable. - */ + // Get the second variable. if (op == '.' && *(*arg + 1) == '.') { // ..string concatenation (*arg)++; } @@ -2745,9 +2646,7 @@ static int eval5(char **arg, typval_T *rettv, int evaluate) } if (evaluate) { - /* - * Compute the result. - */ + // Compute the result. if (op == '.') { char buf1[NUMBUFLEN]; char buf2[NUMBUFLEN]; @@ -2759,7 +2658,7 @@ static int eval5(char **arg, typval_T *rettv, int evaluate) tv_clear(&var2); return FAIL; } - p = (char *)concat_str((const char_u *)s1, (const char_u *)s2); + p = concat_str(s1, s2); tv_clear(rettv); rettv->v_type = VAR_STRING; rettv->vval.v_string = p; @@ -2876,16 +2775,12 @@ static int eval6(char **arg, typval_T *rettv, int evaluate, int want_string) float_T f1 = 0, f2 = 0; bool error = false; - /* - * Get the first variable. - */ + // Get the first variable. if (eval7(arg, rettv, evaluate, want_string) == FAIL) { return FAIL; } - /* - * Repeat computing, until no '*', '/' or '%' is following. - */ + // Repeat computing, until no '*', '/' or '%' is following. for (;;) { op = (char_u)(**arg); if (op != '*' && op != '/' && op != '%') { @@ -2908,9 +2803,7 @@ static int eval6(char **arg, typval_T *rettv, int evaluate, int want_string) n1 = 0; } - /* - * Get the second variable. - */ + // Get the second variable. *arg = skipwhite(*arg + 1); if (eval7(arg, &var2, evaluate, false) == FAIL) { return FAIL; @@ -2935,10 +2828,8 @@ static int eval6(char **arg, typval_T *rettv, int evaluate, int want_string) } } - /* - * Compute the result. - * When either side is a float the result is a float. - */ + // Compute the result. + // When either side is a float the result is a float. if (use_float) { if (op == '*') { f1 = f1 * f2; @@ -3051,9 +2942,9 @@ static int eval7(char **arg, typval_T *rettv, int evaluate, int want_string) get_float = true; p = skipdigits(p + 2); if (*p == 'e' || *p == 'E') { - ++p; + p++; if (*p == '-' || *p == '+') { - ++p; + p++; } if (!ascii_isdigit(*p)) { get_float = false; @@ -3147,7 +3038,7 @@ static int eval7(char **arg, typval_T *rettv, int evaluate, int want_string) // Lambda: {arg, arg -> expr} // Dictionary: {'key': val, 'key': val} case '{': - ret = get_lambda_tv((char_u **)arg, rettv, evaluate); + ret = get_lambda_tv(arg, rettv, evaluate); if (ret == NOTDONE) { ret = dict_get_tv(arg, rettv, evaluate, false); } @@ -3164,13 +3055,13 @@ static int eval7(char **arg, typval_T *rettv, int evaluate, int want_string) // Register contents: @r. case '@': - ++*arg; + (*arg)++; if (evaluate) { rettv->v_type = VAR_STRING; rettv->vval.v_string = get_reg_contents(**arg, kGRegExprSrc); } if (**arg != NUL) { - ++*arg; + (*arg)++; } break; @@ -3179,7 +3070,7 @@ static int eval7(char **arg, typval_T *rettv, int evaluate, int want_string) *arg = skipwhite(*arg + 1); ret = eval1(arg, rettv, evaluate); // recursive! if (**arg == ')') { - ++*arg; + (*arg)++; } else if (ret == OK) { emsg(_("E110: Missing ')'")); tv_clear(rettv); @@ -3325,7 +3216,7 @@ static int call_func_rettv(char **const arg, typval_T *const rettv, const bool e funcexe.selfdict = selfdict; funcexe.basetv = basetv; const int ret = get_func_tv((char_u *)funcname, is_lua ? (int)(*arg - funcname) : -1, rettv, - (char_u **)arg, &funcexe); + arg, &funcexe); // Clear the funcref afterwards, so that deleting it while // evaluating the arguments is possible (see test55). @@ -3353,7 +3244,7 @@ static int eval_lambda(char **const arg, typval_T *const rettv, const bool evalu typval_T base = *rettv; rettv->v_type = VAR_UNKNOWN; - int ret = get_lambda_tv((char_u **)arg, rettv, evaluate); + int ret = get_lambda_tv(arg, rettv, evaluate); if (ret != OK) { return FAIL; } else if (**arg != '(') { @@ -3503,9 +3394,7 @@ static int eval_index(char **arg, typval_T *rettv, int evaluate, int verbose) typval_T var1 = TV_INITIAL_VALUE; typval_T var2 = TV_INITIAL_VALUE; if (**arg == '.') { - /* - * dict.name - */ + // dict.name key = *arg + 1; for (len = 0; ASCII_ISALNUM(key[len]) || key[len] == '_'; len++) {} if (len == 0) { @@ -3513,11 +3402,9 @@ static int eval_index(char **arg, typval_T *rettv, int evaluate, int verbose) } *arg = skipwhite(key + len); } else { - /* - * something[idx] - * - * Get the (first) variable from inside the []. - */ + // something[idx] + // + // Get the (first) variable from inside the []. *arg = skipwhite(*arg + 1); if (**arg == ':') { empty1 = true; @@ -3529,9 +3416,7 @@ static int eval_index(char **arg, typval_T *rettv, int evaluate, int verbose) return FAIL; } - /* - * Get the second variable from inside the [:]. - */ + // Get the second variable from inside the [:]. if (**arg == ':') { range = true; *arg = skipwhite(*arg + 1); @@ -3772,11 +3657,7 @@ 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) { - long numval; - char *stringval; - getoption_T opt_type; bool working = (**arg == '+'); // has("+option") - int ret = OK; int opt_flags; // Isolate the option name and find its value. @@ -3793,10 +3674,14 @@ int get_option_tv(const char **const arg, typval_T *const rettv, const bool eval return OK; } + long numval; + char *stringval; + int ret = OK; + char c = *option_end; *option_end = NUL; - opt_type = get_option_value(*arg, &numval, - rettv == NULL ? NULL : &stringval, opt_flags); + getoption_T opt_type = get_option_value(*arg, &numval, + rettv == NULL ? NULL : &stringval, opt_flags); if (opt_type == gov_unknown) { if (rettv != NULL) { @@ -3837,9 +3722,7 @@ static int get_string_tv(char **arg, typval_T *rettv, int evaluate) char *p; unsigned int extra = 0; - /* - * Find the end of the string, skipping backslashed characters. - */ + // Find the end of the string, skipping backslashed characters. for (p = *arg + 1; *p != NUL && *p != '"'; MB_PTR_ADV(p)) { if (*p == '\\' && p[1] != NUL) { p++; @@ -3863,10 +3746,8 @@ static int get_string_tv(char **arg, typval_T *rettv, int evaluate) return OK; } - /* - * Copy the string into allocated memory, handling backslashed - * characters. - */ + // Copy the string into allocated memory, handling backslashed + // characters. const int len = (int)(p - *arg + extra); char *name = xmalloc((size_t)len); rettv->v_type = VAR_STRING; @@ -3905,7 +3786,7 @@ static int get_string_tv(char **arg, typval_T *rettv, int evaluate) } nr = 0; while (--n >= 0 && ascii_isxdigit(p[1])) { - ++p; + p++; nr = (nr << 4) + hex2nr(*p); } p++; @@ -3935,7 +3816,7 @@ static int get_string_tv(char **arg, typval_T *rettv, int evaluate) *name = (char)((*name << 3) + *p++ - '0'); } } - ++name; + name++; break; // Special key, e.g.: "\<C-W>" @@ -3957,11 +3838,11 @@ static int get_string_tv(char **arg, typval_T *rettv, int evaluate) FALLTHROUGH; default: - mb_copy_char((const char_u **)&p, (char_u **)&name); + mb_copy_char((const char **)&p, &name); break; } } else { - mb_copy_char((const char_u **)&p, (char_u **)&name); + mb_copy_char((const char **)&p, &name); } } *name = NUL; @@ -3979,19 +3860,16 @@ static int get_string_tv(char **arg, typval_T *rettv, int evaluate) static int get_lit_string_tv(char **arg, typval_T *rettv, int evaluate) { char *p; - char *str; int reduce = 0; - /* - * Find the end of the string, skipping ''. - */ + // Find the end of the string, skipping ''. for (p = *arg + 1; *p != NUL; MB_PTR_ADV(p)) { if (*p == '\'') { if (p[1] != '\'') { break; } - ++reduce; - ++p; + reduce++; + p++; } } @@ -4006,10 +3884,8 @@ static int get_lit_string_tv(char **arg, typval_T *rettv, int evaluate) return OK; } - /* - * Copy the string into allocated memory, handling '' to ' reduction. - */ - str = xmalloc((size_t)((p - *arg) - reduce)); + // Copy the string into allocated memory, handling '' to ' reduction. + char *str = xmalloc((size_t)((p - *arg) - reduce)); rettv->v_type = VAR_STRING; rettv->vval.v_string = str; @@ -4018,9 +3894,9 @@ static int get_lit_string_tv(char **arg, typval_T *rettv, int evaluate) if (p[1] != '\'') { break; } - ++p; + p++; } - mb_copy_char((const char_u **)&p, (char_u **)&str); + mb_copy_char((const char **)&p, &str); } *str = NUL; *arg = p + 1; @@ -4119,16 +3995,14 @@ failret: /// @param ic ignore case bool func_equal(typval_T *tv1, typval_T *tv2, bool ic) { - char_u *s1, *s2; - dict_T *d1, *d2; - int a1, a2; - // empty and NULL function name considered the same - s1 = (char_u *)(tv1->v_type == VAR_FUNC ? tv1->vval.v_string : partial_name(tv1->vval.v_partial)); + char_u *s1 = + (char_u *)(tv1->v_type == VAR_FUNC ? tv1->vval.v_string : partial_name(tv1->vval.v_partial)); if (s1 != NULL && *s1 == NUL) { s1 = NULL; } - s2 = (char_u *)(tv2->v_type == VAR_FUNC ? tv2->vval.v_string : partial_name(tv2->vval.v_partial)); + char_u *s2 = + (char_u *)(tv2->v_type == VAR_FUNC ? tv2->vval.v_string : partial_name(tv2->vval.v_partial)); if (s2 != NULL && *s2 == NUL) { s2 = NULL; } @@ -4141,8 +4015,8 @@ bool func_equal(typval_T *tv1, typval_T *tv2, bool ic) } // empty dict and NULL dict is different - d1 = tv1->v_type == VAR_FUNC ? NULL : tv1->vval.v_partial->pt_dict; - d2 = tv2->v_type == VAR_FUNC ? NULL : tv2->vval.v_partial->pt_dict; + dict_T *d1 = tv1->v_type == VAR_FUNC ? NULL : tv1->vval.v_partial->pt_dict; + dict_T *d2 = tv2->v_type == VAR_FUNC ? NULL : tv2->vval.v_partial->pt_dict; if (d1 == NULL || d2 == NULL) { if (d1 != d2) { return false; @@ -4152,8 +4026,8 @@ bool func_equal(typval_T *tv1, typval_T *tv2, bool ic) } // empty list and no list considered the same - a1 = tv1->v_type == VAR_FUNC ? 0 : tv1->vval.v_partial->pt_argc; - a2 = tv2->v_type == VAR_FUNC ? 0 : tv2->vval.v_partial->pt_argc; + int a1 = tv1->v_type == VAR_FUNC ? 0 : tv1->vval.v_partial->pt_argc; + int a2 = tv2->v_type == VAR_FUNC ? 0 : tv2->vval.v_partial->pt_argc; if (a1 != a2) { return false; } @@ -4182,25 +4056,23 @@ int get_copyID(void) return current_copyID; } -/* - * Garbage collection for lists and dictionaries. - * - * We use reference counts to be able to free most items right away when they - * are no longer used. But for composite items it's possible that it becomes - * unused while the reference count is > 0: When there is a recursive - * reference. Example: - * :let l = [1, 2, 3] - * :let d = {9: l} - * :let l[1] = d - * - * Since this is quite unusual we handle this with garbage collection: every - * once in a while find out which lists and dicts are not referenced from any - * variable. - * - * Here is a good reference text about garbage collection (refers to Python - * but it applies to all reference-counting mechanisms): - * http://python.ca/nas/python/gc/ - */ +/// Garbage collection for lists and dictionaries. +/// +/// We use reference counts to be able to free most items right away when they +/// are no longer used. But for composite items it's possible that it becomes +/// unused while the reference count is > 0: When there is a recursive +/// reference. Example: +/// :let l = [1, 2, 3] +/// :let d = {9: l} +/// :let l[1] = d +/// +/// Since this is quite unusual we handle this with garbage collection: every +/// once in a while find out which lists and dicts are not referenced from any +/// variable. +/// +/// Here is a good reference text about garbage collection (refers to Python +/// but it applies to all reference-counting mechanisms): +/// http://python.ca/nas/python/gc/ /// Do garbage collection for lists and dicts. /// @@ -4219,6 +4091,23 @@ bool garbage_collect(bool testing) garbage_collect_at_exit = false; } + // The execution stack can grow big, limit the size. + if (exestack.ga_maxlen - exestack.ga_len > 500) { + // Keep 150% of the current size, with a minimum of the growth size. + int n = exestack.ga_len / 2; + if (n < exestack.ga_growsize) { + n = exestack.ga_growsize; + } + + // Don't make it bigger though. + if (exestack.ga_len + n < exestack.ga_maxlen) { + size_t new_len = (size_t)exestack.ga_itemsize * (size_t)(exestack.ga_len + n); + char *pp = xrealloc(exestack.ga_data, new_len); + exestack.ga_maxlen = exestack.ga_len + n; + exestack.ga_data = pp; + } + } + // We advance by two (COPYID_INC) because we add one for items referenced // through previous_funccal. const int copyID = get_copyID(); @@ -4232,7 +4121,7 @@ bool garbage_collect(bool testing) ABORTING(set_ref_in_previous_funccal)(copyID); // script-local variables - for (int i = 1; i <= ga_scripts.ga_len; ++i) { + for (int i = 1; i <= ga_scripts.ga_len; i++) { ABORTING(set_ref_in_ht)(&SCRIPT_VARS(i), copyID, NULL); } @@ -4691,21 +4580,16 @@ static int get_literal_key(char **arg, typval_T *tv) /// @return OK or FAIL. Returns NOTDONE for {expr}. static int dict_get_tv(char **arg, typval_T *rettv, int evaluate, bool literal) { - dict_T *d = NULL; - typval_T tvkey; typval_T tv; char *key = NULL; - dictitem_T *item; char *start = skipwhite(*arg + 1); char buf[NUMBUFLEN]; - /* - * First check if it's not a curly-braces thing: {expr}. - * Must do this without evaluating, otherwise a function may be called - * twice. Unfortunately this means we need to call eval1() twice for the - * first item. - * But {} is an empty Dictionary. - */ + // First check if it's not a curly-braces thing: {expr}. + // Must do this without evaluating, otherwise a function may be called + // twice. Unfortunately this means we need to call eval1() twice for the + // first item. + // But {} is an empty Dictionary. if (*start != '}') { if (eval1(&start, &tv, false) == FAIL) { // recursive! return FAIL; @@ -4715,9 +4599,11 @@ static int dict_get_tv(char **arg, typval_T *rettv, int evaluate, bool literal) } } + dict_T *d = NULL; if (evaluate) { d = tv_dict_alloc(); } + typval_T tvkey; tvkey.v_type = VAR_UNKNOWN; tv.v_type = VAR_UNKNOWN; @@ -4750,7 +4636,7 @@ static int dict_get_tv(char **arg, typval_T *rettv, int evaluate, bool literal) goto failret; } if (evaluate) { - item = tv_dict_find(d, (const char *)key, -1); + dictitem_T *item = tv_dict_find(d, (const char *)key, -1); if (item != NULL) { semsg(_("E721: Duplicate key in Dictionary: \"%s\""), key); tv_clear(&tvkey); @@ -4805,8 +4691,6 @@ failret: size_t string2float(const char *const text, float_T *const ret_value) FUNC_ATTR_NONNULL_ALL { - char *s = NULL; - // MS-Windows does not deal with "inf" and "nan" properly if (STRNICMP(text, "inf", 3) == 0) { *ret_value = (float_T)INFINITY; @@ -4820,6 +4704,7 @@ size_t string2float(const char *const text, float_T *const ret_value) *ret_value = (float_T)NAN; return 3; } + char *s = NULL; *ret_value = strtod(text, &s); return (size_t)(s - text); } @@ -4833,23 +4718,18 @@ size_t string2float(const char *const text, float_T *const ret_value) /// @return FAIL if the name is invalid. static int get_env_tv(char **arg, typval_T *rettv, int evaluate) { - char *name; - char *string = NULL; - int len; - int cc; - - ++*arg; - name = *arg; - len = get_env_len((const char **)arg); + (*arg)++; + char *name = *arg; + int len = get_env_len((const char **)arg); if (evaluate) { if (len == 0) { return FAIL; // Invalid empty name. } - cc = (char_u)name[len]; + int cc = (int)name[len]; name[len] = NUL; // First try vim_getenv(), fast for normal environment vars. - string = vim_getenv(name); + char *string = vim_getenv(name); if (string == NULL || *string == NUL) { xfree(string); @@ -4867,18 +4747,6 @@ static int get_env_tv(char **arg, typval_T *rettv, int evaluate) return OK; } -/// Get the argument list for a given window -void get_arglist_as_rettv(aentry_T *arglist, int argcount, typval_T *rettv) -{ - tv_list_alloc_ret(rettv, argcount); - if (arglist != NULL) { - for (int idx = 0; idx < argcount; idx++) { - tv_list_append_string(rettv->vval.v_list, - (const char *)alist_name(&arglist[idx]), -1); - } - } -} - /// Add an assert error to v:errors. void assert_error(garray_T *gap) { @@ -4908,17 +4776,10 @@ win_T *find_win_by_nr_or_id(typval_T *vp) /// Implementation of map() and filter(). void filter_map(typval_T *argvars, typval_T *rettv, int map) { - typval_T *expr; list_T *l = NULL; - dictitem_T *di; - hashtab_T *ht; - hashitem_T *hi; dict_T *d = NULL; - typval_T save_val; - typval_T save_key; blob_T *b = NULL; int rem = false; - int todo; char *ermsg = map ? "map()" : "filter()"; const char *const arg_errmsg = (map ? N_("map() argument") @@ -4949,18 +4810,20 @@ void filter_map(typval_T *argvars, typval_T *rettv, int map) return; } - expr = &argvars[1]; + typval_T *expr = &argvars[1]; // On type errors, the preceding call has already displayed an error // message. Avoid a misleading error message for an empty string that // was not passed as argument. if (expr->v_type != VAR_UNKNOWN) { + typval_T save_val; prepare_vimvar(VV_VAL, &save_val); // We reset "did_emsg" to be able to detect whether an error // occurred during evaluation of the expression. save_did_emsg = did_emsg; - did_emsg = FALSE; + did_emsg = false; + typval_T save_key; prepare_vimvar(VV_KEY, &save_key); if (argvars[0].v_type == VAR_DICT) { vimvars[VV_KEY].vv_type = VAR_STRING; @@ -4969,14 +4832,14 @@ void filter_map(typval_T *argvars, typval_T *rettv, int map) if (map && d->dv_lock == VAR_UNLOCKED) { d->dv_lock = VAR_LOCKED; } - ht = &d->dv_hashtab; + hashtab_T *ht = &d->dv_hashtab; hash_lock(ht); - todo = (int)ht->ht_used; - for (hi = ht->ht_array; todo > 0; ++hi) { + int todo = (int)ht->ht_used; + for (hashitem_T *hi = ht->ht_array; todo > 0; hi++) { if (!HASHITEM_EMPTY(hi)) { - --todo; + todo--; - di = TV_DICT_HI2DI(hi); + dictitem_T *di = TV_DICT_HI2DI(hi); if (map && (var_check_lock(di->di_tv.v_lock, arg_errmsg, TV_TRANSLATE) || var_check_ro(di->di_flags, arg_errmsg, TV_TRANSLATE))) { @@ -5100,7 +4963,7 @@ theend: return retval; } -void common_function(typval_T *argvars, typval_T *rettv, bool is_funcref, FunPtr fptr) +void common_function(typval_T *argvars, typval_T *rettv, bool is_funcref) { char *s; char *name; @@ -5125,7 +4988,7 @@ void common_function(typval_T *argvars, typval_T *rettv, bool is_funcref, FunPtr if ((use_string && vim_strchr(s, AUTOLOAD_CHAR) == NULL) || is_funcref) { name = s; - trans_name = (char *)trans_function_name((char_u **)&name, false, + trans_name = (char *)trans_function_name(&name, false, TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD | TFN_NO_DEREF, NULL, NULL); if (*name != NUL) { @@ -5329,29 +5192,6 @@ linenr_T tv_get_lnum_buf(const typval_T *const tv, const buf_T *const buf) return (linenr_T)tv_get_number_chk(tv, NULL); } -void get_qf_loc_list(int is_qf, win_T *wp, typval_T *what_arg, typval_T *rettv) -{ - if (what_arg->v_type == VAR_UNKNOWN) { - tv_list_alloc_ret(rettv, kListLenMayKnow); - if (is_qf || wp != NULL) { - (void)get_errorlist(NULL, wp, -1, 0, rettv->vval.v_list); - } - } else { - tv_dict_alloc_ret(rettv); - if (is_qf || wp != NULL) { - if (what_arg->v_type == VAR_DICT) { - dict_T *d = what_arg->vval.v_dict; - - if (d != NULL) { - qf_get_properties(wp, d, rettv->vval.v_dict); - } - } else { - emsg(_(e_dictreq)); - } - } - } -} - /// @return information (variables, options, etc.) about a tab page /// as a dictionary. dict_T *get_tabpage_info(tabpage_T *tp, int tp_idx) @@ -5383,12 +5223,12 @@ dict_T *get_win_info(win_T *wp, int16_t tpnr, int16_t winnr) 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); + 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); + 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)); @@ -5688,11 +5528,7 @@ void set_buffer_lines(buf_T *buf, linenr_T lnum_arg, bool append, const typval_T FUNC_ATTR_NONNULL_ARG(4, 5) { linenr_T lnum = lnum_arg + (append ? 1 : 0); - const char *line = NULL; - list_T *l = NULL; - listitem_T *li = NULL; long added = 0; - linenr_T append_lnum; buf_T *curbuf_save = NULL; win_T *curwin_save = NULL; const bool is_curbuf = buf == curbuf; @@ -5714,6 +5550,7 @@ void set_buffer_lines(buf_T *buf, linenr_T lnum_arg, bool append, const typval_T find_win_for_curbuf(); } + linenr_T append_lnum; if (append) { // appendbufline() uses the line number below which we insert append_lnum = lnum - 1; @@ -5723,6 +5560,9 @@ void set_buffer_lines(buf_T *buf, linenr_T lnum_arg, bool append, const typval_T 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); @@ -5806,7 +5646,6 @@ void set_buffer_lines(buf_T *buf, linenr_T lnum_arg, bool append, const typval_T void get_xdg_var_list(const XDGVarType xdg, typval_T *rettv) FUNC_ATTR_NONNULL_ALL { - const void *iter = NULL; list_T *const list = tv_list_alloc(kListLenShouldKnow); rettv->v_type = VAR_LIST; rettv->vval.v_list = list; @@ -5815,6 +5654,7 @@ void get_xdg_var_list(const XDGVarType xdg, typval_T *rettv) if (dirs == NULL) { return; } + const void *iter = NULL; do { size_t dir_len; const char *dir; @@ -5924,9 +5764,9 @@ void get_system_output_as_rettv(typval_T *argvars, typval_T *rettv, bool retlist #ifdef USE_CRNL // translate <CR><NL> into <NL> char *d = res; - for (char *s = res; *s; ++s) { + for (char *s = res; *s; s++) { if (s[0] == CAR && s[1] == NL) { - ++s; + s++; } *d++ = *s; @@ -5999,7 +5839,17 @@ bool callback_call(Callback *const callback, const int argcount_in, typval_T *co switch (callback->type) { case kCallbackFuncref: name = callback->data.funcref; - partial = NULL; + int len = (int)STRLEN(name); + if (len >= 6 && !memcmp(name, "v:lua.", 6)) { + name += 6; + len = check_luafunc_name(name, false); + if (len == 0) { + return false; + } + partial = vvlua_partial; + } else { + partial = NULL; + } break; case kCallbackPartial: @@ -6135,7 +5985,7 @@ void timer_due_cb(TimeWatcher *tw, void *data) // Handle error message if (called_emsg > called_emsg_before && did_emsg) { timer->emsg_count++; - if (current_exception != NULL) { + if (did_throw) { discard_current_exception(); } } @@ -6498,12 +6348,9 @@ pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret // Argument can be [lnum, col, coladd]. if (tv->v_type == VAR_LIST) { - list_T *l; - int len; bool error = false; - listitem_T *li; - l = tv->vval.v_list; + list_T *l = tv->vval.v_list; if (l == NULL) { return NULL; } @@ -6520,6 +6367,7 @@ pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret if (error) { return NULL; } + int len; if (charcol) { len = mb_charlen(ml_get(pos.lnum)); } else { @@ -6527,7 +6375,7 @@ pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret } // We accept "$" for the column number: last column. - li = tv_list_find(l, 1L); + listitem_T *li = tv_list_find(l, 1L); if (li != NULL && TV_LIST_ITEM_TV(li)->v_type == VAR_STRING && TV_LIST_ITEM_TV(li)->vval.v_string != NULL && STRCMP(TV_LIST_ITEM_TV(li)->vval.v_string, "$") == 0) { @@ -6627,8 +6475,6 @@ pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret int list2fpos(typval_T *arg, pos_T *posp, int *fnump, colnr_T *curswantp, bool charcol) { list_T *l; - int i = 0; - long n; // List must be: [fnum, lnum, col, coladd, curswant], where "fnum" is only // there when "fnump" isn't NULL; "coladd" and "curswant" are optional. @@ -6639,6 +6485,8 @@ int list2fpos(typval_T *arg, pos_T *posp, int *fnump, colnr_T *curswantp, bool c return FAIL; } + int i = 0; + long n; if (fnump != NULL) { n = tv_list_find_nr(l, i++, NULL); // fnum if (n < 0) { @@ -6691,15 +6539,13 @@ int list2fpos(typval_T *arg, pos_T *posp, int *fnump, colnr_T *curswantp, bool c /// @return 0 for error. int get_env_len(const char **arg) { - int len; - const char *p; for (p = *arg; vim_isIDc(*p); p++) {} if (p == *arg) { // No name found. return 0; } - len = (int)(p - *arg); + int len = (int)(p - *arg); *arg = p; return len; } @@ -6747,8 +6593,6 @@ int get_id_len(const char **const arg) /// 0 if something else is wrong. int get_name_len(const char **const arg, char **alias, bool evaluate, bool verbose) { - int len; - *alias = NULL; // default to no alias if ((*arg)[0] == (char)K_SPECIAL && (*arg)[1] == (char)KS_EXTRA @@ -6757,7 +6601,7 @@ int get_name_len(const char **const arg, char **alias, bool evaluate, bool verbo *arg += 3; return get_id_len(arg) + 3; } - len = eval_fname_script(*arg); + int len = eval_fname_script(*arg); if (len > 0) { // literal "<SID>", "s:" or "<SNR>" *arg += len; @@ -6775,10 +6619,8 @@ int get_name_len(const char **const arg, char **alias, bool evaluate, bool verbo return len; } - /* - * Include any <SID> etc in the expanded string: - * Thus the -len here. - */ + // Include any <SID> etc in the expanded string: + // Thus the -len here. char *temp_string = make_expanded_name(*arg - len, expr_start, expr_end, (char *)p); if (temp_string == NULL) { return -1; @@ -6810,10 +6652,6 @@ int get_name_len(const char **const arg, char **alias, bool evaluate, bool verbo const char *find_name_end(const char *arg, const char **expr_start, const char **expr_end, int flags) { - int mb_nest = 0; - int br_nest = 0; - int len; - if (expr_start != NULL) { *expr_start = NULL; *expr_end = NULL; @@ -6824,6 +6662,10 @@ const char *find_name_end(const char *arg, const char **expr_start, const char * return arg; } + int mb_nest = 0; + int br_nest = 0; + int len; + const char *p; for (p = arg; *p != NUL && (eval_isnamec(*p) @@ -6841,7 +6683,7 @@ const char *find_name_end(const char *arg, const char **expr_start, const char * // skip over "str\"ing" to avoid counting [ and ] inside it. for (p = p + 1; *p != NUL && *p != '"'; MB_PTR_ADV(p)) { if (*p == '\\' && p[1] != NUL) { - ++p; + p++; } } if (*p == NUL) { @@ -6859,9 +6701,9 @@ const char *find_name_end(const char *arg, const char **expr_start, const char * if (mb_nest == 0) { if (*p == '[') { - ++br_nest; + br_nest++; } else if (*p == ']') { - --br_nest; + br_nest--; } } @@ -6897,20 +6739,19 @@ const char *find_name_end(const char *arg, const char **expr_start, const char * static char *make_expanded_name(const char *in_start, char *expr_start, char *expr_end, char *in_end) { - char c1; - char *retval = NULL; - char *temp_result; - char *nextcmd = NULL; - if (expr_end == NULL || in_end == NULL) { return NULL; } + + char *retval = NULL; + char *nextcmd = NULL; + *expr_start = NUL; *expr_end = NUL; - c1 = *in_end; + char c1 = *in_end; *in_end = NUL; - temp_result = eval_to_string(expr_start + 1, &nextcmd, false); + char *temp_result = eval_to_string(expr_start + 1, &nextcmd, false); if (temp_result != NULL && nextcmd == NULL) { retval = xmalloc(STRLEN(temp_result) + (size_t)(expr_start - in_start) + (size_t)(in_end - expr_end) + 1); @@ -6940,14 +6781,14 @@ static char *make_expanded_name(const char *in_start, char *expr_start, char *ex return retval; } -/// @return TRUE if character "c" can be used in a variable or function name. +/// @return true if character "c" can be used in a variable or function name. /// Does not include '{' or '}' for magic braces. int eval_isnamec(int c) { return ASCII_ISALNUM(c) || c == '_' || c == ':' || c == AUTOLOAD_CHAR; } -/// @return TRUE if character "c" can be used as the first character in a +/// @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) { @@ -7000,7 +6841,7 @@ void set_vim_var_char(int c) /// Set v:count to "count" and v:count1 to "count1". /// -/// @param set_prevcount if TRUE, first set v:prevcount from v:count. +/// @param set_prevcount if true, first set v:prevcount from v:count. void set_vcount(long count, long count1, int set_prevcount) { if (set_prevcount) { @@ -7353,7 +7194,7 @@ int handle_subscript(const char **const arg, typval_T *rettv, int evaluate, int if (rettv->v_type == VAR_DICT) { selfdict = rettv->vval.v_dict; if (selfdict != NULL) { - ++selfdict->dv_refcount; + selfdict->dv_refcount++; } } else { selfdict = NULL; @@ -7430,8 +7271,6 @@ dictitem_T *find_var_in_ht(hashtab_T *const ht, int htname, const char *const va const size_t varname_len, int no_autoload) FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { - hashitem_T *hi; - if (varname_len == 0) { // Must be something like "s:", otherwise "ht" would be NULL. switch (htname) { @@ -7455,7 +7294,7 @@ dictitem_T *find_var_in_ht(hashtab_T *const ht, int htname, const char *const va return NULL; } - hi = hash_find_len(ht, varname, varname_len); + hashitem_T *hi = hash_find_len(ht, varname, varname_len); if (HASHITEM_EMPTY(hi)) { // For global variables we may try auto-loading the script. If it // worked find the variable again. Don't auto-load a script if it was @@ -7490,7 +7329,6 @@ dictitem_T *find_var_in_ht(hashtab_T *const ht, int htname, const char *const va hashtab_T *find_var_ht_dict(const char *name, const size_t name_len, const char **varname, dict_T **d) { - hashitem_T *hi; funccall_T *funccal = get_funccal(); *d = NULL; @@ -7506,7 +7344,7 @@ hashtab_T *find_var_ht_dict(const char *name, const size_t name_len, const char *varname = name; // "version" is "v:version" in all scopes - hi = hash_find_len(&compat_hashtab, name, name_len); + hashitem_T *hi = hash_find_len(&compat_hashtab, name, name_len); if (!HASHITEM_EMPTY(hi)) { return &compat_hashtab; } @@ -7562,7 +7400,7 @@ hashtab_T *find_var_ht_dict(const char *name, const size_t name_len, const char bool should_free; // should_free is ignored as script_sctx will be resolved to a fnmae // & new_script_item will consume it. - char *sc_name = (char *)get_scriptname(last_set, &should_free); + char *sc_name = get_scriptname(last_set, &should_free); new_script_item(sc_name, ¤t_sctx.sc_sid); } } @@ -7595,28 +7433,26 @@ hashtab_T *find_var_ht(const char *name, const size_t name_len, const char **var /// sourcing this script and when executing functions defined in the script. void new_script_vars(scid_T id) { - hashtab_T *ht; scriptvar_T *sv; ga_grow(&ga_scripts, id - ga_scripts.ga_len); - { - /* Re-allocating ga_data means that an ht_array pointing to - * ht_smallarray becomes invalid. We can recognize this: ht_mask is - * at its init value. Also reset "v_dict", it's always the same. */ - for (int i = 1; i <= ga_scripts.ga_len; ++i) { - ht = &SCRIPT_VARS(i); - if (ht->ht_mask == HT_INIT_SIZE - 1) { - ht->ht_array = ht->ht_smallarray; - } - sv = SCRIPT_SV(i); - sv->sv_var.di_tv.vval.v_dict = &sv->sv_dict; - } - while (ga_scripts.ga_len < id) { - sv = SCRIPT_SV(ga_scripts.ga_len + 1) = xcalloc(1, sizeof(scriptvar_T)); - init_var_dict(&sv->sv_dict, &sv->sv_var, VAR_SCOPE); - ++ga_scripts.ga_len; + // Re-allocating ga_data means that an ht_array pointing to + // ht_smallarray becomes invalid. We can recognize this: ht_mask is + // at its init value. Also reset "v_dict", it's always the same. + for (int i = 1; i <= ga_scripts.ga_len; i++) { + hashtab_T *ht = &SCRIPT_VARS(i); + if (ht->ht_mask == HT_INIT_SIZE - 1) { + ht->ht_array = ht->ht_smallarray; } + sv = SCRIPT_SV(i); + sv->sv_var.di_tv.vval.v_dict = &sv->sv_dict; + } + + while (ga_scripts.ga_len < id) { + sv = SCRIPT_SV(ga_scripts.ga_len + 1) = xcalloc(1, sizeof(scriptvar_T)); + init_var_dict(&sv->sv_dict, &sv->sv_var, VAR_SCOPE); + ga_scripts.ga_len++; } } @@ -7640,8 +7476,8 @@ void init_var_dict(dict_T *dict, ScopeDictDictItem *dict_var, ScopeType scope) /// Unreference a dictionary initialized by init_var_dict(). void unref_var_dict(dict_T *dict) { - /* Now the dict needs to be freed if no one else is using it, go back to - * normal reference counting. */ + // Now the dict needs to be freed if no one else is using it, go back to + // normal reference counting. dict->dv_refcount -= DO_NOT_FREE_CNT - 1; tv_dict_unref(dict); } @@ -7673,7 +7509,7 @@ int var_item_copy(const vimconv_T *const conv, typval_T *const from, typval_T *c emsg(_("E698: variable nested too deep for making a copy")); return FAIL; } - ++recurse; + recurse++; switch (from->v_type) { case VAR_NUMBER: @@ -7691,9 +7527,9 @@ int var_item_copy(const vimconv_T *const conv, typval_T *const from, typval_T *c } else { to->v_type = VAR_STRING; to->v_lock = VAR_UNLOCKED; - if ((to->vval.v_string = (char *)string_convert((vimconv_T *)conv, - (char_u *)from->vval.v_string, - NULL)) + if ((to->vval.v_string = string_convert((vimconv_T *)conv, + from->vval.v_string, + NULL)) == NULL) { to->vval.v_string = xstrdup(from->vval.v_string); } @@ -7726,7 +7562,7 @@ int var_item_copy(const vimconv_T *const conv, typval_T *const from, typval_T *c } else if (copyID != 0 && from->vval.v_dict->dv_copyID == copyID) { // use the copy made earlier to->vval.v_dict = from->vval.v_dict->dv_copydict; - ++to->vval.v_dict->dv_refcount; + to->vval.v_dict->dv_refcount++; } else { to->vval.v_dict = tv_dict_copy(conv, from->vval.v_dict, deep, copyID); } @@ -7738,7 +7574,7 @@ int var_item_copy(const vimconv_T *const conv, typval_T *const from, typval_T *c internal_error("var_item_copy(UNKNOWN)"); ret = FAIL; } - --recurse; + recurse--; return ret; } @@ -7755,7 +7591,7 @@ void ex_echo(exarg_T *eap) const int called_emsg_before = called_emsg; if (eap->skip) { - ++emsg_skip; + emsg_skip++; } while (*arg != NUL && *arg != '|' && *arg != '\n' && !got_int) { // If eval1() causes an error message the text from the command may @@ -7835,12 +7671,11 @@ void ex_execute(exarg_T *eap) typval_T rettv; int ret = OK; garray_T ga; - int save_did_emsg; ga_init(&ga, 1, 80); if (eap->skip) { - ++emsg_skip; + emsg_skip++; } while (*arg != NUL && *arg != '|' && *arg != '\n') { ret = eval1_emsg(&arg, &rettv, !eap->skip); @@ -7884,7 +7719,7 @@ void ex_execute(exarg_T *eap) ui_flush(); } else if (eap->cmdidx == CMD_echoerr) { // We don't want to abort following commands, restore did_emsg. - save_did_emsg = did_emsg; + int save_did_emsg = did_emsg; msg_ext_set_kind("echoerr"); emsg(ga.ga_data); if (!force_abort) { @@ -7898,7 +7733,7 @@ void ex_execute(exarg_T *eap) ga_clear(&ga); if (eap->skip) { - --emsg_skip; + emsg_skip--; } eap->nextcmd = (char *)check_nextcmd((char_u *)arg); @@ -7914,7 +7749,7 @@ const char *find_option_end(const char **const arg, int *const opt_flags) { const char *p = *arg; - ++p; + p++; if (*p == 'g' && p[1] == ':') { *opt_flags = OPT_GLOBAL; p += 2; @@ -7940,174 +7775,6 @@ const char *find_option_end(const char **const arg, int *const opt_flags) return p; } -/// Start profiling function "fp". -void func_do_profile(ufunc_T *fp) -{ - int len = fp->uf_lines.ga_len; - - if (!fp->uf_prof_initialized) { - if (len == 0) { - len = 1; // avoid getting error for allocating zero bytes - } - fp->uf_tm_count = 0; - fp->uf_tm_self = profile_zero(); - fp->uf_tm_total = profile_zero(); - - if (fp->uf_tml_count == NULL) { - fp->uf_tml_count = xcalloc((size_t)len, sizeof(int)); - } - - if (fp->uf_tml_total == NULL) { - fp->uf_tml_total = xcalloc((size_t)len, sizeof(proftime_T)); - } - - if (fp->uf_tml_self == NULL) { - fp->uf_tml_self = xcalloc((size_t)len, sizeof(proftime_T)); - } - - fp->uf_tml_idx = -1; - fp->uf_prof_initialized = true; - } - - fp->uf_profiling = TRUE; -} - -/// Dump the profiling results for all functions in file "fd". -void func_dump_profile(FILE *fd) -{ - hashitem_T *hi; - int todo; - ufunc_T *fp; - ufunc_T **sorttab; - int st_len = 0; - - todo = (int)func_hashtab.ht_used; - if (todo == 0) { - return; // nothing to dump - } - - sorttab = xmalloc(sizeof(ufunc_T *) * (size_t)todo); - - for (hi = func_hashtab.ht_array; todo > 0; ++hi) { - if (!HASHITEM_EMPTY(hi)) { - --todo; - fp = HI2UF(hi); - if (fp->uf_prof_initialized) { - sorttab[st_len++] = fp; - - if (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); - } - if (fp->uf_script_ctx.sc_sid != 0) { - bool should_free; - const LastSet last_set = (LastSet){ - .script_ctx = fp->uf_script_ctx, - .channel_id = 0, - }; - char *p = (char *)get_scriptname(last_set, &should_free); - fprintf(fd, " Defined: %s:%" PRIdLINENR "\n", - p, fp->uf_script_ctx.sc_lnum); - if (should_free) { - xfree(p); - } - } - if (fp->uf_tm_count == 1) { - fprintf(fd, "Called 1 time\n"); - } else { - fprintf(fd, "Called %d times\n", fp->uf_tm_count); - } - fprintf(fd, "Total time: %s\n", profile_msg(fp->uf_tm_total)); - fprintf(fd, " Self time: %s\n", profile_msg(fp->uf_tm_self)); - fprintf(fd, "\n"); - fprintf(fd, "count total (s) self (s)\n"); - - for (int i = 0; i < fp->uf_lines.ga_len; ++i) { - if (FUNCLINE(fp, i) == NULL) { - continue; - } - prof_func_line(fd, fp->uf_tml_count[i], - &fp->uf_tml_total[i], &fp->uf_tml_self[i], TRUE); - fprintf(fd, "%s\n", FUNCLINE(fp, i)); - } - fprintf(fd, "\n"); - } - } - } - - if (st_len > 0) { - qsort((void *)sorttab, (size_t)st_len, sizeof(ufunc_T *), - prof_total_cmp); - prof_sort_list(fd, sorttab, st_len, "TOTAL", FALSE); - qsort((void *)sorttab, (size_t)st_len, sizeof(ufunc_T *), - prof_self_cmp); - prof_sort_list(fd, sorttab, st_len, "SELF", TRUE); - } - - xfree(sorttab); -} - -/// @param prefer_self when equal print only self time -static void prof_sort_list(FILE *fd, ufunc_T **sorttab, int st_len, char *title, int prefer_self) -{ - int i; - ufunc_T *fp; - - fprintf(fd, "FUNCTIONS SORTED ON %s TIME\n", title); - fprintf(fd, "count total (s) self (s) function\n"); - for (i = 0; i < 20 && i < st_len; ++i) { - 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) { - fprintf(fd, " <SNR>%s()\n", fp->uf_name + 3); - } else { - fprintf(fd, " %s()\n", fp->uf_name); - } - } - fprintf(fd, "\n"); -} - -/// 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, - int prefer_self) -{ - if (count > 0) { - fprintf(fd, "%5d ", count); - if (prefer_self && profile_equal(*total, *self)) { - fprintf(fd, " "); - } else { - fprintf(fd, "%s ", profile_msg(*total)); - } - if (!prefer_self && profile_equal(*total, *self)) { - fprintf(fd, " "); - } else { - fprintf(fd, "%s ", profile_msg(*self)); - } - } else { - fprintf(fd, " "); - } -} - -/// Compare function for total time sorting. -static int prof_total_cmp(const void *s1, const void *s2) -{ - ufunc_T *p1 = *(ufunc_T **)s1; - ufunc_T *p2 = *(ufunc_T **)s2; - return profile_cmp(p1->uf_tm_total, p2->uf_tm_total); -} - -/// Compare function for self time sorting. -static int prof_self_cmp(const void *s1, const void *s2) -{ - ufunc_T *p1 = *(ufunc_T **)s1; - ufunc_T *p2 = *(ufunc_T **)s2; - return profile_cmp(p1->uf_tm_self, p2->uf_tm_self); -} - /// Return the autoload script name for a function or variable name /// Caller must make sure that "name" contains AUTOLOAD_CHAR. /// @@ -8182,61 +7849,6 @@ bool script_autoload(const char *const name, const size_t name_len, const bool r return ret; } -/// Called when starting to read a function line. -/// "sourcing_lnum" must be correct! -/// When skipping lines it may not actually be executed, but we won't find out -/// until later and we need to store the time now. -void func_line_start(void *cookie) -{ - funccall_T *fcp = (funccall_T *)cookie; - ufunc_T *fp = fcp->func; - - if (fp->uf_profiling && sourcing_lnum >= 1 - && sourcing_lnum <= fp->uf_lines.ga_len) { - fp->uf_tml_idx = sourcing_lnum - 1; - // Skip continuation lines. - while (fp->uf_tml_idx > 0 && FUNCLINE(fp, fp->uf_tml_idx) == NULL) { - fp->uf_tml_idx--; - } - fp->uf_tml_execed = false; - fp->uf_tml_start = profile_start(); - fp->uf_tml_children = profile_zero(); - fp->uf_tml_wait = profile_get_wait(); - } -} - -/// Called when actually executing a function line. -void func_line_exec(void *cookie) -{ - funccall_T *fcp = (funccall_T *)cookie; - ufunc_T *fp = fcp->func; - - if (fp->uf_profiling && fp->uf_tml_idx >= 0) { - fp->uf_tml_execed = TRUE; - } -} - -/// Called when done with a function line. -void func_line_end(void *cookie) -{ - funccall_T *fcp = (funccall_T *)cookie; - ufunc_T *fp = fcp->func; - - if (fp->uf_profiling && fp->uf_tml_idx >= 0) { - if (fp->uf_tml_execed) { - ++fp->uf_tml_count[fp->uf_tml_idx]; - fp->uf_tml_start = profile_end(fp->uf_tml_start); - fp->uf_tml_start = profile_sub_wait(fp->uf_tml_wait, fp->uf_tml_start); - fp->uf_tml_total[fp->uf_tml_idx] = - profile_add(fp->uf_tml_total[fp->uf_tml_idx], fp->uf_tml_start); - fp->uf_tml_self[fp->uf_tml_idx] = - profile_self(fp->uf_tml_self[fp->uf_tml_idx], fp->uf_tml_start, - fp->uf_tml_children); - } - fp->uf_tml_idx = -1; - } -} - static var_flavour_T var_flavour(char *varname) FUNC_ATTR_PURE { @@ -8324,11 +7936,9 @@ int store_session_globals(FILE *fd) } if ((fprintf(fd, "let %s = %c%s%c", this_var->di_key, - ((this_var->di_tv.v_type == VAR_STRING) ? '"' - : ' '), + ((this_var->di_tv.v_type == VAR_STRING) ? '"' : ' '), p, - ((this_var->di_tv.v_type == VAR_STRING) ? '"' - : ' ')) < 0) + ((this_var->di_tv.v_type == VAR_STRING) ? '"' : ' ')) < 0) || put_eol(fd) == FAIL) { xfree(p); return FAIL; @@ -8370,7 +7980,7 @@ void option_last_set_msg(LastSet last_set) { if (last_set.script_ctx.sc_sid != 0) { bool should_free; - char *p = (char *)get_scriptname(last_set, &should_free); + char *p = get_scriptname(last_set, &should_free); verbose_enter(); msg_puts(_("\n\tLast set from ")); msg_puts(p); @@ -8413,10 +8023,8 @@ int modify_fname(char *src, bool tilde_file, size_t *usedlen, char **fnamep, cha size_t *fnamelen) { int valid = 0; - char *tail; char *s, *p, *pbuf; char dirname[MAXPATHL]; - int c; bool has_fullname = false; bool has_homerelative = false; @@ -8469,7 +8077,7 @@ repeat: } // Append a path separator to a directory. - if (os_isdir((char_u *)(*fnamep))) { + if (os_isdir(*fnamep)) { // Make room for one or two extra characters. *fnamep = xstrnsave(*fnamep, STRLEN(*fnamep) + 2); xfree(*bufp); // free any allocated file name @@ -8478,6 +8086,8 @@ repeat: } } + int c; + // ":." - path relative to the current directory // ":~" - path relative to the home directory // ":8" - shortname path - postponed till after @@ -8533,6 +8143,7 @@ repeat: // Only replace it when it starts with '~' if (*dirname == '~') { s = xstrdup(dirname); + assert(s != NULL); // suppress clang "Argument with 'nonnull' attribute passed null" *fnamep = s; xfree(*bufp); *bufp = s; @@ -8543,7 +8154,7 @@ repeat: } } - tail = path_tail(*fnamep); + char *tail = path_tail(*fnamep); *fnamelen = STRLEN(*fnamep); // ":h" - head, remove "/file_name", can be repeated @@ -8551,7 +8162,7 @@ repeat: while (src[*usedlen] == ':' && src[*usedlen + 1] == 'h') { valid |= VALID_HEAD; *usedlen += 2; - s = (char *)get_past_head((char_u *)(*fnamep)); + s = get_past_head(*fnamep); while (tail > s && after_pathsep(s, tail)) { MB_PTR_BACK(*fnamep, tail); } @@ -8584,10 +8195,9 @@ repeat: // ":r" - root, without extension, can be repeated while (src[*usedlen] == ':' && (src[*usedlen + 1] == 'e' || src[*usedlen + 1] == 'r')) { - /* find a '.' in the tail: - * - for second :e: before the current fname - * - otherwise: The last '.' - */ + // find a '.' in the tail: + // - for second :e: before the current fname + // - otherwise: The last '.' const bool is_second_e = *fnamep > tail; if (src[*usedlen + 1] == 'e' && is_second_e) { s = (*fnamep) - 2; @@ -8638,18 +8248,16 @@ repeat: if (src[*usedlen] == ':' && (src[*usedlen + 1] == 's' || (src[*usedlen + 1] == 'g' && src[*usedlen + 2] == 's'))) { - int sep; - char *flags; - int didit = false; + bool didit = false; - flags = ""; + char *flags = ""; s = src + *usedlen + 2; if (src[*usedlen + 1] == 'g') { flags = "g"; s++; } - sep = (char_u)(*s++); + int sep = (char_u)(*s++); if (sep) { // find end of pattern p = vim_strchr(s, sep); @@ -8667,7 +8275,7 @@ repeat: *fnamelen = STRLEN(s); xfree(*bufp); *bufp = s; - didit = TRUE; + didit = true; xfree(sub); xfree(str); } @@ -8708,26 +8316,22 @@ char *do_string_sub(char *str, char *pat, char *sub, typval_T *expr, char *flags { int sublen; regmatch_T regmatch; - int do_all; - char *tail; - char *end; garray_T ga; - char *save_cpo; char *zero_width = NULL; // Make 'cpoptions' empty, so that the 'l' flag doesn't work here - save_cpo = p_cpo; - p_cpo = (char *)empty_option; + char *save_cpo = p_cpo; + p_cpo = empty_option; ga_init(&ga, 1, 200); - do_all = (flags[0] == 'g'); + int do_all = (flags[0] == 'g'); regmatch.rm_ic = p_ic; regmatch.regprog = vim_regcomp(pat, RE_MAGIC + RE_STRING); if (regmatch.regprog != NULL) { - tail = str; - end = str + STRLEN(str); + char *tail = str; + char *end = str + STRLEN(str); while (vim_regexec_nl(®match, (char_u *)str, (colnr_T)(tail - str))) { // Skip empty match except for first match. if (regmatch.startp[0] == regmatch.endp[0]) { @@ -8777,11 +8381,16 @@ char *do_string_sub(char *str, char *pat, char *sub, typval_T *expr, char *flags char *ret = xstrdup(ga.ga_data == NULL ? str : ga.ga_data); ga_clear(&ga); - if ((char_u *)p_cpo == empty_option) { + if (p_cpo == empty_option) { p_cpo = save_cpo; } else { // Darn, evaluating {sub} expression or {expr} changed the value. - free_string_option((char_u *)save_cpo); + // If it's still empty it was changed and restored, need to restore in + // the complicated way. + if (*p_cpo == NUL) { + set_option_value_give_err("cpo", 0L, save_cpo, 0); + } + free_string_option(save_cpo); } return ret; @@ -8868,8 +8477,7 @@ typval_T eval_call_provider(char *provider, char *method, list_T *arguments, boo struct caller_scope saved_provider_caller_scope = provider_caller_scope; provider_caller_scope = (struct caller_scope) { .script_ctx = current_sctx, - .sourcing_name = sourcing_name, - .sourcing_lnum = sourcing_lnum, + .es_entry = ((estack_T *)exestack.ga_data)[exestack.ga_len - 1], .autocmd_fname = autocmd_fname, .autocmd_match = autocmd_match, .autocmd_bufnr = autocmd_bufnr, @@ -8968,8 +8576,8 @@ bool eval_has_provider(const char *feat) /// Writes "<sourcing_name>:<sourcing_lnum>" to `buf[bufsize]`. void eval_fmt_source_name_line(char *buf, size_t bufsize) { - if (sourcing_name) { - snprintf(buf, bufsize, "%s:%" PRIdLINENR, sourcing_name, sourcing_lnum); + if (SOURCING_NAME) { + snprintf(buf, bufsize, "%s:%" PRIdLINENR, SOURCING_NAME, SOURCING_LNUM); } else { snprintf(buf, bufsize, "?"); } @@ -8988,7 +8596,7 @@ void ex_checkhealth(exarg_T *eap) if (vimruntime_env == NULL) { emsg(_("E5009: $VIMRUNTIME is empty or unset")); } else { - bool rtp_ok = NULL != strstr((char *)p_rtp, vimruntime_env); + bool rtp_ok = NULL != strstr(p_rtp, vimruntime_env); if (rtp_ok) { semsg(_("E5009: Invalid $VIMRUNTIME: %s"), vimruntime_env); } else { @@ -9011,8 +8619,6 @@ void invoke_prompt_callback(void) { typval_T rettv; typval_T argv[2]; - char *text; - char *prompt; linenr_T lnum = curbuf->b_ml.ml_line_count; // Add a new line for the prompt before invoking the callback, so that @@ -9024,8 +8630,8 @@ void invoke_prompt_callback(void) if (curbuf->b_prompt_callback.type == kCallbackNone) { return; } - text = (char *)ml_get(lnum); - prompt = (char *)prompt_text(); + char *text = (char *)ml_get(lnum); + char *prompt = (char *)prompt_text(); if (STRLEN(text) >= STRLEN(prompt)) { text += STRLEN(prompt); } diff --git a/src/nvim/eval.h b/src/nvim/eval.h index 7dbd18737a..b0cb5fd8c1 100644 --- a/src/nvim/eval.h +++ b/src/nvim/eval.h @@ -11,15 +11,6 @@ #define COPYID_INC 2 #define COPYID_MASK (~0x1) -// All user-defined functions are found in this hashtable. -extern hashtab_T func_hashtab; - -// From user function to hashitem and back. -EXTERN ufunc_T dumuf; -#define UF2HIKEY(fp) ((fp)->uf_name) -#define HIKEY2UF(p) ((ufunc_T *)(p - offsetof(ufunc_T, uf_name))) -#define HI2UF(hi) HIKEY2UF((hi)->hi_key) - /* * Structure returned by get_lval() and used by set_var_lval(). * For a plain name: diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index 6d8776d08b..3e89489459 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -23,7 +23,7 @@ end return { funcs={ abs={args=1, base=1}, - acos={args=1, base=1, func="float_op_wrapper", data="&acos"}, -- WJMc + acos={args=1, base=1, float_func="acos"}, -- WJMc add={args=2, base=1}, ['and']={args=2, base=1}, api_info={}, @@ -33,7 +33,7 @@ return { argidx={}, arglistid={args={0, 2}}, argv={args={0, 2}}, - asin={args=1, base=1, func="float_op_wrapper", data="&asin"}, -- WJMc + asin={args=1, base=1, float_func="asin"}, -- WJMc assert_beeps={args=1, base=1}, assert_equal={args={2, 3}, base=2}, assert_equalfile={args={2, 3}, base=1}, @@ -47,7 +47,7 @@ return { assert_notmatch={args={2, 3}, base=2}, assert_report={args=1, base=1}, assert_true={args={1, 2}, base=1}, - atan={args=1, base=1, func="float_op_wrapper", data="&atan"}, + atan={args=1, base=1, float_func="atan"}, atan2={args=2, base=1}, browse={args=4}, browsedir={args=2}, @@ -67,11 +67,12 @@ return { byteidx={args=2, base=1}, byteidxcomp={args=2, base=1}, call={args={2, 3}, base=1}, - ceil={args=1, base=1, func="float_op_wrapper", data="&ceil"}, + ceil={args=1, base=1, float_func="ceil"}, changenr={}, chanclose={args={1, 2}}, chansend={args=2}, char2nr={args={1, 2}, base=1}, + charclass={args=1, base=1}, charcol={args=1, base=1}, charidx={args={2, 3}, base=1}, chdir={args=1, base=1}, @@ -84,8 +85,8 @@ return { complete_info={args={0, 1}, base=1}, confirm={args={1, 4}, base=1}, copy={args=1, base=1}, - cos={args=1, base=1, func="float_op_wrapper", data="&cos"}, - cosh={args=1, base=1, func="float_op_wrapper", data="&cosh"}, + 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}}, @@ -116,7 +117,7 @@ return { execute={args={1, 2}, base=1}, exepath={args=1, base=1}, exists={args=1, base=1}, - exp={args=1, base=1, func="float_op_wrapper", data="&exp"}, + exp={args=1, base=1, float_func="exp"}, expand={args={1, 3}, base=1}, expandcmd={args=1, base=1}, extend={args={2, 3}, base=1}, @@ -129,7 +130,7 @@ return { findfile={args={1, 3}, base=1}, flatten={args={1, 2}, base=1}, float2nr={args=1, base=1}, - floor={args=1, base=1, func="float_op_wrapper", data="&floor"}, + floor={args=1, base=1, float_func="floor"}, fmod={args=2, base=1}, fnameescape={args=1, base=1}, fnamemodify={args=2, base=1}, @@ -244,8 +245,8 @@ return { lispindent={args=1, base=1}, list2str={args={1, 2}, base=1}, localtime={}, - log={args=1, base=1, func="float_op_wrapper", data="&log"}, - log10={args=1, base=1, func="float_op_wrapper", data="&log10"}, + log={args=1, base=1, float_func="log"}, + log10={args=1, base=1, float_func="log10"}, luaeval={args={1, 2}, base=1}, map={args=2, base=1}, maparg={args={1, 4}, base=1}, @@ -303,7 +304,7 @@ return { ['repeat']={args=2, base=1}, resolve={args=1, base=1}, reverse={args=1, base=1}, - round={args=1, base=1, func="float_op_wrapper", data="&round"}, + round={args=1, base=1, float_func="round"}, rpcnotify={args=varargs(2)}, rpcrequest={args=varargs(2)}, rpcstart={args={1, 2}}, @@ -327,9 +328,11 @@ return { serverstop={args=1}, setbufline={args=3, base=3}, setbufvar={args=3, base=3}, + setcellwidths={args=1, base=1}, setcharpos={args=2, base=2}, setcharsearch={args=1, base=1}, setcmdpos={args=1, base=1}, + setcmdline={args={1, 2}, base=1}, setcursorcharpos={args={1, 3}, base=1}, setenv={args=2, base=2}, setfperm={args=2, base=1}, @@ -356,8 +359,8 @@ return { sign_unplace={args={1, 2}, base=1}, sign_unplacelist={args=1, base=1}, simplify={args=1, base=1}, - sin={args=1, base=1, func="float_op_wrapper", data="&sin"}, - sinh={args=1, base=1, func="float_op_wrapper", data="&sinh"}, + sin={args=1, base=1, float_func="sin"}, + sinh={args=1, base=1, float_func="sinh"}, sockconnect={args={2,3}}, sort={args={1, 3}, base=1}, soundfold={args=1, base=1}, @@ -365,7 +368,7 @@ return { spellbadword={args={0, 1}, base=1}, spellsuggest={args={1, 3}, base=1}, split={args={1, 3}, base=1}, - sqrt={args=1, base=1, func="float_op_wrapper", data="&sqrt"}, + sqrt={args=1, base=1, float_func="sqrt"}, srand={args={0, 1}, base=1}, stdpath={args=1}, str2float={args=1, base=1}, @@ -400,8 +403,8 @@ return { tabpagewinnr={args={1, 2}, base=1}, tagfiles={}, taglist={args={1, 2}, base=1}, - tan={args=1, base=1, func="float_op_wrapper", data="&tan"}, - tanh={args=1, base=1, func="float_op_wrapper", data="&tanh"}, + tan={args=1, base=1, float_func="tan"}, + tanh={args=1, base=1, float_func="tanh"}, tempname={}, termopen={args={1, 2}}, test_garbagecollect_now={}, @@ -415,7 +418,7 @@ return { toupper={args=1, base=1}, tr={args=3, base=1}, trim={args={1, 3}, base=1}, - trunc={args=1, base=1, func="float_op_wrapper", data="&trunc"}, + trunc={args=1, base=1, float_func="trunc"}, type={args=1, base=1}, undofile={args=1, base=1}, undotree={}, diff --git a/src/nvim/eval/encode.c b/src/nvim/eval/encode.c index 090939666d..bb514fba47 100644 --- a/src/nvim/eval/encode.c +++ b/src/nvim/eval/encode.c @@ -30,12 +30,12 @@ #include "nvim/vim.h" // For _() const char *const encode_bool_var_names[] = { - [kBoolVarTrue] = "true", - [kBoolVarFalse] = "false", + [kBoolVarTrue] = "v:true", + [kBoolVarFalse] = "v:false", }; const char *const encode_special_var_names[] = { - [kSpecialVarNull] = "null", + [kSpecialVarNull] = "v:null", }; #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/eval/executor.c b/src/nvim/eval/executor.c index b461456a3a..0e0d0fe696 100644 --- a/src/nvim/eval/executor.c +++ b/src/nvim/eval/executor.c @@ -108,8 +108,7 @@ int eexe_mod_op(typval_T *const tv1, const typval_T *const tv2, const char *cons const char *tvs = tv_get_string(tv1); char numbuf[NUMBUFLEN]; char *const s = - (char *)concat_str((const char_u *)tvs, (const char_u *)tv_get_string_buf(tv2, - numbuf)); + concat_str(tvs, tv_get_string_buf(tv2, numbuf)); tv_clear(tv1); tv1->v_type = VAR_STRING; tv1->vval.v_string = s; diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 060dc50f52..aa328d50ef 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -7,12 +7,15 @@ #include "nvim/api/private/converter.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/buffer.h" #include "nvim/change.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" @@ -26,11 +29,14 @@ #include "nvim/eval/typval.h" #include "nvim/eval/userfunc.h" #include "nvim/eval/vars.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/getchar.h" #include "nvim/globals.h" #include "nvim/highlight_group.h" #include "nvim/if_cscope.h" @@ -45,25 +51,29 @@ #include "nvim/match.h" #include "nvim/math.h" #include "nvim/memline.h" +#include "nvim/menu.h" #include "nvim/mouse.h" #include "nvim/move.h" #include "nvim/msgpack_rpc/channel.h" #include "nvim/msgpack_rpc/server.h" #include "nvim/ops.h" #include "nvim/option.h" +#include "nvim/optionstr.h" #include "nvim/os/dl.h" -#include "nvim/os/input.h" #include "nvim/os/shell.h" #include "nvim/path.h" #include "nvim/plines.h" -#include "nvim/popupmnu.h" +#include "nvim/popupmenu.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/syntax.h" #include "nvim/tag.h" @@ -117,13 +127,12 @@ static va_list dummy_ap; char *get_function_name(expand_T *xp, int idx) { static int intidx = -1; - char_u *name; if (idx == 0) { intidx = -1; } if (intidx < 0) { - name = (char_u *)get_user_func_name(xp, idx); + char_u *name = (char_u *)get_user_func_name(xp, idx); if (name != NULL) { if (*name != NUL && *name != '<' && STRNCMP("g:", xp->xp_pattern, 2) == 0) { @@ -154,13 +163,12 @@ char *get_function_name(expand_T *xp, int idx) char *get_expr_name(expand_T *xp, int idx) { static int intidx = -1; - char_u *name; if (idx == 0) { intidx = -1; } if (intidx < 0) { - name = (char_u *)get_function_name(xp, idx); + char_u *name = (char_u *)get_function_name(xp, idx); if (name != NULL) { return (char *)name; } @@ -226,7 +234,7 @@ int call_internal_method(const char_u *const fname, const int argcount, typval_T return ERROR_NONE; } -/// @return TRUE for a non-zero Number and a non-empty String. +/// @return true for a non-zero Number and a non-empty String. static int non_zero_arg(typval_T *argvars) { return ((argvars[0].v_type == VAR_NUMBER @@ -243,26 +251,25 @@ static int non_zero_arg(typval_T *argvars) /// Some versions of glibc on i386 have an optimization that makes it harder to /// call math functions indirectly from inside an inlined function, causing /// compile-time errors. Avoid `inline` in that case. #3072 -static void float_op_wrapper(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void float_op_wrapper(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { float_T f; - float_T (*function)(float_T) = (float_T (*)(float_T)) fptr; rettv->v_type = VAR_FLOAT; if (tv_get_float_chk(argvars, &f)) { - rettv->vval.v_float = function(f); + rettv->vval.v_float = fptr.float_func(f); } else { rettv->vval.v_float = 0.0; } } -static void api_wrapper(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void api_wrapper(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (check_secure()) { return; } - ApiDispatchWrapper fn = (ApiDispatchWrapper)fptr; + MsgpackRpcRequestHandler handler = *fptr.api_handler; Array args = ARRAY_DICT_INIT; @@ -271,7 +278,8 @@ static void api_wrapper(typval_T *argvars, typval_T *rettv, FunPtr fptr) } Error err = ERROR_INIT; - Object result = fn(VIML_INTERNAL_CALL, args, &err); + Arena res_arena = ARENA_EMPTY; + Object result = handler.fn(VIML_INTERNAL_CALL, args, &res_arena, &err); if (ERROR_SET(&err)) { semsg_multiline((const char *)e_api_error, err.msg); @@ -284,20 +292,23 @@ static void api_wrapper(typval_T *argvars, typval_T *rettv, FunPtr fptr) end: api_free_array(args); - api_free_object(result); + if (handler.arena_return) { + arena_mem_free(arena_finish(&res_arena)); + } else { + api_free_object(result); + } api_clear_error(&err); } /// "abs(expr)" function -static void f_abs(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_abs(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (argvars[0].v_type == VAR_FLOAT) { - float_op_wrapper(argvars, rettv, (FunPtr)&fabs); + float_op_wrapper(argvars, rettv, (EvalFuncData){ .float_func = &fabs }); } else { - varnumber_T n; bool error = false; - n = tv_get_number_chk(&argvars[0], &error); + varnumber_T n = tv_get_number_chk(&argvars[0], &error); if (error) { rettv->vval.v_number = -1; } else if (n > 0) { @@ -309,7 +320,7 @@ static void f_abs(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "add(list, item)" function -static void f_add(typval_T *argvars, typval_T *rettv, FunPtr fptr) +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) { @@ -337,22 +348,21 @@ static void f_add(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "and(expr, expr)" function -static void f_and(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_and(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = tv_get_number_chk(&argvars[0], NULL) & tv_get_number_chk(&argvars[1], NULL); } /// "api_info()" function -static void f_api_info(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_api_info(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { Dictionary metadata = api_metadata(); (void)object_to_vim(DICTIONARY_OBJ(metadata), rettv, NULL); - api_free_dictionary(metadata); } /// "append(lnum, string/list)" function -static void f_append(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_append(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const linenr_T lnum = tv_get_lnum(&argvars[0]); @@ -360,7 +370,7 @@ static void f_append(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "appendbufline(buf, lnum, string/list)" function -static void f_appendbufline(typval_T *argvars, typval_T *rettv, FunPtr fptr) +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) { @@ -371,79 +381,8 @@ static void f_appendbufline(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -static void f_argc(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - if (argvars[0].v_type == VAR_UNKNOWN) { - // use the current window - rettv->vval.v_number = ARGCOUNT; - } else if (argvars[0].v_type == VAR_NUMBER - && tv_get_number(&argvars[0]) == -1) { - // use the global argument list - rettv->vval.v_number = GARGCOUNT; - } else { - // use the argument list of the specified window - win_T *wp = find_win_by_nr_or_id(&argvars[0]); - if (wp != NULL) { - rettv->vval.v_number = WARGCOUNT(wp); - } else { - rettv->vval.v_number = -1; - } - } -} - -/// "argidx()" function -static void f_argidx(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = curwin->w_arg_idx; -} - -/// "arglistid" function -static void f_arglistid(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = -1; - win_T *wp = find_tabwin(&argvars[0], &argvars[1]); - if (wp != NULL) { - rettv->vval.v_number = wp->w_alist->id; - } -} - -/// "argv(nr)" function -static void f_argv(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - aentry_T *arglist = NULL; - int argcount = -1; - - if (argvars[0].v_type != VAR_UNKNOWN) { - if (argvars[1].v_type == VAR_UNKNOWN) { - arglist = ARGLIST; - argcount = ARGCOUNT; - } else if (argvars[1].v_type == VAR_NUMBER - && tv_get_number(&argvars[1]) == -1) { - arglist = GARGLIST; - argcount = GARGCOUNT; - } else { - win_T *wp = find_win_by_nr_or_id(&argvars[1]); - if (wp != NULL) { - // Use the argument list of the specified window - arglist = WARGLIST(wp); - argcount = WARGCOUNT(wp); - } - } - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - int idx = (int)tv_get_number_chk(&argvars[0], NULL); - if (arglist != NULL && idx >= 0 && idx < argcount) { - rettv->vval.v_string = xstrdup((const char *)alist_name(&arglist[idx])); - } else if (idx == -1) { - get_arglist_as_rettv(arglist, argcount, rettv); - } - } else { - get_arglist_as_rettv(ARGLIST, ARGCOUNT, rettv); - } -} - /// "atan2()" function -static void f_atan2(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_atan2(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { float_T fx; float_T fy; @@ -457,16 +396,16 @@ static void f_atan2(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "browse(save, title, initdir, default)" function -static void f_browse(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_browse(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_string = NULL; rettv->v_type = VAR_STRING; } /// "browsedir(title, initdir)" function -static void f_browsedir(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_browsedir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - f_browse(argvars, rettv, NULL); + f_browse(argvars, rettv, fptr); } /// Find a buffer by number or exact name. @@ -479,8 +418,8 @@ static buf_T *find_buffer(typval_T *avar) } 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. */ + // 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)) @@ -495,7 +434,7 @@ static buf_T *find_buffer(typval_T *avar) } /// "bufadd(expr)" function -static void f_bufadd(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_bufadd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { char_u *name = (char_u *)tv_get_string(&argvars[0]); @@ -503,13 +442,13 @@ static void f_bufadd(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "bufexists(expr)" function -static void f_bufexists(typval_T *argvars, typval_T *rettv, FunPtr fptr) +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, FunPtr fptr) +static void f_buflisted(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { buf_T *buf; @@ -518,7 +457,7 @@ static void f_buflisted(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "bufload(expr)" function -static void f_bufload(typval_T *argvars, typval_T *unused, FunPtr fptr) +static void f_bufload(typval_T *argvars, typval_T *unused, EvalFuncData fptr) { buf_T *buf = get_buf_arg(&argvars[0]); @@ -533,7 +472,7 @@ static void f_bufload(typval_T *argvars, typval_T *unused, FunPtr fptr) } /// "bufloaded(expr)" function -static void f_bufloaded(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_bufloaded(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { buf_T *buf; @@ -542,7 +481,7 @@ static void f_bufloaded(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "bufname(expr)" function -static void f_bufname(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_bufname(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const buf_T *buf; rettv->v_type = VAR_STRING; @@ -558,7 +497,7 @@ static void f_bufname(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "bufnr(expr)" function -static void f_bufnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_bufnr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const buf_T *buf; bool error = false; @@ -618,13 +557,13 @@ static void buf_win_common(typval_T *argvars, typval_T *rettv, bool get_nr) } /// "bufwinid(nr)" function -static void f_bufwinid(typval_T *argvars, typval_T *rettv, FunPtr fptr) +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, FunPtr fptr) +static void f_bufwinnr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { buf_win_common(argvars, rettv, true); } @@ -632,17 +571,15 @@ static void f_bufwinnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// Get buffer by number or pattern. buf_T *tv_get_buf(typval_T *tv, int curtab_only) { - char_u *name = (char_u *)tv->vval.v_string; - int save_magic; - char *save_cpo; - buf_T *buf; - if (tv->v_type == VAR_NUMBER) { return buflist_findnr((int)tv->vval.v_number); } if (tv->v_type != VAR_STRING) { return NULL; } + + char_u *name = (char_u *)tv->vval.v_string; + if (name == NULL || *name == NUL) { return curbuf; } @@ -651,13 +588,13 @@ buf_T *tv_get_buf(typval_T *tv, int curtab_only) } // Ignore 'magic' and 'cpoptions' here to make scripts portable - save_magic = p_magic; - p_magic = TRUE; - save_cpo = p_cpo; - p_cpo = ""; + int save_magic = p_magic; + p_magic = true; + char *save_cpo = p_cpo; + p_cpo = empty_option; - buf = buflist_findnr(buflist_findpat((char *)name, (char *)name + STRLEN(name), - true, false, curtab_only)); + buf_T *buf = buflist_findnr(buflist_findpat((char *)name, (char *)name + STRLEN(name), + true, false, curtab_only)); p_magic = save_magic; p_cpo = save_cpo; @@ -686,10 +623,8 @@ buf_T *tv_get_buf_from_arg(typval_T *const tv) FUNC_ATTR_NONNULL_ALL /// valid. buf_T *get_buf_arg(typval_T *arg) { - buf_T *buf; - emsg_off++; - buf = tv_get_buf(arg, false); + buf_T *buf = tv_get_buf(arg, false); emsg_off--; if (buf == NULL) { semsg(_("E158: Invalid buffer name: %s"), tv_get_string(arg)); @@ -698,7 +633,7 @@ buf_T *get_buf_arg(typval_T *arg) } /// "byte2line(byte)" function -static void f_byte2line(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_byte2line(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { long boff = tv_get_number(&argvars[0]) - 1; if (boff < 0) { @@ -733,19 +668,19 @@ static void byteidx(typval_T *argvars, typval_T *rettv, int comp) } /// "byteidx()" function -static void f_byteidx(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_byteidx(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - byteidx(argvars, rettv, FALSE); + byteidx(argvars, rettv, false); } /// "byteidxcomp()" function -static void f_byteidxcomp(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_byteidxcomp(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - byteidx(argvars, rettv, TRUE); + byteidx(argvars, rettv, true); } /// "call(func, arglist [, dict])" function -static void f_call(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_call(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (argvars[1].v_type != VAR_LIST) { emsg(_(e_listreq)); @@ -758,7 +693,6 @@ static void f_call(typval_T *argvars, typval_T *rettv, FunPtr fptr) bool owned = false; char_u *func; partial_T *partial = NULL; - dict_T *selfdict = NULL; if (argvars[0].v_type == VAR_FUNC) { func = (char_u *)argvars[0].vval.v_string; } else if (argvars[0].v_type == VAR_PARTIAL) { @@ -776,6 +710,7 @@ static void f_call(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; // type error or empty name } + dict_T *selfdict = NULL; if (argvars[2].v_type != VAR_UNKNOWN) { if (argvars[2].v_type != VAR_DICT) { emsg(_(e_dictreq)); @@ -794,13 +729,13 @@ static void f_call(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "changenr()" function -static void f_changenr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_changenr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = curbuf->b_u_seq_cur; } /// "chanclose(id[, stream])" function -static void f_chanclose(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_chanclose(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_NUMBER; rettv->vval.v_number = 0; @@ -839,7 +774,7 @@ static void f_chanclose(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "chansend(id, data)" function -static void f_chansend(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_chansend(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_NUMBER; rettv->vval.v_number = 0; @@ -880,7 +815,7 @@ static void f_chansend(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "char2nr(string)" function -static void f_char2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_char2nr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (argvars[1].v_type != VAR_UNKNOWN) { if (!tv_check_num(&argvars[1])) { @@ -898,10 +833,9 @@ static void f_char2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr) static void get_col(typval_T *argvars, typval_T *rettv, bool charcol) { colnr_T col = 0; - pos_T *fp; int fnum = curbuf->b_fnum; - fp = var2fpos(&argvars[0], false, &fnum, charcol); + pos_T *fp = var2fpos(&argvars[0], false, &fnum, charcol); if (fp != NULL && fnum == curbuf->b_fnum) { if (fp->col == MAXCOL) { // '> can be MAXCOL, get the length of the line then @@ -915,11 +849,12 @@ static void get_col(typval_T *argvars, typval_T *rettv, bool charcol) // col(".") when the cursor is on the NUL at the end of the line // because of "coladd" can be seen as an extra column. if (virtual_active() && fp == &curwin->w_cursor) { - char_u *p = get_cursor_pos_ptr(); + char *p = (char *)get_cursor_pos_ptr(); if (curwin->w_cursor.coladd >= - (colnr_T)win_chartabsize(curwin, p, curwin->w_virtcol - curwin->w_cursor.coladd)) { + (colnr_T)win_chartabsize(curwin, p, + curwin->w_virtcol - curwin->w_cursor.coladd)) { int l; - if (*p != NUL && p[(l = utfc_ptr2len((char *)p))] == NUL) { + if (*p != NUL && p[(l = utfc_ptr2len(p))] == NUL) { col += l; } } @@ -930,20 +865,21 @@ static void get_col(typval_T *argvars, typval_T *rettv, bool charcol) } /// "charcol()" function -static void f_charcol(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_charcol(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { get_col(argvars, rettv, true); } /// "charidx()" function -static void f_charidx(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_charidx(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = -1; if (argvars[0].v_type != VAR_STRING || argvars[1].v_type != VAR_NUMBER || (argvars[2].v_type != VAR_UNKNOWN - && argvars[2].v_type != VAR_NUMBER)) { + && argvars[2].v_type != VAR_NUMBER + && argvars[2].v_type != VAR_BOOL)) { emsg(_(e_invarg)); return; } @@ -982,11 +918,8 @@ static void f_charidx(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "chdir(dir)" function -static void f_chdir(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_chdir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - char_u *cwd; - CdScope scope = kCdScopeGlobal; - rettv->v_type = VAR_STRING; rettv->vval.v_string = NULL; @@ -997,7 +930,7 @@ static void f_chdir(typval_T *argvars, typval_T *rettv, FunPtr fptr) } // Return the current directory - cwd = xmalloc(MAXPATHL); + char_u *cwd = xmalloc(MAXPATHL); if (os_dirname(cwd, MAXPATHL) != FAIL) { #ifdef BACKSLASH_IN_FILENAME slash_adjust(cwd); @@ -1006,6 +939,7 @@ static void f_chdir(typval_T *argvars, typval_T *rettv, FunPtr fptr) } xfree(cwd); + CdScope scope = kCdScopeGlobal; if (curwin->w_localdir != NULL) { scope = kCdScopeWindow; } else if (curtab->tp_localdir != NULL) { @@ -1019,13 +953,10 @@ static void f_chdir(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "cindent(lnum)" function -static void f_cindent(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_cindent(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - pos_T pos; - linenr_T lnum; - - pos = curwin->w_cursor; - lnum = tv_get_lnum(argvars); + pos_T pos = curwin->w_cursor; + linenr_T lnum = tv_get_lnum(argvars); if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) { curwin->w_cursor.lnum = lnum; rettv->vval.v_number = get_c_indent(); @@ -1050,24 +981,22 @@ win_T *get_optional_window(typval_T *argvars, int idx) } /// "col(string)" function -static void f_col(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_col(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { get_col(argvars, rettv, false); } /// "confirm(message, buttons[, default [, type]])" function -static void f_confirm(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_confirm(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { char buf[NUMBUFLEN]; char buf2[NUMBUFLEN]; - const char *message; const char *buttons = NULL; int def = 1; int type = VIM_GENERIC; - const char *typestr; bool error = false; - message = tv_get_string_chk(&argvars[0]); + const char *message = tv_get_string_chk(&argvars[0]); if (message == NULL) { error = true; } @@ -1079,7 +1008,7 @@ static void f_confirm(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (argvars[2].v_type != VAR_UNKNOWN) { def = (int)tv_get_number_chk(&argvars[2], &error); if (argvars[3].v_type != VAR_UNKNOWN) { - typestr = tv_get_string_buf_chk(&argvars[3], buf2); + const char *typestr = tv_get_string_buf_chk(&argvars[3], buf2); if (typestr == NULL) { error = true; } else { @@ -1111,13 +1040,13 @@ static void f_confirm(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "copy()" function -static void f_copy(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_copy(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { var_item_copy(NULL, &argvars[0], rettv, false, 0); } /// "count()" function -static void f_count(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_count(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { long n = 0; int ic = 0; @@ -1152,15 +1081,13 @@ static void f_count(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } } else if (argvars[0].v_type == VAR_LIST) { - listitem_T *li; - list_T *l; - long idx; + list_T *l = argvars[0].vval.v_list; - if ((l = argvars[0].vval.v_list) != NULL) { - li = tv_list_first(l); + if (l != NULL) { + listitem_T *li = tv_list_first(l); if (argvars[2].v_type != VAR_UNKNOWN) { if (argvars[3].v_type != VAR_UNKNOWN) { - idx = tv_get_number_chk(&argvars[3], &error); + long idx = tv_get_number_chk(&argvars[3], &error); if (!error) { li = tv_list_find(l, (int)idx); if (li == NULL) { @@ -1180,19 +1107,17 @@ static void f_count(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } } else if (argvars[0].v_type == VAR_DICT) { - int todo; - dict_T *d; - hashitem_T *hi; + dict_T *d = argvars[0].vval.v_dict; - if ((d = argvars[0].vval.v_dict) != NULL) { + if (d != NULL) { if (argvars[2].v_type != VAR_UNKNOWN) { if (argvars[3].v_type != VAR_UNKNOWN) { emsg(_(e_invarg)); } } - todo = error ? 0 : (int)d->dv_hashtab.ht_used; - for (hi = d->dv_hashtab.ht_array; todo > 0; ++hi) { + int todo = error ? 0 : (int)d->dv_hashtab.ht_used; + for (hashitem_T *hi = d->dv_hashtab.ht_array; todo > 0; hi++) { if (!HASHITEM_EMPTY(hi)) { todo--; if (tv_equal(&TV_DICT_HI2DI(hi)->di_tv, &argvars[1], ic, false)) { @@ -1210,7 +1135,7 @@ static void f_count(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "cscope_connection([{num} , {dbpath} [, {prepend}]])" function /// /// Checks the existence of a cscope connection. -static void f_cscope_connection(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_cscope_connection(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int num = 0; const char *dbpath = NULL; @@ -1231,7 +1156,7 @@ static void f_cscope_connection(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "ctxget([{index}])" function -static void f_ctxget(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_ctxget(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { size_t index = 0; if (argvars[0].v_type == VAR_NUMBER) { @@ -1255,7 +1180,7 @@ static void f_ctxget(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "ctxpop()" function -static void f_ctxpop(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_ctxpop(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (!ctx_restore(NULL, kCtxAll)) { emsg(_("Context stack is empty")); @@ -1263,7 +1188,7 @@ static void f_ctxpop(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "ctxpush([{types}])" function -static void f_ctxpush(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_ctxpush(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int types = kCtxAll; if (argvars[0].v_type == VAR_LIST) { @@ -1294,7 +1219,7 @@ static void f_ctxpush(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "ctxset({context}[, {index}])" function -static void f_ctxset(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_ctxset(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (argvars[0].v_type != VAR_DICT) { semsg(_(e_invarg2), "expected dictionary as first argument"); @@ -1334,7 +1259,7 @@ static void f_ctxset(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "ctxsize()" function -static void f_ctxsize(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_ctxsize(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_NUMBER; rettv->vval.v_number = (varnumber_T)ctx_size(); @@ -1406,18 +1331,16 @@ static void set_cursorpos(typval_T *argvars, typval_T *rettv, bool charcol) /// Moves the cursor to the specified line and column. /// /// @return 0 when the position could be set, -1 otherwise. -static void f_cursor(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_cursor(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { set_cursorpos(argvars, rettv, false); } /// "debugbreak()" function -static void f_debugbreak(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_debugbreak(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - int pid; - rettv->vval.v_number = FAIL; - pid = (int)tv_get_number(&argvars[0]); + int pid = (int)tv_get_number(&argvars[0]); if (pid == 0) { emsg(_(e_invarg)); } else { @@ -1436,7 +1359,7 @@ static void f_debugbreak(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "deepcopy()" function -static void f_deepcopy(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_deepcopy(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int noref = 0; @@ -1453,7 +1376,7 @@ static void f_deepcopy(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "delete()" function -static void f_delete(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_delete(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = -1; if (check_secure()) { @@ -1489,7 +1412,7 @@ static void f_delete(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// dictwatcheradd(dict, key, funcref) function -static void f_dictwatcheradd(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_dictwatcheradd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (check_secure()) { return; @@ -1527,7 +1450,7 @@ static void f_dictwatcheradd(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// dictwatcherdel(dict, key, funcref) function -static void f_dictwatcherdel(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_dictwatcherdel(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (check_secure()) { return; @@ -1562,12 +1485,8 @@ static void f_dictwatcherdel(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "deletebufline()" function -static void f_deletebufline(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_deletebufline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - linenr_T last; - buf_T *curbuf_save = NULL; - win_T *curwin_save = NULL; - buf_T *const buf = tv_get_buf(&argvars[0], false); if (buf == NULL) { rettv->vval.v_number = 1; // FAIL @@ -1576,6 +1495,7 @@ static void f_deletebufline(typval_T *argvars, typval_T *rettv, FunPtr fptr) 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); @@ -1589,6 +1509,8 @@ static void f_deletebufline(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } + buf_T *curbuf_save = NULL; + win_T *curwin_save = NULL; if (!is_curbuf) { VIsual_active = false; curbuf_save = curbuf; @@ -1639,19 +1561,19 @@ static void f_deletebufline(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "did_filetype()" function -static void f_did_filetype(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_did_filetype(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = did_filetype; } /// "diff_filler()" function -static void f_diff_filler(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_diff_filler(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = MAX(0, diff_check(curwin, tv_get_lnum(argvars))); } /// "diff_hlID()" function -static void f_diff_hlID(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_diff_hlID(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { linenr_T lnum = tv_get_lnum(argvars); static linenr_T prev_lnum = 0; @@ -1660,8 +1582,6 @@ static void f_diff_hlID(typval_T *argvars, typval_T *rettv, FunPtr fptr) static int change_start = 0; static int change_end = 0; static hlf_T hlID = (hlf_T)0; - int filler_lines; - int col; if (lnum < 0) { // ignore type error in {lnum} arg lnum = 0; @@ -1670,7 +1590,7 @@ static void f_diff_hlID(typval_T *argvars, typval_T *rettv, FunPtr fptr) || changedtick != buf_get_changedtick(curbuf) || fnum != curbuf->b_fnum) { // New line, buffer, change: need to get the values. - filler_lines = diff_check(curwin, lnum); + int filler_lines = diff_check(curwin, lnum); if (filler_lines < 0) { if (filler_lines == -1) { change_start = MAXCOL; @@ -1692,7 +1612,7 @@ static void f_diff_hlID(typval_T *argvars, typval_T *rettv, FunPtr fptr) } if (hlID == HLF_CHD || hlID == HLF_TXD) { - col = (int)tv_get_number(&argvars[1]) - 1; // Ignore type error in {col}. + int col = (int)tv_get_number(&argvars[1]) - 1; // Ignore type error in {col}. if (col >= change_start && col <= change_end) { hlID = HLF_TXD; // Changed text. } else { @@ -1703,7 +1623,7 @@ static void f_diff_hlID(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "empty({expr})" function -static void f_empty(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_empty(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { bool n = true; @@ -1753,7 +1673,7 @@ static void f_empty(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "environ()" function -static void f_environ(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_environ(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { tv_dict_alloc_ret(rettv); @@ -1798,7 +1718,7 @@ static void f_environ(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "escape({string}, {chars})" function -static void f_escape(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_escape(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { char buf[NUMBUFLEN]; @@ -1809,7 +1729,7 @@ static void f_escape(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "getenv()" function -static void f_getenv(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getenv(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { char_u *p = (char_u *)vim_getenv(tv_get_string(&argvars[0])); @@ -1823,7 +1743,7 @@ static void f_getenv(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "eval()" function -static void f_eval(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_eval(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const char *s = tv_get_string_chk(&argvars[0]); if (s != NULL) { @@ -1844,13 +1764,13 @@ static void f_eval(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "eventhandler()" function -static void f_eventhandler(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_eventhandler(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = vgetc_busy; } /// "executable()" function -static void f_executable(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_executable(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (tv_check_for_string(&argvars[0]) == FAIL) { return; @@ -1879,7 +1799,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, FunPtr fptr, int arg_off) +static 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; @@ -1958,13 +1878,13 @@ static void execute_common(typval_T *argvars, typval_T *rettv, FunPtr fptr, int } /// "execute(command)" function -static void f_execute(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_execute(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - execute_common(argvars, rettv, fptr, 0); + execute_common(argvars, rettv, 0); } /// "win_execute(win_id, command)" function -static void f_win_execute(typval_T *argvars, typval_T *rettv, FunPtr fptr) +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; @@ -1974,12 +1894,12 @@ static void f_win_execute(typval_T *argvars, typval_T *rettv, FunPtr fptr) tabpage_T *tp; win_T *wp = win_id2wp_tp(id, &tp); if (wp != NULL && tp != NULL) { - WIN_EXECUTE(wp, tp, execute_common(argvars, rettv, fptr, 1)); + WIN_EXECUTE(wp, tp, execute_common(argvars, rettv, 1)); } } /// "exepath()" function -static void f_exepath(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_exepath(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (tv_check_for_nonempty_string(&argvars[0]) == FAIL) { return; @@ -2000,7 +1920,7 @@ static void f_exepath(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "exists()" function -static void f_exists(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_exists(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int n = false; @@ -2040,14 +1960,10 @@ static void f_exists(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "expand()" function -static void f_expand(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_expand(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - size_t len; - char *errormsg; int options = WILD_SILENT|WILD_USE_NL|WILD_LIST_NOTFOUND; - expand_T xpc; bool error = false; - char_u *result; #ifdef BACKSLASH_IN_FILENAME char_u *p_csl_save = p_csl; @@ -2065,9 +1981,17 @@ static void f_expand(typval_T *argvars, typval_T *rettv, FunPtr fptr) const char *s = tv_get_string(&argvars[0]); if (*s == '%' || *s == '#' || *s == '<') { - emsg_off++; - result = eval_vars((char_u *)s, (char_u *)s, &len, NULL, &errormsg, NULL); - emsg_off--; + if (p_verbose == 0) { + emsg_off++; + } + size_t len; + char *errormsg = NULL; + char_u *result = eval_vars((char_u *)s, (char_u *)s, &len, NULL, &errormsg, NULL, false); + if (p_verbose == 0) { + emsg_off--; + } else if (errormsg != NULL) { + emsg(errormsg); + } if (rettv->v_type == VAR_LIST) { tv_list_alloc_ret(rettv, (result != NULL)); if (result != NULL) { @@ -2085,6 +2009,7 @@ static void f_expand(typval_T *argvars, typval_T *rettv, FunPtr fptr) options |= WILD_KEEP_ALL; } if (!error) { + expand_T xpc; ExpandInit(&xpc); xpc.xp_context = EXPAND_FILES; if (p_wic) { @@ -2112,7 +2037,7 @@ static void f_expand(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "menu_get(path [, modes])" function -static void f_menu_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_menu_get(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { tv_list_alloc_ret(rettv, kListLenMayKnow); int modes = MENU_ALL_MODES; @@ -2125,7 +2050,7 @@ static void f_menu_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "expandcmd()" function /// Expand all the special characters in a command string. -static void f_expandcmd(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_expandcmd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { char *errormsg = NULL; @@ -2141,18 +2066,16 @@ static void f_expandcmd(typval_T *argvars, typval_T *rettv, FunPtr fptr) }; eap.argt |= EX_NOSPC; + emsg_off++; expand_filename(&eap, &cmdstr, &errormsg); - if (errormsg != NULL && *errormsg != NUL) { - emsg(errormsg); - } + emsg_off--; + rettv->vval.v_string = cmdstr; } /// "flatten(list[, {maxdepth}])" function -static void f_flatten(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_flatten(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - list_T *list; - long maxdepth; bool error = false; if (argvars[0].v_type != VAR_LIST) { @@ -2160,6 +2083,7 @@ static void f_flatten(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } + long maxdepth; if (argvars[1].v_type == VAR_UNKNOWN) { maxdepth = 999999; } else { @@ -2173,7 +2097,7 @@ static void f_flatten(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } - list = argvars[0].vval.v_list; + list_T *list = argvars[0].vval.v_list; if (list != NULL && !var_check_lock(tv_list_locked(list), N_("flatten() argument"), @@ -2185,12 +2109,11 @@ static void f_flatten(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "extend(list, list [, idx])" function /// "extend(dict, dict [, action])" function -static void f_extend(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_extend(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const char *const arg_errmsg = N_("extend() argument"); if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_LIST) { - long before; bool error = false; list_T *const l1 = argvars[0].vval.v_list; @@ -2198,7 +2121,7 @@ static void f_extend(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (!var_check_lock(tv_list_locked(l1), arg_errmsg, TV_TRANSLATE)) { listitem_T *item; if (argvars[2].v_type != VAR_UNKNOWN) { - before = (long)tv_get_number_chk(&argvars[2], &error); + long before = (long)tv_get_number_chk(&argvars[2], &error); if (error) { return; // Type error; errmsg already given. } @@ -2262,7 +2185,7 @@ static void f_extend(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "feedkeys()" function -static void f_feedkeys(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_feedkeys(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { // This is not allowed in the sandbox. If the commands would still be // executed in the sandbox it would be OK, but it probably happens later, @@ -2283,17 +2206,17 @@ static void f_feedkeys(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "filereadable()" function -static void f_filereadable(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_filereadable(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const char *const p = tv_get_string(&argvars[0]); rettv->vval.v_number = - (*p && !os_isdir((const char_u *)p) && os_file_is_readable(p)); + (*p && !os_isdir(p) && os_file_is_readable(p)); } /// @return 0 for not writable /// 1 for writable file /// 2 for a dir which we have rights to write into. -static void f_filewritable(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_filewritable(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const char *filename = tv_get_string(&argvars[0]); rettv->vval.v_number = os_file_is_writable(filename); @@ -2302,7 +2225,7 @@ static void f_filewritable(typval_T *argvars, typval_T *rettv, FunPtr 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 : curbuf->b_p_path; + char_u *path = *curbuf->b_p_path == NUL ? p_path : (char_u *)curbuf->b_p_path; int count = 1; bool first = true; bool error = false; @@ -2343,7 +2266,7 @@ static void findfilendir(typval_T *argvars, typval_T *rettv, int find_what) find_what, (char_u *)curbuf->b_ffname, (find_what == FINDFILE_DIR ? (char_u *)"" - : curbuf->b_p_sua)); + : (char_u *)curbuf->b_p_sua)); first = false; if (fresult != NULL && rettv->v_type == VAR_LIST) { @@ -2358,25 +2281,25 @@ static void findfilendir(typval_T *argvars, typval_T *rettv, int find_what) } /// "filter()" function -static void f_filter(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_filter(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - filter_map(argvars, rettv, FALSE); + filter_map(argvars, rettv, false); } /// "finddir({fname}[, {path}[, {count}]])" function -static void f_finddir(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_finddir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { findfilendir(argvars, rettv, FINDFILE_DIR); } /// "findfile({fname}[, {path}[, {count}]])" function -static void f_findfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_findfile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { findfilendir(argvars, rettv, FINDFILE_FILE); } /// "float2nr({float})" function -static void f_float2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_float2nr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { float_T f; @@ -2392,7 +2315,7 @@ static void f_float2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "fmod()" function -static void f_fmod(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_fmod(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { float_T fx; float_T fy; @@ -2406,14 +2329,14 @@ static void f_fmod(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "fnameescape({string})" function -static void f_fnameescape(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_fnameescape(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_string = vim_strsave_fnameescape(tv_get_string(&argvars[0]), VSE_NONE); rettv->v_type = VAR_STRING; } /// "fnamemodify({fname}, {mods})" function -static void f_fnamemodify(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_fnamemodify(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { char_u *fbuf = NULL; size_t len = 0; @@ -2440,146 +2363,22 @@ static void f_fnamemodify(typval_T *argvars, typval_T *rettv, FunPtr fptr) xfree(fbuf); } -/// "foldclosed()" function -static void foldclosed_both(typval_T *argvars, typval_T *rettv, int end) -{ - const linenr_T lnum = tv_get_lnum(argvars); - if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) { - linenr_T first; - linenr_T last; - if (hasFoldingWin(curwin, lnum, &first, &last, false, NULL)) { - if (end) { - rettv->vval.v_number = (varnumber_T)last; - } else { - rettv->vval.v_number = (varnumber_T)first; - } - return; - } - } - rettv->vval.v_number = -1; -} - -/// "foldclosed()" function -static void f_foldclosed(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - foldclosed_both(argvars, rettv, FALSE); -} - -/// "foldclosedend()" function -static void f_foldclosedend(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - foldclosed_both(argvars, rettv, TRUE); -} - -/// "foldlevel()" function -static void f_foldlevel(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const linenr_T lnum = tv_get_lnum(argvars); - if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) { - rettv->vval.v_number = foldLevel(lnum); - } -} - -/// "foldtext()" function -static void f_foldtext(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - linenr_T foldstart; - linenr_T foldend; - char_u *dashes; - linenr_T lnum; - char_u *s; - char_u *r; - int len; - char *txt; - - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - - foldstart = (linenr_T)get_vim_var_nr(VV_FOLDSTART); - foldend = (linenr_T)get_vim_var_nr(VV_FOLDEND); - dashes = (char_u *)get_vim_var_str(VV_FOLDDASHES); - if (foldstart > 0 && foldend <= curbuf->b_ml.ml_line_count) { - // Find first non-empty line in the fold. - for (lnum = foldstart; lnum < foldend; lnum++) { - if (!linewhite(lnum)) { - break; - } - } - - // Find interesting text in this line. - s = (char_u *)skipwhite((char *)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((char *)ml_get(lnum + 1)); - if (*s == '*') { - s = (char_u *)skipwhite((char *)s + 1); - } - } - } - unsigned long count = (unsigned long)foldend - (unsigned long)foldstart + 1; - txt = NGETTEXT("+-%s%3ld line: ", "+-%s%3ld lines: ", count); - r = xmalloc(STRLEN(txt) - + STRLEN(dashes) // for %s - + 20 // for %3ld - + STRLEN(s)); // concatenated - sprintf((char *)r, txt, dashes, count); - len = (int)STRLEN(r); - STRCAT(r, s); - // remove 'foldmarker' and 'commentstring' - foldtext_cleanup(r + len); - rettv->vval.v_string = (char *)r; - } -} - -/// "foldtextresult(lnum)" function -static void f_foldtextresult(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - char_u *text; - char_u buf[FOLD_TEXT_LEN]; - static bool entered = false; - - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - if (entered) { - return; // reject recursive use - } - entered = true; - linenr_T lnum = tv_get_lnum(argvars); - // Treat illegal types and illegal string values for {lnum} the same. - if (lnum < 0) { - lnum = 0; - } - - foldinfo_T info = fold_info(curwin, lnum); - if (info.fi_lines > 0) { - text = get_foldtext(curwin, lnum, lnum + (linenr_T)info.fi_lines - 1, info, buf); - if (text == buf) { - text = vim_strsave(text); - } - rettv->vval.v_string = (char *)text; - } - - entered = false; -} - /// "foreground()" function -static void f_foreground(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_foreground(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) {} -static void f_funcref(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_funcref(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - common_function(argvars, rettv, true, fptr); + common_function(argvars, rettv, true); } -static void f_function(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_function(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - common_function(argvars, rettv, false, fptr); + common_function(argvars, rettv, false); } /// "garbagecollect()" function -static void f_garbagecollect(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_garbagecollect(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { // This is postponed until we are back at the toplevel, because we may be // using Lists and Dicts internally. E.g.: ":echo [garbagecollect()]". @@ -2591,12 +2390,8 @@ static void f_garbagecollect(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "get()" function -static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_get(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - listitem_T *li; - list_T *l; - dictitem_T *di; - dict_T *d; typval_T *tv = NULL; bool what_is_dict = false; @@ -2617,17 +2412,19 @@ static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } } else if (argvars[0].v_type == VAR_LIST) { - if ((l = argvars[0].vval.v_list) != NULL) { + list_T *l = argvars[0].vval.v_list; + if (l != NULL) { bool error = false; - li = tv_list_find(l, (int)tv_get_number_chk(&argvars[1], &error)); + listitem_T *li = tv_list_find(l, (int)tv_get_number_chk(&argvars[1], &error)); if (!error && li != NULL) { tv = TV_LIST_ITEM_TV(li); } } } else if (argvars[0].v_type == VAR_DICT) { - if ((d = argvars[0].vval.v_dict) != NULL) { - di = tv_dict_find(d, tv_get_string(&argvars[1]), -1); + dict_T *d = argvars[0].vval.v_dict; + if (d != NULL) { + dictitem_T *di = tv_dict_find(d, tv_get_string(&argvars[1]), -1); if (di != NULL) { tv = &di->di_tv; } @@ -2639,7 +2436,7 @@ static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (argvars[0].v_type == VAR_PARTIAL) { pt = argvars[0].vval.v_partial; } else { - memset(&fref_pt, 0, sizeof(fref_pt)); + CLEAR_FIELD(fref_pt); fref_pt.pt_name = (char_u *)argvars[0].vval.v_string; pt = &fref_pt; } @@ -2690,7 +2487,7 @@ static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "getbufinfo()" function -static void f_getbufinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getbufinfo(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { buf_T *argbuf = NULL; bool filtered = false; @@ -2752,7 +2549,7 @@ static void f_getbufinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// Get line or list of lines from buffer "buf" into "rettv". /// -/// @param retlist if TRUE, then the lines are returned as a Vim List. +/// @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. @@ -2789,7 +2586,7 @@ static void get_buffer_lines(buf_T *buf, linenr_T start, linenr_T end, int retli } /// "getbufline()" function -static void f_getbufline(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getbufline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { buf_T *const buf = tv_get_buf_from_arg(&argvars[0]); @@ -2802,7 +2599,7 @@ static void f_getbufline(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "getchangelist()" function -static void f_getchangelist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getchangelist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { tv_list_alloc_ret(rettv, 2); @@ -2851,147 +2648,6 @@ static void f_getchangelist(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/// "getchar()" and "getcharstr()" functions -static void getchar_common(typval_T *argvars, typval_T *rettv) - FUNC_ATTR_NONNULL_ALL -{ - varnumber_T n; - bool error = false; - - no_mapping++; - allow_keys++; - for (;;) { - // Position the cursor. Needed after a message that ends in a space, - // or if event processing caused a redraw. - ui_cursor_goto(msg_row, msg_col); - - if (argvars[0].v_type == VAR_UNKNOWN) { - // getchar(): blocking wait. - // TODO(bfredl): deduplicate shared logic with state_enter ? - if (!char_avail()) { - (void)os_inchar(NULL, 0, -1, 0, main_loop.events); - if (!multiqueue_empty(main_loop.events)) { - state_handle_k_event(); - continue; - } - } - n = safe_vgetc(); - } else if (tv_get_number_chk(&argvars[0], &error) == 1) { - // getchar(1): only check if char avail - n = vpeekc_any(); - } else if (error || vpeekc_any() == NUL) { - // illegal argument or getchar(0) and no char avail: return zero - n = 0; - } else { - // getchar(0) and char avail() != NUL: get a character. - // Note that vpeekc_any() returns K_SPECIAL for K_IGNORE. - n = safe_vgetc(); - } - - if (n == K_IGNORE - || n == K_MOUSEMOVE - || n == K_VER_SCROLLBAR - || n == K_HOR_SCROLLBAR) { - continue; - } - break; - } - no_mapping--; - allow_keys--; - - if (!ui_has_messages()) { - // redraw the screen after getchar() - update_screen(CLEAR); - } - - set_vim_var_nr(VV_MOUSE_WIN, 0); - set_vim_var_nr(VV_MOUSE_WINID, 0); - set_vim_var_nr(VV_MOUSE_LNUM, 0); - set_vim_var_nr(VV_MOUSE_COL, 0); - - rettv->vval.v_number = n; - if (n != 0 && (IS_SPECIAL(n) || mod_mask != 0)) { - char_u temp[10]; // modifier: 3, mbyte-char: 6, NUL: 1 - int i = 0; - - // Turn a special key into three bytes, plus modifier. - if (mod_mask != 0) { - temp[i++] = K_SPECIAL; - temp[i++] = KS_MODIFIER; - temp[i++] = (char_u)mod_mask; - } - if (IS_SPECIAL(n)) { - temp[i++] = K_SPECIAL; - temp[i++] = (char_u)K_SECOND(n); - temp[i++] = K_THIRD(n); - } else { - i += utf_char2bytes((int)n, (char *)temp + i); - } - assert(i < 10); - temp[i++] = NUL; - rettv->v_type = VAR_STRING; - rettv->vval.v_string = (char *)vim_strsave(temp); - - if (is_mouse_key((int)n)) { - int row = mouse_row; - int col = mouse_col; - int grid = mouse_grid; - linenr_T lnum; - win_T *wp; - int winnr = 1; - - if (row >= 0 && col >= 0) { - // Find the window at the mouse coordinates and compute the - // text position. - win_T *const win = mouse_find_win(&grid, &row, &col); - if (win == NULL) { - return; - } - (void)mouse_comp_pos(win, &row, &col, &lnum); - for (wp = firstwin; wp != win; wp = wp->w_next) { - ++winnr; - } - set_vim_var_nr(VV_MOUSE_WIN, winnr); - set_vim_var_nr(VV_MOUSE_WINID, wp->handle); - set_vim_var_nr(VV_MOUSE_LNUM, lnum); - set_vim_var_nr(VV_MOUSE_COL, col + 1); - } - } - } -} - -/// "getchar()" function -static void f_getchar(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - getchar_common(argvars, rettv); -} - -/// "getcharstr()" function -static void f_getcharstr(typval_T *argvars, typval_T *rettv, FunPtr 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 (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 -static void f_getcharmod(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = mod_mask; -} - static void getpos_both(typval_T *argvars, typval_T *rettv, bool getcurpos, bool charcol) { pos_T *fp = NULL; @@ -3048,13 +2704,13 @@ static void getpos_both(typval_T *argvars, typval_T *rettv, bool getcurpos, bool } /// "getcharpos()" function -static void f_getcharpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getcharpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { getpos_both(argvars, rettv, false, true); } /// "getcharsearch()" function -static void f_getcharsearch(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getcharsearch(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { tv_dict_alloc_ret(rettv); @@ -3065,42 +2721,8 @@ static void f_getcharsearch(typval_T *argvars, typval_T *rettv, FunPtr fptr) tv_dict_add_nr(dict, S_LEN("until"), last_csearch_until()); } -/// "getcmdcompltype()" function -static void f_getcmdcompltype(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_STRING; - rettv->vval.v_string = (char *)get_cmdline_completion(); -} - -/// "getcmdline()" function -static void f_getcmdline(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_STRING; - rettv->vval.v_string = (char *)get_cmdline_str(); -} - -/// "getcmdpos()" function -static void f_getcmdpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = get_cmdline_pos() + 1; -} - -/// "getcmdscreenpos()" function -static void f_getcmdscreenpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = get_cmdline_screen_pos() + 1; -} - -/// "getcmdtype()" function -static void f_getcmdtype(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_STRING; - rettv->vval.v_string = xmallocz(1); - rettv->vval.v_string[0] = (char)get_cmdline_type(); -} - /// "getcmdwintype()" function -static void f_getcmdwintype(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getcmdwintype(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_STRING; rettv->vval.v_string = NULL; @@ -3108,84 +2730,6 @@ static void f_getcmdwintype(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_string[0] = (char)cmdwin_type; } -/// "getcompletion()" function -static void f_getcompletion(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - char_u *pat; - expand_T xpc; - bool filtered = false; - int options = WILD_SILENT | WILD_USE_NL | WILD_ADD_SLASH - | WILD_NO_BEEP | WILD_HOME_REPLACE; - - if (argvars[1].v_type != VAR_STRING) { - semsg(_(e_invarg2), "type must be a string"); - return; - } - const char *const type = tv_get_string(&argvars[1]); - - if (argvars[2].v_type != VAR_UNKNOWN) { - filtered = (bool)tv_get_number_chk(&argvars[2], NULL); - } - - if (p_wic) { - options |= WILD_ICASE; - } - - // For filtered results, 'wildignore' is used - if (!filtered) { - options |= WILD_KEEP_ALL; - } - - if (argvars[0].v_type != VAR_STRING) { - emsg(_(e_invarg)); - return; - } - const char *pattern = tv_get_string(&argvars[0]); - - if (strcmp(type, "cmdline") == 0) { - set_one_cmd_context(&xpc, pattern); - xpc.xp_pattern_len = STRLEN(xpc.xp_pattern); - xpc.xp_col = (int)STRLEN(pattern); - goto theend; - } - - ExpandInit(&xpc); - xpc.xp_pattern = (char *)pattern; - xpc.xp_pattern_len = STRLEN(xpc.xp_pattern); - xpc.xp_context = cmdcomplete_str_to_type(type); - if (xpc.xp_context == EXPAND_NOTHING) { - semsg(_(e_invarg2), type); - return; - } - - if (xpc.xp_context == EXPAND_MENUS) { - set_context_in_menu_cmd(&xpc, "menu", xpc.xp_pattern, false); - 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, (char_u *)xpc.xp_pattern); - xpc.xp_pattern_len = STRLEN(xpc.xp_pattern); - } - -theend: - pat = addstar((char_u *)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++) { - tv_list_append_string(rettv->vval.v_list, (const char *)xpc.xp_files[i], - -1); - } - xfree(pat); - ExpandCleanup(&xpc); -} - /// `getcwd([{win}[, {tab}]])` function /// /// Every scope not specified implies the currently selected scope object. @@ -3195,7 +2739,7 @@ theend: /// @pre An argument may not be -1 if preceding arguments are not all -1. /// /// @post The return value will be a string. -static void f_getcwd(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getcwd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { // Possible scope of working directory to return. CdScope scope = kCdScopeInvalid; @@ -3203,15 +2747,15 @@ static void f_getcwd(typval_T *argvars, typval_T *rettv, FunPtr fptr) // Numbers of the scope objects (window, tab) we want the working directory // of. A `-1` means to skip this scope, a `0` means the current object. int scope_number[] = { - [kCdScopeWindow] = 0, // Number of window to look at. + [kCdScopeWindow] = 0, // Number of window to look at. [kCdScopeTabpage] = 0, // Number of tab to look at. }; char *cwd = NULL; // Current working directory to print char *from = NULL; // The original string to copy - tabpage_T *tp = curtab; // The tabpage to look at. - win_T *win = curwin; // The window to look at. + tabpage_T *tp = curtab; // The tabpage to look at. + win_T *win = curwin; // The window to look at. rettv->v_type = VAR_STRING; rettv->vval.v_string = NULL; @@ -3308,14 +2852,14 @@ static void f_getcwd(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "getfontname()" function -static void f_getfontname(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getfontname(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_STRING; rettv->vval.v_string = NULL; } /// "getfperm({fname})" function -static void f_getfperm(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getfperm(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { char *perm = NULL; char_u flags[] = "rwx"; @@ -3335,7 +2879,7 @@ static void f_getfperm(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "getfsize({fname})" function -static void f_getfsize(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getfsize(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const char *fname = tv_get_string(&argvars[0]); @@ -3344,7 +2888,7 @@ static void f_getfsize(typval_T *argvars, typval_T *rettv, FunPtr fptr) FileInfo file_info; if (os_fileinfo(fname, &file_info)) { uint64_t filesize = os_fileinfo_size(&file_info); - if (os_isdir((const char_u *)fname)) { + if (os_isdir(fname)) { rettv->vval.v_number = 0; } else { rettv->vval.v_number = (varnumber_T)filesize; @@ -3360,7 +2904,7 @@ static void f_getfsize(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "getftime({fname})" function -static void f_getftime(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getftime(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const char *fname = tv_get_string(&argvars[0]); @@ -3373,7 +2917,7 @@ static void f_getftime(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "getftype({fname})" function -static void f_getftype(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getftype(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { char_u *type = NULL; char *t; @@ -3407,7 +2951,7 @@ static void f_getftype(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "getjumplist()" function -static void f_getjumplist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getjumplist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { tv_list_alloc_ret(rettv, kListLenMayKnow); win_T *const wp = find_tabwin(&argvars[0], &argvars[1]); @@ -3438,7 +2982,7 @@ static void f_getjumplist(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "getline(lnum, [end])" function -static void f_getline(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { linenr_T end; bool retlist; @@ -3455,15 +2999,8 @@ static void f_getline(typval_T *argvars, typval_T *rettv, FunPtr fptr) get_buffer_lines(curbuf, lnum, end, retlist, rettv); } -/// "getloclist()" function -static void f_getloclist(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - win_T *wp = find_win_by_nr_or_id(&argvars[0]); - get_qf_loc_list(false, wp, &argvars[1], rettv); -} - /// "getmarklist()" function -static void f_getmarklist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getmarklist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { tv_list_alloc_ret(rettv, kListLenMayKnow); @@ -3481,10 +3018,8 @@ static void f_getmarklist(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "getmousepos()" function -static void f_getmousepos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getmousepos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - dict_T *d; - win_T *wp; int row = mouse_row; int col = mouse_col; int grid = mouse_grid; @@ -3495,12 +3030,12 @@ static void f_getmousepos(typval_T *argvars, typval_T *rettv, FunPtr fptr) varnumber_T column = 0; tv_dict_alloc_ret(rettv); - d = rettv->vval.v_dict; + dict_T *d = rettv->vval.v_dict; tv_dict_add_nr(d, S_LEN("screenrow"), (varnumber_T)mouse_row + 1); tv_dict_add_nr(d, S_LEN("screencol"), (varnumber_T)mouse_col + 1); - wp = mouse_find_win(&grid, &row, &col); + win_T *wp = mouse_find_win(&grid, &row, &col); if (wp != NULL) { int height = wp->w_height + wp->w_hsep_height + wp->w_status_height; // The height is adjusted by 1 when there is a bottom border. This is not @@ -3524,34 +3059,28 @@ static void f_getmousepos(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "getpid()" function -static void f_getpid(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getpid(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = os_get_pid(); } /// "getcurpos(string)" function -static void f_getcurpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getcurpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { getpos_both(argvars, rettv, true, false); } -static void f_getcursorcharpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getcursorcharpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { getpos_both(argvars, rettv, true, true); } /// "getpos(string)" function -static void f_getpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { getpos_both(argvars, rettv, false, false); } -/// "getqflist()" functions -static void f_getqflist(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - get_qf_loc_list(true, NULL, &argvars[0], rettv); -} - /// Common between getreg(), getreginfo() and getregtype(): get the register /// name from the first argument. /// Returns zero on error. @@ -3573,7 +3102,7 @@ static int getreg_get_regname(typval_T *argvars) } /// "getreg()" function -static void f_getreg(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getreg(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int arg2 = false; bool return_list = false; @@ -3609,7 +3138,7 @@ static void f_getreg(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "getregtype()" function -static void f_getregtype(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getregtype(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { // on error return an empty string rettv->v_type = VAR_STRING; @@ -3629,7 +3158,7 @@ static void f_getregtype(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "gettabinfo()" function -static void f_gettabinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_gettabinfo(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { tabpage_T *tparg = NULL; @@ -3661,7 +3190,7 @@ static void f_gettabinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "gettagstack()" function -static void f_gettagstack(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_gettagstack(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { win_T *wp = curwin; // default is current window @@ -3678,7 +3207,7 @@ static void f_gettagstack(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "getwininfo()" function -static void f_getwininfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getwininfo(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { win_T *wparg = NULL; @@ -3723,7 +3252,7 @@ static void dummy_timer_close_cb(TimeWatcher *tw, void *data) } /// "wait(timeout, condition[, interval])" function -static void f_wait(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_wait(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_NUMBER; rettv->vval.v_number = -1; @@ -3777,7 +3306,7 @@ static void f_wait(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "win_screenpos()" function -static void f_win_screenpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +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]); @@ -3788,7 +3317,6 @@ static void f_win_screenpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// Move the window wp into a new split of targetwin in a given direction static void win_move_into_split(win_T *wp, win_T *targetwin, int size, int flags) { - int dir; int height = wp->w_height; win_T *oldwin = curwin; @@ -3802,6 +3330,7 @@ static void win_move_into_split(win_T *wp, win_T *targetwin, int size, int flags } // 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 @@ -3824,14 +3353,10 @@ static void win_move_into_split(win_T *wp, win_T *targetwin, int size, int flags } /// "win_splitmove()" function -static void f_win_splitmove(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_win_splitmove(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - win_T *wp; - win_T *targetwin; - int flags = 0, size = 0; - - wp = find_win_by_nr_or_id(&argvars[0]); - targetwin = find_win_by_nr_or_id(&argvars[1]); + 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) @@ -3841,6 +3366,8 @@ static void f_win_splitmove(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } + int flags = 0, size = 0; + if (argvars[2].v_type != VAR_UNKNOWN) { dict_T *d; dictitem_T *di; @@ -3864,7 +3391,7 @@ static void f_win_splitmove(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "getwinpos({timeout})" function -static void f_getwinpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +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); @@ -3872,26 +3399,26 @@ static void f_getwinpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "getwinposx()" function -static void f_getwinposx(typval_T *argvars, typval_T *rettv, FunPtr fptr) +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, FunPtr fptr) +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, FunPtr fptr) +static void f_glob(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int options = WILD_SILENT|WILD_USE_NL; expand_T xpc; bool error = false; - /* When the optional second argument is non-zero, don't remove matches - * for 'wildignore' and don't put matches for 'suffixes' at the end. */ + // When the optional second argument is non-zero, don't remove matches + // for 'wildignore' and don't put matches for 'suffixes' at the end. rettv->v_type = VAR_STRING; if (argvars[1].v_type != VAR_UNKNOWN) { if (tv_get_number_chk(&argvars[1], &error)) { @@ -3933,7 +3460,7 @@ static void f_glob(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "globpath()" function -static void f_globpath(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_globpath(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int flags = WILD_IGNORE_COMPLETESLASH; // Flags for globpath. bool error = false; @@ -3964,7 +3491,7 @@ static void f_globpath(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (file != NULL && !error) { garray_T ga; ga_init(&ga, (int)sizeof(char_u *), 10); - globpath((char_u *)tv_get_string(&argvars[0]), (char_u *)file, &ga, flags); + globpath((char *)tv_get_string(&argvars[0]), (char *)file, &ga, flags); if (rettv->v_type == VAR_STRING) { rettv->vval.v_string = ga_concat_strings_sep(&ga, "\n"); @@ -3983,7 +3510,7 @@ static void f_globpath(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "glob2regpat()" function -static void f_glob2regpat(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_glob2regpat(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const char *const pat = tv_get_string_chk(&argvars[0]); // NULL on type error @@ -3992,7 +3519,7 @@ static void f_glob2regpat(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "has()" function -static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_has(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { static const char *const has_list[] = { #if defined(BSD) && !defined(__APPLE__) @@ -4214,7 +3741,7 @@ static bool has_wsl(void) /// @pre An argument may not be -1 if preceding arguments are not all -1. /// /// @post The return value will be either the number `1` or `0`. -static void f_haslocaldir(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_haslocaldir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { // Possible scope of working directory to return. CdScope scope = kCdScopeInvalid; @@ -4303,101 +3830,20 @@ static void f_haslocaldir(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/// "histadd()" function -static void f_histadd(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - HistoryType histype; - - rettv->vval.v_number = false; - if (check_secure()) { - return; - } - 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_u *)str, false, NUL); - rettv->vval.v_number = true; - return; - } - } -} - -/// "histdel()" function -static void f_histdel(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - int n; - const char *const str = tv_get_string_chk(&argvars[0]); // NULL on type error - if (str == NULL) { - n = 0; - } else if (argvars[1].v_type == VAR_UNKNOWN) { - // only one argument: clear entire history - n = clr_history(get_histtype(str, strlen(str), false)); - } else if (argvars[1].v_type == VAR_NUMBER) { - // index given: remove that entry - n = del_history_idx(get_histtype(str, strlen(str), false), - (int)tv_get_number(&argvars[1])); - } else { - // 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)); - } - rettv->vval.v_number = n; -} - -/// "histget()" function -static void f_histget(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - HistoryType type; - int idx; - - const char *const str = tv_get_string_chk(&argvars[0]); // NULL on type error - if (str == NULL) { - rettv->vval.v_string = NULL; - } else { - type = get_histtype(str, strlen(str), false); - if (argvars[1].v_type == VAR_UNKNOWN) { - idx = get_history_idx(type); - } else { - idx = (int)tv_get_number_chk(&argvars[1], NULL); - } - // -1 on type error - rettv->vval.v_string = (char *)vim_strsave(get_history_entry(type, idx)); - } - rettv->v_type = VAR_STRING; -} - -/// "histnr()" function -static void f_histnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *const history = tv_get_string_chk(&argvars[0]); - HistoryType i = history == NULL - ? HIST_INVALID - : get_histtype(history, strlen(history), false); - if (i != HIST_INVALID) { - i = get_history_idx(i); - } - rettv->vval.v_number = i; -} - /// "highlightID(name)" function -static void f_hlID(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_hlID(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = syn_name2id(tv_get_string(&argvars[0])); } /// "highlight_exists()" function -static void f_hlexists(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_hlexists(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = highlight_exists(tv_get_string(&argvars[0])); } /// "hostname()" function -static void f_hostname(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_hostname(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { char hostname[256]; @@ -4407,7 +3853,7 @@ static void f_hostname(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// iconv() function -static void f_iconv(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_iconv(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { vimconv_T vimconv; @@ -4416,17 +3862,19 @@ static void f_iconv(typval_T *argvars, typval_T *rettv, FunPtr fptr) const char *const str = tv_get_string(&argvars[0]); char buf1[NUMBUFLEN]; - char_u *const from = enc_canonize(enc_skip((char_u *)tv_get_string_buf(&argvars[1], buf1))); + char_u *const from = + (char_u *)enc_canonize(enc_skip((char *)tv_get_string_buf(&argvars[1], buf1))); char buf2[NUMBUFLEN]; - char_u *const to = enc_canonize(enc_skip((char_u *)tv_get_string_buf(&argvars[2], buf2))); + char_u *const to = + (char_u *)enc_canonize(enc_skip((char *)tv_get_string_buf(&argvars[2], buf2))); vimconv.vc_type = CONV_NONE; - convert_setup(&vimconv, from, to); + convert_setup(&vimconv, (char *)from, (char *)to); // If the encodings are equal, no conversion needed. if (vimconv.vc_type == CONV_NONE) { rettv->vval.v_string = xstrdup(str); } else { - rettv->vval.v_string = (char *)string_convert(&vimconv, (char_u *)str, NULL); + rettv->vval.v_string = string_convert(&vimconv, (char *)str, NULL); } convert_setup(&vimconv, NULL, NULL); @@ -4435,7 +3883,7 @@ static void f_iconv(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "indent()" function -static void f_indent(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_indent(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const linenr_T lnum = tv_get_lnum(argvars); if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) { @@ -4446,7 +3894,7 @@ static void f_indent(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "index()" function -static void f_index(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_index(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { long idx = 0; bool ic = false; @@ -4521,23 +3969,20 @@ static bool inputsecret_flag = false; /// "input()" function /// Also handles inputsecret() when inputsecret is set. -static void f_input(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_input(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - get_user_input(argvars, rettv, FALSE, inputsecret_flag); + get_user_input(argvars, rettv, false, inputsecret_flag); } /// "inputdialog()" function -static void f_inputdialog(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_inputdialog(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - get_user_input(argvars, rettv, TRUE, inputsecret_flag); + get_user_input(argvars, rettv, true, inputsecret_flag); } /// "inputlist()" function -static void f_inputlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_inputlist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - int selected; - int mouse_used; - if (argvars[0].v_type != VAR_LIST) { semsg(_(e_listarg), "inputlist()"); return; @@ -4555,7 +4000,8 @@ static void f_inputlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) }); // Ask for choice. - selected = prompt_for_number(&mouse_used); + int mouse_used; + int selected = prompt_for_number(&mouse_used); if (mouse_used) { selected -= lines_left; } @@ -4566,7 +4012,7 @@ static void f_inputlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) static garray_T ga_userinput = { 0, 0, sizeof(tasave_T), 4, NULL }; /// "inputrestore()" function -static void f_inputrestore(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_inputrestore(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (!GA_EMPTY(&ga_userinput)) { ga_userinput.ga_len--; @@ -4580,7 +4026,7 @@ static void f_inputrestore(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "inputsave()" function -static void f_inputsave(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_inputsave(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { // Add an entry to the stack of typeahead storage. tasave_T *p = GA_APPEND_VIA_PTR(tasave_T, &ga_userinput); @@ -4588,17 +4034,17 @@ static void f_inputsave(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "inputsecret()" function -static void f_inputsecret(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_inputsecret(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { cmdline_star++; inputsecret_flag = true; - f_input(argvars, rettv, NULL); + f_input(argvars, rettv, fptr); cmdline_star--; inputsecret_flag = false; } /// "insert()" function -static void f_insert(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_insert(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { list_T *l; bool error = false; @@ -4671,28 +4117,27 @@ static void f_insert(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "interrupt()" function static void f_interrupt(typval_T *argvars FUNC_ATTR_UNUSED, typval_T *rettv FUNC_ATTR_UNUSED, - FunPtr fptr FUNC_ATTR_UNUSED) + EvalFuncData fptr FUNC_ATTR_UNUSED) { got_int = true; } /// "invert(expr)" function -static void f_invert(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_invert(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = ~tv_get_number_chk(&argvars[0], NULL); } /// "isdirectory()" function -static void f_isdirectory(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_isdirectory(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - rettv->vval.v_number = os_isdir((const char_u *)tv_get_string(&argvars[0])); + rettv->vval.v_number = os_isdir(tv_get_string(&argvars[0])); } /// "islocked()" function -static void f_islocked(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_islocked(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { lval_T lv; - dictitem_T *di; rettv->vval.v_number = -1; const char_u *const end = (char_u *)get_lval((char *)tv_get_string(&argvars[0]), @@ -4705,7 +4150,7 @@ static void f_islocked(typval_T *argvars, typval_T *rettv, FunPtr fptr) semsg(_(e_trailing_arg), end); } else { if (lv.ll_tv == NULL) { - di = find_var(lv.ll_name, lv.ll_name_len, NULL, true); + dictitem_T *di = find_var(lv.ll_name, lv.ll_name_len, NULL, true); if (di != NULL) { // Consider a variable locked when: // 1. the variable itself is locked @@ -4732,7 +4177,7 @@ static void f_islocked(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "isinf()" function -static void f_isinf(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_isinf(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (argvars[0].v_type == VAR_FLOAT && xisinf(argvars[0].vval.v_float)) { @@ -4741,14 +4186,14 @@ static void f_isinf(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "isnan()" function -static void f_isnan(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_isnan(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = argvars[0].v_type == VAR_FLOAT && xisnan(argvars[0].vval.v_float); } /// "id()" function -static void f_id(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_id(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) FUNC_ATTR_NONNULL_ALL { const int len = vim_vsnprintf_typval(NULL, 0, "%p", dummy_ap, argvars); @@ -4758,7 +4203,7 @@ static void f_id(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "jobpid(id)" function -static void f_jobpid(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_jobpid(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_NUMBER; rettv->vval.v_number = 0; @@ -4782,7 +4227,7 @@ static void f_jobpid(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "jobresize(job, width, height)" function -static void f_jobresize(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_jobresize(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_NUMBER; rettv->vval.v_number = 0; @@ -4849,7 +4294,7 @@ static dict_T *create_environment(const dictitem_T *job_env, const bool clear_en if (!clear_env) { typval_T temp_env = TV_INITIAL_VALUE; - f_environ(NULL, &temp_env, NULL); + f_environ(NULL, &temp_env, (EvalFuncData){ .nullptr = NULL }); tv_dict_extend(env, temp_env.vval.v_dict, "force"); tv_dict_free(temp_env.vval.v_dict); @@ -4938,7 +4383,7 @@ static dict_T *create_environment(const dictitem_T *job_env, const bool clear_en } /// "jobstart()" function -static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_jobstart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_NUMBER; rettv->vval.v_number = 0; @@ -5013,7 +4458,7 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (new_cwd && *new_cwd != NUL) { cwd = new_cwd; // The new cwd must be a directory. - if (!os_isdir_executable((const char *)cwd)) { + if (!os_isdir(cwd)) { semsg(_(e_invarg2), "expected valid directory"); shell_free_argv(argv); return; @@ -5058,7 +4503,7 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "jobstop()" function -static void f_jobstop(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_jobstop(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_NUMBER; rettv->vval.v_number = 0; @@ -5091,7 +4536,7 @@ static void f_jobstop(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "jobwait(ids[, timeout])" function -static void f_jobwait(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_jobwait(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_NUMBER; rettv->vval.v_number = 0; @@ -5190,7 +4635,7 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// json_decode() function -static void f_json_decode(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_json_decode(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { char numbuf[NUMBUFLEN]; const char *s = NULL; @@ -5224,14 +4669,14 @@ static void f_json_decode(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// json_encode() function -static void f_json_encode(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_json_encode(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_STRING; rettv->vval.v_string = encode_tv2json(&argvars[0], NULL); } /// "last_buffer_nr()" function. -static void f_last_buffer_nr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_last_buffer_nr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int n = 0; @@ -5245,7 +4690,7 @@ static void f_last_buffer_nr(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "len()" function -static void f_len(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_len(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { switch (argvars[0].v_type) { case VAR_STRING: @@ -5316,19 +4761,19 @@ static void libcall_common(typval_T *argvars, typval_T *rettv, int out_type) } /// "libcall()" function -static void f_libcall(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_libcall(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { libcall_common(argvars, rettv, VAR_STRING); } /// "libcallnr()" function -static void f_libcallnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_libcallnr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { libcall_common(argvars, rettv, VAR_NUMBER); } /// "line(string, [winid])" function -static void f_line(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_line(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { linenr_T lnum = 0; pos_T *fp = NULL; @@ -5359,7 +4804,7 @@ static void f_line(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "line2byte(lnum)" function -static void f_line2byte(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_line2byte(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const linenr_T lnum = tv_get_lnum(argvars); if (lnum < 1 || lnum > curbuf->b_ml.ml_line_count + 1) { @@ -5373,7 +4818,7 @@ static void f_line2byte(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "lispindent(lnum)" function -static void f_lispindent(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_lispindent(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const pos_T pos = curwin->w_cursor; const linenr_T lnum = tv_get_lnum(argvars); @@ -5387,13 +4832,13 @@ static void f_lispindent(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "localtime()" function -static void f_localtime(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_localtime(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = (varnumber_T)time(NULL); } /// luaeval() function implementation -static void f_luaeval(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_luaeval(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) FUNC_ATTR_NONNULL_ALL { const char *const str = tv_get_string_chk(&argvars[0]); @@ -5405,9 +4850,9 @@ static void f_luaeval(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "map()" function -static void f_map(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_map(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - filter_map(argvars, rettv, TRUE); + filter_map(argvars, rettv, true); } static void find_some_match(typval_T *const argvars, typval_T *const rettv, @@ -5417,19 +4862,17 @@ static void find_some_match(typval_T *const argvars, typval_T *const rettv, long len = 0; char_u *expr = NULL; regmatch_T regmatch; - char *save_cpo; long start = 0; long nth = 1; colnr_T startcol = 0; bool match = false; list_T *l = NULL; - listitem_T *li = NULL; long idx = 0; char_u *tofree = NULL; // Make 'cpoptions' empty, the 'l' flag should not be used here. - save_cpo = p_cpo; - p_cpo = ""; + char *save_cpo = p_cpo; + p_cpo = empty_option; rettv->vval.v_number = -1; switch (type) { @@ -5455,6 +4898,7 @@ static void find_some_match(typval_T *const argvars, typval_T *const rettv, break; } + listitem_T *li = NULL; if (argvars[0].v_type == VAR_LIST) { if ((l = argvars[0].vval.v_list) == NULL) { goto theend; @@ -5563,10 +5007,8 @@ static void find_some_match(typval_T *const argvars, typval_T *const rettv, 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)( - regmatch.startp[0] - expr); - TV_LIST_ITEM_TV(li4)->vval.v_number = (varnumber_T)( - regmatch.endp[0] - expr); + 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; } @@ -5626,31 +5068,31 @@ theend: } /// "match()" function -static void f_match(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_match(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { find_some_match(argvars, rettv, kSomeMatch); } /// "matchend()" function -static void f_matchend(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_matchend(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { find_some_match(argvars, rettv, kSomeMatchEnd); } /// "matchlist()" function -static void f_matchlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_matchlist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { find_some_match(argvars, rettv, kSomeMatchList); } /// "matchstr()" function -static void f_matchstr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_matchstr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { find_some_match(argvars, rettv, kSomeMatchStr); } /// "matchstrpos()" function -static void f_matchstrpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_matchstrpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { find_some_match(argvars, rettv, kSomeMatchStrPos); } @@ -5705,19 +5147,19 @@ static void max_min(const typval_T *const tv, typval_T *const rettv, const bool } /// "max()" function -static void f_max(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_max(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - max_min(argvars, rettv, TRUE); + max_min(argvars, rettv, true); } /// "min()" function -static void f_min(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_min(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - max_min(argvars, rettv, FALSE); + max_min(argvars, rettv, false); } /// "mkdir()" function -static void f_mkdir(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_mkdir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int prot = 0755; // -V536 @@ -5762,7 +5204,7 @@ static void f_mkdir(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "mode()" function -static void f_mode(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_mode(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { char buf[MODE_MAX_LENGTH]; @@ -5779,7 +5221,7 @@ static void f_mode(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "msgpackdump()" function -static void f_msgpackdump(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_msgpackdump(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) FUNC_ATTR_NONNULL_ALL { if (argvars[0].v_type != VAR_LIST) { @@ -5918,7 +5360,7 @@ static void msgpackparse_unpack_blob(const blob_T *const blob, list_T *const ret } /// "msgpackparse" function -static void f_msgpackparse(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_msgpackparse(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) FUNC_ATTR_NONNULL_ALL { if (argvars[0].v_type != VAR_LIST && argvars[0].v_type != VAR_BLOB) { @@ -5934,7 +5376,7 @@ static void f_msgpackparse(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "nextnonblank()" function -static void f_nextnonblank(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_nextnonblank(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { linenr_T lnum; @@ -5951,7 +5393,7 @@ static void f_nextnonblank(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "nr2char()" function -static void f_nr2char(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_nr2char(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (argvars[1].v_type != VAR_UNKNOWN) { if (!tv_check_num(&argvars[1])) { @@ -5982,14 +5424,14 @@ static void f_nr2char(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "or(expr, expr)" function -static void f_or(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_or(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = tv_get_number_chk(&argvars[0], NULL) | tv_get_number_chk(&argvars[1], NULL); } /// "pathshorten()" function -static void f_pathshorten(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_pathshorten(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int trim_len = 1; @@ -6011,7 +5453,7 @@ static void f_pathshorten(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "pow()" function -static void f_pow(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_pow(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { float_T fx; float_T fy; @@ -6025,7 +5467,7 @@ static void f_pow(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "prevnonblank()" function -static void f_prevnonblank(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_prevnonblank(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { linenr_T lnum = tv_get_lnum(argvars); if (lnum < 1 || lnum > curbuf->b_ml.ml_line_count) { @@ -6039,19 +5481,18 @@ static void f_prevnonblank(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "printf()" function -static void f_printf(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_printf(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_STRING; rettv->vval.v_string = NULL; { - int len; int saved_did_emsg = did_emsg; // Get the required length, allocate the buffer and do it for real. did_emsg = false; char buf[NUMBUFLEN]; const char *fmt = tv_get_string_buf(&argvars[0], buf); - len = vim_vsnprintf_typval(NULL, 0, fmt, dummy_ap, argvars + 1); + int len = vim_vsnprintf_typval(NULL, 0, fmt, dummy_ap, argvars + 1); if (!did_emsg) { char *s = xmalloc((size_t)len + 1); rettv->vval.v_string = s; @@ -6062,15 +5503,14 @@ static void f_printf(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "prompt_setcallback({buffer}, {callback})" function -static void f_prompt_setcallback(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_prompt_setcallback(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - buf_T *buf; Callback prompt_callback = { .type = kCallbackNone }; if (check_secure()) { return; } - buf = tv_get_buf(&argvars[0], false); + buf_T *buf = tv_get_buf(&argvars[0], false); if (buf == NULL) { return; } @@ -6086,15 +5526,14 @@ static void f_prompt_setcallback(typval_T *argvars, typval_T *rettv, FunPtr fptr } /// "prompt_setinterrupt({buffer}, {callback})" function -static void f_prompt_setinterrupt(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_prompt_setinterrupt(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - buf_T *buf; Callback interrupt_callback = { .type = kCallbackNone }; if (check_secure()) { return; } - buf = tv_get_buf(&argvars[0], false); + buf_T *buf = tv_get_buf(&argvars[0], false); if (buf == NULL) { return; } @@ -6110,7 +5549,7 @@ static void f_prompt_setinterrupt(typval_T *argvars, typval_T *rettv, FunPtr fpt } /// "prompt_getprompt({buffer})" function -static void f_prompt_getprompt(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_prompt_getprompt(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) FUNC_ATTR_NONNULL_ALL { // return an empty string by default, e.g. it's not a prompt buffer @@ -6130,7 +5569,7 @@ static void f_prompt_getprompt(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "prompt_setprompt({buffer}, {text})" function -static void f_prompt_setprompt(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_prompt_setprompt(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (check_secure()) { return; @@ -6146,14 +5585,14 @@ static void f_prompt_setprompt(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "pum_getpos()" function -static void f_pum_getpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_pum_getpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { tv_dict_alloc_ret(rettv); pum_set_event_info(rettv->vval.v_dict); } /// "pumvisible()" function -static void f_pumvisible(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_pumvisible(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (pum_visible()) { rettv->vval.v_number = 1; @@ -6161,7 +5600,7 @@ static void f_pumvisible(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "py3eval()" and "pyxeval()" functions (always python3) -static void f_py3eval(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_py3eval(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { script_host_eval("python3", argvars, rettv); } @@ -6233,7 +5672,7 @@ static inline uint32_t shuffle_xoshiro128starstar(uint32_t *const x, uint32_t *c } /// "rand()" function -static void f_rand(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_rand(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { uint32_t result; @@ -6244,7 +5683,7 @@ static void f_rand(typval_T *argvars, typval_T *rettv, FunPtr fptr) // When no argument is given use the global seed list. if (!initialized) { // Initialize the global seed list. - uint32_t x; + uint32_t x = 0; init_srand(&x); gx = splitmix32(&x); @@ -6303,7 +5742,7 @@ theend: } /// "srand()" function -static void f_srand(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_srand(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { uint32_t x = 0; @@ -6325,27 +5764,25 @@ static void f_srand(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "perleval()" function -static void f_perleval(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_perleval(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { script_host_eval("perl", argvars, rettv); } /// "rubyeval()" function -static void f_rubyeval(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_rubyeval(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { script_host_eval("ruby", argvars, rettv); } /// "range()" function -static void f_range(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_range(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - varnumber_T start; varnumber_T end; varnumber_T stride = 1; - varnumber_T i; bool error = false; - start = tv_get_number_chk(&argvars[0], &error); + varnumber_T start = tv_get_number_chk(&argvars[0], &error); if (argvars[1].v_type == VAR_UNKNOWN) { end = start - 1; start = 0; @@ -6365,7 +5802,7 @@ static void f_range(typval_T *argvars, typval_T *rettv, FunPtr fptr) emsg(_("E727: Start past end")); } else { tv_list_alloc_ret(rettv, (end - start) / stride); - for (i = start; stride > 0 ? i <= end : i >= end; i += stride) { + for (varnumber_T i = start; stride > 0 ? i <= end : i >= end; i += stride) { tv_list_append_number(rettv->vval.v_list, i); } } @@ -6376,8 +5813,6 @@ static varnumber_T readdir_checkitem(void *context, const char *name) FUNC_ATTR_NONNULL_ALL { typval_T *expr = (typval_T *)context; - typval_T save_val; - typval_T rettv; typval_T argv[2]; varnumber_T retval = 0; bool error = false; @@ -6386,11 +5821,13 @@ static varnumber_T readdir_checkitem(void *context, const char *name) return 1; } + typval_T save_val; prepare_vimvar(VV_VAL, &save_val); set_vim_var_string(VV_VAL, name, -1); argv[0].v_type = VAR_STRING; argv[0].vval.v_string = (char *)name; + typval_T rettv; if (eval_expr_typval(expr, argv, 1, &rettv) == FAIL) { goto theend; } @@ -6409,7 +5846,7 @@ theend: } /// "readdir()" function -static void f_readdir(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_readdir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { tv_list_alloc_ret(rettv, kListLenUnknown); @@ -6427,17 +5864,16 @@ static void f_readdir(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "readfile()" function -static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_readfile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { bool binary = false; bool blob = false; FILE *fd; - char_u buf[(IOSIZE/256) * 256]; // rounded to avoid odd + 1 + char_u buf[(IOSIZE/256) * 256]; // rounded to avoid odd + 1 int io_size = sizeof(buf); - int readlen; // size of last fread() - char_u *prev = NULL; // previously read bytes, if any - long prevlen = 0; // length of data in prev - long prevsize = 0; // size of prev buffer + char_u *prev = NULL; // previously read bytes, if any + long prevlen = 0; // length of data in prev + long prevsize = 0; // size of prev buffer long maxline = MAXLNUM; if (argvars[1].v_type != VAR_UNKNOWN) { @@ -6455,7 +5891,7 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) // their own about CR-LF conversion. const char *const fname = tv_get_string(&argvars[0]); - if (os_isdir((const char_u *)fname)) { + if (os_isdir(fname)) { semsg(_(e_isadir2), fname); return; } @@ -6479,7 +5915,7 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) list_T *const l = tv_list_alloc_ret(rettv, kListLenUnknown); while (maxline < 0 || tv_list_len(l) < maxline) { - readlen = (int)fread(buf, 1, (size_t)io_size, fd); + int readlen = (int)fread(buf, 1, (size_t)io_size, fd); // This for loop processes what was read, but is also entered at end // of file so that either: @@ -6511,9 +5947,9 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) assert(len < INT_MAX); s = vim_strnsave(start, len); } else { - /* Change "prev" buffer to be the right size. This way - * the bytes are only copied once, and very long lines are - * allocated only once. */ + // Change "prev" buffer to be the right size. This way + // the bytes are only copied once, and very long lines are + // allocated only once. s = xrealloc(prev, (size_t)prevlen + len + 1); memcpy(s + prevlen, start, len); s[(size_t)prevlen + len] = NUL; @@ -6587,10 +6023,10 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (start < p) { // There's part of a line in buf, store it in "prev". if (p - start + prevlen >= prevsize) { - /* A common use case is ordinary text files and "prev" gets a - * fragment of a line, so the first allocation is made - * small, to avoid repeatedly 'allocing' large and - * 'reallocing' small. */ + // A common use case is ordinary text files and "prev" gets a + // fragment of a line, so the first allocation is made + // small, to avoid repeatedly 'allocing' large and + // 'reallocing' small. if (prevsize == 0) { prevsize = (long)(p - start); } else { @@ -6611,7 +6047,7 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "getreginfo()" function -static void f_getreginfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getreginfo(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int regname = getreg_get_regname(argvars); if (regname == 0) { @@ -6661,18 +6097,18 @@ static void f_getreginfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "reg_executing()" function -static void f_reg_executing(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_reg_executing(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { return_register(reg_executing, rettv); } /// "reg_recording()" function -static void f_reg_recording(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_reg_recording(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { return_register(reg_recording, rettv); } -static void f_reg_recorded(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_reg_recorded(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { return_register(reg_recorded, rettv); } @@ -6714,7 +6150,7 @@ static int list2proftime(typval_T *arg, proftime_T *tm) FUNC_ATTR_NONNULL_ALL /// one argument it returns the time passed since the argument. /// With two arguments it returns the time passed between /// the two arguments. -static void f_reltime(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_reltime(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { proftime_T res; proftime_T start; @@ -6756,7 +6192,7 @@ static void f_reltime(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "reltimestr()" function -static void f_reltimestr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_reltimestr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) FUNC_ATTR_NONNULL_ALL { proftime_T tm; @@ -6769,7 +6205,7 @@ static void f_reltimestr(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "remove()" function -static void f_remove(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_remove(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const char *const arg_errmsg = N_("remove() argument"); @@ -6785,7 +6221,7 @@ static void f_remove(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "rename({from}, {to})" function -static void f_rename(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_rename(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (check_secure()) { rettv->vval.v_number = -1; @@ -6797,7 +6233,7 @@ static void f_rename(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "repeat()" function -static void f_repeat(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_repeat(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { varnumber_T n = tv_get_number(&argvars[1]); if (argvars[0].v_type == VAR_LIST) { @@ -6834,7 +6270,7 @@ static void f_repeat(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "resolve()" function -static void f_resolve(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_resolve(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_STRING; const char *fname = tv_get_string(&argvars[0]); @@ -6845,7 +6281,7 @@ static void f_resolve(typval_T *argvars, typval_T *rettv, FunPtr fptr) v = os_realpath(fname, v); } } - rettv->vval.v_string = (char_u *)(v == NULL ? xstrdup(fname) : v); + rettv->vval.v_string = (v == NULL ? xstrdup(fname) : v); #else # ifdef HAVE_READLINK { @@ -6907,7 +6343,7 @@ static void f_resolve(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (*q != NUL) { cpy = remain; remain = (remain - ? (char *)concat_str((char_u *)q - 1, (char_u *)remain) + ? concat_str(q - 1, remain) : xstrdup(q - 1)); xfree(cpy); q[-1] = NUL; @@ -6966,7 +6402,7 @@ static void f_resolve(typval_T *argvars, typval_T *rettv, FunPtr fptr) && (p[2] == NUL || vim_ispathsep(p[2])))))) { // Prepend "./". - cpy = (char *)concat_str((const char_u *)"./", (const char_u *)p); + cpy = concat_str("./", p); xfree(p); p = cpy; } else if (!is_relative_to_current) { @@ -7003,7 +6439,7 @@ static void f_resolve(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "reverse({list})" function -static void f_reverse(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_reverse(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (argvars[0].v_type == VAR_BLOB) { blob_T *const b = argvars[0].vval.v_blob; @@ -7028,7 +6464,7 @@ static void f_reverse(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "reduce(list, { accumulator, element -> value } [, initial])" function -static void f_reduce(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_reduce(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (argvars[0].v_type != VAR_LIST && argvars[0].v_type != VAR_BLOB) { emsg(_(e_listblobreq)); @@ -7139,7 +6575,6 @@ static void f_reduce(typval_T *argvars, typval_T *rettv, FunPtr fptr) static int get_search_arg(typval_T *varp, int *flagsp) { int dir = FORWARD; - int mask; if (varp->v_type != VAR_UNKNOWN) { char nbuf[NUMBUFLEN]; @@ -7147,6 +6582,7 @@ static int get_search_arg(typval_T *varp, int *flagsp) if (flags == NULL) { return 0; // Type error; errmsg already given. } + int mask; while (*flags != NUL) { switch (*flags) { case 'b': @@ -7196,26 +6632,19 @@ static int get_search_arg(typval_T *varp, int *flagsp) /// Shared by search() and searchpos() functions. static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp) { - int flags; - pos_T pos; - pos_T save_cursor; bool save_p_ws = p_ws; - int dir; int retval = 0; // default: FAIL long lnum_stop = 0; - proftime_T tm; long time_limit = 0; int options = SEARCH_KEEP; - int subpatnum; - searchit_arg_T sia; bool use_skip = false; const char *const pat = tv_get_string(&argvars[0]); - dir = get_search_arg(&argvars[1], flagsp); // May set p_ws. + int dir = get_search_arg(&argvars[1], flagsp); // May set p_ws. if (dir == 0) { goto theend; } - flags = *flagsp; + int flags = *flagsp; if (flags & SP_START) { options |= SEARCH_START; } @@ -7242,25 +6671,27 @@ static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp) } // Set the time limit, if there is one. - tm = profile_setlimit(time_limit); - - /* - * This function does not accept SP_REPEAT and SP_RETCOUNT flags. - * Check to make sure only those flags are set. - * Also, Only the SP_NOMOVE or the SP_SETPCMARK flag can be set. Both - * flags cannot be set. Check for that condition also. - */ + proftime_T tm = profile_setlimit(time_limit); + + // This function does not accept SP_REPEAT and SP_RETCOUNT flags. + // Check to make sure only those flags are set. + // Also, Only the SP_NOMOVE or the SP_SETPCMARK flag can be set. Both + // flags cannot be set. Check for that condition also. if (((flags & (SP_REPEAT | SP_RETCOUNT)) != 0) || ((flags & SP_NOMOVE) && (flags & SP_SETPCMARK))) { semsg(_(e_invarg2), tv_get_string(&argvars[1])); goto theend; } - pos = save_cursor = curwin->w_cursor; + pos_T save_cursor; + pos_T pos = save_cursor = curwin->w_cursor; pos_T firstpos = { 0 }; - memset(&sia, 0, sizeof(sia)); - sia.sa_stop_lnum = (linenr_T)lnum_stop; - sia.sa_tm = &tm; + searchit_arg_T sia = { + .sa_stop_lnum = (linenr_T)lnum_stop, + .sa_tm = &tm, + }; + + int subpatnum; // Repeat until {skip} returns false. for (;;) { @@ -7333,7 +6764,7 @@ theend: } /// "rpcnotify()" function -static void f_rpcnotify(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_rpcnotify(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_NUMBER; rettv->vval.v_number = 0; @@ -7368,7 +6799,7 @@ static void f_rpcnotify(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "rpcrequest()" function -static void f_rpcrequest(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_rpcrequest(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_NUMBER; rettv->vval.v_number = 0; @@ -7395,8 +6826,7 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, FunPtr fptr) } sctx_T save_current_sctx; - char *save_sourcing_name, *save_autocmd_fname, *save_autocmd_match; - linenr_T save_sourcing_lnum; + char *save_autocmd_fname, *save_autocmd_match; int save_autocmd_bufnr; funccal_entry_T funccal_entry; @@ -7404,16 +6834,14 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, FunPtr fptr) // If this is called from a provider function, restore the scope // information of the caller. save_current_sctx = current_sctx; - save_sourcing_name = sourcing_name; - save_sourcing_lnum = sourcing_lnum; save_autocmd_fname = autocmd_fname; save_autocmd_match = autocmd_match; save_autocmd_bufnr = autocmd_bufnr; save_funccal(&funccal_entry); current_sctx = provider_caller_scope.script_ctx; - sourcing_name = provider_caller_scope.sourcing_name; - sourcing_lnum = provider_caller_scope.sourcing_lnum; + ga_grow(&exestack, 1); + ((estack_T *)exestack.ga_data)[exestack.ga_len++] = provider_caller_scope.es_entry; autocmd_fname = provider_caller_scope.autocmd_fname; autocmd_match = provider_caller_scope.autocmd_match; autocmd_bufnr = provider_caller_scope.autocmd_bufnr; @@ -7430,8 +6858,7 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (l_provider_call_nesting) { current_sctx = save_current_sctx; - sourcing_name = save_sourcing_name; - sourcing_lnum = save_sourcing_lnum; + exestack.ga_len--; autocmd_fname = save_autocmd_fname; autocmd_match = save_autocmd_match; autocmd_bufnr = save_autocmd_bufnr; @@ -7461,12 +6888,12 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, FunPtr fptr) } end: - arena_mem_free(res_mem, NULL); + arena_mem_free(res_mem); api_clear_error(&err); } /// "rpcstart()" function (DEPRECATED) -static void f_rpcstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_rpcstart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_NUMBER; rettv->vval.v_number = 0; @@ -7533,7 +6960,7 @@ static void f_rpcstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "rpcstop()" function -static void f_rpcstop(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_rpcstop(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_NUMBER; rettv->vval.v_number = 0; @@ -7551,7 +6978,7 @@ static void f_rpcstop(typval_T *argvars, typval_T *rettv, FunPtr fptr) // if called with a job, stop it, else closes the channel uint64_t id = (uint64_t)argvars[0].vval.v_number; if (find_job(id, false)) { - f_jobstop(argvars, rettv, NULL); + f_jobstop(argvars, rettv, fptr); } else { const char *error; rettv->vval.v_number = @@ -7563,16 +6990,15 @@ static void f_rpcstop(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "screenattr()" function -static void f_screenattr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_screenattr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - int c; - - ScreenGrid *grid; int row = (int)tv_get_number_chk(&argvars[0], NULL) - 1; int col = (int)tv_get_number_chk(&argvars[1], NULL) - 1; + ScreenGrid *grid; screenchar_adjust(&grid, &row, &col); + int c; if (row < 0 || row >= grid->rows || col < 0 || col >= grid->cols) { c = -1; } else { @@ -7582,16 +7008,15 @@ static void f_screenattr(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "screenchar()" function -static void f_screenchar(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_screenchar(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - int c; - - ScreenGrid *grid; int row = (int)tv_get_number_chk(&argvars[0], NULL) - 1; int col = (int)tv_get_number_chk(&argvars[1], NULL) - 1; + ScreenGrid *grid; screenchar_adjust(&grid, &row, &col); + int c; if (row < 0 || row >= grid->rows || col < 0 || col >= grid->cols) { c = -1; } else { @@ -7601,12 +7026,12 @@ static void f_screenchar(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "screenchars()" function -static void f_screenchars(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_screenchars(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - ScreenGrid *grid; int row = (int)tv_get_number_chk(&argvars[0], NULL) - 1; int col = (int)tv_get_number_chk(&argvars[1], NULL) - 1; + ScreenGrid *grid; screenchar_adjust(&grid, &row, &col); if (row < 0 || row >= grid->rows || col < 0 || col >= grid->cols) { @@ -7614,7 +7039,7 @@ static void f_screenchars(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } int pcc[MAX_MCO]; - int c = utfc_ptr2char(grid->chars[grid->line_offset[row] + (size_t)col], pcc); + int c = utfc_ptr2char((char *)grid->chars[grid->line_offset[row] + (size_t)col], pcc); int composing_len = 0; while (pcc[composing_len] != 0) { composing_len++; @@ -7629,18 +7054,14 @@ static void f_screenchars(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "screencol()" function /// /// First column is 1 to be consistent with virtcol(). -static void f_screencol(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_screencol(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = ui_current_col() + 1; } /// "screenpos({winid}, {lnum}, {col})" function -static void f_screenpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_screenpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - pos_T pos; - int row = 0; - int scol = 0, ccol = 0, ecol = 0; - tv_dict_alloc_ret(rettv); dict_T *dict = rettv->vval.v_dict; @@ -7649,9 +7070,13 @@ static void f_screenpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } - pos.lnum = (linenr_T)tv_get_number(&argvars[1]); - pos.col = (colnr_T)tv_get_number(&argvars[2]) - 1; - pos.coladd = 0; + pos_T pos = { + .lnum = (linenr_T)tv_get_number(&argvars[1]), + .col = (colnr_T)tv_get_number(&argvars[2]) - 1, + .coladd = 0 + }; + int row = 0; + int scol = 0, ccol = 0, ecol = 0; textpos2screenpos(wp, &pos, &row, &scol, &ccol, &ecol, false); tv_dict_add_nr(dict, S_LEN("row"), row); @@ -7661,13 +7086,13 @@ static void f_screenpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "screenrow()" function -static void f_screenrow(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_screenrow(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = ui_current_row() + 1; } /// "screenstring()" function -static void f_screenstring(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_screenstring(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_string = NULL; rettv->v_type = VAR_STRING; @@ -7686,7 +7111,7 @@ static void f_screenstring(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "search()" function -static void f_search(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_search(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int flags = 0; @@ -7694,7 +7119,7 @@ static void f_search(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "searchdecl()" function -static void f_searchdecl(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_searchdecl(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int locally = 1; int thisblock = 0; @@ -7719,7 +7144,6 @@ static void f_searchdecl(typval_T *argvars, typval_T *rettv, FunPtr fptr) static int searchpair_cmn(typval_T *argvars, pos_T *match_pos) { bool save_p_ws = p_ws; - int dir; int flags = 0; int retval = 0; // default: FAIL long lnum_stop = 0; @@ -7737,7 +7161,7 @@ static int searchpair_cmn(typval_T *argvars, pos_T *match_pos) } // Handle the optional fourth argument: flags. - dir = get_search_arg(&argvars[3], &flags); // may set p_ws. + int dir = get_search_arg(&argvars[3], &flags); // may set p_ws. if (dir == 0) { goto theend; } @@ -7790,13 +7214,13 @@ theend: } /// "searchpair()" function -static void f_searchpair(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_searchpair(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = searchpair_cmn(argvars, NULL); } /// "searchpairpos()" function -static void f_searchpairpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_searchpairpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { pos_T match_pos; int lnum = 0; @@ -7831,33 +7255,24 @@ long do_searchpair(const char *spat, const char *mpat, const char *epat, int dir long time_limit) FUNC_ATTR_NONNULL_ARG(1, 2, 3) { - char *save_cpo; - char_u *pat, *pat2 = NULL, *pat3 = NULL; long retval = 0; - pos_T pos; - pos_T firstpos; - pos_T foundpos; - pos_T save_cursor; - pos_T save_pos; - int n; int nest = 1; bool use_skip = false; int options = SEARCH_KEEP; - proftime_T tm; // Make 'cpoptions' empty, the 'l' flag should not be used here. - save_cpo = p_cpo; - p_cpo = (char *)empty_option; + char *save_cpo = p_cpo; + p_cpo = empty_option; // Set the time limit, if there is one. - tm = profile_setlimit(time_limit); + proftime_T tm = profile_setlimit(time_limit); // 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; - pat2 = xmalloc(pat2_len); + char_u *pat2 = xmalloc(pat2_len); const size_t pat3_len = strlen(spat) + strlen(mpat) + strlen(epat) + 25; - pat3 = xmalloc(pat3_len); + char_u *pat3 = xmalloc(pat3_len); snprintf((char *)pat2, pat2_len, "\\m\\(%s\\m\\)\\|\\(%s\\m\\)", spat, epat); if (*mpat == NUL) { STRCPY(pat3, pat2); @@ -7873,19 +7288,21 @@ long do_searchpair(const char *spat, const char *mpat, const char *epat, int dir use_skip = eval_expr_valid_arg(skip); } - save_cursor = curwin->w_cursor; - pos = curwin->w_cursor; + pos_T save_cursor = curwin->w_cursor; + pos_T pos = curwin->w_cursor; + pos_T firstpos; clearpos(&firstpos); + pos_T foundpos; clearpos(&foundpos); - pat = pat3; + char_u *pat = pat3; for (;;) { - searchit_arg_T sia; - memset(&sia, 0, sizeof(sia)); - sia.sa_stop_lnum = lnum_stop; - sia.sa_tm = &tm; + searchit_arg_T sia = { + .sa_stop_lnum = lnum_stop, + .sa_tm = &tm, + }; - n = searchit(curwin, curbuf, &pos, NULL, dir, pat, 1L, - options, RE_SEARCH, &sia); + int n = searchit(curwin, curbuf, &pos, NULL, dir, pat, 1L, + options, RE_SEARCH, &sia); if (n == FAIL || (firstpos.lnum != 0 && equalpos(pos, firstpos))) { // didn't find it or found the first match again: FAIL break; @@ -7911,7 +7328,7 @@ long do_searchpair(const char *spat, const char *mpat, const char *epat, int dir // If the skip pattern matches, ignore this match. if (use_skip) { - save_pos = curwin->w_cursor; + pos_T save_pos = curwin->w_cursor; curwin->w_cursor = pos; bool err = false; const bool r = eval_expr_to_bool(skip, &err); @@ -7971,18 +7388,23 @@ long do_searchpair(const char *spat, const char *mpat, const char *epat, int dir xfree(pat2); xfree(pat3); - if ((char_u *)p_cpo == empty_option) { + if (p_cpo == empty_option) { p_cpo = save_cpo; } else { // Darn, evaluating the {skip} expression changed the value. - free_string_option((char_u *)save_cpo); + // If it's still empty it was changed and restored, need to restore in + // the complicated way. + if (*p_cpo == NUL) { + set_option_value_give_err("cpo", 0L, save_cpo, 0); + } + free_string_option(save_cpo); } return retval; } /// "searchpos()" function -static void f_searchpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_searchpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { pos_T match_pos; int flags = 0; @@ -8002,7 +7424,7 @@ static void f_searchpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "serverlist()" function -static void f_serverlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_serverlist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { size_t n; char **addrs = server_address_list(&n); @@ -8016,7 +7438,7 @@ static void f_serverlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "serverstart()" function -static void f_serverstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_serverstart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_STRING; rettv->vval.v_string = NULL; // Address of the new server @@ -8061,7 +7483,7 @@ static void f_serverstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "serverstop()" function -static void f_serverstop(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_serverstop(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (check_secure()) { return; @@ -8081,7 +7503,7 @@ static void f_serverstop(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "setbufline()" function -static void f_setbufline(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_setbufline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { linenr_T lnum; buf_T *buf; @@ -8096,17 +7518,17 @@ static void f_setbufline(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// Set the cursor or mark position. -/// If 'charpos' is TRUE, then use the column number as a character offset. +/// If 'charpos' is true, then use the column number as a character offset. /// Otherwise use the column number as a byte offset. static void set_position(typval_T *argvars, typval_T *rettv, bool charpos) { - pos_T pos; - int fnum; colnr_T curswant = -1; rettv->vval.v_number = -1; const char *const name = tv_get_string_chk(argvars); if (name != NULL) { + pos_T pos; + int fnum; if (list2fpos(&argvars[1], &pos, &fnum, &curswant, charpos) == OK) { if (pos.col != MAXCOL && --pos.col < 0) { pos.col = 0; @@ -8133,30 +7555,28 @@ static void set_position(typval_T *argvars, typval_T *rettv, bool charpos) } /// "setcharpos()" function -static void f_setcharpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_setcharpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { set_position(argvars, rettv, true); } -static void f_setcharsearch(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_setcharsearch(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - dict_T *d; - dictitem_T *di; - if (argvars[0].v_type != VAR_DICT) { emsg(_(e_dictreq)); return; } - if ((d = argvars[0].vval.v_dict) != NULL) { + 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(csearch, pcc); + const int c = utfc_ptr2char((char *)csearch, pcc); set_last_csearch(c, csearch, utfc_ptr2len((char *)csearch)); } - di = tv_dict_find(d, S_LEN("forward")); + dictitem_T *di = tv_dict_find(d, S_LEN("forward")); if (di != NULL) { set_csearch_direction(tv_get_number(&di->di_tv) ? FORWARD : BACKWARD); } @@ -8168,24 +7588,14 @@ static void f_setcharsearch(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/// "setcmdpos()" function -static void f_setcmdpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const int pos = (int)tv_get_number(&argvars[0]) - 1; - - if (pos >= 0) { - rettv->vval.v_number = set_cmdline_pos(pos); - } -} - /// "setcursorcharpos" function -static void f_setcursorcharpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_setcursorcharpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { set_cursorpos(argvars, rettv, true); } /// "setenv()" function -static void f_setenv(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_setenv(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { char namebuf[NUMBUFLEN]; char valbuf[NUMBUFLEN]; @@ -8193,14 +7603,14 @@ static void f_setenv(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (argvars[1].v_type == VAR_SPECIAL && argvars[1].vval.v_special == kSpecialVarNull) { - os_unsetenv(name); + vim_unsetenv_ext(name); } else { - os_setenv(name, tv_get_string_buf(&argvars[1], valbuf), 1); + vim_setenv_ext(name, tv_get_string_buf(&argvars[1], valbuf)); } } /// "setfperm({fname}, {mode})" function -static void f_setfperm(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_setfperm(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = 0; @@ -8231,123 +7641,23 @@ static void f_setfperm(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "setline()" function -static void f_setline(typval_T *argvars, typval_T *rettv, FunPtr fptr) +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); } -/// Create quickfix/location list from VimL values -/// -/// Used by `setqflist()` and `setloclist()` functions. Accepts invalid -/// args argument in which case errors out, including VAR_UNKNOWN parameters. -/// -/// @param[in,out] wp Window to create location list for. May be NULL in -/// which case quickfix list will be created. -/// @param[in] args [list, action, what] -/// @param[in] args[0] Quickfix list contents. -/// @param[in] args[1] Optional. Action to perform: -/// append to an existing list, replace its content, -/// or create a new one. -/// @param[in] args[2] Optional. Quickfix list properties or title. -/// Defaults to caller function name. -/// @param[out] rettv Return value: 0 in case of success, -1 otherwise. -static void set_qf_ll_list(win_T *wp, typval_T *args, typval_T *rettv) - FUNC_ATTR_NONNULL_ARG(2, 3) -{ - static char *e_invact = N_("E927: Invalid action: '%s'"); - const char *title = NULL; - char action = ' '; - static int recursive = 0; - rettv->vval.v_number = -1; - dict_T *what = NULL; - - typval_T *list_arg = &args[0]; - if (list_arg->v_type != VAR_LIST) { - emsg(_(e_listreq)); - return; - } else if (recursive != 0) { - emsg(_(e_au_recursive)); - return; - } - - typval_T *action_arg = &args[1]; - if (action_arg->v_type == VAR_UNKNOWN) { - // Option argument was not given. - goto skip_args; - } else if (action_arg->v_type != VAR_STRING) { - emsg(_(e_stringreq)); - return; - } - const char *const act = tv_get_string_chk(action_arg); - if ((*act == 'a' || *act == 'r' || *act == ' ' || *act == 'f') - && act[1] == NUL) { - action = *act; - } else { - semsg(_(e_invact), act); - return; - } - - typval_T *const what_arg = &args[2]; - if (what_arg->v_type == VAR_UNKNOWN) { - // Option argument was not given. - goto skip_args; - } else if (what_arg->v_type == VAR_STRING) { - title = tv_get_string_chk(what_arg); - if (!title) { - // Type error. Error already printed by tv_get_string_chk(). - return; - } - } else if (what_arg->v_type == VAR_DICT && what_arg->vval.v_dict != NULL) { - what = what_arg->vval.v_dict; - } else { - emsg(_(e_dictreq)); - return; - } - -skip_args: - if (!title) { - title = (wp ? ":setloclist()" : ":setqflist()"); - } - - recursive++; - list_T *const l = list_arg->vval.v_list; - if (set_errorlist(wp, l, action, (char *)title, what) == OK) { - rettv->vval.v_number = 0; - } - recursive--; -} - -/// "setloclist()" function -static void f_setloclist(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - win_T *win; - - rettv->vval.v_number = -1; - - win = find_win_by_nr_or_id(&argvars[0]); - if (win != NULL) { - set_qf_ll_list(win, &argvars[1], rettv); - } -} - /// "setpos()" function -static void f_setpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_setpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { set_position(argvars, rettv, false); } -/// "setqflist()" function -static void f_setqflist(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - set_qf_ll_list(NULL, argvars, rettv); -} - /// Translate a register type string to the yank type and block length -static int get_yank_type(char_u **const pp, MotionType *const yank_type, long *const block_len) +static int get_yank_type(char **const pp, MotionType *const yank_type, long *const block_len) FUNC_ATTR_NONNULL_ALL { - char *stropt = (char *)(*pp); + char *stropt = *pp; switch (*stropt) { case 'v': case 'c': // character-wise selection @@ -8369,19 +7679,17 @@ static int get_yank_type(char_u **const pp, MotionType *const yank_type, long *c default: return FAIL; } - *pp = (char_u *)stropt; + *pp = stropt; return OK; } /// "setreg()" function -static void f_setreg(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_setreg(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { bool append = false; - MotionType yank_type; - long block_len; - block_len = -1; - yank_type = kMTUnknown; + long block_len = -1; + MotionType yank_type = kMTUnknown; rettv->vval.v_number = 1; // FAIL is default. @@ -8401,7 +7709,7 @@ static void f_setreg(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (tv_dict_len(d) == 0) { // Empty dict, clear the register (like setreg(0, [])) - char_u *lstval[2] = { NULL, NULL }; + char *lstval[2] = { NULL, NULL }; write_reg_contents_lst(regname, lstval, false, kMTUnknown, -1); return; } @@ -8413,7 +7721,7 @@ static void f_setreg(typval_T *argvars, typval_T *rettv, FunPtr fptr) const char *stropt = tv_dict_get_string(d, "regtype", false); if (stropt != NULL) { - const int ret = get_yank_type((char_u **)&stropt, &yank_type, &block_len); + const int ret = get_yank_type((char **)&stropt, &yank_type, &block_len); if (ret == FAIL || *(++stropt) != NUL) { semsg(_(e_invargval), "value"); @@ -8456,7 +7764,7 @@ static void f_setreg(typval_T *argvars, typval_T *rettv, FunPtr fptr) set_unnamed = true; break; default: - get_yank_type((char_u **)&stropt, &yank_type, &block_len); + get_yank_type((char **)&stropt, &yank_type, &block_len); } } } @@ -8490,7 +7798,7 @@ static void f_setreg(typval_T *argvars, typval_T *rettv, FunPtr fptr) }); *curval++ = NULL; - write_reg_contents_lst(regname, (char_u **)lstval, append, yank_type, (colnr_T)block_len); + write_reg_contents_lst(regname, lstval, append, yank_type, (colnr_T)block_len); free_lstval: while (curallocval > allocval) { @@ -8517,17 +7825,15 @@ free_lstval: } /// "settagstack()" function -static void f_settagstack(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_settagstack(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { static char *e_invact2 = N_("E962: Invalid action: '%s'"); - win_T *wp; - dict_T *d; char action = 'r'; rettv->vval.v_number = -1; // first argument: window number or id - wp = find_win_by_nr_or_id(&argvars[0]); + win_T *wp = find_win_by_nr_or_id(&argvars[0]); if (wp == NULL) { return; } @@ -8537,7 +7843,7 @@ static void f_settagstack(typval_T *argvars, typval_T *rettv, FunPtr fptr) emsg(_(e_dictreq)); return; } - d = argvars[1].vval.v_dict; + dict_T *d = argvars[1].vval.v_dict; if (d == NULL) { return; } @@ -8570,7 +7876,7 @@ static void f_settagstack(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// f_sha256 - sha256({string}) function -static void f_sha256(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_sha256(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const char *p = tv_get_string(&argvars[0]); const char *hash = sha256_bytes((const uint8_t *)p, strlen(p), NULL, 0); @@ -8581,7 +7887,7 @@ static void f_sha256(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "shellescape({string})" function -static void f_shellescape(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_shellescape(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const bool do_special = non_zero_arg(&argvars[1]); @@ -8592,14 +7898,12 @@ static void f_shellescape(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// shiftwidth() function -static void f_shiftwidth(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_shiftwidth(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = 0; if (argvars[0].v_type != VAR_UNKNOWN) { - long col; - - col = (long)tv_get_number_chk(argvars, NULL); + long col = (long)tv_get_number_chk(argvars, NULL); if (col < 0) { return; // type error; errmsg already given } @@ -8610,7 +7914,7 @@ static void f_shiftwidth(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "simplify()" function -static void f_simplify(typval_T *argvars, typval_T *rettv, FunPtr fptr) +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); @@ -8619,7 +7923,7 @@ static void f_simplify(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "sockconnect()" function -static void f_sockconnect(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_sockconnect(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (argvars[0].v_type != VAR_STRING || argvars[1].v_type != VAR_STRING) { emsg(_(e_invarg)); @@ -8671,17 +7975,16 @@ static void f_sockconnect(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "stdioopen()" function -static void f_stdioopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_stdioopen(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (argvars[0].v_type != VAR_DICT) { emsg(_(e_invarg)); return; } - bool rpc = false; CallbackReader on_stdin = CALLBACK_READER_INIT; dict_T *opts = argvars[0].vval.v_dict; - rpc = tv_dict_get_number(opts, "rpc") != 0; + bool rpc = tv_dict_get_number(opts, "rpc") != 0; if (!tv_dict_get_callback(opts, S_LEN("on_stdin"), &on_stdin.cb)) { return; @@ -8706,7 +8009,7 @@ static void f_stdioopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "reltimefloat()" function -static void f_reltimefloat(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_reltimefloat(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) FUNC_ATTR_NONNULL_ALL { proftime_T tm; @@ -8719,7 +8022,7 @@ static void f_reltimefloat(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "soundfold({word})" function -static void f_soundfold(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_soundfold(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_STRING; const char *const s = tv_get_string(&argvars[0]); @@ -8727,11 +8030,8 @@ static void f_soundfold(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "spellbadword()" function -static void f_spellbadword(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_spellbadword(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - const char *word = ""; - hlf_T attr = HLF_COUNT; - size_t len = 0; const int wo_spell_save = curwin->w_p_spell; if (!curwin->w_p_spell) { @@ -8745,6 +8045,9 @@ static void f_spellbadword(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } + const char *word = ""; + hlf_T attr = HLF_COUNT; + size_t len = 0; if (argvars[0].v_type == VAR_UNKNOWN) { // Find the start and length of the badly spelled word. len = spell_move_to(curwin, FORWARD, true, true, &attr); @@ -8776,22 +8079,16 @@ static void f_spellbadword(typval_T *argvars, typval_T *rettv, FunPtr fptr) tv_list_alloc_ret(rettv, 2); tv_list_append_string(rettv->vval.v_list, word, (ssize_t)len); tv_list_append_string(rettv->vval.v_list, - (attr == HLF_SPB ? "bad" - : attr == HLF_SPR ? "rare" - : attr == HLF_SPL ? "local" - : attr == - HLF_SPC ? "caps" - : - NULL), -1); + (attr == HLF_SPB ? "bad" : + attr == HLF_SPR ? "rare" : + attr == HLF_SPL ? "local" : + attr == HLF_SPC ? "caps" : NULL), -1); } /// "spellsuggest()" function -static void f_spellsuggest(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_spellsuggest(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - bool typeerr = false; - int maxcount; garray_T ga = GA_EMPTY_INIT_VALUE; - bool need_capital = false; const int wo_spell_save = curwin->w_p_spell; if (!curwin->w_p_spell) { @@ -8805,8 +8102,11 @@ static void f_spellsuggest(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } + int maxcount; + bool need_capital = false; const char *const str = tv_get_string(&argvars[0]); if (argvars[1].v_type != VAR_UNKNOWN) { + bool typeerr = false; maxcount = (int)tv_get_number_chk(&argvars[1], &typeerr); if (maxcount <= 0) { goto f_spellsuggest_return; @@ -8833,17 +8133,15 @@ f_spellsuggest_return: curwin->w_p_spell = wo_spell_save; } -static void f_split(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_split(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - char *save_cpo; - int match; colnr_T col = 0; bool keepempty = false; bool typeerr = false; // Make 'cpoptions' empty, the 'l' flag should not be used here. - save_cpo = p_cpo; - p_cpo = ""; + char *save_cpo = p_cpo; + p_cpo = empty_option; const char *str = tv_get_string(&argvars[0]); const char *pat = NULL; @@ -8875,6 +8173,7 @@ static void f_split(typval_T *argvars, typval_T *rettv, FunPtr fptr) }; if (regmatch.regprog != NULL) { while (*str != NUL || keepempty) { + bool match; if (*str == NUL) { match = false; // Empty item at the end. } else { @@ -8913,7 +8212,7 @@ theend: } /// "stdpath(type)" function -static void f_stdpath(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_stdpath(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_STRING; rettv->vval.v_string = NULL; @@ -8945,7 +8244,7 @@ static void f_stdpath(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "str2float()" function -static void f_str2float(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_str2float(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { char *p = skipwhite(tv_get_string(&argvars[0])); bool isneg = (*p == '-'); @@ -8961,7 +8260,7 @@ static void f_str2float(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "str2list()" function -static void f_str2list(typval_T *argvars, typval_T *rettv, FunPtr 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]); @@ -8972,10 +8271,9 @@ static void f_str2list(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "str2nr()" function -static void f_str2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_str2nr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int base = 10; - varnumber_T n; int what = 0; if (argvars[1].v_type != VAR_UNKNOWN) { @@ -9005,6 +8303,7 @@ static void f_str2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr) what |= STR2NR_HEX | STR2NR_FORCE; break; } + varnumber_T n; vim_str2nr(p, NULL, NULL, what, &n, NULL, 0, false); // Text after the number is silently ignored. if (isneg) { @@ -9015,7 +8314,7 @@ static void f_str2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "strftime({format}[, {time}])" function -static void f_strftime(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_strftime(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { time_t seconds; @@ -9035,13 +8334,12 @@ static void f_strftime(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_string = xstrdup(_("(Invalid)")); } else { vimconv_T conv; - char_u *enc; conv.vc_type = CONV_NONE; - enc = enc_locale(); + char *enc = (char *)enc_locale(); convert_setup(&conv, p_enc, enc); if (conv.vc_type != CONV_NONE) { - p = (char *)string_convert(&conv, (char_u *)p, NULL); + p = string_convert(&conv, p, NULL); } char result_buf[256]; if (p != NULL) { @@ -9055,7 +8353,7 @@ static void f_strftime(typval_T *argvars, typval_T *rettv, FunPtr fptr) } convert_setup(&conv, enc, p_enc); if (conv.vc_type != CONV_NONE) { - rettv->vval.v_string = (char *)string_convert(&conv, (char_u *)result_buf, NULL); + rettv->vval.v_string = string_convert(&conv, result_buf, NULL); } else { rettv->vval.v_string = xstrdup(result_buf); } @@ -9067,7 +8365,7 @@ static void f_strftime(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "strgetchar()" function -static void f_strgetchar(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_strgetchar(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = -1; @@ -9095,7 +8393,7 @@ static void f_strgetchar(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "stridx()" function -static void f_stridx(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_stridx(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = -1; @@ -9127,20 +8425,20 @@ static void f_stridx(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "string()" function -static void f_string(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_string(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_STRING; rettv->vval.v_string = encode_tv2string(&argvars[0], NULL); } /// "strlen()" function -static void f_strlen(typval_T *argvars, typval_T *rettv, FunPtr fptr) +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, FunPtr fptr) +static void f_strchars(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const char *s = tv_get_string(&argvars[0]); int skipcc = 0; @@ -9163,7 +8461,7 @@ static void f_strchars(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "strdisplaywidth()" function -static void f_strdisplaywidth(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_strdisplaywidth(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const char *const s = tv_get_string(&argvars[0]); int col = 0; @@ -9172,11 +8470,11 @@ static void f_strdisplaywidth(typval_T *argvars, typval_T *rettv, FunPtr fptr) col = (int)tv_get_number(&argvars[1]); } - rettv->vval.v_number = (varnumber_T)(linetabsize_col(col, (char_u *)s) - col); + rettv->vval.v_number = (varnumber_T)(linetabsize_col(col, (char *)s) - col); } /// "strwidth()" function -static void f_strwidth(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_strwidth(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const char *const s = tv_get_string(&argvars[0]); @@ -9184,7 +8482,7 @@ static void f_strwidth(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "strcharpart()" function -static void f_strcharpart(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_strcharpart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const char *const p = tv_get_string(&argvars[0]); const size_t slen = STRLEN(p); @@ -9238,7 +8536,7 @@ static void f_strcharpart(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "strpart()" function -static void f_strpart(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_strpart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { bool error = false; @@ -9284,7 +8582,7 @@ static void f_strpart(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "strptime({format}, {timestring})" function -static void f_strptime(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_strptime(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { char fmt_buf[NUMBUFLEN]; char str_buf[NUMBUFLEN]; @@ -9298,10 +8596,10 @@ static void f_strptime(typval_T *argvars, typval_T *rettv, FunPtr fptr) vimconv_T conv = { .vc_type = CONV_NONE, }; - char_u *enc = enc_locale(); + char *enc = (char *)enc_locale(); convert_setup(&conv, p_enc, enc); if (conv.vc_type != CONV_NONE) { - fmt = (char *)string_convert(&conv, (char_u *)fmt, NULL); + fmt = string_convert(&conv, fmt, NULL); } if (fmt == NULL || os_strptime(str, fmt, &tmval) == NULL @@ -9316,7 +8614,7 @@ static void f_strptime(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "strridx()" function -static void f_strridx(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_strridx(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { char buf[NUMBUFLEN]; const char *const needle = tv_get_string_chk(&argvars[1]); @@ -9359,14 +8657,14 @@ static void f_strridx(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "strtrans()" function -static void f_strtrans(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_strtrans(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_STRING; rettv->vval.v_string = transstr(tv_get_string(&argvars[0]), true); } /// "submatch()" function -static void f_submatch(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_submatch(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { bool error = false; int no = (int)tv_get_number_chk(&argvars[0], &error); @@ -9397,7 +8695,7 @@ static void f_submatch(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "substitute()" function -static void f_substitute(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_substitute(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { char patbuf[NUMBUFLEN]; char subbuf[NUMBUFLEN]; @@ -9426,14 +8724,14 @@ static void f_substitute(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "swapinfo(swap_filename)" function -static void f_swapinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_swapinfo(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { tv_dict_alloc_ret(rettv); get_b0_dict(tv_get_string(argvars), rettv->vval.v_dict); } /// "swapname(expr)" function -static void f_swapname(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_swapname(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_STRING; buf_T *buf = tv_get_buf(&argvars[0], false); @@ -9447,7 +8745,7 @@ static void f_swapname(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "synID(lnum, col, trans)" function -static void f_synID(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_synID(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { // -1 on type error (both) const linenr_T lnum = tv_get_lnum(argvars); @@ -9466,7 +8764,7 @@ static void f_synID(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "synIDattr(id, what [, mode])" function -static void f_synIDattr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_synIDattr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const int id = (int)tv_get_number(&argvars[0]); const char *const what = tv_get_string(&argvars[1]); @@ -9553,7 +8851,7 @@ static void f_synIDattr(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "synIDtrans(id)" function -static void f_synIDtrans(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_synIDtrans(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int id = (int)tv_get_number(&argvars[0]); @@ -9567,7 +8865,7 @@ static void f_synIDtrans(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "synconcealed(lnum, col)" function -static void f_synconcealed(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_synconcealed(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int syntax_flags = 0; int cchar; @@ -9580,7 +8878,7 @@ static void f_synconcealed(typval_T *argvars, typval_T *rettv, FunPtr fptr) const linenr_T lnum = tv_get_lnum(argvars); const colnr_T col = (colnr_T)tv_get_number(&argvars[1]) - 1; - memset(str, NUL, sizeof(str)); + CLEAR_FIELD(str); if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count && col >= 0 && (size_t)col <= STRLEN(ml_get(lnum)) && curwin->w_p_cole > 0) { @@ -9609,7 +8907,7 @@ static void f_synconcealed(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "synstack(lnum, col)" function -static void f_synstack(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_synstack(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { tv_list_set_ret(rettv, NULL); @@ -9633,18 +8931,18 @@ static void f_synstack(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// f_system - the VimL system() function -static void f_system(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_system(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { get_system_output_as_rettv(argvars, rettv, false); } -static void f_systemlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_systemlist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { get_system_output_as_rettv(argvars, rettv, true); } /// "tabpagebuflist()" function -static void f_tabpagebuflist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_tabpagebuflist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { win_T *wp = NULL; @@ -9666,7 +8964,7 @@ static void f_tabpagebuflist(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "tabpagenr()" function -static void f_tabpagenr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_tabpagenr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int nr = 1; @@ -9691,11 +8989,9 @@ static void f_tabpagenr(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// Common code for tabpagewinnr() and winnr(). static int get_winnr(tabpage_T *tp, typval_T *argvar) { - win_T *twin; int nr = 1; - win_T *wp; - twin = (tp == curtab) ? curwin : tp->tp_curwin; + 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); @@ -9710,20 +9006,20 @@ static int get_winnr(tabpage_T *tp, typval_T *argvar) } } else { // Extract the window count (if specified). e.g. winnr('3j') - char_u *endp; - long count = strtol((char *)arg, (char **)&endp, 10); + 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((char *)endp, "j")) { + if (strequal(endp, "j")) { twin = win_vert_neighbor(tp, twin, false, count); - } else if (strequal((char *)endp, "k")) { + } else if (strequal(endp, "k")) { twin = win_vert_neighbor(tp, twin, true, count); - } else if (strequal((char *)endp, "h")) { + } else if (strequal(endp, "h")) { twin = win_horz_neighbor(tp, twin, true, count); - } else if (strequal((char *)endp, "l")) { + } else if (strequal(endp, "l")) { twin = win_horz_neighbor(tp, twin, false, count); } else { invalid_arg = true; @@ -9740,21 +9036,21 @@ static int get_winnr(tabpage_T *tp, typval_T *argvar) } if (nr > 0) { - for (wp = (tp == curtab) ? firstwin : tp->tp_firstwin; + 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; + nr++; } } return nr; } /// "tabpagewinnr()" function -static void f_tabpagewinnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +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])); @@ -9767,16 +9063,14 @@ static void f_tabpagewinnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "tagfiles()" function -static void f_tagfiles(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_tagfiles(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - char *fname; - tagname_T tn; - tv_list_alloc_ret(rettv, kListLenUnknown); - fname = xmalloc(MAXPATHL); + char *fname = xmalloc(MAXPATHL); bool first = true; - while (get_tagfname(&tn, first, (char_u *)fname) == OK) { + tagname_T tn; + while (get_tagfname(&tn, first, fname) == OK) { tv_list_append_string(rettv->vval.v_list, fname, -1); first = false; } @@ -9786,7 +9080,7 @@ static void f_tagfiles(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "taglist()" function -static void f_taglist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_taglist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const char *const tag_pattern = tv_get_string(&argvars[0]); @@ -9804,14 +9098,14 @@ static void f_taglist(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "tempname()" function -static void f_tempname(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_tempname(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_STRING; rettv->vval.v_string = (char *)vim_tempname(); } /// "termopen(cmd[, cwd])" function -static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_termopen(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (check_secure()) { return; @@ -9854,7 +9148,7 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (new_cwd && *new_cwd != NUL) { cwd = new_cwd; // The new cwd must be a directory. - if (!os_isdir_executable(cwd)) { + if (!os_isdir(cwd)) { semsg(_(e_invarg2), "expected valid directory"); shell_free_argv(argv); return; @@ -9934,7 +9228,7 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "timer_info([timer])" function -static void f_timer_info(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_timer_info(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (argvars[0].v_type != VAR_UNKNOWN) { if (argvars[0].v_type != VAR_NUMBER) { @@ -9952,7 +9246,7 @@ static void f_timer_info(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "timer_pause(timer, paused)" function -static void f_timer_pause(typval_T *argvars, typval_T *unused, FunPtr fptr) +static void f_timer_pause(typval_T *argvars, typval_T *unused, EvalFuncData fptr) { if (argvars[0].v_type != VAR_NUMBER) { emsg(_(e_number_exp)); @@ -9972,10 +9266,9 @@ static void f_timer_pause(typval_T *argvars, typval_T *unused, FunPtr fptr) } /// "timer_start(timeout, callback, opts)" function -static void f_timer_start(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_timer_start(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int repeat = 1; - dict_T *dict; rettv->vval.v_number = -1; if (check_secure()) { @@ -9983,8 +9276,8 @@ static void f_timer_start(typval_T *argvars, typval_T *rettv, FunPtr fptr) } if (argvars[2].v_type != VAR_UNKNOWN) { - if (argvars[2].v_type != VAR_DICT - || (dict = argvars[2].vval.v_dict) == NULL) { + dict_T *dict = argvars[2].vval.v_dict; + if (argvars[2].v_type != VAR_DICT || dict == NULL) { semsg(_(e_invarg2), tv_get_string(&argvars[2])); return; } @@ -10005,7 +9298,7 @@ static void f_timer_start(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "timer_stop(timerid)" function -static void f_timer_stop(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_timer_stop(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (argvars[0].v_type != VAR_NUMBER) { emsg(_(e_number_exp)); @@ -10020,27 +9313,27 @@ static void f_timer_stop(typval_T *argvars, typval_T *rettv, FunPtr fptr) timer_stop(timer); } -static void f_timer_stopall(typval_T *argvars, typval_T *unused, FunPtr fptr) +static void f_timer_stopall(typval_T *argvars, typval_T *unused, EvalFuncData fptr) { timer_stop_all(); } /// "tolower(string)" function -static void f_tolower(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_tolower(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_STRING; rettv->vval.v_string = strcase_save(tv_get_string(&argvars[0]), false); } /// "toupper(string)" function -static void f_toupper(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_toupper(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_STRING; rettv->vval.v_string = strcase_save(tv_get_string(&argvars[0]), true); } /// "tr(string, fromstr, tostr)" function -static void f_tr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_tr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { char buf[NUMBUFLEN]; char buf2[NUMBUFLEN]; @@ -10119,16 +9412,14 @@ error: } /// "trim({expr})" function -static void f_trim(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_trim(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { char buf1[NUMBUFLEN]; char buf2[NUMBUFLEN]; const char_u *head = (const char_u *)tv_get_string_buf_chk(&argvars[0], buf1); const char_u *mask = NULL; - const char_u *tail; const char_u *prev; const char_u *p; - int c1; int dir = 0; rettv->v_type = VAR_STRING; @@ -10137,6 +9428,11 @@ static void f_trim(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } + if (argvars[1].v_type != VAR_UNKNOWN && argvars[1].v_type != VAR_STRING) { + semsg(_(e_invarg2), tv_get_string(&argvars[1])); + return; + } + if (argvars[1].v_type == VAR_STRING) { mask = (const char_u *)tv_get_string_buf_chk(&argvars[1], buf2); if (argvars[2].v_type != VAR_UNKNOWN) { @@ -10153,6 +9449,7 @@ static void f_trim(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } + int c1; if (dir == 0 || dir == 1) { // Trim leading characters while (*head != NUL) { @@ -10175,7 +9472,7 @@ static void f_trim(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } - tail = head + STRLEN(head); + const char_u *tail = head + STRLEN(head); if (dir == 0 || dir == 2) { // Trim trailing characters for (; tail > head; tail = prev) { @@ -10202,7 +9499,7 @@ static void f_trim(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "type(expr)" function -static void f_type(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_type(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int n = -1; @@ -10234,7 +9531,7 @@ static void f_type(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "undofile(name)" function -static void f_undofile(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_undofile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_STRING; const char *const fname = tv_get_string(&argvars[0]); @@ -10253,7 +9550,7 @@ static void f_undofile(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "undotree()" function -static void f_undotree(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_undotree(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { tv_dict_alloc_ret(rettv); @@ -10271,13 +9568,12 @@ static void f_undotree(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "virtcol(string)" function -static void f_virtcol(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_virtcol(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { colnr_T vcol = 0; - pos_T *fp; int fnum = curbuf->b_fnum; - fp = var2fpos(&argvars[0], false, &fnum, false); + pos_T *fp = var2fpos(&argvars[0], false, &fnum, false); if (fp != NULL && fp->lnum <= curbuf->b_ml.ml_line_count && fnum == curbuf->b_fnum) { // Limit the column to a valid value, getvvcol() doesn't check. @@ -10290,14 +9586,14 @@ static void f_virtcol(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } getvvcol(curwin, fp, NULL, NULL, &vcol); - ++vcol; + vcol++; } rettv->vval.v_number = vcol; } /// "visualmode()" function -static void f_visualmode(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_visualmode(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { char_u str[2]; @@ -10313,28 +9609,28 @@ static void f_visualmode(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "wildmenumode()" function -static void f_wildmenumode(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_wildmenumode(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - if (wild_menu_showing || ((State & MODE_CMDLINE) && pum_visible())) { + if (wild_menu_showing || ((State & MODE_CMDLINE) && cmdline_pum_active())) { rettv->vval.v_number = 1; } } /// "win_findbuf()" function -static void f_win_findbuf(typval_T *argvars, typval_T *rettv, FunPtr fptr) +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, FunPtr fptr) +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, FunPtr fptr) +static void f_win_gettype(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { win_T *wp = curwin; @@ -10361,43 +9657,52 @@ static void f_win_gettype(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "win_gotoid()" function -static void f_win_gotoid(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_win_gotoid(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - rettv->vval.v_number = win_gotoid(argvars); + 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, FunPtr fptr) +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, FunPtr fptr) +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, FunPtr fptr) +static void f_win_move_separator(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]); + win_T *wp = find_win_by_nr_or_id(&argvars[0]); if (wp == NULL || wp->w_floating) { return; } - offset = (int)tv_get_number(&argvars[1]); + 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, FunPtr fptr) +static void f_win_move_statusline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { win_T *wp; int offset; @@ -10415,7 +9720,7 @@ static void f_win_move_statusline(typval_T *argvars, typval_T *rettv, FunPtr fpt } /// "winbufnr(nr)" function -static void f_winbufnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +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) { @@ -10426,25 +9731,25 @@ static void f_winbufnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "wincol()" function -static void f_wincol(typval_T *argvars, typval_T *rettv, FunPtr fptr) +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, FunPtr fptr) +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; + rettv->vval.v_number = wp->w_height_inner; } } /// "winlayout()" function -static void f_winlayout(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_winlayout(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { tabpage_T *tp; @@ -10463,27 +9768,24 @@ static void f_winlayout(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "winline()" function -static void f_winline(typval_T *argvars, typval_T *rettv, FunPtr fptr) +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, FunPtr fptr) +static void f_winnr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - int nr = 1; - - nr = get_winnr(curtab, &argvars[0]); - rettv->vval.v_number = nr; + rettv->vval.v_number = get_winnr(curtab, &argvars[0]); } /// "winrestcmd()" function -static void f_winrestcmd(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_winrestcmd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - garray_T ga; char_u buf[50]; + garray_T ga; ga_init(&ga, (int)sizeof(char), 70); // Do this twice to handle some window layouts properly. @@ -10506,12 +9808,11 @@ static void f_winrestcmd(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "winrestview()" function -static void f_winrestview(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_winrestview(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - dict_T *dict; + dict_T *dict = argvars[0].vval.v_dict; - if (argvars[0].v_type != VAR_DICT - || (dict = argvars[0].vval.v_dict) == NULL) { + if (argvars[0].v_type != VAR_DICT || dict == NULL) { emsg(_(e_invarg)); } else { dictitem_T *di; @@ -10557,12 +9858,10 @@ static void f_winrestview(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "winsaveview()" function -static void f_winsaveview(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_winsaveview(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - dict_T *dict; - tv_dict_alloc_ret(rettv); - dict = rettv->vval.v_dict; + 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); @@ -10577,32 +9876,32 @@ static void f_winsaveview(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "winwidth(nr)" function -static void f_winwidth(typval_T *argvars, typval_T *rettv, FunPtr fptr) +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; + rettv->vval.v_number = wp->w_width_inner; } } /// "windowsversion()" function -static void f_windowsversion(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_windowsversion(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_STRING; rettv->vval.v_string = xstrdup(windowsVersion); } /// "wordcount()" function -static void f_wordcount(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_wordcount(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { tv_dict_alloc_ret(rettv); cursor_pos_info(rettv->vval.v_dict); } /// "writefile()" function -static void f_writefile(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_writefile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = -1; @@ -10680,7 +9979,7 @@ static void f_writefile(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "xor(expr, expr)" function -static void f_xor(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_xor(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = tv_get_number_chk(&argvars[0], NULL) ^ tv_get_number_chk(&argvars[1], NULL); diff --git a/src/nvim/eval/funcs.h b/src/nvim/eval/funcs.h index 583ee0e75e..adff0b2441 100644 --- a/src/nvim/eval/funcs.h +++ b/src/nvim/eval/funcs.h @@ -1,11 +1,12 @@ #ifndef NVIM_EVAL_FUNCS_H #define NVIM_EVAL_FUNCS_H +#include "nvim/api/private/dispatch.h" #include "nvim/buffer_defs.h" #include "nvim/eval/typval.h" /// Prototype of C function that implements VimL function -typedef void (*VimLFunc)(typval_T *args, typval_T *rvar, FunPtr data); +typedef void (*VimLFunc)(typval_T *args, typval_T *rvar, EvalFuncData data); /// Special flags for base_arg @see EvalFuncDef #define BASE_NONE 0 ///< Not a method (no base argument). @@ -13,13 +14,13 @@ typedef void (*VimLFunc)(typval_T *args, typval_T *rvar, FunPtr data); /// Structure holding VimL function definition typedef struct { - char *name; ///< Name of the function. - uint8_t min_argc; ///< Minimal number of arguments. - uint8_t max_argc; ///< Maximal number of arguments. - uint8_t base_arg; ///< Method base arg # (1-indexed), BASE_NONE or BASE_LAST. - bool fast; ///< Can be run in |api-fast| events - VimLFunc func; ///< Function implementation. - FunPtr data; ///< Userdata for function implementation. + char *name; ///< Name of the function. + uint8_t min_argc; ///< Minimal number of arguments. + uint8_t max_argc; ///< Maximal number of arguments. + uint8_t base_arg; ///< Method base arg # (1-indexed), BASE_NONE or BASE_LAST. + bool fast; ///< Can be run in |api-fast| events + VimLFunc func; ///< Function implementation. + EvalFuncData data; ///< Userdata for function implementation. } EvalFuncDef; #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index ff1808ed91..d5b2b1f2ae 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -233,7 +233,7 @@ void tv_list_init_static10(staticList10_T *const sl) #define SL_SIZE ARRAY_SIZE(sl->sl_items) list_T *const l = &sl->sl_list; - memset(sl, 0, sizeof(staticList10_T)); + CLEAR_POINTER(sl); l->lv_first = &sl->sl_items[0]; l->lv_last = &sl->sl_items[SL_SIZE - 1]; l->lv_refcount = DO_NOT_FREE_CNT; @@ -261,7 +261,7 @@ void tv_list_init_static10(staticList10_T *const sl) void tv_list_init_static(list_T *const l) FUNC_ATTR_NONNULL_ALL { - memset(l, 0, sizeof(*l)); + CLEAR_POINTER(l); l->lv_refcount = DO_NOT_FREE_CNT; list_log(l, NULL, NULL, "sinit"); } @@ -831,7 +831,7 @@ int tv_list_join(garray_T *const gap, list_T *const l, const char *const sep) } /// "join()" function -void f_join(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_join(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (argvars[0].v_type != VAR_LIST) { emsg(_(e_listreq)); @@ -855,7 +855,7 @@ void f_join(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "list2str()" function -void f_list2str(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_list2str(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { garray_T ga; @@ -1247,15 +1247,15 @@ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) ; li != NULL;) { listitem_T *const prev_li = TV_LIST_ITEM_PREV(l, li); if (item_compare_func_ptr(&prev_li, &li) == 0) { - if (info.item_compare_func_err) { // -V547 - emsg(_("E882: Uniq compare function failed")); - break; - } li = tv_list_item_remove(l, li); } else { idx++; li = TV_LIST_ITEM_NEXT(l, li); } + if (info.item_compare_func_err) { // -V547 + emsg(_("E882: Uniq compare function failed")); + break; + } } } @@ -1267,13 +1267,13 @@ theend: } /// "sort"({list})" function -void f_sort(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_sort(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { do_sort_uniq(argvars, rettv, true); } /// "uniq({list})" function -void f_uniq(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_uniq(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { do_sort_uniq(argvars, rettv, false); } @@ -2533,7 +2533,7 @@ dict_T *tv_dict_copy(const vimconv_T *const conv, dict_T *const orig, const bool new_di = tv_dict_item_alloc((const char *)di->di_key); } else { size_t len = STRLEN(di->di_key); - char *const key = (char *)string_convert(conv, di->di_key, &len); + 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); } else { @@ -2806,25 +2806,25 @@ static void tv_dict_list(typval_T *const tv, typval_T *const rettv, const DictLi } /// "items(dict)" function -void f_items(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_items(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { tv_dict_list(argvars, rettv, 2); } /// "keys()" function -void f_keys(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_keys(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { tv_dict_list(argvars, rettv, 0); } /// "values(dict)" function -void f_values(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_values(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { tv_dict_list(argvars, rettv, 1); } /// "has_key()" function -void f_has_key(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_has_key(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (argvars[0].v_type != VAR_DICT) { emsg(_(e_dictreq)); diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h index c02351947b..8177d01f90 100644 --- a/src/nvim/eval/typval.h +++ b/src/nvim/eval/typval.h @@ -13,10 +13,9 @@ #include "nvim/hashtab.h" #include "nvim/lib/queue.h" #include "nvim/macros.h" -#include "nvim/mbyte.h" +#include "nvim/mbyte_defs.h" #include "nvim/message.h" #include "nvim/pos.h" // for linenr_T -#include "nvim/profile.h" // for proftime_T #include "nvim/types.h" #ifdef LOG_LIST_ACTIONS # include "nvim/memory.h" @@ -26,9 +25,6 @@ typedef int64_t varnumber_T; typedef uint64_t uvarnumber_T; -/// Type used for VimL VAR_FLOAT values -typedef double float_T; - /// Refcount for dict or list that should not be freed enum { DO_NOT_FREE_CNT = (INT_MAX / 2), }; @@ -356,6 +352,8 @@ 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 + ///< "<SNR>" as a string, otherwise NULL char_u 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/userfunc.c b/src/nvim/eval/userfunc.c index 2f4799db57..4be922a055 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -12,8 +12,8 @@ #include "nvim/eval/funcs.h" #include "nvim/eval/userfunc.h" #include "nvim/eval/vars.h" -#include "nvim/ex_cmds2.h" #include "nvim/ex_docmd.h" +#include "nvim/ex_eval.h" #include "nvim/ex_getln.h" #include "nvim/fileio.h" #include "nvim/getchar.h" @@ -21,7 +21,9 @@ #include "nvim/insexpand.h" #include "nvim/lua/executor.h" #include "nvim/os/input.h" +#include "nvim/profile.h" #include "nvim/regexp.h" +#include "nvim/runtime.h" #include "nvim/search.h" #include "nvim/ui.h" #include "nvim/vim.h" @@ -44,7 +46,7 @@ # include "eval/userfunc.c.generated.h" #endif -hashtab_T func_hashtab; +static hashtab_T func_hashtab; // Used by get_func_tv() static garray_T funcargs = GA_EMPTY_INIT_VALUE; @@ -66,13 +68,19 @@ void func_init(void) hash_init(&func_hashtab); } +/// Return the function hash table +hashtab_T *func_tbl_get(void) +{ + return &func_hashtab; +} + /// Get function arguments. -static int get_function_args(char_u **argp, char_u endchar, garray_T *newargs, int *varargs, +static int get_function_args(char **argp, char_u endchar, garray_T *newargs, int *varargs, garray_T *default_args, bool skip) { bool mustend = false; - char_u *arg = *argp; - char_u *p = arg; + char *arg = *argp; + char *p = arg; char_u c; int i; @@ -89,7 +97,7 @@ static int get_function_args(char_u **argp, char_u endchar, garray_T *newargs, i // Isolate the arguments: "arg1, arg2, ...)" bool any_default = false; - while (*p != endchar) { + while (*p != (char)endchar) { if (p[0] == '.' && p[1] == '.' && p[2] == '.') { if (varargs != NULL) { *varargs = true; @@ -111,44 +119,43 @@ static int get_function_args(char_u **argp, char_u endchar, garray_T *newargs, i } if (newargs != NULL) { ga_grow(newargs, 1); - c = *p; + c = (char_u)(*p); *p = NUL; - arg = vim_strsave(arg); + arg = xstrdup(arg); // Check for duplicate argument name. for (i = 0; i < newargs->ga_len; i++) { - if (STRCMP(((char_u **)(newargs->ga_data))[i], arg) == 0) { + if (STRCMP(((char **)(newargs->ga_data))[i], arg) == 0) { semsg(_("E853: Duplicate argument name: %s"), arg); xfree(arg); goto err_ret; } } - ((char_u **)(newargs->ga_data))[newargs->ga_len] = arg; + ((char **)(newargs->ga_data))[newargs->ga_len] = arg; newargs->ga_len++; - *p = c; + *p = (char)c; } - if (*skipwhite((char *)p) == '=' && default_args != NULL) { + if (*skipwhite(p) == '=' && default_args != NULL) { typval_T rettv; any_default = true; - p = (char_u *)skipwhite((char *)p) + 1; - p = (char_u *)skipwhite((char *)p); - char_u *expr = p; - if (eval1((char **)&p, &rettv, false) != FAIL) { + p = skipwhite(p) + 1; + p = skipwhite(p); + char_u *expr = (char_u *)p; + if (eval1(&p, &rettv, false) != FAIL) { ga_grow(default_args, 1); // trim trailing whitespace - while (p > expr && ascii_iswhite(p[-1])) { + while (p > (char *)expr && ascii_iswhite(p[-1])) { p--; } - c = *p; + c = (char_u)(*p); *p = NUL; expr = vim_strsave(expr); - ((char_u **)(default_args->ga_data)) - [default_args->ga_len] = expr; + ((char **)(default_args->ga_data))[default_args->ga_len] = (char *)expr; default_args->ga_len++; - *p = c; + *p = (char)c; } else { mustend = true; } @@ -162,15 +169,15 @@ static int get_function_args(char_u **argp, char_u endchar, garray_T *newargs, i mustend = true; } } - p = (char_u *)skipwhite((char *)p); - if (mustend && *p != endchar) { + p = skipwhite(p); + if (mustend && *p != (char)endchar) { if (!skip) { semsg(_(e_invarg2), *argp); } break; } } - if (*p != endchar) { + if (*p != (char)endchar) { goto err_ret; } p++; // skip "endchar" @@ -213,10 +220,21 @@ char_u *get_lambda_name(void) return name; } +static void set_ufunc_name(ufunc_T *fp, char_u *name) +{ + STRCPY(fp->uf_name, name); + + if (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); + } +} + /// Parse a lambda expression and get a Funcref from "*arg". /// /// @return OK or FAIL. Returns NOTDONE for dict or {expr}. -int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate) +int get_lambda_tv(char **arg, typval_T *rettv, bool evaluate) { garray_T newargs = GA_EMPTY_INIT_VALUE; garray_T *pnewargs; @@ -224,13 +242,13 @@ int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate) partial_T *pt = NULL; int varargs; int ret; - char_u *start = (char_u *)skipwhite((char *)(*arg) + 1); + char_u *start = (char_u *)skipwhite(*arg + 1); char_u *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(&start, '-', NULL, NULL, NULL, true); + ret = get_function_args((char **)&start, '-', NULL, NULL, NULL, true); if (ret == FAIL || *start != '>') { return NOTDONE; } @@ -241,7 +259,7 @@ int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate) } else { pnewargs = NULL; } - *arg = (char_u *)skipwhite((char *)(*arg) + 1); + *arg = skipwhite(*arg + 1); ret = get_function_args(arg, '-', pnewargs, &varargs, NULL, false); if (ret == FAIL || **arg != '>') { goto errret; @@ -253,14 +271,14 @@ int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate) } // Get the start and the end of the expression. - *arg = (char_u *)skipwhite((char *)(*arg) + 1); - s = *arg; - ret = skip_expr((char **)arg); + *arg = skipwhite((*arg) + 1); + s = (char_u *)(*arg); + ret = skip_expr(arg); if (ret == FAIL) { goto errret; } - e = *arg; - *arg = (char_u *)skipwhite((char *)(*arg)); + e = (char_u *)(*arg); + *arg = skipwhite(*arg); if (**arg != '}') { goto errret; } @@ -282,7 +300,7 @@ int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate) // Add "return " before the expression. size_t len = (size_t)(7 + e - s + 1); p = (char_u *)xmalloc(len); - ((char_u **)(newlines.ga_data))[newlines.ga_len++] = p; + ((char **)(newlines.ga_data))[newlines.ga_len++] = (char *)p; STRCPY(p, "return "); STRLCPY(p + 7, s, e - s + 1); if (strstr((char *)p + 7, "a:") == NULL) { @@ -291,7 +309,7 @@ int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate) } fp->uf_refcount = 1; - STRCPY(fp->uf_name, name); + 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); @@ -313,7 +331,7 @@ int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate) fp->uf_flags = flags; fp->uf_calls = 0; fp->uf_script_ctx = current_sctx; - fp->uf_script_ctx.sc_lnum += sourcing_lnum - newlines.ga_len; + fp->uf_script_ctx.sc_lnum += SOURCING_LNUM - newlines.ga_len; pt->pt_func = fp; pt->pt_refcount = 1; @@ -391,7 +409,7 @@ void emsg_funcname(char *ermsg, const char_u *name) char_u *p; if (*name == K_SPECIAL) { - p = concat_str((char_u *)"<SNR>", name + 3); + p = (char_u *)concat_str("<SNR>", (char *)name + 3); } else { p = (char_u *)name; } @@ -411,34 +429,32 @@ 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_u **arg, funcexe_T *funcexe) +int get_func_tv(const char_u *name, int len, typval_T *rettv, char **arg, funcexe_T *funcexe) { - char_u *argp; + char *argp; int ret = OK; typval_T argvars[MAX_FUNC_ARGS + 1]; // vars for arguments int argcount = 0; // number of arguments found - /* - * Get the arguments. - */ + // Get the arguments. argp = *arg; while (argcount < MAX_FUNC_ARGS - (funcexe->partial == NULL ? 0 : funcexe->partial->pt_argc)) { - argp = (char_u *)skipwhite((char *)argp + 1); // skip the '(' or ',' + argp = skipwhite(argp + 1); // skip the '(' or ',' if (*argp == ')' || *argp == ',' || *argp == NUL) { break; } - if (eval1((char **)&argp, &argvars[argcount], funcexe->evaluate) == FAIL) { + if (eval1(&argp, &argvars[argcount], funcexe->evaluate) == FAIL) { ret = FAIL; break; } - ++argcount; + argcount++; if (*argp != ',') { break; } } if (*argp == ')') { - ++argp; + argp++; } else { ret = FAIL; } @@ -472,7 +488,7 @@ int get_func_tv(const char_u *name, int len, typval_T *rettv, char_u **arg, func tv_clear(&argvars[argcount]); } - *arg = (char_u *)skipwhite((char *)argp); + *arg = skipwhite(argp); return ret; } @@ -740,6 +756,7 @@ static void func_clear_items(ufunc_T *fp) ga_clear_strings(&(fp->uf_args)); ga_clear_strings(&(fp->uf_def_args)); ga_clear_strings(&(fp->uf_lines)); + XFREE_CLEAR(fp->uf_name_exp); if (fp->uf_cb_free != NULL) { fp->uf_cb_free(fp->uf_cb_state); @@ -803,8 +820,6 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett linenr_T firstline, linenr_T lastline, dict_T *selfdict) FUNC_ATTR_NONNULL_ARG(1, 3, 4) { - char_u *save_sourcing_name; - linenr_T save_sourcing_lnum; bool using_sandbox = false; funccall_T *fc; int save_did_emsg; @@ -814,7 +829,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett int ai; bool islambda = false; char_u numbuf[NUMBUFLEN]; - char_u *name; + char *name; typval_T *tv_to_free[MAX_FUNC_ARGS]; int tv_to_free_len = 0; proftime_T wait_start; @@ -830,14 +845,14 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett rettv->vval.v_number = -1; return; } - ++depth; + depth++; // Save search patterns and redo buffer. save_search_patterns(); if (!ins_compl_active()) { saveRedobuff(&save_redo); did_save_redo = true; } - ++fp->uf_calls; + fp->uf_calls++; // check for CTRL-C hit line_breakcheck(); // prepare the funccall_T structure @@ -848,7 +863,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett fc->rettv = rettv; fc->level = ex_nesting_level; // Check if this function has a breakpoint. - fc->breakpoint = dbg_find_breakpoint(false, fp->uf_name, (linenr_T)0); + fc->breakpoint = dbg_find_breakpoint(false, (char *)fp->uf_name, (linenr_T)0); fc->dbg_tick = debug_tick; // Set up fields for closure. @@ -870,7 +885,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett // some compiler that checks the destination size. v = (dictitem_T *)&fc->fixvar[fixvar_idx++]; #ifndef __clang_analyzer__ - name = v->di_key; + name = (char *)v->di_key; STRCPY(name, "self"); #endif v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; @@ -878,7 +893,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett v->di_tv.v_type = VAR_DICT; v->di_tv.v_lock = VAR_UNLOCKED; v->di_tv.vval.v_dict = selfdict; - ++selfdict->dv_refcount; + selfdict->dv_refcount++; } // Init a: variables, unless none found (in lambda). @@ -896,7 +911,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett // destination size. v = (dictitem_T *)&fc->fixvar[fixvar_idx++]; #ifndef __clang_analyzer__ - name = v->di_key; + name = (char *)v->di_key; STRCPY(name, "000"); #endif v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; @@ -935,13 +950,13 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett // evaluate named argument default expression isdefault = ai + fp->uf_def_args.ga_len >= 0 && i >= argcount; if (isdefault) { - char_u *default_expr = NULL; + char *default_expr = NULL; def_rettv.v_type = VAR_NUMBER; def_rettv.vval.v_number = -1; - default_expr = ((char_u **)(fp->uf_def_args.ga_data)) + default_expr = ((char **)(fp->uf_def_args.ga_data)) [ai + fp->uf_def_args.ga_len]; - if (eval1((char **)&default_expr, &def_rettv, true) == FAIL) { + if (eval1(&default_expr, &def_rettv, true) == FAIL) { default_arg_err = true; break; } @@ -953,7 +968,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett } // "..." argument a:1, a:2, etc. snprintf((char *)numbuf, sizeof(numbuf), "%d", ai + 1); - name = numbuf; + name = (char *)numbuf; } if (fixvar_idx < FIXVAR_CNT && STRLEN(name) <= VAR_SHORT_LEN) { v = (dictitem_T *)&fc->fixvar[fixvar_idx++]; @@ -994,80 +1009,56 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett // Don't redraw while executing the function. RedrawingDisabled++; - save_sourcing_name = (char_u *)sourcing_name; - save_sourcing_lnum = sourcing_lnum; - sourcing_lnum = 1; if (fp->uf_flags & FC_SANDBOX) { using_sandbox = true; sandbox++; } - // need space for new sourcing_name: - // * save_sourcing_name - // * "["number"].." or "function " - // * "<SNR>" + fp->uf_name - 3 - // * terminating NUL - size_t len = (save_sourcing_name == NULL ? 0 : STRLEN(save_sourcing_name)) - + STRLEN(fp->uf_name) + 27; - sourcing_name = xmalloc(len); - { - if (save_sourcing_name != NULL - && STRNCMP(save_sourcing_name, "function ", 9) == 0) { - vim_snprintf(sourcing_name, - len, - "%s[%" PRId64 "]..", - save_sourcing_name, - (int64_t)save_sourcing_lnum); - } else { - STRCPY(sourcing_name, "function "); - } - cat_func_name((char_u *)sourcing_name + STRLEN(sourcing_name), fp); - - if (p_verbose >= 12) { - ++no_wait_return; - verbose_enter_scroll(); + estack_push_ufunc(fp, 1); + if (p_verbose >= 12) { + no_wait_return++; + verbose_enter_scroll(); - smsg(_("calling %s"), sourcing_name); - if (p_verbose >= 14) { - msg_puts("("); - for (int i = 0; i < argcount; i++) { - if (i > 0) { - msg_puts(", "); - } - if (argvars[i].v_type == VAR_NUMBER) { - msg_outnum((long)argvars[i].vval.v_number); - } else { - // Do not want errors such as E724 here. - emsg_off++; - char *tofree = encode_tv2string(&argvars[i], NULL); - emsg_off--; - if (tofree != NULL) { - char *s = tofree; - char buf[MSG_BUF_LEN]; - if (vim_strsize(s) > MSG_BUF_CLEN) { - trunc_string(s, buf, MSG_BUF_CLEN, sizeof(buf)); - s = buf; - } - msg_puts(s); - xfree(tofree); + smsg(_("calling %s"), SOURCING_NAME); + if (p_verbose >= 14) { + msg_puts("("); + for (int i = 0; i < argcount; i++) { + if (i > 0) { + msg_puts(", "); + } + if (argvars[i].v_type == VAR_NUMBER) { + msg_outnum((long)argvars[i].vval.v_number); + } else { + // Do not want errors such as E724 here. + emsg_off++; + char *tofree = encode_tv2string(&argvars[i], NULL); + emsg_off--; + if (tofree != NULL) { + char *s = tofree; + char buf[MSG_BUF_LEN]; + if (vim_strsize(s) > MSG_BUF_CLEN) { + trunc_string(s, buf, MSG_BUF_CLEN, sizeof(buf)); + s = buf; } + msg_puts(s); + xfree(tofree); } } - msg_puts(")"); } - msg_puts("\n"); // don't overwrite this either - - verbose_leave_scroll(); - --no_wait_return; + msg_puts(")"); } + msg_puts("\n"); // don't overwrite this either + + verbose_leave_scroll(); + no_wait_return--; } const bool do_profiling_yes = do_profiling == PROF_YES; bool func_not_yet_profiling_but_should = do_profiling_yes - && !fp->uf_profiling && has_profiling(false, fp->uf_name, NULL); + && !fp->uf_profiling && has_profiling(false, (char *)fp->uf_name, NULL); if (func_not_yet_profiling_but_should) { started_profiling = true; @@ -1080,7 +1071,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett || (fc->caller != NULL && fc->caller->func->uf_profiling)); if (func_or_func_caller_profiling) { - ++fp->uf_tm_count; + fp->uf_tm_count++; call_start = profile_start(); fp->uf_tm_children = profile_zero(); } @@ -1092,17 +1083,17 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett const sctx_T save_current_sctx = current_sctx; current_sctx = fp->uf_script_ctx; save_did_emsg = did_emsg; - did_emsg = FALSE; + did_emsg = false; if (default_arg_err && (fp->uf_flags & FC_ABORT)) { did_emsg = true; } else if (islambda) { - char_u *p = *(char_u **)fp->uf_lines.ga_data + 7; + char *p = *(char **)fp->uf_lines.ga_data + 7; // A Lambda always has the command "return {expr}". It is much faster // to evaluate {expr} directly. ex_nesting_level++; - (void)eval1((char **)&p, rettv, true); + (void)eval1(&p, rettv, true); ex_nesting_level--; } else { // call do_cmdline() to execute the lines @@ -1110,7 +1101,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT); } - --RedrawingDisabled; + RedrawingDisabled--; // when the function was aborted because of an error, return -1 if ((did_emsg @@ -1140,14 +1131,14 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett // when being verbose, mention the return value if (p_verbose >= 12) { - ++no_wait_return; + no_wait_return++; verbose_enter_scroll(); if (aborting()) { - smsg(_("%s aborted"), sourcing_name); + smsg(_("%s aborted"), SOURCING_NAME); } else if (fc->rettv->v_type == VAR_NUMBER) { smsg(_("%s returning #%" PRId64 ""), - sourcing_name, (int64_t)fc->rettv->vval.v_number); + SOURCING_NAME, (int64_t)fc->rettv->vval.v_number); } else { char buf[MSG_BUF_LEN]; @@ -1163,19 +1154,17 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett trunc_string(s, buf, MSG_BUF_CLEN, MSG_BUF_LEN); s = buf; } - smsg(_("%s returning %s"), sourcing_name, s); + smsg(_("%s returning %s"), SOURCING_NAME, s); xfree(tofree); } } msg_puts("\n"); // don't overwrite this either verbose_leave_scroll(); - --no_wait_return; + no_wait_return--; } - xfree(sourcing_name); - sourcing_name = (char *)save_sourcing_name; - sourcing_lnum = save_sourcing_lnum; + estack_pop(); current_sctx = save_current_sctx; if (do_profiling_yes) { script_prof_restore(&wait_start); @@ -1184,15 +1173,15 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett sandbox--; } - if (p_verbose >= 12 && sourcing_name != NULL) { - ++no_wait_return; + if (p_verbose >= 12 && SOURCING_NAME != NULL) { + no_wait_return++; verbose_enter_scroll(); - smsg(_("continuing in %s"), sourcing_name); + smsg(_("continuing in %s"), SOURCING_NAME); msg_puts("\n"); // don't overwrite this either verbose_leave_scroll(); - --no_wait_return; + no_wait_return--; } did_emsg |= save_did_emsg; @@ -1633,9 +1622,8 @@ static void list_func_head(ufunc_T *fp, int indent, bool force) msg_puts(" "); } msg_puts(force ? "function! " : "function "); - if (fp->uf_name[0] == K_SPECIAL) { - msg_puts_attr("<SNR>", HL_ATTR(HLF_8)); - msg_puts((const char *)fp->uf_name + 3); + if (fp->uf_name_exp != NULL) { + msg_puts((const char *)fp->uf_name_exp); } else { msg_puts((const char *)fp->uf_name); } @@ -1691,7 +1679,7 @@ 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_u **pp, bool skip, int flags, funcdict_T *fdp, partial_T **partial) +char_u *trans_function_name(char **pp, bool skip, int flags, funcdict_T *fdp, partial_T **partial) FUNC_ATTR_NONNULL_ARG(1) { char_u *name = NULL; @@ -1702,13 +1690,13 @@ char_u *trans_function_name(char_u **pp, bool skip, int flags, funcdict_T *fdp, lval_T lv; if (fdp != NULL) { - memset(fdp, 0, sizeof(funcdict_T)); + CLEAR_POINTER(fdp); } - start = *pp; + start = (char_u *)(*pp); // Check for hard coded <SNR>: already translated function ID (from a user // command). - if ((*pp)[0] == K_SPECIAL && (*pp)[1] == KS_EXTRA + if ((unsigned char)(*pp)[0] == K_SPECIAL && (unsigned char)(*pp)[1] == KS_EXTRA && (*pp)[2] == KE_SNR) { *pp += 3; len = get_id_len((const char **)pp) + 3; @@ -1742,7 +1730,7 @@ char_u *trans_function_name(char_u **pp, bool skip, int flags, funcdict_T *fdp, semsg(_(e_invarg2), start); } } else { - *pp = (char_u *)find_name_end((char *)start, NULL, NULL, FNE_INCL_BR); + *pp = (char *)find_name_end((char *)start, NULL, NULL, FNE_INCL_BR); } goto theend; } @@ -1756,7 +1744,7 @@ char_u *trans_function_name(char_u **pp, bool skip, int flags, funcdict_T *fdp, } if (lv.ll_tv->v_type == VAR_FUNC && lv.ll_tv->vval.v_string != NULL) { name = vim_strsave((char_u *)lv.ll_tv->vval.v_string); - *pp = (char_u *)end; + *pp = (char *)end; } 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 == '.') { @@ -1767,10 +1755,10 @@ char_u *trans_function_name(char_u **pp, bool skip, int flags, funcdict_T *fdp, } name = xmallocz((size_t)len); memcpy(name, end + 1, (size_t)len); - *pp = (char_u *)end + 1 + len; + *pp = (char *)end + 1 + len; } else { name = vim_strsave((char_u *)partial_name(lv.ll_tv->vval.v_partial)); - *pp = (char_u *)end; + *pp = (char *)end; } if (partial != NULL) { *partial = lv.ll_tv->vval.v_partial; @@ -1781,7 +1769,7 @@ char_u *trans_function_name(char_u **pp, bool skip, int flags, funcdict_T *fdp, || fdp->fd_newkey == NULL)) { emsg(_(e_funcref)); } else { - *pp = (char_u *)end; + *pp = (char *)end; } name = NULL; } @@ -1790,7 +1778,7 @@ char_u *trans_function_name(char_u **pp, bool skip, int flags, funcdict_T *fdp, if (lv.ll_name == NULL) { // Error found, but continue after the function name. - *pp = (char_u *)end; + *pp = (char *)end; goto theend; } @@ -1803,16 +1791,16 @@ char_u *trans_function_name(char_u **pp, bool skip, int flags, funcdict_T *fdp, name = NULL; } } else if (!(flags & TFN_NO_DEREF)) { - len = (int)(end - *pp); + len = (int)(end - (char_u *)(*pp)); name = deref_func_name((const char *)(*pp), &len, partial, flags & TFN_NO_AUTOLOAD); - if (name == *pp) { + if (name == (char_u *)(*pp)) { name = NULL; } } if (name != NULL) { name = vim_strsave(name); - *pp = (char_u *)end; + *pp = (char *)end; if (STRNCMP(name, "<SNR>", 5) == 0) { // Change "<SNR>" to the byte sequence. name[0] = K_SPECIAL; @@ -1890,13 +1878,15 @@ char_u *trans_function_name(char_u **pp, bool skip, int flags, funcdict_T *fdp, } memmove(name + lead, lv.ll_name, (size_t)len); name[lead + len] = NUL; - *pp = (char_u *)end; + *pp = (char *)end; theend: clear_lval(&lv); return name; } +#define MAX_FUNC_NESTING 50 + /// ":function" void ex_function(exarg_T *eap) { @@ -1939,11 +1929,11 @@ void ex_function(exarg_T *eap) 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) { + for (hi = func_hashtab.ht_array; todo > 0 && !got_int; hi++) { if (!HASHITEM_EMPTY(hi)) { - --todo; + todo--; fp = HI2UF(hi); - if (message_filtered(fp->uf_name)) { + if (message_filtered((char *)fp->uf_name)) { continue; } if (!func_name_refcount(fp->uf_name)) { @@ -1960,7 +1950,7 @@ void ex_function(exarg_T *eap) * ":function /pat": list functions matching pattern. */ if (*eap->arg == '/') { - p = skip_regexp((char_u *)eap->arg + 1, '/', true, NULL); + p = (char_u *)skip_regexp(eap->arg + 1, '/', true, NULL); if (!eap->skip) { regmatch_T regmatch; @@ -1972,9 +1962,9 @@ void ex_function(exarg_T *eap) regmatch.rm_ic = p_ic; todo = (int)func_hashtab.ht_used; - for (hi = func_hashtab.ht_array; todo > 0 && !got_int; ++hi) { + for (hi = func_hashtab.ht_array; todo > 0 && !got_int; hi++) { if (!HASHITEM_EMPTY(hi)) { - --todo; + todo--; fp = HI2UF(hi); if (!isdigit(*fp->uf_name) && vim_regexec(®match, (char *)fp->uf_name, 0)) { @@ -1986,7 +1976,7 @@ void ex_function(exarg_T *eap) } } if (*p == '/') { - ++p; + p++; } eap->nextcmd = (char *)check_nextcmd(p); return; @@ -2007,7 +1997,7 @@ void ex_function(exarg_T *eap) // s:func script-local function name // g:func global function name, same as "func" p = (char_u *)eap->arg; - name = trans_function_name(&p, eap->skip, TFN_NO_AUTOLOAD, &fudi, NULL); + name = trans_function_name((char **)&p, eap->skip, TFN_NO_AUTOLOAD, &fudi, NULL); paren = (vim_strchr((char *)p, '(') != NULL); if (name == NULL && (fudi.fd_dict == NULL || !paren) && !eap->skip) { /* @@ -2022,14 +2012,14 @@ 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 // braces should not cause the function not to be defined. saved_did_emsg = did_emsg; - did_emsg = FALSE; + did_emsg = false; // // ":function func" with only function name: list function. @@ -2064,7 +2054,7 @@ void ex_function(exarg_T *eap) msg_putchar(' '); } } - msg_prt_line(FUNCLINE(fp, j), false); + msg_prt_line((char_u *)FUNCLINE(fp, j), false); ui_flush(); // show a line at a time os_breakcheck(); } @@ -2108,9 +2098,8 @@ void ex_function(exarg_T *eap) } if (arg != NULL && (fudi.fd_di == NULL || !tv_is_func(fudi.fd_di->di_tv))) { int j = (*arg == K_SPECIAL) ? 3 : 0; - while (arg[j] != NUL && (j == 0 ? eval_isnamec1(arg[j]) - : eval_isnamec(arg[j]))) { - ++j; + while (arg[j] != NUL && (j == 0 ? eval_isnamec1(arg[j]) : eval_isnamec(arg[j]))) { + j++; } if (arg[j] != NUL) { emsg_funcname((char *)e_invarg2, arg); @@ -2122,7 +2111,7 @@ void ex_function(exarg_T *eap) } } - if (get_function_args(&p, ')', &newargs, &varargs, + if (get_function_args((char **)&p, ')', &newargs, &varargs, &default_args, eap->skip) == FAIL) { goto errret_2; } @@ -2192,7 +2181,7 @@ void ex_function(exarg_T *eap) } // Save the starting line number. - sourcing_lnum_top = sourcing_lnum; + sourcing_lnum_top = SOURCING_LNUM; indent = 2; nesting = 0; @@ -2234,10 +2223,10 @@ void ex_function(exarg_T *eap) ui_ext_cmdline_block_append((size_t)indent, (const char *)theline); } - // Detect line continuation: sourcing_lnum increased more than one. + // Detect line continuation: SOURCING_LNUM increased more than one. sourcing_lnum_off = get_sourced_lnum(eap->getline, eap->cookie); - if (sourcing_lnum < sourcing_lnum_off) { - sourcing_lnum_off -= sourcing_lnum; + if (SOURCING_LNUM < sourcing_lnum_off) { + sourcing_lnum_off -= SOURCING_LNUM; } else { sourcing_lnum_off = 0; } @@ -2315,10 +2304,14 @@ void ex_function(exarg_T *eap) p = (char_u *)skipwhite((char *)p + 1); } p += eval_fname_script((const char *)p); - xfree(trans_function_name(&p, true, 0, NULL, NULL)); + xfree(trans_function_name((char **)&p, true, 0, NULL, NULL)); if (*skipwhite((char *)p) == '(') { - nesting++; - indent += 2; + if (nesting == MAX_FUNC_NESTING - 1) { + emsg(_("E1058: function nesting too deep")); + } else { + nesting++; + indent += 2; + } } } @@ -2339,7 +2332,7 @@ void ex_function(exarg_T *eap) } // heredoc: Check for ":python <<EOF", ":lua <<EOF", etc. - arg = (char_u *)skipwhite((char *)skiptowhite(p)); + arg = (char_u *)skipwhite(skiptowhite((char *)p)); if (arg[0] == '<' && arg[1] == '<' && ((p[0] == 'p' && p[1] == 'y' && (!ASCII_ISALNUM(p[2]) || p[2] == 't' @@ -2366,12 +2359,12 @@ void ex_function(exarg_T *eap) // Check for ":let v =<< [trim] EOF" // and ":let [a, b] =<< [trim] EOF" - arg = (char_u *)skipwhite((char *)skiptowhite(p)); + arg = (char_u *)skipwhite(skiptowhite((char *)p)); if (*arg == '[') { arg = (char_u *)vim_strchr((char *)arg, ']'); } if (arg != NULL) { - arg = (char_u *)skipwhite((char *)skiptowhite(arg)); + arg = (char_u *)skipwhite(skiptowhite((char *)arg)); if (arg[0] == '=' && arg[1] == '<' && arg[2] == '<' @@ -2386,7 +2379,7 @@ void ex_function(exarg_T *eap) heredoc_trimmed = vim_strnsave(theline, (size_t)((char_u *)skipwhite((char *)theline) - theline)); } - skip_until = vim_strnsave(p, (size_t)(skiptowhite(p) - p)); + skip_until = vim_strnsave(p, (size_t)((char_u *)skiptowhite((char *)p) - p)); do_concat = false; is_heredoc = true; } @@ -2400,12 +2393,12 @@ void ex_function(exarg_T *eap) // allocates 250 bytes per line, this saves 80% on average. The cost // is an extra alloc/free. p = vim_strsave(theline); - ((char_u **)(newlines.ga_data))[newlines.ga_len++] = p; + ((char **)(newlines.ga_data))[newlines.ga_len++] = (char *)p; // Add NULL lines for continuation lines, so that the line count is // equal to the index in the growarray. while (sourcing_lnum_off-- > 0) { - ((char_u **)(newlines.ga_data))[newlines.ga_len++] = NULL; + ((char **)(newlines.ga_data))[newlines.ga_len++] = NULL; } // Check for end of eap->arg. @@ -2454,9 +2447,12 @@ void ex_function(exarg_T *eap) fp = NULL; overwrite = true; } else { - // redefine existing function + char_u *exp_name = fp->uf_name_exp; + // redefine existing function, keep the expanded name XFREE_CLEAR(name); + fp->uf_name_exp = NULL; func_clear_items(fp); + fp->uf_name_exp = exp_name; fp->uf_profiling = false; fp->uf_prof_initialized = false; } @@ -2495,13 +2491,12 @@ void ex_function(exarg_T *eap) // Check that the autoload name matches the script name. int j = FAIL; - if (sourcing_name != NULL) { + if (SOURCING_NAME != NULL) { scriptname = (char_u *)autoload_name((const char *)name, STRLEN(name)); p = (char_u *)vim_strchr((char *)scriptname, '/'); plen = (int)STRLEN(p); - slen = (int)STRLEN(sourcing_name); - if (slen > plen && FNAMECMP(p, - sourcing_name + slen - plen) == 0) { + slen = (int)STRLEN(SOURCING_NAME); + if (slen > plen && FNAMECMP(p, SOURCING_NAME + slen - plen) == 0) { j = OK; } xfree(scriptname); @@ -2536,7 +2531,7 @@ void ex_function(exarg_T *eap) } // insert the new function in the function list - STRCPY(fp->uf_name, name); + set_ufunc_name(fp, name); if (overwrite) { hi = hash_find(&func_hashtab, (char *)name); hi->hi_key = UF2HIKEY(fp); @@ -2628,8 +2623,7 @@ bool function_exists(const char *const name, bool no_deref) if (no_deref) { flag |= TFN_NO_DEREF; } - char *const p = (char *)trans_function_name((char_u **)&nm, false, flag, NULL, - NULL); + char *const p = (char *)trans_function_name((char **)&nm, false, flag, NULL, NULL); nm = (char_u *)skipwhite((char *)nm); // Only accept "funcname", "funcname ", "funcname (..." and @@ -2656,10 +2650,10 @@ char *get_user_func_name(expand_T *xp, int idx) assert(hi); if (done < func_hashtab.ht_used) { if (done++ > 0) { - ++hi; + hi++; } while (HASHITEM_EMPTY(hi)) { - ++hi; + hi++; } fp = HI2UF(hi); @@ -2693,7 +2687,7 @@ void ex_delfunction(exarg_T *eap) funcdict_T fudi; p = (char_u *)eap->arg; - name = trans_function_name(&p, eap->skip, 0, &fudi, NULL); + name = trans_function_name((char **)&p, eap->skip, 0, &fudi, NULL); xfree(fudi.fd_newkey); if (name == NULL) { if (fudi.fd_dict != NULL && !eap->skip) { @@ -2859,7 +2853,7 @@ void ex_return(exarg_T *eap) { char_u *arg = (char_u *)eap->arg; typval_T rettv; - int returning = FALSE; + int returning = false; if (current_funccal == NULL) { emsg(_("E133: :return not inside a function")); @@ -2867,7 +2861,7 @@ void ex_return(exarg_T *eap) } if (eap->skip) { - ++emsg_skip; + emsg_skip++; } eap->nextcmd = NULL; @@ -2899,7 +2893,7 @@ void ex_return(exarg_T *eap) } if (eap->skip) { - --emsg_skip; + emsg_skip--; } } @@ -2932,7 +2926,7 @@ void ex_call(exarg_T *eap) return; } - tofree = trans_function_name(&arg, false, TFN_INT, &fudi, &partial); + tofree = trans_function_name((char **)&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); @@ -2987,7 +2981,7 @@ void ex_call(exarg_T *eap) funcexe.evaluate = true; funcexe.partial = partial; funcexe.selfdict = fudi.fd_dict; - if (get_func_tv(name, -1, &rettv, &arg, &funcexe) == FAIL) { + if (get_func_tv(name, -1, &rettv, (char **)&arg, &funcexe) == FAIL) { failed = true; break; } @@ -3016,7 +3010,7 @@ void ex_call(exarg_T *eap) // When inside :try we need to check for following "| catch" or "| endtry". // Not when there was an error, but do check if an exception was thrown. - if ((!aborting() || current_exception != NULL) && (!failed || eap->cstack->cs_trylevel > 0)) { + if ((!aborting() || did_throw) && (!failed || eap->cstack->cs_trylevel > 0)) { // Check for trailing illegal characters and a following command. if (!ends_excmd(*arg)) { if (!failed && !aborting()) { @@ -3041,8 +3035,8 @@ end: /// @param is_cmd set when called due to a ":return" command. /// @param rettv may point to a typval_T with the return rettv. /// -/// @return TRUE when the return can be carried out, -/// FALSE when the return gets pending. +/// @return true when the return can be carried out, +/// false when the return gets pending. int do_return(exarg_T *eap, int reanimate, int is_cmd, void *rettv) { int idx; @@ -3092,7 +3086,7 @@ int do_return(exarg_T *eap, int reanimate, int is_cmd, void *rettv) } report_make_pending(CSTP_RETURN, rettv); } else { - current_funccal->returned = TRUE; + current_funccal->returned = true; // If the return is carried out now, store the return value. For // a return immediately after reanimation, the value is already @@ -3111,16 +3105,16 @@ int do_return(exarg_T *eap, int reanimate, int is_cmd, void *rettv) /// Generate a return command for producing the value of "rettv". The result /// is an allocated string. Used by report_pending() for verbose messages. -char_u *get_return_cmd(void *rettv) +char *get_return_cmd(void *rettv) { - char_u *s = NULL; - char_u *tofree = NULL; + char *s = NULL; + char *tofree = NULL; if (rettv != NULL) { - tofree = s = (char_u *)encode_tv2echo((typval_T *)rettv, NULL); + tofree = s = encode_tv2echo((typval_T *)rettv, NULL); } if (s == NULL) { - s = (char_u *)""; + s = ""; } STRCPY(IObuff, ":return "); @@ -3129,7 +3123,7 @@ char_u *get_return_cmd(void *rettv) STRCPY(IObuff + IOSIZE - 4, "..."); } xfree(tofree); - return vim_strsave(IObuff); + return (char *)vim_strsave(IObuff); } /// Get next function line. @@ -3140,13 +3134,12 @@ char *get_func_line(int c, void *cookie, int indent, bool do_concat) { funccall_T *fcp = (funccall_T *)cookie; ufunc_T *fp = fcp->func; - char_u *retval; + char *retval; garray_T *gap; // growarray with function lines // If breakpoints have been added/deleted need to check for it. if (fcp->dbg_tick != debug_tick) { - fcp->breakpoint = dbg_find_breakpoint(FALSE, fp->uf_name, - sourcing_lnum); + fcp->breakpoint = dbg_find_breakpoint(false, (char *)fp->uf_name, SOURCING_LNUM); fcp->dbg_tick = debug_tick; } if (do_profiling == PROF_YES) { @@ -3160,14 +3153,14 @@ char *get_func_line(int c, void *cookie, int indent, bool do_concat) } else { // Skip NULL lines (continuation lines). while (fcp->linenr < gap->ga_len - && ((char_u **)(gap->ga_data))[fcp->linenr] == NULL) { + && ((char **)(gap->ga_data))[fcp->linenr] == NULL) { fcp->linenr++; } if (fcp->linenr >= gap->ga_len) { retval = NULL; } else { - retval = vim_strsave(((char_u **)(gap->ga_data))[fcp->linenr++]); - sourcing_lnum = fcp->linenr; + retval = xstrdup(((char **)(gap->ga_data))[fcp->linenr++]); + SOURCING_LNUM = fcp->linenr; if (do_profiling == PROF_YES) { func_line_start(cookie); } @@ -3175,18 +3168,17 @@ char *get_func_line(int c, void *cookie, int indent, bool do_concat) } // Did we encounter a breakpoint? - if (fcp->breakpoint != 0 && fcp->breakpoint <= sourcing_lnum) { - dbg_breakpoint(fp->uf_name, sourcing_lnum); + if (fcp->breakpoint != 0 && fcp->breakpoint <= SOURCING_LNUM) { + dbg_breakpoint((char *)fp->uf_name, SOURCING_LNUM); // Find next breakpoint. - fcp->breakpoint = dbg_find_breakpoint(false, fp->uf_name, - sourcing_lnum); + fcp->breakpoint = dbg_find_breakpoint(false, (char *)fp->uf_name, SOURCING_LNUM); fcp->dbg_tick = debug_tick; } - return (char *)retval; + return retval; } -/// @return TRUE if the currently active function should be ended, because a +/// @return true if the currently active function should be ended, because a /// return was encountered or an error occurred. Used inside a ":while". int func_has_ended(void *cookie) { @@ -3198,7 +3190,7 @@ int func_has_ended(void *cookie) || fcp->returned; } -/// @return TRUE if cookie indicates a function which "abort"s on errors. +/// @return true if cookie indicates a function which "abort"s on errors. int func_has_abort(void *cookie) { return ((funccall_T *)cookie)->func->uf_flags & FC_ABORT; @@ -3289,7 +3281,7 @@ int func_level(void *cookie) return ((funccall_T *)cookie)->level; } -/// @return TRUE when a function was ended by a ":return" command. +/// @return true when a function was ended by a ":return" command. int current_func_returned(void) { return current_funccal->returned; diff --git a/src/nvim/eval/userfunc.h b/src/nvim/eval/userfunc.h index ed86aaad4a..4b7007aae9 100644 --- a/src/nvim/eval/userfunc.h +++ b/src/nvim/eval/userfunc.h @@ -4,6 +4,11 @@ #include "nvim/eval/typval.h" #include "nvim/ex_cmds_defs.h" +// From user function to hashitem and back. +#define UF2HIKEY(fp) ((fp)->uf_name) +#define HIKEY2UF(p) ((ufunc_T *)(p - offsetof(ufunc_T, uf_name))) +#define HI2UF(hi) HIKEY2UF((hi)->hi_key) + ///< Structure used by trans_function_name() typedef struct { dict_T *fd_dict; ///< Dictionary used. @@ -59,8 +64,8 @@ typedef struct { .basetv = NULL, \ } -#define FUNCARG(fp, j) ((char_u **)(fp->uf_args.ga_data))[j] -#define FUNCLINE(fp, j) ((char_u **)(fp->uf_lines.ga_data))[j] +#define FUNCARG(fp, j) ((char **)(fp->uf_args.ga_data))[j] +#define FUNCLINE(fp, j) ((char **)(fp->uf_lines.ga_data))[j] #ifdef INCLUDE_GENERATED_DECLARATIONS # include "eval/userfunc.h.generated.h" diff --git a/src/nvim/eval/vars.c b/src/nvim/eval/vars.c index cf2755f639..75410d40d8 100644 --- a/src/nvim/eval/vars.c +++ b/src/nvim/eval/vars.c @@ -7,6 +7,7 @@ #include "nvim/autocmd.h" #include "nvim/buffer.h" #include "nvim/charset.h" +#include "nvim/drawscreen.h" #include "nvim/eval.h" #include "nvim/eval/encode.h" #include "nvim/eval/funcs.h" @@ -15,8 +16,10 @@ #include "nvim/eval/vars.h" #include "nvim/ex_cmds.h" #include "nvim/ex_docmd.h" +#include "nvim/ex_eval.h" #include "nvim/ops.h" #include "nvim/option.h" +#include "nvim/screen.h" #include "nvim/search.h" #include "nvim/window.h" @@ -78,7 +81,7 @@ static list_T *heredoc_get(exarg_T *eap, char *cmd) // The marker is the next word. if (*cmd != NUL && *cmd != '"') { marker = skipwhite(cmd); - p = (char *)skiptowhite((char_u *)marker); + p = skiptowhite(marker); if (*skipwhite(p) != NUL && *skipwhite(p) != '"') { semsg(_(e_trailing_arg), p); return NULL; @@ -410,7 +413,7 @@ void list_hashtable_vars(hashtab_T *ht, const char *prefix, int empty, int *firs // apply :filter /pat/ to variable name xstrlcpy(buf, prefix, IOSIZE); xstrlcat(buf, (char *)di->di_key, IOSIZE); - if (message_filtered((char_u *)buf)) { + if (message_filtered(buf)) { continue; } @@ -584,21 +587,13 @@ static char *ex_let_one(char *arg, typval_T *const tv, const bool copy, const bo char *s = vim_getenv(name); if (s != NULL) { - tofree = (char *)concat_str((const char_u *)s, (const char_u *)p); + tofree = concat_str(s, p); p = (const char *)tofree; xfree(s); } } if (p != NULL) { - os_setenv(name, p, 1); - if (STRICMP(name, "HOME") == 0) { - init_homedir(); - } else if (didset_vim && STRICMP(name, "VIM") == 0) { - didset_vim = false; - } else if (didset_vimruntime - && STRICMP(name, "VIMRUNTIME") == 0) { - didset_vimruntime = false; - } + vim_setenv_ext(name, p); arg_end = arg; } name[len] = c1; @@ -668,8 +663,7 @@ static char *ex_let_one(char *arg, typval_T *const tv, const bool copy, const bo } else if (opt_type == gov_string && stringval != NULL && s != NULL) { // string char *const oldstringval = stringval; - stringval = (char *)concat_str((const char_u *)stringval, - (const char_u *)s); + stringval = concat_str(stringval, s); xfree(oldstringval); s = stringval; } @@ -710,7 +704,7 @@ static char *ex_let_one(char *arg, typval_T *const tv, const bool copy, const bo if (p != NULL && op != NULL && *op == '.') { s = get_reg_contents(*arg == '@' ? '"' : *arg, kGRegExprSrc); if (s != NULL) { - ptofree = (char *)concat_str((char_u *)s, (const char_u *)p); + ptofree = concat_str(s, p); p = (const char *)ptofree; xfree(s); } @@ -796,6 +790,7 @@ static void ex_unletlock(exarg_T *eap, char *argstart, int deep, ex_unletlock_ca semsg(_(e_invarg2), arg - 1); return; } + assert(*lv.ll_name == '$'); // suppress clang "Uninitialized argument value" if (!error && !eap->skip && callback(&lv, arg, eap, deep) == FAIL) { error = true; } @@ -856,7 +851,7 @@ static int do_unlet_var(lval_T *lp, char *name_end, exarg_T *eap, int deep FUNC_ // Environment variable, normal name or expanded name. if (*lp->ll_name == '$') { - os_unsetenv(lp->ll_name + 1); + vim_unsetenv_ext(lp->ll_name + 1); } else if (do_unlet(lp->ll_name, lp->ll_name_len, forceit) == FAIL) { ret = FAIL; } @@ -1123,7 +1118,7 @@ void vars_clear(hashtab_T *ht) vars_clear_ext(ht, true); } -/// Like vars_clear(), but only free the value if "free_val" is TRUE. +/// Like vars_clear(), but only free the value if "free_val" is true. void vars_clear_ext(hashtab_T *ht, int free_val) { int todo; @@ -1305,7 +1300,7 @@ void set_var_const(const char *name, const size_t name_len, typval_T *const tv, set_search_direction(v->di_tv.vval.v_number ? '/' : '?'); } else if (strcmp(varname, "hlsearch") == 0) { no_hlsearch = !v->di_tv.vval.v_number; - redraw_all_later(SOME_VALID); + redraw_all_later(UPD_SOME_VALID); } return; } else if (v->di_tv.v_type != tv->v_type) { @@ -1632,7 +1627,7 @@ static void set_option_from_tv(const char *varname, typval_T *varp) strval = tv_get_string_buf_chk(varp, nbuf); } if (!error && strval != NULL) { - set_option_value(varname, numval, strval, OPT_LOCAL); + set_option_value_give_err(varname, numval, strval, OPT_LOCAL); } } @@ -1707,7 +1702,7 @@ bool var_exists(const char *var) } /// "gettabvar()" function -void f_gettabvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_gettabvar(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const char *const varname = tv_get_string_chk(&argvars[1]); tabpage_T *const tp = find_tabpage((int)tv_get_number_chk(&argvars[0], NULL)); @@ -1721,19 +1716,19 @@ void f_gettabvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "gettabwinvar()" function -void f_gettabwinvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_gettabwinvar(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { getwinvar(argvars, rettv, 1); } /// "getwinvar()" function -void f_getwinvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_getwinvar(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { getwinvar(argvars, rettv, 0); } /// "getbufvar()" function -void f_getbufvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_getbufvar(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const char *const varname = tv_get_string_chk(&argvars[1]); buf_T *const buf = tv_get_buf_from_arg(&argvars[0]); @@ -1742,7 +1737,7 @@ void f_getbufvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "settabvar()" function -void f_settabvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_settabvar(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = 0; @@ -1773,19 +1768,19 @@ void f_settabvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "settabwinvar()" function -void f_settabwinvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_settabwinvar(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { setwinvar(argvars, rettv, 1); } /// "setwinvar()" function -void f_setwinvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_setwinvar(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { setwinvar(argvars, rettv, 0); } /// "setbufvar()" function -void f_setbufvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_setbufvar(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (check_secure() || !tv_check_str_or_nr(&argvars[0])) { diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 9d1ac185d4..e672b80d69 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -15,15 +15,18 @@ #include "nvim/api/buffer.h" #include "nvim/api/private/defs.h" +#include "nvim/arglist.h" #include "nvim/ascii.h" #include "nvim/buffer.h" #include "nvim/buffer_updates.h" #include "nvim/change.h" #include "nvim/charset.h" +#include "nvim/cmdhist.h" #include "nvim/cursor.h" #include "nvim/decoration.h" #include "nvim/diff.h" #include "nvim/digraph.h" +#include "nvim/drawscreen.h" #include "nvim/edit.h" #include "nvim/eval.h" #include "nvim/ex_cmds.h" @@ -36,6 +39,7 @@ #include "nvim/fold.h" #include "nvim/garray.h" #include "nvim/getchar.h" +#include "nvim/help.h" #include "nvim/highlight.h" #include "nvim/highlight_group.h" #include "nvim/indent.h" @@ -52,6 +56,7 @@ #include "nvim/normal.h" #include "nvim/ops.h" #include "nvim/option.h" +#include "nvim/optionstr.h" #include "nvim/os/input.h" #include "nvim/os/os.h" #include "nvim/os/shell.h" @@ -59,9 +64,9 @@ #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" @@ -115,7 +120,7 @@ void do_ascii(const exarg_T *const eap) { char *dig; int cc[MAX_MCO]; - int c = utfc_ptr2char(get_cursor_pos_ptr(), cc); + int c = utfc_ptr2char((char *)get_cursor_pos_ptr(), cc); if (c == NUL) { msg("NUL"); return; @@ -269,7 +274,7 @@ void ex_align(exarg_T *eap) if (eap->cmdidx == CMD_left) { // left align new_indent = indent; } else { - has_tab = FALSE; // avoid uninit warnings + has_tab = false; // avoid uninit warnings len = linelen(eap->cmdidx == CMD_right ? &has_tab : NULL) - get_indent(); @@ -300,7 +305,7 @@ void ex_align(exarg_T *eap) new_indent--; break; } - --new_indent; + new_indent--; } } } @@ -399,7 +404,7 @@ static int sort_compare(const void *s1, const void *s2) } fast_breakcheck(); if (got_int) { - sort_abort = TRUE; + sort_abort = true; } // When sorting numbers "start_col_nr" is the number, not the column @@ -508,7 +513,7 @@ void ex_sort(exarg_T *eap) eap->nextcmd = (char *)check_nextcmd((char_u *)p); break; } else if (!ASCII_ISALPHA(*p) && regmatch.regprog == NULL) { - s = (char *)skip_regexp((char_u *)p + 1, *p, true, NULL); + s = skip_regexp(p + 1, *p, true, NULL); if (*s != *p) { emsg(_(e_invalpat)); goto sortend; @@ -581,11 +586,11 @@ void ex_sort(exarg_T *eap) p = s + start_col; if (sort_nr) { if (sort_what & STR2NR_HEX) { - s = (char *)skiptohex((char_u *)p); + s = skiptohex(p); } else if (sort_what & STR2NR_BIN) { s = (char *)skiptobin(p); } else { - s = (char *)skiptodigit((char_u *)p); + s = skiptodigit(p); } if (s > p && s[-1] == '-') { s--; // include preceding negative sign @@ -673,7 +678,7 @@ void ex_sort(exarg_T *eap) // delete the original lines if appending worked if (i == count) { - for (i = 0; i < count; ++i) { + for (i = 0; i < count; i++) { ml_delete(eap->line1, false); } } else { @@ -738,7 +743,7 @@ void ex_retab(exarg_T *eap) curwin->w_p_list = 0; // don't want list mode here new_ts_str = eap->arg; - if (!tabstop_set((char_u *)eap->arg, &new_vts_array)) { + if (!tabstop_set(eap->arg, &new_vts_array)) { return; } while (ascii_isdigit(*(eap->arg)) || *(eap->arg) == ',') { @@ -836,7 +841,7 @@ void ex_retab(exarg_T *eap) if (ptr[col] == NUL) { break; } - vcol += win_chartabsize(curwin, (char_u *)ptr + col, (colnr_T)vcol); + vcol += win_chartabsize(curwin, ptr + col, (colnr_T)vcol); if (vcol >= MAXCOL) { emsg(_(e_resulting_text_too_long)); break; @@ -862,7 +867,7 @@ void ex_retab(exarg_T *eap) && tabstop_eq(curbuf->b_p_vts_array, new_vts_array)) { // not changed } else { - redraw_curbuf_later(NOT_VALID); + redraw_curbuf_later(UPD_NOT_VALID); } if (first_line != 0) { changed_lines(first_line, 0, last_line + 1, 0L, true); @@ -1091,14 +1096,14 @@ void ex_copy(linenr_T line1, linenr_T line2, linenr_T n) if (line1 == n) { line1 = curwin->w_cursor.lnum; } - ++line1; + line1++; if (curwin->w_cursor.lnum < line1) { - ++line1; + line1++; } if (curwin->w_cursor.lnum < line2) { - ++line2; + line2++; } - ++curwin->w_cursor.lnum; + curwin->w_cursor.lnum++; } appended_lines_mark(n, count); @@ -1145,7 +1150,7 @@ void do_bang(int addr_count, exarg_T *eap, bool forceit, bool do_in, bool do_out } if (addr_count == 0) { // :! - msg_scroll = FALSE; // don't scroll here + msg_scroll = false; // don't scroll here autowrite_all(); msg_scroll = scroll_save; } @@ -1198,7 +1203,7 @@ void do_bang(int addr_count, exarg_T *eap, bool forceit, bool do_in, bool do_out break; } } - ++p; + p++; } } while (trailarg != NULL); @@ -1342,7 +1347,8 @@ static void do_filter(linenr_T line1, linenr_T line2, exarg_T *eap, char *cmd, b msg_putchar('\n'); // Keep message from buf_write(). no_wait_return--; if (!aborting()) { - semsg(_("E482: Can't create file %s"), itmp); // Will call wait_return. + // will call wait_return() + semsg(_("E482: Can't create file %s"), itmp); } goto filterend; } @@ -1363,7 +1369,7 @@ static void do_filter(linenr_T line1, linenr_T line2, exarg_T *eap, char *cmd, b xfree(cmd_buf); goto error; } - redraw_curbuf_later(VALID); + redraw_curbuf_later(UPD_VALID); } read_linecount = curbuf->b_ml.ml_line_count; @@ -1371,14 +1377,14 @@ static void do_filter(linenr_T line1, linenr_T line2, exarg_T *eap, char *cmd, b call_shell((char_u *)cmd_buf, (ShellOpts)(kShellOptFilter | shell_flags), NULL); xfree(cmd_buf); - did_check_timestamps = FALSE; - need_check_timestamps = TRUE; + did_check_timestamps = false; + need_check_timestamps = true; // When interrupting the shell command, it may still have produced some // useful output. Reset got_int here, so that readfile() won't cancel // reading. os_breakcheck(); - got_int = FALSE; + got_int = false; if (do_out) { if (otmp != NULL) { @@ -1442,7 +1448,7 @@ static void do_filter(linenr_T line1, linenr_T line2, exarg_T *eap, char *cmd, b } beginline(BL_WHITE | BL_FIX); // cursor on first non-blank - --no_wait_return; + no_wait_return--; if (linecount > p_report) { if (do_in) { @@ -1460,8 +1466,8 @@ static void do_filter(linenr_T line1, linenr_T line2, exarg_T *eap, char *cmd, b error: // put cursor back in same position for ":w !cmd" curwin->w_cursor = cursor_save; - --no_wait_return; - wait_return(FALSE); + no_wait_return--; + wait_return(false); } filterend: @@ -1651,7 +1657,7 @@ char *make_filter_cmd(char *cmd, char *itmp, char *otmp) } #endif if (otmp != NULL) { - append_redir(buf, len, (char *)p_srr, otmp); + append_redir(buf, len, p_srr, otmp); } return buf; } @@ -1704,12 +1710,12 @@ void print_line(linenr_T lnum, int use_number, int list) int save_silent = silent_mode; // apply :filter /pat/ - if (message_filtered(ml_get(lnum))) { + if (message_filtered((char *)ml_get(lnum))) { return; } msg_start(); - silent_mode = FALSE; + silent_mode = false; info_message = true; // use mch_msg(), not mch_errmsg() print_line_no_prefix(lnum, use_number, list); if (save_silent) { @@ -1817,7 +1823,7 @@ void ex_write(exarg_T *eap) } /// write current buffer to file 'eap->arg' -/// if 'eap->append' is TRUE, append to the file +/// if 'eap->append' is true, append to the file /// /// if *eap->arg == NUL write to current file /// @@ -1842,7 +1848,7 @@ int do_write(exarg_T *eap) emsg(_(e_argreq)); goto theend; } - other = FALSE; + other = false; } else { fname = ffname; free_fname = fix_fname(ffname); @@ -1875,8 +1881,7 @@ 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 || check_readonly(&eap->forceit, curbuf))) { goto theend; } @@ -1895,7 +1900,7 @@ int do_write(exarg_T *eap) (char_u *)_("Write partial file?"), 2) != VIM_YES) { goto theend; } - eap->forceit = TRUE; + eap->forceit = true; } else { emsg(_("E140: Use ! to write partial buffer")); goto theend; @@ -1932,8 +1937,8 @@ int do_write(exarg_T *eap) apply_autocmds(EVENT_BUFFILEPOST, NULL, NULL, false, curbuf); apply_autocmds(EVENT_BUFFILEPOST, NULL, NULL, false, alt_buf); if (!alt_buf->b_p_bl) { - alt_buf->b_p_bl = TRUE; - apply_autocmds(EVENT_BUFADD, NULL, NULL, FALSE, alt_buf); + alt_buf->b_p_bl = true; + apply_autocmds(EVENT_BUFADD, NULL, NULL, false, alt_buf); } if (curbuf != was_curbuf || aborting()) { // buffer changed, don't write the file @@ -1961,7 +1966,7 @@ int do_write(exarg_T *eap) // After ":saveas fname" reset 'readonly'. if (eap->cmdidx == CMD_saveas) { if (retval == OK) { - curbuf->b_p_ro = FALSE; + curbuf->b_p_ro = false; redraw_tabline = true; } } @@ -1991,17 +1996,22 @@ int check_overwrite(exarg_T *eap, buf_T *buf, char *fname, char *ffname, int oth { // Write to another file or b_flags set or not writing the whole file: // overwriting only allowed with '!' + // If "other" is false and bt_nofilename(buf) is true, this must be + // writing an "acwrite" buffer to the same file as its b_ffname, and + // buf_write() will only allow writing with BufWriteCmd autocommands, + // so there is no need for an overwrite check. if ((other - || (buf->b_flags & BF_NOTEDITED) - || ((buf->b_flags & BF_NEW) - && vim_strchr(p_cpo, CPO_OVERNEW) == NULL) - || (buf->b_flags & BF_READERR)) + || (!bt_nofilename(buf) + && ((buf->b_flags & BF_NOTEDITED) + || ((buf->b_flags & BF_NEW) + && vim_strchr(p_cpo, CPO_OVERNEW) == NULL) + || (buf->b_flags & BF_READERR)))) && !p_wa && os_path_exists((char_u *)ffname)) { if (!eap->forceit && !eap->append) { #ifdef UNIX // It is possible to open a directory on Unix. - if (os_isdir((char_u *)ffname)) { + if (os_isdir(ffname)) { semsg(_(e_isadir2), ffname); return FAIL; } @@ -2013,7 +2023,7 @@ int check_overwrite(exarg_T *eap, buf_T *buf, char *fname, char *ffname, int oth if (vim_dialog_yesno(VIM_QUESTION, NULL, (char_u *)buff, 2) != VIM_YES) { return FAIL; } - eap->forceit = TRUE; + eap->forceit = true; } else { emsg(_(e_exists)); return FAIL; @@ -2036,7 +2046,7 @@ int check_overwrite(exarg_T *eap, buf_T *buf, char *fname, char *ffname, int oth STRCPY(dir, "."); } else { dir = xmalloc(MAXPATHL); - p = (char *)p_dir; + p = p_dir; copy_option_part(&p, dir, MAXPATHL, ","); } swapname = (char *)makeswapname((char_u *)fname, (char_u *)ffname, curbuf, (char_u *)dir); @@ -2053,7 +2063,7 @@ int check_overwrite(exarg_T *eap, buf_T *buf, char *fname, char *ffname, int oth xfree(swapname); return FAIL; } - eap->forceit = TRUE; + eap->forceit = true; } else { semsg(_("E768: Swap file exists: %s (:silent! overrides)"), swapname); @@ -2111,7 +2121,7 @@ void do_wqall(exarg_T *eap) * 4. if overwriting is allowed (even after a dialog) */ if (not_writing()) { - ++error; + error++; break; } if (buf->b_ffname == NULL) { @@ -2154,7 +2164,7 @@ bool not_writing(void) } /// Check if a buffer is read-only (either 'readonly' option is set or file is -/// read-only). Ask for overruling in a dialog. Return TRUE and give an error +/// read-only). Ask for overruling in a dialog. Return true and give an error /// message when the buffer is readonly. static int check_readonly(int *forceit, buf_T *buf) { @@ -2179,10 +2189,10 @@ static int check_readonly(int *forceit, buf_T *buf) if (vim_dialog_yesno(VIM_QUESTION, NULL, (char_u *)buff, 2) == VIM_YES) { // Set forceit, to force the writing of a readonly file - *forceit = TRUE; - return FALSE; + *forceit = true; + return false; } else { - return TRUE; + return true; } } else if (buf->b_p_ro) { emsg(_(e_readonly)); @@ -2190,10 +2200,10 @@ static int check_readonly(int *forceit, buf_T *buf) semsg(_("E505: \"%s\" is read-only (add ! to override)"), buf->b_fname); } - return TRUE; + return true; } - return FALSE; + return false; } /// Try to abandon the current file and edit a new or existing file. @@ -2246,7 +2256,7 @@ int getfile(int fnum, char *ffname_arg, char *sfname_arg, int setpm, linenr_T ln } } if (other) { - --no_wait_return; + no_wait_return--; } if (setpm) { setpcmark(); @@ -2287,7 +2297,7 @@ theend: /// ECMD_LASTL: use last position in loaded file /// ECMD_LAST: use last position in all files /// ECMD_ONE: use first line -/// @param flags ECMD_HIDE: if TRUE don't free the current buffer +/// @param flags ECMD_HIDE: if true don't free the current buffer /// ECMD_SET_HELP: set b_help flag of (new) buffer before /// opening file /// ECMD_OLDBUF: use existing buffer if it exists @@ -2305,7 +2315,7 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum win_T *oldwin) { bool other_file; // true if editing another file - int oldbuf; // TRUE if using existing buffer + int oldbuf; // true if using existing buffer bool auto_buf = false; // true if autocommands brought us // into the buffer unexpectedly char *new_name = NULL; @@ -2381,7 +2391,7 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum // If the file was changed we may not be allowed to abandon it: // - if we are going to re-edit the same file - // - or if we are the only window on this file and if ECMD_HIDE is FALSE + // - or if we are the only window on this file and if ECMD_HIDE is false if (((!other_file && !(flags & ECMD_OLDBUF)) || (curbuf->b_nwindows == 1 && !(flags & (ECMD_HIDE | ECMD_ADDBUF | ECMD_ALTBUF)))) @@ -2499,7 +2509,7 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum /* * Make the (new) buffer the one used by the current window. - * If the old buffer becomes unused, free it if ECMD_HIDE is FALSE. + * If the old buffer becomes unused, free it if ECMD_HIDE is false. * If the current buffer was empty and has no file name, curbuf * is returned by buflist_new(), nothing to do here. */ @@ -2596,11 +2606,11 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum curwin->w_buffer = buf; curbuf = buf; - ++curbuf->b_nwindows; + curbuf->b_nwindows++; // Set 'fileformat', 'binary' and 'fenc' when forced. if (!oldbuf && eap != NULL) { - set_file_options(TRUE, eap); + set_file_options(true, eap); set_forced_fenc(eap); } } @@ -2636,7 +2646,7 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum } else if (!curbuf->b_help) { // Don't make a buffer listed if it's a help buffer. Useful when using // CTRL-O to go back to a help file. - set_buflisted(TRUE); + set_buflisted(true); } // If autocommands change buffers under our fingers, forget about @@ -2655,10 +2665,10 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum /* * other_file oldbuf - * FALSE FALSE re-edit same file, buffer is re-used - * FALSE TRUE re-edit same file, nothing changes - * TRUE FALSE start editing new file, new buffer - * TRUE TRUE start editing in existing buffer (nothing to do) + * false false re-edit same file, buffer is re-used + * false true re-edit same file, nothing changes + * true false start editing new file, new buffer + * true true start editing in existing buffer (nothing to do) */ if (!other_file && !oldbuf) { // re-use the buffer set_last_cursor(curwin); // may set b_last_cursor @@ -2785,7 +2795,7 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum // changed by the user. do_modelines(OPT_WINONLY); - apply_autocmds_retval(EVENT_BUFENTER, NULL, NULL, FALSE, curbuf, + apply_autocmds_retval(EVENT_BUFENTER, NULL, NULL, false, curbuf, &retval); if ((flags & ECMD_NOWINENTER) == 0) { apply_autocmds_retval(EVENT_BUFWINENTER, NULL, NULL, false, curbuf, @@ -2843,7 +2853,7 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum curwin->w_cursor.col = solcol; check_cursor_col(); curwin->w_cursor.coladd = 0; - curwin->w_set_curswant = TRUE; + curwin->w_set_curswant = true; } else { beginline(BL_SOL | BL_FIX); } @@ -2869,7 +2879,7 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum // Obey the 'O' flag in 'cpoptions': overwrite any previous file // message. if (shortmess(SHM_OVERALL) && !exiting && p_verbose == 0) { - msg_scroll = FALSE; + msg_scroll = false; } if (!msg_scroll) { // wait a bit when overwriting an error msg check_for_delay(false); @@ -2905,7 +2915,7 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum update_topline(curwin); curwin->w_scbind_pos = curwin->w_topline; *so_ptr = n; - redraw_curbuf_later(NOT_VALID); // redraw this buffer later + redraw_curbuf_later(UPD_NOT_VALID); // redraw this buffer later } // Change directories when the 'acd' option is set. @@ -2959,7 +2969,7 @@ void ex_append(exarg_T *eap) } if (eap->cmdidx != CMD_append) { - --lnum; + lnum--; } // when the buffer is empty need to delete the dummy line @@ -3013,9 +3023,9 @@ void ex_append(exarg_T *eap) // Look for the "." after automatic indent. vcol = 0; - for (p = theline; indent > vcol; ++p) { + for (p = theline; indent > vcol; p++) { if (*p == ' ') { - ++vcol; + vcol++; } else if (*p == TAB) { vcol += 8 - vcol % 8; } else { @@ -3044,7 +3054,7 @@ void ex_append(exarg_T *eap) } xfree(theline); - ++lnum; + lnum++; if (empty) { ml_delete(2L, false); @@ -3093,7 +3103,7 @@ void ex_change(exarg_T *eap) append_indent = get_indent_lnum(eap->line1); } - for (lnum = eap->line2; lnum >= eap->line1; --lnum) { + for (lnum = eap->line2; lnum >= eap->line1; lnum--) { if (curbuf->b_ml.ml_flags & ML_EMPTY) { // nothing to delete break; } @@ -3136,10 +3146,10 @@ void ex_z(exarg_T *eap) kind = x; if (*kind == '-' || *kind == '+' || *kind == '=' || *kind == '^' || *kind == '.') { - ++x; + x++; } while (*x == '-' || *x == '+') { - ++x; + x++; } if (*x != 0) { @@ -3196,7 +3206,7 @@ void ex_z(exarg_T *eap) if (*kind == '+') { start += (linenr_T)bigness * (linenr_T)(x - kind - 1) + 1; } else if (eap->addr_count == 0) { - ++start; + start++; } end = start + (linenr_T)bigness - 1; curs = end; @@ -3343,6 +3353,7 @@ static bool sub_joining_lines(exarg_T *eap, char *pat, char *sub, char *cmd, boo if ((cmdmod.cmod_flags & CMOD_KEEPPATTERNS) == 0) { save_re_pat(RE_SUBST, (char_u *)pat, p_magic); } + // put pattern in history add_to_history(HIST_SEARCH, (char_u *)pat, true, NUL); } @@ -3541,7 +3552,7 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T which_pat = RE_LAST; // use last used regexp delimiter = (char_u)(*cmd++); // remember delimiter character pat = cmd; // remember start of search pat - cmd = (char *)skip_regexp((char_u *)cmd, delimiter, p_magic, (char_u **)&eap->arg); + cmd = skip_regexp(cmd, delimiter, p_magic, &eap->arg); if (cmd[0] == delimiter) { // end delimiter found *cmd++ = NUL; // replace it with a NUL has_second_delim = true; @@ -3798,7 +3809,7 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T // Save the line number of the last change for the final // cursor position (just like Vi). curwin->w_cursor.lnum = lnum; - do_again = FALSE; + do_again = false; /* * 1. Match empty string does not count, except for first @@ -3934,8 +3945,7 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T // what matches. Temporarily replace the line // and change it back afterwards. orig_line = (char *)vim_strsave(ml_get(lnum)); - char *new_line = (char *)concat_str((char_u *)new_start, - (char_u *)sub_firstline + copycol); + char *new_line = concat_str(new_start, sub_firstline + copycol); // Position the cursor relative to the end of the line, the // previous substitute may have inserted or deleted characters @@ -3949,13 +3959,13 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T - regmatch.startpos[0].lnum; search_match_endcol = regmatch.endpos[0].col + len_change; - highlight_match = TRUE; + highlight_match = true; update_topline(curwin); validate_cursor(); - update_screen(SOME_VALID); + update_screen(UPD_SOME_VALID); highlight_match = false; - redraw_later(curwin, SOME_VALID); + redraw_later(curwin, UPD_SOME_VALID); curwin->w_p_fen = save_p_fen; if (msg_row == Rows - 1) { @@ -3971,7 +3981,7 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T _("replace with %s (y/n/a/q/l/^E/^Y)?"), sub); msg_no_more = false; msg_scroll = (int)i; - showruler(true); + show_cursor_info(true); ui_cursor_goto(msg_row, msg_col); RedrawingDisabled = temp; @@ -4296,7 +4306,7 @@ skip: * has been appended to new_start, we don't need * it in the buffer. */ - ++lnum; + lnum++; if (u_savedel(lnum, nmatch_tl) != OK) { break; } @@ -4626,7 +4636,7 @@ void ex_global(exarg_T *eap) delim = *cmd; // get the delimiter cmd++; // skip delimiter if there is one pat = cmd; // remember start of pattern - cmd = (char *)skip_regexp((char_u *)cmd, delim, p_magic, (char_u **)&eap->arg); + cmd = skip_regexp(cmd, delim, p_magic, &eap->arg); if (cmd[0] == delim) { // end delimiter found *cmd++ = NUL; // replace it with a NUL } @@ -4762,10 +4772,9 @@ bool prepare_tagpreview(bool undo_sync) == 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_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); @@ -4775,1124 +4784,6 @@ bool prepare_tagpreview(bool undo_sync) return false; } -/// ":help": open a read-only window on a help file -void ex_help(exarg_T *eap) -{ - char *arg; - char *tag; - FILE *helpfd; // file descriptor of help file - int n; - int i; - win_T *wp; - int num_matches; - char **matches; - char *p; - int empty_fnum = 0; - int alt_fnum = 0; - buf_T *buf; - int len; - char *lang; - const bool old_KeyTyped = KeyTyped; - - if (eap != NULL) { - /* - * A ":help" command ends at the first LF, or at a '|' that is - * followed by some text. Set nextcmd to the following command. - */ - for (arg = eap->arg; *arg; arg++) { - if (*arg == '\n' || *arg == '\r' - || (*arg == '|' && arg[1] != NUL && arg[1] != '|')) { - *arg++ = NUL; - eap->nextcmd = arg; - break; - } - } - arg = eap->arg; - - if (eap->forceit && *arg == NUL && !curbuf->b_help) { - emsg(_("E478: Don't panic!")); - return; - } - - if (eap->skip) { // not executing commands - return; - } - } else { - arg = ""; - } - - // remove trailing blanks - p = arg + STRLEN(arg) - 1; - while (p > arg && ascii_iswhite(*p) && p[-1] != '\\') { - *p-- = NUL; - } - - // Check for a specified language - lang = check_help_lang(arg); - - // When no argument given go to the index. - if (*arg == NUL) { - arg = "help.txt"; - } - - /* - * Check if there is a match for the argument. - */ - n = find_help_tags(arg, &num_matches, &matches, eap != NULL && eap->forceit); - - i = 0; - if (n != FAIL && lang != NULL) { - // Find first item with the requested language. - for (i = 0; i < num_matches; ++i) { - len = (int)STRLEN(matches[i]); - if (len > 3 && matches[i][len - 3] == '@' - && STRICMP(matches[i] + len - 2, lang) == 0) { - break; - } - } - } - if (i >= num_matches || n == FAIL) { - if (lang != NULL) { - semsg(_("E661: Sorry, no '%s' help for %s"), lang, arg); - } else { - semsg(_("E149: Sorry, no help for %s"), arg); - } - if (n != FAIL) { - FreeWild(num_matches, matches); - } - return; - } - - // The first match (in the requested language) is the best match. - tag = xstrdup(matches[i]); - FreeWild(num_matches, matches); - - /* - * Re-use an existing help window or open a new one. - * Always open a new one for ":tab help". - */ - if (!bt_help(curwin->w_buffer) || cmdmod.cmod_tab != 0) { - if (cmdmod.cmod_tab != 0) { - wp = NULL; - } else { - wp = NULL; - FOR_ALL_WINDOWS_IN_TAB(wp2, curtab) { - if (bt_help(wp2->w_buffer)) { - wp = wp2; - break; - } - } - } - if (wp != NULL && wp->w_buffer->b_nwindows > 0) { - win_enter(wp, true); - } else { - // There is no help window yet. - // Try to open the file specified by the "helpfile" option. - if ((helpfd = os_fopen((char *)p_hf, READBIN)) == NULL) { - smsg(_("Sorry, help file \"%s\" not found"), p_hf); - goto erret; - } - fclose(helpfd); - - // Split off help window; put it at far top if no position - // specified, the current window is vertically split and - // narrow. - n = WSP_HELP; - if (cmdmod.cmod_split == 0 && curwin->w_width != Columns - && curwin->w_width < 80) { - n |= WSP_TOP; - } - if (win_split(0, n) == FAIL) { - goto erret; - } - - if (curwin->w_height < p_hh) { - win_setheight((int)p_hh); - } - - /* - * Open help file (do_ecmd() will set b_help flag, readfile() will - * set b_p_ro flag). - * Set the alternate file to the previously edited file. - */ - alt_fnum = curbuf->b_fnum; - (void)do_ecmd(0, NULL, NULL, NULL, ECMD_LASTL, - ECMD_HIDE + ECMD_SET_HELP, - NULL); // buffer is still open, don't store info - - if ((cmdmod.cmod_flags & CMOD_KEEPALT) == 0) { - curwin->w_alt_fnum = alt_fnum; - } - empty_fnum = curbuf->b_fnum; - } - } - - restart_edit = 0; // don't want insert mode in help file - - // Restore KeyTyped, setting 'filetype=help' may reset it. - // It is needed for do_tag top open folds under the cursor. - KeyTyped = old_KeyTyped; - - do_tag((char_u *)tag, DT_HELP, 1, false, true); - - // Delete the empty buffer if we're not using it. Careful: autocommands - // may have jumped to another window, check that the buffer is not in a - // window. - if (empty_fnum != 0 && curbuf->b_fnum != empty_fnum) { - buf = buflist_findnr(empty_fnum); - if (buf != NULL && buf->b_nwindows == 0) { - wipe_buffer(buf, true); - } - } - - // keep the previous alternate file - if (alt_fnum != 0 && curwin->w_alt_fnum == empty_fnum - && (cmdmod.cmod_flags & CMOD_KEEPALT) == 0) { - curwin->w_alt_fnum = alt_fnum; - } - -erret: - xfree(tag); -} - -/// In an argument search for a language specifiers in the form "@xx". -/// Changes the "@" to NUL if found, and returns a pointer to "xx". -/// -/// @return NULL if not found. -char *check_help_lang(char *arg) -{ - int len = (int)STRLEN(arg); - - if (len >= 3 && arg[len - 3] == '@' && ASCII_ISALPHA(arg[len - 2]) - && ASCII_ISALPHA(arg[len - 1])) { - arg[len - 3] = NUL; // remove the '@' - return arg + len - 2; - } - return NULL; -} - -/// Return a heuristic indicating how well the given string matches. The -/// smaller the number, the better the match. This is the order of priorities, -/// from best match to worst match: -/// - Match with least alphanumeric characters is better. -/// - Match with least total characters is better. -/// - Match towards the start is better. -/// - Match starting with "+" is worse (feature instead of command) -/// Assumption is made that the matched_string passed has already been found to -/// match some string for which help is requested. webb. -/// -/// @param offset offset for match -/// @param wrong_case no matching case -/// -/// @return a heuristic indicating how well the given string matches. -int help_heuristic(char *matched_string, int offset, int wrong_case) - FUNC_ATTR_PURE -{ - int num_letters; - char *p; - - num_letters = 0; - for (p = matched_string; *p; p++) { - if (ASCII_ISALNUM(*p)) { - num_letters++; - } - } - - /* - * Multiply the number of letters by 100 to give it a much bigger - * weighting than the number of characters. - * If there only is a match while ignoring case, add 5000. - * If the match starts in the middle of a word, add 10000 to put it - * somewhere in the last half. - * If the match is more than 2 chars from the start, multiply by 200 to - * put it after matches at the start. - */ - if (offset > 0 - && ASCII_ISALNUM(matched_string[offset]) - && ASCII_ISALNUM(matched_string[offset - 1])) { - offset += 10000; - } else if (offset > 2) { - offset *= 200; - } - if (wrong_case) { - offset += 5000; - } - // Features are less interesting than the subjects themselves, but "+" - // alone is not a feature. - if (matched_string[0] == '+' && matched_string[1] != NUL) { - offset += 100; - } - return 100 * num_letters + (int)STRLEN(matched_string) + offset; -} - -/// Compare functions for qsort() below, that checks the help heuristics number -/// that has been put after the tagname by find_tags(). -static int help_compare(const void *s1, const void *s2) -{ - char *p1; - char *p2; - - p1 = *(char **)s1 + strlen(*(char **)s1) + 1; - p2 = *(char **)s2 + strlen(*(char **)s2) + 1; - - // Compare by help heuristic number first. - int cmp = strcmp(p1, p2); - if (cmp != 0) { - return cmp; - } - - // Compare by strings as tie-breaker when same heuristic number. - return strcmp(*(char **)s1, *(char **)s2); -} - -/// Find all help tags matching "arg", sort them and return in matches[], with -/// the number of matches in num_matches. -/// The matches will be sorted with a "best" match algorithm. -/// When "keep_lang" is true try keeping the language of the current buffer. -int find_help_tags(const char *arg, int *num_matches, char ***matches, bool keep_lang) -{ - int i; - - // Specific tags that either have a specific replacement or won't go - // through the generic rules. - static char *(except_tbl[][2]) = { - { "*", "star" }, - { "g*", "gstar" }, - { "[*", "[star" }, - { "]*", "]star" }, - { ":*", ":star" }, - { "/*", "/star" }, // NOLINT - { "/\\*", "/\\\\star" }, - { "\"*", "quotestar" }, - { "**", "starstar" }, - { "cpo-*", "cpo-star" }, - { "/\\(\\)", "/\\\\(\\\\)" }, - { "/\\%(\\)", "/\\\\%(\\\\)" }, - { "?", "?" }, - { "??", "??" }, - { ":?", ":?" }, - { "?<CR>", "?<CR>" }, - { "g?", "g?" }, - { "g?g?", "g?g?" }, - { "g??", "g??" }, - { "-?", "-?" }, - { "q?", "q?" }, - { "v_g?", "v_g?" }, - { "/\\?", "/\\\\?" }, - { "/\\z(\\)", "/\\\\z(\\\\)" }, - { "\\=", "\\\\=" }, - { ":s\\=", ":s\\\\=" }, - { "[count]", "\\[count]" }, - { "[quotex]", "\\[quotex]" }, - { "[range]", "\\[range]" }, - { ":[range]", ":\\[range]" }, - { "[pattern]", "\\[pattern]" }, - { "\\|", "\\\\bar" }, - { "\\%$", "/\\\\%\\$" }, - { "s/\\~", "s/\\\\\\~" }, - { "s/\\U", "s/\\\\U" }, - { "s/\\L", "s/\\\\L" }, - { "s/\\1", "s/\\\\1" }, - { "s/\\2", "s/\\\\2" }, - { "s/\\3", "s/\\\\3" }, - { "s/\\9", "s/\\\\9" }, - { NULL, NULL } - }; - - static const char *(expr_table[]) = { - "!=?", "!~?", "<=?", "<?", "==?", "=~?", - ">=?", ">?", "is?", "isnot?" - }; - char *d = (char *)IObuff; // assume IObuff is long enough! - d[0] = NUL; - - if (STRNICMP(arg, "expr-", 5) == 0) { - // When the string starting with "expr-" and containing '?' and matches - // the table, it is taken literally (but ~ is escaped). Otherwise '?' - // is recognized as a wildcard. - for (i = (int)ARRAY_SIZE(expr_table); --i >= 0;) { - if (STRCMP(arg + 5, expr_table[i]) == 0) { - for (int si = 0, di = 0;; si++) { - if (arg[si] == '~') { - d[di++] = '\\'; - } - d[di++] = arg[si]; - if (arg[si] == NUL) { - break; - } - } - break; - } - } - } else { - // Recognize a few exceptions to the rule. Some strings that contain - // '*'are changed to "star", otherwise '*' is recognized as a wildcard. - for (i = 0; except_tbl[i][0] != NULL; i++) { - if (STRCMP(arg, except_tbl[i][0]) == 0) { - STRCPY(d, except_tbl[i][1]); - break; - } - } - } - - if (d[0] == NUL) { // no match in table - // Replace "\S" with "/\\S", etc. Otherwise every tag is matched. - // Also replace "\%^" and "\%(", they match every tag too. - // Also "\zs", "\z1", etc. - // Also "\@<", "\@=", "\@<=", etc. - // And also "\_$" and "\_^". - if (arg[0] == '\\' - && ((arg[1] != NUL && arg[2] == NUL) - || (vim_strchr("%_z@", arg[1]) != NULL - && arg[2] != NUL))) { - vim_snprintf(d, IOSIZE, "/\\\\%s", arg + 1); - // Check for "/\\_$", should be "/\\_\$" - if (d[3] == '_' && d[4] == '$') { - STRCPY(d + 4, "\\$"); - } - } else { - // Replace: - // "[:...:]" with "\[:...:]" - // "[++...]" with "\[++...]" - // "\{" with "\\{" -- matching "} \}" - if ((arg[0] == '[' && (arg[1] == ':' - || (arg[1] == '+' && arg[2] == '+'))) - || (arg[0] == '\\' && arg[1] == '{')) { - *d++ = '\\'; - } - - // If tag starts with "('", skip the "(". Fixes CTRL-] on ('option'. - if (*arg == '(' && arg[1] == '\'') { - arg++; - } - for (const char *s = arg; *s; s++) { - // Replace "|" with "bar" and '"' with "quote" to match the name of - // the tags for these commands. - // Replace "*" with ".*" and "?" with "." to match command line - // completion. - // Insert a backslash before '~', '$' and '.' to avoid their - // special meaning. - if ((char_u *)d - IObuff > IOSIZE - 10) { // getting too long!? - break; - } - switch (*s) { - case '|': - STRCPY(d, "bar"); - d += 3; - continue; - case '"': - STRCPY(d, "quote"); - d += 5; - continue; - case '*': - *d++ = '.'; - break; - case '?': - *d++ = '.'; - continue; - case '$': - case '.': - case '~': - *d++ = '\\'; - break; - } - - /* - * Replace "^x" by "CTRL-X". Don't do this for "^_" to make - * ":help i_^_CTRL-D" work. - * Insert '-' before and after "CTRL-X" when applicable. - */ - if (*s < ' ' - || (*s == '^' && s[1] - && (ASCII_ISALPHA(s[1]) || vim_strchr("?@[\\]^", s[1]) != NULL))) { - if ((char_u *)d > IObuff && d[-1] != '_' && d[-1] != '\\') { - *d++ = '_'; // prepend a '_' to make x_CTRL-x - } - STRCPY(d, "CTRL-"); - d += 5; - if (*s < ' ') { - *d++ = (char)(*s + '@'); - if (d[-1] == '\\') { - *d++ = '\\'; // double a backslash - } - } else { - *d++ = *++s; - } - if (s[1] != NUL && s[1] != '_') { - *d++ = '_'; // append a '_' - } - continue; - } else if (*s == '^') { // "^" or "CTRL-^" or "^_" - *d++ = '\\'; - } - /* - * Insert a backslash before a backslash after a slash, for search - * pattern tags: "/\|" --> "/\\|". - */ - else if (s[0] == '\\' && s[1] != '\\' - && *arg == '/' && s == arg + 1) { - *d++ = '\\'; - } - - // "CTRL-\_" -> "CTRL-\\_" to avoid the special meaning of "\_" in - // "CTRL-\_CTRL-N" - if (STRNICMP(s, "CTRL-\\_", 7) == 0) { - STRCPY(d, "CTRL-\\\\"); - d += 7; - s += 6; - } - - *d++ = *s; - - // If tag contains "({" or "([", tag terminates at the "(". - // This is for help on functions, e.g.: abs({expr}). - if (*s == '(' && (s[1] == '{' || s[1] == '[')) { - break; - } - - // If tag starts with ', toss everything after a second '. Fixes - // CTRL-] on 'option'. (would include the trailing '.'). - if (*s == '\'' && s > arg && *arg == '\'') { - break; - } - // Also '{' and '}'. Fixes CTRL-] on '{address}'. - if (*s == '}' && s > arg && *arg == '{') { - break; - } - } - *d = NUL; - - if (*IObuff == '`') { - if ((char_u *)d > IObuff + 2 && d[-1] == '`') { - // remove the backticks from `command` - memmove(IObuff, IObuff + 1, STRLEN(IObuff)); - d[-2] = NUL; - } else if ((char_u *)d > IObuff + 3 && d[-2] == '`' && d[-1] == ',') { - // remove the backticks and comma from `command`, - memmove(IObuff, IObuff + 1, STRLEN(IObuff)); - d[-3] = NUL; - } else if ((char_u *)d > IObuff + 4 && d[-3] == '`' - && d[-2] == '\\' && d[-1] == '.') { - // remove the backticks and dot from `command`\. - memmove(IObuff, IObuff + 1, STRLEN(IObuff)); - d[-4] = NUL; - } - } - } - } - - *matches = NULL; - *num_matches = 0; - int flags = TAG_HELP | TAG_REGEXP | TAG_NAMES | TAG_VERBOSE | TAG_NO_TAGFUNC; - if (keep_lang) { - flags |= TAG_KEEP_LANG; - } - 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); - // Delete more than TAG_MANY to reduce the size of the listing. - while (*num_matches > TAG_MANY) { - xfree((*matches)[--*num_matches]); - } - } - return OK; -} - -/// Called when starting to edit a buffer for a help file. -static void prepare_help_buffer(void) -{ - curbuf->b_help = true; - set_string_option_direct("buftype", -1, "help", OPT_FREE|OPT_LOCAL, 0); - - // Always set these options after jumping to a help tag, because the - // user may have an autocommand that gets in the way. - // Accept all ASCII chars for keywords, except ' ', '*', '"', '|', and - // latin1 word characters (for translated help files). - // Only set it when needed, buf_init_chartab() is some work. - char *p = "!-~,^*,^|,^\",192-255"; - if (STRCMP(curbuf->b_p_isk, p) != 0) { - set_string_option_direct("isk", -1, p, OPT_FREE|OPT_LOCAL, 0); - check_buf_options(curbuf); - (void)buf_init_chartab(curbuf, FALSE); - } - - // Don't use the global foldmethod. - set_string_option_direct("fdm", -1, "manual", OPT_FREE|OPT_LOCAL, 0); - - curbuf->b_p_ts = 8; // 'tabstop' is 8. - curwin->w_p_list = FALSE; // No list mode. - - curbuf->b_p_ma = FALSE; // Not modifiable. - curbuf->b_p_bin = FALSE; // Reset 'bin' before reading file. - curwin->w_p_nu = 0; // No line numbers. - curwin->w_p_rnu = 0; // No relative line numbers. - RESET_BINDING(curwin); // No scroll or cursor binding. - curwin->w_p_arab = FALSE; // No arabic mode. - curwin->w_p_rl = FALSE; // Help window is left-to-right. - curwin->w_p_fen = FALSE; // No folding in the help window. - curwin->w_p_diff = FALSE; // No 'diff'. - curwin->w_p_spell = FALSE; // No spell checking. - - set_buflisted(FALSE); -} - -/// After reading a help file: May cleanup a help buffer when syntax -/// highlighting is not used. -void fix_help_buffer(void) -{ - linenr_T lnum; - char *line; - bool in_example = false; - - // Set filetype to "help". - if (STRCMP(curbuf->b_p_ft, "help") != 0) { - curbuf->b_ro_locked++; - set_option_value("ft", 0L, "help", OPT_LOCAL); - curbuf->b_ro_locked--; - } - - if (!syntax_present(curwin)) { - for (lnum = 1; lnum <= curbuf->b_ml.ml_line_count; lnum++) { - line = (char *)ml_get_buf(curbuf, lnum, false); - 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] == '<') { - // blank-out a '<' in the first column - line = (char *)ml_get_buf(curbuf, lnum, true); - line[0] = ' '; - } - in_example = false; - } - if (!in_example && len > 0) { - if (line[len - 1] == '>' && (len == 1 || line[len - 2] == ' ')) { - // blank-out a '>' in the last column (start of example) - line = (char *)ml_get_buf(curbuf, lnum, true); - line[len - 1] = ' '; - in_example = true; - } else if (line[len - 1] == '~') { - // blank-out a '~' at the end of line (header marker) - line = (char *)ml_get_buf(curbuf, lnum, true); - line[len - 1] = ' '; - } - } - } - } - - /* - * In the "help.txt" and "help.abx" file, add the locally added help - * files. This uses the very first line in the help file. - */ - char *const fname = path_tail(curbuf->b_fname); - if (FNAMECMP(fname, "help.txt") == 0 - || (FNAMENCMP(fname, "help.", 5) == 0 - && ASCII_ISALPHA(fname[5]) - && ASCII_ISALPHA(fname[6]) - && TOLOWER_ASC(fname[7]) == 'x' - && fname[8] == NUL)) { - for (lnum = 1; lnum < curbuf->b_ml.ml_line_count; lnum++) { - line = (char *)ml_get_buf(curbuf, lnum, false); - if (strstr(line, "*local-additions*") == NULL) { - continue; - } - - // Go through all directories in 'runtimepath', skipping - // $VIMRUNTIME. - char *p = (char *)p_rtp; - while (*p != NUL) { - copy_option_part(&p, (char *)NameBuff, MAXPATHL, ","); - char *const rt = vim_getenv("VIMRUNTIME"); - if (rt != NULL - && path_full_compare(rt, (char *)NameBuff, false, true) != kEqualFiles) { - int fcount; - char **fnames; - char *s; - vimconv_T vc; - char *cp; - - // Find all "doc/ *.txt" files in this directory. - if (!add_pathsep((char *)NameBuff) - || STRLCAT(NameBuff, "doc/*.??[tx]", - sizeof(NameBuff)) >= MAXPATHL) { - emsg(_(e_fnametoolong)); - continue; - } - - // 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 }; - 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) { - 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 = (char *)STRRCHR(t1, '.'); - const char *const e2 = (char *)STRRCHR(t2, '.'); - if (e1 == NULL || e2 == NULL) { - continue; - } - if (FNAMECMP(e1, ".txt") != 0 - && FNAMECMP(e1, fname + 4) != 0) { - // Not .txt and not .abx, remove it. - XFREE_CLEAR(fnames[i1]); - continue; - } - if (e1 - f1 != e2 - f2 - || FNAMENCMP(f1, f2, e1 - f1) != 0) { - continue; - } - if (FNAMECMP(e1, ".txt") == 0 - && FNAMECMP(e2, fname + 4) == 0) { - // use .abx instead of .txt - XFREE_CLEAR(fnames[i1]); - } - } - } - for (int fi = 0; fi < fcount; fi++) { - if (fnames[fi] == NULL) { - continue; - } - - FILE *const fd = os_fopen(fnames[fi], "r"); - if (fd == NULL) { - continue; - } - vim_fgets(IObuff, IOSIZE, fd); - if (IObuff[0] == '*' - && (s = vim_strchr((char *)IObuff + 1, '*')) - != NULL) { - TriState this_utf = kNone; - // Change tag definition to a - // reference and remove <CR>/<NL>. - IObuff[0] = '|'; - *s = '|'; - while (*s != NUL) { - if (*s == '\r' || *s == '\n') { - *s = NUL; - } - // 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) { - this_utf = kTrue; - const int l = utf_ptr2len(s); - if (l == 1) { - this_utf = kFalse; - } - s += l - 1; - } - ++s; - } - // The help file is latin1 or utf-8; - // conversion to the current - // 'encoding' may be required. - vc.vc_type = CONV_NONE; - convert_setup(&vc, - (char_u *)(this_utf == kTrue ? "utf-8" : "latin1"), - p_enc); - if (vc.vc_type == CONV_NONE) { - // No conversion needed. - cp = (char *)IObuff; - } else { - // Do the conversion. If it fails - // use the unconverted text. - cp = (char *)string_convert(&vc, IObuff, NULL); - if (cp == NULL) { - cp = (char *)IObuff; - } - } - convert_setup(&vc, NULL, NULL); - - ml_append(lnum, cp, (colnr_T)0, false); - if ((char_u *)cp != IObuff) { - xfree(cp); - } - lnum++; - } - fclose(fd); - } - FreeWild(fcount, fnames); - } - } - xfree(rt); - } - break; - } - } -} - -/// ":exusage" -void ex_exusage(exarg_T *eap) -{ - do_cmdline_cmd("help ex-cmd-index"); -} - -/// ":viusage" -void ex_viusage(exarg_T *eap) -{ - do_cmdline_cmd("help normal-index"); -} - -/// Generate tags in one help directory -/// -/// @param dir Path to the doc directory -/// @param ext Suffix of the help files (".txt", ".itx", ".frx", etc.) -/// @param tagname Name of the tags file ("tags" for English, "tags-fr" for -/// French) -/// @param add_help_tags Whether to add the "help-tags" tag -/// @param ignore_writeerr ignore write error -static void helptags_one(char *dir, const char *ext, const char *tagfname, bool add_help_tags, - bool ignore_writeerr) - FUNC_ATTR_NONNULL_ALL -{ - garray_T ga; - int filecount; - char **files; - char *p1, *p2; - char *s; - TriState utf8 = kNone; - bool mix = false; // detected mixed encodings - - // Find all *.txt files. - size_t dirlen = STRLCPY(NameBuff, dir, sizeof(NameBuff)); - if (dirlen >= MAXPATHL - || STRLCAT(NameBuff, "/**/*", sizeof(NameBuff)) >= MAXPATHL // NOLINT - || STRLCAT(NameBuff, ext, sizeof(NameBuff)) >= MAXPATHL) { - emsg(_(e_fnametoolong)); - return; - } - - // 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 }; - const int res = gen_expand_wildcards(1, buff_list, &filecount, &files, - EW_FILE|EW_SILENT); - if (res == FAIL || filecount == 0) { - if (!got_int) { - semsg(_("E151: No match: %s"), NameBuff); - } - if (res != FAIL) { - FreeWild(filecount, files); - } - return; - } - - // - // Open the tags file for writing. - // Do this before scanning through all the files. - // - memcpy(NameBuff, dir, dirlen + 1); - if (!add_pathsep((char *)NameBuff) - || STRLCAT(NameBuff, tagfname, sizeof(NameBuff)) >= MAXPATHL) { - emsg(_(e_fnametoolong)); - return; - } - - FILE *const fd_tags = os_fopen((char *)NameBuff, "w"); - if (fd_tags == NULL) { - if (!ignore_writeerr) { - semsg(_("E152: Cannot open %s for writing"), NameBuff); - } - FreeWild(filecount, files); - return; - } - - // If using the "++t" argument or generating tags for "$VIMRUNTIME/doc" - // add the "help-tags" tag. - ga_init(&ga, (int)sizeof(char_u *), 100); - if (add_help_tags - || path_full_compare("$VIMRUNTIME/doc", dir, false, true) == kEqualFiles) { - size_t s_len = 18 + STRLEN(tagfname); - s = xmalloc(s_len); - snprintf(s, s_len, "help-tags\t%s\t1\n", tagfname); - GA_APPEND(char *, &ga, s); - } - - // Go over all the files and extract the tags. - for (int fi = 0; fi < filecount && !got_int; fi++) { - FILE *const fd = os_fopen(files[fi], "r"); - if (fd == NULL) { - semsg(_("E153: Unable to open %s for reading"), files[fi]); - continue; - } - const char *const fname = files[fi] + dirlen + 1; - - bool firstline = true; - 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) { - this_utf8 = kTrue; - const int l = utf_ptr2len(s); - if (l == 1) { - // Illegal UTF-8 byte sequence. - this_utf8 = kFalse; - break; - } - s += l - 1; - } - } - if (this_utf8 == kNone) { // only ASCII characters found - this_utf8 = kFalse; - } - if (utf8 == kNone) { // first file - utf8 = this_utf8; - } else if (utf8 != this_utf8) { - semsg(_("E670: Mix of help file encodings within a language: %s"), - files[fi]); - mix = !got_int; - got_int = TRUE; - } - firstline = false; - } - p1 = vim_strchr((char *)IObuff, '*'); // find first '*' - while (p1 != NULL) { - p2 = strchr((const char *)p1 + 1, '*'); // Find second '*'. - if (p2 != NULL && p2 > p1 + 1) { // Skip "*" and "**". - for (s = p1 + 1; s < p2; s++) { - if (*s == ' ' || *s == '\t' || *s == '|') { - break; - } - } - - // Only accept a *tag* when it consists of valid - // characters, there is white space before it and is - // followed by a white character or end-of-line. - if (s == p2 - && ((char_u *)p1 == IObuff || p1[-1] == ' ' || p1[-1] == '\t') - && (vim_strchr(" \t\n\r", s[1]) != NULL - || s[1] == '\0')) { - *p2 = '\0'; - p1++; - 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); - - // find next '*' - p2 = vim_strchr(p2 + 1, '*'); - } - } - p1 = p2; - } - line_breakcheck(); - } - - fclose(fd); - } - - FreeWild(filecount, files); - - if (!got_int && ga.ga_data != NULL) { - // Sort the tags. - sort_strings(ga.ga_data, ga.ga_len); - - // Check for duplicates. - for (int i = 1; i < ga.ga_len; i++) { - p1 = ((char **)ga.ga_data)[i - 1]; - p2 = ((char **)ga.ga_data)[i]; - while (*p1 == *p2) { - if (*p2 == '\t') { - *p2 = NUL; - vim_snprintf((char *)NameBuff, MAXPATHL, - _("E154: Duplicate tag \"%s\" in file %s/%s"), - ((char_u **)ga.ga_data)[i], dir, p2 + 1); - emsg((char *)NameBuff); - *p2 = '\t'; - break; - } - ++p1; - ++p2; - } - } - - if (utf8 == kTrue) { - fprintf(fd_tags, "!_TAG_FILE_ENCODING\tutf-8\t//\n"); - } - - // 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) { - // help-tags entry was added in formatted form - fputs(s, fd_tags); - } else { - fprintf(fd_tags, "%s\t/" "*", s); - for (p1 = s; *p1 != '\t'; p1++) { - // insert backslash before '\\' and '/' - if (*p1 == '\\' || *p1 == '/') { - putc('\\', fd_tags); - } - putc(*p1, fd_tags); - } - fprintf(fd_tags, "*\n"); - } - } - } - if (mix) { - got_int = false; // continue with other languages - } - - GA_DEEP_CLEAR_PTR(&ga); - fclose(fd_tags); // there is no check for an error... -} - -/// Generate tags in one help directory, taking care of translations. -static void do_helptags(char *dirname, bool add_help_tags, bool ignore_writeerr) - FUNC_ATTR_NONNULL_ALL -{ - int len; - garray_T ga; - char lang[2]; - char ext[5]; - char fname[8]; - int filecount; - char **files; - - // Get a list of all files in the help directory and in subdirectories. - STRLCPY(NameBuff, dirname, sizeof(NameBuff)); - if (!add_pathsep((char *)NameBuff) - || STRLCAT(NameBuff, "**", sizeof(NameBuff)) >= MAXPATHL) { - emsg(_(e_fnametoolong)); - return; - } - - // 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 }; - if (gen_expand_wildcards(1, buff_list, &filecount, &files, - EW_FILE|EW_SILENT) == FAIL - || filecount == 0) { - semsg(_("E151: No match: %s"), NameBuff); - return; - } - - // Go over all files in the directory to find out what languages are - // present. - int j; - ga_init(&ga, 1, 10); - for (int i = 0; i < filecount; i++) { - len = (int)STRLEN(files[i]); - if (len <= 4) { - continue; - } - - if (STRICMP(files[i] + len - 4, ".txt") == 0) { - // ".txt" -> language "en" - lang[0] = 'e'; - lang[1] = 'n'; - } else if (files[i][len - 4] == '.' - && ASCII_ISALPHA(files[i][len - 3]) - && ASCII_ISALPHA(files[i][len - 2]) - && TOLOWER_ASC(files[i][len - 1]) == 'x') { - // ".abx" -> language "ab" - lang[0] = (char)TOLOWER_ASC(files[i][len - 3]); - lang[1] = (char)TOLOWER_ASC(files[i][len - 2]); - } else { - continue; - } - - // 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) { - break; - } - } - if (j == ga.ga_len) { - // New language, add it. - ga_grow(&ga, 2); - ((char *)ga.ga_data)[ga.ga_len++] = lang[0]; - ((char *)ga.ga_data)[ga.ga_len++] = lang[1]; - } - } - - /* - * Loop over the found languages to generate a tags file for each one. - */ - for (j = 0; j < ga.ga_len; j += 2) { - STRCPY(fname, "tags-xx"); - fname[5] = ((char *)ga.ga_data)[j]; - fname[6] = ((char *)ga.ga_data)[j + 1]; - if (fname[5] == 'e' && fname[6] == 'n') { - // English is an exception: use ".txt" and "tags". - fname[4] = NUL; - STRCPY(ext, ".txt"); - } else { - // Language "ab" uses ".abx" and "tags-ab". - STRCPY(ext, ".xxx"); - ext[1] = fname[5]; - ext[2] = fname[6]; - } - helptags_one(dirname, (char *)ext, (char *)fname, add_help_tags, ignore_writeerr); - } - - ga_clear(&ga); - FreeWild(filecount, files); -} - -static void helptags_cb(char *fname, void *cookie) - FUNC_ATTR_NONNULL_ALL -{ - do_helptags(fname, *(bool *)cookie, true); -} - -/// ":helptags" -void ex_helptags(exarg_T *eap) -{ - expand_T xpc; - char *dirname; - bool add_help_tags = false; - - // Check for ":helptags ++t {dir}". - if (STRNCMP(eap->arg, "++t", 3) == 0 && ascii_iswhite(eap->arg[3])) { - add_help_tags = true; - eap->arg = skipwhite(eap->arg + 3); - } - - if (STRCMP(eap->arg, "ALL") == 0) { - do_in_path(p_rtp, "doc", DIP_ALL + DIP_DIR, helptags_cb, &add_help_tags); - } else { - ExpandInit(&xpc); - xpc.xp_context = EXPAND_DIRECTORIES; - dirname = (char *)ExpandOne(&xpc, (char_u *)eap->arg, NULL, - WILD_LIST_NOTFOUND|WILD_SILENT, WILD_EXPAND_FREE); - if (dirname == NULL || !os_isdir((char_u *)dirname)) { - semsg(_("E150: Not a directory: %s"), eap->arg); - } else { - do_helptags(dirname, add_help_tags, false); - } - xfree(dirname); - } -} - -/// ":helpclose": Close one help window -void ex_helpclose(exarg_T *eap) -{ - FOR_ALL_WINDOWS_IN_TAB(win, curtab) { - if (bt_help(win->w_buffer)) { - win_close(win, false, eap->forceit); - return; - } - } -} - /// Shows the effects of the :substitute command being typed ('inccommand'). /// If inccommand=split, shows a preview window and later restores the layout. /// @@ -5901,7 +4792,7 @@ static int show_sub(exarg_T *eap, pos_T old_cusr, PreviewLines *preview_lines, i long cmdpreview_ns, handle_T cmdpreview_bufnr) FUNC_ATTR_NONNULL_ALL { - char *save_shm_p = (char *)vim_strsave(p_shm); + char *save_shm_p = xstrdup(p_shm); PreviewLines lines = *preview_lines; buf_T *orig_buf = curbuf; // We keep a special-purpose buffer around, but don't assume it exists. @@ -6052,7 +4943,7 @@ char *skip_vimgrep_pat(char *p, char **s, int *flags) if (s != NULL) { *s = p; } - p = (char *)skiptowhite((char_u *)p); + p = skiptowhite(p); if (s != NULL && *p != NUL) { *p++ = NUL; } @@ -6062,7 +4953,7 @@ char *skip_vimgrep_pat(char *p, char **s, int *flags) *s = p + 1; } c = (char_u)(*p); - p = (char *)skip_regexp((char_u *)p + 1, c, true, NULL); + p = skip_regexp(p + 1, c, true, NULL); if (*p != c) { return NULL; } @@ -6107,7 +4998,7 @@ void ex_oldfiles(exarg_T *eap) } nr++; const char *fname = tv_get_string(TV_LIST_ITEM_TV(li)); - if (!message_filtered((char_u *)fname)) { + if (!message_filtered((char *)fname)) { msg_outnum(nr); msg_puts(": "); msg_outtrans((char *)tv_get_string(TV_LIST_ITEM_TV(li))); diff --git a/src/nvim/ex_cmds.h b/src/nvim/ex_cmds.h index a55e74a789..3aaba9ce42 100644 --- a/src/nvim/ex_cmds.h +++ b/src/nvim/ex_cmds.h @@ -20,9 +20,9 @@ #define ECMD_NOWINENTER 0x40 // do not trigger BufWinEnter // for lnum argument in do_ecmd() -#define ECMD_LASTL (linenr_T)0 // use last position in loaded file -#define ECMD_LAST ((linenr_T) - 1) // use last position in all files -#define ECMD_ONE (linenr_T)1 // use first line +#define ECMD_LASTL (linenr_T)0 // use last position in loaded file +#define ECMD_LAST ((linenr_T)(-1)) // use last position in all files +#define ECMD_ONE (linenr_T)1 // use first line /// Previous :substitute replacement string definition typedef struct { diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua index a5ba5e0b30..4bed1e94b9 100644 --- a/src/nvim/ex_cmds.lua +++ b/src/nvim/ex_cmds.lua @@ -107,6 +107,12 @@ module.cmds = { func='ex_listdo', }, { + command='argdedupe', + flags=TRLBAR, + addr_type='ADDR_NONE', + func='ex_argdedupe', + }, + { command='argedit', flags=bit.bor(BANG, NEEDARG, RANGE, ZEROR, FILES, CMDARG, ARGOPT, TRLBAR), addr_type='ADDR_ARGUMENTS', @@ -3175,7 +3181,7 @@ module.cmds = { }, { command='wincmd', - flags=bit.bor(NEEDARG, WORD1, RANGE, CMDWIN, LOCK_OK), + flags=bit.bor(NEEDARG, WORD1, RANGE, COUNT, CMDWIN, LOCK_OK), addr_type='ADDR_OTHER', func='ex_wincmd', }, diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index 730a2f1b69..11280eecbb 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -11,30 +11,23 @@ #include <stdbool.h> #include <string.h> +#include "nvim/arglist.h" #include "nvim/ascii.h" -#include "nvim/globals.h" -#include "nvim/vim.h" -#ifdef HAVE_LOCALE_H -# include <locale.h> -#endif -#include "nvim/api/private/defs.h" -#include "nvim/api/private/helpers.h" +#include "nvim/autocmd.h" #include "nvim/buffer.h" #include "nvim/change.h" #include "nvim/charset.h" -#include "nvim/debugger.h" -#include "nvim/eval/userfunc.h" +#include "nvim/eval.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_getln.h" #include "nvim/fileio.h" -#include "nvim/garray.h" -#include "nvim/lua/executor.h" +#include "nvim/globals.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" @@ -42,101 +35,19 @@ #include "nvim/ops.h" #include "nvim/option.h" #include "nvim/os/fs_defs.h" -#include "nvim/os/input.h" -#include "nvim/os/shell.h" #include "nvim/os_unix.h" #include "nvim/path.h" -#include "nvim/profile.h" #include "nvim/quickfix.h" -#include "nvim/regexp.h" +#include "nvim/runtime.h" #include "nvim/strings.h" #include "nvim/undo.h" -#include "nvim/version.h" +#include "nvim/vim.h" #include "nvim/window.h" -/// Growarray to store info about already sourced scripts. -static garray_T script_items = { 0, 0, sizeof(scriptitem_T), 4, NULL }; -#define SCRIPT_ITEM(id) (((scriptitem_T *)script_items.ga_data)[(id) - 1]) - -// Struct used in sn_prl_ga for every line of a script. -typedef struct sn_prl_S { - int snp_count; ///< nr of times line was executed - proftime_T sn_prl_total; ///< time spent in a line + children - proftime_T sn_prl_self; ///< time spent in a line itself -} sn_prl_T; - -/// Structure used to store info for each sourced file. -/// It is shared between do_source() and getsourceline(). -/// This is required, because it needs to be handed to do_cmdline() and -/// sourcing can be done recursively. -struct source_cookie { - FILE *fp; ///< opened file for sourcing - char *nextline; ///< if not NULL: line that was read ahead - linenr_T sourcing_lnum; ///< line number of the source file - int finished; ///< ":finish" used -#if defined(USE_CRNL) - int fileformat; ///< EOL_UNKNOWN, EOL_UNIX or EOL_DOS - bool error; ///< true if LF found after CR-LF -#endif - linenr_T breakpoint; ///< next line with breakpoint or zero - char *fname; ///< name of sourced file - int dbg_tick; ///< debug_tick when breakpoint was set - int level; ///< top nesting level of sourced file - vimconv_T conv; ///< type of conversion -}; - -#define PRL_ITEM(si, idx) (((sn_prl_T *)(si)->sn_prl_ga.ga_data)[(idx)]) - #ifdef INCLUDE_GENERATED_DECLARATIONS # include "ex_cmds2.c.generated.h" #endif -static char *profile_fname = NULL; - -/// ":profile cmd args" -void ex_profile(exarg_T *eap) -{ - static proftime_T pause_time; - - char *e; - int len; - - e = (char *)skiptowhite((char_u *)eap->arg); - len = (int)(e - eap->arg); - e = skipwhite(e); - - 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); - do_profiling = PROF_YES; - profile_set_wait(profile_zero()); - set_vim_var_nr(VV_PROFILING, 1L); - } else if (do_profiling == PROF_NONE) { - emsg(_("E750: First use \":profile start {fname}\"")); - } else if (STRCMP(eap->arg, "stop") == 0) { - profile_dump(); - do_profiling = PROF_NONE; - set_vim_var_nr(VV_PROFILING, 0L); - profile_reset(); - } else if (STRCMP(eap->arg, "pause") == 0) { - if (do_profiling == PROF_YES) { - pause_time = profile_start(); - } - do_profiling = PROF_PAUSED; - } else if (STRCMP(eap->arg, "continue") == 0) { - if (do_profiling == PROF_PAUSED) { - pause_time = profile_end(pause_time); - profile_set_wait(profile_add(profile_get_wait(), pause_time)); - } - do_profiling = PROF_YES; - } else if (STRCMP(eap->arg, "dump") == 0) { - profile_dump(); - } else { - // The rest is similar to ":breakadd". - ex_breakadd(eap); - } -} - void ex_ruby(exarg_T *eap) { script_host_execute("ruby", eap); @@ -182,273 +93,6 @@ void ex_perldo(exarg_T *eap) script_host_do_range("perl", eap); } -// Command line expansion for :profile. -static enum { - PEXP_SUBCMD, ///< expand :profile sub-commands - PEXP_FUNC, ///< expand :profile func {funcname} -} pexpand_what; - -static char *pexpand_cmds[] = { - "continue", - "dump", - "file", - "func", - "pause", - "start", - "stop", - NULL -}; - -/// Function given to ExpandGeneric() to obtain the profile command -/// specific expansion. -char *get_profile_name(expand_T *xp, int idx) - FUNC_ATTR_PURE -{ - switch (pexpand_what) { - case PEXP_SUBCMD: - return pexpand_cmds[idx]; - // case PEXP_FUNC: TODO - default: - return NULL; - } -} - -/// Handle command line completion for :profile command. -void set_context_in_profile_cmd(expand_T *xp, const char *arg) -{ - // Default: expand subcommands. - xp->xp_context = EXPAND_PROFILE; - pexpand_what = PEXP_SUBCMD; - xp->xp_pattern = (char *)arg; - - char_u *const end_subcmd = skiptowhite((const char_u *)arg); - if (*end_subcmd == NUL) { - return; - } - - if ((const char *)end_subcmd - arg == 5 && strncmp(arg, "start", 5) == 0) { - xp->xp_context = EXPAND_FILES; - xp->xp_pattern = skipwhite((char *)end_subcmd); - return; - } - - // TODO(tarruda): expand function names after "func" - xp->xp_context = EXPAND_NOTHING; -} - -/// Dump the profiling info. -void profile_dump(void) -{ - FILE *fd; - - 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); - } - } -} - -/// Reset all profiling information. -static void profile_reset(void) -{ - // Reset sourced files. - for (int id = 1; id <= script_items.ga_len; id++) { - scriptitem_T *si = &SCRIPT_ITEM(id); - if (si->sn_prof_on) { - si->sn_prof_on = false; - si->sn_pr_force = false; - si->sn_pr_child = profile_zero(); - si->sn_pr_nest = 0; - si->sn_pr_count = 0; - si->sn_pr_total = profile_zero(); - si->sn_pr_self = profile_zero(); - si->sn_pr_start = profile_zero(); - si->sn_pr_children = profile_zero(); - ga_clear(&si->sn_prl_ga); - si->sn_prl_start = profile_zero(); - si->sn_prl_children = profile_zero(); - si->sn_prl_wait = profile_zero(); - si->sn_prl_idx = -1; - si->sn_prl_execed = 0; - } - } - - // Reset functions. - size_t n = func_hashtab.ht_used; - hashitem_T *hi = func_hashtab.ht_array; - - for (; n > (size_t)0; hi++) { - if (!HASHITEM_EMPTY(hi)) { - n--; - ufunc_T *uf = HI2UF(hi); - if (uf->uf_prof_initialized) { - uf->uf_profiling = 0; - uf->uf_tm_count = 0; - uf->uf_tm_total = profile_zero(); - uf->uf_tm_self = profile_zero(); - uf->uf_tm_children = profile_zero(); - - for (int i = 0; i < uf->uf_lines.ga_len; i++) { - uf->uf_tml_count[i] = 0; - uf->uf_tml_total[i] = uf->uf_tml_self[i] = 0; - } - - uf->uf_tml_start = profile_zero(); - uf->uf_tml_children = profile_zero(); - uf->uf_tml_wait = profile_zero(); - uf->uf_tml_idx = -1; - uf->uf_tml_execed = 0; - } - } - } - - XFREE_CLEAR(profile_fname); -} - -/// Start profiling a script. -static void profile_init(scriptitem_T *si) -{ - si->sn_pr_count = 0; - si->sn_pr_total = profile_zero(); - si->sn_pr_self = profile_zero(); - - ga_init(&si->sn_prl_ga, sizeof(sn_prl_T), 100); - si->sn_prl_idx = -1; - si->sn_prof_on = true; - si->sn_pr_nest = 0; -} - -/// Save time when starting to invoke another script or function. -/// -/// @param tm place to store wait time -void script_prof_save(proftime_T *tm) -{ - scriptitem_T *si; - - 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_start(); - } - } - *tm = profile_get_wait(); -} - -/// Count time spent in children after invoking another script or function. -void script_prof_restore(proftime_T *tm) -{ - scriptitem_T *si; - - 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); - } - } -} - -static proftime_T inchar_time; - -/// Called when starting to wait for the user to type a character. -void prof_inchar_enter(void) -{ - inchar_time = profile_start(); -} - -/// Called when finished waiting for the user to type a character. -void prof_inchar_exit(void) -{ - inchar_time = profile_end(inchar_time); - profile_set_wait(profile_add(profile_get_wait(), inchar_time)); -} - -/// Dump the profiling results for all scripts in file "fd". -static void script_dump_profile(FILE *fd) -{ - scriptitem_T *si; - FILE *sfd; - sn_prl_T *pp; - - for (int id = 1; id <= script_items.ga_len; id++) { - si = &SCRIPT_ITEM(id); - if (si->sn_prof_on) { - fprintf(fd, "SCRIPT %s\n", si->sn_name); - if (si->sn_pr_count == 1) { - fprintf(fd, "Sourced 1 time\n"); - } else { - fprintf(fd, "Sourced %d times\n", si->sn_pr_count); - } - fprintf(fd, "Total time: %s\n", profile_msg(si->sn_pr_total)); - fprintf(fd, " Self time: %s\n", profile_msg(si->sn_pr_self)); - fprintf(fd, "\n"); - fprintf(fd, "count total (s) self (s)\n"); - - sfd = os_fopen((char *)si->sn_name, "r"); - if (sfd == NULL) { - fprintf(fd, "Cannot open file!\n"); - } else { - // Keep going till the end of file, so that trailing - // continuation lines are listed. - for (int i = 0;; i++) { - if (vim_fgets(IObuff, IOSIZE, sfd)) { - break; - } - // When a line has been truncated, append NL, taking care - // of multi-byte characters . - if (IObuff[IOSIZE - 2] != NUL && IObuff[IOSIZE - 2] != NL) { - int n = IOSIZE - 2; - - // Move to the first byte of this char. - // utf_head_off() doesn't work, because it checks - // for a truncated character. - while (n > 0 && (IObuff[n] & 0xc0) == 0x80) { - n--; - } - - IObuff[n] = NL; - IObuff[n + 1] = NUL; - } - if (i < si->sn_prl_ga.ga_len - && (pp = &PRL_ITEM(si, i))->snp_count > 0) { - fprintf(fd, "%5d ", pp->snp_count); - if (profile_equal(pp->sn_prl_total, pp->sn_prl_self)) { - fprintf(fd, " "); - } else { - fprintf(fd, "%s ", profile_msg(pp->sn_prl_total)); - } - fprintf(fd, "%s ", profile_msg(pp->sn_prl_self)); - } else { - fprintf(fd, " "); - } - fprintf(fd, "%s", IObuff); - } - fclose(sfd); - } - fprintf(fd, "\n"); - } - } -} - -/// @return true when a function defined in the current script should be -/// profiled. -bool prof_def_func(void) - FUNC_ATTR_PURE -{ - if (current_sctx.sc_sid > 0) { - return SCRIPT_ITEM(current_sctx.sc_sid).sn_pr_force; - } - return false; -} - /// If 'autowrite' option set, try to write the file. /// Careful: autocommands may make "buf" invalid! /// @@ -720,7 +364,7 @@ bool check_changed_any(bool hidden, bool unload) exiting = false; // When ":confirm" used, don't give an error message. if (!(p_confirm || (cmdmod.cmod_flags & CMOD_CONFIRM))) { - // There must be a wait_return for this message, do_buffer() + // There must be a wait_return() for this message, do_buffer() // may cause a redraw. But wait_return() is a no-op when vgetc() // is busy (Quit used from window menu), then make sure we don't // cause a scroll up. @@ -796,483 +440,6 @@ int buf_write_all(buf_T *buf, int forceit) return retval; } -/// Code to handle the argument list. - -#define AL_SET 1 -#define AL_ADD 2 -#define AL_DEL 3 - -/// Isolate one argument, taking backticks. -/// Changes the argument in-place, puts a NUL after it. Backticks remain. -/// -/// @return a pointer to the start of the next argument. -static char *do_one_arg(char *str) -{ - char *p; - bool inbacktick; - - inbacktick = false; - for (p = str; *str; str++) { - // When the backslash is used for escaping the special meaning of a - // character we need to keep it until wildcard expansion. - if (rem_backslash((char_u *)str)) { - *p++ = *str++; - *p++ = *str; - } else { - // An item ends at a space not in backticks - if (!inbacktick && ascii_isspace(*str)) { - break; - } - if (*str == '`') { - inbacktick ^= true; - } - *p++ = *str; - } - } - str = skipwhite(str); - *p = NUL; - - return str; -} - -/// Separate the arguments in "str" and return a list of pointers in the -/// growarray "gap". -static void get_arglist(garray_T *gap, char *str, int escaped) -{ - ga_init(gap, (int)sizeof(char_u *), 20); - while (*str != NUL) { - GA_APPEND(char *, gap, str); - - // If str is escaped, don't handle backslashes or spaces - if (!escaped) { - return; - } - - // Isolate one argument, change it in-place, put a NUL after it. - str = do_one_arg(str); - } -} - -/// Parse a list of arguments (file names), expand them and return in -/// "fnames[fcountp]". When "wig" is true, removes files matching 'wildignore'. -/// -/// @return FAIL or OK. -int get_arglist_exp(char_u *str, int *fcountp, char ***fnamesp, bool wig) -{ - garray_T ga; - int i; - - get_arglist(&ga, (char *)str, true); - - if (wig) { - i = expand_wildcards(ga.ga_len, ga.ga_data, - fcountp, fnamesp, EW_FILE|EW_NOTFOUND|EW_NOTWILD); - } else { - i = gen_expand_wildcards(ga.ga_len, ga.ga_data, - fcountp, fnamesp, EW_FILE|EW_NOTFOUND|EW_NOTWILD); - } - - ga_clear(&ga); - return i; -} - -/// @param str -/// @param what -/// AL_SET: Redefine the argument list to 'str'. -/// AL_ADD: add files in 'str' to the argument list after "after". -/// AL_DEL: remove files in 'str' from the argument list. -/// @param after -/// 0 means before first one -/// @param will_edit will edit added argument -/// -/// @return FAIL for failure, OK otherwise. -static int do_arglist(char *str, int what, int after, bool will_edit) - FUNC_ATTR_NONNULL_ALL -{ - garray_T new_ga; - int exp_count; - char **exp_files; - char *p; - int match; - int arg_escaped = true; - - // Set default argument for ":argadd" command. - if (what == AL_ADD && *str == NUL) { - if (curbuf->b_ffname == NULL) { - return FAIL; - } - str = curbuf->b_fname; - arg_escaped = false; - } - - // Collect all file name arguments in "new_ga". - get_arglist(&new_ga, str, arg_escaped); - - if (what == AL_DEL) { - regmatch_T regmatch; - bool didone; - - // Delete the items: use each item as a regexp and find a match in the - // argument list. - regmatch.rm_ic = p_fic; // ignore case when 'fileignorecase' is set - for (int i = 0; i < new_ga.ga_len && !got_int; i++) { - p = ((char **)new_ga.ga_data)[i]; - p = file_pat_to_reg_pat(p, NULL, NULL, false); - if (p == NULL) { - break; - } - regmatch.regprog = vim_regcomp(p, p_magic ? RE_MAGIC : 0); - if (regmatch.regprog == NULL) { - xfree(p); - break; - } - - didone = false; - for (match = 0; match < ARGCOUNT; match++) { - if (vim_regexec(®match, alist_name(&ARGLIST[match]), (colnr_T)0)) { - didone = true; - xfree(ARGLIST[match].ae_fname); - memmove(ARGLIST + match, ARGLIST + match + 1, - (size_t)(ARGCOUNT - match - 1) * sizeof(aentry_T)); - ALIST(curwin)->al_ga.ga_len--; - if (curwin->w_arg_idx > match) { - curwin->w_arg_idx--; - } - match--; - } - } - - vim_regfree(regmatch.regprog); - xfree(p); - if (!didone) { - semsg(_(e_nomatch2), ((char_u **)new_ga.ga_data)[i]); - } - } - ga_clear(&new_ga); - } else { - int i = expand_wildcards(new_ga.ga_len, new_ga.ga_data, - &exp_count, &exp_files, - EW_DIR|EW_FILE|EW_ADDSLASH|EW_NOTFOUND); - ga_clear(&new_ga); - if (i == FAIL || exp_count == 0) { - emsg(_(e_nomatch)); - return FAIL; - } - - if (what == AL_ADD) { - alist_add_list(exp_count, exp_files, after, will_edit); - xfree(exp_files); - } else { - assert(what == AL_SET); - alist_set(ALIST(curwin), exp_count, exp_files, will_edit, NULL, 0); - } - } - - alist_check_arg_idx(); - - return OK; -} - -/// Check the validity of the arg_idx for each other window. -static void alist_check_arg_idx(void) -{ - FOR_ALL_TAB_WINDOWS(tp, win) { - if (win->w_alist == curwin->w_alist) { - check_arg_idx(win); - } - } -} - -/// @return true if window "win" is editing the file at the current argument -/// index. -static bool editing_arg_idx(win_T *win) -{ - return !(win->w_arg_idx >= WARGCOUNT(win) - || (win->w_buffer->b_fnum - != WARGLIST(win)[win->w_arg_idx].ae_fnum - && (win->w_buffer->b_ffname == NULL - || !(path_full_compare(alist_name(&WARGLIST(win)[win->w_arg_idx]), - win->w_buffer->b_ffname, true, - true) & kEqualFiles)))); -} - -/// Check if window "win" is editing the w_arg_idx file in its argument list. -void check_arg_idx(win_T *win) -{ - if (WARGCOUNT(win) > 1 && !editing_arg_idx(win)) { - // We are not editing the current entry in the argument list. - // Set "arg_had_last" if we are editing the last one. - win->w_arg_idx_invalid = true; - if (win->w_arg_idx != WARGCOUNT(win) - 1 - && arg_had_last == false - && ALIST(win) == &global_alist - && GARGCOUNT > 0 - && win->w_arg_idx < GARGCOUNT - && (win->w_buffer->b_fnum == GARGLIST[GARGCOUNT - 1].ae_fnum - || (win->w_buffer->b_ffname != NULL - && (path_full_compare(alist_name(&GARGLIST[GARGCOUNT - 1]), - win->w_buffer->b_ffname, true, true) - & kEqualFiles)))) { - arg_had_last = true; - } - } else { - // We are editing the current entry in the argument list. - // Set "arg_had_last" if it's also the last one - win->w_arg_idx_invalid = false; - if (win->w_arg_idx == WARGCOUNT(win) - 1 && win->w_alist == &global_alist) { - arg_had_last = true; - } - } -} - -/// ":args", ":argslocal" and ":argsglobal". -void ex_args(exarg_T *eap) -{ - if (eap->cmdidx != CMD_args) { - alist_unlink(ALIST(curwin)); - if (eap->cmdidx == CMD_argglobal) { - ALIST(curwin) = &global_alist; - } else { // eap->cmdidx == CMD_arglocal - alist_new(); - } - } - - if (*eap->arg != NUL) { - // ":args file ..": define new argument list, handle like ":next" - // Also for ":argslocal file .." and ":argsglobal file ..". - ex_next(eap); - } else if (eap->cmdidx == CMD_args) { - // ":args": list arguments. - if (ARGCOUNT > 0) { - char **items = xmalloc(sizeof(char_u *) * (size_t)ARGCOUNT); - // Overwrite the command, for a short list there is no scrolling - // required and no wait_return(). - gotocmdline(true); - for (int i = 0; i < ARGCOUNT; i++) { - items[i] = alist_name(&ARGLIST[i]); - } - list_in_columns((char_u **)items, ARGCOUNT, curwin->w_arg_idx); - xfree(items); - } - } else if (eap->cmdidx == CMD_arglocal) { - garray_T *gap = &curwin->w_alist->al_ga; - - // ":argslocal": make a local copy of the global argument list. - ga_grow(gap, GARGCOUNT); - for (int i = 0; i < GARGCOUNT; i++) { - if (GARGLIST[i].ae_fname != NULL) { - AARGLIST(curwin->w_alist)[gap->ga_len].ae_fname = - vim_strsave(GARGLIST[i].ae_fname); - AARGLIST(curwin->w_alist)[gap->ga_len].ae_fnum = - GARGLIST[i].ae_fnum; - gap->ga_len++; - } - } - } -} - -/// ":previous", ":sprevious", ":Next" and ":sNext". -void ex_previous(exarg_T *eap) -{ - // If past the last one already, go to the last one. - if (curwin->w_arg_idx - (int)eap->line2 >= ARGCOUNT) { - do_argfile(eap, ARGCOUNT - 1); - } else { - do_argfile(eap, curwin->w_arg_idx - (int)eap->line2); - } -} - -/// ":rewind", ":first", ":sfirst" and ":srewind". -void ex_rewind(exarg_T *eap) -{ - do_argfile(eap, 0); -} - -/// ":last" and ":slast". -void ex_last(exarg_T *eap) -{ - do_argfile(eap, ARGCOUNT - 1); -} - -/// ":argument" and ":sargument". -void ex_argument(exarg_T *eap) -{ - int i; - - if (eap->addr_count > 0) { - i = (int)eap->line2 - 1; - } else { - i = curwin->w_arg_idx; - } - do_argfile(eap, i); -} - -/// Edit file "argn" of the argument lists. -void do_argfile(exarg_T *eap, int argn) -{ - int other; - char *p; - int old_arg_idx = curwin->w_arg_idx; - - if (argn < 0 || argn >= ARGCOUNT) { - if (ARGCOUNT <= 1) { - emsg(_("E163: There is only one file to edit")); - } else if (argn < 0) { - emsg(_("E164: Cannot go before first file")); - } 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; - } - } - - 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. -void ex_next(exarg_T *eap) -{ - int i; - - // check for changed buffer now, if this fails the argument list is not - // redefined. - if (buf_hide(curbuf) - || eap->cmdidx == CMD_snext - || !check_changed(curbuf, CCGD_AW - | (eap->forceit ? CCGD_FORCEIT : 0) - | CCGD_EXCMD)) { - if (*eap->arg != NUL) { // redefine file list - if (do_arglist(eap->arg, AL_SET, 0, true) == FAIL) { - return; - } - i = 0; - } else { - i = curwin->w_arg_idx + (int)eap->line2; - } - do_argfile(eap, i); - } -} - -/// ":argedit" -void ex_argedit(exarg_T *eap) -{ - int i = eap->addr_count ? (int)eap->line2 : curwin->w_arg_idx + 1; - // Whether curbuf will be reused, curbuf->b_ffname will be set. - bool curbuf_is_reusable = curbuf_reusable(); - - if (do_arglist(eap->arg, AL_ADD, i, true) == FAIL) { - return; - } - maketitle(); - - if (curwin->w_arg_idx == 0 - && (curbuf->b_ml.ml_flags & ML_EMPTY) - && (curbuf->b_ffname == NULL || curbuf_is_reusable)) { - i = 0; - } - // Edit the argument. - if (i < ARGCOUNT) { - do_argfile(eap, i); - } -} - -/// ":argadd" -void ex_argadd(exarg_T *eap) -{ - do_arglist(eap->arg, AL_ADD, - eap->addr_count > 0 ? (int)eap->line2 : curwin->w_arg_idx + 1, - false); - maketitle(); -} - -/// ":argdelete" -void ex_argdelete(exarg_T *eap) -{ - if (eap->addr_count > 0 || *eap->arg == NUL) { - // ":argdel" works like ":.argdel" - if (eap->addr_count == 0) { - if (curwin->w_arg_idx >= ARGCOUNT) { - emsg(_("E610: No argument to delete")); - return; - } - eap->line1 = eap->line2 = curwin->w_arg_idx + 1; - } else if (eap->line2 > ARGCOUNT) { - // ":1,4argdel": Delete all arguments in the range. - eap->line2 = ARGCOUNT; - } - linenr_T n = eap->line2 - eap->line1 + 1; - if (*eap->arg != NUL) { - // Can't have both a range and an argument. - emsg(_(e_invarg)); - } else if (n <= 0) { - // Don't give an error for ":%argdel" if the list is empty. - if (eap->line1 != 1 || eap->line2 != 0) { - emsg(_(e_invrange)); - } - } else { - for (linenr_T i = eap->line1; i <= eap->line2; i++) { - xfree(ARGLIST[i - 1].ae_fname); - } - memmove(ARGLIST + eap->line1 - 1, ARGLIST + eap->line2, - (size_t)(ARGCOUNT - eap->line2) * sizeof(aentry_T)); - ALIST(curwin)->al_ga.ga_len -= (int)n; - if (curwin->w_arg_idx >= eap->line2) { - curwin->w_arg_idx -= (int)n; - } else if (curwin->w_arg_idx > eap->line1) { - curwin->w_arg_idx = (int)eap->line1; - } - if (ARGCOUNT == 0) { - curwin->w_arg_idx = 0; - } else if (curwin->w_arg_idx >= ARGCOUNT) { - curwin->w_arg_idx = ARGCOUNT - 1; - } - } - } else { - do_arglist(eap->arg, AL_DEL, 0, false); - } - maketitle(); -} - /// ":argdo", ":windo", ":bufdo", ":tabdo", ":cdo", ":ldo", ":cfdo" and ":lfdo" void ex_listdo(exarg_T *eap) { @@ -1372,10 +539,10 @@ void ex_listdo(exarg_T *eap) if (curwin->w_arg_idx != i || !editing_arg_idx(curwin)) { // Clear 'shm' to avoid that the file message overwrites // any output from the command. - p_shm_save = (char *)vim_strsave(p_shm); - set_option_value("shm", 0L, "", 0); + p_shm_save = xstrdup(p_shm); + set_option_value_give_err("shm", 0L, "", 0); do_argfile(eap, i); - set_option_value("shm", 0L, p_shm_save, 0); + set_option_value_give_err("shm", 0L, p_shm_save, 0); xfree(p_shm_save); } if (curwin->w_arg_idx != i) { @@ -1441,10 +608,10 @@ void ex_listdo(exarg_T *eap) // Go to the next buffer. Clear 'shm' to avoid that the file // message overwrites any output from the command. - p_shm_save = (char *)vim_strsave(p_shm); - set_option_value("shm", 0L, "", 0); + p_shm_save = xstrdup(p_shm); + set_option_value_give_err("shm", 0L, "", 0); goto_buffer(eap, DOBUF_FIRST, FORWARD, next_fnum); - set_option_value("shm", 0L, p_shm_save, 0); + set_option_value_give_err("shm", 0L, p_shm_save, 0); xfree(p_shm_save); // If autocommands took us elsewhere, quit here. @@ -1464,10 +631,10 @@ void ex_listdo(exarg_T *eap) // Clear 'shm' to avoid that the file message overwrites // any output from the command. - p_shm_save = (char *)vim_strsave(p_shm); - set_option_value("shm", 0L, "", 0); + p_shm_save = xstrdup(p_shm); + set_option_value_give_err("shm", 0L, "", 0); ex_cnext(eap); - set_option_value("shm", 0L, p_shm_save, 0); + set_option_value_give_err("shm", 0L, p_shm_save, 0); xfree(p_shm_save); // If jumping to the next quickfix entry fails, quit here. @@ -1509,11 +676,11 @@ void ex_listdo(exarg_T *eap) // buffer was opened while Syntax autocommands were disabled, // need to trigger them now. if (buf == curbuf) { - apply_autocmds(EVENT_SYNTAX, (char *)curbuf->b_p_syn, curbuf->b_fname, true, + apply_autocmds(EVENT_SYNTAX, curbuf->b_p_syn, curbuf->b_fname, true, curbuf); } else { aucmd_prepbuf(&aco, buf); - apply_autocmds(EVENT_SYNTAX, (char *)buf->b_p_syn, buf->b_fname, true, buf); + apply_autocmds(EVENT_SYNTAX, buf->b_p_syn, buf->b_fname, true, buf); aucmd_restbuf(&aco); } @@ -1524,51 +691,6 @@ void ex_listdo(exarg_T *eap) } } -/// Add files[count] to the arglist of the current window after arg "after". -/// The file names in files[count] must have been allocated and are taken over. -/// Files[] itself is not taken over. -/// -/// @param after: where to add: 0 = before first one -/// @param will_edit will edit adding argument -static void alist_add_list(int count, char **files, int after, bool will_edit) - FUNC_ATTR_NONNULL_ALL -{ - int old_argcount = ARGCOUNT; - ga_grow(&ALIST(curwin)->al_ga, count); - { - if (after < 0) { - after = 0; - } - if (after > ARGCOUNT) { - after = ARGCOUNT; - } - if (after < ARGCOUNT) { - memmove(&(ARGLIST[after + count]), &(ARGLIST[after]), - (size_t)(ARGCOUNT - after) * sizeof(aentry_T)); - } - for (int i = 0; i < count; i++) { - const int flags = BLN_LISTED | (will_edit ? BLN_CURBUF : 0); - ARGLIST[after + i].ae_fname = (char_u *)files[i]; - ARGLIST[after + i].ae_fnum = buflist_add(files[i], flags); - } - ALIST(curwin)->al_ga.ga_len += count; - if (old_argcount > 0 && curwin->w_arg_idx >= after) { - curwin->w_arg_idx += count; - } - return; - } -} - -// Function given to ExpandGeneric() to obtain the possible arguments of the -// argedit and argdelete commands. -char *get_arglist_name(expand_T *xp FUNC_ATTR_UNUSED, int idx) -{ - if (idx >= ARGCOUNT) { - return NULL; - } - return alist_name(&ARGLIST[idx]); -} - /// ":compiler[!] {name}" void ex_compiler(exarg_T *eap) { @@ -1632,987 +754,6 @@ void ex_compiler(exarg_T *eap) } } -/// ":options" -void ex_options(exarg_T *eap) -{ - char buf[500]; - bool multi_mods = 0; - - buf[0] = NUL; - (void)add_win_cmd_modifers(buf, &cmdmod, &multi_mods); - - os_setenv("OPTWIN_CMD", buf, 1); - cmd_source(SYS_OPTWIN_FILE, NULL); -} - -/// ":source [{fname}]" -void ex_source(exarg_T *eap) -{ - cmd_source(eap->arg, eap); -} - -static void cmd_source(char *fname, exarg_T *eap) -{ - if (eap != NULL && *fname == NUL) { - cmd_source_buffer(eap); - } else if (eap != NULL && eap->forceit) { - // ":source!": read Normal mode commands - // Need to execute the commands directly. This is required at least - // for: - // - ":g" command busy - // - after ":argdo", ":windo" or ":bufdo" - // - another command follows - // - inside a loop - openscript((char_u *)fname, global_busy || listcmd_busy || eap->nextcmd != NULL - || eap->cstack->cs_idx >= 0); - - // ":source" read ex commands - } else if (do_source(fname, false, DOSO_NONE) == FAIL) { - semsg(_(e_notopen), fname); - } -} - -/// Concatenate VimL line if it starts with a line continuation into a growarray -/// (excluding the continuation chars and leading whitespace) -/// -/// @note Growsize of the growarray may be changed to speed up concatenations! -/// -/// @param ga the growarray to append to -/// @param init_growsize the starting growsize value of the growarray -/// @param p pointer to the beginning of the line to consider -/// @param len the length of this line -/// -/// @return true if this line did begin with a continuation (the next line -/// should also be considered, if it exists); false otherwise -static bool concat_continued_line(garray_T *const ga, const int init_growsize, - const char_u *const p, size_t len) - FUNC_ATTR_NONNULL_ALL -{ - const char *const line = (char *)skipwhite_len(p, len); - len -= (size_t)((char_u *)line - p); - // Skip lines starting with '\" ', concat lines starting with '\' - if (len >= 3 && STRNCMP(line, "\"\\ ", 3) == 0) { - return true; - } else if (len == 0 || line[0] != '\\') { - return false; - } - if (ga->ga_len > init_growsize) { - ga_set_growsize(ga, MIN(ga->ga_len, 8000)); - } - ga_concat_len(ga, line + 1, len - 1); - return true; -} - -typedef struct { - linenr_T curr_lnum; - const linenr_T final_lnum; -} GetBufferLineCookie; - -/// ":source" and associated commands. -/// -/// @return address holding the next breakpoint line for a source cookie -linenr_T *source_breakpoint(void *cookie) -{ - return &((struct source_cookie *)cookie)->breakpoint; -} - -/// @return the address holding the debug tick for a source cookie. -int *source_dbg_tick(void *cookie) -{ - return &((struct source_cookie *)cookie)->dbg_tick; -} - -/// @return the nesting level for a source cookie. -int source_level(void *cookie) - FUNC_ATTR_PURE -{ - return ((struct source_cookie *)cookie)->level; -} - -/// Special function to open a file without handle inheritance. -/// If possible the handle is closed on exec(). -static FILE *fopen_noinh_readbin(char *filename) -{ -#ifdef WIN32 - int fd_tmp = os_open(filename, O_RDONLY | O_BINARY | O_NOINHERIT, 0); -#else - int fd_tmp = os_open(filename, O_RDONLY, 0); -#endif - - if (fd_tmp < 0) { - return NULL; - } - - (void)os_set_cloexec(fd_tmp); - - return fdopen(fd_tmp, READBIN); -} - -typedef struct { - char *buf; - size_t offset; -} GetStrLineCookie; - -/// Get one full line from a sourced string (in-memory, no file). -/// Called by do_cmdline() when it's called from do_source_str(). -/// -/// @return pointer to allocated line, or NULL for end-of-file or -/// some error. -static char *get_str_line(int c, void *cookie, int indent, bool do_concat) -{ - GetStrLineCookie *p = cookie; - if (STRLEN(p->buf) <= p->offset) { - return NULL; - } - const char *line = p->buf + p->offset; - const char *eol = (char *)skip_to_newline((char_u *)line); - garray_T ga; - ga_init(&ga, sizeof(char_u), 400); - ga_concat_len(&ga, line, (size_t)(eol - line)); - if (do_concat && vim_strchr(p_cpo, CPO_CONCAT) == NULL) { - while (eol[0] != NUL) { - line = eol + 1; - const char_u *const next_eol = skip_to_newline((char_u *)line); - if (!concat_continued_line(&ga, 400, (char_u *)line, (size_t)(next_eol - (char_u *)line))) { - break; - } - eol = (char *)next_eol; - } - } - ga_append(&ga, NUL); - p->offset = (size_t)(eol - p->buf) + 1; - return ga.ga_data; -} - -/// Create a new script item and allocate script-local vars. @see new_script_vars -/// -/// @param name File name of the script. NULL for anonymous :source. -/// @param[out] sid_out SID of the new item. -/// -/// @return pointer to the created script item. -scriptitem_T *new_script_item(char *const name, scid_T *const sid_out) -{ - static scid_T last_current_SID = 0; - const scid_T sid = ++last_current_SID; - if (sid_out != NULL) { - *sid_out = sid; - } - ga_grow(&script_items, sid - script_items.ga_len); - while (script_items.ga_len < sid) { - script_items.ga_len++; - SCRIPT_ITEM(script_items.ga_len).sn_name = NULL; - SCRIPT_ITEM(script_items.ga_len).sn_prof_on = false; - } - SCRIPT_ITEM(sid).sn_name = (char_u *)name; - new_script_vars(sid); // Allocate the local script variables to use for this script. - return &SCRIPT_ITEM(sid); -} - -static int source_using_linegetter(void *cookie, LineGetter fgetline, const char *traceback_name) -{ - char *save_sourcing_name = sourcing_name; - linenr_T save_sourcing_lnum = sourcing_lnum; - char sourcing_name_buf[256]; - if (save_sourcing_name == NULL) { - sourcing_name = (char *)traceback_name; - } else { - snprintf((char *)sourcing_name_buf, sizeof(sourcing_name_buf), - "%s called at %s:%" PRIdLINENR, traceback_name, save_sourcing_name, - save_sourcing_lnum); - sourcing_name = sourcing_name_buf; // -V507 reassigned below, before return. - } - sourcing_lnum = 0; - - const sctx_T save_current_sctx = current_sctx; - if (current_sctx.sc_sid != SID_LUA) { - current_sctx.sc_sid = SID_STR; - } - current_sctx.sc_seq = 0; - current_sctx.sc_lnum = save_sourcing_lnum; - funccal_entry_T entry; - save_funccal(&entry); - int retval = do_cmdline(NULL, fgetline, cookie, - DOCMD_VERBOSE | DOCMD_NOWAIT | DOCMD_REPEAT); - sourcing_lnum = save_sourcing_lnum; - sourcing_name = save_sourcing_name; - current_sctx = save_current_sctx; - restore_funccal(); - return retval; -} - -static void cmd_source_buffer(const exarg_T *const eap) - FUNC_ATTR_NONNULL_ALL -{ - if (curbuf == NULL) { - return; - } - garray_T ga; - ga_init(&ga, sizeof(char_u), 400); - const linenr_T final_lnum = eap->line2; - // Copy the contents to be executed. - for (linenr_T curr_lnum = eap->line1; curr_lnum <= final_lnum; curr_lnum++) { - // Adjust growsize to current length to speed up concatenating many lines. - if (ga.ga_len > 400) { - ga_set_growsize(&ga, MIN(ga.ga_len, 8000)); - } - ga_concat(&ga, (char *)ml_get(curr_lnum)); - ga_append(&ga, NL); - } - ((char_u *)ga.ga_data)[ga.ga_len - 1] = NUL; - const GetStrLineCookie cookie = { - .buf = ga.ga_data, - .offset = 0, - }; - if (curbuf->b_fname - && path_with_extension((const char *)curbuf->b_fname, "lua")) { - nlua_source_using_linegetter(get_str_line, (void *)&cookie, - ":source (no file)"); - } else { - source_using_linegetter((void *)&cookie, get_str_line, - ":source (no file)"); - } - ga_clear(&ga); -} - -/// Executes lines in `src` as Ex commands. -/// -/// @see do_source() -int do_source_str(const char *cmd, const char *traceback_name) -{ - GetStrLineCookie cookie = { - .buf = (char *)cmd, - .offset = 0, - }; - return source_using_linegetter((void *)&cookie, get_str_line, traceback_name); -} - -/// When fname is a 'lua' file nlua_exec_file() is invoked to source it. -/// Otherwise reads the file `fname` and executes its lines as Ex commands. -/// -/// This function may be called recursively! -/// -/// @see do_source_str -/// -/// @param fname -/// @param check_other check for .vimrc and _vimrc -/// @param is_vimrc DOSO_ value -/// -/// @return FAIL if file could not be opened, OK otherwise -int do_source(char *fname, int check_other, int is_vimrc) -{ - struct source_cookie cookie; - char *save_sourcing_name; - linenr_T save_sourcing_lnum; - char *p; - char *fname_exp; - uint8_t *firstline = NULL; - int retval = FAIL; - int save_debug_break_level = debug_break_level; - scriptitem_T *si = NULL; - proftime_T wait_start; - bool trigger_source_post = false; - - p = expand_env_save(fname); - if (p == NULL) { - return retval; - } - fname_exp = fix_fname(p); - xfree(p); - if (fname_exp == NULL) { - return retval; - } - if (os_isdir((char_u *)fname_exp)) { - smsg(_("Cannot source a directory: \"%s\""), fname); - goto theend; - } - - // Apply SourceCmd autocommands, they should get the file and source it. - if (has_autocmd(EVENT_SOURCECMD, fname_exp, NULL) - && apply_autocmds(EVENT_SOURCECMD, fname_exp, fname_exp, - false, curbuf)) { - retval = aborting() ? FAIL : OK; - if (retval == OK) { - // Apply SourcePost autocommands. - apply_autocmds(EVENT_SOURCEPOST, fname_exp, fname_exp, false, curbuf); - } - goto theend; - } - - // Apply SourcePre autocommands, they may get the file. - apply_autocmds(EVENT_SOURCEPRE, fname_exp, fname_exp, false, curbuf); - - cookie.fp = fopen_noinh_readbin(fname_exp); - if (cookie.fp == NULL && check_other) { - // Try again, replacing file name ".vimrc" by "_vimrc" or vice versa, - // and ".exrc" by "_exrc" or vice versa. - p = path_tail(fname_exp); - if ((*p == '.' || *p == '_') - && (STRICMP(p + 1, "nvimrc") == 0 || STRICMP(p + 1, "exrc") == 0)) { - *p = (*p == '_') ? '.' : '_'; - cookie.fp = fopen_noinh_readbin(fname_exp); - } - } - - if (cookie.fp == NULL) { - if (p_verbose > 1) { - verbose_enter(); - if (sourcing_name == NULL) { - smsg(_("could not source \"%s\""), fname); - } else { - smsg(_("line %" PRId64 ": could not source \"%s\""), - (int64_t)sourcing_lnum, fname); - } - verbose_leave(); - } - goto theend; - } - - // The file exists. - // - In verbose mode, give a message. - // - For a vimrc file, may want to call vimrc_found(). - if (p_verbose > 1) { - verbose_enter(); - if (sourcing_name == NULL) { - smsg(_("sourcing \"%s\""), fname); - } else { - smsg(_("line %" PRId64 ": sourcing \"%s\""), - (int64_t)sourcing_lnum, fname); - } - verbose_leave(); - } - if (is_vimrc == DOSO_VIMRC) { - vimrc_found(fname_exp, "MYVIMRC"); - } - -#ifdef USE_CRNL - // If no automatic file format: Set default to CR-NL. - if (*p_ffs == NUL) { - cookie.fileformat = EOL_DOS; - } else { - cookie.fileformat = EOL_UNKNOWN; - } - cookie.error = false; -#endif - - cookie.nextline = NULL; - cookie.sourcing_lnum = 0; - cookie.finished = false; - - // Check if this script has a breakpoint. - cookie.breakpoint = dbg_find_breakpoint(true, (char_u *)fname_exp, (linenr_T)0); - cookie.fname = fname_exp; - cookie.dbg_tick = debug_tick; - - cookie.level = ex_nesting_level; - - // Keep the sourcing name/lnum, for recursive calls. - save_sourcing_name = sourcing_name; - sourcing_name = fname_exp; - save_sourcing_lnum = sourcing_lnum; - sourcing_lnum = 0; - - // start measuring script load time if --startuptime was passed and - // time_fd was successfully opened afterwards. - proftime_T rel_time; - proftime_T start_time; - FILE * const l_time_fd = time_fd; - if (l_time_fd != NULL) { - time_push(&rel_time, &start_time); - } - - const int l_do_profiling = do_profiling; - if (l_do_profiling == PROF_YES) { - prof_child_enter(&wait_start); // entering a child now - } - - // Don't use local function variables, if called from a function. - // Also starts profiling timer for nested script. - funccal_entry_T funccalp_entry; - save_funccal(&funccalp_entry); - - const sctx_T save_current_sctx = current_sctx; - si = get_current_script_id((char_u *)fname_exp, ¤t_sctx); - - if (l_do_profiling == PROF_YES) { - bool forceit = false; - - // Check if we do profiling for this script. - if (!si->sn_prof_on && has_profiling(true, si->sn_name, &forceit)) { - profile_init(si); - si->sn_pr_force = forceit; - } - if (si->sn_prof_on) { - si->sn_pr_count++; - si->sn_pr_start = profile_start(); - si->sn_pr_children = profile_zero(); - } - } - - cookie.conv.vc_type = CONV_NONE; // no conversion - - // 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 - && firstline[1] == 0xbb && firstline[2] == 0xbf) { - // Found BOM; setup conversion, skip over BOM and recode the line. - convert_setup(&cookie.conv, (char_u *)"utf-8", p_enc); - p = (char *)string_convert(&cookie.conv, (char_u *)firstline + 3, NULL); - if (p == NULL) { - p = xstrdup((char *)firstline + 3); - } - xfree(firstline); - firstline = (uint8_t *)p; - } - - if (path_with_extension((const char *)fname_exp, "lua")) { - const sctx_T current_sctx_backup = current_sctx; - const linenr_T sourcing_lnum_backup = sourcing_lnum; - current_sctx.sc_sid = SID_LUA; - current_sctx.sc_lnum = 0; - sourcing_lnum = 0; - // Source the file as lua - nlua_exec_file((const char *)fname_exp); - current_sctx = current_sctx_backup; - sourcing_lnum = sourcing_lnum_backup; - } else { - // Call do_cmdline, which will call getsourceline() to get the lines. - do_cmdline((char *)firstline, getsourceline, (void *)&cookie, - DOCMD_VERBOSE|DOCMD_NOWAIT|DOCMD_REPEAT); - } - retval = OK; - - if (l_do_profiling == PROF_YES) { - // Get "si" again, "script_items" may have been reallocated. - si = &SCRIPT_ITEM(current_sctx.sc_sid); - if (si->sn_prof_on) { - si->sn_pr_start = profile_end(si->sn_pr_start); - si->sn_pr_start = profile_sub_wait(wait_start, si->sn_pr_start); - si->sn_pr_total = profile_add(si->sn_pr_total, si->sn_pr_start); - si->sn_pr_self = profile_self(si->sn_pr_self, si->sn_pr_start, - si->sn_pr_children); - } - } - - if (got_int) { - emsg(_(e_interr)); - } - sourcing_name = save_sourcing_name; - sourcing_lnum = save_sourcing_lnum; - if (p_verbose > 1) { - verbose_enter(); - smsg(_("finished sourcing %s"), fname); - if (sourcing_name != NULL) { - smsg(_("continuing in %s"), sourcing_name); - } - verbose_leave(); - } - - if (l_time_fd != NULL) { - vim_snprintf((char *)IObuff, IOSIZE, "sourcing %s", fname); - time_msg((char *)IObuff, &start_time); - time_pop(rel_time); - } - - if (!got_int) { - trigger_source_post = true; - } - - // After a "finish" in debug mode, need to break at first command of next - // sourced file. - if (save_debug_break_level > ex_nesting_level - && debug_break_level == ex_nesting_level) { - debug_break_level++; - } - - current_sctx = save_current_sctx; - restore_funccal(); - if (l_do_profiling == PROF_YES) { - prof_child_exit(&wait_start); // leaving a child now - } - fclose(cookie.fp); - xfree(cookie.nextline); - xfree(firstline); - convert_setup(&cookie.conv, NULL, NULL); - - if (trigger_source_post) { - apply_autocmds(EVENT_SOURCEPOST, fname_exp, fname_exp, false, curbuf); - } - -theend: - xfree(fname_exp); - return retval; -} - -/// Check if fname was sourced before to finds its SID. -/// If it's new, generate a new SID. -/// -/// @param[in] fname file path of script -/// @param[out] ret_sctx sctx of this script -scriptitem_T *get_current_script_id(char_u *fname, sctx_T *ret_sctx) -{ - static int last_current_SID_seq = 0; - - sctx_T script_sctx = { .sc_seq = ++last_current_SID_seq, - .sc_lnum = 0, - .sc_sid = 0 }; - scriptitem_T *si = NULL; - - assert(script_items.ga_len >= 0); - for (script_sctx.sc_sid = script_items.ga_len; script_sctx.sc_sid > 0; script_sctx.sc_sid--) { - // We used to check inode here, but that doesn't work: - // - If a script is edited and written, it may get a different - // inode number, even though to the user it is the same script. - // - If a script is deleted and another script is written, with a - // different name, the inode may be re-used. - si = &SCRIPT_ITEM(script_sctx.sc_sid); - if (si->sn_name != NULL && FNAMECMP(si->sn_name, fname) == 0) { - // Found it! - break; - } - } - if (script_sctx.sc_sid == 0) { - si = new_script_item((char *)vim_strsave(fname), &script_sctx.sc_sid); - } - if (ret_sctx != NULL) { - *ret_sctx = script_sctx; - } - - return si; -} - -/// ":scriptnames" -void ex_scriptnames(exarg_T *eap) -{ - if (eap->addr_count > 0) { - // :script {scriptId}: edit the script - if (eap->line2 < 1 || eap->line2 > script_items.ga_len) { - emsg(_(e_invarg)); - } else { - eap->arg = (char *)SCRIPT_ITEM(eap->line2).sn_name; - do_exedit(eap, NULL); - } - return; - } - - for (int i = 1; i <= script_items.ga_len && !got_int; i++) { - if (SCRIPT_ITEM(i).sn_name != NULL) { - home_replace(NULL, (char *)SCRIPT_ITEM(i).sn_name, (char *)NameBuff, MAXPATHL, true); - vim_snprintf((char *)IObuff, IOSIZE, "%3d: %s", i, NameBuff); - if (!message_filtered(IObuff)) { - msg_putchar('\n'); - msg_outtrans((char *)IObuff); - line_breakcheck(); - } - } - } -} - -#if defined(BACKSLASH_IN_FILENAME) -/// Fix slashes in the list of script names for 'shellslash'. -void scriptnames_slash_adjust(void) -{ - for (int i = 1; i <= script_items.ga_len; i++) { - if (SCRIPT_ITEM(i).sn_name != NULL) { - slash_adjust(SCRIPT_ITEM(i).sn_name); - } - } -} - -#endif - -/// Get a pointer to a script name. Used for ":verbose set". -/// Message appended to "Last set from " -char_u *get_scriptname(LastSet last_set, bool *should_free) -{ - *should_free = false; - - switch (last_set.script_ctx.sc_sid) { - case SID_MODELINE: - return (char_u *)_("modeline"); - case SID_CMDARG: - return (char_u *)_("--cmd argument"); - case SID_CARG: - return (char_u *)_("-c argument"); - case SID_ENV: - return (char_u *)_("environment variable"); - case SID_ERROR: - return (char_u *)_("error handler"); - case SID_WINLAYOUT: - return (char_u *)_("changed window size"); - case SID_LUA: - return (char_u *)_("Lua"); - case SID_API_CLIENT: - snprintf((char *)IObuff, IOSIZE, _("API client (channel id %" PRIu64 ")"), last_set.channel_id); - return IObuff; - case SID_STR: - return (char_u *)_("anonymous :source"); - default: { - char *const sname = (char *)SCRIPT_ITEM(last_set.script_ctx.sc_sid).sn_name; - if (sname == NULL) { - snprintf((char *)IObuff, IOSIZE, _("anonymous :source (script id %d)"), - last_set.script_ctx.sc_sid); - return IObuff; - } - - *should_free = true; - return (char_u *)home_replace_save(NULL, sname); - } - } -} - -#if defined(EXITFREE) -void free_scriptnames(void) -{ - profile_reset(); - -# define FREE_SCRIPTNAME(item) xfree((item)->sn_name) - GA_DEEP_CLEAR(&script_items, scriptitem_T, FREE_SCRIPTNAME); -} -#endif - -linenr_T get_sourced_lnum(LineGetter fgetline, void *cookie) - FUNC_ATTR_PURE -{ - return fgetline == getsourceline - ? ((struct source_cookie *)cookie)->sourcing_lnum - : sourcing_lnum; -} - -/// Get one full line from a sourced file. -/// Called by do_cmdline() when it's called from do_source(). -/// -/// @return pointer to the line in allocated memory, or NULL for end-of-file or -/// some error. -char *getsourceline(int c, void *cookie, int indent, bool do_concat) -{ - struct source_cookie *sp = (struct source_cookie *)cookie; - char *line; - char *p; - - // If breakpoints have been added/deleted need to check for it. - if (sp->dbg_tick < debug_tick) { - sp->breakpoint = dbg_find_breakpoint(true, (char_u *)sp->fname, sourcing_lnum); - sp->dbg_tick = debug_tick; - } - if (do_profiling == PROF_YES) { - script_line_end(); - } - // Set the current sourcing line number. - sourcing_lnum = sp->sourcing_lnum + 1; - // Get current line. If there is a read-ahead line, use it, otherwise get - // one now. - if (sp->finished) { - line = NULL; - } else if (sp->nextline == NULL) { - line = get_one_sourceline(sp); - } else { - line = sp->nextline; - sp->nextline = NULL; - sp->sourcing_lnum++; - } - if (line != NULL && do_profiling == PROF_YES) { - script_line_start(); - } - - // Only concatenate lines starting with a \ when 'cpoptions' doesn't - // contain the 'C' flag. - if (line != NULL && do_concat && (vim_strchr(p_cpo, CPO_CONCAT) == NULL)) { - // compensate for the one line read-ahead - sp->sourcing_lnum--; - - // Get the next line and concatenate it when it starts with a - // backslash. We always need to read the next line, keep it in - // sp->nextline. - // Also check for a comment in between continuation lines: "\ . - sp->nextline = get_one_sourceline(sp); - if (sp->nextline != NULL - && (*(p = skipwhite(sp->nextline)) == '\\' - || (p[0] == '"' && p[1] == '\\' && p[2] == ' '))) { - garray_T ga; - - ga_init(&ga, (int)sizeof(char_u), 400); - ga_concat(&ga, line); - while (sp->nextline != NULL - && concat_continued_line(&ga, 400, (char_u *)sp->nextline, - STRLEN(sp->nextline))) { - xfree(sp->nextline); - sp->nextline = get_one_sourceline(sp); - } - ga_append(&ga, NUL); - xfree(line); - line = ga.ga_data; - } - } - - if (line != NULL && sp->conv.vc_type != CONV_NONE) { - char *s; - - // Convert the encoding of the script line. - s = (char *)string_convert(&sp->conv, (char_u *)line, NULL); - if (s != NULL) { - xfree(line); - line = s; - } - } - - // Did we encounter a breakpoint? - if (sp->breakpoint != 0 && sp->breakpoint <= sourcing_lnum) { - dbg_breakpoint((char_u *)sp->fname, sourcing_lnum); - // Find next breakpoint. - sp->breakpoint = dbg_find_breakpoint(true, (char_u *)sp->fname, sourcing_lnum); - sp->dbg_tick = debug_tick; - } - - return line; -} - -static char *get_one_sourceline(struct source_cookie *sp) -{ - garray_T ga; - int len; - int c; - char *buf; -#ifdef USE_CRNL - int has_cr; // CR-LF found -#endif - bool have_read = false; - - // use a growarray to store the sourced line - ga_init(&ga, 1, 250); - - // Loop until there is a finished line (or end-of-file). - sp->sourcing_lnum++; - for (;;) { - // make room to read at least 120 (more) characters - ga_grow(&ga, 120); - buf = ga.ga_data; - -retry: - errno = 0; - if (fgets(buf + ga.ga_len, ga.ga_maxlen - ga.ga_len, - sp->fp) == NULL) { - if (errno == EINTR) { - goto retry; - } - - break; - } - len = ga.ga_len + (int)STRLEN(buf + ga.ga_len); -#ifdef USE_CRNL - // Ignore a trailing CTRL-Z, when in Dos mode. Only recognize the - // CTRL-Z by its own, or after a NL. - if ((len == 1 || (len >= 2 && buf[len - 2] == '\n')) - && sp->fileformat == EOL_DOS - && buf[len - 1] == Ctrl_Z) { - buf[len - 1] = NUL; - break; - } -#endif - - have_read = true; - ga.ga_len = len; - - // If the line was longer than the buffer, read more. - if (ga.ga_maxlen - ga.ga_len == 1 && buf[len - 1] != '\n') { - continue; - } - - if (len >= 1 && buf[len - 1] == '\n') { // remove trailing NL -#ifdef USE_CRNL - has_cr = (len >= 2 && buf[len - 2] == '\r'); - if (sp->fileformat == EOL_UNKNOWN) { - if (has_cr) { - sp->fileformat = EOL_DOS; - } else { - sp->fileformat = EOL_UNIX; - } - } - - if (sp->fileformat == EOL_DOS) { - if (has_cr) { // replace trailing CR - buf[len - 2] = '\n'; - len--; - ga.ga_len--; - } else { // lines like ":map xx yy^M" will have failed - if (!sp->error) { - msg_source(HL_ATTR(HLF_W)); - emsg(_("W15: Warning: Wrong line separator, ^M may be missing")); - } - sp->error = true; - sp->fileformat = EOL_UNIX; - } - } -#endif - // The '\n' is escaped if there is an odd number of ^V's just - // before it, first set "c" just before the 'V's and then check - // len&c parities (is faster than ((len-c)%2 == 0)) -- Acevedo - for (c = len - 2; c >= 0 && buf[c] == Ctrl_V; c--) {} - if ((len & 1) != (c & 1)) { // escaped NL, read more - sp->sourcing_lnum++; - continue; - } - - buf[len - 1] = NUL; // remove the NL - } - - // Check for ^C here now and then, so recursive :so can be broken. - line_breakcheck(); - break; - } - - if (have_read) { - return ga.ga_data; - } - - xfree(ga.ga_data); - return NULL; -} - -/// Called when starting to read a script line. -/// "sourcing_lnum" must be correct! -/// When skipping lines it may not actually be executed, but we won't find out -/// until later and we need to store the time now. -void script_line_start(void) -{ - scriptitem_T *si; - sn_prl_T *pp; - - if (current_sctx.sc_sid <= 0 || current_sctx.sc_sid > script_items.ga_len) { - return; - } - si = &SCRIPT_ITEM(current_sctx.sc_sid); - if (si->sn_prof_on && sourcing_lnum >= 1) { - // Grow the array before starting the timer, so that the time spent - // here isn't counted. - (void)ga_grow(&si->sn_prl_ga, sourcing_lnum - si->sn_prl_ga.ga_len); - si->sn_prl_idx = sourcing_lnum - 1; - while (si->sn_prl_ga.ga_len <= si->sn_prl_idx - && si->sn_prl_ga.ga_len < si->sn_prl_ga.ga_maxlen) { - // Zero counters for a line that was not used before. - pp = &PRL_ITEM(si, si->sn_prl_ga.ga_len); - pp->snp_count = 0; - pp->sn_prl_total = profile_zero(); - pp->sn_prl_self = profile_zero(); - si->sn_prl_ga.ga_len++; - } - si->sn_prl_execed = false; - si->sn_prl_start = profile_start(); - si->sn_prl_children = profile_zero(); - si->sn_prl_wait = profile_get_wait(); - } -} - -/// Called when actually executing a function line. -void script_line_exec(void) -{ - scriptitem_T *si; - - if (current_sctx.sc_sid <= 0 || current_sctx.sc_sid > script_items.ga_len) { - return; - } - si = &SCRIPT_ITEM(current_sctx.sc_sid); - if (si->sn_prof_on && si->sn_prl_idx >= 0) { - si->sn_prl_execed = true; - } -} - -/// Called when done with a function line. -void script_line_end(void) -{ - scriptitem_T *si; - sn_prl_T *pp; - - if (current_sctx.sc_sid <= 0 || current_sctx.sc_sid > script_items.ga_len) { - return; - } - si = &SCRIPT_ITEM(current_sctx.sc_sid); - if (si->sn_prof_on && si->sn_prl_idx >= 0 - && si->sn_prl_idx < si->sn_prl_ga.ga_len) { - if (si->sn_prl_execed) { - pp = &PRL_ITEM(si, si->sn_prl_idx); - pp->snp_count++; - si->sn_prl_start = profile_end(si->sn_prl_start); - si->sn_prl_start = profile_sub_wait(si->sn_prl_wait, si->sn_prl_start); - pp->sn_prl_total = profile_add(pp->sn_prl_total, si->sn_prl_start); - pp->sn_prl_self = profile_self(pp->sn_prl_self, si->sn_prl_start, - si->sn_prl_children); - } - si->sn_prl_idx = -1; - } -} - -/// ":scriptencoding": Set encoding conversion for a sourced script. -/// Without the multi-byte feature it's simply ignored. -void ex_scriptencoding(exarg_T *eap) -{ - struct source_cookie *sp; - char *name; - - if (!getline_equal(eap->getline, eap->cookie, getsourceline)) { - emsg(_("E167: :scriptencoding used outside of a sourced file")); - return; - } - - if (*eap->arg != NUL) { - name = (char *)enc_canonize((char_u *)eap->arg); - } else { - name = eap->arg; - } - - // Setup for conversion from the specified encoding to 'encoding'. - sp = (struct source_cookie *)getline_cookie(eap->getline, eap->cookie); - convert_setup(&sp->conv, (char_u *)name, p_enc); - - if (name != eap->arg) { - xfree(name); - } -} - -/// ":finish": Mark a sourced file as finished. -void ex_finish(exarg_T *eap) -{ - if (getline_equal(eap->getline, eap->cookie, getsourceline)) { - do_finish(eap, false); - } else { - emsg(_("E168: :finish used outside of a sourced file")); - } -} - -/// Mark a sourced file as finished. Possibly makes the ":finish" pending. -/// Also called for a pending finish at the ":endtry" or after returning from -/// an extra do_cmdline(). "reanimate" is used in the latter case. -void do_finish(exarg_T *eap, int reanimate) -{ - int idx; - - if (reanimate) { - ((struct source_cookie *)getline_cookie(eap->getline, - eap->cookie))->finished = false; - } - - // Cleanup (and deactivate) conditionals, but stop when a try conditional - // not in its finally clause (which then is to be executed next) is found. - // In this case, make the ":finish" pending for execution at the ":endtry". - // Otherwise, finish normally. - idx = cleanup_conditionals(eap->cstack, 0, true); - if (idx >= 0) { - eap->cstack->cs_pending[idx] = CSTP_FINISH; - report_make_pending(CSTP_FINISH, NULL); - } else { - ((struct source_cookie *)getline_cookie(eap->getline, - eap->cookie))->finished = true; - } -} - -/// @return true when a sourced file had the ":finish" command: Don't give error -/// message for missing ":endif". -/// false when not sourcing a file. -bool source_finished(LineGetter fgetline, void *cookie) -{ - return getline_equal(fgetline, cookie, getsourceline) - && ((struct source_cookie *)getline_cookie(fgetline, cookie))->finished; -} - /// ":checktime [buffer]" void ex_checktime(exarg_T *eap) { @@ -2631,324 +772,6 @@ void ex_checktime(exarg_T *eap) no_check_timestamps = save_no_check_timestamps; } -#if defined(HAVE_LOCALE_H) -# define HAVE_GET_LOCALE_VAL - -static char *get_locale_val(int what) -{ - // Obtain the locale value from the libraries. - char *loc = setlocale(what, NULL); - - return loc; -} -#endif - -/// @return true when "lang" starts with a valid language name. -/// Rejects NULL, empty string, "C", "C.UTF-8" and others. -static bool is_valid_mess_lang(char *lang) -{ - return lang != NULL && ASCII_ISALPHA(lang[0]) && ASCII_ISALPHA(lang[1]); -} - -/// Obtain the current messages language. Used to set the default for -/// 'helplang'. May return NULL or an empty string. -char *get_mess_lang(void) -{ - char *p; - -#ifdef HAVE_GET_LOCALE_VAL -# if defined(LC_MESSAGES) - p = get_locale_val(LC_MESSAGES); -# else - // This is necessary for Win32, where LC_MESSAGES is not defined and $LANG - // may be set to the LCID number. LC_COLLATE is the best guess, LC_TIME - // and LC_MONETARY may be set differently for a Japanese working in the - // US. - p = get_locale_val(LC_COLLATE); -# endif -#else - p = os_getenv("LC_ALL"); - if (!is_valid_mess_lang(p)) { - p = os_getenv("LC_MESSAGES"); - if (!is_valid_mess_lang(p)) { - p = os_getenv("LANG"); - } - } -#endif - return is_valid_mess_lang(p) ? p : NULL; -} - -// Complicated #if; matches with where get_mess_env() is used below. -#ifdef HAVE_WORKING_LIBINTL -/// Get the language used for messages from the environment. -static char *get_mess_env(void) -{ - char *p; - - p = (char *)os_getenv("LC_ALL"); - if (p == NULL) { - p = (char *)os_getenv("LC_MESSAGES"); - if (p == NULL) { - p = (char *)os_getenv("LANG"); - if (p != NULL && ascii_isdigit(*p)) { - p = NULL; // ignore something like "1043" - } -# ifdef HAVE_GET_LOCALE_VAL - if (p == NULL) { - p = get_locale_val(LC_CTYPE); - } -# endif - } - } - return p; -} - -#endif - -/// Set the "v:lang" variable according to the current locale setting. -/// Also do "v:lc_time"and "v:ctype". -void set_lang_var(void) -{ - const char *loc; - -#ifdef HAVE_GET_LOCALE_VAL - loc = get_locale_val(LC_CTYPE); -#else - // setlocale() not supported: use the default value - loc = "C"; -#endif - set_vim_var_string(VV_CTYPE, loc, -1); - - // When LC_MESSAGES isn't defined use the value from $LC_MESSAGES, fall - // back to LC_CTYPE if it's empty. -#ifdef HAVE_WORKING_LIBINTL - loc = get_mess_env(); -#elif defined(LC_MESSAGES) - loc = get_locale_val(LC_MESSAGES); -#else - // In Windows LC_MESSAGES is not defined fallback to LC_CTYPE - loc = get_locale_val(LC_CTYPE); -#endif - set_vim_var_string(VV_LANG, loc, -1); - -#ifdef HAVE_GET_LOCALE_VAL - loc = get_locale_val(LC_TIME); -#endif - set_vim_var_string(VV_LC_TIME, loc, -1); - -#ifdef HAVE_GET_LOCALE_VAL - loc = get_locale_val(LC_COLLATE); -#else - // setlocale() not supported: use the default value - loc = "C"; -#endif - set_vim_var_string(VV_COLLATE, loc, -1); -} - -#ifdef HAVE_WORKING_LIBINTL - -/// ":language": Set the language (locale). -/// -/// @param eap -void ex_language(exarg_T *eap) -{ - char *loc; - char *p; - char *name; - int what = LC_ALL; - char *whatstr = ""; -# ifdef LC_MESSAGES -# define VIM_LC_MESSAGES LC_MESSAGES -# else -# define VIM_LC_MESSAGES 6789 -# endif - - name = eap->arg; - - // Check for "messages {name}", "ctype {name}" or "time {name}" argument. - // Allow abbreviation, but require at least 3 characters to avoid - // confusion with a two letter language name "me" or "ct". - p = (char *)skiptowhite((char_u *)eap->arg); - if ((*p == NUL || ascii_iswhite(*p)) && p - eap->arg >= 3) { - if (STRNICMP(eap->arg, "messages", p - eap->arg) == 0) { - what = VIM_LC_MESSAGES; - name = skipwhite(p); - whatstr = "messages "; - } else if (STRNICMP(eap->arg, "ctype", p - eap->arg) == 0) { - what = LC_CTYPE; - name = skipwhite(p); - whatstr = "ctype "; - } else if (STRNICMP(eap->arg, "time", p - eap->arg) == 0) { - what = LC_TIME; - name = skipwhite(p); - whatstr = "time "; - } else if (STRNICMP(eap->arg, "collate", p - eap->arg) == 0) { - what = LC_COLLATE; - name = skipwhite(p); - whatstr = "collate "; - } - } - - if (*name == NUL) { - if (what == VIM_LC_MESSAGES) { - p = get_mess_env(); - } else { - p = setlocale(what, NULL); - } - if (p == NULL || *p == NUL) { - p = "Unknown"; - } - smsg(_("Current %slanguage: \"%s\""), whatstr, p); - } else { -# ifndef LC_MESSAGES - if (what == VIM_LC_MESSAGES) { - loc = ""; - } else { -# endif - loc = setlocale(what, name); -# ifdef LC_NUMERIC - // Make sure strtod() uses a decimal point, not a comma. - setlocale(LC_NUMERIC, "C"); -# endif -# ifndef LC_MESSAGES - } -# endif - if (loc == NULL) { - semsg(_("E197: Cannot set language to \"%s\""), name); - } else { -# ifdef HAVE_NL_MSG_CAT_CNTR - // Need to do this for GNU gettext, otherwise cached translations - // will be used again. - extern int _nl_msg_cat_cntr; - - _nl_msg_cat_cntr++; -# endif - // Reset $LC_ALL, otherwise it would overrule everything. - os_setenv("LC_ALL", "", 1); - - if (what != LC_TIME && what != LC_COLLATE) { - // Tell gettext() what to translate to. It apparently doesn't - // use the currently effective locale. - if (what == LC_ALL) { - os_setenv("LANG", name, 1); - - // Clear $LANGUAGE because GNU gettext uses it. - os_setenv("LANGUAGE", "", 1); - } - if (what != LC_CTYPE) { - os_setenv("LC_MESSAGES", name, 1); - set_helplang_default(name); - } - } - - // Set v:lang, v:lc_time, v:collate and v:ctype to the final result. - set_lang_var(); - maketitle(); - } - } -} - -static char **locales = NULL; // Array of all available locales - -# ifndef WIN32 -static bool did_init_locales = false; - -/// @return an array of strings for all available locales + NULL for the -/// last element or, -/// NULL in case of error. -static char **find_locales(void) -{ - garray_T locales_ga; - char *loc; - char *saveptr = NULL; - - // 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); - if (locale_a == NULL) { - return NULL; - } - ga_init(&locales_ga, sizeof(char_u *), 20); - - // Transform locale_a string where each locale is separated by "\n" - // into an array of locale strings. - loc = os_strtok(locale_a, "\n", &saveptr); - - while (loc != NULL) { - loc = xstrdup(loc); - GA_APPEND(char *, &locales_ga, loc); - loc = os_strtok(NULL, "\n", &saveptr); - } - 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; - return locales_ga.ga_data; -} -# endif - -/// Lazy initialization of all available locales. -static void init_locales(void) -{ -# ifndef WIN32 - if (!did_init_locales) { - 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); - } -} - -# endif - -/// Function given to ExpandGeneric() to obtain the possible arguments of the -/// ":language" command. -char *get_lang_arg(expand_T *xp, int idx) -{ - if (idx == 0) { - return "messages"; - } - if (idx == 1) { - return "ctype"; - } - if (idx == 2) { - return "time"; - } - if (idx == 3) { - return "collate"; - } - - init_locales(); - if (locales == NULL) { - return NULL; - } - return locales[idx - 4]; -} - -/// Function given to ExpandGeneric() to obtain the available locales. -char *get_locales(expand_T *xp, int idx) -{ - init_locales(); - if (locales == NULL) { - return NULL; - } - return locales[idx]; -} - -#endif - static void script_host_execute(char *name, exarg_T *eap) { size_t len; @@ -3007,7 +830,7 @@ void ex_drop(exarg_T *eap) // and mostly only one file is dropped. // This also ignores wildcards, since it is very unlikely the user is // editing a file name with a wildcard character. - do_arglist(eap->arg, AL_SET, 0, false); + set_arglist(eap->arg); // Expanding wildcards may result in an empty argument list. E.g. when // editing "foo.pyc" and ".pyc" is in 'wildignore'. Assume that we diff --git a/src/nvim/ex_cmds2.h b/src/nvim/ex_cmds2.h index c463bfa5ab..3a41e105f3 100644 --- a/src/nvim/ex_cmds2.h +++ b/src/nvim/ex_cmds2.h @@ -1,10 +1,7 @@ #ifndef NVIM_EX_CMDS2_H #define NVIM_EX_CMDS2_H -#include <stdbool.h> - -#include "nvim/ex_docmd.h" -#include "nvim/runtime.h" +#include "nvim/ex_cmds_defs.h" // // flags for check_changed() @@ -15,27 +12,6 @@ #define CCGD_ALLBUF 8 // may write all buffers #define CCGD_EXCMD 16 // may suggest using ! -typedef struct scriptitem_S { - char_u *sn_name; - bool sn_prof_on; ///< true when script is/was profiled - bool sn_pr_force; ///< forceit: profile functions in this script - proftime_T sn_pr_child; ///< time set when going into first child - int sn_pr_nest; ///< nesting for sn_pr_child - // profiling the script as a whole - int sn_pr_count; ///< nr of times sourced - proftime_T sn_pr_total; ///< time spent in script + children - proftime_T sn_pr_self; ///< time spent in script itself - proftime_T sn_pr_start; ///< time at script start - proftime_T sn_pr_children; ///< time in children after script start - // profiling the script per line - garray_T sn_prl_ga; ///< things stored for every line - proftime_T sn_prl_start; ///< start time for current line - proftime_T sn_prl_children; ///< time spent in children for this line - proftime_T sn_prl_wait; ///< wait start time for current line - linenr_T sn_prl_idx; ///< index of line being timed; -1 if none - int sn_prl_execed; ///< line being timed was executed -} scriptitem_T; - #ifdef INCLUDE_GENERATED_DECLARATIONS # include "ex_cmds2.h.generated.h" #endif diff --git a/src/nvim/ex_cmds_defs.h b/src/nvim/ex_cmds_defs.h index e80e47bcff..71956b2246 100644 --- a/src/nvim/ex_cmds_defs.h +++ b/src/nvim/ex_cmds_defs.h @@ -188,7 +188,7 @@ struct exarg { cmdidx_T cmdidx; ///< the index for the command uint32_t argt; ///< flags for the command int skip; ///< don't execute the command, only parse it - int forceit; ///< TRUE if ! present + int forceit; ///< true if ! present int addr_count; ///< the number of addresses given linenr_T line1; ///< the first line number linenr_T line2; ///< the second line number or count @@ -196,8 +196,8 @@ struct exarg { int flags; ///< extra flags after count: EXFLAG_ char *do_ecmd_cmd; ///< +command arg to be used in edited file linenr_T do_ecmd_lnum; ///< the line number in an edited file - int append; ///< TRUE with ":w >>file" command - int usefilter; ///< TRUE with ":w !command" and ":r!command" + int append; ///< true with ":w >>file" command + int usefilter; ///< true with ":w !command" and ":r!command" int amount; ///< number of '>' or '<' for shift command int regname; ///< register name (NUL if none) int force_bin; ///< 0, FORCE_BIN or FORCE_NOBIN @@ -230,13 +230,15 @@ struct expand { sctx_T xp_script_ctx; // SCTX for completion function int xp_backslash; // one of the XP_BS_ values #ifndef BACKSLASH_IN_FILENAME - int xp_shell; // TRUE for a shell command, more + int xp_shell; // true for a shell command, more // characters need to be escaped #endif int xp_numfiles; // number of files found by file name completion int xp_col; // cursor position in line char **xp_files; // list of files char *xp_line; // text being completed +#define EXPAND_BUF_LEN 256 + char xp_buf[EXPAND_BUF_LEN]; // buffer for returned match }; // values for xp_backslash diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 0dde82c235..26b0c0f1ef 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -9,14 +9,18 @@ #include <stdlib.h> #include <string.h> +#include "nvim/arglist.h" #include "nvim/ascii.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/userfunc.h" @@ -38,10 +42,12 @@ #include "nvim/getchar.h" #include "nvim/globals.h" #include "nvim/hardcopy.h" +#include "nvim/help.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/main.h" #include "nvim/mapping.h" @@ -57,15 +63,16 @@ #include "nvim/normal.h" #include "nvim/ops.h" #include "nvim/option.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/popupmnu.h" +#include "nvim/popupmenu.h" +#include "nvim/profile.h" #include "nvim/quickfix.h" #include "nvim/regexp.h" -#include "nvim/screen.h" #include "nvim/search.h" #include "nvim/shada.h" #include "nvim/sign.h" @@ -88,6 +95,12 @@ static char e_ambiguous_use_of_user_defined_command[] = N_("E464: Ambiguous use of user-defined command"); static char e_not_an_editor_command[] = N_("E492: Not an editor command"); +static char e_no_source_file_name_to_substitute_for_sfile[] + = N_("E498: no :source file name to substitute for \"<sfile>\""); +static char e_no_call_stack_to_substitute_for_stack[] + = N_("E489: no call stack to substitute for \"<stack>\""); +static char e_no_script_file_name_to_substitute_for_script[] + = N_("E1274: No script file name to substitute for \"<script>\""); static int quitmore = 0; static bool ex_pressedreturn = false; @@ -100,16 +113,14 @@ typedef struct { #define FREE_WCMD(wcmd) xfree((wcmd)->line) -/* - * Structure used to store info for line position in a while or for loop. - * This is required, because do_one_cmd() may invoke ex_function(), which - * reads more lines that may come from the while/for loop. - */ +/// Structure used to store info for line position in a while or for loop. +/// This is required, because do_one_cmd() may invoke ex_function(), which +/// reads more lines that may come from the while/for loop. struct loop_cookie { garray_T *lines_gap; // growarray with line info int current_line; // last read line from growarray - int repeating; // TRUE when looping a second time - // When "repeating" is FALSE use "getline" and "cookie" to get lines + int repeating; // true when looping a second time + // When "repeating" is false use "getline" and "cookie" to get lines char *(*getline)(int, void *, int, bool); void *cookie; }; @@ -123,6 +134,7 @@ struct dbg_stuff { char *vv_throwpoint; int did_emsg; int got_int; + bool did_throw; int need_rethrow; int check_cstack; except_T *current_exception; @@ -136,9 +148,7 @@ struct dbg_stuff { # define ex_language ex_ni #endif -/* - * Declare cmdnames[]. - */ +// Declare cmdnames[]. #ifdef INCLUDE_GENERATED_DECLARATIONS # include "ex_cmds_defs.generated.h" #endif @@ -148,7 +158,7 @@ static char dollar_command[2] = { '$', 0 }; static void save_dbg_stuff(struct dbg_stuff *dsp) { dsp->trylevel = trylevel; trylevel = 0; - dsp->force_abort = force_abort; force_abort = FALSE; + dsp->force_abort = force_abort; force_abort = false; dsp->caught_stack = caught_stack; caught_stack = NULL; dsp->vv_exception = v_exception(NULL); dsp->vv_throwpoint = v_throwpoint(NULL); @@ -156,6 +166,7 @@ static void save_dbg_stuff(struct dbg_stuff *dsp) // Necessary for debugging an inactive ":catch", ":finally", ":endtry". dsp->did_emsg = did_emsg; did_emsg = false; dsp->got_int = got_int; got_int = false; + dsp->did_throw = did_throw; did_throw = false; dsp->need_rethrow = need_rethrow; need_rethrow = false; dsp->check_cstack = check_cstack; check_cstack = false; dsp->current_exception = current_exception; current_exception = NULL; @@ -171,6 +182,7 @@ static void restore_dbg_stuff(struct dbg_stuff *dsp) (void)v_throwpoint(dsp->vv_throwpoint); did_emsg = dsp->did_emsg; got_int = dsp->got_int; + did_throw = dsp->did_throw; need_rethrow = dsp->need_rethrow; check_cstack = dsp->check_cstack; current_exception = dsp->current_exception; @@ -179,11 +191,6 @@ static void restore_dbg_stuff(struct dbg_stuff *dsp) /// Repeatedly get commands for Ex mode, until the ":vi" command is given. void do_exmode(void) { - int save_msg_scroll; - int prev_msg_row; - linenr_T prev_line; - varnumber_T changedtick; - exmode_active = true; State = MODE_NORMAL; may_trigger_modechanged(); @@ -194,7 +201,7 @@ void do_exmode(void) return; } - save_msg_scroll = msg_scroll; + int save_msg_scroll = msg_scroll; RedrawingDisabled++; // don't redisplay the window no_wait_return++; // don't wait for return @@ -209,9 +216,9 @@ void do_exmode(void) need_wait_return = false; ex_pressedreturn = false; ex_no_reprint = false; - changedtick = buf_get_changedtick(curbuf); - prev_msg_row = msg_row; - prev_line = curwin->w_cursor.lnum; + varnumber_T changedtick = buf_get_changedtick(curbuf); + int prev_msg_row = msg_row; + linenr_T prev_line = curwin->w_cursor.lnum; cmdline_row = msg_row; do_cmdline(NULL, getexline, NULL, 0); lines_left = Rows - 1; @@ -232,7 +239,7 @@ void do_exmode(void) } } msg_col = 0; - print_line_no_prefix(curwin->w_cursor.lnum, FALSE, FALSE); + print_line_no_prefix(curwin->w_cursor.lnum, false, false); msg_clr_eos(); } } else if (ex_pressedreturn && !ex_no_reprint) { // must be at EOF @@ -246,8 +253,8 @@ void do_exmode(void) RedrawingDisabled--; no_wait_return--; - redraw_all_later(NOT_VALID); - update_screen(NOT_VALID); + redraw_all_later(UPD_NOT_VALID); + update_screen(UPD_NOT_VALID); need_wait_return = false; msg_scroll = save_msg_scroll; } @@ -301,26 +308,26 @@ int do_cmdline_cmd(const char *cmd) /// @return FAIL if cmdline could not be executed, OK otherwise int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) { - char *next_cmdline; // next cmd to execute - char *cmdline_copy = NULL; // copy of cmd line + char *next_cmdline; // next cmd to execute + char *cmdline_copy = NULL; // copy of cmd line bool used_getline = false; // used "fgetline" to obtain command static int recursive = 0; // recursive depth bool msg_didout_before_start = false; int count = 0; // line number count - int did_inc = FALSE; // incremented RedrawingDisabled + bool did_inc = false; // incremented RedrawingDisabled int retval = OK; cstack_T cstack = { // conditional stack .cs_idx = -1, }; garray_T lines_ga; // keep lines for ":while"/":for" int current_line = 0; // active line in lines_ga - char *fname = NULL; // function or script name + char *fname = NULL; // function or script name linenr_T *breakpoint = NULL; // ptr to breakpoint field in cookie - int *dbg_tick = NULL; // ptr to dbg_tick field in cookie + int *dbg_tick = NULL; // ptr to dbg_tick field in cookie struct dbg_stuff debug_saved; // saved things for debug mode int initial_trylevel; - struct msglist **saved_msg_list = NULL; - struct msglist *private_msg_list; + msglist_T **saved_msg_list = NULL; + msglist_T *private_msg_list; // "fgetline" and "cookie" passed to do_one_cmd() char *(*cmd_getline)(int, void *, int, bool); @@ -361,7 +368,7 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) // Inside a function use a higher nesting level. getline_is_func = getline_equal(fgetline, cookie, get_func_line); if (getline_is_func && ex_nesting_level == func_level(real_cookie)) { - ++ex_nesting_level; + ex_nesting_level++; } // Get the function or script name and the address where the next breakpoint @@ -371,14 +378,12 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) breakpoint = func_breakpoint(real_cookie); dbg_tick = func_dbg_tick(real_cookie); } else if (getline_equal(fgetline, cookie, getsourceline)) { - fname = sourcing_name; + fname = SOURCING_NAME; breakpoint = source_breakpoint(real_cookie); dbg_tick = source_dbg_tick(real_cookie); } - /* - * Initialize "force_abort" and "suppress_errthrow" at the top level. - */ + // Initialize "force_abort" and "suppress_errthrow" at the top level. if (!recursive) { force_abort = false; suppress_errthrow = false; @@ -390,13 +395,14 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) if (flags & DOCMD_EXCRESET) { save_dbg_stuff(&debug_saved); } else { - memset(&debug_saved, 0, sizeof(debug_saved)); + CLEAR_FIELD(debug_saved); } initial_trylevel = trylevel; - current_exception = NULL; - // "did_emsg" will be set to TRUE when emsg() is used, in which case we + // "did_throw" will be set to true when an exception is being thrown. + did_throw = false; + // "did_emsg" will be set to true when emsg() is used, in which case we // cancel the whole command line, and any if/endif or loop. // If force_abort is set, we cancel everything. did_emsg = false; @@ -408,12 +414,10 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) KeyTyped = false; } - /* - * Continue executing command lines: - * - when inside an ":if", ":while" or ":for" - * - for multiple commands on one line, separated with '|' - * - when repeating until there are no more lines (for ":source") - */ + // Continue executing command lines: + // - when inside an ":if", ":while" or ":for" + // - for multiple commands on one line, separated with '|' + // - when repeating until there are no more lines (for ":source") next_cmdline = cmdline; do { getline_is_func = getline_equal(fgetline, cookie, get_func_line); @@ -427,11 +431,9 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) did_emsg = false; } - /* - * 1. If repeating a line in a loop, get a line from lines_ga. - * 2. If no line given: Get an allocated line with fgetline(). - * 3. If a line is given: Make a copy, so we can mess with it. - */ + // 1. If repeating a line in a loop, get a line from lines_ga. + // 2. If no line given: Get an allocated line with fgetline(). + // 3. If a line is given: Make a copy, so we can mess with it. // 1. If repeating, get a previous line from lines_ga. if (cstack.cs_looplevel > 0 && current_line < lines_ga.ga_len) { @@ -464,20 +466,19 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) if (breakpoint != NULL && dbg_tick != NULL && *dbg_tick != debug_tick) { *breakpoint = dbg_find_breakpoint(getline_equal(fgetline, cookie, getsourceline), - (char_u *)fname, sourcing_lnum); + fname, SOURCING_LNUM); *dbg_tick = debug_tick; } next_cmdline = ((wcmd_T *)(lines_ga.ga_data))[current_line].line; - sourcing_lnum = ((wcmd_T *)(lines_ga.ga_data))[current_line].lnum; + SOURCING_LNUM = ((wcmd_T *)(lines_ga.ga_data))[current_line].lnum; // Did we encounter a breakpoint? - if (breakpoint != NULL && *breakpoint != 0 - && *breakpoint <= sourcing_lnum) { - dbg_breakpoint((char_u *)fname, sourcing_lnum); + if (breakpoint != NULL && *breakpoint != 0 && *breakpoint <= SOURCING_LNUM) { + dbg_breakpoint(fname, SOURCING_LNUM); // Find next breakpoint. *breakpoint = dbg_find_breakpoint(getline_equal(fgetline, cookie, getsourceline), - (char_u *)fname, sourcing_lnum); + fname, SOURCING_LNUM); *dbg_tick = debug_tick; } if (do_profiling == PROF_YES) { @@ -509,10 +510,8 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) // 2. If no line given, get an allocated line with fgetline(). if (next_cmdline == NULL) { - /* - * Need to set msg_didout for the first line after an ":if", - * otherwise the ":if" will be overwritten. - */ + // Need to set msg_didout for the first line after an ":if", + // otherwise the ":if" will be overwritten. if (count == 1 && getline_equal(fgetline, cookie, getexline)) { msg_didout = true; } @@ -521,7 +520,7 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) cstack.cs_idx < 0 ? 0 : (cstack.cs_idx + 1) * 2, true)) == NULL) { - // Don't call wait_return for aborted command line. The NULL + // Don't call wait_return() for aborted command line. The NULL // returned for the end of a sourced file or executed function // doesn't do this. if (KeyTyped && !(flags & DOCMD_REPEAT)) { @@ -532,13 +531,11 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) } used_getline = true; - /* - * Keep the first typed line. Clear it when more lines are typed. - */ + // Keep the first typed line. Clear it when more lines are typed. if (flags & DOCMD_KEEPLINE) { xfree(repeat_cmdline); if (count == 0) { - repeat_cmdline = vim_strsave((char_u *)next_cmdline); + repeat_cmdline = (char *)vim_strsave((char_u *)next_cmdline); } else { repeat_cmdline = NULL; } @@ -549,13 +546,11 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) } cmdline_copy = next_cmdline; - /* - * Save the current line when inside a ":while" or ":for", and when - * the command looks like a ":while" or ":for", because we may need it - * later. When there is a '|' and another command, it is stored - * separately, because we need to be able to jump back to it from an - * :endwhile/:endfor. - */ + // Save the current line when inside a ":while" or ":for", and when + // the command looks like a ":while" or ":for", because we may need it + // later. When there is a '|' and another command, it is stored + // separately, because we need to be able to jump back to it from an + // :endwhile/:endfor. if (current_line == lines_ga.ga_len && (cstack.cs_looplevel || has_loop_cmd(next_cmdline))) { store_loop_line(&lines_ga, next_cmdline); @@ -563,32 +558,28 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) did_endif = false; if (count++ == 0) { - /* - * All output from the commands is put below each other, without - * waiting for a return. Don't do this when executing commands - * from a script or when being called recursive (e.g. for ":e - * +command file"). - */ + // All output from the commands is put below each other, without + // waiting for a return. Don't do this when executing commands + // from a script or when being called recursive (e.g. for ":e + // +command file"). if (!(flags & DOCMD_NOWAIT) && !recursive) { msg_didout_before_start = msg_didout; msg_didany = false; // no output yet msg_start(); - msg_scroll = TRUE; // put messages below each other - ++no_wait_return; // don't wait for return until finished - ++RedrawingDisabled; - did_inc = TRUE; + msg_scroll = true; // put messages below each other + no_wait_return++; // don't wait for return until finished + RedrawingDisabled++; + did_inc = true; } } - if ((p_verbose >= 15 && sourcing_name != NULL) || p_verbose >= 16) { - msg_verbose_cmd(sourcing_lnum, cmdline_copy); + if ((p_verbose >= 15 && SOURCING_NAME != NULL) || p_verbose >= 16) { + msg_verbose_cmd(SOURCING_LNUM, cmdline_copy); } - /* - * 2. Execute one '|' separated command. - * do_one_cmd() will return NULL if there is no trailing '|'. - * "cmdline_copy" can change, e.g. for '%' and '#' expansion. - */ + // 2. Execute one '|' separated command. + // do_one_cmd() will return NULL if there is no trailing '|'. + // "cmdline_copy" can change, e.g. for '%' and '#' expansion. recursive++; next_cmdline = do_one_cmd(&cmdline_copy, flags, &cstack, cmd_getline, cmd_cookie); recursive--; @@ -601,10 +592,9 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) if (next_cmdline == NULL) { XFREE_CLEAR(cmdline_copy); - // + // If the command was typed, remember it for the ':' register. // Do this AFTER executing the command to make :@: work. - // if (getline_equal(fgetline, cookie, getexline) && new_last_cmdline != NULL) { xfree(last_cmdline); @@ -622,18 +612,16 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) if (did_emsg && !force_abort && getline_equal(fgetline, cookie, get_func_line) && !func_has_abort(real_cookie)) { - did_emsg = FALSE; + did_emsg = false; } if (cstack.cs_looplevel > 0) { - ++current_line; - - /* - * An ":endwhile", ":endfor" and ":continue" is handled here. - * If we were executing commands, jump back to the ":while" or - * ":for". - * If we were not executing commands, decrement cs_looplevel. - */ + current_line++; + + // An ":endwhile", ":endfor" and ":continue" is handled here. + // If we were executing commands, jump back to the ":while" or + // ":for". + // If we were not executing commands, decrement cs_looplevel. if (cstack.cs_lflags & (CSL_HAD_CONT | CSL_HAD_ENDLOOP)) { cstack.cs_lflags &= ~(CSL_HAD_CONT | CSL_HAD_ENDLOOP); @@ -641,7 +629,7 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) // not to use a cs_line[] from an entry that isn't a ":while" // or ":for": It would make "current_line" invalid and can // cause a crash. - if (!did_emsg && !got_int && !current_exception + if (!did_emsg && !got_int && !did_throw && cstack.cs_idx >= 0 && (cstack.cs_flags[cstack.cs_idx] & (CSF_WHILE | CSF_FOR)) @@ -655,8 +643,7 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) // Check for the next breakpoint at or after the ":while" // or ":for". if (breakpoint != NULL) { - *breakpoint = dbg_find_breakpoint(getline_equal(fgetline, cookie, getsourceline), - (char_u *)fname, + *breakpoint = dbg_find_breakpoint(getline_equal(fgetline, cookie, getsourceline), fname, ((wcmd_T *)lines_ga.ga_data)[current_line].lnum - 1); *dbg_tick = debug_tick; } @@ -667,41 +654,33 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) CSF_WHILE | CSF_FOR, &cstack.cs_looplevel); } } - } - /* - * For a ":while" or ":for" we need to remember the line number. - */ - else if (cstack.cs_lflags & CSL_HAD_LOOP) { + } else if (cstack.cs_lflags & CSL_HAD_LOOP) { + // For a ":while" or ":for" we need to remember the line number. cstack.cs_lflags &= ~CSL_HAD_LOOP; cstack.cs_line[cstack.cs_idx] = current_line - 1; } } - /* - * When not inside any ":while" loop, clear remembered lines. - */ + // When not inside any ":while" loop, clear remembered lines. if (cstack.cs_looplevel == 0) { if (!GA_EMPTY(&lines_ga)) { - sourcing_lnum = ((wcmd_T *)lines_ga.ga_data)[lines_ga.ga_len - 1].lnum; + SOURCING_LNUM = ((wcmd_T *)lines_ga.ga_data)[lines_ga.ga_len - 1].lnum; GA_DEEP_CLEAR(&lines_ga, wcmd_T, FREE_WCMD); } current_line = 0; } - /* - * A ":finally" makes did_emsg, got_int and current_exception pending for - * being restored at the ":endtry". Reset them here and set the - * ACTIVE and FINALLY flags, so that the finally clause gets executed. - * This includes the case where a missing ":endif", ":endwhile" or - * ":endfor" was detected by the ":finally" itself. - */ + // A ":finally" makes did_emsg, got_int and did_throw pending for + // being restored at the ":endtry". Reset them here and set the + // ACTIVE and FINALLY flags, so that the finally clause gets executed. + // This includes the case where a missing ":endif", ":endwhile" or + // ":endfor" was detected by the ":finally" itself. if (cstack.cs_lflags & CSL_HAD_FINA) { cstack.cs_lflags &= ~CSL_HAD_FINA; report_make_pending((cstack.cs_pending[cstack.cs_idx] & (CSTP_ERROR | CSTP_INTERRUPT | CSTP_THROW)), - current_exception); - did_emsg = got_int = false; - current_exception = NULL; + did_throw ? current_exception : NULL); + did_emsg = got_int = did_throw = false; cstack.cs_flags[cstack.cs_idx] |= CSF_ACTIVE | CSF_FINALLY; } @@ -714,45 +693,41 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) // exception, cancel everything. If it is left normally, reset // force_abort to get the non-EH compatible abortion behavior for // the rest of the script. - if (trylevel == 0 && !did_emsg && !got_int && !current_exception) { + if (trylevel == 0 && !did_emsg && !got_int && !did_throw) { force_abort = false; } // Convert an interrupt to an exception if appropriate. (void)do_intthrow(&cstack); - } - /* - * Continue executing command lines when: - * - no CTRL-C typed, no aborting error, no exception thrown or try - * conditionals need to be checked for executing finally clauses or - * catching an interrupt exception - * - didn't get an error message or lines are not typed - * - there is a command after '|', inside a :if, :while, :for or :try, or - * looping for ":source" command or function call. - */ - while (!((got_int || (did_emsg && force_abort) || current_exception) - && cstack.cs_trylevel == 0) - && !(did_emsg - // Keep going when inside try/catch, so that the error can be - // deal with, except when it is a syntax error, it may cause - // the :endtry to be missed. - && (cstack.cs_trylevel == 0 || did_emsg_syntax) - && used_getline - && getline_equal(fgetline, cookie, getexline)) - && (next_cmdline != NULL - || cstack.cs_idx >= 0 - || (flags & DOCMD_REPEAT))); + + // Continue executing command lines when: + // - no CTRL-C typed, no aborting error, no exception thrown or try + // conditionals need to be checked for executing finally clauses or + // catching an interrupt exception + // - didn't get an error message or lines are not typed + // - there is a command after '|', inside a :if, :while, :for or :try, or + // looping for ":source" command or function call. + } while (!((got_int || (did_emsg && force_abort) || did_throw) + && cstack.cs_trylevel == 0) + && !(did_emsg + // Keep going when inside try/catch, so that the error can be + // deal with, except when it is a syntax error, it may cause + // the :endtry to be missed. + && (cstack.cs_trylevel == 0 || did_emsg_syntax) + && used_getline + && getline_equal(fgetline, cookie, getexline)) + && (next_cmdline != NULL + || cstack.cs_idx >= 0 + || (flags & DOCMD_REPEAT))); xfree(cmdline_copy); did_emsg_syntax = false; GA_DEEP_CLEAR(&lines_ga, wcmd_T, FREE_WCMD); if (cstack.cs_idx >= 0) { - /* - * If a sourced file or executed function ran to its end, report the - * unclosed conditional. - */ - if (!got_int && !current_exception + // If a sourced file or executed function ran to its end, report the + // unclosed conditional. + if (!got_int && !did_throw && ((getline_equal(fgetline, cookie, getsourceline) && !source_finished(fgetline, cookie)) || (getline_equal(fgetline, cookie, get_func_line) @@ -768,18 +743,16 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) } } - /* - * Reset "trylevel" in case of a ":finish" or ":return" or a missing - * ":endtry" in a sourced file or executed function. If the try - * conditional is in its finally clause, ignore anything pending. - * If it is in a catch clause, finish the caught exception. - * Also cleanup any "cs_forinfo" structures. - */ + // Reset "trylevel" in case of a ":finish" or ":return" or a missing + // ":endtry" in a sourced file or executed function. If the try + // conditional is in its finally clause, ignore anything pending. + // If it is in a catch clause, finish the caught exception. + // Also cleanup any "cs_forinfo" structures. do { - int idx = cleanup_conditionals(&cstack, 0, TRUE); + int idx = cleanup_conditionals(&cstack, 0, true); if (idx >= 0) { - --idx; // remove try block not in its finally clause + idx--; // remove try block not in its finally clause } rewind_conditionals(&cstack, idx, CSF_WHILE | CSF_FOR, &cstack.cs_looplevel); @@ -797,19 +770,16 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) // conditional, discard the uncaught exception, disable the conversion // of interrupts or errors to exceptions, and ensure that no more // commands are executed. - if (current_exception) { + if (did_throw) { + assert(current_exception != NULL); char *p = NULL; - char *saved_sourcing_name; - linenr_T saved_sourcing_lnum; - struct msglist *messages = NULL; - struct msglist *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. - */ + 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, @@ -825,10 +795,7 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) break; } - saved_sourcing_name = sourcing_name; - saved_sourcing_lnum = sourcing_lnum; - sourcing_name = current_exception->throw_name; - sourcing_lnum = current_exception->throw_lnum; + estack_push(ETYPE_EXCEPT, current_exception->throw_name, current_exception->throw_lnum); current_exception->throw_name = NULL; discard_current_exception(); // uses IObuff if 'verbose' @@ -848,9 +815,8 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) emsg(p); xfree(p); } - xfree(sourcing_name); - sourcing_name = saved_sourcing_name; - sourcing_lnum = saved_sourcing_lnum; + xfree(SOURCING_NAME); + estack_pop(); } 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 @@ -867,38 +833,34 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) // cstack belongs to the same function or, respectively, script file, it // will have to be checked for finally clauses to be executed due to the // ":return" or ":finish". This is done in do_one_cmd(). - if (current_exception) { + if (did_throw) { need_rethrow = true; } if ((getline_equal(fgetline, cookie, getsourceline) && ex_nesting_level > source_level(real_cookie)) || (getline_equal(fgetline, cookie, get_func_line) && ex_nesting_level > func_level(real_cookie) + 1)) { - if (!current_exception) { + if (!did_throw) { check_cstack = true; } } else { // When leaving a function, reduce nesting level. if (getline_equal(fgetline, cookie, get_func_line)) { - --ex_nesting_level; + ex_nesting_level--; } - /* - * Go to debug mode when returning from a function in which we are - * single-stepping. - */ + // Go to debug mode when returning from a function in which we are + // single-stepping. if ((getline_equal(fgetline, cookie, getsourceline) || getline_equal(fgetline, cookie, get_func_line)) && ex_nesting_level + 1 <= debug_break_level) { do_debug(getline_equal(fgetline, cookie, getsourceline) - ? (char_u *)_("End of sourced file") - : (char_u *)_("End of function")); + ? _("End of sourced file") + : _("End of function")); } } - /* - * Restore the exception environment (done after returning from the - * debugger). - */ + // Restore the exception environment (done after returning from the + // debugger). if (flags & DOCMD_EXCRESET) { restore_dbg_stuff(&debug_saved); } @@ -914,32 +876,26 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) } } - /* - * If there was too much output to fit on the command line, ask the user to - * hit return before redrawing the screen. With the ":global" command we do - * this only once after the command is finished. - */ + // If there was too much output to fit on the command line, ask the user to + // hit return before redrawing the screen. With the ":global" command we do + // this only once after the command is finished. if (did_inc) { - --RedrawingDisabled; - --no_wait_return; - msg_scroll = FALSE; - - /* - * When just finished an ":if"-":else" which was typed, no need to - * wait for hit-return. Also for an error situation. - */ + RedrawingDisabled--; + no_wait_return--; + msg_scroll = false; + + // When just finished an ":if"-":else" which was typed, no need to + // wait for hit-return. Also for an error situation. if (retval == FAIL || (did_endif && KeyTyped && !did_emsg)) { need_wait_return = false; msg_didany = false; // don't wait when restarting edit } else if (need_wait_return) { - /* - * The msg_start() above clears msg_didout. The wait_return we do - * here should not overwrite the command that may be shown before - * doing that. - */ + // The msg_start() above clears msg_didout. The wait_return() we do + // here should not overwrite the command that may be shown before + // doing that. msg_didout |= msg_didout_before_start; - wait_return(FALSE); + wait_return(false); } } @@ -954,13 +910,12 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) static char *get_loop_line(int c, void *cookie, int indent, bool do_concat) { struct loop_cookie *cp = (struct loop_cookie *)cookie; - wcmd_T *wp; - char *line; if (cp->current_line + 1 >= cp->lines_gap->ga_len) { if (cp->repeating) { return NULL; // trying to read past ":endwhile"/":endfor" } + char *line; // First time inside the ":while"/":for": get line normally. if (cp->getline == NULL) { line = (char *)getcmdline(c, 0L, indent, do_concat); @@ -969,7 +924,7 @@ static char *get_loop_line(int c, void *cookie, int indent, bool do_concat) } if (line != NULL) { store_loop_line(cp->lines_gap, line); - ++cp->current_line; + cp->current_line++; } return line; @@ -977,8 +932,8 @@ static char *get_loop_line(int c, void *cookie, int indent, bool do_concat) KeyTyped = false; cp->current_line++; - wp = (wcmd_T *)(cp->lines_gap->ga_data) + cp->current_line; - sourcing_lnum = wp->lnum; + wcmd_T *wp = (wcmd_T *)(cp->lines_gap->ga_data) + cp->current_line; + SOURCING_LNUM = wp->lnum; return xstrdup(wp->line); } @@ -987,23 +942,20 @@ static void store_loop_line(garray_T *gap, char *line) { wcmd_T *p = GA_APPEND_VIA_PTR(wcmd_T, gap); p->line = xstrdup(line); - p->lnum = sourcing_lnum; + p->lnum = SOURCING_LNUM; } -/// If "fgetline" is get_loop_line(), return TRUE if the getline it uses equals -/// "func". * Otherwise return TRUE when "fgetline" equals "func". +/// If "fgetline" is get_loop_line(), return true if the getline it uses equals +/// "func". * Otherwise return true when "fgetline" equals "func". /// /// @param cookie argument for fgetline() -int getline_equal(LineGetter fgetline, void *cookie, LineGetter func) +bool getline_equal(LineGetter fgetline, void *cookie, LineGetter func) { - LineGetter gp; - struct loop_cookie *cp; - // When "fgetline" is "get_loop_line()" use the "cookie" to find the // function that's originally used to obtain the lines. This may be // nested several levels. - gp = fgetline; - cp = (struct loop_cookie *)cookie; + LineGetter gp = fgetline; + struct loop_cookie *cp = (struct loop_cookie *)cookie; while (gp == get_loop_line) { gp = cp->getline; cp = cp->cookie; @@ -1017,14 +969,11 @@ int getline_equal(LineGetter fgetline, void *cookie, LineGetter func) /// @param cookie argument for fgetline() void *getline_cookie(LineGetter fgetline, void *cookie) { - LineGetter gp; - struct loop_cookie *cp; - // When "fgetline" is "get_loop_line()" use the "cookie" to find the // cookie that's originally used to obtain the lines. This may be nested // several levels. - gp = fgetline; - cp = (struct loop_cookie *)cookie; + LineGetter gp = fgetline; + struct loop_cookie *cp = (struct loop_cookie *)cookie; while (gp == get_loop_line) { gp = cp->getline; cp = cp->cookie; @@ -1038,11 +987,10 @@ void *getline_cookie(LineGetter fgetline, void *cookie) /// @return the buffer number. static int compute_buffer_local_count(cmd_addr_T addr_type, linenr_T lnum, long offset) { - buf_T *buf; buf_T *nextbuf; long count = offset; - buf = firstbuf; + buf_T *buf = firstbuf; while (buf->b_next != NULL && buf->b_fnum < lnum) { buf = buf->b_next; } @@ -1085,7 +1033,7 @@ static int current_win_nr(const win_T *win) int nr = 0; FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - ++nr; + nr++; if (wp == win) { break; } @@ -1098,7 +1046,7 @@ static int current_tab_nr(tabpage_T *tab) int nr = 0; FOR_ALL_TABS(tp) { - ++nr; + nr++; if (tp == tab) { break; } @@ -1385,12 +1333,11 @@ static int parse_count(exarg_T *eap, char **errormsg, bool validate) // Check for a count. When accepting a EX_BUFNAME, don't use "123foo" as a // count, it's a buffer name. char *p; - long n; if ((eap->argt & EX_COUNT) && ascii_isdigit(*eap->arg) && (!(eap->argt & EX_BUFNAME) || *(p = skipdigits(eap->arg + 1)) == NUL || ascii_iswhite(*p))) { - n = getdigits_long(&eap->arg, false, -1); + long n = getdigits_long(&eap->arg, false, -1); eap->arg = skipwhite(eap->arg); if (n <= 0 && (eap->argt & EX_ZEROR) == 0) { if (errormsg != NULL) { @@ -1423,56 +1370,59 @@ bool is_cmd_ni(cmdidx_T cmdidx) /// @return Success or failure bool parse_cmdline(char *cmdline, exarg_T *eap, CmdParseInfo *cmdinfo, char **errormsg) { - char *cmd; - char *p; char *after_modifier = NULL; + bool retval = false; + // parsing the command modifiers may set ex_pressedreturn + const bool save_ex_pressedreturn = ex_pressedreturn; + // parsing the command range may require moving the cursor + const pos_T save_cursor = curwin->w_cursor; + // parsing the command range may set the last search pattern + save_last_search_pattern(); // Initialize cmdinfo - memset(cmdinfo, 0, sizeof(*cmdinfo)); + CLEAR_POINTER(cmdinfo); // Initialize eap - memset(eap, 0, sizeof(*eap)); - eap->line1 = 1; - eap->line2 = 1; - eap->cmd = cmdline; - eap->cmdlinep = &cmdline; - eap->getline = NULL; - eap->cookie = NULL; + *eap = (exarg_T){ + .line1 = 1, + .line2 = 1, + .cmd = cmdline, + .cmdlinep = &cmdline, + .getline = NULL, + .cookie = NULL, + }; - const bool save_ex_pressedreturn = ex_pressedreturn; // Parse command modifiers if (parse_command_modifiers(eap, errormsg, &cmdinfo->cmdmod, false) == FAIL) { - ex_pressedreturn = save_ex_pressedreturn; - goto err; + goto end; } - ex_pressedreturn = save_ex_pressedreturn; after_modifier = eap->cmd; // Save location after command modifiers - cmd = eap->cmd; + char *cmd = eap->cmd; // Skip ranges to find command name since we need the command to know what kind of range it uses eap->cmd = skip_range(eap->cmd, NULL); if (*eap->cmd == '*') { eap->cmd = skipwhite(eap->cmd + 1); } - p = find_ex_command(eap, NULL); + char *p = find_ex_command(eap, NULL); if (p == NULL) { *errormsg = _(e_ambiguous_use_of_user_defined_command); - goto err; + goto end; } // Set command address type and parse command range set_cmd_addr_type(eap, p); eap->cmd = cmd; - if (parse_cmd_address(eap, errormsg, false) == FAIL) { - goto err; + if (parse_cmd_address(eap, errormsg, true) == FAIL) { + goto end; } // Skip colon and whitespace eap->cmd = skip_colon_white(eap->cmd, true); // Fail if command is a comment or if command doesn't exist if (*eap->cmd == NUL || *eap->cmd == '"') { - goto err; + goto end; } // Fail if command is invalid if (eap->cmdidx == CMD_SIZE) { @@ -1481,7 +1431,7 @@ bool parse_cmdline(char *cmdline, exarg_T *eap, CmdParseInfo *cmdinfo, char **er char *cmdname = after_modifier ? after_modifier : cmdline; append_command(cmdname); *errormsg = (char *)IObuff; - goto err; + goto end; } // Correctly set 'forceit' for commands @@ -1520,12 +1470,12 @@ bool parse_cmdline(char *cmdline, exarg_T *eap, CmdParseInfo *cmdinfo, char **er // Fail if command doesn't support bang but is used with a bang if (!(eap->argt & EX_BANG) && eap->forceit) { *errormsg = _(e_nobang); - goto err; + goto end; } // Fail if command doesn't support a range but it is given a range if (!(eap->argt & EX_RANGE) && eap->addr_count > 0) { *errormsg = _(e_norange); - goto err; + goto end; } // Set default range for command if required if ((eap->argt & EX_DFLALL) && eap->addr_count == 0) { @@ -1535,7 +1485,7 @@ bool parse_cmdline(char *cmdline, exarg_T *eap, CmdParseInfo *cmdinfo, char **er // Parse register and count parse_register(eap); if (parse_count(eap, errormsg, false) == FAIL) { - goto err; + goto end; } // Remove leading whitespace and colon from next command @@ -1551,10 +1501,15 @@ bool parse_cmdline(char *cmdline, exarg_T *eap, CmdParseInfo *cmdinfo, char **er cmdinfo->magic.bar = true; } - return true; -err: - undo_cmdmod(&cmdinfo->cmdmod); - return false; + retval = true; +end: + if (!retval) { + undo_cmdmod(&cmdinfo->cmdmod); + } + ex_pressedreturn = save_ex_pressedreturn; + curwin->w_cursor = save_cursor; + restore_last_search_pattern(); + return retval; } static int execute_cmd0(int *retv, exarg_T *eap, char **errormsg, bool preview) @@ -1719,12 +1674,12 @@ end: static void profile_cmd(const exarg_T *eap, cstack_T *cstack, LineGetter fgetline, void *cookie) { - // Count this line for profiling if skip is TRUE. + // Count this line for profiling if skip is true. if (do_profiling == PROF_YES && (!eap->skip || cstack->cs_idx == 0 || (cstack->cs_idx > 0 && (cstack->cs_flags[cstack->cs_idx - 1] & CSF_ACTIVE)))) { - int skip = did_emsg || got_int || current_exception; + bool skip = did_emsg || got_int || did_throw; if (eap->cmdidx == CMD_catch) { skip = !skip && !(cstack->cs_idx >= 0 @@ -1855,7 +1810,7 @@ static bool skip_cmd(const exarg_T *eap) /// Execute one Ex command. /// -/// If 'sourcing' is TRUE, the command will be included in the error message. +/// If 'sourcing' is true, the command will be included in the error message. /// /// 1. skip comment lines and leading space /// 2. handle command modifiers @@ -1877,10 +1832,10 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter const int save_reg_executing = reg_executing; const bool save_pending_end_reg_executing = pending_end_reg_executing; - exarg_T ea; - memset(&ea, 0, sizeof(ea)); - ea.line1 = 1; - ea.line2 = 1; + exarg_T ea = { + .line1 = 1, + .line2 = 1, + }; ex_nesting_level++; // When the last file has not been edited :q has to be typed twice. @@ -1890,7 +1845,7 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter // avoid that an autocommand, e.g. QuitPre, does this && !getline_equal(fgetline, cookie, getnextac)) { - --quitmore; + quitmore--; } // Reset browse, confirm, etc.. They are restored when returning, for @@ -1921,7 +1876,7 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter ea.skip = (did_emsg || got_int - || current_exception + || did_throw || (cstack->cs_idx >= 0 && !(cstack->cs_flags[cstack->cs_idx] & CSF_ACTIVE))); @@ -1941,7 +1896,7 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter // used, throw an interrupt exception and skip the next command. dbg_check_breakpoint(&ea); if (!ea.skip && got_int) { - ea.skip = TRUE; + ea.skip = true; (void)do_intthrow(cstack); } @@ -2015,7 +1970,7 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter && has_event(EVENT_CMDUNDEFINED)) { p = ea.cmd; while (ASCII_ISALNUM(*p)) { - ++p; + p++; } p = xstrnsave(ea.cmd, (size_t)(p - ea.cmd)); int ret = apply_autocmds(EVENT_CMDUNDEFINED, p, p, true, NULL); @@ -2194,16 +2149,16 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter ea.arg = skipwhite(ea.arg + 1); ea.append = true; } else if (*ea.arg == '!' && ea.cmdidx == CMD_write) { // :w !filter - ++ea.arg; - ea.usefilter = TRUE; + ea.arg++; + ea.usefilter = true; } } else if (ea.cmdidx == CMD_read) { if (ea.forceit) { - ea.usefilter = TRUE; // :r! filter if ea.forceit - ea.forceit = FALSE; + ea.usefilter = true; // :r! filter if ea.forceit + ea.forceit = false; } else if (*ea.arg == '!') { // :r !filter - ++ea.arg; - ea.usefilter = TRUE; + ea.arg++; + ea.usefilter = true; } } else if (ea.cmdidx == CMD_lshift || ea.cmdidx == CMD_rshift) { ea.amount = 1; @@ -2294,10 +2249,10 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter do_throw(cstack); } else if (check_cstack) { if (source_finished(fgetline, cookie)) { - do_finish(&ea, TRUE); + do_finish(&ea, true); } else if (getline_equal(fgetline, cookie, get_func_line) && current_func_returned()) { - do_return(&ea, TRUE, FALSE, NULL); + do_return(&ea, true, false, NULL); } } need_rethrow = check_cstack = false; @@ -2332,7 +2287,7 @@ doend: ea.nextcmd = NULL; } - --ex_nesting_level; + ex_nesting_level--; return ea.nextcmd; } @@ -2368,8 +2323,6 @@ char *ex_errmsg(const char *const msg, const char *const arg) /// @return FAIL when the command is not to be executed. int parse_command_modifiers(exarg_T *eap, char **errormsg, cmdmod_T *cmod, bool skip_only) { - char *p; - CLEAR_POINTER(cmod); // Repeat until no more command modifiers are found. @@ -2401,7 +2354,7 @@ int parse_command_modifiers(exarg_T *eap, char **errormsg, cmdmod_T *cmod, bool return FAIL; } - p = skip_range(eap->cmd, NULL); + char *p = skip_range(eap->cmd, NULL); switch (*p) { // When adding an entry, also modify cmd_exists(). case 'a': @@ -2631,7 +2584,7 @@ static void apply_cmdmod(cmdmod_T *cmod) if ((cmod->cmod_flags & CMOD_NOAUTOCMD) && cmod->cmod_save_ei == NULL) { // Set 'eventignore' to "all". // First save the existing option value for restoring it later. - cmod->cmod_save_ei = (char *)vim_strsave(p_ei); + cmod->cmod_save_ei = xstrdup(p_ei); set_string_option_direct("ei", -1, "all", OPT_FREE, SID_NONE); } } @@ -2653,7 +2606,7 @@ void undo_cmdmod(cmdmod_T *cmod) if (cmod->cmod_save_ei != NULL) { // Restore 'eventignore' to the value before ":noautocmd". set_string_option_direct("ei", -1, cmod->cmod_save_ei, OPT_FREE, SID_NONE); - free_string_option((char_u *)cmod->cmod_save_ei); + free_string_option(cmod->cmod_save_ei); cmod->cmod_save_ei = NULL; } @@ -2840,12 +2793,12 @@ theend: } /// Check for an Ex command with optional tail. -/// If there is a match advance "pp" to the argument and return TRUE. +/// If there is a match advance "pp" to the argument and return true. /// /// @param pp start of command /// @param cmd name of command /// @param len required length -int checkforcmd(char **pp, char *cmd, int len) +bool checkforcmd(char **pp, char *cmd, int len) { int i; @@ -2858,7 +2811,7 @@ int checkforcmd(char **pp, char *cmd, int len) *pp = skipwhite(*pp + i); return true; } - return FALSE; + return false; } /// Append "cmd" to the error message in IObuff. @@ -2886,7 +2839,7 @@ static void append_command(char *cmd) } else if ((char_u *)d - IObuff + utfc_ptr2len(s) + 1 >= IOSIZE) { break; } else { - mb_copy_char((const char_u **)&s, (char_u **)&d); + mb_copy_char((const char **)&s, &d); } } *d = NUL; @@ -2895,29 +2848,23 @@ static void append_command(char *cmd) /// Find an Ex command by its name, either built-in or user. /// Start of the name can be found at eap->cmd. /// Sets eap->cmdidx and returns a pointer to char after the command name. -/// "full" is set to TRUE if the whole command name matched. +/// "full" is set to true if the whole command name matched. /// /// @return NULL for an ambiguous user command. char *find_ex_command(exarg_T *eap, int *full) FUNC_ATTR_NONNULL_ARG(1) { - int len; - char *p; - int i; - - /* - * 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. - */ - p = eap->cmd; + // 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; + p++; } else if (p[0] == 's' && ((p[1] == 'c' && (p[2] == NUL @@ -2929,15 +2876,15 @@ char *find_ex_command(exarg_T *eap, int *full) || p[1] == 'I' || (p[1] == 'r' && p[2] != 'e'))) { eap->cmdidx = CMD_substitute; - ++p; + p++; } else { while (ASCII_ISALPHA(*p)) { - ++p; + p++; } // for python 3.x support ":py3", ":python3", ":py3file", etc. if (eap->cmd[0] == 'p' && eap->cmd[1] == 'y') { while (ASCII_ISALNUM(*p)) { - ++p; + p++; } } @@ -2945,17 +2892,18 @@ char *find_ex_command(exarg_T *eap, int *full) if (p == eap->cmd && vim_strchr("@!=><&~#", *p) != NULL) { p++; } - len = (int)(p - eap->cmd); + int len = (int)(p - eap->cmd); 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'. + int i; for (i = 0; i < len; i++) { if (eap->cmd[i] != ("delete")[i]) { break; } } if (i == len - 1) { - --len; + len--; if (p[-1] == 'l') { eap->flags |= EXFLAG_LIST; } else { @@ -2992,7 +2940,7 @@ char *find_ex_command(exarg_T *eap, int *full) (size_t)len) == 0) { if (full != NULL && cmdnames[(int)eap->cmdidx].cmd_name[len] == NUL) { - *full = TRUE; + *full = true; } break; } @@ -3003,7 +2951,7 @@ char *find_ex_command(exarg_T *eap, int *full) && *eap->cmd >= 'A' && *eap->cmd <= 'Z') { // User defined commands may contain digits. while (ASCII_ISALNUM(*p)) { - ++p; + p++; } p = find_ucmd(eap, p, full, NULL, NULL); } @@ -3075,9 +3023,6 @@ int modifier_len(char *cmd) /// 3 if there is an ambiguous match. int cmd_exists(const char *const name) { - exarg_T ea; - char *p; - // Check command modifiers. for (int i = 0; i < (int)ARRAY_SIZE(cmdmods); i++) { int j; @@ -3093,11 +3038,12 @@ int cmd_exists(const char *const name) // Check built-in commands and user defined commands. // For ":2match" and ":3match" we need to skip the number. + exarg_T ea; ea.cmd = (char *)((*name == '2' || *name == '3') ? name + 1 : name); ea.cmdidx = (cmdidx_T)0; ea.flags = 0; int full = false; - p = find_ex_command(&ea, &full); + char *p = find_ex_command(&ea, &full); if (p == NULL) { return 3; } @@ -3111,9 +3057,8 @@ int cmd_exists(const char *const name) } /// "fullcommand" function -void f_fullcommand(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_fullcommand(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - exarg_T ea; char *name = argvars[0].vval.v_string; rettv->v_type = VAR_STRING; @@ -3127,6 +3072,7 @@ void f_fullcommand(typval_T *argvars, typval_T *rettv, FunPtr fptr) } name = skip_range(name, NULL); + exarg_T ea; ea.cmd = (*name == '2' || *name == '3') ? name + 1 : name; ea.cmdidx = (cmdidx_T)0; ea.flags = 0; @@ -3141,843 +3087,22 @@ void f_fullcommand(typval_T *argvars, typval_T *rettv, FunPtr fptr) : (char_u *)cmdnames[ea.cmdidx].cmd_name); } -/// This is all pretty much copied from do_one_cmd(), with all the extra stuff -/// we don't need/want deleted. Maybe this could be done better if we didn't -/// repeat all this stuff. The only problem is that they may not stay -/// perfectly compatible with each other, but then the command line syntax -/// probably won't change that much -- webb. -/// -/// @param buff buffer for command string -const char *set_one_cmd_context(expand_T *xp, const char *buff) +cmdidx_T excmd_get_cmdidx(const char *cmd, size_t len) { - size_t len = 0; - exarg_T ea; - int context = EXPAND_NOTHING; - bool forceit = false; - bool usefilter = false; // Filter instead of file name. - - ExpandInit(xp); - xp->xp_pattern = (char *)buff; - xp->xp_line = (char *)buff; - xp->xp_context = EXPAND_COMMANDS; // Default until we get past command - ea.argt = 0; - - // 2. skip comment lines and leading space, colons or bars - const char *cmd; - for (cmd = buff; vim_strchr(" \t:|", *cmd) != NULL; cmd++) {} - xp->xp_pattern = (char *)cmd; - - if (*cmd == NUL) { - return NULL; - } - if (*cmd == '"') { // ignore comment lines - xp->xp_context = EXPAND_NOTHING; - return NULL; - } - - /* - * 3. parse a range specifier of the form: addr [,addr] [;addr] .. - */ - cmd = (const char *)skip_range(cmd, &xp->xp_context); - - /* - * 4. parse command - */ - xp->xp_pattern = (char *)cmd; - if (*cmd == NUL) { - return NULL; - } - if (*cmd == '"') { - xp->xp_context = EXPAND_NOTHING; - return NULL; - } - - if (*cmd == '|' || *cmd == '\n') { - return cmd + 1; // There's another command - } - /* - * 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 's' command can be followed directly by 'c', 'g', 'i', 'I' or 'r' - */ - const char *p; - if (*cmd == 'k' && cmd[1] != 'e') { - ea.cmdidx = CMD_k; - p = cmd + 1; - } else { - p = cmd; - while (ASCII_ISALPHA(*p) || *p == '*') { // Allow * wild card - p++; - } - // a user command may contain digits - if (ASCII_ISUPPER(cmd[0])) { - while (ASCII_ISALNUM(*p) || *p == '*') { - p++; - } - } - // for python 3.x: ":py3*" commands completion - if (cmd[0] == 'p' && cmd[1] == 'y' && p == cmd + 2 && *p == '3') { - p++; - while (ASCII_ISALPHA(*p) || *p == '*') { - p++; - } - } - // check for non-alpha command - if (p == cmd && vim_strchr("@*!=><&~#", *p) != NULL) { - p++; - } - len = (size_t)(p - cmd); - - if (len == 0) { - xp->xp_context = EXPAND_UNSUCCESSFUL; - return NULL; - } - for (ea.cmdidx = (cmdidx_T)0; (int)ea.cmdidx < CMD_SIZE; - ea.cmdidx = (cmdidx_T)((int)ea.cmdidx + 1)) { - if (STRNCMP(cmdnames[(int)ea.cmdidx].cmd_name, cmd, len) == 0) { - break; - } - } - - if (cmd[0] >= 'A' && cmd[0] <= 'Z') { - while (ASCII_ISALNUM(*p) || *p == '*') { // Allow * wild card - p++; - } - } - } - - // - // If the cursor is touching the command, and it ends in an alphanumeric - // character, complete the command name. - // - if (*p == NUL && ASCII_ISALNUM(p[-1])) { - return NULL; - } - - if (ea.cmdidx == CMD_SIZE) { - if (*cmd == 's' && vim_strchr("cgriI", cmd[1]) != NULL) { - ea.cmdidx = CMD_substitute; - p = cmd + 1; - } else if (cmd[0] >= 'A' && cmd[0] <= 'Z') { - ea.cmd = (char *)cmd; - p = (const char *)find_ucmd(&ea, (char *)p, NULL, xp, &context); - if (p == NULL) { - ea.cmdidx = CMD_SIZE; // Ambiguous user command. - } - } - } - if (ea.cmdidx == CMD_SIZE) { - // Not still touching the command and it was an illegal one - xp->xp_context = EXPAND_UNSUCCESSFUL; - return NULL; - } - - xp->xp_context = EXPAND_NOTHING; // Default now that we're past command - - if (*p == '!') { // forced commands - forceit = true; - p++; - } - - /* - * 5. parse arguments - */ - if (!IS_USER_CMDIDX(ea.cmdidx)) { - ea.argt = cmdnames[(int)ea.cmdidx].cmd_argt; - } - - const char *arg = (const char *)skipwhite(p); - - // Skip over ++argopt argument - if ((ea.argt & EX_ARGOPT) && *arg != NUL && strncmp(arg, "++", 2) == 0) { - p = arg; - while (*p && !ascii_isspace(*p)) { - MB_PTR_ADV(p); - } - arg = (const char *)skipwhite(p); - } - - if (ea.cmdidx == CMD_write || ea.cmdidx == CMD_update) { - if (*arg == '>') { // Append. - if (*++arg == '>') { - arg++; - } - arg = (const char *)skipwhite(arg); - } else if (*arg == '!' && ea.cmdidx == CMD_write) { // :w !filter - arg++; - usefilter = true; - } - } + cmdidx_T idx; - if (ea.cmdidx == CMD_read) { - usefilter = forceit; // :r! filter if forced - if (*arg == '!') { // :r !filter - arg++; - usefilter = true; - } - } - - if (ea.cmdidx == CMD_lshift || ea.cmdidx == CMD_rshift) { - while (*arg == *cmd) { // allow any number of '>' or '<' - arg++; - } - arg = (const char *)skipwhite(arg); - } - - // Does command allow "+command"? - if ((ea.argt & EX_CMDARG) && !usefilter && *arg == '+') { - // Check if we're in the +command - p = arg + 1; - arg = (const char *)skip_cmd_arg((char *)arg, false); - - // Still touching the command after '+'? - if (*arg == NUL) { - return p; - } - - // Skip space(s) after +command to get to the real argument. - arg = (const char *)skipwhite(arg); - } - - /* - * Check for '|' to separate commands and '"' to start comments. - * Don't do this for ":read !cmd" and ":write !cmd". - */ - if ((ea.argt & EX_TRLBAR) && !usefilter) { - p = arg; - // ":redir @" is not the start of a comment - if (ea.cmdidx == CMD_redir && p[0] == '@' && p[1] == '"') { - p += 2; - } - while (*p) { - if (*p == Ctrl_V) { - if (p[1] != NUL) { - p++; - } - } else if ((*p == '"' && !(ea.argt & EX_NOTRLCOM)) - || *p == '|' - || *p == '\n') { - if (*(p - 1) != '\\') { - if (*p == '|' || *p == '\n') { - return p + 1; - } - return NULL; // It's a comment - } - } - MB_PTR_ADV(p); - } - } - - if (!(ea.argt & EX_EXTRA) && *arg != NUL && strchr("|\"", *arg) == NULL) { - // no arguments allowed but there is something - return NULL; - } - - // Find start of last argument (argument just before cursor): - p = buff; - xp->xp_pattern = (char *)p; - len = strlen(buff); - while (*p && p < buff + len) { - if (*p == ' ' || *p == TAB) { - // Argument starts after a space. - xp->xp_pattern = (char *)++p; - } else { - if (*p == '\\' && *(p + 1) != NUL) { - p++; // skip over escaped character - } - MB_PTR_ADV(p); - } - } - - if (ea.argt & EX_XFILE) { - int c; - int in_quote = false; - const char *bow = NULL; // Beginning of word. - - /* - * Allow spaces within back-quotes to count as part of the argument - * being expanded. - */ - xp->xp_pattern = skipwhite(arg); - p = (const char *)xp->xp_pattern; - while (*p != NUL) { - c = utf_ptr2char(p); - if (c == '\\' && p[1] != NUL) { - p++; - } else if (c == '`') { - if (!in_quote) { - xp->xp_pattern = (char *)p; - bow = p + 1; - } - in_quote = !in_quote; - } - /* An argument can contain just about everything, except - * characters that end the command and white space. */ - else if (c == '|' - || c == '\n' - || c == '"' - || ascii_iswhite(c)) { - len = 0; // avoid getting stuck when space is in 'isfname' - while (*p != NUL) { - c = utf_ptr2char(p); - if (c == '`' || vim_isfilec_or_wc(c)) { - break; - } - len = (size_t)utfc_ptr2len(p); - MB_PTR_ADV(p); - } - if (in_quote) { - bow = p; - } else { - xp->xp_pattern = (char *)p; - } - p -= len; - } - MB_PTR_ADV(p); - } - - /* - * If we are still inside the quotes, and we passed a space, just - * expand from there. - */ - if (bow != NULL && in_quote) { - xp->xp_pattern = (char *)bow; - } - xp->xp_context = EXPAND_FILES; - - // For a shell command more chars need to be escaped. - if (usefilter || ea.cmdidx == CMD_bang || ea.cmdidx == CMD_terminal) { -#ifndef BACKSLASH_IN_FILENAME - xp->xp_shell = TRUE; -#endif - // When still after the command name expand executables. - if (xp->xp_pattern == skipwhite(arg)) { - xp->xp_context = EXPAND_SHELLCMD; - } - } - - // Check for environment variable. - if (*xp->xp_pattern == '$') { - for (p = (const char *)xp->xp_pattern + 1; *p != NUL; p++) { - if (!vim_isIDc((uint8_t)(*p))) { - break; - } - } - if (*p == NUL) { - xp->xp_context = EXPAND_ENV_VARS; - xp->xp_pattern++; - // Avoid that the assignment uses EXPAND_FILES again. - if (context != EXPAND_USER_DEFINED && context != EXPAND_USER_LIST) { - context = EXPAND_ENV_VARS; - } - } - } - // Check for user names. - if (*xp->xp_pattern == '~') { - for (p = (const char *)xp->xp_pattern + 1; *p != NUL && *p != '/'; p++) {} - // Complete ~user only if it partially matches a user name. - // A full match ~user<Tab> will be replaced by user's home - // directory i.e. something like ~user<Tab> -> /home/user/ - if (*p == NUL && p > (const char *)xp->xp_pattern + 1 - && match_user((char_u *)xp->xp_pattern + 1) >= 1) { - xp->xp_context = EXPAND_USER; - ++xp->xp_pattern; - } - } - } - - /* - * 6. switch on command name - */ - switch (ea.cmdidx) { - case CMD_find: - case CMD_sfind: - case CMD_tabfind: - if (xp->xp_context == EXPAND_FILES) { - xp->xp_context = EXPAND_FILES_IN_PATH; - } - break; - case CMD_cd: - case CMD_chdir: - case CMD_lcd: - case CMD_lchdir: - case CMD_tcd: - case CMD_tchdir: - if (xp->xp_context == EXPAND_FILES) { - xp->xp_context = EXPAND_DIRECTORIES; - } - break; - case CMD_help: - xp->xp_context = EXPAND_HELP; - xp->xp_pattern = (char *)arg; - break; - - /* Command modifiers: return the argument. - * Also for commands with an argument that is a command. */ - case CMD_aboveleft: - case CMD_argdo: - case CMD_belowright: - case CMD_botright: - case CMD_browse: - case CMD_bufdo: - case CMD_cdo: - case CMD_cfdo: - case CMD_confirm: - case CMD_debug: - case CMD_folddoclosed: - case CMD_folddoopen: - case CMD_hide: - case CMD_keepalt: - case CMD_keepjumps: - case CMD_keepmarks: - case CMD_keeppatterns: - case CMD_ldo: - case CMD_leftabove: - case CMD_lfdo: - case CMD_lockmarks: - case CMD_noautocmd: - case CMD_noswapfile: - case CMD_rightbelow: - case CMD_sandbox: - case CMD_silent: - case CMD_tab: - case CMD_tabdo: - case CMD_topleft: - case CMD_verbose: - case CMD_vertical: - case CMD_windo: - 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); - - case CMD_match: - if (*arg == NUL || !ends_excmd(*arg)) { - // also complete "None" - set_context_in_echohl_cmd(xp, arg); - arg = (const char *)skipwhite((char *)skiptowhite((const char_u *)arg)); - if (*arg != NUL) { - xp->xp_context = EXPAND_NOTHING; - arg = (const char *)skip_regexp((char_u *)arg + 1, (uint8_t)(*arg), - p_magic, NULL); - } - } - return (const char *)find_nextcmd((char_u *)arg); - - /* - * All completion for the +cmdline_compl feature goes here. - */ - - case CMD_command: - return set_context_in_user_cmd(xp, arg); - - case CMD_delcommand: - xp->xp_context = EXPAND_USER_COMMANDS; - xp->xp_pattern = (char *)arg; - 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_and: - case CMD_substitute: { - const int delim = (uint8_t)(*arg); - if (delim) { - // Skip "from" part. - arg++; - arg = (const char *)skip_regexp((char_u *)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; + 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; } - break; } - case CMD_isearch: - case CMD_dsearch: - case CMD_ilist: - case CMD_dlist: - case CMD_ijump: - case CMD_psearch: - 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; - case CMD_autocmd: - return (const char *)set_context_in_autocmd(xp, (char *)arg, false); - - case CMD_doautocmd: - 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); - break; - case CMD_setglobal: - set_context_in_set_cmd(xp, (char_u *)arg, OPT_GLOBAL); - break; - case CMD_setlocal: - set_context_in_set_cmd(xp, (char_u *)arg, OPT_LOCAL); - break; - case CMD_tag: - case CMD_stag: - case CMD_ptag: - case CMD_ltag: - case CMD_tselect: - case CMD_stselect: - case CMD_ptselect: - case CMD_tjump: - case CMD_stjump: - case CMD_ptjump: - if (wop_flags & WOP_TAGFILE) { - xp->xp_context = EXPAND_TAGS_LISTFILES; - } else { - xp->xp_context = EXPAND_TAGS; - } - xp->xp_pattern = (char *)arg; - break; - case CMD_augroup: - xp->xp_context = EXPAND_AUGROUP; - xp->xp_pattern = (char *)arg; - break; - case CMD_syntax: - set_context_in_syntax_cmd(xp, arg); - break; - case CMD_const: - case CMD_let: - case CMD_if: - case CMD_elseif: - case CMD_while: - case CMD_for: - case CMD_echo: - case CMD_echon: - case CMD_execute: - case CMD_echomsg: - case CMD_echoerr: - case CMD_call: - case CMD_return: - case CMD_cexpr: - case CMD_caddexpr: - case CMD_cgetexpr: - case CMD_lexpr: - case CMD_laddexpr: - case CMD_lgetexpr: - set_context_for_expression(xp, (char *)arg, ea.cmdidx); - 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; - - case CMD_function: - case CMD_delfunction: - xp->xp_context = EXPAND_USER_FUNC; - xp->xp_pattern = (char *)arg; - break; - - case CMD_echohl: - set_context_in_echohl_cmd(xp, arg); - break; - 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, ea.cmdidx); - break; - case CMD_sign: - set_context_in_sign_cmd(xp, (char_u *)arg); - break; - case CMD_bdelete: - case CMD_bwipeout: - case CMD_bunload: - while ((xp->xp_pattern = strchr(arg, ' ')) != NULL) { - arg = (const char *)xp->xp_pattern + 1; - } - FALLTHROUGH; - case CMD_buffer: - case CMD_sbuffer: - case CMD_checktime: - xp->xp_context = EXPAND_BUFFERS; - xp->xp_pattern = (char *)arg; - break; - case CMD_diffget: - case CMD_diffput: - // If current buffer is in diff mode, complete buffer names - // which are in diff mode, and different than current buffer. - xp->xp_context = EXPAND_DIFF_BUFFERS; - xp->xp_pattern = (char *)arg; - break; - - case CMD_USER: - case CMD_USER_BUF: - if (context != EXPAND_NOTHING) { - // EX_XFILE: file names are handled above. - if (!(ea.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, (char_u *)"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; - case CMD_map: - case CMD_noremap: - case CMD_nmap: - case CMD_nnoremap: - case CMD_vmap: - case CMD_vnoremap: - case CMD_omap: - case CMD_onoremap: - case CMD_imap: - case CMD_inoremap: - case CMD_cmap: - case CMD_cnoremap: - case CMD_lmap: - case CMD_lnoremap: - case CMD_smap: - case CMD_snoremap: - case CMD_xmap: - case CMD_xnoremap: - return (const char *)set_context_in_map_cmd(xp, (char_u *)cmd, (char_u *)arg, forceit, false, - false, ea.cmdidx); - case CMD_unmap: - case CMD_nunmap: - case CMD_vunmap: - case CMD_ounmap: - case CMD_iunmap: - case CMD_cunmap: - case CMD_lunmap: - case CMD_sunmap: - case CMD_xunmap: - return (const char *)set_context_in_map_cmd(xp, (char_u *)cmd, (char_u *)arg, forceit, false, - true, ea.cmdidx); - case CMD_mapclear: - case CMD_nmapclear: - case CMD_vmapclear: - case CMD_omapclear: - case CMD_imapclear: - case CMD_cmapclear: - case CMD_lmapclear: - case CMD_smapclear: - case CMD_xmapclear: - xp->xp_context = EXPAND_MAPCLEAR; - xp->xp_pattern = (char *)arg; - break; - - case CMD_abbreviate: - case CMD_noreabbrev: - case CMD_cabbrev: - case CMD_cnoreabbrev: - case CMD_iabbrev: - case CMD_inoreabbrev: - return (const char *)set_context_in_map_cmd(xp, (char_u *)cmd, (char_u *)arg, forceit, true, - false, ea.cmdidx); - case CMD_unabbreviate: - case CMD_cunabbrev: - case CMD_iunabbrev: - return (const char *)set_context_in_map_cmd(xp, (char_u *)cmd, (char_u *)arg, forceit, true, - true, ea.cmdidx); - case CMD_menu: - case CMD_noremenu: - case CMD_unmenu: - case CMD_amenu: - case CMD_anoremenu: - case CMD_aunmenu: - case CMD_nmenu: - case CMD_nnoremenu: - case CMD_nunmenu: - case CMD_vmenu: - case CMD_vnoremenu: - case CMD_vunmenu: - case CMD_omenu: - case CMD_onoremenu: - case CMD_ounmenu: - case CMD_imenu: - case CMD_inoremenu: - case CMD_iunmenu: - case CMD_cmenu: - case CMD_cnoremenu: - case CMD_cunmenu: - case CMD_tlmenu: - case CMD_tlnoremenu: - case CMD_tlunmenu: - case CMD_tmenu: - case CMD_tunmenu: - case CMD_popup: - case CMD_emenu: - return (const char *)set_context_in_menu_cmd(xp, cmd, (char *)arg, forceit); - - case CMD_colorscheme: - xp->xp_context = EXPAND_COLORS; - xp->xp_pattern = (char *)arg; - break; - - case CMD_compiler: - xp->xp_context = EXPAND_COMPILER; - xp->xp_pattern = (char *)arg; - break; - - case CMD_ownsyntax: - xp->xp_context = EXPAND_OWNSYNTAX; - xp->xp_pattern = (char *)arg; - break; - - case CMD_setfiletype: - xp->xp_context = EXPAND_FILETYPE; - xp->xp_pattern = (char *)arg; - break; - - case CMD_packadd: - xp->xp_context = EXPAND_PACKADD; - xp->xp_pattern = (char *)arg; - break; - -#ifdef HAVE_WORKING_LIBINTL - case CMD_language: - p = (const char *)skiptowhite((const char_u *)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; -#endif - case CMD_profile: - set_context_in_profile_cmd(xp, arg); - break; - case CMD_checkhealth: - xp->xp_context = EXPAND_CHECKHEALTH; - xp->xp_pattern = (char *)arg; - break; - case CMD_behave: - xp->xp_context = EXPAND_BEHAVE; - xp->xp_pattern = (char *)arg; - break; - - case CMD_messages: - xp->xp_context = EXPAND_MESSAGES; - xp->xp_pattern = (char *)arg; - break; - - case CMD_history: - xp->xp_context = EXPAND_HISTORY; - xp->xp_pattern = (char *)arg; - break; - case CMD_syntime: - xp->xp_context = EXPAND_SYNTIME; - xp->xp_pattern = (char *)arg; - break; - - case CMD_argdelete: - while ((xp->xp_pattern = vim_strchr(arg, ' ')) != NULL) { - arg = (const char *)(xp->xp_pattern + 1); - } - xp->xp_context = EXPAND_ARGLIST; - xp->xp_pattern = (char *)arg; - break; - - case CMD_lua: - xp->xp_context = EXPAND_LUA; - break; + return idx; +} - default: - break; - } - return NULL; +uint32_t excmd_get_argt(cmdidx_T idx) +{ + return cmdnames[(int)idx].cmd_argt; } /// Skip a range specifier of the form: addr [,addr] [;addr] .. @@ -3992,8 +3117,6 @@ const char *set_one_cmd_context(expand_T *xp, const char *buff) /// @return the "cmd" pointer advanced to beyond the range. char *skip_range(const char *cmd, int *ctx) { - unsigned delim; - while (vim_strchr(" \t0123456789.$%'/?-+,;\\", *cmd) != NULL) { if (*cmd == '\\') { if (cmd[1] == '?' || cmd[1] == '/' || cmd[1] == '&') { @@ -4006,10 +3129,10 @@ char *skip_range(const char *cmd, int *ctx) *ctx = EXPAND_NOTHING; } } else if (*cmd == '/' || *cmd == '?') { - delim = (unsigned)(*cmd++); + unsigned delim = (unsigned)(*cmd++); while (*cmd != NUL && *cmd != (char)delim) { if (*cmd++ == '\\' && *cmd != NUL) { - ++cmd; + cmd++; } } if (*cmd == NUL && ctx != NULL) { @@ -4055,17 +3178,15 @@ static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, int int c; int i; linenr_T n; - char *cmd; pos_T pos; - linenr_T lnum; buf_T *buf; - cmd = skipwhite(*ptr); - lnum = MAXLNUM; + char *cmd = skipwhite(*ptr); + linenr_T lnum = MAXLNUM; do { switch (*cmd) { case '.': // '.' - Cursor position - ++cmd; + cmd++; switch (addr_type) { case ADDR_LINES: case ADDR_OTHER: @@ -4101,7 +3222,7 @@ static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, int break; case '$': // '$' - last line - ++cmd; + cmd++; switch (addr_type) { case ADDR_LINES: case ADDR_OTHER: @@ -4192,9 +3313,9 @@ static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, int goto error; } if (skip) { // skip "/pat/" - cmd = (char *)skip_regexp((char_u *)cmd, c, p_magic, NULL); + cmd = skip_regexp(cmd, c, p_magic, NULL); if (*cmd == c) { - ++cmd; + cmd++; } } else { int flags; @@ -4234,7 +3355,7 @@ static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, int break; case '\\': // "\?", "\/" or "\&", repeat search - ++cmd; + cmd++; if (addr_type != ADDR_LINES) { addr_error(addr_type); cmd = NULL; @@ -4265,7 +3386,7 @@ static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, int goto error; } } - ++cmd; + cmd++; break; default: @@ -4468,6 +3589,9 @@ char *invalid_range(exarg_T *eap) assert(eap->line2 >= 0); // No error for value that is too big, will use the last entry. if (eap->line2 <= 0) { + if (eap->addr_count == 0) { + return _(e_no_errors); + } return _(e_invrange); } break; @@ -4529,8 +3653,8 @@ char *replace_makeprg(exarg_T *eap, char *arg, char **cmdlinep) // Don't do it when ":vimgrep" is used for ":grep". if ((eap->cmdidx == CMD_make || eap->cmdidx == CMD_lmake || isgrep) && !grep_internal(eap->cmdidx)) { - const char *program = isgrep ? (*curbuf->b_p_gp == NUL ? (char *)p_gp : (char *)curbuf->b_p_gp) - : (*curbuf->b_p_mp == NUL ? (char *)p_mp : (char *)curbuf->b_p_mp); + const char *program = isgrep ? (*curbuf->b_p_gp == NUL ? (char *)p_gp : curbuf->b_p_gp) + : (*curbuf->b_p_mp == NUL ? (char *)p_mp : curbuf->b_p_mp); arg = skipwhite(arg); @@ -4544,7 +3668,7 @@ char *replace_makeprg(exarg_T *eap, char *arg, char **cmdlinep) STRCAT(new_cmdline, arg); } - msg_make((char_u *)arg); + msg_make(arg); // 'eap->cmd' is not set here, because it is not used at CMD_make xfree(*cmdlinep); @@ -4560,45 +3684,35 @@ char *replace_makeprg(exarg_T *eap, char *arg, char **cmdlinep) /// @return FAIL for failure, OK otherwise. int expand_filename(exarg_T *eap, char **cmdlinep, char **errormsgp) { - int has_wildcards; // need to expand wildcards - char *repl; - size_t srclen; - char *p; - int escaped; - // Skip a regexp pattern for ":vimgrep[add] pat file..." - p = skip_grep_pat(eap); - - /* - * Decide to expand wildcards *before* replacing '%', '#', etc. If - * the file name contains a wildcard it should not cause expanding. - * (it will be expanded anyway if there is a wildcard before replacing). - */ - has_wildcards = path_has_wildcard((char_u *)p); + char *p = skip_grep_pat(eap); + + // Decide to expand wildcards *before* replacing '%', '#', etc. If + // the file name contains a wildcard it should not cause expanding. + // (it will be expanded anyway if there is a wildcard before replacing). + int has_wildcards = path_has_wildcard((char_u *)p); while (*p != NUL) { // Skip over `=expr`, wildcards in it are not expanded. if (p[0] == '`' && p[1] == '=') { p += 2; (void)skip_expr(&p); if (*p == '`') { - ++p; + p++; } continue; } - /* - * Quick check if this cannot be the start of a special string. - * Also removes backslash before '%', '#' and '<'. - */ + // Quick check if this cannot be the start of a special string. + // Also removes backslash before '%', '#' and '<'. if (vim_strchr("%#<", *p) == NULL) { p++; continue; } - /* - * Try to find a match at this position. - */ - repl = (char *)eval_vars((char_u *)p, (char_u *)eap->arg, &srclen, &(eap->do_ecmd_lnum), - errormsgp, &escaped); + // 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); if (*errormsgp != NULL) { // error detected return FAIL; } @@ -4668,24 +3782,20 @@ int expand_filename(exarg_T *eap, char **cmdlinep, char **errormsgp) xfree(repl); } - /* - * One file argument: Expand wildcards. - * Don't do this with ":r !command" or ":w !command". - */ + // One file argument: Expand wildcards. + // Don't do this with ":r !command" or ":w !command". if ((eap->argt & EX_NOSPC) && !eap->usefilter) { // Replace environment variables. if (has_wildcards) { - /* - * May expand environment variables. This - * can be done much faster with expand_env() than with - * something else (e.g., calling a shell). - * After expanding environment variables, check again - * if there are still wildcards present. - */ + // May expand environment variables. This + // can be done much faster with expand_env() than with + // something else (e.g., calling a shell). + // After expanding environment variables, check again + // if there are still wildcards present. if (vim_strchr(eap->arg, '$') != NULL || vim_strchr(eap->arg, '~') != NULL) { - expand_env_esc((char_u *)eap->arg, NameBuff, MAXPATHL, true, true, NULL); - has_wildcards = path_has_wildcard(NameBuff); + expand_env_esc((char_u *)eap->arg, (char_u *)NameBuff, MAXPATHL, true, true, NULL); + has_wildcards = path_has_wildcard((char_u *)NameBuff); p = (char *)NameBuff; } else { p = NULL; @@ -4695,15 +3805,16 @@ int expand_filename(exarg_T *eap, char **cmdlinep, char **errormsgp) } } - /* - * Halve the number of backslashes (this is Vi compatible). - * For Unix, when wildcards are expanded, this is - * done by ExpandOne() below. - */ + // Halve the number of backslashes (this is Vi compatible). + // For Unix, when wildcards are expanded, this is + // done by ExpandOne() below. #ifdef UNIX - if (!has_wildcards) -#endif + if (!has_wildcards) { + backslash_halve(eap->arg); + } +#else backslash_halve((char_u *)eap->arg); +#endif if (has_wildcards) { expand_T xpc; @@ -4733,11 +3844,9 @@ int expand_filename(exarg_T *eap, char **cmdlinep, char **errormsgp) /// @return a pointer to the character after the replaced string. static char *repl_cmdline(exarg_T *eap, char *src, size_t srclen, char *repl, char **cmdlinep) { - /* - * The new command line is build in new_cmdline[]. - * First allocate it. - * Careful: a "+cmd" argument may have been NUL terminated. - */ + // The new command line is build in new_cmdline[]. + // First allocate it. + // Careful: a "+cmd" argument may have been NUL terminated. size_t len = STRLEN(repl); size_t i = (size_t)(src - *cmdlinep) + STRLEN(src + srclen) + len + 3; if (eap->nextcmd != NULL) { @@ -4746,12 +3855,10 @@ static char *repl_cmdline(exarg_T *eap, char *src, size_t srclen, char *repl, ch char *new_cmdline = xmalloc(i); size_t offset = (size_t)(src - *cmdlinep); - /* - * Copy the stuff before the expanded part. - * Copy the expanded stuff. - * Copy what came after the expanded part. - * Copy the next commands, if there are any. - */ + // Copy the stuff before the expanded part. + // Copy the expanded stuff. + // Copy what came after the expanded part. + // Copy the next commands, if there are any. i = offset; // length of part before match memmove(new_cmdline, *cmdlinep, i); @@ -4847,12 +3954,12 @@ static char *getargcmd(char **argp) char *command = NULL; if (*arg == '+') { // +[command] - ++arg; + arg++; if (ascii_isspace(*arg) || *arg == '\0') { command = (char *)dollar_command; } else { command = arg; - arg = skip_cmd_arg(command, TRUE); + arg = skip_cmd_arg(command, true); if (*arg != NUL) { *arg++ = NUL; // terminate command with NUL } @@ -4866,15 +3973,15 @@ static char *getargcmd(char **argp) /// Find end of "+command" argument. Skip over "\ " and "\\". /// -/// @param rembs TRUE to halve the number of backslashes -static char *skip_cmd_arg(char *p, int rembs) +/// @param rembs true to halve the number of backslashes +char *skip_cmd_arg(char *p, int rembs) { while (*p && !ascii_isspace(*p)) { if (*p == '\\' && p[1] != NUL) { if (rembs) { STRMOVE(p, p + 1); } else { - ++p; + p++; } } MB_PTR_ADV(p); @@ -4905,7 +4012,6 @@ static int getargopt(exarg_T *eap) char *arg = eap->arg + 2; int *pp = NULL; int bad_char_idx; - char *p; // ":edit ++[no]bin[ary] file" if (STRNCMP(arg, "bin", 3) == 0 || STRNCMP(arg, "nobin", 5) == 0) { @@ -4958,13 +4064,13 @@ static int getargopt(exarg_T *eap) *arg = NUL; if (pp == &eap->force_ff) { - if (check_ff_value((char_u *)eap->cmd + eap->force_ff) == FAIL) { + if (check_ff_value(eap->cmd + eap->force_ff) == FAIL) { return FAIL; } eap->force_ff = (char_u)eap->cmd[eap->force_ff]; } else if (pp == &eap->force_enc) { // Make 'fileencoding' lower case. - for (p = eap->cmd + eap->force_enc; *p != NUL; p++) { + for (char *p = eap->cmd + eap->force_enc; *p != NUL; p++) { *p = (char)TOLOWER_ASC(*p); } } else { @@ -4989,7 +4095,6 @@ static int get_tabpage_arg(exarg_T *eap) if (eap->arg && *eap->arg != NUL) { char *p = eap->arg; - char *p_save; int relative = 0; // argument +N/-N means: go to N places to the // right/left relative to the current position. @@ -5001,7 +4106,7 @@ static int get_tabpage_arg(exarg_T *eap) p++; } - p_save = p; + char *p_save = p; tab_number = (int)getdigits(&p, false, tab_number); if (relative == 0) { @@ -5193,7 +4298,7 @@ int ends_excmd(int c) FUNC_ATTR_CONST /// @return the next command, after the first '|' or '\n' or, /// NULL if not found. -char_u *find_nextcmd(const char_u *p) +char *find_nextcmd(const char *p) { while (*p != '|' && *p != '\n') { if (*p == NUL) { @@ -5201,7 +4306,7 @@ char_u *find_nextcmd(const char_u *p) } p++; } - return (char_u *)p + 1; + return (char *)p + 1; } /// Check if *p is a separator between Ex commands, skipping over white space. @@ -5223,9 +4328,9 @@ char_u *check_nextcmd(char_u *p) /// - and forceit not used /// - and not repeated twice on a row /// -/// @param message when FALSE check only, no messages +/// @param message when false check only, no messages /// -/// @return FAIL and give error message if 'message' TRUE, return OK otherwise +/// @return FAIL and give error message if 'message' true, return OK otherwise static int check_more(int message, bool forceit) { int n = ARGCOUNT - curwin->w_arg_idx - 1; @@ -5266,10 +4371,9 @@ static void ex_colorscheme(exarg_T *eap) { if (*eap->arg == NUL) { char *expr = xstrdup("g:colors_name"); - char *p = NULL; emsg_off++; - p = eval_to_string(expr, NULL, false); + char *p = eval_to_string(expr, NULL, false); emsg_off--; xfree(expr); @@ -5473,16 +4577,15 @@ static void ex_pclose(exarg_T *eap) /// @param tp NULL or the tab page "win" is in void ex_win_close(int forceit, win_T *win, tabpage_T *tp) { - int need_hide; - buf_T *buf = win->w_buffer; - // Never close the autocommand window. if (win == aucmd_win) { emsg(_(e_autocmd_close)); return; } - need_hide = (bufIsChanged(buf) && buf->b_nwindows <= 1); + buf_T *buf = win->w_buffer; + + bool need_hide = (bufIsChanged(buf) && buf->b_nwindows <= 1); if (need_hide && !buf_hide(buf) && !forceit) { if ((p_confirm || (cmdmod.cmod_flags & CMOD_CONFIRM)) && p_write) { bufref_T bufref; @@ -5510,8 +4613,6 @@ void ex_win_close(int forceit, win_T *win, tabpage_T *tp) /// ":tabclose N": close tab page N. static void ex_tabclose(exarg_T *eap) { - tabpage_T *tp; - if (cmdwin_type != 0) { cmdwin_result = K_IGNORE; } else if (first_tabpage->tp_next == NULL) { @@ -5519,7 +4620,7 @@ static void ex_tabclose(exarg_T *eap) } else { int tab_number = get_tabpage_arg(eap); if (eap->errmsg == NULL) { - tp = find_tabpage(tab_number); + tabpage_T *tp = find_tabpage(tab_number); if (tp == NULL) { beep_flush(); return; @@ -5591,7 +4692,6 @@ void tabpage_close(int forceit) void tabpage_close_other(tabpage_T *tp, int forceit) { int done = 0; - win_T *wp; int h = tabline_height(); char prev_idx[NUMBUFLEN]; @@ -5599,7 +4699,7 @@ void tabpage_close_other(tabpage_T *tp, int forceit) // one. OK, so I'm paranoid... while (++done < 1000) { snprintf((char *)prev_idx, sizeof(prev_idx), "%i", tabpage_index(tp)); - wp = tp->tp_lastwin; + win_T *wp = tp->tp_lastwin; ex_win_close(forceit, wp, tp); // Autocommands may delete the tab page under our fingers and we may @@ -5619,10 +4719,9 @@ void tabpage_close_other(tabpage_T *tp, int forceit) static void ex_only(exarg_T *eap) { win_T *wp; - linenr_T wnr; if (eap->addr_count > 0) { - wnr = eap->line2; + linenr_T wnr = eap->line2; for (wp = firstwin; --wnr > 0;) { if (wp->w_next == NULL) { break; @@ -5636,17 +4735,7 @@ static void ex_only(exarg_T *eap) if (wp != curwin) { win_goto(wp); } - close_others(TRUE, eap->forceit); -} - -/// ":all" and ":sall". -/// Also used for ":tab drop file ..." after setting the argument list. -void ex_all(exarg_T *eap) -{ - if (eap->addr_count == 0) { - eap->line2 = 9999; - } - do_arg_all((int)eap->line2, eap->forceit, eap->cmdidx == CMD_drop); + close_others(true, eap->forceit); } static void ex_hide(exarg_T *eap) @@ -5761,162 +4850,6 @@ static void ex_goto(exarg_T *eap) goto_byte(eap->line2); } -/// Clear an argument list: free all file names and reset it to zero entries. -void alist_clear(alist_T *al) -{ -#define FREE_AENTRY_FNAME(arg) xfree((arg)->ae_fname) - GA_DEEP_CLEAR(&al->al_ga, aentry_T, FREE_AENTRY_FNAME); -} - -/// Init an argument list. -void alist_init(alist_T *al) -{ - ga_init(&al->al_ga, (int)sizeof(aentry_T), 5); -} - -/// Remove a reference from an argument list. -/// Ignored when the argument list is the global one. -/// If the argument list is no longer used by any window, free it. -void alist_unlink(alist_T *al) -{ - if (al != &global_alist && --al->al_refcount <= 0) { - alist_clear(al); - xfree(al); - } -} - -/// Create a new argument list and use it for the current window. -void alist_new(void) -{ - curwin->w_alist = xmalloc(sizeof(*curwin->w_alist)); - curwin->w_alist->al_refcount = 1; - curwin->w_alist->id = ++max_alist_id; - alist_init(curwin->w_alist); -} - -#if !defined(UNIX) - -/// Expand the file names in the global argument list. -/// If "fnum_list" is not NULL, use "fnum_list[fnum_len]" as a list of buffer -/// numbers to be re-used. -void alist_expand(int *fnum_list, int fnum_len) -{ - char **old_arg_files; - int old_arg_count; - char **new_arg_files; - int new_arg_file_count; - char *save_p_su = p_su; - int i; - - /* Don't use 'suffixes' here. This should work like the shell did the - * expansion. Also, the vimrc file isn't read yet, thus the user - * can't set the options. */ - p_su = empty_option; - old_arg_files = xmalloc(sizeof(*old_arg_files) * GARGCOUNT); - for (i = 0; i < GARGCOUNT; ++i) { - old_arg_files[i] = vim_strsave(GARGLIST[i].ae_fname); - } - old_arg_count = GARGCOUNT; - if (expand_wildcards(old_arg_count, old_arg_files, - &new_arg_file_count, &new_arg_files, - EW_FILE|EW_NOTFOUND|EW_ADDSLASH|EW_NOERROR) == OK - && new_arg_file_count > 0) { - alist_set(&global_alist, new_arg_file_count, new_arg_files, - TRUE, fnum_list, fnum_len); - FreeWild(old_arg_count, old_arg_files); - } - p_su = save_p_su; -} -#endif - -/// Set the argument list for the current window. -/// Takes over the allocated files[] and the allocated fnames in it. -void alist_set(alist_T *al, int count, char **files, int use_curbuf, int *fnum_list, int fnum_len) -{ - int i; - static int recursive = 0; - - if (recursive) { - emsg(_(e_au_recursive)); - return; - } - recursive++; - - alist_clear(al); - ga_grow(&al->al_ga, count); - { - for (i = 0; i < count; ++i) { - if (got_int) { - /* When adding many buffers this can take a long time. Allow - * interrupting here. */ - while (i < count) { - xfree(files[i++]); - } - break; - } - - /* May set buffer name of a buffer previously used for the - * argument list, so that it's re-used by alist_add. */ - if (fnum_list != NULL && i < fnum_len) { - buf_set_name(fnum_list[i], files[i]); - } - - alist_add(al, files[i], use_curbuf ? 2 : 1); - os_breakcheck(); - } - xfree(files); - } - - if (al == &global_alist) { - arg_had_last = false; - } - recursive--; -} - -/// Add file "fname" to argument list "al". -/// "fname" must have been allocated and "al" must have been checked for room. -/// -/// @param set_fnum 1: set buffer number; 2: re-use curbuf -void alist_add(alist_T *al, char *fname, int set_fnum) -{ - if (fname == NULL) { // don't add NULL file names - return; - } -#ifdef BACKSLASH_IN_FILENAME - slash_adjust(fname); -#endif - AARGLIST(al)[al->al_ga.ga_len].ae_fname = (char_u *)fname; - if (set_fnum > 0) { - AARGLIST(al)[al->al_ga.ga_len].ae_fnum = - buflist_add(fname, BLN_LISTED | (set_fnum == 2 ? BLN_CURBUF : 0)); - } - ++al->al_ga.ga_len; -} - -#if defined(BACKSLASH_IN_FILENAME) - -/// Adjust slashes in file names. Called after 'shellslash' was set. -void alist_slash_adjust(void) -{ - for (int i = 0; i < GARGCOUNT; ++i) { - if (GARGLIST[i].ae_fname != NULL) { - slash_adjust(GARGLIST[i].ae_fname); - } - } - - FOR_ALL_TAB_WINDOWS(tp, wp) { - if (wp->w_alist != &global_alist) { - for (int i = 0; i < WARGCOUNT(wp); ++i) { - if (WARGLIST(wp)[i].ae_fname != NULL) { - slash_adjust(WARGLIST(wp)[i].ae_fname); - } - } - } - } -} - -#endif - /// ":preserve". static void ex_preserve(exarg_T *eap) { @@ -5985,9 +4918,7 @@ void ex_splitview(exarg_T *eap) eap->arg = fname; } - /* - * Either open new tab page or split the window. - */ + // 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) { @@ -6021,12 +4952,11 @@ theend: /// Open a new tab page. void tabpage_new(void) { - exarg_T ea; - - memset(&ea, 0, sizeof(ea)); - ea.cmdidx = CMD_tabnew; - ea.cmd = "tabn"; - ea.arg = ""; + exarg_T ea = { + .cmdidx = CMD_tabnew, + .cmd = "tabn", + .arg = "", + }; ex_splitview(&ea); } @@ -6092,7 +5022,7 @@ static void ex_tabs(exarg_T *eap) int tabcount = 1; msg_start(); - msg_scroll = TRUE; + msg_scroll = true; win_T *lastused_win = valid_tabpage(lastused_tabpage) ? lastused_tabpage->tp_curwin @@ -6105,7 +5035,7 @@ static void ex_tabs(exarg_T *eap) msg_putchar('\n'); vim_snprintf((char *)IObuff, IOSIZE, _("Tab page %d"), tabcount++); - msg_outtrans_attr(IObuff, HL_ATTR(HLF_T)); + msg_outtrans_attr((char *)IObuff, HL_ATTR(HLF_T)); ui_flush(); // output one line at a time os_breakcheck(); @@ -6136,7 +5066,7 @@ static void ex_tabs(exarg_T *eap) static void ex_mode(exarg_T *eap) { if (*eap->arg == NUL) { - must_redraw = CLEAR; + must_redraw = UPD_CLEAR; ex_redraw(eap); } else { emsg(_(e_screenmode)); @@ -6147,15 +5077,14 @@ static void ex_mode(exarg_T *eap) /// set, increment or decrement current window height static void ex_resize(exarg_T *eap) { - int n; win_T *wp = curwin; if (eap->addr_count > 0) { - n = (int)eap->line2; + int n = (int)eap->line2; for (wp = firstwin; wp->w_next != NULL && --n > 0; wp = wp->w_next) {} } - n = (int)atol(eap->arg); + int n = (int)atol(eap->arg); if (cmdmod.cmod_split & WSP_VERT) { if (*eap->arg == '-' || *eap->arg == '+') { n += wp->w_width; @@ -6176,15 +5105,12 @@ static void ex_resize(exarg_T *eap) /// ":find [+command] <file>" command. static void ex_find(exarg_T *eap) { - char *fname; - linenr_T count; - - fname = (char *)find_file_in_path((char_u *)eap->arg, STRLEN(eap->arg), - FNAME_MESS, true, (char_u *)curbuf->b_ffname); + char *fname = (char *)find_file_in_path((char_u *)eap->arg, STRLEN(eap->arg), + FNAME_MESS, true, (char_u *)curbuf->b_ffname); if (eap->addr_count > 0) { // Repeat finding the file "count" times. This matters when it // appears several times in the path. - count = eap->line2; + 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); @@ -6210,11 +5136,8 @@ static void ex_edit(exarg_T *eap) void do_exedit(exarg_T *eap, win_T *old_curwin) { int n; - int need_hide; - /* - * ":vi" command ends Ex mode. - */ + // ":vi" command ends Ex mode. if (exmode_active && (eap->cmdidx == CMD_visual || eap->cmdidx == CMD_view)) { exmode_active = false; @@ -6235,7 +5158,7 @@ void do_exedit(exarg_T *eap, win_T *old_curwin) no_wait_return = 0; need_wait_return = false; msg_scroll = 0; - redraw_all_later(NOT_VALID); + redraw_all_later(UPD_NOT_VALID); pending_exmode_active = true; normal_enter(false, true); @@ -6286,12 +5209,12 @@ void do_exedit(exarg_T *eap, win_T *old_curwin) old_curwin == NULL ? curwin : NULL) == FAIL) { // Editing the file failed. If the window was split, close it. if (old_curwin != NULL) { - need_hide = (curbufIsChanged() && curbuf->b_nwindows <= 1); + bool need_hide = (curbufIsChanged() && curbuf->b_nwindows <= 1); if (!need_hide || buf_hide(curbuf)) { cleanup_T cs; // Reset the error/interrupt/exception state here so that - // aborting() returns FALSE when closing a window. + // aborting() returns false when closing a window. enter_cleanup(&cs); win_close(curwin, !need_hide && !buf_hide(curbuf), false); @@ -6320,10 +5243,8 @@ void do_exedit(exarg_T *eap, win_T *old_curwin) } } - /* - * if ":split file" worked, set alternate file name in old window to new - * file - */ + // if ":split file" worked, set alternate file name in old window to new + // file if (old_curwin != NULL && *eap->arg != NUL && curwin != old_curwin @@ -6369,9 +5290,7 @@ static void ex_syncbind(exarg_T *eap) setpcmark(); - /* - * determine max topline - */ + // determine max topline if (curwin->w_p_scb) { topline = curwin->w_topline; FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { @@ -6389,23 +5308,21 @@ static void ex_syncbind(exarg_T *eap) topline = 1; } - /* - * Set all scrollbind windows to the same topline. - */ + // Set all scrollbind windows to the same topline. FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { curwin = wp; if (curwin->w_p_scb) { curbuf = curwin->w_buffer; y = topline - curwin->w_topline; if (y > 0) { - scrollup(y, TRUE); + scrollup(y, true); } else { - scrolldown(-y, TRUE); + scrolldown(-y, true); } curwin->w_scbind_pos = topline; - redraw_later(curwin, VALID); + redraw_later(curwin, UPD_VALID); cursor_correct(); - curwin->w_redr_status = TRUE; + curwin->w_redr_status = true; } } curwin = save_curwin; @@ -6425,9 +5342,7 @@ static void ex_syncbind(exarg_T *eap) static void ex_read(exarg_T *eap) { - int i; int empty = (curbuf->b_ml.ml_flags & ML_EMPTY); - linenr_T lnum; if (eap->usefilter) { // :r!cmd do_bang(1, eap, false, false, true); @@ -6435,6 +5350,7 @@ static void ex_read(exarg_T *eap) 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 @@ -6457,6 +5373,7 @@ static void ex_read(exarg_T *eap) 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 { @@ -6471,7 +5388,7 @@ static void ex_read(exarg_T *eap) deleted_lines_mark(lnum, 1L); } } - redraw_curbuf_later(VALID); + redraw_curbuf_later(UPD_VALID); } } } @@ -6571,8 +5488,8 @@ bool changedir_func(char *new_dir, CdScope scope) new_dir = pdir; } - if (os_dirname(NameBuff, MAXPATHL) == OK) { - pdir = (char *)vim_strsave(NameBuff); + if (os_dirname((char_u *)NameBuff, MAXPATHL) == OK) { + pdir = xstrdup(NameBuff); } else { pdir = NULL; } @@ -6585,7 +5502,7 @@ bool changedir_func(char *new_dir, CdScope scope) if (*new_dir == NUL && p_cdh) { #endif // Use NameBuff for home directory name. - expand_env((char_u *)"$HOME", NameBuff, MAXPATHL); + expand_env("$HOME", NameBuff, MAXPATHL); new_dir = (char *)NameBuff; } @@ -6654,7 +5571,7 @@ void ex_cd(exarg_T *eap) /// ":pwd". static void ex_pwd(exarg_T *eap) { - if (os_dirname(NameBuff, MAXPATHL) == OK) { + if (os_dirname((char_u *)NameBuff, MAXPATHL) == OK) { #ifdef BACKSLASH_IN_FILENAME slash_adjust(NameBuff); #endif @@ -6685,17 +5602,14 @@ static void ex_equal(exarg_T *eap) static void ex_sleep(exarg_T *eap) { - int n; - long len; - if (cursor_valid()) { - n = curwin->w_winrow + curwin->w_wrow - msg_scrolled; + int n = curwin->w_winrow + curwin->w_wrow - msg_scrolled; if (n >= 0) { ui_cursor_goto(n, curwin->w_wincol + curwin->w_wcol); } } - len = eap->line2; + long len = eap->line2; switch (*eap->arg) { case 'm': break; @@ -6815,7 +5729,7 @@ static void ex_operators(exarg_T *eap) } else { oa.op_type = OP_LSHIFT; } - op_shift(&oa, FALSE, eap->amount); + op_shift(&oa, false, eap->amount); break; } virtual_op = kNone; @@ -6828,7 +5742,7 @@ static void ex_put(exarg_T *eap) // ":0put" works like ":1put!". if (eap->line2 == 0) { eap->line2 = 1; - eap->forceit = TRUE; + eap->forceit = true; } curwin->w_cursor.lnum = eap->line2; check_cursor_col(); @@ -6846,9 +5760,7 @@ static void ex_copymove(exarg_T *eap) } get_flags(eap); - /* - * move or copy lines from 'eap->line1'-'eap->line2' to below line 'n' - */ + // move or copy lines from 'eap->line1'-'eap->line2' to below line 'n' if (n == MAXLNUM || n < 0 || n > curbuf->b_ml.ml_line_count) { emsg(_(e_invrange)); return; @@ -6910,7 +5822,7 @@ static void ex_join(exarg_T *eap) beep_flush(); return; } - ++eap->line2; + eap->line2++; } do_join((size_t)((ssize_t)eap->line2 - eap->line1 + 1), !eap->forceit, true, true, true); beginline(BL_WHITE | BL_FIX); @@ -6939,11 +5851,9 @@ static void ex_at(exarg_T *eap) exec_from_reg = true; - /* - * Execute from the typeahead buffer. - * Continue until the stuff buffer is empty and all added characters - * have been consumed. - */ + // 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); } @@ -7032,15 +5942,15 @@ static void ex_later(exarg_T *eap) count = getdigits_long(&p, false, 0); switch (*p) { case 's': - ++p; sec = true; break; + p++; sec = true; break; case 'm': - ++p; sec = true; count *= 60; break; + p++; sec = true; count *= 60; break; case 'h': - ++p; sec = true; count *= 60 * 60; break; + p++; sec = true; count *= 60 * 60; break; case 'd': - ++p; sec = true; count *= 24 * 60 * 60; break; + p++; sec = true; count *= 24 * 60 * 60; break; case 'f': - ++p; file = true; break; + p++; file = true; break; } } @@ -7055,17 +5965,16 @@ static void ex_later(exarg_T *eap) /// ":redir": start/stop redirection. static void ex_redir(exarg_T *eap) { - char *mode; - char *fname; char *arg = eap->arg; if (STRICMP(eap->arg, "END") == 0) { close_redir(); } else { if (*arg == '>') { - ++arg; + arg++; + char *mode; if (*arg == '>') { - ++arg; + arg++; mode = "a"; } else { mode = "w"; @@ -7075,7 +5984,7 @@ static void ex_redir(exarg_T *eap) close_redir(); // Expand environment variables and "~/". - fname = expand_env_save(arg); + char *fname = expand_env_save(arg); if (fname == NULL) { return; } @@ -7085,7 +5994,7 @@ static void ex_redir(exarg_T *eap) } else if (*arg == '@') { // redirect to a register a-z (resp. A-Z for appending) close_redir(); - ++arg; + arg++; if (valid_yank_reg(*arg, true) && *arg != '_') { redir_reg = (char_u)(*arg++); if (*arg == '>' && arg[1] == '>') { // append @@ -7114,10 +6023,10 @@ static void ex_redir(exarg_T *eap) arg += 2; if (*arg == '>') { - ++arg; - append = TRUE; + arg++; + append = true; } else { - append = FALSE; + append = false; } if (var_redir_start(skipwhite(arg), append) == OK) { @@ -7146,14 +6055,15 @@ static void ex_redraw(exarg_T *eap) int p = p_lz; RedrawingDisabled = 0; - p_lz = FALSE; + p_lz = false; validate_cursor(); update_topline(curwin); if (eap->forceit) { - redraw_all_later(NOT_VALID); + redraw_all_later(UPD_NOT_VALID); + redraw_cmdline = true; } - update_screen(eap->forceit ? NOT_VALID - : VIsual_active ? INVERTED : 0); + update_screen(eap->forceit ? UPD_NOT_VALID + : VIsual_active ? UPD_INVERTED : 0); if (need_maketitle) { maketitle(); } @@ -7180,13 +6090,13 @@ static void ex_redrawstatus(exarg_T *eap) int p = p_lz; RedrawingDisabled = 0; - p_lz = FALSE; + p_lz = false; if (eap->forceit) { status_redraw_all(); } else { status_redraw_curbuf(); } - update_screen(VIsual_active ? INVERTED : 0); + update_screen(VIsual_active ? UPD_INVERTED : 0); RedrawingDisabled = r; p_lz = p; ui_flush(); @@ -7245,11 +6155,9 @@ int vim_mkdir_emsg(const char *const name, const int prot) /// @return file descriptor, or NULL on failure. FILE *open_exfile(char_u *fname, int forceit, char *mode) { - FILE *fd; - #ifdef UNIX // with Unix it is possible to open a directory - if (os_isdir(fname)) { + if (os_isdir((char *)fname)) { semsg(_(e_isadir2), fname); return NULL; } @@ -7259,6 +6167,7 @@ FILE *open_exfile(char_u *fname, int forceit, char *mode) return NULL; } + FILE *fd; if ((fd = os_fopen((char *)fname, mode)) == NULL) { semsg(_("E190: Cannot open \"%s\" for writing"), fname); } @@ -7269,14 +6178,12 @@ FILE *open_exfile(char_u *fname, int forceit, char *mode) /// ":mark" and ":k". static void ex_mark(exarg_T *eap) { - pos_T pos; - if (*eap->arg == NUL) { // No argument? emsg(_(e_argreq)); } else if (eap->arg[1] != NUL) { // more than one character? semsg(_(e_trailing_arg), eap->arg); } else { - pos = curwin->w_cursor; // save curwin->w_cursor + 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 @@ -7357,10 +6264,7 @@ static void ex_normal(exarg_T *eap) emsg("Can't re-enter normal mode from terminal mode"); return; } - save_state_T save_state; char *arg = NULL; - int l; - char *p; if (ex_normal_lock > 0) { emsg(_(e_secure)); @@ -7378,6 +6282,8 @@ static void ex_normal(exarg_T *eap) int len = 0; // Count the number of characters to be escaped. + int l; + char *p; for (p = eap->arg; *p != NUL; p++) { for (l = utfc_ptr2len(p) - 1; l > 0; l--) { if (*++p == (char)K_SPECIAL) { // trailbyte K_SPECIAL @@ -7403,6 +6309,7 @@ static void ex_normal(exarg_T *eap) } ex_normal_busy++; + save_state_T save_state; if (save_current_state(&save_state)) { // Repeat the :normal command for each line in the range. When no // range given, execute it just once, without positioning the cursor @@ -7521,8 +6428,6 @@ static void ex_psearch(exarg_T *eap) static void ex_findpat(exarg_T *eap) { bool whole = true; - long n; - char *p; int action; switch (cmdnames[eap->cmdidx].cmd_name[2]) { @@ -7544,7 +6449,7 @@ static void ex_findpat(exarg_T *eap) break; } - n = 1; + long n = 1; if (ascii_isdigit(*eap->arg)) { // get count n = getdigits_long(&eap->arg, false, 0); eap->arg = skipwhite(eap->arg); @@ -7552,7 +6457,7 @@ static void ex_findpat(exarg_T *eap) if (*eap->arg == '/') { // Match regexp, not just whole words whole = false; eap->arg++; - p = (char *)skip_regexp((char_u *)eap->arg, '/', p_magic, NULL); + char *p = skip_regexp(eap->arg, '/', p_magic, NULL); if (*p) { *p++ = NUL; p = skipwhite(p); @@ -7594,7 +6499,7 @@ static void ex_pedit(exarg_T *eap) if (curwin != curwin_save && win_valid(curwin_save)) { // Return cursor to where we were validate_cursor(); - redraw_later(curwin, VALID); + redraw_later(curwin, UPD_VALID); win_enter(curwin_save, true); } g_do_tagpreview = 0; @@ -7672,6 +6577,7 @@ enum { SPEC_SFILE, SPEC_SLNUM, SPEC_STACK, + SPEC_SCRIPT, SPEC_AFILE, SPEC_ABUF, SPEC_AMATCH, @@ -7686,7 +6592,6 @@ enum { ssize_t find_cmdline_var(const char_u *src, size_t *usedlen) FUNC_ATTR_NONNULL_ALL { - size_t len; static char *(spec_str[]) = { [SPEC_PERC] = "%", [SPEC_HASH] = "#", @@ -7697,6 +6602,7 @@ ssize_t find_cmdline_var(const char_u *src, size_t *usedlen) [SPEC_SFILE] = "<sfile>", // ":so" file name [SPEC_SLNUM] = "<slnum>", // ":so" file line number [SPEC_STACK] = "<stack>", // call stack + [SPEC_SCRIPT] = "<script>", // script file name [SPEC_AFILE] = "<afile>", // autocommand file name [SPEC_ABUF] = "<abuf>", // autocommand buffer number [SPEC_AMATCH] = "<amatch>", // autocommand match name @@ -7705,8 +6611,8 @@ ssize_t find_cmdline_var(const char_u *src, size_t *usedlen) // [SPEC_CLIENT] = "<client>", }; - for (size_t i = 0; i < ARRAY_SIZE(spec_str); ++i) { - len = STRLEN(spec_str[i]); + 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) { *usedlen = len; assert(i <= SSIZE_MAX); @@ -7718,40 +6624,40 @@ ssize_t find_cmdline_var(const char_u *src, size_t *usedlen) /// Evaluate cmdline variables. /// -/// change '%' to curbuf->b_ffname -/// '#' to curwin->w_alt_fnum -/// '<cword>' to word under the cursor -/// '<cWORD>' to WORD under the cursor -/// '<cexpr>' to C-expression under the cursor -/// '<cfile>' to path name under the cursor -/// '<sfile>' to sourced file name -/// '<slnum>' to sourced file line number -/// '<afile>' to file name for autocommand -/// '<abuf>' to buffer number for autocommand -/// '<amatch>' to matching name for autocommand +/// change "%" to curbuf->b_ffname +/// "#" to curwin->w_alt_fnum +/// "<cword>" to word under the cursor +/// "<cWORD>" to WORD under the cursor +/// "<cexpr>" to C-expression under the cursor +/// "<cfile>" to path name under the cursor +/// "<sfile>" to sourced file name +/// "<stack>" to call stack +/// "<script>" to current script name +/// "<slnum>" to sourced file line number +/// "<afile>" to file name for autocommand +/// "<abuf>" to buffer number for autocommand +/// "<amatch>" to matching name for autocommand /// /// When an error is detected, "errormsg" is set to a non-NULL pointer (may be /// "" for error without a message) and NULL is returned. /// -/// @param src pointer into commandline -/// @param srcstart beginning of valid memory for src -/// @param usedlen characters after src that are used -/// @param lnump line number for :e command, or NULL -/// @param errormsg pointer to error message -/// @param escaped return value has escaped white space (can be NULL) +/// @param src pointer into commandline +/// @param srcstart beginning of valid memory for src +/// @param usedlen characters after src that are used +/// @param lnump line number for :e command, or NULL +/// @param errormsg pointer to error message +/// @param escaped return value has escaped white space (can be NULL) +/// @param empty_is_error empty result is considered an error /// /// @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) + int *escaped, bool empty_is_error) { - int i; - char *s; char *result; char *resultbuf = NULL; size_t resultlen; - buf_T *buf; int valid = VALID_HEAD | VALID_PATH; // Assume valid result. bool tilde_file = false; bool skip_mod = false; @@ -7759,40 +6665,34 @@ char_u *eval_vars(char_u *src, char_u *srcstart, size_t *usedlen, linenr_T *lnum *errormsg = NULL; if (escaped != NULL) { - *escaped = FALSE; + *escaped = false; } - /* - * Check if there is something to do. - */ + // Check if there is something to do. ssize_t spec_idx = find_cmdline_var(src, usedlen); if (spec_idx < 0) { // no match *usedlen = 1; return NULL; } - /* - * Skip when preceded with a backslash "\%" and "\#". - * Note: In "\\%" the % is also not recognized! - */ + // Skip when preceded with a backslash "\%" and "\#". + // Note: In "\\%" the % is also not recognized! if (src > srcstart && src[-1] == '\\') { *usedlen = 0; STRMOVE(src - 1, src); // remove backslash return NULL; } - /* - * word or WORD under cursor - */ + // word or WORD under cursor if (spec_idx == SPEC_CWORD || spec_idx == SPEC_CCWORD || spec_idx == SPEC_CEXPR) { - resultlen = find_ident_under_cursor((char_u **)&result, + resultlen = find_ident_under_cursor(&result, spec_idx == SPEC_CWORD - ? (FIND_IDENT | FIND_STRING) - : (spec_idx == SPEC_CEXPR - ? (FIND_IDENT | FIND_STRING | FIND_EVAL) - : FIND_STRING)); + ? (FIND_IDENT | FIND_STRING) + : (spec_idx == SPEC_CEXPR + ? (FIND_IDENT | FIND_STRING | FIND_EVAL) + : FIND_STRING)); if (resultlen == 0) { *errormsg = ""; return NULL; @@ -7822,16 +6722,16 @@ char_u *eval_vars(char_u *src, char_u *srcstart, size_t *usedlen, linenr_T *lnum resultbuf = result; *usedlen = 2; if (escaped != NULL) { - *escaped = TRUE; + *escaped = true; } skip_mod = true; break; } - s = (char *)src + 1; + char *s = (char *)src + 1; if (*s == '<') { // "#<99" uses v:oldfiles. s++; } - i = getdigits_int(&s, false, 0); + int i = getdigits_int(&s, false, 0); if ((char_u *)s == src + 2 && src[1] == '-') { // just a minus sign, don't skip over it s--; @@ -7853,7 +6753,7 @@ char_u *eval_vars(char_u *src, char_u *srcstart, size_t *usedlen, linenr_T *lnum if (i == 0 && src[1] == '<' && *usedlen > 1) { *usedlen = 1; } - buf = buflist_findnr(i); + buf_T *buf = buflist_findnr(i); if (buf == NULL) { *errormsg = _("E194: No alternate file name to substitute for '#'"); return NULL; @@ -7918,29 +6818,46 @@ char_u *eval_vars(char_u *src, char_u *srcstart, size_t *usedlen, linenr_T *lnum break; case SPEC_SFILE: // file name for ":so" command - result = sourcing_name; + result = estack_sfile(ESTACK_SFILE); if (result == NULL) { - *errormsg = _("E498: no :source file name to substitute for \"<sfile>\""); + *errormsg = _(e_no_source_file_name_to_substitute_for_sfile); return NULL; } + resultbuf = result; // remember allocated string + break; + case SPEC_STACK: // call stack + result = estack_sfile(ESTACK_STACK); + if (result == NULL) { + *errormsg = _(e_no_call_stack_to_substitute_for_stack); + return NULL; + } + resultbuf = result; // remember allocated string + break; + case SPEC_SCRIPT: // script file name + result = estack_sfile(ESTACK_SCRIPT); + if (result == NULL) { + *errormsg = _(e_no_script_file_name_to_substitute_for_script); + return NULL; + } + resultbuf = result; // remember allocated string break; case SPEC_SLNUM: // line in file for ":so" command - if (sourcing_name == NULL || sourcing_lnum == 0) { + if (SOURCING_NAME == NULL || SOURCING_LNUM == 0) { *errormsg = _("E842: no line number to use for \"<slnum>\""); return NULL; } - snprintf(strbuf, sizeof(strbuf), "%" PRIdLINENR, sourcing_lnum); + snprintf(strbuf, sizeof(strbuf), "%" PRIdLINENR, SOURCING_LNUM); result = strbuf; break; case SPEC_SFLNUM: // line in script file - if (current_sctx.sc_lnum + sourcing_lnum == 0) { + if (current_sctx.sc_lnum + SOURCING_LNUM == 0) { *errormsg = _("E961: no line number to use for \"<sflnum>\""); return NULL; } snprintf((char *)strbuf, sizeof(strbuf), "%" PRIdLINENR, - current_sctx.sc_lnum + sourcing_lnum); + current_sctx.sc_lnum + SOURCING_LNUM); result = strbuf; break; @@ -7966,7 +6883,8 @@ char_u *eval_vars(char_u *src, char_u *srcstart, size_t *usedlen, linenr_T *lnum // Remove the file name extension. if (src[*usedlen] == '<') { (*usedlen)++; - if ((s = (char *)STRRCHR(result, '.')) != NULL + char *s; + if ((s = strrchr(result, '.')) != NULL && s >= path_tail(result)) { resultlen = (size_t)(s - result); } @@ -7981,11 +6899,13 @@ char_u *eval_vars(char_u *src, char_u *srcstart, size_t *usedlen, linenr_T *lnum } if (resultlen == 0 || valid != VALID_HEAD + VALID_PATH) { - if (valid != VALID_HEAD + VALID_PATH) { - // xgettext:no-c-format - *errormsg = _("E499: Empty file name for '%' or '#', only works with \":p:h\""); - } else { - *errormsg = _("E500: Evaluates to an empty string"); + if (empty_is_error) { + if (valid != VALID_HEAD + VALID_PATH) { + // xgettext:no-c-format + *errormsg = _("E499: Empty file name for '%' or '#', only works with \":p:h\""); + } else { + *errormsg = _("E500: Evaluates to an empty string"); + } } result = NULL; } else { @@ -7995,88 +6915,22 @@ char_u *eval_vars(char_u *src, char_u *srcstart, size_t *usedlen, linenr_T *lnum return (char_u *)result; } -/// Concatenate all files in the argument list, separated by spaces, and return -/// it in one allocated string. -/// Spaces and backslashes in the file names are escaped with a backslash. -static char *arg_all(void) -{ - int len; - int idx; - char *retval = NULL; - char *p; - - /* - * Do this loop two times: - * first time: compute the total length - * second time: concatenate the names - */ - for (;;) { - len = 0; - for (idx = 0; idx < ARGCOUNT; idx++) { - p = alist_name(&ARGLIST[idx]); - if (p == NULL) { - continue; - } - if (len > 0) { - // insert a space in between names - if (retval != NULL) { - retval[len] = ' '; - } - ++len; - } - for (; *p != NUL; p++) { - if (*p == ' ' -#ifndef BACKSLASH_IN_FILENAME - || *p == '\\' -#endif - || *p == '`') { - // insert a backslash - if (retval != NULL) { - retval[len] = '\\'; - } - len++; - } - if (retval != NULL) { - retval[len] = *p; - } - len++; - } - } - - // second time: break here - if (retval != NULL) { - retval[len] = NUL; - break; - } - - // allocate memory - retval = xmalloc((size_t)len + 1); - } - - return retval; -} - /// Expand the <sfile> string in "arg". /// /// @return an allocated string, or NULL for any error. char *expand_sfile(char *arg) { - char *errormsg; - size_t len; - char *result; - char *newres; - char *repl; - size_t srclen; - char *p; + char *result = xstrdup(arg); - result = xstrdup(arg); - - for (p = result; *p;) { + for (char *p = result; *p;) { if (STRNCMP(p, "<sfile>", 7) != 0) { - ++p; + p++; } else { // replace "<sfile>" with the sourced file name, and do ":" stuff - repl = (char *)eval_vars((char_u *)p, (char_u *)result, &srclen, NULL, &errormsg, NULL); + size_t srclen; + char *errormsg; + char *repl = (char *)eval_vars((char_u *)p, (char_u *)result, &srclen, NULL, &errormsg, NULL, + true); if (errormsg != NULL) { if (*errormsg) { emsg(errormsg); @@ -8088,8 +6942,8 @@ char *expand_sfile(char *arg) p += srclen; continue; } - len = STRLEN(result) - srclen + STRLEN(repl) + 1; - newres = xmalloc(len); + size_t len = STRLEN(result) - srclen + STRLEN(repl) + 1; + char *newres = xmalloc(len); memmove(newres, result, (size_t)(p - result)); STRCPY(newres + (p - result), repl); len = STRLEN(newres); @@ -8107,18 +6961,16 @@ char *expand_sfile(char *arg) /// ":rshada" and ":wshada". static void ex_shada(exarg_T *eap) { - char *save_shada; - - save_shada = (char *)p_shada; + char *save_shada = p_shada; if (*p_shada == NUL) { - p_shada = (char_u *)"'100"; + p_shada = "'100"; } if (eap->cmdidx == CMD_rviminfo || eap->cmdidx == CMD_rshada) { (void)shada_read_everything(eap->arg, eap->forceit, false); } else { shada_write_file(eap->arg, eap->forceit); } - p_shada = (char_u *)save_shada; + p_shada = save_shada; } /// Make a dialog message in "buff[DIALOG_MSG_SIZE]". @@ -8135,51 +6987,20 @@ void dialog_msg(char *buff, char *format, char *fname) static void ex_behave(exarg_T *eap) { if (STRCMP(eap->arg, "mswin") == 0) { - set_option_value("selection", 0L, "exclusive", 0); - set_option_value("selectmode", 0L, "mouse,key", 0); - set_option_value("mousemodel", 0L, "popup", 0); - set_option_value("keymodel", 0L, "startsel,stopsel", 0); + set_option_value_give_err("selection", 0L, "exclusive", 0); + set_option_value_give_err("selectmode", 0L, "mouse,key", 0); + set_option_value_give_err("mousemodel", 0L, "popup", 0); + set_option_value_give_err("keymodel", 0L, "startsel,stopsel", 0); } else if (STRCMP(eap->arg, "xterm") == 0) { - set_option_value("selection", 0L, "inclusive", 0); - set_option_value("selectmode", 0L, "", 0); - set_option_value("mousemodel", 0L, "extend", 0); - set_option_value("keymodel", 0L, "", 0); + set_option_value_give_err("selection", 0L, "inclusive", 0); + set_option_value_give_err("selectmode", 0L, "", 0); + set_option_value_give_err("mousemodel", 0L, "extend", 0); + set_option_value_give_err("keymodel", 0L, "", 0); } else { semsg(_(e_invarg2), eap->arg); } } -/// Function given to ExpandGeneric() to obtain the possible arguments of the -/// ":behave {mswin,xterm}" command. -char *get_behave_arg(expand_T *xp, int idx) -{ - if (idx == 0) { - return "mswin"; - } - if (idx == 1) { - return "xterm"; - } - return NULL; -} - -/// Function given to ExpandGeneric() to obtain the possible arguments of the -/// ":messages {clear}" command. -char *get_messages_arg(expand_T *xp FUNC_ATTR_UNUSED, int idx) -{ - if (idx == 0) { - return "clear"; - } - return NULL; -} - -char *get_mapclear_arg(expand_T *xp FUNC_ATTR_UNUSED, int idx) -{ - if (idx == 0) { - return "<buffer>"; - } - return NULL; -} - static TriState filetype_detect = kNone; static TriState filetype_plugin = kNone; static TriState filetype_indent = kNone; @@ -8193,10 +7014,6 @@ static TriState filetype_indent = kNone; /// indent off: load indoff.vim static void ex_filetype(exarg_T *eap) { - char *arg = eap->arg; - bool plugin = false; - bool indent = false; - if (*eap->arg == NUL) { // Print current status. smsg("filetype detection:%s plugin:%s indent:%s", @@ -8206,6 +7023,10 @@ static void ex_filetype(exarg_T *eap) return; } + char *arg = eap->arg; + bool plugin = false; + bool indent = false; + // Accept "plugin" and "indent" in any order. for (;;) { if (STRNCMP(arg, "plugin", 6) == 0) { @@ -8294,7 +7115,7 @@ static void ex_setfiletype(exarg_T *eap) arg += 9; } - set_option_value("filetype", 0L, arg, OPT_LOCAL); + set_option_value_give_err("filetype", 0L, arg, OPT_LOCAL); if (arg != eap->arg) { did_filetype = false; } @@ -8320,7 +7141,7 @@ void set_no_hlsearch(bool flag) static void ex_nohlsearch(exarg_T *eap) { set_no_hlsearch(true); - redraw_all_later(SOME_VALID); + redraw_all_later(UPD_SOME_VALID); } static void ex_fold(exarg_T *eap) @@ -8342,7 +7163,7 @@ static void ex_foldopen(exarg_T *eap) static void ex_folddo(exarg_T *eap) { // First set the marks for all lines closed/open. - for (linenr_T lnum = eap->line1; lnum <= eap->line2; ++lnum) { + for (linenr_T lnum = eap->line1; lnum <= eap->line2; lnum++) { if (hasFolding(lnum, NULL, NULL) == (eap->cmdidx == CMD_folddoclosed)) { ml_setmarked(lnum); } diff --git a/src/nvim/ex_eval.c b/src/nvim/ex_eval.c index f67c8a6720..6dddafcfcb 100644 --- a/src/nvim/ex_eval.c +++ b/src/nvim/ex_eval.c @@ -16,12 +16,12 @@ #include "nvim/debugger.h" #include "nvim/eval.h" #include "nvim/eval/userfunc.h" -#include "nvim/ex_cmds2.h" #include "nvim/ex_docmd.h" #include "nvim/ex_eval.h" #include "nvim/memory.h" #include "nvim/message.h" #include "nvim/regexp.h" +#include "nvim/runtime.h" #include "nvim/strings.h" #include "nvim/vim.h" @@ -45,20 +45,19 @@ // there or even outside the try conditional. Try conditionals may be nested. // Configuration whether an exception is thrown on error or interrupt. When -// the preprocessor macros below evaluate to FALSE, an error (did_emsg) or +// the preprocessor macros below evaluate to false, an error (did_emsg) or // interrupt (got_int) under an active try conditional terminates the script // after the non-active finally clauses of all active try conditionals have been // executed. Otherwise, errors and/or interrupts are converted into catchable -// exceptions, which terminate the script only if not caught. For user -// exceptions, only current_exception is set. (Note: got_int can be set -// asynchronously afterwards by a SIGINT, so current_exception && got_int is not +// exceptions (did_throw additionally set), which terminate the script only if +// not caught. For user exceptions, only did_throw is set. (Note: got_int can +// be set asynchronously afterwards by a SIGINT, so did_throw && got_int is not // a reliant test that the exception currently being thrown is an interrupt // exception. Similarly, did_emsg can be set afterwards on an error in an -// (unskipped) conditional command inside an inactive conditional, so -// current_exception && did_emsg is not a reliant test that the exception -// currently being thrown is an error exception.) - The macros can be defined -// as expressions checking for a variable that is allowed to be changed during -// execution of a script. +// (unskipped) conditional command inside an inactive conditional, so did_throw +// && did_emsg is not a reliant test that the exception currently being thrown +// is an error exception.) - The macros can be defined as expressions checking +// for a variable that is allowed to be changed during execution of a script. // Values used for the Vim release. #define THROW_ON_ERROR true @@ -71,7 +70,7 @@ #define CHECK_SKIP \ (did_emsg \ || got_int \ - || current_exception \ + || did_throw \ || (cstack->cs_idx > 0 \ && !(cstack->cs_flags[cstack->cs_idx - 1] & CSF_ACTIVE))) @@ -82,14 +81,14 @@ static void discard_pending_return(typval_T *p) /* * When several errors appear in a row, setting "force_abort" is delayed until - * the failing command returned. "cause_abort" is set to TRUE meanwhile, in + * the failing command returned. "cause_abort" is set to true meanwhile, in * order to indicate that situation. This is useful when "force_abort" was set * during execution of a function call from an expression: the aborting of the * expression evaluation is done without producing any error messages, but all * error messages on parsing errors during the expression evaluation are given * (even if a try conditional is active). */ -static int cause_abort = FALSE; +static int cause_abort = false; /// @return true when immediately aborting on error, or when an interrupt /// occurred or an exception was thrown but not caught. @@ -104,7 +103,7 @@ static int cause_abort = FALSE; /// value. "got_int" is also set by calling interrupt(). int aborting(void) { - return (did_emsg && force_abort) || got_int || current_exception; + return (did_emsg && force_abort) || got_int || did_throw; } /// The value of "force_abort" is temporarily reset by the first emsg() call @@ -114,11 +113,11 @@ int aborting(void) void update_force_abort(void) { if (cause_abort) { - force_abort = TRUE; + force_abort = true; } } -/// @return TRUE if a command with a subcommand resulting in "retcode" should +/// @return true if a command with a subcommand resulting in "retcode" should /// abort the script processing. Can be used to suppress an autocommand after /// execution of a failing subcommand as long as the error message has not been /// displayed and actually caused the abortion. @@ -127,7 +126,7 @@ int should_abort(int retcode) return (retcode == FAIL && trylevel != 0 && !emsg_silent) || aborting(); } -/// @return TRUE if a function with the "abort" flag should not be considered +/// @return true if a function with the "abort" flag should not be considered /// ended on an error. This means that parsing commands is continued in order /// to find finally clauses to be executed, and that some errors in skipped /// commands are still reported. @@ -151,8 +150,8 @@ int aborted_in_try(void) bool cause_errthrow(const char *mesg, bool severe, bool *ignore) FUNC_ATTR_NONNULL_ALL { - struct msglist *elem; - struct msglist **plist; + msglist_T *elem; + msglist_T **plist; /* * Do nothing when displaying the interrupt message or reporting an @@ -175,7 +174,7 @@ bool cause_errthrow(const char *mesg, bool severe, bool *ignore) */ if (!did_emsg) { cause_abort = force_abort; - force_abort = FALSE; + force_abort = false; } /* @@ -186,7 +185,7 @@ bool cause_errthrow(const char *mesg, bool severe, bool *ignore) * currently throwing an exception, do nothing. The message text will * then be stored to v:errmsg by emsg() without displaying it. */ - if (((trylevel == 0 && !cause_abort) || emsg_silent) && !current_exception) { + if (((trylevel == 0 && !cause_abort) || emsg_silent) && !did_throw) { return false; } @@ -206,7 +205,7 @@ bool cause_errthrow(const char *mesg, bool severe, bool *ignore) * Ensure that all commands in nested function calls and sourced files * are aborted immediately. */ - cause_abort = TRUE; + cause_abort = true; /* * When an exception is being thrown, some commands (like conditionals) are @@ -217,7 +216,7 @@ bool cause_errthrow(const char *mesg, bool severe, bool *ignore) * exception currently being thrown to prevent it from being caught. Just * execute finally clauses and terminate. */ - if (current_exception) { + if (did_throw) { // When discarding an interrupt exception, reset got_int to prevent the // same interrupt being converted to an exception again and discarding // the error exception we are about to throw here. @@ -254,7 +253,7 @@ bool cause_errthrow(const char *mesg, bool severe, bool *ignore) plist = &(*plist)->next; } - elem = xmalloc(sizeof(struct msglist)); + elem = xmalloc(sizeof(msglist_T)); elem->msg = xstrdup(mesg); elem->next = NULL; elem->throw_msg = NULL; @@ -275,20 +274,26 @@ bool cause_errthrow(const char *mesg, bool severe, bool *ignore) (*msg_list)->throw_msg = tmsg; } } + + // Get the source name and lnum now, it may change before + // reaching do_errthrow(). + elem->sfile = estack_sfile(ESTACK_NONE); + elem->slnum = SOURCING_LNUM; } return true; } } /// Free a "msg_list" and the messages it contains. -static void free_msglist(struct msglist *l) +static void free_msglist(msglist_T *l) { - struct msglist *messages, *next; + msglist_T *messages, *next; messages = l; while (messages != NULL) { next = messages->next; xfree(messages->msg); + xfree(messages->sfile); xfree(messages); messages = next; } @@ -312,8 +317,8 @@ void do_errthrow(cstack_T *cstack, char *cmdname) * are aborted immediately. */ if (cause_abort) { - cause_abort = FALSE; - force_abort = TRUE; + cause_abort = false; + force_abort = true; } // If no exception is to be thrown or the conversion should be done after @@ -328,7 +333,7 @@ void do_errthrow(cstack_T *cstack, char *cmdname) if (cstack != NULL) { do_throw(cstack); } else { - need_rethrow = TRUE; + need_rethrow = true; } } *msg_list = NULL; @@ -337,13 +342,13 @@ void do_errthrow(cstack_T *cstack, char *cmdname) /// do_intthrow(): Replace the current exception by an interrupt or interrupt /// exception if appropriate. /// -/// @return TRUE if the current exception is discarded or, -/// FALSE otherwise. +/// @return true if the current exception is discarded or, +/// false otherwise. int do_intthrow(cstack_T *cstack) { // If no interrupt occurred or no try conditional is active and no exception // is being thrown, do nothing (for compatibility of non-EH scripts). - if (!got_int || (trylevel == 0 && !current_exception)) { + if (!got_int || (trylevel == 0 && !did_throw)) { return false; } @@ -352,7 +357,7 @@ int do_intthrow(cstack_T *cstack) // The interrupt aborts everything except for executing finally clauses. // Discard any user or error or interrupt exception currently being // thrown. - if (current_exception) { + if (did_throw) { discard_current_exception(); } } else { @@ -363,7 +368,7 @@ int do_intthrow(cstack_T *cstack) // will be terminated then. - If an interrupt exception is already // being thrown, do nothing. - if (current_exception) { + if (did_throw) { if (current_exception->type == ET_INTERRUPT) { return false; } @@ -389,7 +394,7 @@ char *get_exception_string(void *value, except_type_T type, char *cmdname, int * if (type == ET_ERROR) { *should_free = true; - mesg = ((struct msglist *)value)->throw_msg; + mesg = ((msglist_T *)value)->throw_msg; if (cmdname != NULL && *cmdname != NUL) { size_t cmdlen = STRLEN(cmdname); ret = xstrnsave("Vim(", 4 + cmdlen + 2 + STRLEN(mesg)); @@ -469,7 +474,7 @@ static int throw_exception(void *value, except_type_T type, char *cmdname) if (type == ET_ERROR) { // Store the original message and prefix the exception value with // "Vim:" or, if a command name is given, "Vim(cmdname):". - excp->messages = (struct msglist *)value; + excp->messages = (msglist_T *)value; } excp->value = get_exception_string(value, type, cmdname, &should_free); @@ -478,20 +483,30 @@ static int throw_exception(void *value, except_type_T type, char *cmdname) } excp->type = type; - excp->throw_name = xstrdup(sourcing_name == NULL ? "" : sourcing_name); - excp->throw_lnum = sourcing_lnum; + if (type == ET_ERROR && ((msglist_T *)value)->sfile != NULL) { + msglist_T *entry = (msglist_T *)value; + excp->throw_name = entry->sfile; + entry->sfile = NULL; + excp->throw_lnum = entry->slnum; + } else { + excp->throw_name = estack_sfile(ESTACK_NONE); + if (excp->throw_name == NULL) { + excp->throw_name = xstrdup(""); + } + excp->throw_lnum = SOURCING_LNUM; + } if (p_verbose >= 13 || debug_break_level > 0) { int save_msg_silent = msg_silent; if (debug_break_level > 0) { - msg_silent = FALSE; // display messages + msg_silent = false; // display messages } else { verbose_enter(); } - ++no_wait_return; + no_wait_return++; if (debug_break_level > 0 || *p_vfile == NUL) { - msg_scroll = TRUE; // always scroll up, don't overwrite + msg_scroll = true; // always scroll up, don't overwrite } smsg(_("Exception thrown: %s"), excp->value); msg_puts("\n"); // don't overwrite this either @@ -499,7 +514,7 @@ static int throw_exception(void *value, except_type_T type, char *cmdname) if (debug_break_level > 0 || *p_vfile == NUL) { cmdline_row = msg_row; } - --no_wait_return; + no_wait_return--; if (debug_break_level > 0) { msg_silent = save_msg_silent; } else { @@ -538,13 +553,13 @@ static void discard_exception(except_T *excp, bool was_finished) saved_IObuff = (char *)vim_strsave(IObuff); if (debug_break_level > 0) { - msg_silent = FALSE; // display messages + msg_silent = false; // display messages } else { verbose_enter(); } - ++no_wait_return; + no_wait_return++; if (debug_break_level > 0 || *p_vfile == NUL) { - msg_scroll = TRUE; // always scroll up, don't overwrite + msg_scroll = true; // always scroll up, don't overwrite } smsg(was_finished ? _("Exception finished: %s") : _("Exception discarded: %s"), @@ -580,6 +595,7 @@ void discard_current_exception(void) } // Note: all globals manipulated here should be saved/restored in // try_enter/try_leave. + did_throw = false; need_rethrow = false; } @@ -606,13 +622,13 @@ static void catch_exception(except_T *excp) int save_msg_silent = msg_silent; if (debug_break_level > 0) { - msg_silent = FALSE; // display messages + msg_silent = false; // display messages } else { verbose_enter(); } - ++no_wait_return; + no_wait_return++; if (debug_break_level > 0 || *p_vfile == NUL) { - msg_scroll = TRUE; // always scroll up, don't overwrite + msg_scroll = true; // always scroll up, don't overwrite } smsg(_("Exception caught: %s"), excp->value); msg_puts("\n"); // don't overwrite this either @@ -620,7 +636,7 @@ static void catch_exception(except_T *excp) if (debug_break_level > 0 || *p_vfile == NUL) { cmdline_row = msg_row; } - --no_wait_return; + no_wait_return--; if (debug_break_level > 0) { msg_silent = save_msg_silent; } else { @@ -710,14 +726,14 @@ static void report_pending(int action, int pending, void *value) break; case CSTP_RETURN: // ":return" command producing value, allocated - s = (char *)get_return_cmd(value); + s = get_return_cmd(value); break; default: if (pending & CSTP_THROW) { vim_snprintf((char *)IObuff, IOSIZE, mesg, _("Exception")); - mesg = (char *)concat_str(IObuff, (char_u *)": %s"); + mesg = concat_str((char *)IObuff, ": %s"); s = ((except_T *)value)->value; } else if ((pending & CSTP_ERROR) && (pending & CSTP_INTERRUPT)) { s = _("Error and interrupt"); @@ -730,14 +746,14 @@ static void report_pending(int action, int pending, void *value) save_msg_silent = msg_silent; if (debug_break_level > 0) { - msg_silent = FALSE; // display messages + msg_silent = false; // display messages } - ++no_wait_return; - msg_scroll = TRUE; // always scroll up, don't overwrite + no_wait_return++; + msg_scroll = true; // always scroll up, don't overwrite smsg(mesg, s); msg_puts("\n"); // don't overwrite this either cmdline_row = msg_row; - --no_wait_return; + no_wait_return--; if (debug_break_level > 0) { msg_silent = save_msg_silent; } @@ -814,7 +830,7 @@ void ex_if(exarg_T *eap) if (cstack->cs_idx == CSTACK_LEN - 1) { eap->errmsg = _("E579: :if nesting too deep"); } else { - ++cstack->cs_idx; + cstack->cs_idx++; cstack->cs_flags[cstack->cs_idx] = 0; skip = CHECK_SKIP; @@ -854,7 +870,7 @@ void ex_endif(exarg_T *eap) (void)do_intthrow(eap->cstack); } - --eap->cstack->cs_idx; + eap->cstack->cs_idx--; } } @@ -948,8 +964,8 @@ void ex_while(exarg_T *eap) * cstack entry. */ if ((cstack->cs_lflags & CSL_HAD_LOOP) == 0) { - ++cstack->cs_idx; - ++cstack->cs_looplevel; + cstack->cs_idx++; + cstack->cs_looplevel++; cstack->cs_line[cstack->cs_idx] = -1; } cstack->cs_flags[cstack->cs_idx] = @@ -971,7 +987,7 @@ void ex_while(exarg_T *eap) // Jumping here from a ":continue" or ":endfor": use the // previously evaluated list. fi = cstack->cs_forinfo[cstack->cs_idx]; - error = FALSE; + error = false; } else { // Evaluate the argument and get the info in a structure. fi = eval_for_line(eap->arg, &error, &eap->nextcmd, skip); @@ -982,7 +998,7 @@ void ex_while(exarg_T *eap) if (!error && fi != NULL && !skip) { result = next_for_item(fi, eap->arg); } else { - result = FALSE; + result = false; } if (!result) { @@ -1102,7 +1118,7 @@ void ex_endwhile(exarg_T *eap) eap->errmsg = _(e_endtry); } // Try to find the matching ":while" and report what's missing. - for (idx = cstack->cs_idx; idx > 0; --idx) { + for (idx = cstack->cs_idx; idx > 0; idx--) { fl = cstack->cs_flags[idx]; if ((fl & CSF_TRY) && !(fl & CSF_FINALLY)) { // Give up at a try conditional not in its finally clause. @@ -1115,7 +1131,7 @@ void ex_endwhile(exarg_T *eap) } } // Cleanup and rewind all contained (and unclosed) conditionals. - (void)cleanup_conditionals(cstack, CSF_WHILE | CSF_FOR, FALSE); + (void)cleanup_conditionals(cstack, CSF_WHILE | CSF_FOR, false); rewind_conditionals(cstack, idx, CSF_TRY, &cstack->cs_trylevel); } else if (cstack->cs_flags[cstack->cs_idx] & CSF_TRUE && !(cstack->cs_flags[cstack->cs_idx] & CSF_ACTIVE) @@ -1170,7 +1186,7 @@ void ex_throw(exarg_T *eap) void do_throw(cstack_T *cstack) { int idx; - int inactivate_try = FALSE; + int inactivate_try = false; // // Cleanup and deactivate up to the next surrounding try conditional that @@ -1183,14 +1199,14 @@ void do_throw(cstack_T *cstack) // #ifndef THROW_ON_ERROR_TRUE if (did_emsg && !THROW_ON_ERROR) { - inactivate_try = TRUE; - did_emsg = FALSE; + inactivate_try = true; + did_emsg = false; } #endif #ifndef THROW_ON_INTERRUPT_TRUE if (got_int && !THROW_ON_INTERRUPT) { - inactivate_try = TRUE; - got_int = FALSE; + inactivate_try = true; + got_int = false; } #endif idx = cleanup_conditionals(cstack, 0, inactivate_try); @@ -1221,6 +1237,8 @@ void do_throw(cstack_T *cstack) cstack->cs_flags[idx] &= ~CSF_ACTIVE; cstack->cs_exception[idx] = current_exception; } + + did_throw = true; } /// Handle ":try" @@ -1232,8 +1250,8 @@ void ex_try(exarg_T *eap) if (cstack->cs_idx == CSTACK_LEN - 1) { eap->errmsg = _("E601: :try nesting too deep"); } else { - ++cstack->cs_idx; - ++cstack->cs_trylevel; + cstack->cs_idx++; + cstack->cs_trylevel++; cstack->cs_flags[cstack->cs_idx] = CSF_TRY; cstack->cs_pending[cstack->cs_idx] = CSTP_NONE; @@ -1298,7 +1316,7 @@ void ex_catch(exarg_T *eap) eap->errmsg = get_end_emsg(cstack); skip = true; } - for (idx = cstack->cs_idx; idx > 0; --idx) { + for (idx = cstack->cs_idx; idx > 0; idx--) { if (cstack->cs_flags[idx] & CSF_TRY) { break; } @@ -1317,10 +1335,10 @@ void ex_catch(exarg_T *eap) if (ends_excmd(*eap->arg)) { // no argument, catch all errors pat = ".*"; end = NULL; - eap->nextcmd = (char *)find_nextcmd((char_u *)eap->arg); + eap->nextcmd = find_nextcmd(eap->arg); } else { pat = eap->arg + 1; - end = (char *)skip_regexp((char_u *)pat, *eap->arg, true, NULL); + end = skip_regexp(pat, *eap->arg, true, NULL); } if (!give_up) { @@ -1329,7 +1347,7 @@ void ex_catch(exarg_T *eap) * corresponding try block never got active (because of an inactive * surrounding conditional or after an error or interrupt or throw). */ - if (!current_exception || !(cstack->cs_flags[idx] & CSF_TRUE)) { + if (!did_throw || !(cstack->cs_flags[idx] & CSF_TRUE)) { skip = true; } @@ -1360,7 +1378,7 @@ void ex_catch(exarg_T *eap) *end = NUL; } save_cpo = p_cpo; - p_cpo = ""; + p_cpo = empty_option; // Disable error messages, it will make current exception // invalid emsg_off++; @@ -1390,10 +1408,10 @@ void ex_catch(exarg_T *eap) } if (caught) { - // Make this ":catch" clause active and reset did_emsg and got_int. - // Put the exception on the caught stack. + // Make this ":catch" clause active and reset did_emsg, got_int, + // and did_throw. Put the exception on the caught stack. cstack->cs_flags[idx] |= CSF_ACTIVE | CSF_CAUGHT; - did_emsg = got_int = false; + did_emsg = got_int = did_throw = false; catch_exception((except_T *)cstack->cs_exception[idx]); // It's mandatory that the current exception is stored in the cstack // so that it can be discarded at the next ":catch", ":finally", or @@ -1403,10 +1421,6 @@ void ex_catch(exarg_T *eap) if (cstack->cs_exception[cstack->cs_idx] != current_exception) { internal_error("ex_catch()"); } - // Discarding current_exceptions happens based on what is stored in - // cstack->cs_exception, *all* calls to discard_current_exception() are - // (and must be) guarded by current_exception check. - current_exception = NULL; } else { /* * If there is a preceding catch clause and it caught the exception, @@ -1418,12 +1432,12 @@ void ex_catch(exarg_T *eap) * a ":continue", ":break", ":return", or ":finish", discard the * pending action. */ - cleanup_conditionals(cstack, CSF_TRY, TRUE); + cleanup_conditionals(cstack, CSF_TRY, true); } } if (end != NULL) { - eap->nextcmd = (char *)find_nextcmd((char_u *)end); + eap->nextcmd = find_nextcmd(end); } } @@ -1431,7 +1445,7 @@ void ex_catch(exarg_T *eap) void ex_finally(exarg_T *eap) { int idx; - int skip = FALSE; + int skip = false; int pending = CSTP_NONE; cstack_T *const cstack = eap->cstack; @@ -1440,7 +1454,7 @@ void ex_finally(exarg_T *eap) } 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) { + for (idx = cstack->cs_idx - 1; idx > 0; idx--) { if (cstack->cs_flags[idx] & CSF_TRY) { break; } @@ -1461,14 +1475,12 @@ void ex_finally(exarg_T *eap) 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 (current_exception), the finally clause must be executed. - */ + // 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) { @@ -1491,24 +1503,22 @@ void ex_finally(exarg_T *eap) * ":continue", ":break", ":finish", or ":return" from the preceding * try block or catch clause. */ - cleanup_conditionals(cstack, CSF_TRY, FALSE); - - /* - * Make did_emsg, got_int, current_exception 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 current_exception had - * already been set, respectively in case that the error is not - * converted to an exception, current_exception 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 || current_exception) { + 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]); @@ -1517,7 +1527,7 @@ void ex_finally(exarg_T *eap) if (pending == CSTP_ERROR && !did_emsg) { pending |= (THROW_ON_ERROR ? CSTP_THROW : 0); } else { - pending |= (current_exception ? CSTP_THROW : 0); + pending |= (did_throw ? CSTP_THROW : 0); } pending |= did_emsg ? CSTP_ERROR : 0; pending |= got_int ? CSTP_INTERRUPT : 0; @@ -1531,19 +1541,16 @@ void ex_finally(exarg_T *eap) // exception. When emsg() is called for a missing ":endif" or // a missing ":endwhile"/":endfor" detected here, the // exception will be discarded. - if (current_exception - && cstack->cs_exception[cstack->cs_idx] != current_exception) { + 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 current_exception 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. - */ + // 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; } } @@ -1571,8 +1578,7 @@ void ex_endtry(exarg_T *eap) // made inactive by a ":continue", ":break", ":return", or ":finish" in // the finally clause. The latter case need not be tested since then // anything pending has already been discarded. - bool skip = did_emsg || got_int || current_exception - || !(cstack->cs_flags[cstack->cs_idx] & CSF_TRUE); + bool skip = did_emsg || got_int || did_throw || !(cstack->cs_flags[cstack->cs_idx] & CSF_TRUE); if (!(cstack->cs_flags[cstack->cs_idx] & CSF_TRY)) { eap->errmsg = get_end_emsg(cstack); @@ -1586,14 +1592,12 @@ void ex_endtry(exarg_T *eap) &cstack->cs_looplevel); skip = 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 current_exception. - * This does not affect the script termination due to the error - * since "trylevel" is decremented after emsg() has been called. - */ - if (current_exception) { + // 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(); } @@ -1608,7 +1612,7 @@ void ex_endtry(exarg_T *eap) * a finally clause, we need to rethrow it after closing the try * conditional. */ - if (current_exception + if (did_throw && (cstack->cs_flags[idx] & CSF_TRUE) && !(cstack->cs_flags[idx] & CSF_FINALLY)) { rethrow = true; @@ -1632,10 +1636,10 @@ void ex_endtry(exarg_T *eap) if (got_int) { skip = true; (void)do_intthrow(cstack); - // The do_intthrow() call may have reset current_exception or + // The do_intthrow() call may have reset did_throw or // cstack->cs_pending[idx]. rethrow = false; - if (current_exception && !(cstack->cs_flags[idx] & CSF_FINALLY)) { + if (did_throw && !(cstack->cs_flags[idx] & CSF_FINALLY)) { rethrow = true; } } @@ -1667,7 +1671,7 @@ void ex_endtry(exarg_T *eap) * 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); + (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--; @@ -1696,16 +1700,16 @@ void ex_endtry(exarg_T *eap) ex_break(eap); break; case CSTP_RETURN: - do_return(eap, FALSE, FALSE, rettv); + do_return(eap, false, false, rettv); break; case CSTP_FINISH: - do_finish(eap, FALSE); + 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 current_exception. This is skipped, if there + // 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: @@ -1752,35 +1756,32 @@ void enter_cleanup(cleanup_T *csp) { int pending = CSTP_NONE; - /* - * Postpone did_emsg, got_int, current_exception. The pending values will be - * restored by leave_cleanup() except if there was an aborting error, - * interrupt, or uncaught exception after this function ends. - */ - if (did_emsg || got_int || current_exception || need_rethrow) { + // Postpone did_emsg, got_int, did_throw. The pending values will be + // restored by leave_cleanup() except if there was an aborting error, + // interrupt, or uncaught exception after this function ends. + if (did_emsg || got_int || did_throw || need_rethrow) { csp->pending = (did_emsg ? CSTP_ERROR : 0) | (got_int ? CSTP_INTERRUPT : 0) - | (current_exception ? CSTP_THROW : 0) + | (did_throw ? CSTP_THROW : 0) | (need_rethrow ? CSTP_THROW : 0); - // If we are currently throwing an exception, save it as well. On an error - // not yet converted to an exception, update "force_abort" and reset - // "cause_abort" (as do_errthrow() would do). This is needed for the - // do_cmdline() call that is going to be made for autocommand execution. We - // need not save *msg_list because there is an extra instance for every call - // of do_cmdline(), anyway. - if (current_exception || need_rethrow) { + // If we are currently throwing an exception (did_throw), save it as + // well. On an error not yet converted to an exception, update + // "force_abort" and reset "cause_abort" (as do_errthrow() would do). + // This is needed for the do_cmdline() call that is going to be made + // for autocommand execution. We need not save *msg_list because + // there is an extra instance for every call of do_cmdline(), anyway. + if (did_throw || need_rethrow) { csp->exception = current_exception; current_exception = NULL; } else { csp->exception = NULL; if (did_emsg) { force_abort |= cause_abort; - cause_abort = FALSE; + cause_abort = false; } } - did_emsg = got_int = need_rethrow = false; - current_exception = NULL; + did_emsg = got_int = did_throw = need_rethrow = false; // Report if required by the 'verbose' option or when debugging. report_make_pending(pending, csp->exception); @@ -1847,10 +1848,10 @@ void leave_cleanup(cleanup_T *csp) // enter_cleanup() was called, let "cause_abort" take the part of // "force_abort" (as done by cause_errthrow()). cause_abort = force_abort; - force_abort = FALSE; + force_abort = false; } - // Restore the pending values of did_emsg, got_int, and current_exception. + // Restore the pending values of did_emsg, got_int, and did_throw. if (pending & CSTP_ERROR) { did_emsg = true; } @@ -1858,7 +1859,7 @@ void leave_cleanup(cleanup_T *csp) got_int = true; } if (pending & CSTP_THROW) { - need_rethrow = true; // current_exception will be set by do_one_cmd() + need_rethrow = true; // did_throw will be set by do_one_cmd() } // Report if required by the 'verbose' option or when debugging. @@ -1879,7 +1880,7 @@ void leave_cleanup(cleanup_T *csp) /// inactive itself (a try conditional not in its finally /// clause possibly find before is always made inactive). /// -/// If "inclusive" is TRUE and "searched_cond" is CSF_TRY|CSF_SILENT, the saved +/// If "inclusive" is true and "searched_cond" is CSF_TRY|CSF_SILENT, the saved /// former value of "emsg_silent", if reset when the try conditional finally /// reached was entered, is restored (used by ex_endtry()). This is normally /// done only when such a try conditional is left. @@ -1888,9 +1889,9 @@ void leave_cleanup(cleanup_T *csp) int cleanup_conditionals(cstack_T *cstack, int searched_cond, int inclusive) { int idx; - int stop = FALSE; + int stop = false; - for (idx = cstack->cs_idx; idx >= 0; --idx) { + for (idx = cstack->cs_idx; idx >= 0; idx--) { if (cstack->cs_flags[idx] & CSF_TRY) { /* * Discard anything pending in a finally clause and continue the @@ -1952,20 +1953,20 @@ int cleanup_conditionals(cstack_T *cstack, int searched_cond, int inclusive) if (searched_cond == 0 && !inclusive) { break; } - stop = TRUE; + stop = true; } } } // Stop on the searched conditional type (even when the surrounding // conditional is not active or something has been made pending). - // If "inclusive" is TRUE and "searched_cond" is CSF_TRY|CSF_SILENT, + // If "inclusive" is true and "searched_cond" is CSF_TRY|CSF_SILENT, // check first whether "emsg_silent" needs to be restored. if (cstack->cs_flags[idx] & searched_cond) { if (!inclusive) { break; } - stop = TRUE; + stop = true; } cstack->cs_flags[idx] &= ~CSF_ACTIVE; if (stop && searched_cond != (CSF_TRY | CSF_SILENT)) { @@ -2020,7 +2021,7 @@ void rewind_conditionals(cstack_T *cstack, int idx, int cond_type, int *cond_lev if (cstack->cs_flags[cstack->cs_idx] & CSF_FOR) { free_for_info(cstack->cs_forinfo[cstack->cs_idx]); } - --cstack->cs_idx; + cstack->cs_idx--; } } @@ -2030,7 +2031,7 @@ void ex_endfunction(exarg_T *eap) emsg(_("E193: :endfunction not inside a function")); } -/// @return TRUE if the string "p" looks like a ":while" or ":for" command. +/// @return true if the string "p" looks like a ":while" or ":for" command. int has_loop_cmd(char *p) { int len; @@ -2038,7 +2039,7 @@ int has_loop_cmd(char *p) // skip modifiers, white space and ':' for (;;) { while (*p == ' ' || *p == '\t' || *p == ':') { - ++p; + p++; } len = modifier_len(p); if (len == 0) { @@ -2048,7 +2049,7 @@ int has_loop_cmd(char *p) } if ((p[0] == 'w' && p[1] == 'h') || (p[0] == 'f' && p[1] == 'o' && p[2] == 'r')) { - return TRUE; + return true; } - return FALSE; + return false; } diff --git a/src/nvim/ex_eval.h b/src/nvim/ex_eval.h index 235875fb91..9e3ac5e9c1 100644 --- a/src/nvim/ex_eval.h +++ b/src/nvim/ex_eval.h @@ -2,81 +2,7 @@ #define NVIM_EX_EVAL_H #include "nvim/ex_cmds_defs.h" // for exarg_T -#include "nvim/pos.h" // for linenr_T - -/* There is no CSF_IF, the lack of CSF_WHILE, CSF_FOR and CSF_TRY means ":if" - * was used. */ -#define CSF_TRUE 0x0001 // condition was TRUE -#define CSF_ACTIVE 0x0002 // current state is active -#define CSF_ELSE 0x0004 // ":else" has been passed -#define CSF_WHILE 0x0008 // is a ":while" -#define CSF_FOR 0x0010 // is a ":for" - -#define CSF_TRY 0x0100 // is a ":try" -#define CSF_FINALLY 0x0200 // ":finally" has been passed -#define CSF_THROWN 0x0800 // exception thrown to this try conditional -#define CSF_CAUGHT 0x1000 // exception caught by this try conditional -#define CSF_FINISHED 0x2000 // CSF_CAUGHT was handled by finish_exception() -#define CSF_SILENT 0x4000 // "emsg_silent" reset by ":try" -// Note that CSF_ELSE is only used when CSF_TRY and CSF_WHILE are unset -// (an ":if"), and CSF_SILENT is only used when CSF_TRY is set. - -/* - * What's pending for being reactivated at the ":endtry" of this try - * conditional: - */ -#define CSTP_NONE 0 // nothing pending in ":finally" clause -#define CSTP_ERROR 1 // an error is pending -#define CSTP_INTERRUPT 2 // an interrupt is pending -#define CSTP_THROW 4 // a throw is pending -#define CSTP_BREAK 8 // ":break" is pending -#define CSTP_CONTINUE 16 // ":continue" is pending -#define CSTP_RETURN 24 // ":return" is pending -#define CSTP_FINISH 32 // ":finish" is pending - -/* - * A list of error messages that can be converted to an exception. "throw_msg" - * is only set in the first element of the list. Usually, it points to the - * original message stored in that element, but sometimes it points to a later - * message in the list. See cause_errthrow() below. - */ -struct msglist { - char *msg; // original message - char *throw_msg; // msg to throw: usually original one - struct msglist *next; // next of several messages in a row -}; - -// The exception types. -typedef enum { - ET_USER, // exception caused by ":throw" command - ET_ERROR, // error exception - ET_INTERRUPT, // interrupt exception triggered by Ctrl-C -} except_type_T; - -/* - * Structure describing an exception. - * (don't use "struct exception", it's used by the math library). - */ -typedef struct vim_exception except_T; -struct vim_exception { - except_type_T type; // exception type - char *value; // exception value - struct msglist *messages; // message(s) causing error exception - char *throw_name; // name of the throw point - linenr_T throw_lnum; // line number of the throw point - except_T *caught; // next exception on the caught stack -}; - -/* - * Structure to save the error/interrupt/exception state between calls to - * enter_cleanup() and leave_cleanup(). Must be allocated as an automatic - * variable by the (common) caller of these functions. - */ -typedef struct cleanup_stuff cleanup_T; -struct cleanup_stuff { - int pending; // error/interrupt/exception state - except_T *exception; // exception value -}; +#include "nvim/ex_eval_defs.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "ex_eval.h.generated.h" diff --git a/src/nvim/ex_eval_defs.h b/src/nvim/ex_eval_defs.h new file mode 100644 index 0000000000..9da0c9ad12 --- /dev/null +++ b/src/nvim/ex_eval_defs.h @@ -0,0 +1,79 @@ +#ifndef NVIM_EX_EVAL_DEFS_H +#define NVIM_EX_EVAL_DEFS_H + +#include "nvim/pos.h" // for linenr_T + +/// There is no CSF_IF, the lack of CSF_WHILE, CSF_FOR and CSF_TRY means ":if" +/// was used. +enum { + CSF_TRUE = 0x0001, ///< condition was TRUE + CSF_ACTIVE = 0x0002, ///< current state is active + CSF_ELSE = 0x0004, ///< ":else" has been passed + CSF_WHILE = 0x0008, ///< is a ":while" + CSF_FOR = 0x0010, ///< is a ":for" + + CSF_TRY = 0x0100, ///< is a ":try" + CSF_FINALLY = 0x0200, ///< ":finally" has been passed + CSF_THROWN = 0x0800, ///< exception thrown to this try conditional + CSF_CAUGHT = 0x1000, ///< exception caught by this try conditional + CSF_FINISHED = 0x2000, ///< CSF_CAUGHT was handled by finish_exception() + CSF_SILENT = 0x4000, ///< "emsg_silent" reset by ":try" +}; +// Note that CSF_ELSE is only used when CSF_TRY and CSF_WHILE are unset +// (an ":if"), and CSF_SILENT is only used when CSF_TRY is set. + +/// What's pending for being reactivated at the ":endtry" of this try +/// conditional: +enum { + CSTP_NONE = 0, ///< nothing pending in ":finally" clause + CSTP_ERROR = 1, ///< an error is pending + CSTP_INTERRUPT = 2, ///< an interrupt is pending + CSTP_THROW = 4, ///< a throw is pending + CSTP_BREAK = 8, ///< ":break" is pending + CSTP_CONTINUE = 16, ///< ":continue" is pending + CSTP_RETURN = 24, ///< ":return" is pending + CSTP_FINISH = 32, ///< ":finish" is pending +}; + +/// A list of error messages that can be converted to an exception. "throw_msg" +/// is only set in the first element of the list. Usually, it points to the +/// original message stored in that element, but sometimes it points to a later +/// message in the list. See cause_errthrow(). +typedef struct msglist msglist_T; +struct msglist { + char *msg; ///< original message, allocated + char *throw_msg; ///< msg to throw: usually original one + char *sfile; ///< value from estack_sfile(), allocated + linenr_T slnum; ///< line number for "sfile" + msglist_T *next; ///< next of several messages in a row +}; + +/// The exception types. +typedef enum { + ET_USER, ///< exception caused by ":throw" command + ET_ERROR, ///< error exception + ET_INTERRUPT, ///< interrupt exception triggered by Ctrl-C +} except_type_T; + +/// Structure describing an exception. +/// (don't use "struct exception", it's used by the math library). +typedef struct vim_exception except_T; +struct vim_exception { + except_type_T type; ///< exception type + char *value; ///< exception value + msglist_T *messages; ///< message(s) causing error exception + char *throw_name; ///< name of the throw point + linenr_T throw_lnum; ///< line number of the throw point + except_T *caught; ///< next exception on the caught stack +}; + +/// Structure to save the error/interrupt/exception state between calls to +/// enter_cleanup() and leave_cleanup(). Must be allocated as an automatic +/// variable by the (common) caller of these functions. +typedef struct cleanup_stuff cleanup_T; +struct cleanup_stuff { + int pending; ///< error/interrupt/exception state + except_T *exception; ///< exception value +}; + +#endif // NVIM_EX_EVAL_DEFS_H diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 07a0e68884..0998bdd867 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -12,23 +12,22 @@ #include <string.h> #include "nvim/api/extmark.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/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/eval/funcs.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" #include "nvim/ex_eval.h" #include "nvim/ex_getln.h" @@ -37,113 +36,47 @@ #include "nvim/garray.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/if_cscope.h" #include "nvim/indent.h" #include "nvim/keycodes.h" #include "nvim/lib/kvec.h" #include "nvim/log.h" -#include "nvim/lua/executor.h" #include "nvim/main.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" #include "nvim/ops.h" #include "nvim/option.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/popupmnu.h" +#include "nvim/popupmenu.h" +#include "nvim/profile.h" #include "nvim/regexp.h" -#include "nvim/screen.h" #include "nvim/search.h" -#include "nvim/sign.h" #include "nvim/state.h" #include "nvim/strings.h" -#include "nvim/syntax.h" -#include "nvim/tag.h" #include "nvim/ui.h" #include "nvim/undo.h" +#include "nvim/usercmd.h" #include "nvim/vim.h" #include "nvim/viml/parser/expressions.h" #include "nvim/viml/parser/parser.h" #include "nvim/window.h" -/// Command-line colors: one chunk -/// -/// Defines a region which has the same highlighting. -typedef struct { - int start; ///< Colored chunk start. - int end; ///< Colored chunk end (exclusive, > start). - int attr; ///< Highlight attr. -} CmdlineColorChunk; - -/// Command-line colors -/// -/// Holds data about all colors. -typedef kvec_t(CmdlineColorChunk) CmdlineColors; - -/// Command-line coloring -/// -/// Holds both what are the colors and what have been colored. Latter is used to -/// suppress unnecessary calls to coloring callbacks. -typedef struct { - unsigned prompt_id; ///< ID of the prompt which was colored last. - char *cmdbuff; ///< What exactly was colored last time or NULL. - CmdlineColors colors; ///< Last colors. -} ColoredCmdline; - -/// Keeps track how much state must be sent to external ui. -typedef enum { - kCmdRedrawNone, - kCmdRedrawPos, - kCmdRedrawAll, -} CmdRedraw; - -// Variables shared between getcmdline(), redrawcmdline() and others. -// These need to be saved when using CTRL-R |, that's why they are in a -// structure. -struct cmdline_info { - char_u *cmdbuff; // pointer to command line buffer - int cmdbufflen; // length of cmdbuff - int cmdlen; // number of chars in command line - int cmdpos; // current cursor position - 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 - 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 - 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. - ColoredCmdline last_colors; ///< Last cmdline colors - int level; // current cmdline level - struct cmdline_info *prev_ccline; ///< pointer to saved cmdline state - char special_char; ///< last putcmdline char (used for redraws) - bool special_shift; ///< shift of last putcmdline char - CmdRedraw redraw_state; ///< needed redraw for external cmdline -}; - /// Last value of prompt_id, incremented when doing new prompt static unsigned last_prompt_id = 0; -// Struct to store the viewstate during 'incsearch' highlighting. +// Struct to store the viewstate during 'incsearch' highlighting and 'inccommand' preview. typedef struct { colnr_T vs_curswant; colnr_T vs_leftcol; @@ -172,8 +105,8 @@ typedef struct command_line_state { long count; int indent; int c; - int gotesc; // TRUE when <ESC> just typed - int do_abbr; // when TRUE check for abbr. + int gotesc; // true when <ESC> just typed + int do_abbr; // when true check for abbr. char_u *lookfor; // string to match int hiscnt; // current history line in use int save_hiscnt; // history line before attempting @@ -195,44 +128,49 @@ typedef struct command_line_state { long *b_im_ptr; } CommandLineState; -typedef struct cmdline_info CmdlineInfo; +typedef struct cmdpreview_win_info { + win_T *win; + pos_T save_w_cursor; + viewstate_T save_viewstate; + int save_w_p_cul; + int save_w_p_cuc; +} CpWinInfo; + +typedef struct cmdpreview_buf_info { + buf_T *buf; + time_t save_b_u_time_cur; + long save_b_u_seq_cur; + u_header_T *save_b_u_newhead; + long save_b_p_ul; + int save_b_changed; + varnumber_T save_changedtick; +} CpBufInfo; + +typedef struct cmdpreview_info { + kvec_t(CpWinInfo) win_info; + kvec_t(CpBufInfo) buf_info; + bool save_hls; + cmdmod_T save_cmdmod; + garray_T save_view; +} CpInfo; /// The current cmdline_info. It is initialized in getcmdline() and after that /// used by other functions. When invoking getcmdline() recursively it needs /// to be saved with save_cmdline() and restored with restore_cmdline(). -static struct cmdline_info ccline; - -static int cmd_showtail; // Only show path tail in lists ? +static CmdlineInfo ccline; static int new_cmdpos; // position set by set_cmdline_pos() /// currently displayed block of context static Array cmdline_block = ARRAY_DICT_INIT; -/* - * Type used by call_user_expand_func - */ -typedef void *(*user_expand_func_T)(const char_u *, int, typval_T *); - -static histentry_T *(history[HIST_COUNT]) = { NULL, NULL, NULL, NULL, NULL }; -static int hisidx[HIST_COUNT] = { -1, -1, -1, -1, -1 }; // lastused entry -static int hisnum[HIST_COUNT] = { 0, 0, 0, 0, 0 }; -// identifying (unique) number of newest history entry -static int hislen = 0; // actual length of history tables - /// Flag for command_line_handle_key to ignore <C-c> /// /// Used if it was received while processing highlight function in order for /// user interrupting highlight function to not interrupt command-line. static bool getln_interrupted_highlight = false; -// "compl_match_array" points the currently displayed list of entries in the -// popup menu. It is NULL when there is no popup menu. -static pumitem_T *compl_match_array = NULL; -static int compl_match_arraysize; -// First column in cmdline of the matched item for completion. -static int compl_startcol; -static int compl_selected; +static int cedit_key = -1; ///< key value of 'cedit' option #ifdef INCLUDE_GENERATED_DECLARATIONS # include "ex_getln.c.generated.h" @@ -243,26 +181,26 @@ static long cmdpreview_ns = 0; static int cmd_hkmap = 0; // Hebrew mapping during command line -static void save_viewstate(viewstate_T *vs) +static void save_viewstate(win_T *wp, viewstate_T *vs) FUNC_ATTR_NONNULL_ALL { - vs->vs_curswant = curwin->w_curswant; - vs->vs_leftcol = curwin->w_leftcol; - vs->vs_topline = curwin->w_topline; - vs->vs_topfill = curwin->w_topfill; - vs->vs_botline = curwin->w_botline; - vs->vs_empty_rows = curwin->w_empty_rows; + vs->vs_curswant = wp->w_curswant; + vs->vs_leftcol = wp->w_leftcol; + vs->vs_topline = wp->w_topline; + vs->vs_topfill = wp->w_topfill; + vs->vs_botline = wp->w_botline; + vs->vs_empty_rows = wp->w_empty_rows; } -static void restore_viewstate(viewstate_T *vs) +static void restore_viewstate(win_T *wp, viewstate_T *vs) FUNC_ATTR_NONNULL_ALL { - curwin->w_curswant = vs->vs_curswant; - curwin->w_leftcol = vs->vs_leftcol; - curwin->w_topline = vs->vs_topline; - curwin->w_topfill = vs->vs_topfill; - curwin->w_botline = vs->vs_botline; - curwin->w_empty_rows = vs->vs_empty_rows; + wp->w_curswant = vs->vs_curswant; + wp->w_leftcol = vs->vs_leftcol; + wp->w_topline = vs->vs_topline; + wp->w_topfill = vs->vs_topfill; + wp->w_botline = vs->vs_botline; + wp->w_empty_rows = vs->vs_empty_rows; } static void init_incsearch_state(incsearch_state_T *s) @@ -274,34 +212,8 @@ static void init_incsearch_state(incsearch_state_T *s) clearpos(&s->match_end); s->save_cursor = curwin->w_cursor; // may be restored later s->search_start = curwin->w_cursor; - save_viewstate(&s->init_viewstate); - save_viewstate(&s->old_viewstate); -} - -/// Completion for |:checkhealth| command. -/// -/// Given to ExpandGeneric() to obtain all available heathcheck names. -/// @param[in] idx Index of the healthcheck item. -/// @param[in] xp Not used. -static char *get_healthcheck_names(expand_T *xp, int idx) -{ - static Object names = OBJECT_INIT; - static unsigned last_gen = 0; - - if (last_gen != last_prompt_id || last_gen == 0) { - Array a = ARRAY_DICT_INIT; - Error err = ERROR_INIT; - Object res = nlua_exec(STATIC_CSTR_AS_STRING("return vim.health._complete()"), a, &err); - api_clear_error(&err); - api_free_object(names); - names = res; - last_gen = last_prompt_id; - } - - if (names.type == kObjectTypeArray && idx < (int)names.data.array.size) { - return names.data.array.items[idx].data.string.data; - } - return NULL; + save_viewstate(curwin, &s->init_viewstate); + save_viewstate(curwin, &s->old_viewstate); } // Return true when 'incsearch' highlighting is to be done. @@ -316,7 +228,6 @@ static bool do_incsearch_highlighting(int firstc, int *search_delim, incsearch_s int delim; char *end; char *dummy; - exarg_T ea; pos_T save_cursor; bool use_last_pat; bool retval = false; @@ -341,11 +252,12 @@ static bool do_incsearch_highlighting(int firstc, int *search_delim, incsearch_s } emsg_off++; - memset(&ea, 0, sizeof(ea)); - ea.line1 = 1; - ea.line2 = 1; - ea.cmd = (char *)ccline.cmdbuff; - ea.addr_type = ADDR_LINES; + exarg_T ea = { + .line1 = 1, + .line2 = 1, + .cmd = (char *)ccline.cmdbuff, + .addr_type = ADDR_LINES, + }; cmdmod_T dummy_cmdmod; parse_command_modifiers(&ea, &dummy, &dummy_cmdmod, true); @@ -403,7 +315,7 @@ static bool do_incsearch_highlighting(int firstc, int *search_delim, incsearch_s p = skipwhite(p); delim = (delim_optional && vim_isIDc(*p)) ? ' ' : *p++; *search_delim = delim; - end = (char *)skip_regexp((char_u *)p, delim, p_magic, NULL); + end = skip_regexp(p, delim, p_magic, NULL); use_last_pat = end == p && *end == delim; if (end == p && !use_last_pat) { @@ -458,7 +370,6 @@ static void may_do_incsearch_highlighting(int firstc, long count, incsearch_stat { pos_T end_pos; proftime_T tm; - searchit_arg_T sia; int skiplen, patlen; char_u next_char; char_u use_last_pat; @@ -506,7 +417,7 @@ static void may_do_incsearch_highlighting(int firstc, long count, incsearch_stat if (patlen == 0 && !use_last_pat) { found = 0; set_no_hlsearch(true); // turn off previous highlight - redraw_all_later(SOME_VALID); + redraw_all_later(UPD_SOME_VALID); } else { int search_flags = SEARCH_OPT + SEARCH_NOOF + SEARCH_PEEK; ui_busy_start(); @@ -521,8 +432,9 @@ static void may_do_incsearch_highlighting(int firstc, long count, incsearch_stat search_flags += SEARCH_START; } ccline.cmdbuff[skiplen + patlen] = NUL; - memset(&sia, 0, sizeof(sia)); - sia.sa_tm = &tm; + searchit_arg_T sia = { + .sa_tm = &tm, + }; found = do_search(NULL, firstc == ':' ? '/' : firstc, search_delim, ccline.cmdbuff + skiplen, count, search_flags, &sia); @@ -555,7 +467,7 @@ static void may_do_incsearch_highlighting(int firstc, long count, incsearch_stat // first restore the old curwin values, so the screen is // positioned in the same way as the actual search command - restore_viewstate(&s->old_viewstate); + restore_viewstate(curwin, &s->old_viewstate); changed_cline_bef_curs(); update_topline(curwin); @@ -578,7 +490,7 @@ static void may_do_incsearch_highlighting(int firstc, long count, incsearch_stat next_char = ccline.cmdbuff[skiplen + patlen]; ccline.cmdbuff[skiplen + patlen] = NUL; if (empty_pattern(ccline.cmdbuff) && !no_hlsearch) { - redraw_all_later(SOME_VALID); + redraw_all_later(UPD_SOME_VALID); set_no_hlsearch(true); } ccline.cmdbuff[skiplen + patlen] = next_char; @@ -590,7 +502,7 @@ static void may_do_incsearch_highlighting(int firstc, long count, incsearch_stat curwin->w_redr_status = true; } - update_screen(SOME_VALID); + update_screen(UPD_SOME_VALID); highlight_match = false; restore_last_search_pattern(); @@ -665,7 +577,7 @@ static void finish_incsearch_highlighting(int gotesc, incsearch_state_T *s, bool } curwin->w_cursor = s->search_start; // -V519 } - restore_viewstate(&s->old_viewstate); + restore_viewstate(curwin, &s->old_viewstate); highlight_match = false; // by default search all lines @@ -675,9 +587,9 @@ static void finish_incsearch_highlighting(int gotesc, incsearch_state_T *s, bool p_magic = s->magic_save; validate_cursor(); // needed for TAB - redraw_all_later(SOME_VALID); + redraw_all_later(UPD_SOME_VALID); if (call_update_screen) { - update_screen(SOME_VALID); + update_screen(UPD_SOME_VALID); } } } @@ -701,7 +613,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent, bool init lastwin->w_p_so = 0; set_option_value("ch", 1L, NULL, 0); - update_screen(VALID); // redraw the screen NOW + update_screen(UPD_VALID); // redraw the screen NOW made_cmdheight_nonzero = false; lastwin->w_p_so = save_so; @@ -722,7 +634,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent, bool init .ignore_drag_release = true, }; CommandLineState *s = &state; - s->save_p_icm = vim_strsave(p_icm); + s->save_p_icm = vim_strsave((char_u *)p_icm); init_incsearch_state(&s->is_state); CmdlineInfo save_ccline; bool did_save_ccline = false; @@ -736,7 +648,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent, bool init save_cmdline(&save_ccline); did_save_ccline = true; } else if (init_ccline) { - memset(&ccline, 0, sizeof(struct cmdline_info)); + CLEAR_FIELD(ccline); } if (s->firstc == -1) { @@ -869,7 +781,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent, bool init may_trigger_modechanged(); init_history(); - s->hiscnt = hislen; // set hiscnt to impossible history value + s->hiscnt = get_hislen(); // set hiscnt to impossible history value s->histype = hist_char2type(s->firstc); do_digraph(-1); // init digraph typeahead @@ -942,7 +854,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent, bool init s->histype == HIST_SEARCH ? s->firstc : NUL); if (s->firstc == ':') { xfree(new_last_cmdline); - new_last_cmdline = vim_strsave(ccline.cmdbuff); + new_last_cmdline = (char *)vim_strsave(ccline.cmdbuff); } } @@ -973,7 +885,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent, bool init State = s->save_State; if (cmdpreview != save_cmdpreview) { cmdpreview = save_cmdpreview; // restore preview state - redraw_all_later(SOME_VALID); + redraw_all_later(UPD_SOME_VALID); } may_trigger_modechanged(); setmouse(); @@ -1006,7 +918,7 @@ theend: // Restore cmdheight set_option_value("ch", 0L, NULL, 0); // Redraw is needed for command line completion - redraw_all_later(CLEAR); + redraw_all_later(UPD_NOT_VALID); made_cmdheight_nonzero = false; } @@ -1111,41 +1023,24 @@ static int command_line_execute(VimState *state, int key) s->c = Ctrl_P; } - // Special translations for 'wildmenu' - if (s->did_wild_list && p_wmnu) { - if (s->c == K_LEFT) { - s->c = Ctrl_P; - } else if (s->c == K_RIGHT) { - s->c = Ctrl_N; - } + if (p_wmnu) { + s->c = wildmenu_translate_key(&ccline, s->c, &s->xpc, s->did_wild_list); } - if (compl_match_array || s->did_wild_list) { - if (s->c == Ctrl_E) { - s->res = nextwild(&s->xpc, WILD_CANCEL, WILD_NO_BEEP, - s->firstc != '@'); - } else if (s->c == Ctrl_Y) { - s->res = nextwild(&s->xpc, WILD_APPLY, WILD_NO_BEEP, - s->firstc != '@'); + + if (cmdline_pum_active() || s->did_wild_list) { + 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 != '@'); s->c = Ctrl_E; } } - // Hitting CR after "emenu Name.": complete submenu - if (s->xpc.xp_context == EXPAND_MENUNAMES && p_wmnu - && ccline.cmdpos > 1 - && ccline.cmdbuff[ccline.cmdpos - 1] == '.' - && ccline.cmdbuff[ccline.cmdpos - 2] != '\\' - && (s->c == '\n' || s->c == '\r' || s->c == K_KENTER)) { - s->c = K_DOWN; - } - // 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 (compl_match_array) { - pum_undisplay(true); - XFREE_CLEAR(compl_match_array); + if (cmdline_pum_active()) { + cmdline_pum_remove(); } if (s->xpc.xp_numfiles != -1) { (void)ExpandOne(&s->xpc, NULL, NULL, 0, WILD_FREE); @@ -1155,174 +1050,11 @@ static int command_line_execute(VimState *state, int key) s->xpc.xp_context = EXPAND_NOTHING; } s->wim_index = 0; - if (p_wmnu && wild_menu_showing != 0) { - const bool skt = KeyTyped; - int old_RedrawingDisabled = RedrawingDisabled; - - if (ccline.input_fn) { - RedrawingDisabled = 0; - } - - if (wild_menu_showing == WM_SCROLLED) { - // Entered command line, move it up - cmdline_row--; - redrawcmd(); - wild_menu_showing = 0; - } else if (save_p_ls != -1) { - // restore 'laststatus' and 'winminheight' - p_ls = save_p_ls; - p_wmh = save_p_wmh; - last_status(false); - update_screen(VALID); // redraw the screen NOW - redrawcmd(); - save_p_ls = -1; - wild_menu_showing = 0; - // don't redraw statusline if WM_LIST is showing - } else if (wild_menu_showing != WM_LIST) { - win_redraw_last_status(topframe); - wild_menu_showing = 0; // must be before redraw_statuslines #8385 - redraw_statuslines(); - } else { - wild_menu_showing = 0; - } - KeyTyped = skt; - if (ccline.input_fn) { - RedrawingDisabled = old_RedrawingDisabled; - } - } + wildmenu_cleanup(&ccline); } - // Special translations for 'wildmenu' - if (s->xpc.xp_context == EXPAND_MENUNAMES && p_wmnu) { - // Hitting <Down> after "emenu Name.": complete submenu - if (s->c == K_DOWN && ccline.cmdpos > 0 - && ccline.cmdbuff[ccline.cmdpos - 1] == '.') { - s->c = (int)p_wc; - KeyTyped = true; // in case the key was mapped - } else if (s->c == K_UP) { - // Hitting <Up>: Remove one submenu name in front of the - // cursor - int found = false; - - int j = (int)((char_u *)s->xpc.xp_pattern - ccline.cmdbuff); - int i = 0; - while (--j > 0) { - // check for start of menu name - if (ccline.cmdbuff[j] == ' ' - && ccline.cmdbuff[j - 1] != '\\') { - i = j + 1; - break; - } - - // check for start of submenu name - if (ccline.cmdbuff[j] == '.' - && ccline.cmdbuff[j - 1] != '\\') { - if (found) { - i = j + 1; - break; - } else { - found = true; - } - } - } - if (i > 0) { - cmdline_del(i); - } - s->c = (int)p_wc; - KeyTyped = true; // in case the key was mapped - s->xpc.xp_context = EXPAND_NOTHING; - } - } - if ((s->xpc.xp_context == EXPAND_FILES - || s->xpc.xp_context == EXPAND_DIRECTORIES - || s->xpc.xp_context == EXPAND_SHELLCMD) && p_wmnu) { - char_u upseg[5]; - - upseg[0] = PATHSEP; - upseg[1] = '.'; - upseg[2] = '.'; - upseg[3] = PATHSEP; - upseg[4] = NUL; - - if (s->c == K_DOWN - && ccline.cmdpos > 0 - && ccline.cmdbuff[ccline.cmdpos - 1] == PATHSEP - && (ccline.cmdpos < 3 - || ccline.cmdbuff[ccline.cmdpos - 2] != '.' - || ccline.cmdbuff[ccline.cmdpos - 3] != '.')) { - // go down a directory - s->c = (int)p_wc; - KeyTyped = true; // in case the key was mapped - } else if (STRNCMP(s->xpc.xp_pattern, upseg + 1, 3) == 0 - && s->c == K_DOWN) { - // If in a direct ancestor, strip off one ../ to go down - int found = false; - - int j = ccline.cmdpos; - int i = (int)((char_u *)s->xpc.xp_pattern - ccline.cmdbuff); - while (--j > i) { - j -= utf_head_off(ccline.cmdbuff, ccline.cmdbuff + j); - if (vim_ispathsep(ccline.cmdbuff[j])) { - found = true; - break; - } - } - if (found - && ccline.cmdbuff[j - 1] == '.' - && ccline.cmdbuff[j - 2] == '.' - && (vim_ispathsep(ccline.cmdbuff[j - 3]) || j == i + 2)) { - cmdline_del(j - 2); - s->c = (int)p_wc; - KeyTyped = true; // in case the key was mapped - } - } else if (s->c == K_UP) { - // go up a directory - int found = false; - - int j = ccline.cmdpos - 1; - int i = (int)((char_u *)s->xpc.xp_pattern - ccline.cmdbuff); - while (--j > i) { - j -= utf_head_off(ccline.cmdbuff, ccline.cmdbuff + j); - if (vim_ispathsep(ccline.cmdbuff[j]) -#ifdef BACKSLASH_IN_FILENAME - && vim_strchr((const char_u *)" *?[{`$%#", ccline.cmdbuff[j + 1]) - == NULL -#endif - ) { - if (found) { - i = j + 1; - break; - } else { - found = true; - } - } - } - - if (!found) { - j = i; - } else if (STRNCMP(ccline.cmdbuff + j, upseg, 4) == 0) { - j += 4; - } else if (STRNCMP(ccline.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(j); - put_on_cmdline(upseg + 1, 3, false); - } else if (ccline.cmdpos > i) { - cmdline_del(i); - } - - // Now complete in the new directory. Set KeyTyped in case the - // Up key came from a mapping. - s->c = (int)p_wc; - KeyTyped = true; - } + 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. @@ -1530,7 +1262,7 @@ static int command_line_execute(VimState *state, int key) } if (s->wim_index < 3) { - ++s->wim_index; + s->wim_index++; } if (s->c == ESC) { @@ -1547,8 +1279,11 @@ static int command_line_execute(VimState *state, int key) // <S-Tab> goes to last match, in a clumsy way if (s->c == K_S_TAB && KeyTyped) { if (nextwild(&s->xpc, WILD_EXPAND_KEEP, 0, s->firstc != '@') == OK) { - showmatches(&s->xpc, p_wmnu - && ((wim_flags[s->wim_index] & WIM_LIST) == 0)); + if (s->xpc.xp_numfiles > 1 + && ((!s->did_wild_list && (wim_flags[s->wim_index] & WIM_LIST)) || p_wmnu)) { + // Trigger the popup menu when wildoptions=pum + showmatches(&s->xpc, p_wmnu && ((wim_flags[s->wim_index] & WIM_LIST) == 0)); + } nextwild(&s->xpc, WILD_PREV, 0, s->firstc != '@'); nextwild(&s->xpc, WILD_PREV, 0, s->firstc != '@'); return command_line_changed(s); @@ -1664,8 +1399,8 @@ static int may_do_command_line_next_incsearch(int firstc, long count, incsearch_ update_topline(curwin); validate_cursor(); highlight_match = true; - save_viewstate(&s->old_viewstate); - update_screen(NOT_VALID); + save_viewstate(curwin, &s->old_viewstate); + update_screen(UPD_NOT_VALID); highlight_match = false; redrawcmdline(); curwin->w_cursor = s->match_end; @@ -1682,12 +1417,12 @@ static void command_line_next_histidx(CommandLineState *s, bool next_match) for (;;) { // one step backwards if (!next_match) { - if (s->hiscnt == hislen) { + if (s->hiscnt == get_hislen()) { // first time - s->hiscnt = hisidx[s->histype]; - } else if (s->hiscnt == 0 && hisidx[s->histype] != hislen - 1) { - s->hiscnt = hislen - 1; - } else if (s->hiscnt != hisidx[s->histype] + 1) { + s->hiscnt = *get_hisidx(s->histype); + } else if (s->hiscnt == 0 && *get_hisidx(s->histype) != get_hislen() - 1) { + s->hiscnt = get_hislen() - 1; + } else if (s->hiscnt != *get_hisidx(s->histype) + 1) { s->hiscnt--; } else { // at top of list @@ -1696,17 +1431,17 @@ static void command_line_next_histidx(CommandLineState *s, bool next_match) } } else { // one step forwards // on last entry, clear the line - if (s->hiscnt == hisidx[s->histype]) { - s->hiscnt = hislen; + if (s->hiscnt == *get_hisidx(s->histype)) { + s->hiscnt = get_hislen(); break; } // not on a history line, nothing to do - if (s->hiscnt == hislen) { + if (s->hiscnt == get_hislen()) { break; } - if (s->hiscnt == hislen - 1) { + if (s->hiscnt == get_hislen() - 1) { // wrap around s->hiscnt = 0; } else { @@ -1714,14 +1449,14 @@ static void command_line_next_histidx(CommandLineState *s, bool next_match) } } - if (s->hiscnt < 0 || history[s->histype][s->hiscnt].hisstr == NULL) { + if (s->hiscnt < 0 || get_histentry(s->histype)[s->hiscnt].hisstr == NULL) { s->hiscnt = s->save_hiscnt; break; } if ((s->c != K_UP && s->c != K_DOWN) || s->hiscnt == s->save_hiscnt - || STRNCMP(history[s->histype][s->hiscnt].hisstr, + || STRNCMP(get_histentry(s->histype)[s->hiscnt].hisstr, s->lookfor, (size_t)j) == 0) { break; } @@ -1744,7 +1479,7 @@ static int command_line_handle_key(CommandLineState *s) // 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; + ccline.cmdpos++; } if (s->c == K_DEL) { @@ -2068,9 +1803,17 @@ static int command_line_handle_key(CommandLineState *s) return command_line_not_changed(s); case Ctrl_A: // all matches + if (cmdline_pum_active()) { + // As Ctrl-A completes all the matches, close the popup + // menu (if present) + cmdline_pum_cleanup(&ccline); + } + if (nextwild(&s->xpc, WILD_ALL, 0, s->firstc != '@') == FAIL) { break; } + s->xpc.xp_context = EXPAND_NOTHING; + s->did_wild_list = false; return command_line_changed(s); case Ctrl_L: @@ -2103,7 +1846,7 @@ static int command_line_handle_key(CommandLineState *s) case K_KPAGEUP: case K_PAGEDOWN: case K_KPAGEDOWN: - if (s->histype == HIST_INVALID || hislen == 0 || s->firstc == NUL) { + if (s->histype == HIST_INVALID || get_hislen() == 0 || s->firstc == NUL) { // no history return command_line_not_changed(s); } @@ -2128,10 +1871,10 @@ static int command_line_handle_key(CommandLineState *s) XFREE_CLEAR(ccline.cmdbuff); s->xpc.xp_context = EXPAND_NOTHING; - if (s->hiscnt == hislen) { + if (s->hiscnt == get_hislen()) { p = s->lookfor; // back to the old one } else { - p = history[s->histype][s->hiscnt].hisstr; + p = get_histentry(s->histype)[s->hiscnt].hisstr; } if (s->histype == HIST_SEARCH @@ -2159,14 +1902,14 @@ static int command_line_handle_key(CommandLineState *s) if (i > 0) { ccline.cmdbuff[len] = '\\'; } - ++len; + len++; } if (i > 0) { ccline.cmdbuff[len] = p[j]; } } - ++len; + len++; } if (i == 0) { @@ -2396,6 +2139,126 @@ static void cmdpreview_close_win(void) } } +/// Save current state and prepare windows and buffers for command preview. +static void cmdpreview_prepare(CpInfo *cpinfo) +{ + kv_init(cpinfo->buf_info); + kv_init(cpinfo->win_info); + + FOR_ALL_WINDOWS_IN_TAB(win, curtab) { + buf_T *buf = win->w_buffer; + + // Don't save state of command preview buffer or preview window. + if (buf->handle == cmdpreview_bufnr) { + continue; + } + + CpBufInfo cp_bufinfo; + cp_bufinfo.buf = buf; + + cp_bufinfo.save_b_u_time_cur = buf->b_u_time_cur; + cp_bufinfo.save_b_u_seq_cur = buf->b_u_seq_cur; + cp_bufinfo.save_b_u_newhead = buf->b_u_newhead; + cp_bufinfo.save_b_p_ul = buf->b_p_ul; + cp_bufinfo.save_b_changed = buf->b_changed; + cp_bufinfo.save_changedtick = buf_get_changedtick(buf); + + kv_push(cpinfo->buf_info, cp_bufinfo); + + buf->b_p_ul = LONG_MAX; // Make sure we can undo all changes + + CpWinInfo cp_wininfo; + cp_wininfo.win = win; + + // Save window cursor position and viewstate + cp_wininfo.save_w_cursor = win->w_cursor; + save_viewstate(win, &cp_wininfo.save_viewstate); + + // Save 'cursorline' and 'cursorcolumn' + cp_wininfo.save_w_p_cul = win->w_p_cul; + cp_wininfo.save_w_p_cuc = win->w_p_cuc; + + kv_push(cpinfo->win_info, cp_wininfo); + + win->w_p_cul = false; // Disable 'cursorline' so it doesn't mess up the highlights + win->w_p_cuc = false; // Disable 'cursorcolumn' so it doesn't mess up the highlights + } + + cpinfo->save_hls = p_hls; + cpinfo->save_cmdmod = cmdmod; + win_size_save(&cpinfo->save_view); + save_search_patterns(); + + p_hls = false; // Don't show search highlighting during live substitution + cmdmod.cmod_split = 0; // Disable :leftabove/botright modifiers + cmdmod.cmod_tab = 0; // Disable :tab modifier + cmdmod.cmod_flags |= CMOD_NOSWAPFILE; // Disable swap for preview buffer +} + +// Restore the state of buffers and windows before command preview. +static void cmdpreview_restore_state(CpInfo *cpinfo) +{ + for (size_t i = 0; i < cpinfo->buf_info.size; i++) { + CpBufInfo cp_bufinfo = cpinfo->buf_info.items[i]; + buf_T *buf = cp_bufinfo.buf; + + buf->b_changed = cp_bufinfo.save_b_changed; + + if (buf->b_u_seq_cur != cp_bufinfo.save_b_u_seq_cur) { + int count = 0; + + // Calculate how many undo steps are necessary to restore earlier state. + for (u_header_T *uhp = buf->b_u_curhead ? buf->b_u_curhead : buf->b_u_newhead; + uhp != NULL && uhp->uh_seq > cp_bufinfo.save_b_u_seq_cur; + uhp = uhp->uh_next.ptr, ++count) {} + + aco_save_T aco; + aucmd_prepbuf(&aco, buf); + // Undo invisibly. This also moves the cursor! + if (!u_undo_and_forget(count)) { + abort(); + } + aucmd_restbuf(&aco); + + // Restore newhead. It is meaningless when curhead is valid, but we must + // restore it so that undotree() is identical before/after the preview. + buf->b_u_newhead = cp_bufinfo.save_b_u_newhead; + buf->b_u_time_cur = cp_bufinfo.save_b_u_time_cur; + } + if (cp_bufinfo.save_changedtick != buf_get_changedtick(buf)) { + buf_set_changedtick(buf, cp_bufinfo.save_changedtick); + } + + buf->b_p_ul = cp_bufinfo.save_b_p_ul; // Restore 'undolevels' + + // Clear preview highlights. + extmark_clear(buf, (uint32_t)cmdpreview_ns, 0, 0, MAXLNUM, MAXCOL); + } + for (size_t i = 0; i < cpinfo->win_info.size; i++) { + CpWinInfo cp_wininfo = cpinfo->win_info.items[i]; + win_T *win = cp_wininfo.win; + + // Restore window cursor position and viewstate + win->w_cursor = cp_wininfo.save_w_cursor; + restore_viewstate(win, &cp_wininfo.save_viewstate); + + // Restore 'cursorline' and 'cursorcolumn' + win->w_p_cul = cp_wininfo.save_w_p_cul; + win->w_p_cuc = cp_wininfo.save_w_p_cuc; + + update_topline(win); + } + + cmdmod = cpinfo->save_cmdmod; // Restore cmdmod + p_hls = cpinfo->save_hls; // Restore 'hlsearch' + restore_search_patterns(); // Restore search patterns + win_size_restore(&cpinfo->save_view); // Restore window sizes + + ga_clear(&cpinfo->save_view); + kv_destroy(cpinfo->win_info); + kv_destroy(cpinfo->buf_info); +} + /// Show 'inccommand' preview if command is previewable. It works like this: /// 1. Store current undo information so we can revert to current state later. /// 2. Execute the preview callback with the parsed command, preview buffer number and preview @@ -2440,35 +2303,18 @@ static bool cmdpreview_may_show(CommandLineState *s) ea.line2 = lnum; } - time_t save_b_u_time_cur = curbuf->b_u_time_cur; - long save_b_u_seq_cur = curbuf->b_u_seq_cur; - u_header_T *save_b_u_newhead = curbuf->b_u_newhead; - long save_b_p_ul = curbuf->b_p_ul; - int save_b_changed = curbuf->b_changed; - int save_w_p_cul = curwin->w_p_cul; - int save_w_p_cuc = curwin->w_p_cuc; - bool save_hls = p_hls; - varnumber_T save_changedtick = buf_get_changedtick(curbuf); + CpInfo cpinfo; bool icm_split = *p_icm == 's'; // inccommand=split buf_T *cmdpreview_buf; win_T *cmdpreview_win; - cmdmod_T save_cmdmod = cmdmod; - cmdpreview = true; emsg_silent++; // Block error reporting as the command may be incomplete, // but still update v:errmsg msg_silent++; // Block messages, namely ones that prompt block_autocmds(); // Block events - garray_T save_view; - win_size_save(&save_view); // Save current window sizes - save_search_patterns(); // Save search patterns - curbuf->b_p_ul = LONG_MAX; // Make sure we can undo all changes - curwin->w_p_cul = false; // Disable 'cursorline' so it doesn't mess up the highlights - curwin->w_p_cuc = false; // Disable 'cursorcolumn' so it doesn't mess up the highlights - p_hls = false; // Don't show search highlighting during live substitution - cmdmod.cmod_split = 0; // Disable :leftabove/botright modifiers - cmdmod.cmod_tab = 0; // Disable :tab modifier - cmdmod.cmod_flags |= CMOD_NOSWAPFILE; // Disable swap for preview buffer + + // Save current state and prepare for command preview. + cmdpreview_prepare(&cpinfo); // Open preview buffer if inccommand=split. if (!icm_split) { @@ -2476,12 +2322,14 @@ static bool cmdpreview_may_show(CommandLineState *s) } else if ((cmdpreview_buf = cmdpreview_open_buf()) == NULL) { abort(); } - // Setup preview namespace if it's not already set. if (!cmdpreview_ns) { cmdpreview_ns = (int)nvim_create_namespace((String)STRING_INIT); } + // Set cmdpreview state. + cmdpreview = true; + // Execute the preview callback and use its return value to determine whether to show preview or // open the preview window. The preview callback also handles doing the changes and highlights for // the preview. @@ -2500,11 +2348,11 @@ static bool cmdpreview_may_show(CommandLineState *s) cmdpreview_type = 1; } - // If preview callback is nonzero, update screen now. + // If preview callback return value is nonzero, update screen now. if (cmdpreview_type != 0) { int save_rd = RedrawingDisabled; RedrawingDisabled = 0; - update_screen(SOME_VALID); + update_screen(UPD_SOME_VALID); RedrawingDisabled = save_rd; } @@ -2512,53 +2360,22 @@ static bool cmdpreview_may_show(CommandLineState *s) if (icm_split && cmdpreview_type == 2 && cmdpreview_win != NULL) { cmdpreview_close_win(); } - // Clear preview highlights. - extmark_clear(curbuf, (uint32_t)cmdpreview_ns, 0, 0, MAXLNUM, MAXCOL); - - curbuf->b_changed = save_b_changed; // Preserve 'modified' during preview - if (curbuf->b_u_seq_cur != save_b_u_seq_cur) { - // Undo invisibly. This also moves the cursor! - while (curbuf->b_u_seq_cur != save_b_u_seq_cur) { - if (!u_undo_and_forget(1)) { - abort(); - } - } - // Restore newhead. It is meaningless when curhead is valid, but we must - // restore it so that undotree() is identical before/after the preview. - curbuf->b_u_newhead = save_b_u_newhead; - curbuf->b_u_time_cur = save_b_u_time_cur; - } - if (save_changedtick != buf_get_changedtick(curbuf)) { - buf_set_changedtick(curbuf, save_changedtick); - } + // Restore state. + cmdpreview_restore_state(&cpinfo); - cmdmod = save_cmdmod; // Restore cmdmod - p_hls = save_hls; // Restore 'hlsearch' - curwin->w_p_cul = save_w_p_cul; // Restore 'cursorline' - curwin->w_p_cuc = save_w_p_cuc; // Restore 'cursorcolumn' - curbuf->b_p_ul = save_b_p_ul; // Restore 'undolevels' - restore_search_patterns(); // Restore search patterns - win_size_restore(&save_view); // Restore window sizes - ga_clear(&save_view); unblock_autocmds(); // Unblock events msg_silent--; // Unblock messages emsg_silent--; // Unblock error reporting - - // Restore the window "view". - curwin->w_cursor = s->is_state.save_cursor; - restore_viewstate(&s->is_state.old_viewstate); - update_topline(curwin); - redrawcmdline(); end: xfree(cmdline); return cmdpreview_type != 0; } -static int command_line_changed(CommandLineState *s) +/// Trigger CmdlineChanged autocommands. +static void do_autocmd_cmdlinechanged(int firstc) { - // Trigger CmdlineChanged autocommands. if (has_event(EVENT_CMDLINECHANGED)) { TryState tstate; Error err = ERROR_INIT; @@ -2566,7 +2383,7 @@ static int command_line_changed(CommandLineState *s) dict_T *dict = get_v_event(&save_v_event); char firstcbuf[2]; - firstcbuf[0] = (char)(s->firstc > 0 ? s->firstc : '-'); + firstcbuf[0] = (char)firstc; firstcbuf[1] = 0; // set v:event to a dictionary with information about the commandline @@ -2586,6 +2403,12 @@ static int command_line_changed(CommandLineState *s) redrawcmd(); } } +} + +static int command_line_changed(CommandLineState *s) +{ + // Trigger CmdlineChanged autocommands. + do_autocmd_cmdlinechanged(s->firstc > 0 ? s->firstc : '-'); if (s->firstc == ':' && current_sctx.sc_sid == 0 // only if interactive @@ -2597,7 +2420,7 @@ static int command_line_changed(CommandLineState *s) // 'inccommand' preview has been shown. } else if (cmdpreview) { cmdpreview = false; - update_screen(SOME_VALID); // Clear 'inccommand' preview. + update_screen(UPD_SOME_VALID); // Clear 'inccommand' preview. } else { if (s->xpc.xp_context == EXPAND_NOTHING && (KeyTyped || vpeekc() == NUL)) { may_do_incsearch_highlighting(s->firstc, s->count, &s->is_state); @@ -2682,7 +2505,7 @@ char *getcmdline_prompt(const int firstc, const char *const prompt, const int at save_cmdline(&save_ccline); did_save_ccline = true; } else { - memset(&ccline, 0, sizeof(struct cmdline_info)); + CLEAR_FIELD(ccline); } ccline.prompt_id = last_prompt_id++; ccline.cmdprompt = (char_u *)prompt; @@ -2718,6 +2541,58 @@ char_u *get_cmdprompt(void) return ccline.cmdprompt; } +/// Read the 'wildmode' option, fill wim_flags[]. +int check_opt_wim(void) +{ + char_u new_wim_flags[4]; + int i; + int idx = 0; + + for (i = 0; i < 4; i++) { + new_wim_flags[i] = 0; + } + + for (char *p = p_wim; *p; p++) { + for (i = 0; ASCII_ISALPHA(p[i]); i++) {} + if (p[i] != NUL && p[i] != ',' && p[i] != ':') { + return FAIL; + } + if (i == 7 && STRNCMP(p, "longest", 7) == 0) { + new_wim_flags[idx] |= WIM_LONGEST; + } else if (i == 4 && STRNCMP(p, "full", 4) == 0) { + new_wim_flags[idx] |= WIM_FULL; + } else if (i == 4 && STRNCMP(p, "list", 4) == 0) { + new_wim_flags[idx] |= WIM_LIST; + } else if (i == 8 && STRNCMP(p, "lastused", 8) == 0) { + new_wim_flags[idx] |= WIM_BUFLASTUSED; + } else { + return FAIL; + } + p += i; + if (*p == NUL) { + break; + } + if (*p == ',') { + if (idx == 3) { + return FAIL; + } + idx++; + } + } + + // fill remaining entries with last flag + while (idx < 3) { + new_wim_flags[idx + 1] = new_wim_flags[idx]; + idx++; + } + + // only when there are no errors, wim_flags[] is changed + for (i = 0; i < 4; i++) { + wim_flags[i] = new_wim_flags[i]; + } + return OK; +} + /// Return true when the text must not be changed and we can't switch to /// another window or buffer. True when editing the command line etc. bool text_locked(void) @@ -2795,7 +2670,7 @@ static int cmd_startcol(void) } /// Compute the column position for a byte position on the command line. -static int cmd_screencol(int bytepos) +int cmd_screencol(int bytepos) { int m; // maximum column @@ -2882,10 +2757,8 @@ static void alloc_cmdbuff(int len) ccline.cmdbufflen = len; } -/* - * Re-allocate the command line to length len + something extra. - */ -static void realloc_cmdbuff(int len) +/// Re-allocate the command line to length len + something extra. +void realloc_cmdbuff(int len) { if (len < ccline.cmdbufflen) { return; // no need to resize @@ -3217,7 +3090,7 @@ color_cmdline_error: /* * Draw part of the cmdline at the current cursor position. But draw stars - * when cmdline_star is TRUE. + * when cmdline_star is true. */ static void draw_cmdline(int start, int len) { @@ -3325,7 +3198,7 @@ static void draw_cmdline(int start, int len) } } - msg_outtrans_len((char_u *)arshape_buf, newlen); + msg_outtrans_len(arshape_buf, newlen); } else { draw_cmdline_no_arabicshape: if (kv_size(ccline.last_colors.colors)) { @@ -3340,7 +3213,7 @@ draw_cmdline_no_arabicshape: chunk.attr); } } else { - msg_outtrans_len(ccline.cmdbuff + start, len); + msg_outtrans_len((char *)ccline.cmdbuff + start, len); } } } @@ -3348,7 +3221,6 @@ draw_cmdline_no_arabicshape: static void ui_ext_cmdline_show(CmdlineInfo *line) { Arena arena = ARENA_EMPTY; - arena_start(&arena, &ui_ext_fixblk); Array content; if (cmdline_star) { content = arena_array(&arena, 1); @@ -3393,7 +3265,7 @@ static void ui_ext_cmdline_show(CmdlineInfo *line) line->special_shift, line->level); } - arena_mem_free(arena_finish(&arena), &ui_ext_fixblk); + arena_mem_free(arena_finish(&arena)); } void ui_ext_cmdline_block_append(size_t indent, const char *line) @@ -3472,7 +3344,7 @@ void cmdline_ui_flush(void) /* * Put a character on the command line. Shifts the following text to the - * right when "shift" is TRUE. Used for CTRL-V, CTRL-K, etc. + * right when "shift" is true. Used for CTRL-V, CTRL-K, etc. * "c" must be printable (fit in one display cell)! */ void putcmdline(char c, int shift) @@ -3498,7 +3370,7 @@ void putcmdline(char c, int shift) ui_cursor_shape(); } -/// Undo a putcmdline(c, FALSE). +/// Undo a putcmdline(c, false). void unputcmdline(void) { if (cmd_silent) { @@ -3519,9 +3391,9 @@ void unputcmdline(void) /* * Put the given string, of the given length, onto the command line. * If len is -1, then STRLEN() is used to calculate the length. - * If 'redraw' is TRUE then the new part of the command line, and the remaining + * If 'redraw' is true then the new part of the command line, and the remaining * 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 + * 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) @@ -3592,13 +3464,13 @@ void put_on_cmdline(char_u *str, int len, int redraw) msg_col -= i; if (msg_col < 0) { msg_col += Columns; - --msg_row; + msg_row--; } } } if (redraw && !cmd_silent) { - msg_no_more = TRUE; + msg_no_more = true; i = cmdline_row; cursorcmd(); draw_cmdline(ccline.cmdpos, ccline.cmdlen - ccline.cmdpos); @@ -3606,7 +3478,7 @@ void put_on_cmdline(char_u *str, int len, int redraw) if (cmdline_row != i || ccline.overstrike) { msg_clr_eos(); } - msg_no_more = FALSE; + msg_no_more = false; } if (KeyTyped) { m = Columns * Rows; @@ -3642,16 +3514,16 @@ void put_on_cmdline(char_u *str, int len, int redraw) /// Save ccline, because obtaining the "=" register may execute "normal :cmd" /// and overwrite it. -static void save_cmdline(struct cmdline_info *ccp) +static void save_cmdline(CmdlineInfo *ccp) { *ccp = ccline; - memset(&ccline, 0, sizeof(struct cmdline_info)); + CLEAR_FIELD(ccline); ccline.prev_ccline = ccp; ccline.cmdbuff = NULL; // signal that ccline is not in use } /// Restore ccline after it has been saved with save_cmdline(). -static void restore_cmdline(struct cmdline_info *ccp) +static void restore_cmdline(CmdlineInfo *ccp) FUNC_ATTR_NONNULL_ALL { ccline = *ccp; @@ -3669,7 +3541,7 @@ static void restore_cmdline(struct cmdline_info *ccp) /// @returns FAIL for failure, OK otherwise static bool cmdline_paste(int regname, bool literally, bool remcr) { - char_u *arg; + char *arg; char_u *p; bool allocated; @@ -3702,7 +3574,7 @@ 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 = arg; + p = (char_u *)arg; if (p_is && regname == Ctrl_W) { char_u *w; int len; @@ -3733,8 +3605,8 @@ static bool cmdline_paste(int regname, bool literally, bool remcr) /* * Put a string on the command line. - * When "literally" is TRUE, insert literally. - * When "literally" is FALSE, insert as typed, but don't leave the command + * 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) @@ -3742,7 +3614,7 @@ void cmdline_paste_str(char_u *s, int literally) int c, cv; if (literally) { - put_on_cmdline(s, -1, TRUE); + put_on_cmdline(s, -1, true); } else { while (*s != NUL) { cv = *s; @@ -3760,16 +3632,6 @@ void cmdline_paste_str(char_u *s, int literally) } } -/// Delete characters on the command line, from "from" to the current position. -static void cmdline_del(int from) -{ - assert(ccline.cmdpos <= ccline.cmdlen); - memmove(ccline.cmdbuff + from, ccline.cmdbuff + ccline.cmdpos, - (size_t)ccline.cmdlen - (size_t)ccline.cmdpos + 1); - ccline.cmdlen -= ccline.cmdpos - from; - ccline.cmdpos = from; -} - // This function is called when the screen size changes and with incremental // search and in other situations where the command line may have been // overwritten. @@ -3841,7 +3703,7 @@ void redrawcmd(void) redrawcmdprompt(); // Don't use more prompt, truncate the cmdline if it doesn't fit. - msg_no_more = TRUE; + msg_no_more = true; draw_cmdline(0, ccline.cmdlen); msg_clr_eos(); msg_no_more = false; @@ -3856,7 +3718,7 @@ void redrawcmd(void) * An emsg() before may have set msg_scroll. This is used in normal mode, * in cmdline mode we can reset them now. */ - msg_scroll = FALSE; // next message overwrites cmdline + msg_scroll = false; // next message overwrites cmdline // Typing ':' at the more prompt may set skip_redraw. We don't want this // in cmdline mode. @@ -3877,7 +3739,7 @@ void compute_cmdrow(void) lines_left = cmdline_row; } -static void cursorcmd(void) +void cursorcmd(void) { if (cmd_silent) { return; @@ -3964,462 +3826,6 @@ static int ccheck_abbr(int c) return check_abbr(c, ccline.cmdbuff, ccline.cmdpos, spos); } -static int sort_func_compare(const void *s1, const void *s2) -{ - char_u *p1 = *(char_u **)s1; - char_u *p2 = *(char_u **)s2; - - if (*p1 != '<' && *p2 == '<') { - return -1; - } - if (*p1 == '<' && *p2 != '<') { - return 1; - } - return STRCMP(p1, p2); -} - -/// 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 -/// normal character (instead of being expanded). This allows :s/^I^D etc. -/// -/// @param options extra options for ExpandOne() -/// @param escape if TRUE, escape the returned matches -static int nextwild(expand_T *xp, int type, int options, int escape) -{ - int i, j; - char_u *p1; - char_u *p2; - int difflen; - - if (xp->xp_numfiles == -1) { - set_expand_context(xp); - cmd_showtail = expand_showtail(xp); - } - - if (xp->xp_context == EXPAND_UNSUCCESSFUL) { - beep_flush(); - return OK; // Something illegal on command line - } - if (xp->xp_context == EXPAND_NOTHING) { - // Caller can use the character as a normal char instead - return FAIL; - } - - if (!(ui_has(kUICmdline) || ui_has(kUIWildmenu))) { - msg_puts("..."); // show that we are busy - ui_flush(); - } - - i = (int)((char_u *)xp->xp_pattern - ccline.cmdbuff); - assert(ccline.cmdpos >= i); - xp->xp_pattern_len = (size_t)ccline.cmdpos - (size_t)i; - - if (type == WILD_NEXT || type == WILD_PREV) { - // Get next/previous match for a previous expanded pattern. - p2 = ExpandOne(xp, NULL, NULL, 0, type); - } else { - // Translate string into pattern and expand it. - p1 = addstar((char_u *)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 = ExpandOne(xp, p1, vim_strnsave(&ccline.cmdbuff[i], xp->xp_pattern_len), - use_options, type); - xfree(p1); - - // xp->xp_pattern might have been modified by ExpandOne (for example, - // in lua completion), so recompute the pattern index and length - i = (int)((char_u *)xp->xp_pattern - ccline.cmdbuff); - xp->xp_pattern_len = (size_t)ccline.cmdpos - (size_t)i; - - // Longest match: make sure it is not shorter, happens with :help. - if (p2 != NULL && type == WILD_LONGEST) { - for (j = 0; (size_t)j < xp->xp_pattern_len; j++) { - if (ccline.cmdbuff[i + j] == '*' - || ccline.cmdbuff[i + j] == '?') { - break; - } - } - if ((int)STRLEN(p2) < j) { - XFREE_CLEAR(p2); - } - } - } - - if (p2 != NULL && !got_int) { - 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 = (char *)ccline.cmdbuff + i; - } - assert(ccline.cmdpos <= ccline.cmdlen); - 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)); - ccline.cmdlen += difflen; - ccline.cmdpos += difflen; - } - xfree(p2); - - redrawcmd(); - cursorcmd(); - - /* When expanding a ":map" command and no matches are found, assume that - * the key is supposed to be inserted literally */ - if (xp->xp_context == EXPAND_MAPPINGS && p2 == NULL) { - return FAIL; - } - - if (xp->xp_numfiles <= 0 && p2 == NULL) { - beep_flush(); - } else if (xp->xp_numfiles == 1) { - // free expanded pattern - (void)ExpandOne(xp, NULL, NULL, 0, WILD_FREE); - } - - return OK; -} - -/// 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. -/// -/// "orig" is the originally expanded string, copied to allocated memory. It -/// should either be kept in orig_save or freed. When "mode" is WILD_NEXT or -/// WILD_PREV "orig" should be NULL. -/// -/// Results are cached in xp->xp_files and xp->xp_numfiles, except when "mode" -/// is WILD_EXPAND_FREE or WILD_ALL. -/// -/// mode = WILD_FREE: just free previously expanded matches -/// mode = WILD_EXPAND_FREE: normal expansion, do not keep matches -/// mode = WILD_EXPAND_KEEP: normal expansion, keep matches -/// mode = WILD_NEXT: use next match in multiple match, wrap to first -/// mode = WILD_PREV: use previous match in multiple match, wrap to first -/// mode = WILD_ALL: return all matches concatenated -/// mode = WILD_LONGEST: return longest matched part -/// mode = WILD_ALL_KEEP: get all matches, keep matches -/// -/// options = WILD_LIST_NOTFOUND: list entries without a match -/// options = WILD_HOME_REPLACE: do home_replace() for buffer names -/// options = WILD_USE_NL: Use '\n' for WILD_ALL -/// options = WILD_NO_BEEP: Don't beep for multiple matches -/// options = WILD_ADD_SLASH: add a slash after directory names -/// options = WILD_KEEP_ALL: don't remove 'wildignore' entries -/// options = WILD_SILENT: don't print warning messages -/// options = WILD_ESCAPE: put backslash before special chars -/// options = WILD_ICASE: ignore case for files -/// -/// The variables xp->xp_context and xp->xp_backslash must have been set! -/// -/// @param orig allocated copy of original of expanded string -char_u *ExpandOne(expand_T *xp, char_u *str, char_u *orig, int options, int mode) -{ - char_u *ss = NULL; - static int findex; - static char_u *orig_save = NULL; // kept value of orig - int orig_saved = FALSE; - int i; - int non_suf_match; // number without matching suffix - - /* - * first handle the case of using an old match - */ - if (mode == WILD_NEXT || mode == WILD_PREV) { - if (xp->xp_numfiles > 0) { - if (mode == WILD_PREV) { - if (findex == -1) { - findex = xp->xp_numfiles; - } - --findex; - } else { // mode == WILD_NEXT - ++findex; - } - - /* - * When wrapping around, return the original string, set findex to - * -1. - */ - if (findex < 0) { - if (orig_save == NULL) { - findex = xp->xp_numfiles - 1; - } else { - findex = -1; - } - } - if (findex >= xp->xp_numfiles) { - if (orig_save == NULL) { - findex = 0; - } else { - findex = -1; - } - } - if (compl_match_array) { - compl_selected = findex; - cmdline_pum_display(false); - } else if (p_wmnu) { - win_redr_status_matches(xp, xp->xp_numfiles, xp->xp_files, findex, cmd_showtail); - } - if (findex == -1) { - return vim_strsave(orig_save); - } - return vim_strsave((char_u *)xp->xp_files[findex]); - } else { - return NULL; - } - } - - if (mode == WILD_CANCEL) { - ss = vim_strsave(orig_save ? orig_save : (char_u *)""); - } else if (mode == WILD_APPLY) { - ss = vim_strsave(findex == -1 ? (orig_save ? orig_save : (char_u *)"") : - (char_u *)xp->xp_files[findex]); - } - - // free old names - if (xp->xp_numfiles != -1 && mode != WILD_ALL && mode != WILD_LONGEST) { - FreeWild(xp->xp_numfiles, xp->xp_files); - xp->xp_numfiles = -1; - XFREE_CLEAR(orig_save); - } - findex = 0; - - if (mode == WILD_FREE) { // only release file name - return NULL; - } - - if (xp->xp_numfiles == -1 && mode != WILD_APPLY && mode != WILD_CANCEL) { - xfree(orig_save); - orig_save = orig; - orig_saved = TRUE; - - /* - * Do the expansion. - */ - if (ExpandFromContext(xp, str, &xp->xp_numfiles, &xp->xp_files, 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, - * causing the pattern to be added, which has illegal characters. - */ - if (!(options & WILD_SILENT) && (options & WILD_LIST_NOTFOUND)) { - semsg(_(e_nomatch2), str); - } -#endif - } else if (xp->xp_numfiles == 0) { - if (!(options & WILD_SILENT)) { - semsg(_(e_nomatch2), str); - } - } else { - // Escape the matches for use on the command line. - 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 (xp->xp_numfiles) { - non_suf_match = xp->xp_numfiles; - } else { - non_suf_match = 1; - } - if ((xp->xp_context == EXPAND_FILES - || xp->xp_context == EXPAND_DIRECTORIES) - && xp->xp_numfiles > 1) { - /* - * More than one match; check suffix. - * The files will have been sorted on matching suffix in - * expand_wildcards, only need to check the first two. - */ - non_suf_match = 0; - for (i = 0; i < 2; i++) { - if (match_suffix((char_u *)xp->xp_files[i])) { - non_suf_match++; - } - } - } - if (non_suf_match != 1) { - /* Can we ever get here unless it's while expanding - * interactively? If not, we can get rid of this all - * together. Don't really want to wait for this message - * (and possibly have to hit return to continue!). - */ - if (!(options & WILD_SILENT)) { - emsg(_(e_toomany)); - } else if (!(options & WILD_NO_BEEP)) { - beep_flush(); - } - } - if (!(non_suf_match != 1 && mode == WILD_EXPAND_FREE)) { - ss = vim_strsave((char_u *)xp->xp_files[0]); - } - } - } - } - - // Find longest common part - if (mode == WILD_LONGEST && xp->xp_numfiles > 0) { - size_t len = 0; - - for (size_t mb_len; xp->xp_files[0][len]; len += mb_len) { - mb_len = (size_t)utfc_ptr2len(&xp->xp_files[0][len]); - int c0 = utf_ptr2char(&xp->xp_files[0][len]); - for (i = 1; i < xp->xp_numfiles; i++) { - int ci = utf_ptr2char(&xp->xp_files[i][len]); - - if (p_fic && (xp->xp_context == EXPAND_DIRECTORIES - || xp->xp_context == EXPAND_FILES - || xp->xp_context == EXPAND_SHELLCMD - || xp->xp_context == EXPAND_BUFFERS)) { - if (mb_tolower(c0) != mb_tolower(ci)) { - break; - } - } else if (c0 != ci) { - break; - } - } - if (i < xp->xp_numfiles) { - if (!(options & WILD_NO_BEEP)) { - vim_beep(BO_WILD); - } - break; - } - } - - ss = (char_u *)xstrndup(xp->xp_files[0], len); - findex = -1; // next p_wc gets first one - } - - // Concatenate all matching names - // TODO(philix): use xstpcpy instead of strcat in a loop (ExpandOne) - if (mode == WILD_ALL && xp->xp_numfiles > 0) { - size_t len = 0; - for (i = 0; i < xp->xp_numfiles; ++i) { - len += STRLEN(xp->xp_files[i]) + 1; - } - ss = xmalloc(len); - *ss = NUL; - for (i = 0; i < xp->xp_numfiles; ++i) { - STRCAT(ss, xp->xp_files[i]); - if (i != xp->xp_numfiles - 1) { - STRCAT(ss, (options & WILD_USE_NL) ? "\n" : " "); - } - } - } - - if (mode == WILD_EXPAND_FREE || mode == WILD_ALL) { - ExpandCleanup(xp); - } - - // Free "orig" if it wasn't stored in "orig_save". - if (!orig_saved) { - xfree(orig); - } - - return ss; -} - -/* - * Prepare an expand structure for use. - */ -void ExpandInit(expand_T *xp) - FUNC_ATTR_NONNULL_ALL -{ - CLEAR_POINTER(xp); - xp->xp_backslash = XP_BS_NONE; - xp->xp_numfiles = -1; -} - -/* - * Cleanup an expand structure after use. - */ -void ExpandCleanup(expand_T *xp) -{ - if (xp->xp_numfiles >= 0) { - FreeWild(xp->xp_numfiles, xp->xp_files); - xp->xp_numfiles = -1; - } -} - -void ExpandEscape(expand_T *xp, char_u *str, int numfiles, char **files, int options) -{ - int i; - char_u *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 defined(BACKSLASH_IN_FILENAME) - p = vim_strsave_escaped(files[i], (char_u *)" "); - xfree(files[i]); - files[i] = p; -#endif - } -#ifdef BACKSLASH_IN_FILENAME - p = (char_u *)vim_strsave_fnameescape((const char *)files[i], vse_what); -#else - p = (char_u *)vim_strsave_fnameescape((const char *)files[i], - xp->xp_shell ? VSE_SHELL : vse_what); -#endif - xfree(files[i]); - files[i] = (char *)p; - - /* 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; - - /* 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; - } - } - } -} - /// Escape special characters in "fname", depending on "what": /// /// @param[in] fname File name to escape. @@ -4476,10 +3882,8 @@ char *vim_strsave_fnameescape(const char *const fname, const int what) return p; } -/* - * Put a backslash before the file name in "pp", which is in allocated memory. - */ -static void escape_fname(char **pp) +/// 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); p[0] = '\\'; @@ -4503,1761 +3907,152 @@ void tilde_replace(char_u *orig_pat, int num_files, char **files) } } -void cmdline_pum_display(bool changed_array) -{ - pum_display(compl_match_array, compl_match_arraysize, compl_selected, - changed_array, compl_startcol); -} - -/* - * 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. - */ -static int showmatches(expand_T *xp, int wildmenu) -{ -#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 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, ccline.cmdbuff, ccline.cmdpos, - &num_files, &files_found); - showtail = expand_showtail(xp); - if (i != EXPAND_OK) { - return i; - } - } else { - num_files = xp->xp_numfiles; - files_found = xp->xp_files; - showtail = cmd_showtail; - } - - bool compl_use_pum = (ui_has(kUICmdline) - ? ui_has(kUIPopupmenu) - : wildmenu && (wop_flags & WOP_PUM)) - || ui_has(kUIWildmenu); - - if (compl_use_pum) { - assert(num_files >= 0); - compl_match_arraysize = num_files; - compl_match_array = xcalloc((size_t)compl_match_arraysize, - sizeof(pumitem_T)); - for (i = 0; i < num_files; i++) { - compl_match_array[i].pum_text = (char_u *)L_SHOWFILE(i); - } - char_u *endpos = (char_u *)(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; - } - - if (!wildmenu) { - msg_didany = false; // lines_left will be set - msg_start(); // prepare for paging - msg_putchar('\n'); - ui_flush(); - cmdline_row = msg_row; - msg_didany = false; // lines_left will be set again - msg_start(); // prepare for paging - } - - if (got_int) { - got_int = false; // only int. the completion, not the cmd line - } else if (wildmenu) { - win_redr_status_matches(xp, num_files, files_found, -1, showtail); - } else { - // find the length of the longest file name - maxlen = 0; - for (i = 0; i < num_files; ++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); - } else { - j = vim_strsize(L_SHOWFILE(i)); - } - if (j > maxlen) { - maxlen = j; - } - } - - if (xp->xp_context == EXPAND_TAGS_LISTFILES) { - lines = num_files; - } else { - // compute the number of columns and lines for the listing - maxlen += 2; // two spaces between file names - columns = (Columns + 2) / maxlen; - if (columns < 1) { - columns = 1; - } - lines = (num_files + columns - 1) / columns; - } - - attr = HL_ATTR(HLF_D); // find out highlighting for directories - - if (xp->xp_context == EXPAND_TAGS_LISTFILES) { - msg_puts_attr(_("tagname"), HL_ATTR(HLF_T)); - msg_clr_eos(); - msg_advance(maxlen - 3); - msg_puts_attr(_(" kind file\n"), HL_ATTR(HLF_T)); - } - - // 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((char_u *)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(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 = backslash_halve_save(path); - j = os_isdir(halved_slash); - xfree(exp_path); - if (halved_slash != path) { - xfree(halved_slash); - } - } else { - // Expansion was done here, file names are literal. - j = os_isdir((char_u *)files_found[k]); - } - if (showtail) { - p = (char_u *)L_SHOWFILE(k); - } else { - home_replace(NULL, files_found[k], (char *)NameBuff, MAXPATHL, true); - p = NameBuff; - } - } else { - j = false; - p = (char_u *)L_SHOWFILE(k); - } - lastlen = msg_outtrans_attr(p, j ? attr : 0); - } - if (msg_col > 0) { // when not wrapped around - msg_clr_eos(); - msg_putchar('\n'); - } - ui_flush(); // show one line at a time - if (got_int) { - got_int = FALSE; - break; - } - } - - /* - * we redraw the command below the lines that we have just listed - * This is a bit tricky, but it saves a lot of screen updating. - */ - cmdline_row = msg_row; // will put it back later - } - - if (xp->xp_numfiles == -1) { - FreeWild(num_files, files_found); - } - - return EXPAND_OK; -} - -/// Private path_tail for showmatches() (and win_redr_status_matches()): -/// Find tail of file name path, but ignore trailing "/". -char *sm_gettail(char *s, bool eager) -{ - char_u *p; - char_u *t = (char_u *)s; - int had_sep = false; - - for (p = (char_u *)s; *p != NUL;) { - if (vim_ispathsep(*p) -#ifdef BACKSLASH_IN_FILENAME - && !rem_backslash(p) -#endif - ) { - if (eager) { - t = p + 1; - } else { - had_sep = true; - } - } else if (had_sep) { - t = p; - had_sep = FALSE; - } - MB_PTR_ADV(p); - } - return (char *)t; -} - -/* - * Return TRUE if we only need to show the tail of completion matches. - * When not completing file names or there is a wildcard in the path FALSE is - * returned. - */ -static int expand_showtail(expand_T *xp) +/// Get a pointer to the current command line info. +CmdlineInfo *get_cmdline_info(void) { - char_u *s; - char_u *end; - - // When not completing file names a "/" may mean something different. - if (xp->xp_context != EXPAND_FILES - && xp->xp_context != EXPAND_SHELLCMD - && xp->xp_context != EXPAND_DIRECTORIES) { - return FALSE; - } - - end = (char_u *)path_tail(xp->xp_pattern); - if (end == (char_u *)xp->xp_pattern) { // there is no path separator - return false; - } - - for (s = (char_u *)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(s)) { - s++; - } else if (vim_strchr("*?[", *s) != NULL) { - return false; - } - } - return TRUE; -} - -/// Prepare a string for expansion. -/// -/// When expanding file names: The string will be used with expand_wildcards(). -/// Copy "fname[len]" into allocated memory and add a '*' at the end. -/// When expanding other names: The string will be used with regcomp(). Copy -/// the name into allocated memory and prepend "^". -/// -/// @param context EXPAND_FILES etc. -char_u *addstar(char_u *fname, size_t len, int context) - FUNC_ATTR_NONNULL_RET -{ - char_u *retval; - size_t i, j; - size_t new_len; - char_u *tail; - int ends_in_star; - - if (context != EXPAND_FILES - && context != EXPAND_FILES_IN_PATH - && context != EXPAND_SHELLCMD - && context != EXPAND_DIRECTORIES) { - /* - * Matching will be done internally (on something other than files). - * So we convert the file-matching-type wildcards into our kind for - * use with vim_regcomp(). First work out how long it will be: - */ - - // For help tags the translation is done in find_help_tags(). - // For a tag pattern starting with "/" no translation is needed. - if (context == EXPAND_HELP - || context == EXPAND_CHECKHEALTH - || context == EXPAND_COLORS - || context == EXPAND_COMPILER - || context == EXPAND_OWNSYNTAX - || context == EXPAND_FILETYPE - || context == EXPAND_PACKADD - || ((context == EXPAND_TAGS_LISTFILES || context == EXPAND_TAGS) - && fname[0] == '/')) { - retval = vim_strnsave(fname, len); - } else { - new_len = len + 2; // +2 for '^' at start, NUL at end - for (i = 0; i < len; i++) { - if (fname[i] == '*' || fname[i] == '~') { - new_len++; /* '*' needs to be replaced by ".*" - '~' needs to be replaced by "\~" */ - } - // Buffer names are like file names. "." should be literal - if (context == EXPAND_BUFFERS && fname[i] == '.') { - new_len++; // "." becomes "\." - } - /* Custom expansion takes care of special things, match - * backslashes literally (perhaps also for other types?) */ - if ((context == EXPAND_USER_DEFINED - || context == EXPAND_USER_LIST) && fname[i] == '\\') { - new_len++; // '\' becomes "\\" - } - } - retval = xmalloc(new_len); - { - retval[0] = '^'; - j = 1; - for (i = 0; i < len; i++, j++) { - /* Skip backslash. But why? At least keep it for custom - * expansion. */ - if (context != EXPAND_USER_DEFINED - && context != EXPAND_USER_LIST - && fname[i] == '\\' - && ++i == len) { - break; - } - - switch (fname[i]) { - case '*': - retval[j++] = '.'; - break; - case '~': - retval[j++] = '\\'; - break; - case '?': - retval[j] = '.'; - continue; - case '.': - if (context == EXPAND_BUFFERS) { - retval[j++] = '\\'; - } - break; - case '\\': - if (context == EXPAND_USER_DEFINED - || context == EXPAND_USER_LIST) { - retval[j++] = '\\'; - } - break; - } - retval[j] = fname[i]; - } - retval[j] = NUL; - } - } - } else { - retval = xmalloc(len + 4); - STRLCPY(retval, fname, len + 1); - - /* - * Don't add a star to *, ~, ~user, $var or `cmd`. - * * would become **, which walks the whole tree. - * ~ would be at the start of the file name, but not the tail. - * $ could be anywhere in the tail. - * ` could be anywhere in the file name. - * When the name ends in '$' don't add a star, remove the '$'. - */ - tail = (char_u *)path_tail((char *)retval); - ends_in_star = (len > 0 && retval[len - 1] == '*'); -#ifndef BACKSLASH_IN_FILENAME - for (ssize_t k = (ssize_t)len - 2; k >= 0; k--) { - if (retval[k] != '\\') { - break; - } - ends_in_star = !ends_in_star; - } -#endif - if ((*retval != '~' || tail != retval) - && !ends_in_star - && vim_strchr((char *)tail, '$') == NULL - && vim_strchr((char *)retval, '`') == NULL) { - retval[len++] = '*'; - } else if (len > 0 && retval[len - 1] == '$') { - --len; - } - retval[len] = NUL; - } - return retval; -} - -/* - * Must parse the command line so far to work out what context we are in. - * Completion can then be done based on that context. - * This routine sets the variables: - * xp->xp_pattern The start of the pattern to be expanded within - * the command line (ends at the cursor). - * xp->xp_context The type of thing to expand. Will be one of: - * - * EXPAND_UNSUCCESSFUL Used sometimes when there is something illegal on - * the command line, like an unknown command. Caller - * should beep. - * EXPAND_NOTHING Unrecognised context for completion, use char like - * a normal char, rather than for completion. eg - * :s/^I/ - * EXPAND_COMMANDS Cursor is still touching the command, so complete - * it. - * EXPAND_BUFFERS Complete file names for :buf and :sbuf commands. - * EXPAND_FILES After command with EX_XFILE set, or after setting - * with P_EXPAND set. eg :e ^I, :w>>^I - * EXPAND_DIRECTORIES In some cases this is used instead of the latter - * when we know only directories are of interest. eg - * :set dir=^I - * EXPAND_SHELLCMD After ":!cmd", ":r !cmd" or ":w !cmd". - * EXPAND_SETTINGS Complete variable names. eg :set d^I - * EXPAND_BOOL_SETTINGS Complete boolean variables only, eg :set no^I - * EXPAND_TAGS Complete tags from the files in p_tags. eg :ta a^I - * EXPAND_TAGS_LISTFILES As above, but list filenames on ^D, after :tselect - * EXPAND_HELP Complete tags from the file 'helpfile'/tags - * EXPAND_EVENTS Complete event names - * EXPAND_SYNTAX Complete :syntax command arguments - * EXPAND_HIGHLIGHT Complete highlight (syntax) group names - * EXPAND_AUGROUP Complete autocommand group names - * EXPAND_USER_VARS Complete user defined variable names, eg :unlet a^I - * EXPAND_MAPPINGS Complete mapping and abbreviation names, - * eg :unmap a^I , :cunab x^I - * EXPAND_FUNCTIONS Complete internal or user defined function names, - * eg :call sub^I - * EXPAND_USER_FUNC Complete user defined function names, eg :delf F^I - * EXPAND_EXPRESSION Complete internal or user defined function/variable - * names in expressions, eg :while s^I - * EXPAND_ENV_VARS Complete environment variable names - * EXPAND_USER Complete user names - */ -void set_expand_context(expand_T *xp) -{ - // only expansion for ':', '>' and '=' command-lines - if (ccline.cmdfirstc != ':' - && ccline.cmdfirstc != '>' && ccline.cmdfirstc != '=' - && !ccline.input_fn) { - xp->xp_context = EXPAND_NOTHING; - return; - } - set_cmd_context(xp, ccline.cmdbuff, ccline.cmdlen, ccline.cmdpos, true); -} - -/// @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) -{ - char_u old_char = NUL; - - /* - * Avoid a UMR warning from Purify, only save the character if it has been - * written before. - */ - if (col < len) { - old_char = str[col]; - } - str[col] = NUL; - const char *nextcomm = (const char *)str; - - if (use_ccline && ccline.cmdfirstc == '=') { - // pass CMD_SIZE because there is no real command - set_context_for_expression(xp, (char *)str, CMD_SIZE); - } else if (use_ccline && ccline.input_fn) { - xp->xp_context = ccline.xp_context; - xp->xp_pattern = (char *)ccline.cmdbuff; - xp->xp_arg = (char *)ccline.xp_arg; - } else { - while (nextcomm != NULL) { - nextcomm = set_one_cmd_context(xp, nextcomm); - } - } - - /* Store the string here so that call_user_expand_func() can get to them - * easily. */ - xp->xp_line = (char *)str; - xp->xp_col = col; - - str[col] = old_char; -} - -/// Expand the command line "str" from context "xp". -/// "xp" must have been set by set_cmd_context(). -/// xp->xp_pattern points into "str", to where the text that is to be expanded -/// starts. -/// Returns EXPAND_UNSUCCESSFUL when there is something illegal before the -/// cursor. -/// Returns EXPAND_NOTHING when there is nothing to expand, might insert the -/// key that triggered expansion literally. -/// Returns EXPAND_OK otherwise. -/// -/// @param str start of command line -/// @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) -{ - char_u *file_str = NULL; - int options = WILD_ADD_SLASH|WILD_SILENT; - - if (xp->xp_context == EXPAND_UNSUCCESSFUL) { - beep_flush(); - return EXPAND_UNSUCCESSFUL; // Something illegal on command line - } - if (xp->xp_context == EXPAND_NOTHING) { - // Caller can use the character as a normal char instead - return EXPAND_NOTHING; - } - - // 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 = addstar((char_u *)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) { - *matchcount = 0; - *matches = NULL; - } - xfree(file_str); - - return EXPAND_OK; -} - -// Cleanup matches for help tags: -// Remove "@ab" if the top of 'helplang' is "ab" and the language of the first -// tag matches it. Otherwise remove "@en" if "en" is the only language. -static void cleanup_help_tags(int num_file, char **file) -{ - char_u buf[4]; - char_u *p = buf; - - if (p_hlg[0] != NUL && (p_hlg[0] != 'e' || p_hlg[1] != 'n')) { - *p++ = '@'; - *p++ = p_hlg[0]; - *p++ = p_hlg[1]; - } - *p = NUL; - - for (int i = 0; i < num_file; i++) { - int len = (int)STRLEN(file[i]) - 3; - if (len <= 0) { - continue; - } - if (STRCMP(file[i] + len, "@en") == 0) { - // Sorting on priority means the same item in another language may - // be anywhere. Search all items for a match up to the "@en". - int j; - for (j = 0; j < num_file; j++) { - if (j != i - && (int)STRLEN(file[j]) == len + 3 - && STRNCMP(file[i], file[j], len + 1) == 0) { - break; - } - } - if (j == num_file) { - // item only exists with @en, remove it - file[i][len] = NUL; - } - } - } - - if (*buf != NUL) { - for (int i = 0; i < num_file; i++) { - int len = (int)STRLEN(file[i]) - 3; - if (len <= 0) { - continue; - } - if (STRCMP(file[i] + len, buf) == 0) { - // remove the default language - file[i][len] = NUL; - } - } - } -} - -typedef char *(*ExpandFunc)(expand_T *, int); - -/// 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) -{ - regmatch_T regmatch; - int ret; - int flags; - - flags = EW_DIR; // include directories - if (options & WILD_LIST_NOTFOUND) { - flags |= EW_NOTFOUND; - } - if (options & WILD_ADD_SLASH) { - flags |= EW_ADDSLASH; - } - if (options & WILD_KEEP_ALL) { - flags |= EW_KEEPALL; - } - if (options & WILD_SILENT) { - flags |= EW_SILENT; - } - if (options & WILD_NOERROR) { - flags |= EW_NOERROR; - } - if (options & WILD_ALLLINKS) { - flags |= EW_ALLLINKS; - } - - if (xp->xp_context == EXPAND_FILES - || xp->xp_context == EXPAND_DIRECTORIES - || xp->xp_context == EXPAND_FILES_IN_PATH) { - /* - * Expand file or directory names. - */ - int free_pat = FALSE; - int i; - - // for ":set path=" and ":set tags=" halve backslashes for escaped space - if (xp->xp_backslash != XP_BS_NONE) { - free_pat = TRUE; - pat = vim_strsave(pat); - for (i = 0; pat[i]; ++i) { - if (pat[i] == '\\') { - if (xp->xp_backslash == XP_BS_THREE - && pat[i + 1] == '\\' - && pat[i + 2] == '\\' - && pat[i + 3] == ' ') { - STRMOVE(pat + i, pat + i + 3); - } - if (xp->xp_backslash == XP_BS_ONE - && pat[i + 1] == ' ') { - STRMOVE(pat + i, pat + i + 1); - } - } - } - } - - if (xp->xp_context == EXPAND_FILES) { - flags |= EW_FILE; - } else if (xp->xp_context == EXPAND_FILES_IN_PATH) { - flags |= (EW_FILE | EW_PATH); - } else { - flags = (flags | EW_DIR) & ~EW_FILE; - } - if (options & WILD_ICASE) { - flags |= EW_ICASE; - } - - // Expand wildcards, supporting %:h and the like. - ret = expand_wildcards_eval(&pat, num_file, file, flags); - if (free_pat) { - xfree(pat); - } -#ifdef BACKSLASH_IN_FILENAME - if (p_csl[0] != NUL && (options & WILD_IGNORE_COMPLETESLASH) == 0) { - for (int i = 0; i < *num_file; i++) { - char_u *ptr = (*file)[i]; - while (*ptr != NUL) { - if (p_csl[0] == 's' && *ptr == '\\') { - *ptr = '/'; - } else if (p_csl[0] == 'b' && *ptr == '/') { - *ptr = '\\'; - } - ptr += utfc_ptr2len(ptr); - } - } - } -#endif - return ret; - } - - *file = NULL; - *num_file = 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); - return OK; - } - return FAIL; - } - - if (xp->xp_context == EXPAND_SHELLCMD) { - *file = NULL; - expand_shellcmd(pat, num_file, file, flags); - return OK; - } - if (xp->xp_context == EXPAND_OLD_SETTING) { - ExpandOldSetting(num_file, file); - return OK; - } - if (xp->xp_context == EXPAND_BUFFERS) { - return ExpandBufnames((char *)pat, num_file, file, options); - } - if (xp->xp_context == EXPAND_DIFF_BUFFERS) { - return ExpandBufnames((char *)pat, num_file, file, 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); - } - if (xp->xp_context == EXPAND_COLORS) { - char *directories[] = { "colors", NULL }; - return ExpandRTDir(pat, DIP_START + DIP_OPT + DIP_LUA, num_file, file, directories); - } - if (xp->xp_context == EXPAND_COMPILER) { - char *directories[] = { "compiler", NULL }; - return ExpandRTDir(pat, DIP_LUA, num_file, file, directories); - } - if (xp->xp_context == EXPAND_OWNSYNTAX) { - char *directories[] = { "syntax", NULL }; - return ExpandRTDir(pat, 0, num_file, file, directories); - } - if (xp->xp_context == EXPAND_FILETYPE) { - char *directories[] = { "syntax", "indent", "ftplugin", NULL }; - return ExpandRTDir(pat, DIP_LUA, num_file, file, directories); - } - if (xp->xp_context == EXPAND_USER_LIST) { - return ExpandUserList(xp, num_file, file); - } - if (xp->xp_context == EXPAND_USER_LUA) { - return ExpandUserLua(xp, num_file, file); - } - if (xp->xp_context == EXPAND_PACKADD) { - return ExpandPackAddDir(pat, num_file, file); - } - - // 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; - - tofree = xmalloc(len); - snprintf(tofree, len, "^<SNR>\\d\\+_%s", pat + 3); - pat = (char_u *)tofree; - } - - if (xp->xp_context == EXPAND_LUA) { - ILOG("PAT %s", pat); - return nlua_expand_pat(xp, pat, num_file, file); - } - - regmatch.regprog = vim_regcomp((char *)pat, p_magic ? 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); - - if (xp->xp_context == EXPAND_SETTINGS - || xp->xp_context == EXPAND_BOOL_SETTINGS) { - ret = ExpandSettings(xp, ®match, num_file, file); - } else if (xp->xp_context == EXPAND_MAPPINGS) { - ret = ExpandMappings(®match, num_file, file); - } else if (xp->xp_context == EXPAND_USER_DEFINED) { - ret = ExpandUserDefined(xp, ®match, num_file, file); - } else { - static struct expgen { - int context; - ExpandFunc func; - int ic; - int escaped; - } tab[] = { - { EXPAND_COMMANDS, get_command_name, false, true }, - { EXPAND_BEHAVE, get_behave_arg, true, true }, - { EXPAND_MAPCLEAR, get_mapclear_arg, true, true }, - { EXPAND_MESSAGES, get_messages_arg, true, true }, - { EXPAND_HISTORY, get_history_arg, true, true }, - { EXPAND_USER_COMMANDS, get_user_commands, false, true }, - { EXPAND_USER_ADDR_TYPE, get_user_cmd_addr_type, false, true }, - { EXPAND_USER_CMD_FLAGS, get_user_cmd_flags, false, true }, - { EXPAND_USER_NARGS, get_user_cmd_nargs, false, true }, - { EXPAND_USER_COMPLETE, get_user_cmd_complete, false, true }, - { EXPAND_USER_VARS, get_user_var_name, false, true }, - { EXPAND_FUNCTIONS, get_function_name, false, true }, - { EXPAND_USER_FUNC, get_user_func_name, false, true }, - { EXPAND_EXPRESSION, get_expr_name, false, true }, - { EXPAND_MENUS, get_menu_name, false, true }, - { EXPAND_MENUNAMES, get_menu_names, false, true }, - { EXPAND_SYNTAX, get_syntax_name, true, true }, - { EXPAND_SYNTIME, get_syntime_arg, true, true }, - { EXPAND_HIGHLIGHT, (ExpandFunc)get_highlight_name, true, true }, - { EXPAND_EVENTS, expand_get_event_name, true, true }, - { EXPAND_AUGROUP, expand_get_augroup_name, true, true }, - { EXPAND_CSCOPE, get_cscope_name, true, true }, - { EXPAND_SIGN, get_sign_name, true, true }, - { EXPAND_PROFILE, get_profile_name, true, true }, -#ifdef HAVE_WORKING_LIBINTL - { EXPAND_LANGUAGE, get_lang_arg, true, false }, - { EXPAND_LOCALES, get_locales, true, false }, -#endif - { EXPAND_ENV_VARS, get_env_name, true, true }, - { EXPAND_USER, get_users, true, false }, - { EXPAND_ARGLIST, get_arglist_name, true, false }, - { EXPAND_CHECKHEALTH, get_healthcheck_names, true, false }, - }; - int i; - - /* - * Find a context in the table and call the ExpandGeneric() with the - * right function to do the expansion. - */ - ret = FAIL; - for (i = 0; i < (int)ARRAY_SIZE(tab); ++i) { - if (xp->xp_context == tab[i].context) { - if (tab[i].ic) { - regmatch.rm_ic = TRUE; - } - ExpandGeneric(xp, ®match, num_file, file, tab[i].func, tab[i].escaped); - ret = OK; - break; - } - } - } - - vim_regfree(regmatch.regprog); - xfree(tofree); - - return ret; + return &ccline; } -/// Expand a list of names. -/// -/// Generic function for command line completion. It calls a function to -/// obtain strings, one by one. The strings are matched against a regexp -/// 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) +unsigned get_cmdline_last_prompt_id(void) { - int i; - size_t count = 0; - char_u *str; - - // count the number of matching names - for (i = 0;; i++) { - str = (char_u *)(*func)(xp, i); - if (str == NULL) { // end of list - break; - } - if (*str == NUL) { // skip empty strings - continue; - } - if (vim_regexec(regmatch, (char *)str, (colnr_T)0)) { - count++; - } - } - if (count == 0) { - return; - } - 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 = (char_u *)(*func)(xp, i); - if (str == NULL) { // End of list. - break; - } - if (*str == NUL) { // Skip empty strings. - continue; - } - if (vim_regexec(regmatch, (char *)str, (colnr_T)0)) { - if (escaped) { - str = vim_strsave_escaped(str, (char_u *)" \t\\."); - } else { - str = vim_strsave(str); - } - (*file)[count++] = (char *)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) { - // <SNR> functions should be sorted to the end. - qsort((void *)*file, (size_t)*num_file, sizeof(char_u *), - sort_func_compare); - } else { - sort_strings(*file, *num_file); - } - } - - /* 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(); + return last_prompt_id; } -/// 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_u *filepat, int *num_file, char ***file, int flagsarg) - FUNC_ATTR_NONNULL_ALL +/// Get pointer to the command line info to use. save_cmdline() may clear +/// ccline and put the previous value in ccline.prev_ccline. +static CmdlineInfo *get_ccline_ptr(void) { - char_u *pat; - int i; - char_u *path = NULL; - garray_T ga; - char *buf = xmalloc(MAXPATHL); - size_t l; - char_u *s, *e; - int flags = flagsarg; - int ret; - bool did_curdir = false; - - // for ":set path=" and ":set tags=" halve backslashes for escaped space - pat = vim_strsave(filepat); - for (i = 0; pat[i]; ++i) { - if (pat[i] == '\\' && pat[i + 1] == ' ') { - STRMOVE(pat + i, pat + i + 1); - } - } - - flags |= EW_FILE | EW_EXEC | EW_SHELLCMD; - - bool mustfree = false; // Track memory allocation for *path. - if (pat[0] == '.' && (vim_ispathsep(pat[1]) - || (pat[1] == '.' && vim_ispathsep(pat[2])))) { - path = (char_u *)"."; + if ((State & MODE_CMDLINE) == 0) { + return NULL; + } else if (ccline.cmdbuff != NULL) { + return &ccline; + } else if (ccline.prev_ccline && ccline.prev_ccline->cmdbuff != NULL) { + return ccline.prev_ccline; } else { - // For an absolute name we don't use $PATH. - if (!path_is_absolute(pat)) { - path = (char_u *)vim_getenv("PATH"); - } - if (path == NULL) { - path = (char_u *)""; - } else { - mustfree = true; - } - } - - /* - * 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 - * current directory, to find "subdir/cmd". - */ - ga_init(&ga, (int)sizeof(char *), 10); - hashtab_T found_ht; - hash_init(&found_ht); - for (s = path;; s = e) { - e = (char_u *)vim_strchr((char *)s, ENV_SEPCHAR); - if (e == NULL) { - e = s + STRLEN(s); - } - - if (*s == NUL) { - if (did_curdir) { - break; - } - // Find directories in the current directory, path is empty. - did_curdir = true; - flags |= EW_DIR; - } else if (STRNCMP(s, ".", e - s) == 0) { - did_curdir = true; - flags |= EW_DIR; - } else { - // Do not match directories inside a $PATH item. - flags &= ~EW_DIR; - } - - l = (size_t)(e - s); - 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); - } - } - if (*e != NUL) { - ++e; - } - } - *file = ga.ga_data; - *num_file = ga.ga_len; - - xfree(buf); - xfree(pat); - if (mustfree) { - xfree(path); - } - hash_clear(&found_ht); -} - -/// 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) - FUNC_ATTR_NONNULL_ALL -{ - char_u keep = 0; - typval_T args[4]; - char_u *pat = NULL; - const sctx_T save_current_sctx = current_sctx; - - 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]; - ccline.cmdbuff[ccline.cmdlen] = 0; - } - - pat = vim_strnsave((char_u *)xp->xp_pattern, xp->xp_pattern_len); - args[0].v_type = VAR_STRING; - args[1].v_type = VAR_STRING; - args[2].v_type = VAR_NUMBER; - args[3].v_type = VAR_UNKNOWN; - args[0].vval.v_string = (char *)pat; - args[1].vval.v_string = xp->xp_line; - args[2].vval.v_number = xp->xp_col; - - current_sctx = xp->xp_script_ctx; - - void *const ret = user_expand_func((char_u *)xp->xp_arg, 3, args); - - current_sctx = save_current_sctx; - if (ccline.cmdbuff != NULL) { - ccline.cmdbuff[ccline.cmdlen] = keep; - } - - xfree(pat); - 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) +/// Get the current command-line type. +/// Returns ':' or '/' or '?' or '@' or '>' or '-' +/// Only works when the command line is being edited. +/// Returns NUL when something is wrong. +static int get_cmdline_type(void) { - char_u *e; - garray_T ga; - - char_u *const retstr = call_user_expand_func((user_expand_func_T)call_func_retstr, xp, num_file, - file); - - if (retstr == NULL) { - return FAIL; - } - - ga_init(&ga, (int)sizeof(char *), 3); - for (char_u *s = retstr; *s != NUL; s = e) { - e = (char_u *)vim_strchr((char *)s, '\n'); - if (e == NULL) { - e = s + STRLEN(s); - } - const char_u keep = *e; - *e = NUL; - - const bool skip = xp->xp_pattern[0] - && vim_regexec(regmatch, (char *)s, (colnr_T)0) == 0; - *e = keep; - if (!skip) { - GA_APPEND(char_u *, &ga, vim_strnsave(s, (size_t)(e - s))); - } - - if (*e != NUL) { - e++; - } - } - xfree(retstr); - *file = ga.ga_data; - *num_file = ga.ga_len; - return OK; -} + CmdlineInfo *p = get_ccline_ptr(); -/// Expand names with a list returned by a function defined by the user. -static int ExpandUserList(expand_T *xp, int *num_file, char ***file) -{ - list_T *const retlist = call_user_expand_func((user_expand_func_T)call_func_retlist, xp, num_file, - file); - if (retlist == NULL) { - return FAIL; + if (p == NULL) { + return NUL; } - - garray_T ga; - ga_init(&ga, (int)sizeof(char *), 3); - // Loop over the items in the list. - TV_LIST_ITER_CONST(retlist, li, { - if (TV_LIST_ITEM_TV(li)->v_type != VAR_STRING - || TV_LIST_ITEM_TV(li)->vval.v_string == NULL) { - continue; // Skip non-string items and empty strings. - } - - GA_APPEND(char *, &ga, xstrdup((const char *)TV_LIST_ITEM_TV(li)->vval.v_string)); - }); - tv_list_unref(retlist); - - *file = ga.ga_data; - *num_file = ga.ga_len; - return OK; -} - -static int ExpandUserLua(expand_T *xp, int *num_file, char ***file) -{ - typval_T rettv; - nlua_call_user_expand_func(xp, &rettv); - if (rettv.v_type != VAR_LIST) { - tv_clear(&rettv); - return FAIL; + if (p->cmdfirstc == NUL) { + return (p->input_fn) ? '@' : '-'; } - - list_T *const retlist = rettv.vval.v_list; - - garray_T ga; - ga_init(&ga, (int)sizeof(char *), 3); - // Loop over the items in the list. - TV_LIST_ITER_CONST(retlist, li, { - if (TV_LIST_ITEM_TV(li)->v_type != VAR_STRING - || TV_LIST_ITEM_TV(li)->vval.v_string == NULL) { - continue; // Skip non-string items and empty strings. - } - - GA_APPEND(char *, &ga, xstrdup((const char *)TV_LIST_ITEM_TV(li)->vval.v_string)); - }); - tv_list_unref(retlist); - - *file = ga.ga_data; - *num_file = ga.ga_len; - return OK; + return p->cmdfirstc; } -/// 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 -/// "dirnames" is an array with one or more directory names. -static int ExpandRTDir(char_u *pat, int flags, int *num_file, char ***file, char *dirnames[]) +/// Get the current command line in allocated memory. +/// Only works when the command line is being edited. +/// Returns NULL when something is wrong. +static char_u *get_cmdline_str(void) { - *num_file = 0; - *file = NULL; - size_t pat_len = STRLEN(pat); - - garray_T ga; - ga_init(&ga, (int)sizeof(char *), 10); - - // TODO(bfredl): this is bullshit, exandpath should not reinvent path logic. - for (int i = 0; dirnames[i] != NULL; i++) { - size_t size = STRLEN(dirnames[i]) + pat_len + 7; - char_u *s = xmalloc(size); - snprintf((char *)s, size, "%s/%s*.vim", dirnames[i], pat); - globpath(p_rtp, s, &ga, 0); - if (flags & DIP_LUA) { - snprintf((char *)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; - char_u *s = xmalloc(size); - snprintf((char *)s, size, "pack/*/start/*/%s/%s*.vim", dirnames[i], pat); // NOLINT - globpath(p_pp, s, &ga, 0); - if (flags & DIP_LUA) { - snprintf((char *)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; - char_u *s = xmalloc(size); - snprintf((char *)s, size, "start/*/%s/%s*.vim", dirnames[i], pat); // NOLINT - globpath(p_pp, s, &ga, 0); - if (flags & DIP_LUA) { - snprintf((char *)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; - char_u *s = xmalloc(size); - snprintf((char *)s, size, "pack/*/opt/*/%s/%s*.vim", dirnames[i], pat); // NOLINT - globpath(p_pp, s, &ga, 0); - if (flags & DIP_LUA) { - snprintf((char *)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; - char_u *s = xmalloc(size); - snprintf((char *)s, size, "opt/*/%s/%s*.vim", dirnames[i], pat); // NOLINT - globpath(p_pp, s, &ga, 0); - if (flags & DIP_LUA) { - snprintf((char *)s, size, "opt/*/%s/%s*.lua", dirnames[i], pat); // NOLINT - globpath(p_pp, s, &ga, 0); - } - xfree(s); - } - } - - for (int i = 0; i < ga.ga_len; i++) { - char_u *match = ((char_u **)ga.ga_data)[i]; - char_u *s = match; - char_u *e = s + STRLEN(s); - if (e - s > 4 && (STRNICMP(e - 4, ".vim", 4) == 0 - || ((flags & DIP_LUA) - && STRNICMP(e - 4, ".lua", 4) == 0))) { - e -= 4; - for (s = e; s > match; MB_PTR_BACK(match, s)) { - if (vim_ispathsep(*s)) { - break; - } - } - s++; - *e = NUL; - assert((e - s) + 1 >= 0); - memmove(match, s, (size_t)(e - s) + 1); - } + if (cmdline_star > 0) { + return NULL; } + CmdlineInfo *p = get_ccline_ptr(); - if (GA_EMPTY(&ga)) { - return FAIL; + if (p == NULL) { + return NULL; } - - /* Sort and remove duplicates which can happen when specifying multiple - * directories in dirnames. */ - ga_remove_duplicate_strings(&ga); - - *file = ga.ga_data; - *num_file = ga.ga_len; - return OK; + return vim_strnsave(p->cmdbuff, (size_t)p->cmdlen); } -/// Expand loadplugin names: -/// 'packpath'/pack/ * /opt/{pat} -static int ExpandPackAddDir(char_u *pat, int *num_file, char ***file) +/// Get the current command-line completion type. +static char_u *get_cmdline_completion(void) { - garray_T ga; - - *num_file = 0; - *file = NULL; - size_t pat_len = STRLEN(pat); - ga_init(&ga, (int)sizeof(char *), 10); - - size_t buflen = pat_len + 26; - char_u *s = xmalloc(buflen); - snprintf((char *)s, buflen, "pack/*/opt/%s*", pat); // NOLINT - globpath(p_pp, s, &ga, 0); - snprintf((char *)s, buflen, "opt/%s*", pat); // NOLINT - globpath(p_pp, s, &ga, 0); - xfree(s); - - for (int i = 0; i < ga.ga_len; i++) { - char_u *match = ((char_u **)ga.ga_data)[i]; - s = (char_u *)path_tail((char *)match); - memmove(match, s, STRLEN(s) + 1); - } - - if (GA_EMPTY(&ga)) { - return FAIL; + if (cmdline_star > 0) { + return NULL; } + CmdlineInfo *p = get_ccline_ptr(); - // Sort and remove duplicates which can happen when specifying multiple - // directories in dirnames. - ga_remove_duplicate_strings(&ga); - - *file = ga.ga_data; - *num_file = ga.ga_len; - return OK; -} - -/// Expand `file` for all comma-separated directories in `path`. -/// Adds matches to `ga`. -void globpath(char_u *path, char_u *file, garray_T *ga, int expand_options) -{ - expand_T xpc; - ExpandInit(&xpc); - xpc.xp_context = EXPAND_FILES; - - char_u *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((char **)&path, (char *)buf, MAXPATHL, ","); - if (STRLEN(buf) + STRLEN(file) + 2 < MAXPATHL) { - add_pathsep((char *)buf); - STRCAT(buf, file); // NOLINT - - char **p; - int num_p = 0; - (void)ExpandFromContext(&xpc, buf, &num_p, &p, - WILD_SILENT | expand_options); - if (num_p > 0) { - ExpandEscape(&xpc, buf, num_p, p, WILD_SILENT | expand_options); - - // Concatenate new results to previous ones. - ga_grow(ga, num_p); - // take over the pointers and put them in "ga" - for (int i = 0; i < num_p; i++) { - ((char_u **)ga->ga_data)[ga->ga_len] = (char_u *)p[i]; - ga->ga_len++; - } - xfree(p); - } + 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 vim_strsave((char_u *)cmd_compl); } } - xfree(buf); -} - -/********************************* -* Command line history stuff * -*********************************/ - -/// Translate a history character to the associated type number -static HistoryType hist_char2type(const int c) - FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT -{ - switch (c) { - case ':': - return HIST_CMD; - case '=': - return HIST_EXPR; - case '@': - return HIST_INPUT; - case '>': - return HIST_DEBUG; - case NUL: - case '/': - case '?': - return HIST_SEARCH; - default: - return HIST_INVALID; - } - // Silence -Wreturn-type - return 0; -} - -/* - * Table of history names. - * These names are used in :history and various hist...() functions. - * It is sufficient to give the significant prefix of a history name. - */ - -static char *(history_names[]) = -{ - "cmd", - "search", - "expr", - "input", - "debug", - NULL -}; - -/* - * Function given to ExpandGeneric() to obtain the possible first - * arguments of the ":history command. - */ -static char *get_history_arg(expand_T *xp, int idx) -{ - static char_u compl[2] = { NUL, NUL }; - char *short_names = ":=@>?/"; - int short_names_count = (int)STRLEN(short_names); - int history_name_count = ARRAY_SIZE(history_names) - 1; - - if (idx < short_names_count) { - compl[0] = (char_u)short_names[idx]; - return (char *)compl; - } - if (idx < short_names_count + history_name_count) { - return history_names[idx - short_names_count]; - } - if (idx == short_names_count + history_name_count) { - return "all"; - } return NULL; } -/// Initialize command line history. -/// Also used to re-allocate history tables when size changes. -void init_history(void) -{ - assert(p_hi >= 0 && p_hi <= INT_MAX); - int newlen = (int)p_hi; - int oldlen = hislen; - - // 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); - } - } - - // 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)); - } - - hisidx[type] = l3 - 1; - xfree(history[type]); - history[type] = temp; - } - hislen = newlen; - } -} - -static inline void hist_free_entry(histentry_T *hisptr) - FUNC_ATTR_NONNULL_ALL +/// "getcmdcompltype()" function +void f_getcmdcompltype(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - xfree(hisptr->hisstr); - tv_list_unref(hisptr->additional_elements); - clear_hist_entry(hisptr); + rettv->v_type = VAR_STRING; + rettv->vval.v_string = (char *)get_cmdline_completion(); } -static inline void clear_hist_entry(histentry_T *hisptr) - FUNC_ATTR_NONNULL_ALL +/// "getcmdline()" function +void f_getcmdline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - memset(hisptr, 0, sizeof(*hisptr)); + rettv->v_type = VAR_STRING; + rettv->vval.v_string = (char *)get_cmdline_str(); } -/// Check if command line 'str' is already in history. -/// If 'move_to_front' is TRUE, matching entry is moved to end of history. -/// -/// @param move_to_front Move the entry to the front if it exists -static int in_history(int type, char_u *str, int move_to_front, int sep) +/// "getcmdpos()" function +void f_getcmdpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - int i; - int last_i = -1; - char_u *p; - - if (hisidx[type] < 0) { - return FALSE; - } - i = hisidx[type]; - do { - if (history[type][i].hisstr == NULL) { - return FALSE; - } - - /* For search history, check that the separator character matches as - * well. */ - p = history[type][i].hisstr; - if (STRCMP(str, p) == 0 - && (type != HIST_SEARCH || sep == p[STRLEN(p) + 1])) { - if (!move_to_front) { - return TRUE; - } - last_i = i; - break; - } - if (--i < 0) { - i = hislen - 1; - } - } 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; - } - 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; + CmdlineInfo *p = get_ccline_ptr(); + rettv->vval.v_number = p != NULL ? p->cmdpos + 1 : 0; } -/// Convert history name to its HIST_ equivalent -/// -/// Names are taken from the table above. When `name` is empty returns currently -/// active history or HIST_DEFAULT, depending on `return_default` argument. -/// -/// @param[in] name Converted name. -/// @param[in] len Name length. -/// @param[in] return_default Determines whether HIST_DEFAULT should be -/// returned or value based on `ccline.cmdfirstc`. -/// -/// @return Any value from HistoryType enum, including HIST_INVALID. May not -/// return HIST_DEFAULT unless return_default is true. -HistoryType get_histtype(const char *const name, const size_t len, const bool return_default) - FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +/// "getcmdscreenpos()" function +void f_getcmdscreenpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - // No argument: use current history. - if (len == 0) { - return return_default ? HIST_DEFAULT : hist_char2type(ccline.cmdfirstc); - } - - for (HistoryType i = 0; history_names[i] != NULL; i++) { - if (STRNICMP(name, history_names[i], len) == 0) { - return i; - } - } - - if (vim_strchr(":=@>?/", name[0]) != NULL && len == 1) { - return hist_char2type(name[0]); - } - - return HIST_INVALID; + CmdlineInfo *p = get_ccline_ptr(); + rettv->vval.v_number = p != NULL ? p->cmdspos + 1 : 0; } -static int last_maptick = -1; // last seen maptick - -/// Add the given string to the given history. If the string is already in the -/// history then it is moved to the front. "histype" may be one of the HIST_ -/// values. -/// -/// @parma in_map consider maptick when inside a mapping -/// @param sep separator character used (search hist) -void add_to_history(int histype, char_u *new_entry, int in_map, int sep) +/// "getcmdtype()" function +void f_getcmdtype(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - histentry_T *hisptr; - - if (hislen == 0 || histype == HIST_INVALID) { // no history - return; - } - assert(histype != HIST_DEFAULT); - - if ((cmdmod.cmod_flags & CMOD_KEEPPATTERNS) && histype == HIST_SEARCH) { - return; - } - - /* - * Searches inside the same mapping overwrite each other, so that only - * the last line is kept. Be careful not to remove a line that was moved - * down, only lines that were added. - */ - if (histype == HIST_SEARCH && in_map) { - if (maptick == last_maptick && hisidx[HIST_SEARCH] >= 0) { - // Current line is from the same mapping, remove it - hisptr = &history[HIST_SEARCH][hisidx[HIST_SEARCH]]; - hist_free_entry(hisptr); - --hisnum[histype]; - if (--hisidx[HIST_SEARCH] < 0) { - hisidx[HIST_SEARCH] = hislen - 1; - } - } - 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 = vim_strnsave(new_entry, len + 2); - hisptr->timestamp = os_time(); - hisptr->additional_elements = NULL; - hisptr->hisstr[len + 1] = (char_u)sep; - - hisptr->hisnum = ++hisnum[histype]; - if (histype == HIST_SEARCH && in_map) { - last_maptick = maptick; - } - } + rettv->v_type = VAR_STRING; + rettv->vval.v_string = xmallocz(1); + rettv->vval.v_string[0] = (char)get_cmdline_type(); } -/* - * Get identifier of newest history entry. - * "histype" may be one of the HIST_ values. - */ -int get_history_idx(int histype) +/// Set the command line str to "str". +/// @return 1 when failed, 0 when OK. +static int set_cmdline_str(const char *str, int pos) { - if (hislen == 0 || histype < 0 || histype >= HIST_COUNT - || hisidx[histype] < 0) { - return -1; - } - - return history[histype][hisidx[histype]].hisnum; -} - -/// Get pointer to the command line info to use. save_cmdline() may clear -/// ccline and put the previous value in ccline.prev_ccline. -static struct cmdline_info *get_ccline_ptr(void) -{ - if ((State & MODE_CMDLINE) == 0) { - return NULL; - } else if (ccline.cmdbuff != NULL) { - return &ccline; - } else if (ccline.prev_ccline && ccline.prev_ccline->cmdbuff != NULL) { - return ccline.prev_ccline; - } else { - return NULL; - } -} - -/// Get the current command-line completion type. -char_u *get_cmdline_completion(void) -{ - if (cmdline_star > 0) { - return NULL; - } - struct cmdline_info *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 vim_strsave((char_u *)cmd_compl); - } - } - - return NULL; -} - -/* - * Get the current command line in allocated memory. - * Only works when the command line is being edited. - * Returns NULL when something is wrong. - */ -char_u *get_cmdline_str(void) -{ - if (cmdline_star > 0) { - return NULL; - } - struct cmdline_info *p = get_ccline_ptr(); + CmdlineInfo *p = get_ccline_ptr(); if (p == NULL) { - return NULL; + return 1; } - return vim_strnsave(p->cmdbuff, (size_t)p->cmdlen); -} -/* - * Get the current command line position, counted in bytes. - * Zero is the first position. - * Only works when the command line is being edited. - * Returns -1 when something is wrong. - */ -int get_cmdline_pos(void) -{ - struct cmdline_info *p = get_ccline_ptr(); + int len = (int)STRLEN(str); + realloc_cmdbuff(len + 1); + p->cmdlen = len; + STRCPY(p->cmdbuff, str); - if (p == NULL) { - return -1; - } - return p->cmdpos; -} + p->cmdpos = pos < 0 || pos > p->cmdlen ? p->cmdlen : pos; + new_cmdpos = p->cmdpos; -/// Get the command line cursor screen position. -int get_cmdline_screen_pos(void) -{ - struct cmdline_info *p = get_ccline_ptr(); + redrawcmd(); - if (p == NULL) { - return -1; - } - return p->cmdspos; + // Trigger CmdlineChanged autocommands. + do_autocmd_cmdlinechanged(get_cmdline_type()); + + return 0; } -/* - * Set the command line byte position to "pos". Zero is the first position. - * Only works when the command line is being edited. - * Returns 1 when failed, 0 when OK. - */ -int set_cmdline_pos(int pos) +/// Set the command line byte position to "pos". Zero is the first position. +/// Only works when the command line is being edited. +/// @return 1 when failed, 0 when OK. +static int set_cmdline_pos(int pos) { - struct cmdline_info *p = get_ccline_ptr(); + CmdlineInfo *p = get_ccline_ptr(); if (p == NULL) { return 1; @@ -6273,187 +4068,45 @@ int set_cmdline_pos(int pos) return 0; } -/* - * Get the current command-line type. - * Returns ':' or '/' or '?' or '@' or '>' or '-' - * Only works when the command line is being edited. - * Returns NUL when something is wrong. - */ -int get_cmdline_type(void) +/// "setcmdline()" function +void f_setcmdline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - struct cmdline_info *p = get_ccline_ptr(); - - if (p == NULL) { - return NUL; - } - if (p->cmdfirstc == NUL) { - return (p->input_fn) ? '@' : '-'; + if (argvars[0].v_type != VAR_STRING || argvars[0].vval.v_string == NULL) { + emsg(_(e_stringreq)); + return; } - return p->cmdfirstc; -} - -/* - * Calculate history index from a number: - * num > 0: seen as identifying number of a history entry - * num < 0: relative position in history wrt newest entry - * "histype" may be one of the HIST_ values. - */ -static int calc_hist_idx(int histype, int num) -{ - int i; - histentry_T *hist; - int wrapped = FALSE; - if (hislen == 0 || histype < 0 || histype >= HIST_COUNT - || (i = hisidx[histype]) < 0 || num == 0) { - return -1; - } + int pos = -1; + if (argvars[1].v_type != VAR_UNKNOWN) { + bool error = false; - hist = history[histype]; - if (num > 0) { - while (hist[i].hisnum > num) { - if (--i < 0) { - if (wrapped) { - break; - } - i += hislen; - wrapped = TRUE; - } - } - if (i >= 0 && hist[i].hisnum == num && hist[i].hisstr != NULL) { - return i; - } - } else if (-num <= hislen) { - i += num + 1; - if (i < 0) { - i += hislen; + pos = (int)tv_get_number_chk(&argvars[1], &error) - 1; + if (error) { + return; } - if (hist[i].hisstr != NULL) { - return i; + if (pos < 0) { + emsg(_(e_positive)); + return; } } - return -1; -} -/* - * Get a history entry by its index. - * "histype" may be one of the HIST_ values. - */ -char_u *get_history_entry(int histype, int idx) -{ - idx = calc_hist_idx(histype, idx); - if (idx >= 0) { - return history[histype][idx].hisstr; - } else { - return (char_u *)""; - } + rettv->vval.v_number = set_cmdline_str(argvars[0].vval.v_string, pos); } -/// Clear all entries in a history -/// -/// @param[in] histype One of the HIST_ values. -/// -/// @return OK if there was something to clean and histype was one of HIST_ -/// values, FAIL otherwise. -int clr_history(const int histype) +/// "setcmdpos()" function +void f_setcmdpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - if (hislen != 0 && histype >= 0 && histype < HIST_COUNT) { - histentry_T *hisptr = history[histype]; - for (int i = hislen; i--; hisptr++) { - hist_free_entry(hisptr); - } - hisidx[histype] = -1; // mark history as cleared - hisnum[histype] = 0; // reset identifier counter - return OK; - } - return FAIL; -} + const int pos = (int)tv_get_number(&argvars[0]) - 1; -/* - * Remove all entries matching {str} from a history. - * "histype" may be one of the HIST_ values. - */ -int del_history_entry(int histype, char_u *str) -{ - regmatch_T regmatch; - histentry_T *hisptr; - int idx; - int i; - int last; - bool found = 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, (char *)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; - } - } - if (--i < 0) { - i += hislen; - } - } while (i != idx); - if (history[histype][idx].hisstr == NULL) { - hisidx[histype] = -1; - } + if (pos >= 0) { + rettv->vval.v_number = set_cmdline_pos(pos); } - vim_regfree(regmatch.regprog); - return found; } -/* - * Remove an indexed entry from a history. - * "histype" may be one of the HIST_ values. - */ -int del_history_idx(int histype, int idx) +/// Return the first character of the current command line. +int get_cmdline_firstc(void) { - int i, j; - - i = calc_hist_idx(histype, idx); - if (i < 0) { - return FALSE; - } - idx = hisidx[histype]; - hist_free_entry(&history[histype][i]); - - /* When deleting the last added search string in a mapping, reset - * last_maptick, so that the last added search string isn't deleted again. - */ - if (histype == HIST_SEARCH && maptick == last_maptick && i == idx) { - last_maptick = -1; - } - - while (i != idx) { - j = (i + 1) % hislen; - history[histype][i] = history[histype][j]; - i = j; - } - clear_hist_entry(&history[histype][idx]); - if (--i < 0) { - i += hislen; - } - hisidx[histype] = i; - return TRUE; + return ccline.cmdfirstc; } /// Get indices that specify a range within a list (not a range of text lines @@ -6464,26 +4117,26 @@ int del_history_idx(int histype, int idx) /// @param num2 to /// /// @return OK if parsed successfully, otherwise FAIL. -int get_list_range(char_u **str, int *num1, int *num2) +int get_list_range(char **str, int *num1, int *num2) { int len; int first = false; varnumber_T num; - *str = (char_u *)skipwhite((char *)(*str)); + *str = skipwhite((*str)); if (**str == '-' || ascii_isdigit(**str)) { // parse "from" part of range - vim_str2nr(*str, NULL, &len, 0, &num, NULL, 0, false); + vim_str2nr((char_u *)(*str), NULL, &len, 0, &num, NULL, 0, false); *str += len; *num1 = (int)num; first = true; } - *str = (char_u *)skipwhite((char *)(*str)); + *str = skipwhite((*str)); if (**str == ',') { // parse "to" part of range - *str = (char_u *)skipwhite((char *)(*str) + 1); - vim_str2nr(*str, NULL, &len, 0, &num, NULL, 0, false); + *str = skipwhite((*str) + 1); + vim_str2nr((char_u *)(*str), NULL, &len, 0, &num, NULL, 0, false); if (len > 0) { *num2 = (int)num; - *str = (char_u *)skipwhite((char *)(*str) + len); + *str = skipwhite((*str) + len); } else if (!first) { // no number given at all return FAIL; } @@ -6493,118 +4146,27 @@ int get_list_range(char_u **str, int *num1, int *num2) return OK; } -/* - * :history command - print a history - */ -void ex_history(exarg_T *eap) +void cmdline_init(void) { - histentry_T *hist; - int histype1 = HIST_CMD; - int histype2 = HIST_CMD; - int hisidx1 = 1; - int hisidx2 = -1; - int idx; - int i, j, k; - char_u *end; - char_u *arg = (char_u *)eap->arg; - - if (hislen == 0) { - msg(_("'history' option is zero")); - return; - } - - if (!(ascii_isdigit(*arg) || *arg == '-' || *arg == ',')) { - end = arg; - while (ASCII_ISALPHA(*end) - || vim_strchr(":=@>/?", *end) != NULL) { - end++; - } - histype1 = get_histtype((const char *)arg, (size_t)(end - arg), false); - if (histype1 == HIST_INVALID) { - if (STRNICMP(arg, "all", end - arg) == 0) { - histype1 = 0; - histype2 = HIST_COUNT - 1; - } else { - semsg(_(e_trailing_arg), arg); - return; - } - } else { - histype2 = histype1; - } - } else { - end = arg; - } - if (!get_list_range(&end, &hisidx1, &hisidx2) || *end != NUL) { - semsg(_(e_trailing_arg), end); - return; - } - - for (; !got_int && histype1 <= histype2; ++histype1) { - STRCPY(IObuff, "\n # "); - assert(history_names[histype1] != NULL); - STRCAT(STRCAT(IObuff, history_names[histype1]), " history"); - msg_puts_title((char *)IObuff); - idx = hisidx[histype1]; - hist = history[histype1]; - j = hisidx1; - k = hisidx2; - if (j < 0) { - j = (-j > hislen) ? 0 : hist[(hislen + j + idx + 1) % hislen].hisnum; - } - if (k < 0) { - k = (-k > hislen) ? 0 : hist[(hislen + k + idx + 1) % hislen].hisnum; - } - if (idx >= 0 && j <= k) { - for (i = idx + 1; !got_int; ++i) { - if (i == hislen) { - i = 0; - } - if (hist[i].hisstr != NULL - && hist[i].hisnum >= j && hist[i].hisnum <= k) { - msg_putchar('\n'); - snprintf((char *)IObuff, IOSIZE, "%c%6d ", i == idx ? '>' : ' ', - hist[i].hisnum); - if (vim_strsize((char *)hist[i].hisstr) > Columns - 10) { - trunc_string((char *)hist[i].hisstr, (char *)IObuff + STRLEN(IObuff), - Columns - 10, IOSIZE - (int)STRLEN(IObuff)); - } else { - STRCAT(IObuff, hist[i].hisstr); - } - msg_outtrans((char *)IObuff); - ui_flush(); - } - if (i == idx) { - break; - } - } - } - } + CLEAR_FIELD(ccline); } -/// Translate a history type number to the associated character -int hist_type2char(int type) - FUNC_ATTR_CONST +/// Check value of 'cedit' and set cedit_key. +/// Returns NULL if value is OK, error message otherwise. +char *check_cedit(void) { - switch (type) { - case HIST_CMD: - return ':'; - case HIST_SEARCH: - return '/'; - case HIST_EXPR: - return '='; - case HIST_INPUT: - return '@'; - case HIST_DEBUG: - return '>'; - default: - abort(); - } - return NUL; -} + int n; -void cmdline_init(void) -{ - memset(&ccline, 0, sizeof(struct cmdline_info)); + if (*p_cedit == NUL) { + cedit_key = -1; + } else { + n = string_to_key((char_u *)p_cedit); + if (vim_isprintc(n)) { + return e_invarg; + } + cedit_key = n; + } + return NULL; } /// Open a window on the current command line and history. Allow editing in @@ -6654,17 +4216,27 @@ static int open_cmdwin(void) ga_clear(&winsizes); return K_IGNORE; } + // Don't let quitting the More prompt make this fail. + got_int = false; + + // Set "cmdwin_type" before any autocommands may mess things up. cmdwin_type = get_cmdline_type(); cmdwin_level = ccline.level; // Create empty command-line buffer. - buf_open_scratch(0, _("[Command Line]")); + if (buf_open_scratch(0, _("[Command Line]")) == FAIL) { + // Some autocommand messed it up? + win_close(curwin, true, false); + ga_clear(&winsizes); + cmdwin_type = 0; + return Ctrl_C; + } // Command-line buffer has bufhidden=wipe, unlike a true "scratch" buffer. - set_option_value("bh", 0L, "wipe", OPT_LOCAL); - curwin->w_p_rl = cmdmsg_rl; - cmdmsg_rl = false; + set_option_value_give_err("bh", 0L, "wipe", OPT_LOCAL); curbuf->b_p_ma = true; curwin->w_p_fen = false; + curwin->w_p_rl = cmdmsg_rl; + cmdmsg_rl = false; // Don't allow switching to another buffer. curbuf->b_ro_locked++; @@ -6678,7 +4250,7 @@ static int open_cmdwin(void) add_map("<Tab>", "<C-X><C-V>", MODE_INSERT, true); add_map("<Tab>", "a<C-X><C-V>", MODE_NORMAL, true); } - set_option_value("ft", 0L, "vim", OPT_LOCAL); + set_option_value_give_err("ft", 0L, "vim", OPT_LOCAL); } curbuf->b_ro_locked--; @@ -6688,18 +4260,18 @@ static int open_cmdwin(void) // Fill the buffer with the history. init_history(); - if (hislen > 0 && histtype != HIST_INVALID) { - i = hisidx[histtype]; + if (get_hislen() > 0 && histtype != HIST_INVALID) { + i = *get_hisidx(histtype); if (i >= 0) { lnum = 0; do { - if (++i == hislen) { + if (++i == get_hislen()) { i = 0; } - if (history[histtype][i].hisstr != NULL) { - ml_append(lnum++, (char *)history[histtype][i].hisstr, (colnr_T)0, false); + if (get_histentry(histtype)[i].hisstr != NULL) { + ml_append(lnum++, (char *)get_histentry(histtype)[i].hisstr, (colnr_T)0, false); } - } while (i != hisidx[histtype]); + } while (i != *get_hisidx(histtype)); } } @@ -6714,7 +4286,7 @@ static int open_cmdwin(void) ccline.redraw_state = kCmdRedrawNone; ui_call_cmdline_hide(ccline.level); } - redraw_later(curwin, SOME_VALID); + redraw_later(curwin, UPD_SOME_VALID); // No Ex mode here! exmode_active = false; @@ -6904,90 +4476,6 @@ char *script_get(exarg_T *const eap, size_t *const lenp) return (char *)ga.ga_data; } -/// Iterate over history items -/// -/// @warning No history-editing functions must be run while iteration is in -/// progress. -/// -/// @param[in] iter Pointer to the last history entry. -/// @param[in] history_type Type of the history (HIST_*). Ignored if iter -/// parameter is not NULL. -/// @param[in] zero If true then zero (but not free) returned items. -/// -/// @warning When using this parameter user is -/// responsible for calling clr_history() -/// itself after iteration is over. If -/// clr_history() is not called behaviour is -/// undefined. No functions that work with -/// history must be called during iteration -/// in this case. -/// @param[out] hist Next history entry. -/// -/// @return Pointer used in next iteration or NULL to indicate that iteration -/// was finished. -const void *hist_iter(const void *const iter, const uint8_t history_type, const bool zero, - histentry_T *const hist) - FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(4) -{ - *hist = (histentry_T) { - .hisstr = NULL - }; - if (hisidx[history_type] == -1) { - return NULL; - } - histentry_T *const hstart = &(history[history_type][0]); - histentry_T *const hlast = ( - &(history[history_type][hisidx[history_type]])); - const histentry_T *const hend = &(history[history_type][hislen - 1]); - histentry_T *hiter; - if (iter == NULL) { - histentry_T *hfirst = hlast; - do { - hfirst++; - if (hfirst > hend) { - hfirst = hstart; - } - if (hfirst->hisstr != NULL) { - break; - } - } while (hfirst != hlast); - hiter = hfirst; - } else { - hiter = (histentry_T *)iter; - } - if (hiter == NULL) { - return NULL; - } - *hist = *hiter; - if (zero) { - memset(hiter, 0, sizeof(*hiter)); - } - if (hiter == hlast) { - return NULL; - } - hiter++; - return (const void *)((hiter > hend) ? hstart : hiter); -} - -/// Get array of history items -/// -/// @param[in] history_type Type of the history to get array for. -/// @param[out] new_hisidx Location where last index in the new array should -/// be saved. -/// @param[out] new_hisnum Location where last history number in the new -/// history should be saved. -/// -/// @return Pointer to the array or NULL. -histentry_T *hist_get_array(const uint8_t history_type, int **const new_hisidx, - int **const new_hisnum) - FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL -{ - init_history(); - *new_hisidx = &(hisidx[history_type]); - *new_hisnum = &(hisnum[history_type]); - return history[history_type]; -} - static void set_search_match(pos_T *t) { // First move cursor to end of match, then to the start. This diff --git a/src/nvim/ex_getln.h b/src/nvim/ex_getln.h index 1cc6faf87c..5e3ad20a31 100644 --- a/src/nvim/ex_getln.h +++ b/src/nvim/ex_getln.h @@ -2,66 +2,77 @@ #define NVIM_EX_GETLN_H #include "nvim/eval/typval.h" -#include "nvim/ex_cmds.h" #include "nvim/ex_cmds_defs.h" -#include "nvim/os/time.h" -#include "nvim/regexp_defs.h" +#include "nvim/types.h" -// Values for nextwild() and ExpandOne(). See ExpandOne() for meaning. -#define WILD_FREE 1 -#define WILD_EXPAND_FREE 2 -#define WILD_EXPAND_KEEP 3 -#define WILD_NEXT 4 -#define WILD_PREV 5 -#define WILD_ALL 6 -#define WILD_LONGEST 7 -#define WILD_ALL_KEEP 8 -#define WILD_CANCEL 9 -#define WILD_APPLY 10 +/// Command-line colors: one chunk +/// +/// Defines a region which has the same highlighting. +typedef struct { + int start; ///< Colored chunk start. + int end; ///< Colored chunk end (exclusive, > start). + int attr; ///< Highlight attr. +} CmdlineColorChunk; -#define WILD_LIST_NOTFOUND 0x01 -#define WILD_HOME_REPLACE 0x02 -#define WILD_USE_NL 0x04 -#define WILD_NO_BEEP 0x08 -#define WILD_ADD_SLASH 0x10 -#define WILD_KEEP_ALL 0x20 -#define WILD_SILENT 0x40 -#define WILD_ESCAPE 0x80 -#define WILD_ICASE 0x100 -#define WILD_ALLLINKS 0x200 -#define WILD_IGNORE_COMPLETESLASH 0x400 -#define WILD_NOERROR 0x800 // sets EW_NOERROR -#define WILD_BUFLASTUSED 0x1000 -#define BUF_DIFF_FILTER 0x2000 +/// Command-line colors +/// +/// Holds data about all colors. +typedef kvec_t(CmdlineColorChunk) CmdlineColors; -// flags used by vim_strsave_fnameescape() -#define VSE_NONE 0 -#define VSE_SHELL 1 ///< escape for a shell command -#define VSE_BUFFER 2 ///< escape for a ":buffer" command +/// Command-line coloring +/// +/// Holds both what are the colors and what have been colored. Latter is used to +/// suppress unnecessary calls to coloring callbacks. +typedef struct { + unsigned prompt_id; ///< ID of the prompt which was colored last. + char *cmdbuff; ///< What exactly was colored last time or NULL. + CmdlineColors colors; ///< Last colors. +} ColoredCmdline; -/// Present history tables +/// Keeps track how much state must be sent to external ui. typedef enum { - HIST_DEFAULT = -2, ///< Default (current) history. - HIST_INVALID = -1, ///< Unknown history. - HIST_CMD = 0, ///< Colon commands. - HIST_SEARCH, ///< Search commands. - HIST_EXPR, ///< Expressions (e.g. from entering = register). - HIST_INPUT, ///< input() lines. - HIST_DEBUG, ///< Debug commands. -} HistoryType; + kCmdRedrawNone, + kCmdRedrawPos, + kCmdRedrawAll, +} CmdRedraw; -/// Number of history tables -#define HIST_COUNT (HIST_DEBUG + 1) +/// Variables shared between getcmdline(), redrawcmdline() and others. +/// These need to be saved when using CTRL-R |, that's why they are in a +/// structure. +typedef struct cmdline_info CmdlineInfo; +struct cmdline_info { + char_u *cmdbuff; ///< pointer to command line buffer + int cmdbufflen; ///< length of cmdbuff + int cmdlen; ///< number of chars in command line + int cmdpos; ///< current cursor position + 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 + 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 + 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. + ColoredCmdline last_colors; ///< Last cmdline colors + int level; ///< current cmdline level + CmdlineInfo *prev_ccline; ///< pointer to saved cmdline state + char special_char; ///< last putcmdline char (used for redraws) + bool special_shift; ///< shift of last putcmdline char + CmdRedraw redraw_state; ///< needed redraw for external cmdline +}; -typedef char *(*CompleteListItemGetter)(expand_T *, int); - -/// History entry definition -typedef struct hist_entry { - int hisnum; ///< Entry identifier number. - char_u *hisstr; ///< Actual entry, separator char after the NUL. - Timestamp timestamp; ///< Time when entry was added. - list_T *additional_elements; ///< Additional entries from ShaDa file. -} histentry_T; +/// flags used by vim_strsave_fnameescape() +enum { + VSE_NONE = 0, + VSE_SHELL = 1, ///< escape for a shell command + VSE_BUFFER = 2, ///< escape for a ":buffer" command +}; #ifdef INCLUDE_GENERATED_DECLARATIONS # include "ex_getln.h.generated.h" diff --git a/src/nvim/ex_session.c b/src/nvim/ex_session.c index 1d670afa6d..e907c45e79 100644 --- a/src/nvim/ex_session.c +++ b/src/nvim/ex_session.c @@ -13,12 +13,12 @@ #include <stdlib.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_cmds2.h" #include "nvim/ex_docmd.h" #include "nvim/ex_getln.h" #include "nvim/ex_session.h" @@ -34,6 +34,7 @@ #include "nvim/os/os.h" #include "nvim/os/time.h" #include "nvim/path.h" +#include "nvim/runtime.h" #include "nvim/vim.h" #include "nvim/window.h" @@ -358,7 +359,7 @@ static int put_view(FILE *fd, win_T *wp, int add_edit, unsigned *flagp, int curr // Then ":help" will re-use both the buffer and the window and set // the options, even when "options" is not in 'sessionoptions'. if (0 < wp->w_tagstackidx && wp->w_tagstackidx <= wp->w_tagstacklen) { - curtag = (char *)wp->w_tagstack[wp->w_tagstackidx - 1].tagname; + curtag = wp->w_tagstack[wp->w_tagstackidx - 1].tagname; } if (put_line(fd, "enew | setl bt=help") == FAIL @@ -955,7 +956,7 @@ void ex_mkrc(exarg_T *eap) } // When using 'viewdir' may have to create the directory. - if (using_vdir && !os_isdir(p_vdir)) { + if (using_vdir && !os_isdir((char *)p_vdir)) { vim_mkdir_emsg((const char *)p_vdir, 0755); } diff --git a/src/nvim/extmark.c b/src/nvim/extmark.c index 1639f72990..8e780f4aaa 100644 --- a/src/nvim/extmark.c +++ b/src/nvim/extmark.c @@ -510,11 +510,11 @@ void extmark_adjust(buf_T *buf, linenr_T line1, linenr_T line2, linenr_T amount, bcount_t old_byte = 0, new_byte = 0; int old_row, new_row; if (amount == MAXLNUM) { - old_row = (int)(line2 - line1 + 1); + old_row = line2 - line1 + 1; // TODO(bfredl): ej kasta? old_byte = (bcount_t)buf->deleted_bytes2; - new_row = (int)(amount_after + old_row); + new_row = amount_after + old_row; } else { // A region is either deleted (amount == MAXLNUM) or // added (line2 == MAXLNUM). The only other case is :move diff --git a/src/nvim/file_search.c b/src/nvim/file_search.c index ebefd1157c..bbc6e53aa8 100644 --- a/src/nvim/file_search.c +++ b/src/nvim/file_search.c @@ -50,6 +50,7 @@ #include <string.h> #include "nvim/ascii.h" +#include "nvim/autocmd.h" #include "nvim/charset.h" #include "nvim/eval.h" #include "nvim/file_search.h" @@ -109,16 +110,15 @@ typedef struct ff_stack { typedef struct ff_visited { struct ff_visited *ffv_next; - /* Visited directories are different if the wildcard string are - * different. So we have to save it. - */ + // Visited directories are different if the wildcard string are + // different. So we have to save it. char_u *ffv_wc_path; + // use FileID for comparison (needed because of links), else use filename. bool file_id_valid; FileID file_id; - /* The memory for this struct is allocated according to the length of - * ffv_fname. - */ + // The memory for this struct is allocated according to the length of + // ffv_fname. char_u ffv_fname[1]; // actually longer } ff_visited_T; @@ -220,8 +220,8 @@ static char_u e_pathtoolong[] = N_("E854: path too long for completion"); /// /// Upward search is only done on the starting dir. /// -/// If 'free_visited' is TRUE the list of already visited files/directories is -/// cleared. Set this to FALSE if you just want to search from another +/// If 'free_visited' is true the list of already visited files/directories is +/// cleared. Set this to false if you just want to search from another /// directory, but want to be sure that no directory from a previous search is /// searched again. This is useful if you search for a file at different places. /// The list of visited files/dirs can also be cleared with the function @@ -268,7 +268,7 @@ void *vim_findfile_init(char_u *path, char_u *filename, char_u *stopdirs, int le ff_clear(search_ctx); // clear visited list if wanted - if (free_visited == TRUE) { + if (free_visited == true) { vim_findfile_free_visited(search_ctx); } else { /* Reuse old visited lists. Get the visited list for the given @@ -301,12 +301,12 @@ void *vim_findfile_init(char_u *path, char_u *filename, char_u *stopdirs, int le if (!vim_isAbsName(rel_fname) && len + 1 < MAXPATHL) { // Make the start dir an absolute path name. STRLCPY(ff_expand_buffer, rel_fname, len + 1); - search_ctx->ffsc_start_dir = (char_u *)FullName_save((char *)ff_expand_buffer, FALSE); + search_ctx->ffsc_start_dir = (char_u *)FullName_save((char *)ff_expand_buffer, false); } else { search_ctx->ffsc_start_dir = vim_strnsave(rel_fname, len); } if (*++path != NUL) { - ++path; + path++; } } else if (*path == NUL || !vim_isAbsName(path)) { #ifdef BACKSLASH_IN_FILENAME @@ -471,7 +471,7 @@ void *vim_findfile_init(char_u *path, char_u *filename, char_u *stopdirs, int le STRCPY(buf, ff_expand_buffer); STRCPY(buf + eb_len, search_ctx->ffsc_fix_path); - if (os_isdir(buf)) { + if (os_isdir((char *)buf)) { STRCAT(ff_expand_buffer, search_ctx->ffsc_fix_path); add_pathsep((char *)ff_expand_buffer); } else { @@ -509,9 +509,7 @@ void *vim_findfile_init(char_u *path, char_u *filename, char_u *stopdirs, int le xfree(buf); } - sptr = ff_create_stack_element(ff_expand_buffer, - search_ctx->ffsc_wc_path, - level, 0); + sptr = ff_create_stack_element(ff_expand_buffer, search_ctx->ffsc_wc_path, level, 0); ff_push(search_ctx, sptr); search_ctx->ffsc_file_to_search = vim_strsave(filename); @@ -583,7 +581,7 @@ char_u *vim_findfile(void *search_ctx_arg) ff_stack_T *stackp = NULL; size_t len; char_u *p; - char_u *suf; + char *suf; ff_search_ctx_T *search_ctx; if (search_ctx_arg == NULL) { @@ -640,11 +638,8 @@ 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, - stackp->ffs_fix_path, - stackp->ffs_wc_path - ) == FAIL) { + && ff_check_visited(&search_ctx->ffsc_dir_visited_list->ffvl_visited_list, + stackp->ffs_fix_path, stackp->ffs_wc_path) == FAIL) { #ifdef FF_VERBOSE if (p_verbose >= 5) { verbose_enter_scroll(); @@ -789,8 +784,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) { @@ -802,7 +796,7 @@ char_u *vim_findfile(void *search_ctx_arg) */ for (int i = stackp->ffs_filearray_cur; i < stackp->ffs_filearray_size; i++) { if (!path_with_url(stackp->ffs_filearray[i]) - && !os_isdir((char_u *)stackp->ffs_filearray[i])) { + && !os_isdir(stackp->ffs_filearray[i])) { continue; // not a directory } // prepare the filename to be checked for existence below @@ -824,7 +818,7 @@ char_u *vim_findfile(void *search_ctx_arg) */ len = STRLEN(file_path); if (search_ctx->ffsc_tagfile) { - suf = (char_u *)""; + suf = ""; } else { suf = curbuf->b_p_sua; } @@ -832,23 +826,17 @@ char_u *vim_findfile(void *search_ctx_arg) // if file exists and we didn't already find it if ((path_with_url((char *)file_path) || (os_path_exists(file_path) - && (search_ctx->ffsc_find_what - == FINDFILE_BOTH - || ((search_ctx->ffsc_find_what - == FINDFILE_DIR) - == os_isdir(file_path))))) + && (search_ctx->ffsc_find_what == FINDFILE_BOTH + || ((search_ctx->ffsc_find_what == FINDFILE_DIR) + == os_isdir((char *)file_path))))) #ifndef FF_VERBOSE && (ff_check_visited(&search_ctx->ffsc_visited_list->ffvl_visited_list, - file_path, - (char_u *)"" - ) == OK) + file_path, (char_u *)"") == OK) #endif ) { #ifdef FF_VERBOSE if (ff_check_visited(&search_ctx->ffsc_visited_list->ffvl_visited_list, - file_path, - (char_u *)"" - ) == FAIL) { + file_path, (char_u *)"") == FAIL) { if (p_verbose >= 5) { verbose_enter_scroll(); smsg("Already: %s", file_path); @@ -891,13 +879,13 @@ char_u *vim_findfile(void *search_ctx_arg) break; } assert(MAXPATHL >= len); - copy_option_part((char **)&suf, (char *)file_path + len, MAXPATHL - len, ","); + copy_option_part(&suf, (char *)file_path + len, MAXPATHL - len, ","); } } } else { // still wildcards left, push the directories for further search for (int i = stackp->ffs_filearray_cur; i < stackp->ffs_filearray_size; i++) { - if (!os_isdir((char_u *)stackp->ffs_filearray[i])) { + if (!os_isdir(stackp->ffs_filearray[i])) { continue; // not a directory } ff_push(search_ctx, @@ -921,7 +909,7 @@ char_u *vim_findfile(void *search_ctx_arg) stackp->ffs_fix_path) == 0) { continue; // don't repush same directory } - if (!os_isdir((char_u *)stackp->ffs_filearray[i])) { + if (!os_isdir(stackp->ffs_filearray[i])) { continue; // not a directory } ff_push(search_ctx, @@ -944,7 +932,7 @@ 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 - search_ctx->ffsc_start_dir), - search_ctx->ffsc_stopdirs_v) == TRUE) { + search_ctx->ffsc_stopdirs_v) == true) { break; } @@ -1288,7 +1276,7 @@ static void ff_clear(ff_search_ctx_T *search_ctx) /// check if the given path is in the stopdirs /// -/// @return TRUE if yes else FALSE +/// @return true if yes else false static int ff_path_in_stoplist(char_u *path, int path_len, char_u **stopdirs_v) { int i = 0; @@ -1300,7 +1288,7 @@ static int ff_path_in_stoplist(char_u *path, int path_len, char_u **stopdirs_v) // if no path consider it as match if (path_len == 0) { - return TRUE; + return true; } for (i = 0; stopdirs_v[i] != NULL; i++) { @@ -1311,7 +1299,7 @@ static int ff_path_in_stoplist(char_u *path, int path_len, char_u **stopdirs_v) */ if (FNAMENCMP(stopdirs_v[i], path, path_len) == 0 && vim_ispathsep(stopdirs_v[i][path_len])) { - return TRUE; + return true; } } else { if (FNAMECMP(stopdirs_v[i], path) == 0) { @@ -1319,13 +1307,13 @@ static int ff_path_in_stoplist(char_u *path, int path_len, char_u **stopdirs_v) } } } - return FALSE; + return false; } /// Find the file name "ptr[len]" in the path. Also finds directory names. /// -/// On the first call set the parameter 'first' to TRUE to initialize -/// the search. For repeating calls to FALSE. +/// On the first call set the parameter 'first' to true to initialize +/// the search. For repeating calls to false. /// /// Repeating calls will return other files called 'ptr[len]' from the path. /// @@ -1354,8 +1342,8 @@ char_u *find_file_in_path(char_u *ptr, size_t len, int options, int first, char_ return find_file_in_path_option(ptr, len, options, first, (*curbuf->b_p_path == NUL ? p_path - : curbuf->b_p_path), - FINDFILE_BOTH, rel_fname, curbuf->b_p_sua); + : (char_u *)curbuf->b_p_path), + FINDFILE_BOTH, rel_fname, (char_u *)curbuf->b_p_sua); } static char_u *ff_file_to_find = NULL; @@ -1386,7 +1374,7 @@ void free_findfile(void) /// @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) { - return find_file_in_path_option(ptr, len, options, TRUE, p_cdpath, + return find_file_in_path_option(ptr, len, options, true, p_cdpath, FINDFILE_DIR, rel_fname, (char_u *)""); } @@ -1401,11 +1389,11 @@ 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) { - static char_u *dir; - static int did_findfile_init = FALSE; + static char *dir; + static int did_findfile_init = false; char_u save_char; char_u *file_name = NULL; - char_u *buf = NULL; + char *buf = NULL; int rel_to_curdir; if (rel_fname != NULL && path_with_url((const char *)rel_fname)) { @@ -1421,14 +1409,14 @@ 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, NameBuff, MAXPATHL, false, true, NULL); + expand_env_esc(ptr, (char_u *)NameBuff, MAXPATHL, false, true, NULL); ptr[len] = save_char; xfree(ff_file_to_find); - ff_file_to_find = vim_strsave(NameBuff); + ff_file_to_find = vim_strsave((char_u *)NameBuff); if (options & FNAME_UNESC) { // Change all "\ " to " ". - for (ptr = 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)); } @@ -1457,7 +1445,7 @@ char_u *find_file_in_path_option(char_u *ptr, size_t len, int options, int first * If this is not a first call, return NULL. We already returned a * filename on the first call. */ - if (first == TRUE) { + if (first == true) { if (path_with_url((char *)ff_file_to_find)) { file_name = vim_strsave(ff_file_to_find); goto theend; @@ -1465,7 +1453,7 @@ char_u *find_file_in_path_option(char_u *ptr, size_t len, int options, int first /* When FNAME_REL flag given first use the directory of the file. * Otherwise or when this fails use the current directory. */ - for (int run = 1; run <= 2; ++run) { + for (int run = 1; run <= 2; run++) { size_t l = STRLEN(ff_file_to_find); if (run == 1 && rel_to_curdir @@ -1482,21 +1470,21 @@ 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 = suffixes; + buf = (char *)suffixes; for (;;) { if ( - (os_path_exists(NameBuff) + (os_path_exists((char_u *)NameBuff) && (find_what == FINDFILE_BOTH || ((find_what == FINDFILE_DIR) == os_isdir(NameBuff))))) { - file_name = vim_strsave(NameBuff); + file_name = vim_strsave((char_u *)NameBuff); goto theend; } if (*buf == NUL) { break; } assert(MAXPATHL >= l); - copy_option_part((char **)&buf, (char *)NameBuff + l, MAXPATHL - l, ","); + copy_option_part(&buf, (char *)NameBuff + l, MAXPATHL - l, ","); } } } @@ -1506,11 +1494,11 @@ char_u *find_file_in_path_option(char_u *ptr, size_t len, int options, int first * When "first" is set, first setup to the start of the option. * Otherwise continue to find the next match. */ - if (first == TRUE) { + if (first == true) { // vim_findfile_free_visited can handle a possible NULL pointer vim_findfile_free_visited(fdip_search_ctx); - dir = path_option; - did_findfile_init = FALSE; + dir = (char *)path_option; + did_findfile_init = false; } for (;;) { @@ -1520,7 +1508,7 @@ char_u *find_file_in_path_option(char_u *ptr, size_t len, int options, int first break; } - did_findfile_init = FALSE; + did_findfile_init = false; } else { char_u *r_ptr; @@ -1536,22 +1524,22 @@ char_u *find_file_in_path_option(char_u *ptr, size_t len, int options, int first // copy next path buf[0] = 0; - copy_option_part((char **)&dir, (char *)buf, MAXPATHL, " ,"); + copy_option_part(&dir, buf, MAXPATHL, " ,"); // get the stopdir string - r_ptr = vim_findfile_stopdir(buf); - fdip_search_ctx = vim_findfile_init(buf, ff_file_to_find, - r_ptr, 100, FALSE, find_what, - fdip_search_ctx, FALSE, rel_fname); + r_ptr = vim_findfile_stopdir((char_u *)buf); + fdip_search_ctx = vim_findfile_init((char_u *)buf, ff_file_to_find, + r_ptr, 100, false, find_what, + fdip_search_ctx, false, rel_fname); if (fdip_search_ctx != NULL) { - did_findfile_init = TRUE; + did_findfile_init = true; } xfree(buf); } } } if (file_name == NULL && (options & FNAME_MESS)) { - if (first == TRUE) { + if (first == true) { if (find_what == FINDFILE_DIR) { semsg(_("E344: Can't find directory \"%s\" in cdpath"), ff_file_to_find); @@ -1653,7 +1641,7 @@ int vim_chdirfile(char *fname, CdCause cause) STRLCPY(dir, fname, MAXPATHL); *path_tail_with_sep(dir) = NUL; - if (os_dirname(NameBuff, sizeof(NameBuff)) != OK) { + if (os_dirname((char_u *)NameBuff, sizeof(NameBuff)) != OK) { NameBuff[0] = NUL; } diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index b98984017b..d67cd36a85 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -18,7 +18,9 @@ #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" @@ -40,6 +42,7 @@ #include "nvim/move.h" #include "nvim/normal.h" #include "nvim/option.h" +#include "nvim/optionstr.h" #include "nvim/os/input.h" #include "nvim/os/os.h" #include "nvim/os/os_defs.h" @@ -48,7 +51,6 @@ #include "nvim/path.h" #include "nvim/quickfix.h" #include "nvim/regexp.h" -#include "nvim/screen.h" #include "nvim/search.h" #include "nvim/sha256.h" #include "nvim/shada.h" @@ -132,7 +134,7 @@ void filemess(buf_T *buf, char_u *name, char_u *s, int attr) // calling filemess(). msg_scroll_save = msg_scroll; if (shortmess(SHM_OVERALL) && !exiting && p_verbose == 0) { - msg_scroll = FALSE; + msg_scroll = false; } if (!msg_scroll) { // wait a bit when overwriting an error msg check_for_delay(false); @@ -141,7 +143,7 @@ void filemess(buf_T *buf, char_u *name, char_u *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, IObuff), attr); + msg_outtrans_attr((char *)msg_may_trunc(false, IObuff), attr); msg_clr_eos(); ui_flush(); msg_scrolled_ign = false; @@ -165,6 +167,7 @@ void filemess(buf_T *buf, char_u *name, char_u *s, int attr) /// READ_STDIN read from stdin instead of a file /// READ_BUFFER read from curbuf instead of a file (converting after reading /// stdin) +/// READ_NOFILE do not read a file, only trigger BufReadCmd /// READ_DUMMY read into a dummy buffer (to check if file contents changed) /// READ_KEEP_UNDO don't clear undo info or read it from a file /// READ_FIFO read from fifo/socket instead of a file @@ -332,12 +335,18 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip, } curbuf->b_op_start = orig_start; + + if (flags & READ_NOFILE) { + // Return NOTDONE instead of FAIL so that BufEnter can be triggered + // and other operations don't fail. + return NOTDONE; + } } if ((shortmess(SHM_OVER) || curbuf->b_help) && p_verbose == 0) { - msg_scroll = FALSE; // overwrite previous file message + msg_scroll = false; // overwrite previous file message } else { - msg_scroll = TRUE; // don't overwrite previous file message + msg_scroll = true; // don't overwrite previous file message } // If the name is too long we might crash further on, quit here. if (fname != NULL && *fname != NUL) { @@ -400,7 +409,7 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip, */ check_readonly = (newfile && (curbuf->b_flags & BF_CHECK_RO)); if (check_readonly && !readonlymode) { - curbuf->b_p_ro = FALSE; + curbuf->b_p_ro = false; } if (newfile && !read_stdin && !read_buffer && !read_fifo) { @@ -516,18 +525,18 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip, * loaded. Help files always get readonly mode */ if ((check_readonly && file_readonly) || curbuf->b_help) { - curbuf->b_p_ro = TRUE; + curbuf->b_p_ro = true; } if (set_options) { // Don't change 'eol' if reading from buffer as it will already be // correctly set when reading stdin. if (!read_buffer) { - curbuf->b_p_eol = TRUE; - curbuf->b_start_eol = TRUE; + curbuf->b_p_eol = true; + curbuf->b_start_eol = true; } - curbuf->b_p_bomb = FALSE; - curbuf->b_start_bomb = FALSE; + curbuf->b_p_bomb = false; + curbuf->b_start_bomb = false; } // Create a swap file now, so that other Vims are warned that we are @@ -581,7 +590,7 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip, return FAIL; } - ++no_wait_return; // don't wait for return yet + no_wait_return++; // don't wait for return yet // Set '[ mark to the line above where the lines go (line 1 if zero). orig_start = curbuf->b_op_start; @@ -631,9 +640,9 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip, } if (aborting()) { // autocmds may abort script processing - --no_wait_return; + no_wait_return--; msg_scroll = msg_save; - curbuf->b_p_ro = TRUE; // must use "w!" now + curbuf->b_p_ro = true; // must use "w!" now return FAIL; } /* @@ -654,7 +663,7 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip, } else { emsg(_("E201: *ReadPre autocommands must not change current buffer")); } - curbuf->b_p_ro = TRUE; // must use "w!" now + curbuf->b_p_ro = true; // must use "w!" now return FAIL; } } @@ -668,7 +677,7 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip, } } - msg_scroll = FALSE; // overwrite the file message + msg_scroll = false; // overwrite the file message /* * Set linecnt now, before the "retry" caused by a wrong guess for @@ -690,7 +699,7 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip, * Decide which 'encoding' to use or use first. */ if (eap != NULL && eap->force_enc != 0) { - fenc = (char *)enc_canonize((char_u *)eap->cmd + eap->force_enc); + fenc = enc_canonize(eap->cmd + eap->force_enc); fenc_alloced = true; keep_dest_enc = true; } else if (curbuf->b_p_bin) { @@ -706,10 +715,10 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip, fenc_alloced = false; } else if (*p_fencs == NUL) { - fenc = (char *)curbuf->b_p_fenc; // use format from buffer + fenc = curbuf->b_p_fenc; // use format from buffer fenc_alloced = false; } else { - fenc_next = (char *)p_fencs; // try items in 'fileencodings' + fenc_next = p_fencs; // try items in 'fileencodings' fenc = (char *)next_fenc(&fenc_next, &fenc_alloced); } @@ -749,8 +758,8 @@ retry: } file_rewind = false; if (set_options) { - curbuf->b_p_bomb = FALSE; - curbuf->b_start_bomb = FALSE; + curbuf->b_p_bomb = false; + curbuf->b_start_bomb = false; } conv_error = 0; } @@ -764,7 +773,7 @@ retry: } else { if (eap != NULL && eap->force_ff != 0) { fileformat = get_fileformat_force(curbuf, eap); - try_unix = try_dos = try_mac = FALSE; + try_unix = try_dos = try_mac = false; } else if (curbuf->b_p_bin) { fileformat = EOL_UNIX; // binary: use Unix format } else if (*p_ffs == @@ -890,7 +899,7 @@ retry: } // Set "can_retry" when it's possible to rewind the file and try with - // another "fenc" value. It's FALSE when no other "fenc" to try, reading + // another "fenc" value. It's false when no other "fenc" to try, reading // stdin or fixed at a specific encoding. can_retry = (*fenc != NUL && !read_stdin && !keep_dest_enc && !read_fifo); @@ -1009,7 +1018,7 @@ retry: // Change NL to NUL to reverse the effect done // below. n = (int)(size - tlen); - for (ni = 0; ni < n; ++ni) { + for (ni = 0; ni < n; ni++) { if (p[ni] == NL) { ptr[tlen++] = NUL; } else { @@ -1136,8 +1145,8 @@ retry: size -= blen; memmove(ptr, ptr + blen, (size_t)size); if (set_options) { - curbuf->b_p_bomb = TRUE; - curbuf->b_start_bomb = TRUE; + curbuf->b_p_bomb = true; + curbuf->b_start_bomb = true; } } @@ -1196,11 +1205,11 @@ retry: } // Deal with a bad byte and continue with the next. - ++fromp; - --from_size; + fromp++; + from_size--; if (bad_char_behavior == BAD_KEEP) { *top++ = *(fromp - 1); - --to_size; + to_size--; } else if (bad_char_behavior != BAD_DROP) { *top++ = (char)bad_char_behavior; to_size--; @@ -1238,7 +1247,7 @@ retry: // Check for a trailing incomplete UTF-8 sequence tail = ptr + size - 1; while (tail > ptr && (*tail & 0xc0) == 0x80) { - --tail; + tail--; } if (tail + utf_byte2len(*tail) <= ptr + size) { tail = NULL; @@ -1556,7 +1565,7 @@ rewind_retry: * Keep it fast! */ if (fileformat == EOL_MAC) { - --ptr; + ptr--; while (++ptr, --size >= 0) { // catch most common case first if ((c = *ptr) != NUL && c != CAR && c != NL) { @@ -1577,20 +1586,20 @@ rewind_retry: if (read_undo_file) { sha256_update(&sha_ctx, (char_u *)line_start, (size_t)len); } - ++lnum; + lnum++; if (--read_count == 0) { error = true; // break loop line_start = ptr; // nothing left to write break; } } else { - --skip_count; + skip_count--; } line_start = ptr + 1; } } } else { - --ptr; + ptr--; while (++ptr, --size >= 0) { if ((c = *ptr) != NUL && c != NL) { // catch most common case continue; @@ -1633,14 +1642,14 @@ rewind_retry: if (read_undo_file) { sha256_update(&sha_ctx, (char_u *)line_start, (size_t)len); } - ++lnum; + lnum++; if (--read_count == 0) { error = true; // break loop line_start = ptr; // nothing left to write break; } } else { - --skip_count; + skip_count--; } line_start = ptr + 1; } @@ -1670,7 +1679,7 @@ failed: && ptr == line_start + 1)) { // remember for when writing if (set_options) { - curbuf->b_p_eol = FALSE; + curbuf->b_p_eol = false; } *ptr = NUL; len = (colnr_T)(ptr - line_start + 1); @@ -1727,7 +1736,7 @@ failed: os_remove(tmpname); // delete converted file xfree(tmpname); } - --no_wait_return; // may wait for return now + no_wait_return--; // may wait for return now /* * In recovery mode everything but autocommands is skipped. @@ -1747,7 +1756,7 @@ failed: linecnt = 0; } if (newfile || read_buffer) { - redraw_curbuf_later(NOT_VALID); + redraw_curbuf_later(UPD_NOT_VALID); // After reading the text into the buffer the diff info needs to // be updated. diff_invalidate(curbuf); @@ -1771,7 +1780,7 @@ failed: if (!(flags & READ_DUMMY)) { filemess(curbuf, (char_u *)sfname, (char_u *)_(e_interr), 0); if (newfile) { - curbuf->b_p_ro = TRUE; // must use "w!" now + curbuf->b_p_ro = true; // must use "w!" now } } msg_scroll = msg_save; @@ -1786,30 +1795,30 @@ failed: #ifdef UNIX if (S_ISFIFO(perm)) { // fifo STRCAT(IObuff, _("[fifo]")); - c = TRUE; + c = true; } if (S_ISSOCK(perm)) { // or socket STRCAT(IObuff, _("[socket]")); - c = TRUE; + c = true; } # ifdef OPEN_CHR_FILES if (S_ISCHR(perm)) { // or character special STRCAT(IObuff, _("[character special]")); - c = TRUE; + c = true; } # endif #endif if (curbuf->b_p_ro) { STRCAT(IObuff, shortmess(SHM_RO) ? _("[RO]") : _("[readonly]")); - c = TRUE; + c = true; } if (read_no_eol_lnum) { msg_add_eol(); - c = TRUE; + c = true; } if (ff_error == EOL_DOS) { STRCAT(IObuff, _("[CR missing]")); - c = TRUE; + c = true; } if (split) { STRCAT(IObuff, _("[long lines split]")); @@ -1817,25 +1826,25 @@ failed: } if (notconverted) { STRCAT(IObuff, _("[NOT converted]")); - c = TRUE; + c = true; } else if (converted) { STRCAT(IObuff, _("[converted]")); - c = TRUE; + c = true; } if (conv_error != 0) { sprintf((char *)IObuff + STRLEN(IObuff), _("[CONVERSION ERROR in line %" PRId64 "]"), (int64_t)conv_error); - c = TRUE; + c = true; } else if (illegal_byte > 0) { sprintf((char *)IObuff + STRLEN(IObuff), _("[ILLEGAL BYTE in line %" PRId64 "]"), (int64_t)illegal_byte); - c = TRUE; + c = true; } else if (error) { STRCAT(IObuff, _("[READ ERRORS]")); - c = TRUE; + c = true; } if (msg_add_fileformat(fileformat)) { - c = TRUE; + c = true; } msg_add_lines(c, (long)linecnt, filesize); @@ -1863,9 +1872,8 @@ failed: // with errors writing the file requires ":w!" if (newfile && (error || conv_error != 0 - || (illegal_byte > 0 && bad_char_behavior != BAD_KEEP) - )) { - curbuf->b_p_ro = TRUE; + || (illegal_byte > 0 && bad_char_behavior != BAD_KEEP))) { + curbuf->b_p_ro = true; } u_clearline(); // cannot use "U" command after adding lines @@ -1946,7 +1954,7 @@ failed: if (!au_did_filetype && *curbuf->b_p_ft != NUL) { // EVENT_FILETYPE was not triggered but the buffer already has a // filetype. Trigger EVENT_FILETYPE using the existing filetype. - apply_autocmds(EVENT_FILETYPE, (char *)curbuf->b_p_ft, curbuf->b_fname, true, curbuf); + apply_autocmds(EVENT_FILETYPE, curbuf->b_p_ft, curbuf->b_fname, true, curbuf); } } else { apply_autocmds_exarg(EVENT_FILEREADPOST, sfname, sfname, @@ -1997,9 +2005,9 @@ static linenr_T readfile_linenr(linenr_T linecnt, char_u *p, char_u *endp) linenr_T lnum; lnum = curbuf->b_ml.ml_line_count - linecnt + 1; - for (s = p; s < endp; ++s) { + for (s = p; s < endp; s++) { if (*s == '\n') { - ++lnum; + lnum++; } } return lnum; @@ -2016,11 +2024,11 @@ void prep_exarg(exarg_T *eap, const buf_T *buf) snprintf(eap->cmd, cmd_len, "e ++enc=%s", buf->b_p_fenc); eap->force_enc = 8; eap->bad_char = buf->b_bad_char; - eap->force_ff = *buf->b_p_ff; + eap->force_ff = (unsigned char)(*buf->b_p_ff); eap->force_bin = buf->b_p_bin ? FORCE_BIN : FORCE_NOBIN; - eap->read_edit = FALSE; - eap->forceit = FALSE; + eap->read_edit = false; + eap->forceit = false; } /// Set default or forced 'fileformat' and 'binary'. @@ -2048,8 +2056,8 @@ void set_file_options(int set_options, exarg_T *eap) void set_forced_fenc(exarg_T *eap) { if (eap->force_enc != 0) { - char_u *fenc = enc_canonize((char_u *)eap->cmd + eap->force_enc); - set_string_option_direct("fenc", -1, (char *)fenc, OPT_FREE|OPT_LOCAL, 0); + char *fenc = enc_canonize(eap->cmd + eap->force_enc); + set_string_option_direct("fenc", -1, fenc, OPT_FREE|OPT_LOCAL, 0); xfree(fenc); } } @@ -2073,12 +2081,12 @@ static char_u *next_fenc(char **pp, bool *alloced) } p = (char_u *)vim_strchr((*pp), ','); if (p == NULL) { - r = enc_canonize((char_u *)(*pp)); + r = (char_u *)enc_canonize(*pp); *pp += STRLEN(*pp); } else { r = vim_strnsave((char_u *)(*pp), (size_t)(p - (char_u *)(*pp))); *pp = (char *)p + 1; - p = enc_canonize(r); + p = (char_u *)enc_canonize((char *)r); xfree(r); r = p; } @@ -2159,9 +2167,9 @@ char *new_file_message(void) /// /// If "forceit" is true, we don't care for errors when attempting backups. /// In case of an error everything possible is done to restore the original -/// file. But when "forceit" is TRUE, we risk losing it. +/// file. But when "forceit" is true, we risk losing it. /// -/// When "reset_changed" is TRUE and "append" == FALSE and "start" == 1 and +/// When "reset_changed" is true and "append" == false and "start" == 1 and /// "end" == curbuf->b_ml.ml_line_count, reset curbuf->b_changed. /// /// This function must NOT use NameBuff (because it's called by autowrite()). @@ -2202,9 +2210,9 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en int bufsize; long perm; // file permissions int retval = OK; - int newfile = false; // TRUE if file doesn't exist yet + int newfile = false; // true if file doesn't exist yet int msg_save = msg_scroll; - int overwriting; // TRUE if writing over original + int overwriting; // true if writing over original int no_eol = false; // no end-of-line written int device = false; // writing to a device int prev_got_int = got_int; @@ -2213,7 +2221,7 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en static char *err_readonly = "is read-only (cannot override: \"W\" in 'cpoptions')"; #if defined(UNIX) - int made_writable = FALSE; // 'w' bit has been set + int made_writable = false; // 'w' bit has been set #endif // writing everything int whole = (start == 1 && end == buf->b_ml.ml_line_count); @@ -2232,7 +2240,7 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en vim_acl_T acl = NULL; /* ACL copied from original file to backup or new file */ #endif - int write_undo_file = FALSE; + int write_undo_file = false; context_sha256_T sha_ctx; unsigned int bkc = get_bkc_value(buf); const pos_T orig_start = buf->b_op_start; @@ -2264,7 +2272,7 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en // must init bw_conv_buf and bw_iconv_fd before jumping to "fail" write_info.bw_conv_buf = NULL; - write_info.bw_conv_error = FALSE; + write_info.bw_conv_error = false; write_info.bw_conv_error_lnum = 0; write_info.bw_restlen = 0; #ifdef HAVE_ICONV @@ -2312,10 +2320,10 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en if (buf->b_ffname != NULL && FNAMECMP(ffname, buf->b_ffname) == 0) { overwriting = true; } else { - overwriting = FALSE; + overwriting = false; } - ++no_wait_return; // don't wait for return yet + no_wait_return++; // don't wait for return yet /* * Set '[ and '] marks to the lines to be written. @@ -2327,12 +2335,12 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en { aco_save_T aco; - int buf_ffname = FALSE; - int buf_sfname = FALSE; - int buf_fname_f = FALSE; - int buf_fname_s = FALSE; - int did_cmd = FALSE; - int nofile_err = FALSE; + int buf_ffname = false; + int buf_sfname = false; + int buf_fname_f = false; + int buf_fname_s = false; + int did_cmd = false; + int nofile_err = false; int empty_memline = (buf->b_ml.ml_mfp == NULL); bufref_T bufref; @@ -2477,7 +2485,7 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en } else { // less lines end -= old_line_count - buf->b_ml.ml_line_count; if (end < start) { - --no_wait_return; + no_wait_return--; msg_scroll = msg_save; emsg(_("E204: Autocommand changed number of lines in unexpected way")); return FAIL; @@ -2510,9 +2518,9 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en } if (shortmess(SHM_OVER) && !exiting) { - msg_scroll = FALSE; // overwrite previous file message + msg_scroll = false; // overwrite previous file message } else { - msg_scroll = TRUE; // don't overwrite previous file message + msg_scroll = true; // don't overwrite previous file message } if (!filtering) { filemess(buf, @@ -2523,7 +2531,7 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en #endif (char_u *)"", 0); // show that we are busy } - msg_scroll = FALSE; // always overwrite the file message now + msg_scroll = false; // always overwrite the file message now buffer = verbose_try_malloc(BUFSIZE); // can't allocate big buffer, use small one (to be able to write when out of @@ -2556,8 +2564,8 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en } /* It's a device of some kind (or a fifo) which we can write to * but for which we can't make a backup. */ - device = TRUE; - newfile = TRUE; + device = true; + newfile = true; perm = -1; } } @@ -2569,8 +2577,8 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en goto fail; } if (c == NODE_WRITABLE) { - device = TRUE; - newfile = TRUE; + device = true; + newfile = true; perm = -1; } else { perm = os_getperm((const char *)fname); @@ -2636,7 +2644,7 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en * abort it. */ prev_got_int = got_int; - got_int = FALSE; + got_int = false; // Mark the buffer as 'being saved' to prevent changed buffer warnings buf->b_saving = true; @@ -2654,7 +2662,7 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en const bool no_prepend_dot = false; if ((bkc & BKC_YES) || append) { // "yes" - backup_copy = TRUE; + backup_copy = true; } else if ((bkc & BKC_AUTO)) { // "auto" int i; @@ -2667,7 +2675,7 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en if (os_fileinfo_hardlinks(&file_info_old) > 1 || !os_fileinfo_link(fname, &file_info) || !os_fileinfo_id_equal(&file_info, &file_info_old)) { - backup_copy = TRUE; + backup_copy = true; } else { /* * Check if we can create a file and set the owner/group to @@ -2687,7 +2695,7 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en fd = os_open((char *)IObuff, O_CREAT|O_WRONLY|O_EXCL|O_NOFOLLOW, (int)perm); if (fd < 0) { // can't write in directory - backup_copy = TRUE; + 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); @@ -2695,7 +2703,7 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en || 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) { - backup_copy = TRUE; + backup_copy = true; } #endif /* Close the file before removing it, on MS-Windows we @@ -2717,7 +2725,7 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en if ((bkc & BKC_BREAKSYMLINK) && file_info_link_ok && !os_fileinfo_id_equal(&file_info, &file_info_old)) { - backup_copy = FALSE; + backup_copy = false; } // Hardlinks. @@ -2725,7 +2733,7 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en && os_fileinfo_hardlinks(&file_info_old) > 1 && (!file_info_link_ok || os_fileinfo_id_equal(&file_info, &file_info_old))) { - backup_copy = FALSE; + backup_copy = false; } #endif } @@ -2734,7 +2742,7 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en if (*p_bex == NUL) { backup_ext = ".bak"; } else { - backup_ext = (char *)p_bex; + backup_ext = p_bex; } if (backup_copy) { @@ -2756,7 +2764,7 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en * For these reasons, the existing writable file must be truncated * and reused. Creation of a backup COPY will be attempted. */ - dirp = (char *)p_bdir; + dirp = p_bdir; while (*dirp) { /* * Isolate one directory name, using an entry in 'bdir'. @@ -2767,7 +2775,7 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en if (trailing_pathseps) { IObuff[dir_len - 2] = NUL; } - if (*dirp == NUL && !os_isdir(IObuff)) { + if (*dirp == NUL && !os_isdir((char *)IObuff)) { int ret; char *failed_dir; if ((ret = os_mkdir_recurse((char *)IObuff, 0755, &failed_dir)) != 0) { @@ -2787,7 +2795,7 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en rootname = (char *)get_file_in_dir((char_u *)fname, IObuff); if (rootname == NULL) { - some_error = TRUE; // out of memory + some_error = true; // out of memory goto nobackup; } @@ -2802,7 +2810,7 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en if (backup == NULL) { xfree(rootname); - some_error = TRUE; // out of memory + some_error = true; // out of memory goto nobackup; } @@ -2887,7 +2895,7 @@ nobackup: if (backup == NULL && errmsg == NULL) { SET_ERRMSG(_("E509: Cannot create backup file (add ! to override)")); } - // Ignore errors when forceit is TRUE. + // Ignore errors when forceit is true. if ((some_error || errmsg != NULL) && !forceit) { retval = FAIL; goto fail; @@ -2917,7 +2925,7 @@ nobackup: * path/fo.o.h.bak Try all directories in 'backupdir', first one * that works is used. */ - dirp = (char *)p_bdir; + dirp = p_bdir; while (*dirp) { /* * Isolate one directory name and make the backup file name. @@ -2928,7 +2936,7 @@ nobackup: if (trailing_pathseps) { IObuff[dir_len - 2] = NUL; } - if (*dirp == NUL && !os_isdir(IObuff)) { + if (*dirp == NUL && !os_isdir((char *)IObuff)) { int ret; char *failed_dir; if ((ret = os_mkdir_recurse((char *)IObuff, 0755, &failed_dir)) != 0) { @@ -3047,10 +3055,10 @@ nobackup: // Check for forced 'fileencoding' from "++opt=val" argument. if (eap != NULL && eap->force_enc != 0) { fenc = eap->cmd + eap->force_enc; - fenc = (char *)enc_canonize((char_u *)fenc); + fenc = enc_canonize(fenc); fenc_tofree = fenc; } else { - fenc = (char *)buf->b_p_fenc; + fenc = buf->b_p_fenc; } // Check if the file needs to be converted. @@ -3087,8 +3095,8 @@ nobackup: if (!write_info.bw_conv_buf) { end = 0; } - write_info.bw_first = TRUE; - } else + write_info.bw_first = true; + } else { #endif /* @@ -3104,6 +3112,11 @@ nobackup: } } } + +#ifdef HAVE_ICONV +} +#endif + if (converted && wb_flags == 0 #ifdef HAVE_ICONV && write_info.bw_iconv_fd == (iconv_t)-1 @@ -3113,7 +3126,7 @@ nobackup: SET_ERRMSG(_("E213: Cannot convert (add ! to write without conversion)")); goto restore_backup; } - notconverted = TRUE; + notconverted = true; } // If conversion is taking place, we may first pretend to write and check @@ -3135,12 +3148,12 @@ nobackup: } else { // Open the file "wfname" for writing. // We may try to open the file twice: If we can't write to the file - // and forceit is TRUE we delete the existing file and try to + // and forceit is true we delete the existing file and try to // create a new one. If this still fails we may have lost the // original file! (this may happen when the user reached his // quotum for number of files). // Appending will fail if the file does not exist and forceit is - // FALSE. + // false. while ((fd = os_open(wfname, O_WRONLY | (append ? @@ -3364,7 +3377,7 @@ restore_backup: // been written to disk and we don't lose it. // For a device do try the fsync() but don't complain if it does not work // (could be a pipe). - // If the 'fsync' option is FALSE, don't fsync(). Useful for laptops. + // If the 'fsync' option is false, don't fsync(). Useful for laptops. int error; if (p_fs && (error = os_fsync(fd)) != 0 && !device // fsync not supported on this storage. @@ -3483,7 +3496,7 @@ restore_backup: } lnum -= start; // compute number of written lines - --no_wait_return; // may wait for return now + no_wait_return--; // may wait for return now #if !defined(UNIX) fname = sfname; // use shortname now, for the messages @@ -3493,32 +3506,32 @@ restore_backup: c = false; if (write_info.bw_conv_error) { STRCAT(IObuff, _(" CONVERSION ERROR")); - c = TRUE; + c = true; if (write_info.bw_conv_error_lnum != 0) { vim_snprintf_add((char *)IObuff, IOSIZE, _(" in line %" PRId64 ";"), (int64_t)write_info.bw_conv_error_lnum); } } else if (notconverted) { STRCAT(IObuff, _("[NOT converted]")); - c = TRUE; + c = true; } else if (converted) { STRCAT(IObuff, _("[converted]")); - c = TRUE; + c = true; } if (device) { STRCAT(IObuff, _("[Device]")); - c = TRUE; + c = true; } else if (newfile) { STRCAT(IObuff, new_file_message()); c = true; } if (no_eol) { msg_add_eol(); - c = TRUE; + c = true; } // may add [unix/dos/mac] if (msg_add_fileformat(fileformat)) { - c = TRUE; + c = true; } msg_add_lines(c, (long)lnum, nchars); // add line/char count if (!shortmess(SHM_WRITE)) { @@ -3566,7 +3579,7 @@ restore_backup: * the backup file our 'original' file. */ if (*p_pm && dobackup) { - char *const org = modname(fname, (char *)p_pm, false); + char *const org = modname(fname, p_pm, false); if (backup != NULL) { /* @@ -3622,7 +3635,7 @@ restore_backup: * Finish up. We get here either after failure or success. */ fail: - --no_wait_return; // may wait for return now + no_wait_return--; // may wait for return now nofail: // Done saving, we accept changed buffer warnings again @@ -3709,23 +3722,23 @@ nofail: if (append) { apply_autocmds_exarg(EVENT_FILEAPPENDPOST, fname, fname, - FALSE, curbuf, eap); + false, curbuf, eap); } else if (filtering) { apply_autocmds_exarg(EVENT_FILTERWRITEPOST, NULL, fname, - FALSE, curbuf, eap); + false, curbuf, eap); } else if (reset_changed && whole) { apply_autocmds_exarg(EVENT_BUFWRITEPOST, fname, fname, - FALSE, curbuf, eap); + false, curbuf, eap); } else { apply_autocmds_exarg(EVENT_FILEWRITEPOST, fname, fname, - FALSE, curbuf, eap); + false, curbuf, eap); } // restore curwin/curbuf and a few other things aucmd_restbuf(&aco); if (aborting()) { // autocmds may abort script processing - retval = FALSE; + retval = false; } } @@ -3992,11 +4005,11 @@ static int buf_write_bytes(struct bw_info *ip) } if (ucs2bytes(c, &p, flags) && !ip->bw_conv_error) { - ip->bw_conv_error = TRUE; + ip->bw_conv_error = true; ip->bw_conv_error_lnum = ip->bw_start_lnum; } if (c == NL) { - ++ip->bw_start_lnum; + ip->bw_start_lnum++; } } if (flags & FIO_LATIN1) { @@ -4047,7 +4060,7 @@ static int buf_write_bytes(struct bw_info *ip) to = (char *)ip->bw_conv_buf; tolen = save_len; } - ip->bw_first = FALSE; + ip->bw_first = false; } /* @@ -4056,7 +4069,7 @@ static int buf_write_bytes(struct bw_info *ip) if ((iconv(ip->bw_iconv_fd, (void *)&from, &fromlen, &to, &tolen) == (size_t)-1 && ICONV_ERRNO != ICONV_EINVAL) || fromlen > CONV_RESTLEN) { - ip->bw_conv_error = TRUE; + ip->bw_conv_error = true; return FAIL; } @@ -4161,12 +4174,12 @@ static bool need_conversion(const char_u *fenc) int fenc_flags; if (*fenc == NUL || STRCMP(p_enc, fenc) == 0) { - same_encoding = TRUE; + same_encoding = true; fenc_flags = 0; } else { // Ignore difference between "ansi" and "latin1", "ucs-4" and // "ucs-4be", etc. - enc_flags = get_fio_flags(p_enc); + enc_flags = get_fio_flags((char_u *)p_enc); fenc_flags = get_fio_flags(fenc); same_encoding = (enc_flags != 0 && fenc_flags == enc_flags); } @@ -4190,7 +4203,7 @@ static int get_fio_flags(const char_u *name) int prop; if (*name == NUL) { - name = p_enc; + name = (char_u *)p_enc; } prop = enc_canon_props(name); if (prop & ENC_UNICODE) { @@ -4294,10 +4307,10 @@ static int make_bom(char_u *buf, char_u *name) /// Shorten filename of a buffer. /// -/// @param force when TRUE: Use full path from now on for files currently being +/// @param force when true: Use full path from now on for files currently being /// edited, both for file name and swap file name. Try to shorten the file /// names a bit, if safe to do so. -/// when FALSE: Only try to shorten absolute file names. +/// when false: Only try to shorten absolute file names. /// /// For buffers that have buftype "nofile" or "scratch": never change the file /// name. @@ -4384,7 +4397,7 @@ char *modname(const char *fname, const char *ext, bool prepend_dot) } add_pathsep(retval); fnamelen = strlen(retval); - prepend_dot = FALSE; // nothing to prepend a dot to + prepend_dot = false; // nothing to prepend a dot to } else { fnamelen = strlen(fname); retval = xmalloc(fnamelen + extlen + 3); @@ -4767,7 +4780,7 @@ int vim_rename(const char_u *from, const char_u *to) return 0; } -static int already_warned = FALSE; +static int already_warned = false; /// Check if any not hidden buffer has been changed. /// Postpone the check if there are characters in the stuff buffer, a global @@ -4776,7 +4789,7 @@ static int already_warned = FALSE; /// /// @param focus called for GUI focus event /// -/// @return TRUE if some message was written (screen should be redrawn and cursor positioned). +/// @return true if some message was written (screen should be redrawn and cursor positioned). int check_timestamps(int focus) { int didit = 0; @@ -4784,15 +4797,15 @@ int check_timestamps(int focus) // Don't check timestamps while system() or another low-level function may // cause us to lose and gain focus. if (no_check_timestamps > 0) { - return FALSE; + return false; } // Avoid doing a check twice. The OK/Reload dialog can cause a focus // event and we would keep on checking if the file is steadily growing. // Do check again after typing something. if (focus && did_check_timestamps) { - need_check_timestamps = TRUE; - return FALSE; + need_check_timestamps = true; + return false; } if (!stuff_empty() || global_busy || !typebuf_typed() @@ -4819,8 +4832,8 @@ int check_timestamps(int focus) } } } - --no_wait_return; - need_check_timestamps = FALSE; + no_wait_return--; + need_check_timestamps = false; if (need_wait_return && didit == 2) { // make sure msg isn't overwritten msg_puts("\n"); @@ -4935,7 +4948,7 @@ int buf_check_timestamp(buf_T *buf) buf_store_file_info(buf, &file_info); } - if (os_isdir((char_u *)buf->b_fname)) { + if (os_isdir(buf->b_fname)) { // Don't do anything for a directory. Might contain the file explorer. } else if ((buf->b_p_ar >= 0 ? buf->b_p_ar : p_ar) && !bufIsChanged(buf) && file_info_ok) { @@ -5073,7 +5086,7 @@ int buf_check_timestamp(buf_T *buf) redraw_cmdline = false; } } - already_warned = TRUE; + already_warned = true; } xfree(path); @@ -5122,7 +5135,7 @@ void buf_reload(buf_T *buf, int orig_mode, bool reload_options) // file, not reset the syntax highlighting, clear marks, diff status, etc. // Force the fileformat and encoding to be the same. if (reload_options) { - memset(&ea, 0, sizeof(ea)); + CLEAR_FIELD(ea); } else { prep_exarg(&ea, buf); } @@ -5304,8 +5317,8 @@ static void vim_mktempdir(void) mode_t umask_save = umask(0077); for (size_t i = 0; i < ARRAY_SIZE(temp_dirs); i++) { // Expand environment variables, leave room for "/tmp/nvim.<user>/XXXXXX/999999999". - expand_env((char_u *)temp_dirs[i], (char_u *)tmp, TEMP_FILE_PATH_MAXLEN - 64); - if (!os_isdir((char_u *)tmp)) { + expand_env((char *)temp_dirs[i], tmp, TEMP_FILE_PATH_MAXLEN - 64); + if (!os_isdir(tmp)) { continue; } @@ -5315,7 +5328,7 @@ static void vim_mktempdir(void) xstrlcat(tmp, user, sizeof(tmp)); (void)os_mkdir(tmp, 0700); // Always create, to avoid a race. bool owned = os_file_owned(tmp); - bool isdir = os_isdir((char_u *)tmp); + bool isdir = os_isdir(tmp); #ifdef UNIX int perm = os_getperm(tmp); // XDG_RUNTIME_DIR must be owned by the user, mode 0700. bool valid = isdir && owned && 0700 == (perm & 0777); @@ -5580,14 +5593,14 @@ bool match_file_list(char_u *list, char_u *sfname, char_u *ffname) char_u *regpat; char allow_dirs; bool match; - char_u *p; + char *p; tail = (char_u *)path_tail((char *)sfname); // try all patterns in 'wildignore' - p = list; + p = (char *)list; while (*p) { - copy_option_part((char **)&p, (char *)buf, ARRAY_SIZE(buf), ","); + copy_option_part(&p, (char *)buf, ARRAY_SIZE(buf), ","); regpat = (char_u *)file_pat_to_reg_pat((char *)buf, NULL, &allow_dirs, false); if (regpat == NULL) { break; @@ -5604,8 +5617,8 @@ bool match_file_list(char_u *list, char_u *sfname, char_u *ffname) /// Convert the given pattern "pat" which has shell style wildcards in it, into /// a regular expression, and return the result in allocated memory. If there -/// is a directory path separator to be matched, then TRUE is put in -/// allow_dirs, otherwise FALSE is put there -- webb. +/// is a directory path separator to be matched, then true is put in +/// allow_dirs, otherwise false is put there -- webb. /// Handle backslashes before special characters, like "\*" and "\ ". /// /// @param pat_end first char after pattern or NULL @@ -5623,7 +5636,7 @@ char *file_pat_to_reg_pat(const char *pat, const char *pat_end, char *allow_dirs bool add_dollar = true; if (allow_dirs != NULL) { - *allow_dirs = FALSE; + *allow_dirs = false; } if (pat_end == NULL) { pat_end = pat + STRLEN(pat); @@ -5680,7 +5693,7 @@ char *file_pat_to_reg_pat(const char *pat, const char *pat_end, char *allow_dirs reg_pat[i++] = '.'; reg_pat[i++] = '*'; while (p[1] == '*') { // "**" matches like "*" - ++p; + p++; } break; case '.': @@ -5709,7 +5722,7 @@ char *file_pat_to_reg_pat(const char *pat, const char *pat_end, char *allow_dirs reg_pat[i++] = '/'; reg_pat[i++] = ']'; if (allow_dirs != NULL) { - *allow_dirs = TRUE; + *allow_dirs = true; } break; } @@ -5743,7 +5756,7 @@ char *file_pat_to_reg_pat(const char *pat, const char *pat_end, char *allow_dirs && (!no_bslash || *p != '\\') #endif ) { - *allow_dirs = TRUE; + *allow_dirs = true; } reg_pat[i++] = '\\'; reg_pat[i++] = *p; @@ -5756,7 +5769,7 @@ char *file_pat_to_reg_pat(const char *pat, const char *pat_end, char *allow_dirs reg_pat[i++] = '/'; reg_pat[i++] = ']'; if (allow_dirs != NULL) { - *allow_dirs = TRUE; + *allow_dirs = true; } break; #endif @@ -5768,7 +5781,7 @@ char *file_pat_to_reg_pat(const char *pat, const char *pat_end, char *allow_dirs case '}': reg_pat[i++] = '\\'; reg_pat[i++] = ')'; - --nested; + nested--; break; case ',': if (nested) { diff --git a/src/nvim/fileio.h b/src/nvim/fileio.h index 62d8b6142e..ae3c51f1bc 100644 --- a/src/nvim/fileio.h +++ b/src/nvim/fileio.h @@ -1,7 +1,6 @@ #ifndef NVIM_FILEIO_H #define NVIM_FILEIO_H -#include "nvim/autocmd.h" #include "nvim/buffer_defs.h" #include "nvim/eval/typval.h" #include "nvim/garray.h" @@ -16,6 +15,7 @@ #define READ_KEEP_UNDO 0x20 // keep undo info #define READ_FIFO 0x40 // read from fifo or socket #define READ_NOWINENTER 0x80 // do not trigger BufWinEnter +#define READ_NOFILE 0x100 // do not read a file, do trigger BufReadCmd #define READ_STRING(x, y) (char_u *)read_string((x), (size_t)(y)) diff --git a/src/nvim/fold.c b/src/nvim/fold.c index 8f26e03a94..b4ddb3ec08 100644 --- a/src/nvim/fold.c +++ b/src/nvim/fold.c @@ -14,6 +14,7 @@ #include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/diff.h" +#include "nvim/drawscreen.h" #include "nvim/eval.h" #include "nvim/ex_docmd.h" #include "nvim/ex_session.h" @@ -31,7 +32,7 @@ #include "nvim/option.h" #include "nvim/os/input.h" #include "nvim/plines.h" -#include "nvim/screen.h" +#include "nvim/search.h" #include "nvim/strings.h" #include "nvim/syntax.h" #include "nvim/undo.h" @@ -247,7 +248,7 @@ bool hasFoldingWin(win_T *const win, const linenr_T lnum, linenr_T *const firstp // foldLevel() {{{2 /// @return fold level at line number "lnum" in the current window. -int foldLevel(linenr_T lnum) +static int foldLevel(linenr_T lnum) { // While updating the folds lines between invalid_top and invalid_bot have // an undefined fold level. Otherwise update the folds first. @@ -393,7 +394,7 @@ void opFoldRange(pos_T firstpos, pos_T lastpos, int opening, int recurse, int ha } // Force a redraw to remove the Visual highlighting. if (had_visual) { - redraw_curbuf_later(INVERTED); + redraw_curbuf_later(UPD_INVERTED); } } @@ -720,7 +721,7 @@ void deleteFold(win_T *const wp, const linenr_T start, const linenr_T end, const emsg(_(e_nofold)); // Force a redraw to remove the Visual highlighting. if (had_visual) { - redraw_buf_later(wp->w_buffer, INVERTED); + redraw_buf_later(wp->w_buffer, UPD_INVERTED); } } else { // Deleting markers may make cursor column invalid @@ -756,7 +757,7 @@ void clearFolding(win_T *win) /// The changes in lines from top to bot (inclusive). void foldUpdate(win_T *wp, linenr_T top, linenr_T bot) { - if (disable_fold_update || compl_busy || State & MODE_INSERT) { + if (disable_fold_update || State & MODE_INSERT) { return; } @@ -818,7 +819,7 @@ void foldUpdateAfterInsert(void) void foldUpdateAll(win_T *win) { win->w_foldinvalid = true; - redraw_later(win, NOT_VALID); + redraw_later(win, UPD_NOT_VALID); } // foldMoveTo() {{{2 @@ -862,7 +863,7 @@ int foldMoveTo(const bool updown, const int dir, const long count) if (fp - (fold_T *)gap->ga_data >= gap->ga_len) { break; } - --fp; + fp--; } else { if (fp == (fold_T *)gap->ga_data) { break; @@ -1555,7 +1556,7 @@ static void foldCreateMarkers(win_T *wp, pos_T start, pos_T end) } parseMarker(wp); - foldAddMarker(buf, start, wp->w_p_fmr, foldstartmarkerlen); + foldAddMarker(buf, start, (char_u *)wp->w_p_fmr, foldstartmarkerlen); foldAddMarker(buf, end, foldendmarker, foldendmarkerlen); // Update both changes here, to avoid all folds after the start are @@ -1574,9 +1575,9 @@ static void foldCreateMarkers(win_T *wp, pos_T start, pos_T end) /// Add "marker[markerlen]" in 'commentstring' to position `pos`. static void foldAddMarker(buf_T *buf, pos_T pos, const char_u *marker, size_t markerlen) { - char_u *cms = buf->b_p_cms; + char_u *cms = (char_u *)buf->b_p_cms; char_u *newline; - char_u *p = (char_u *)strstr((char *)buf->b_p_cms, "%s"); + char_u *p = (char_u *)strstr(buf->b_p_cms, "%s"); bool line_is_comment = false; linenr_T lnum = pos.lnum; @@ -1620,7 +1621,7 @@ static void deleteFoldMarkers(win_T *wp, fold_T *fp, int recursive, linenr_T lnu lnum_off + fp->fd_top); } } - foldDelMarker(wp->w_buffer, fp->fd_top + lnum_off, wp->w_p_fmr, + foldDelMarker(wp->w_buffer, fp->fd_top + lnum_off, (char_u *)wp->w_p_fmr, foldstartmarkerlen); foldDelMarker(wp->w_buffer, fp->fd_top + lnum_off + fp->fd_len - 1, foldendmarker, foldendmarkerlen); @@ -1638,7 +1639,7 @@ static void foldDelMarker(buf_T *buf, linenr_T lnum, char_u *marker, size_t mark return; } - char_u *cms = buf->b_p_cms; + char_u *cms = (char_u *)buf->b_p_cms; char_u *line = ml_get_buf(buf, lnum, false); for (char_u *p = line; *p != NUL; p++) { if (STRNCMP(p, marker, markerlen) != 0) { @@ -1728,7 +1729,7 @@ char_u *get_foldtext(win_T *wp, linenr_T lnum, linenr_T lnume, foldinfo_T foldin emsg_silent++; // handle exceptions, but don't display errors text = - (char_u *)eval_to_string_safe((char *)wp->w_p_fdt, NULL, + (char_u *)eval_to_string_safe(wp->w_p_fdt, NULL, was_set_insecurely(wp, "foldtext", OPT_LOCAL)); emsg_silent--; @@ -1786,13 +1787,13 @@ char_u *get_foldtext(win_T *wp, linenr_T lnum, linenr_T lnume, foldinfo_T foldin // foldtext_cleanup() {{{2 /// Remove 'foldmarker' and 'commentstring' from "str" (in-place). -void foldtext_cleanup(char_u *str) +static void foldtext_cleanup(char_u *str) { // Ignore leading and trailing white space in 'commentstring'. - char_u *cms_start = (char_u *)skipwhite((char *)curbuf->b_p_cms); + char_u *cms_start = (char_u *)skipwhite(curbuf->b_p_cms); size_t cms_slen = STRLEN(cms_start); while (cms_slen > 0 && ascii_iswhite(cms_start[cms_slen - 1])) { - --cms_slen; + cms_slen--; } // locate "%s" in 'commentstring', use the part before and after it. @@ -1804,7 +1805,7 @@ void foldtext_cleanup(char_u *str) // exclude white space before "%s" while (cms_slen > 0 && ascii_iswhite(cms_start[cms_slen - 1])) { - --cms_slen; + cms_slen--; } // skip "%s" and white space after it @@ -2853,7 +2854,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((char *)flp->wp->w_p_fdi, *s) != NULL) { + if (*s == NUL || vim_strchr(flp->wp->w_p_fdi, *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; @@ -2906,7 +2907,7 @@ static void foldlevelExpr(fline_T *flp) const bool save_keytyped = KeyTyped; int c; - const int n = eval_foldexpr((char *)flp->wp->w_p_fde, &c); + const int n = eval_foldexpr(flp->wp->w_p_fde, &c); KeyTyped = save_keytyped; switch (c) { @@ -2984,8 +2985,8 @@ static void foldlevelExpr(fline_T *flp) /// Relies on the option value to have been checked for correctness already. static void parseMarker(win_T *wp) { - foldendmarker = (char_u *)vim_strchr((char *)wp->w_p_fmr, ','); - foldstartmarkerlen = (size_t)(foldendmarker++ - wp->w_p_fmr); + foldendmarker = (char_u *)vim_strchr(wp->w_p_fmr, ','); + foldstartmarkerlen = (size_t)(foldendmarker++ - (char_u *)wp->w_p_fmr); foldendmarkerlen = STRLEN(foldendmarker); } @@ -3002,7 +3003,7 @@ static void foldlevelMarker(fline_T *flp) int start_lvl = flp->lvl; // cache a few values for speed - char_u *startmarker = flp->wp->w_p_fmr; + char_u *startmarker = (char_u *)flp->wp->w_p_fmr; int cstart = *startmarker; startmarker++; int cend = *foldendmarker; @@ -3190,3 +3191,119 @@ static int put_fold_open_close(FILE *fd, fold_T *fp, linenr_T off) } // }}}1 + +/// "foldclosed()" and "foldclosedend()" functions +static void foldclosed_both(typval_T *argvars, typval_T *rettv, int end) +{ + const linenr_T lnum = tv_get_lnum(argvars); + if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) { + linenr_T first; + linenr_T last; + if (hasFoldingWin(curwin, lnum, &first, &last, false, NULL)) { + if (end) { + rettv->vval.v_number = (varnumber_T)last; + } else { + rettv->vval.v_number = (varnumber_T)first; + } + return; + } + } + rettv->vval.v_number = -1; +} + +/// "foldclosed()" function +void f_foldclosed(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + foldclosed_both(argvars, rettv, false); +} + +/// "foldclosedend()" function +void f_foldclosedend(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + foldclosed_both(argvars, rettv, true); +} + +/// "foldlevel()" function +void f_foldlevel(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + const linenr_T lnum = tv_get_lnum(argvars); + if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) { + rettv->vval.v_number = foldLevel(lnum); + } +} + +/// "foldtext()" function +void f_foldtext(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + + linenr_T foldstart = (linenr_T)get_vim_var_nr(VV_FOLDSTART); + linenr_T foldend = (linenr_T)get_vim_var_nr(VV_FOLDEND); + char_u *dashes = (char_u *)get_vim_var_str(VV_FOLDDASHES); + if (foldstart > 0 && foldend <= curbuf->b_ml.ml_line_count) { + // Find first non-empty line in the fold. + linenr_T lnum; + for (lnum = foldstart; lnum < foldend; lnum++) { + if (!linewhite(lnum)) { + break; + } + } + + // Find interesting text in this line. + char_u *s = (char_u *)skipwhite((char *)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((char *)ml_get(lnum + 1)); + if (*s == '*') { + s = (char_u *)skipwhite((char *)s + 1); + } + } + } + int 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 + char_u *r = xmalloc(len); + snprintf((char *)r, len, txt, dashes, count); + len = STRLEN(r); + STRCAT(r, s); + // remove 'foldmarker' and 'commentstring' + foldtext_cleanup(r + len); + rettv->vval.v_string = (char *)r; + } +} + +/// "foldtextresult(lnum)" function +void f_foldtextresult(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + char_u buf[FOLD_TEXT_LEN]; + static bool entered = false; + + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + if (entered) { + return; // reject recursive use + } + entered = true; + linenr_T lnum = tv_get_lnum(argvars); + // Treat illegal types and illegal string values for {lnum} the same. + if (lnum < 0) { + lnum = 0; + } + + foldinfo_T info = fold_info(curwin, lnum); + if (info.fi_lines > 0) { + char_u *text = get_foldtext(curwin, lnum, lnum + info.fi_lines - 1, info, buf); + if (text == buf) { + text = vim_strsave(text); + } + rettv->vval.v_string = (char *)text; + } + + entered = false; +} diff --git a/src/nvim/garray.c b/src/nvim/garray.c index 7a3c14b1bb..1afabe4e10 100644 --- a/src/nvim/garray.c +++ b/src/nvim/garray.c @@ -131,7 +131,7 @@ void ga_remove_duplicate_strings(garray_T *gap) fnames[j - 1] = fnames[j]; } - --gap->ga_len; + gap->ga_len--; } } } diff --git a/src/nvim/garray.h b/src/nvim/garray.h index 56bd5c9130..0281678925 100644 --- a/src/nvim/garray.h +++ b/src/nvim/garray.h @@ -4,7 +4,7 @@ #include <stddef.h> // for size_t #include "nvim/log.h" -#include "nvim/types.h" // for char_u +#include "nvim/types.h" /// Structure used for growing arrays. /// This is used to store information that only grows, is deleted all at diff --git a/src/nvim/generators/c_grammar.lua b/src/nvim/generators/c_grammar.lua index 70a7be86b5..d872ffd6a9 100644 --- a/src/nvim/generators/c_grammar.lua +++ b/src/nvim/generators/c_grammar.lua @@ -26,6 +26,7 @@ local c_id = ( local c_void = P('void') local c_param_type = ( ((P('Error') * fill * P('*') * fill) * Cc('error')) + + ((P('Arena') * fill * P('*') * fill) * Cc('arena')) + C((P('const ') ^ -1) * (c_id) * (ws ^ 1) * P('*')) + (C(c_id) * (ws ^ 1)) ) diff --git a/src/nvim/generators/gen_api_dispatch.lua b/src/nvim/generators/gen_api_dispatch.lua index b167767f7a..67b8f5f0f5 100644 --- a/src/nvim/generators/gen_api_dispatch.lua +++ b/src/nvim/generators/gen_api_dispatch.lua @@ -17,6 +17,7 @@ local nvimdir = arg[1] package.path = nvimdir .. '/?.lua;' .. package.path _G.vim = loadfile(nvimdir..'/../../runtime/lua/vim/shared.lua')() +_G.vim.inspect = loadfile(nvimdir..'/../../runtime/lua/vim/inspect.lua')() local hashy = require'generators.hashy' @@ -72,6 +73,11 @@ for i = 6, #arg do -- for specifying errors fn.parameters[#fn.parameters] = nil end + if #fn.parameters ~= 0 and fn.parameters[#fn.parameters][1] == 'arena' then + -- return value is allocated in an arena + fn.arena_return = true + fn.parameters[#fn.parameters] = nil + end end end input:close() @@ -210,7 +216,7 @@ for i = 1, #functions do if fn.impl_name == nil and fn.remote then local args = {} - output:write('Object handle_'..fn.name..'(uint64_t channel_id, Array args, Error *error)') + output:write('Object handle_'..fn.name..'(uint64_t channel_id, Array args, Arena* arena, Error *error)') output:write('\n{') output:write('\n#if MIN_LOG_LEVEL <= LOGLVL_DBG') output:write('\n logmsg(LOGLVL_DBG, "RPC: ", NULL, -1, true, "ch %" PRIu64 ": invoke ' @@ -319,6 +325,10 @@ for i = 1, #functions do output:write(call_args) end + if fn.arena_return then + output:write(', arena') + end + if fn.can_fail then -- if the function can fail, also pass a pointer to the local error object if #args > 0 then @@ -355,11 +365,12 @@ local hashorder, hashfun = hashy.hashy_hash("msgpack_rpc_get_handler_for", vim.t return "method_handlers["..idx.."].name" end) -output:write("static const MsgpackRpcRequestHandler method_handlers[] = {\n") -for _, name in ipairs(hashorder) do +output:write("const MsgpackRpcRequestHandler method_handlers[] = {\n") +for n, name in ipairs(hashorder) do local fn = remote_fns[name] + fn.handler_id = n-1 output:write(' { .name = "'..name..'", .fn = handle_'.. (fn.impl_name or fn.name).. - ', .fast = '..tostring(fn.fast)..'},\n') + ', .fast = '..tostring(fn.fast)..', .arena_return = '..tostring(not not fn.arena_return)..'},\n') end output:write("};\n\n") output:write(hashfun) @@ -400,6 +411,8 @@ output:write([[ #include "nvim/api/private/helpers.h" #include "nvim/lua/converter.h" #include "nvim/lua/executor.h" +#include "nvim/memory.h" + ]]) include_headers(output, headers) output:write('\n') @@ -477,6 +490,13 @@ local function process_function(fn) if fn.receives_channel_id then cparams = 'LUA_INTERNAL_CALL, ' .. cparams end + if fn.arena_return then + cparams = cparams .. '&arena, ' + write_shifted_output(output, [[ + Arena arena = ARENA_EMPTY; + ]]) + end + if fn.can_fail then cparams = cparams .. '&err' else @@ -511,15 +531,21 @@ local function process_function(fn) else return_type = fn.return_type end + local free_retval + if fn.arena_return then + free_retval = "arena_mem_free(arena_finish(&arena));" + else + free_retval = "api_free_"..return_type:lower().."(ret);" + end write_shifted_output(output, string.format([[ const %s ret = %s(%s); nlua_push_%s(lstate, ret, true); - api_free_%s(ret); + %s %s %s return 1; - ]], fn.return_type, fn.name, cparams, return_type, return_type:lower(), - free_at_exit_code, err_throw_code)) + ]], fn.return_type, fn.name, cparams, return_type, + 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 93bbaab74c..f9e888c20d 100755 --- a/src/nvim/generators/gen_api_ui_events.lua +++ b/src/nvim/generators/gen_api_ui_events.lua @@ -125,7 +125,7 @@ for i = 1, #events 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]..');\n' + 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..'],'.. @@ -134,7 +134,7 @@ for i = 1, #events do 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]..');\n' + 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..'],'.. @@ -144,7 +144,7 @@ for i = 1, #events do argc = argc+2 elseif param[1] == 'Object' then send = send..' Object *'..copy..' = xmalloc(sizeof(Object));\n' - send = send..' *'..copy..' = copy_object('..param[2]..');\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] diff --git a/src/nvim/generators/gen_eval.lua b/src/nvim/generators/gen_eval.lua index c72249161b..8e6d1f2634 100644 --- a/src/nvim/generators/gen_eval.lua +++ b/src/nvim/generators/gen_eval.lua @@ -28,13 +28,20 @@ local hashy = require'generators.hashy' local hashpipe = io.open(funcsfname, 'wb') local funcs = require('eval').funcs +for _, func in pairs(funcs) do + if func.float_func then + func.func = "float_op_wrapper" + func.data = "{ .float_func = &"..func.float_func.." }" + end +end + local metadata = mpack.unpack(io.open(metadata_file, 'rb'):read("*all")) for _,fun in ipairs(metadata) do if fun.eval then funcs[fun.name] = { args=#fun.parameters, func='api_wrapper', - data='&handle_'..fun.name, + data='{ .api_handler = &method_handlers['..fun.handler_id..'] }' } end end @@ -60,12 +67,12 @@ for _, name in ipairs(neworder) do end local base = def.base or "BASE_NONE" local func = def.func or ('f_' .. name) - local data = def.data or "NULL" + local data = def.data or "{ .nullptr = NULL }" local fast = def.fast and 'true' or 'false' - hashpipe:write((' { "%s", %s, %s, %s, %s, &%s, (FunPtr)%s },\n') + hashpipe:write((' { "%s", %s, %s, %s, %s, &%s, %s },\n') :format(name, args[1], args[2], base, fast, func, data)) end -hashpipe:write(' { NULL, 0, 0, BASE_NONE, false, NULL, NULL },\n') +hashpipe:write(' { NULL, 0, 0, BASE_NONE, false, NULL, { .nullptr = NULL } },\n') hashpipe:write("};\n\n") hashpipe:write(hashfun) hashpipe:close() diff --git a/src/nvim/generators/gen_unicode_tables.lua b/src/nvim/generators/gen_unicode_tables.lua index aa96c97bc1..36553f4649 100644 --- a/src/nvim/generators/gen_unicode_tables.lua +++ b/src/nvim/generators/gen_unicode_tables.lua @@ -12,8 +12,8 @@ -- 2 then interval applies only to first, third, fifth, … character in range. -- Fourth value is number that should be added to the codepoint to yield -- folded/lower/upper codepoint. --- 4. emoji_width and emoji_all tables: sorted lists of non-overlapping closed --- intervals of Emoji characters. emoji_width contains all the characters +-- 4. emoji_wide and emoji_all tables: sorted lists of non-overlapping closed +-- intervals of Emoji characters. emoji_wide contains all the characters -- which don't have ambiguous or double width, and emoji_all has all Emojis. if arg[1] == '--help' then print('Usage:') @@ -288,7 +288,7 @@ local build_emoji_table = function(ut_fp, emojiprops, doublewidth, ambiwidth) end ut_fp:write('};\n') - ut_fp:write('static const struct interval emoji_width[] = {\n') + ut_fp:write('static const struct interval emoji_wide[] = {\n') for _, p in ipairs(emojiwidth) do ut_fp:write(make_range(p[1], p[2])) end diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index 2b2889d4d6..0a9457ef35 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -15,8 +15,12 @@ #include "nvim/buffer_defs.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/event/loop.h" +#include "nvim/ex_cmds.h" #include "nvim/ex_docmd.h" #include "nvim/ex_getln.h" #include "nvim/garray.h" @@ -40,7 +44,6 @@ #include "nvim/os/input.h" #include "nvim/os/os.h" #include "nvim/plines.h" -#include "nvim/screen.h" #include "nvim/state.h" #include "nvim/strings.h" #include "nvim/ui.h" @@ -78,11 +81,9 @@ static buffheader_T readbuf2 = { { NULL, { NUL } }, NULL, 0, 0 }; static int typeahead_char = 0; // typeahead char that's not flushed -/* - * when block_redo is TRUE redo buffer will not be changed - * used by edit() to repeat insertions and 'V' command for redoing - */ -static int block_redo = FALSE; +// when block_redo is true redo buffer will not be changed +// used by edit() to repeat insertions and 'V' command for redoing +static int block_redo = false; static int KeyNoremap = 0; // remapping flags @@ -159,7 +160,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 = bp->b_str; *str;) { + for (const char_u *str = (char_u *)bp->b_str; *str;) { *p2++ = *str++; } } @@ -176,7 +177,7 @@ char_u *get_recorded(void) char_u *p; size_t len; - p = get_buffcont(&recordbuff, TRUE); + p = get_buffcont(&recordbuff, true); free_buff(&recordbuff); /* @@ -204,7 +205,7 @@ char_u *get_recorded(void) /// K_SPECIAL in the returned string is escaped. char_u *get_inserted(void) { - return get_buffcont(&redobuff, FALSE); + return get_buffcont(&redobuff, false); } /// Add string after the current block of the given buffer @@ -316,7 +317,7 @@ static void add_char_buff(buffheader_T *buf, int c) /// Get one byte from the read buffers. Use readbuf1 one first, use readbuf2 /// if that one is empty. -/// If advance == TRUE go to the next char. +/// If advance == true go to the next char. /// No translation is done K_SPECIAL is escaped. static int read_readbuffers(int advance) { @@ -338,7 +339,7 @@ static int read_readbuf(buffheader_T *buf, int advance) } buffblock_T *const curr = buf->bh_first.b_next; - c = curr->b_str[buf->bh_index]; + c = (char_u)curr->b_str[buf->bh_index]; if (advance) { if (curr->b_str[++buf->bh_index] == NUL) { @@ -365,19 +366,15 @@ static void start_stuff(void) } } -/* - * Return TRUE if the stuff buffer is empty. - */ +/// Return true if the stuff buffer is empty. int stuff_empty(void) FUNC_ATTR_PURE { return (readbuf1.bh_first.b_next == NULL && readbuf2.bh_first.b_next == NULL); } -/* - * Return TRUE if readbuf1 is empty. There may still be redo characters in - * redbuf2. - */ +/// Return true if readbuf1 is empty. There may still be redo characters in +/// redbuf2. int readbuf1_empty(void) FUNC_ATTR_PURE { @@ -626,6 +623,34 @@ void stuffnumReadbuff(long n) add_num_buff(&readbuf1, n); } +/// Stuff a string into the typeahead buffer, such that edit() will insert it +/// literally ("literally" true) or interpret is as typed characters. +void stuffescaped(const char *arg, bool literally) +{ + while (*arg != NUL) { + // Stuff a sequence of normal ASCII characters, that's fast. Also + // stuff K_SPECIAL to get the effect of a special key when "literally" + // is true. + const char *const start = arg; + while ((*arg >= ' ' && *arg < DEL) || ((uint8_t)(*arg) == K_SPECIAL + && !literally)) { + arg++; + } + if (arg > start) { + stuffReadbuffLen(start, (arg - start)); + } + + // stuff a single special character + if (*arg != NUL) { + const int c = mb_cptr2char_adv((const char_u **)&arg); + if (literally && ((c < ' ' && c != TAB) || c == DEL)) { + stuffcharReadbuff(Ctrl_V); + } + stuffcharReadbuff(c); + } + } +} + /// Read a character from the redo buffer. Translates K_SPECIAL and /// multibyte characters. /// The redo buffer is left as it is. @@ -646,7 +671,7 @@ static int read_redo(bool init, bool old_redo) if (bp == NULL) { return FAIL; } - p = bp->b_str; + p = (char_u *)bp->b_str; return OK; } if ((c = *p) == NUL) { @@ -667,7 +692,7 @@ static int read_redo(bool init, bool old_redo) } if (*++p == NUL && bp->b_next != NULL) { bp = bp->b_next; - p = bp->b_str; + p = (char_u *)bp->b_str; } buf[i] = (char_u)c; if (i == n - 1) { // last byte of a character @@ -790,7 +815,7 @@ int start_redo_ins(void) void stop_redo_ins(void) { - block_redo = FALSE; + block_redo = false; } /* @@ -929,9 +954,9 @@ int ins_typebuf(char *str, int noremap, int offset, bool nottyped, bool silent) } else { nrm = noremap; } - for (i = 0; i < addlen; ++i) { + for (i = 0; i < addlen; i++) { typebuf.tb_noremap[typebuf.tb_off + i + offset] = - (char_u)((--nrm >= 0) ? val : RM_YES); + (uint8_t)((--nrm >= 0) ? val : RM_YES); } // tb_maplen and tb_silent only remember the length of mapped and/or @@ -967,7 +992,7 @@ int ins_char_typebuf(int c, int modifiers) return (int)len; } -/// Return TRUE if the typeahead buffer was changed (while waiting for a +/// Return true if the typeahead buffer was changed (while waiting for a /// character to arrive). Happens when a message was received from a client or /// from feedkeys(). /// But check in a more generic way to avoid trouble: When "typebuf.tb_buf" @@ -983,10 +1008,8 @@ bool typebuf_changed(int tb_change_cnt) || typebuf_was_filled); } -/* - * Return TRUE if there are no characters in the typeahead buffer that have - * not been typed (result from a mapping or come from ":normal"). - */ +/// Return true if there are no characters in the typeahead buffer that have +/// not been typed (result from a mapping or come from ":normal"). int typebuf_typed(void) FUNC_ATTR_PURE { @@ -1257,7 +1280,7 @@ void restore_typeahead(tasave_T *tp) /// Open a new script file for the ":source!" command. /// /// @param directly when true execute directly -void openscript(char_u *name, bool directly) +void openscript(char *name, bool directly) { if (curscript + 1 == NSCRIPT) { emsg(_(e_nesting)); @@ -1336,7 +1359,7 @@ static void closescript(void) file_free(scriptin[curscript], false); scriptin[curscript] = NULL; if (curscript > 0) { - --curscript; + curscript--; } } @@ -1350,9 +1373,7 @@ void close_all_scripts(void) #endif -/* - * Return TRUE when reading keys from a script file. - */ +/// Return true when reading keys from a script file. int using_script(void) FUNC_ATTR_PURE { @@ -1683,7 +1704,7 @@ int vpeekc_any(void) /* * Call vpeekc() without causing anything to be mapped. - * Return TRUE if a character is available, FALSE otherwise. + * Return true if a character is available, false otherwise. */ int char_avail(void) { @@ -1695,6 +1716,149 @@ int char_avail(void) return retval != NUL; } +/// "getchar()" and "getcharstr()" functions +static void getchar_common(typval_T *argvars, typval_T *rettv) + FUNC_ATTR_NONNULL_ALL +{ + varnumber_T n; + bool error = false; + + no_mapping++; + allow_keys++; + for (;;) { + // Position the cursor. Needed after a message that ends in a space, + // or if event processing caused a redraw. + ui_cursor_goto(msg_row, msg_col); + + if (argvars[0].v_type == VAR_UNKNOWN) { + // getchar(): blocking wait. + // TODO(bfredl): deduplicate shared logic with state_enter ? + if (!char_avail()) { + // flush output before waiting + ui_flush(); + (void)os_inchar(NULL, 0, -1, 0, main_loop.events); + if (!multiqueue_empty(main_loop.events)) { + state_handle_k_event(); + continue; + } + } + n = safe_vgetc(); + } else if (tv_get_number_chk(&argvars[0], &error) == 1) { + // getchar(1): only check if char avail + n = vpeekc_any(); + } else if (error || vpeekc_any() == NUL) { + // illegal argument or getchar(0) and no char avail: return zero + n = 0; + } else { + // getchar(0) and char avail() != NUL: get a character. + // Note that vpeekc_any() returns K_SPECIAL for K_IGNORE. + n = safe_vgetc(); + } + + if (n == K_IGNORE + || n == K_MOUSEMOVE + || n == K_VER_SCROLLBAR + || n == K_HOR_SCROLLBAR) { + continue; + } + break; + } + no_mapping--; + allow_keys--; + + if (!ui_has_messages()) { + // redraw the screen after getchar() + update_screen(UPD_NOT_VALID); + } + + set_vim_var_nr(VV_MOUSE_WIN, 0); + set_vim_var_nr(VV_MOUSE_WINID, 0); + set_vim_var_nr(VV_MOUSE_LNUM, 0); + set_vim_var_nr(VV_MOUSE_COL, 0); + + rettv->vval.v_number = n; + if (n != 0 && (IS_SPECIAL(n) || mod_mask != 0)) { + char_u temp[10]; // modifier: 3, mbyte-char: 6, NUL: 1 + int i = 0; + + // Turn a special key into three bytes, plus modifier. + if (mod_mask != 0) { + temp[i++] = K_SPECIAL; + temp[i++] = KS_MODIFIER; + temp[i++] = (char_u)mod_mask; + } + if (IS_SPECIAL(n)) { + temp[i++] = K_SPECIAL; + temp[i++] = (char_u)K_SECOND(n); + temp[i++] = K_THIRD(n); + } else { + i += utf_char2bytes((int)n, (char *)temp + i); + } + assert(i < 10); + temp[i++] = NUL; + rettv->v_type = VAR_STRING; + rettv->vval.v_string = (char *)vim_strsave(temp); + + if (is_mouse_key((int)n)) { + int row = mouse_row; + int col = mouse_col; + int grid = mouse_grid; + linenr_T lnum; + win_T *wp; + int winnr = 1; + + if (row >= 0 && col >= 0) { + // Find the window at the mouse coordinates and compute the + // text position. + win_T *const win = mouse_find_win(&grid, &row, &col); + if (win == NULL) { + return; + } + (void)mouse_comp_pos(win, &row, &col, &lnum); + for (wp = firstwin; wp != win; wp = wp->w_next) { + winnr++; + } + set_vim_var_nr(VV_MOUSE_WIN, winnr); + set_vim_var_nr(VV_MOUSE_WINID, wp->handle); + set_vim_var_nr(VV_MOUSE_LNUM, lnum); + set_vim_var_nr(VV_MOUSE_COL, col + 1); + } + } + } +} + +/// "getchar()" function +void f_getchar(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + getchar_common(argvars, rettv); +} + +/// "getcharstr()" function +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 (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 +void f_getcharmod(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + rettv->vval.v_number = mod_mask; +} + typedef enum { map_result_fail, // failed, break loop map_result_get, // get a character from typeahead @@ -1735,7 +1899,7 @@ static bool at_ins_compl_key(void) c = p[3] & 0x1f; } return (ctrl_x_mode_not_default() && vim_is_ctrl_x_key(c)) - || ((compl_cont_status & CONT_LOCAL) && (c == Ctrl_N || c == Ctrl_P)); + || (compl_status_local() && (c == Ctrl_N || c == Ctrl_P)); } /// Check if typebuf.tb_buf[] contains a modifier plus key that can be changed @@ -1901,12 +2065,11 @@ static int handle_mapping(int *keylenp, bool *timedout, int *mapdepth) // - Partly match: mlen == typebuf.tb_len keylen = mp->m_keylen; if (mlen == keylen || (mlen == typebuf.tb_len && typebuf.tb_len < keylen)) { - char_u *s; int n; // If only script-local mappings are allowed, check if the // mapping starts with K_SNR. - s = typebuf.tb_noremap + typebuf.tb_off; + 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 @@ -1957,7 +2120,7 @@ static int handle_mapping(int *keylenp, bool *timedout, int *mapdepth) // Check for match with 'pastetoggle' if (*p_pt != NUL && mp == NULL && (State & (MODE_INSERT | MODE_NORMAL))) { - bool match = typebuf_match_len(p_pt, &mlen); + bool match = typebuf_match_len((char_u *)p_pt, &mlen); if (match) { // write chars to script file(s) if (mlen > typebuf.tb_maplen) { @@ -1966,7 +2129,7 @@ static int handle_mapping(int *keylenp, bool *timedout, int *mapdepth) } del_typebuf(mlen, 0); // remove the chars - set_option_value("paste", !p_paste, NULL, 0); + set_option_value_give_err("paste", !p_paste, NULL, 0); if (!(State & MODE_INSERT)) { msg_col = 0; msg_row = Rows - 1; @@ -2102,7 +2265,7 @@ static int handle_mapping(int *keylenp, bool *timedout, int *mapdepth) save_m_keys = vim_strsave(mp->m_keys); if (save_m_luaref == LUA_NOREF) { - save_m_str = vim_strsave(mp->m_str); + save_m_str = vim_strsave((char_u *)mp->m_str); } map_str = eval_map_expr(mp, NUL); @@ -2134,7 +2297,7 @@ static int handle_mapping(int *keylenp, bool *timedout, int *mapdepth) vgetc_busy = save_vgetc_busy; may_garbage_collect = save_may_garbage_collect; } else { - map_str = mp->m_str; + map_str = (char_u *)mp->m_str; } // Insert the 'to' part in the typebuf.tb_buf. @@ -2218,8 +2381,8 @@ void check_end_reg_executing(bool advance) /// /// if "advance" is true (vgetc()): /// Really get the character. -/// KeyTyped is set to TRUE in the case the user typed the key. -/// KeyStuffed is TRUE if the character comes from the stuff buffer. +/// KeyTyped is set to true in the case the user typed the key. +/// KeyStuffed is true if the character comes from the stuff buffer. /// if "advance" is false (vpeekc()): /// Just look whether there is a character available. /// Return NUL if not. @@ -2251,10 +2414,11 @@ static int vgetorpeek(bool advance) return NUL; } - ++vgetc_busy; + vgetc_busy++; if (advance) { - KeyStuffed = FALSE; + KeyStuffed = false; + typebuf_was_empty = false; } init_typebuf(); @@ -2305,7 +2469,7 @@ static int vgetorpeek(bool advance) // flush all input c = inchar(typebuf.tb_buf, typebuf.tb_buflen - 1, 0L); - // If inchar() returns TRUE (script file was active) or we + // If inchar() returns true (script file was active) or we // are inside a mapping, get out of Insert mode. // Otherwise we behave like having gotten a CTRL-C. // As a result typing CTRL-C in insert mode will @@ -2354,7 +2518,7 @@ static int vgetorpeek(bool advance) // write char to script file(s) gotchars(typebuf.tb_buf + typebuf.tb_off, 1); } - KeyNoremap = typebuf.tb_noremap[typebuf.tb_off]; + KeyNoremap = (unsigned char)typebuf.tb_noremap[typebuf.tb_off]; del_typebuf(1, 0); } break; // got character, break the for loop @@ -2383,7 +2547,7 @@ static int vgetorpeek(bool advance) && (State & MODE_INSERT) && (p_timeout || (keylen == KEYLEN_PART_KEY && p_ttimeout)) && (c = inchar(typebuf.tb_buf + typebuf.tb_off + typebuf.tb_len, 3, 25L)) == 0) { - colnr_T col = 0, vcol; + colnr_T col = 0; char_u *ptr; if (mode_displayed) { @@ -2401,22 +2565,27 @@ static int vgetorpeek(bool advance) // We are expecting to truncate the trailing // white-space, so find the last non-white // character -- webb - col = vcol = curwin->w_wcol = 0; + curwin->w_wcol = 0; ptr = get_cursor_line_ptr(); - while (col < curwin->w_cursor.col) { - if (!ascii_iswhite(ptr[col])) { - curwin->w_wcol = vcol; + chartabsize_T cts; + init_chartabsize_arg(&cts, curwin, + curwin->w_cursor.lnum, 0, ptr, ptr); + while ((char_u *)cts.cts_ptr < ptr + curwin->w_cursor.col) { + if (!ascii_iswhite(*cts.cts_ptr)) { + curwin->w_wcol = cts.cts_vcol; } - vcol += lbr_chartabsize(ptr, ptr + col, vcol); - col += utfc_ptr2len((char *)ptr + col); + cts.cts_vcol += lbr_chartabsize(&cts); + cts.cts_ptr += utfc_ptr2len(cts.cts_ptr); } + clear_chartabsize_arg(&cts); + curwin->w_wrow = curwin->w_cline_row + curwin->w_wcol / curwin->w_width_inner; curwin->w_wcol %= curwin->w_width_inner; curwin->w_wcol += curwin_col_off(); col = 0; // no correction needed } else { - --curwin->w_wcol; + curwin->w_wcol--; col = curwin->w_cursor.col - 1; } } else if (curwin->w_p_wrap && curwin->w_wrow) { @@ -2480,6 +2649,11 @@ static int vgetorpeek(bool advance) } tc = c; + // set a flag to indicate this wasn't a normal char + if (advance) { + typebuf_was_empty = true; + } + // return 0 in normal_check() if (pending_exmode_active) { exmode_active = true; @@ -2638,7 +2812,7 @@ static int vgetorpeek(bool advance) gotchars(nop_buf, 3); } - --vgetc_busy; + vgetc_busy--; return c; } @@ -2714,7 +2888,7 @@ int inchar(char_u *buf, int maxlen, long wait_time) if (read_size <= 0) { // Did not get a character from script. // If we got an interrupt, skip all previously typed characters and - // return TRUE if quit reading script file. + // return true if quit reading script file. // Stop reading typeahead when a single CTRL-C was read, // fill_input_buf() returns this when not able to read from stdin. // Don't use buf[] here, closescript() may have freed typebuf.tb_buf[] @@ -2777,7 +2951,7 @@ int fix_input_buffer(char_u *buf, int len) // Two characters are special: NUL and K_SPECIAL. // Replace NUL by K_SPECIAL KS_ZERO KE_FILLER // Replace K_SPECIAL by K_SPECIAL KS_SPECIAL KE_FILLER - for (i = len; --i >= 0; ++p) { + for (i = len; --i >= 0; p++) { if (p[0] == NUL || (p[0] == K_SPECIAL && (i < 2 || p[1] != KS_EXTRA))) { diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 317423ffa0..060db3b3b1 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -6,12 +6,14 @@ #include "nvim/ascii.h" #include "nvim/event/loop.h" -#include "nvim/ex_eval.h" +#include "nvim/ex_cmds_defs.h" +#include "nvim/ex_eval_defs.h" #include "nvim/iconv.h" #include "nvim/macros.h" #include "nvim/mbyte.h" -#include "nvim/menu.h" +#include "nvim/menu_defs.h" #include "nvim/os/os_defs.h" +#include "nvim/runtime.h" #include "nvim/syntax_defs.h" #include "nvim/types.h" @@ -94,9 +96,6 @@ EXTERN struct nvim_stats_s { EXTERN int Rows INIT(= DFLT_ROWS); // nr of rows in the screen EXTERN int Columns INIT(= DFLT_COLS); // nr of columns in the screen -EXTERN NS ns_hl_active INIT(= 0); // current ns that defines highlights -EXTERN bool ns_hl_changed INIT(= false); // highlight need update - // We use 64-bit file functions here, if available. E.g. ftello() returns // off_t instead of long, which helps if long is 32 bit and off_t is 64 bit. // We assume that when fseeko() is available then ftello() is too. @@ -143,6 +142,7 @@ EXTERN int vgetc_char INIT(= 0); EXTERN int cmdline_row; EXTERN bool redraw_cmdline INIT(= false); // cmdline must be redrawn +EXTERN bool redraw_mode INIT(= false); // mode must be redrawn EXTERN bool clear_cmdline INIT(= false); // cmdline must be cleared EXTERN bool mode_displayed INIT(= false); // mode is being displayed EXTERN int cmdline_star INIT(= false); // cmdline is encrypted @@ -159,31 +159,10 @@ EXTERN colnr_T dollar_vcol INIT(= -1); // Variables for Insert mode completion. -// Length in bytes of the text being completed (this is deleted to be replaced -// by the match.) -EXTERN int compl_length INIT(= 0); - -// Set when doing something for completion that may call edit() recursively, -// which is not allowed. Also used to disable folding during completion -EXTERN bool compl_busy INIT(= false); - -// List of flags for method of completion. -EXTERN int compl_cont_status INIT(= 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 -#define CONT_N_ADDS 4 // next ^X<> will add-new or expand-current -#define CONT_S_IPOS 8 // next ^X<> will set initial_pos? - // if so, word-wise-expansion will set SOL -#define CONT_SOL 16 // pattern includes start of line, just for - // word-wise expansion, not set for ^X^L -#define CONT_LOCAL 32 // for ctrl_x_mode 0, ^X^P/^X^N do a local - // expansion, (eg use complete=.) - -EXTERN char_u *edit_submode INIT(= NULL); // msg for CTRL-X submode -EXTERN char_u *edit_submode_pre INIT(= NULL); // prepended to edit_submode -EXTERN char_u *edit_submode_extra INIT(= NULL); // appended to edit_submode -EXTERN hlf_T edit_submode_highl; // highl. method for extra info +EXTERN char *edit_submode INIT(= NULL); // msg for CTRL-X submode +EXTERN char *edit_submode_pre INIT(= NULL); // prepended to edit_submode +EXTERN char *edit_submode_extra INIT(= NULL); // appended to edit_submode +EXTERN hlf_T edit_submode_highl; // highl. method for extra info // state for putting characters in the message area EXTERN int cmdmsg_rl INIT(= false); // cmdline is drawn right to left @@ -198,7 +177,7 @@ EXTERN bool msg_scrolled_ign INIT(= false); // is reset before the screen is redrawn, so we need to keep track of this. EXTERN bool msg_did_scroll INIT(= false); -EXTERN char_u *keep_msg INIT(= NULL); // msg to be shown after redraw +EXTERN char *keep_msg INIT(= NULL); // msg to be shown after redraw EXTERN int keep_msg_attr INIT(= 0); // highlight attr for keep_msg EXTERN bool need_fileinfo INIT(= false); // do fileinfo() after redraw EXTERN int msg_scroll INIT(= false); // msg_start() will scroll @@ -248,9 +227,6 @@ EXTERN int lines_left INIT(= -1); // lines left for listing EXTERN int msg_no_more INIT(= false); // don't use more prompt, truncate // messages -EXTERN char *sourcing_name INIT(= NULL); // name of error message source -EXTERN linenr_T sourcing_lnum INIT(= 0); // line number of the source file - EXTERN int ex_nesting_level INIT(= 0); // nesting level EXTERN int debug_break_level INIT(= -1); // break below this level EXTERN bool debug_did_msg INIT(= false); // did "debug mode" message @@ -265,9 +241,13 @@ EXTERN int do_profiling INIT(= PROF_NONE); ///< PROF_ values /// Exception currently being thrown. Used to pass an exception to a different /// cstack. Also used for discarding an exception before it is caught or made -/// pending. +/// pending. Only valid when did_throw is true. EXTERN except_T *current_exception; +/// An exception is being thrown. Reset when the exception is caught or as +/// long as it is pending in a finally clause. +EXTERN bool did_throw INIT(= false); + /// Set when a throw that cannot be handled in do_cmdline() must be propagated /// to the cstack of the previously called do_cmdline(). EXTERN bool need_rethrow INIT(= false); @@ -296,7 +276,7 @@ EXTERN int force_abort INIT(= false); /// same as the "msg" field of that element, but can be identical to the "msg" /// field of a later list element, when the "emsg_severe" flag was set when the /// emsg() call was made. -EXTERN struct msglist **msg_list INIT(= NULL); +EXTERN msglist_T **msg_list INIT(= NULL); /// When set, don't convert an error to an exception. Used when displaying the /// interrupt message or reporting an exception that is still uncaught at the @@ -348,8 +328,8 @@ EXTERN bool did_source_packages INIT(= false); // provider function call EXTERN struct caller_scope { sctx_T script_ctx; - char *sourcing_name, *autocmd_fname, *autocmd_match; - linenr_T sourcing_lnum; + estack_T es_entry; + char *autocmd_fname, *autocmd_match; int autocmd_bufnr; void *funccalp; } provider_caller_scope; @@ -583,6 +563,8 @@ EXTERN bool can_si INIT(= false); // one indent will be removed. EXTERN bool can_si_back INIT(= false); +EXTERN int old_indent INIT(= 0); ///< for ^^D command in insert mode + // w_cursor before formatting text. EXTERN pos_T saved_cursor INIT(= { 0, 0, 0 }); @@ -689,7 +671,7 @@ EXTERN int swap_exists_action INIT(= SEA_NONE); ///< For dialog when swap file EXTERN bool swap_exists_did_quit INIT(= false); ///< Selected "quit" at the dialog. EXTERN char_u IObuff[IOSIZE]; ///< Buffer for sprintf, I/O, etc. -EXTERN char_u NameBuff[MAXPATHL]; ///< Buffer for expanding file names +EXTERN char NameBuff[MAXPATHL]; ///< Buffer for expanding file names EXTERN char msg_buf[MSG_BUF_LEN]; ///< Small buffer for messages EXTERN char os_buf[ ///< Buffer for the os/ layer #if MAXPATHL > IOSIZE @@ -708,6 +690,10 @@ EXTERN int recoverymode INIT(= false); // Set to true for "-r" option // typeahead buffer EXTERN typebuf_T typebuf INIT(= { NULL, NULL, 0, 0, 0, 0, 0, 0, 0 }); +/// Flag used to indicate that vgetorpeek() returned a char like Esc when the +/// :normal argument was exhausted. +EXTERN bool typebuf_was_empty INIT(= false); + EXTERN int ex_normal_busy INIT(= 0); // recursiveness of ex_normal() EXTERN int ex_normal_lock INIT(= 0); // forbid use of ex_normal() EXTERN int ignore_script INIT(= false); // ignore script input @@ -749,9 +735,9 @@ EXTERN bool need_start_insertmode INIT(= false); ///< start insert mode soon // including the terminating NUL EXTERN char last_mode[MODE_MAX_LENGTH] INIT(= "n"); -EXTERN char_u *last_cmdline INIT(= NULL); // last command line (for ":) -EXTERN char_u *repeat_cmdline INIT(= NULL); // command line for "." -EXTERN char_u *new_last_cmdline INIT(= NULL); // new value for last_cmdline +EXTERN char *last_cmdline INIT(= NULL); // last command line (for ":) +EXTERN char *repeat_cmdline INIT(= NULL); // command line for "." +EXTERN char *new_last_cmdline INIT(= NULL); // new value for last_cmdline EXTERN char *autocmd_fname INIT(= NULL); // fname for <afile> on cmdline EXTERN int autocmd_bufnr INIT(= 0); // fnum for <abuf> on cmdline EXTERN char *autocmd_match INIT(= NULL); // name for <amatch> on cmdline @@ -775,7 +761,7 @@ EXTERN int keep_help_flag INIT(= false); // doing :ta from help file // When a string option is NULL (which only happens in out-of-memory // situations), it is set to empty_option, to avoid having to check for NULL // everywhere. -EXTERN char_u *empty_option INIT(= (char_u *)""); +EXTERN char *empty_option INIT(= ""); EXTERN bool redir_off INIT(= false); // no redirection for a moment EXTERN FILE *redir_fd INIT(= NULL); // message redirection file @@ -815,7 +801,6 @@ EXTERN char *last_chdir_reason INIT(= NULL); EXTERN bool km_stopsel INIT(= false); EXTERN bool km_startsel INIT(= false); -EXTERN int cedit_key INIT(= -1); ///< key value of 'cedit' option 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 @@ -939,7 +924,7 @@ EXTERN char e_patnotf2[] INIT(= N_("E486: Pattern not found: %s")); EXTERN char e_positive[] INIT(= N_("E487: Argument must be positive")); EXTERN char e_prev_dir[] INIT(= N_("E459: Cannot go back to previous directory")); -EXTERN char e_quickfix[] INIT(= N_("E42: No Errors")); +EXTERN char e_no_errors[] INIT(= N_("E42: No Errors")); EXTERN char e_loclist[] INIT(= N_("E776: No location list")); EXTERN char e_re_damg[] INIT(= N_("E43: Damaged match string")); EXTERN char e_re_corr[] INIT(= N_("E44: Corrupted regexp program")); @@ -1020,6 +1005,8 @@ EXTERN char e_resulting_text_too_long[] INIT(= N_("E1240: Resulting text too lon EXTERN char e_line_number_out_of_range[] INIT(= N_("E1247: Line number out of range")); +EXTERN char e_highlight_group_name_invalid_char[] INIT(= N_("E5248: Invalid character in group name")); + EXTERN char e_highlight_group_name_too_long[] INIT(= N_("E1249: Highlight group name too long")); EXTERN char e_undobang_cannot_redo_or_move_branch[] diff --git a/src/nvim/grid.c b/src/nvim/grid.c index 72e85c425d..329adfda12 100644 --- a/src/nvim/grid.c +++ b/src/nvim/grid.c @@ -1,10 +1,19 @@ // 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 +// Most of the routines in this file perform screen (grid) manipulations. The +// given operation is performed physically on the screen. The corresponding +// change is also made to the internal screen image. In this way, the editor +// anticipates the effect of editing changes on the appearance of the screen. +// That way, when we call update_screen() a complete redraw isn't usually +// necessary. Another advantage is that we can keep adding code to anticipate +// screen changes, and in the meantime, everything still works. +// +// The grid_*() functions write to the screen and handle updating grid->lines[]. + #include "nvim/arabic.h" #include "nvim/grid.h" #include "nvim/highlight.h" -#include "nvim/screen.h" #include "nvim/ui.h" #include "nvim/vim.h" @@ -116,7 +125,7 @@ void grid_putchar(ScreenGrid *grid, int c, int row, int col, int attr) char buf[MB_MAXBYTES + 1]; buf[utf_char2bytes(c, buf)] = NUL; - grid_puts(grid, (char_u *)buf, row, col, attr); + grid_puts(grid, buf, row, col, attr); } /// get a single character directly from grid.chars into "bytes[]". @@ -139,7 +148,7 @@ void grid_getbytes(ScreenGrid *grid, int row, int col, char_u *bytes, int *attrp /// attributes 'attr', and update chars[] and attrs[]. /// Note: only outputs within one row, message is truncated at grid boundary! /// Note: if grid, row and/or col is invalid, nothing is done. -void grid_puts(ScreenGrid *grid, char_u *text, int row, int col, int attr) +void grid_puts(ScreenGrid *grid, char *text, int row, int col, int attr) { grid_puts_len(grid, text, -1, row, col, attr); } @@ -178,10 +187,10 @@ void grid_put_schar(ScreenGrid *grid, int row, int col, char_u *schar, int attr) /// like grid_puts(), but output "text[len]". When "len" is -1 output up to /// a NUL. -void grid_puts_len(ScreenGrid *grid, char_u *text, int textlen, int row, int col, int attr) +void grid_puts_len(ScreenGrid *grid, char *text, int textlen, int row, int col, int attr) { size_t off; - char_u *ptr = text; + char *ptr = text; int len = textlen; int c; size_t max_off; @@ -228,13 +237,13 @@ void grid_puts_len(ScreenGrid *grid, char_u *text, int textlen, int row, int col while (col < grid->cols && (len < 0 || (int)(ptr - text) < len) && *ptr != NUL) { - c = *ptr; + c = (unsigned char)(*ptr); // check if this is the first byte of a multibyte mbyte_blen = len > 0 - ? utfc_ptr2len_len(ptr, (int)((text + len) - ptr)) - : utfc_ptr2len((char *)ptr); + ? utfc_ptr2len_len((char_u *)ptr, (int)((text + len) - ptr)) + : utfc_ptr2len(ptr); u8c = len >= 0 - ? utfc_ptr2char_len(ptr, u8cc, (int)((text + len) - ptr)) + ? utfc_ptr2char_len((char_u *)ptr, u8cc, (int)((text + len) - ptr)) : utfc_ptr2char(ptr, u8cc); mbyte_cells = utf_char2cells(u8c); if (p_arshape && !p_tbidi && ARABIC_CHAR(u8c)) { @@ -245,7 +254,8 @@ void grid_puts_len(ScreenGrid *grid, char_u *text, int textlen, int row, int col nc1 = NUL; } else { nc = len >= 0 - ? utfc_ptr2char_len(ptr + mbyte_blen, pcc, (int)((text + len) - ptr - mbyte_blen)) + ? utfc_ptr2char_len((char_u *)ptr + mbyte_blen, pcc, + (int)((text + len) - ptr - mbyte_blen)) : utfc_ptr2char(ptr + mbyte_blen, pcc); nc1 = pcc[0]; } @@ -311,7 +321,7 @@ void grid_puts_len(ScreenGrid *grid, char_u *text, int textlen, int row, int col ptr += mbyte_blen; if (clear_next_cell) { // This only happens at the end, display one space next. - ptr = (char_u *)" "; + ptr = " "; len = -1; } } @@ -384,11 +394,11 @@ void grid_fill(ScreenGrid *grid, int start_row, int end_row, int start_col, int // double wide-char clear out the right half. Only needed in a // terminal. if (start_col > 0 && grid_fix_col(grid, start_col, row) != start_col) { - grid_puts_len(grid, (char_u *)" ", 1, row, start_col - 1, 0); + grid_puts_len(grid, " ", 1, row, start_col - 1, 0); } if (end_col < grid->cols && grid_fix_col(grid, end_col, row) != end_col) { - grid_puts_len(grid, (char_u *)" ", 1, row, end_col, 0); + grid_puts_len(grid, " ", 1, row, end_col, 0); } // if grid was resized (in ext_multigrid mode), the UI has no redraw updates @@ -466,9 +476,9 @@ static int grid_char_needs_redraw(ScreenGrid *grid, size_t off_from, size_t off_ /// "endcol" gives the columns where valid characters are. /// "clear_width" is the width of the window. It's > 0 if the rest of the line /// needs to be cleared, negative otherwise. -/// "rlflag" is TRUE in a rightleft window: -/// When TRUE and "clear_width" > 0, clear columns 0 to "endcol" -/// When FALSE and "clear_width" > 0, clear columns "endcol" to "clear_width" +/// "rlflag" is true in a rightleft window: +/// When true and "clear_width" > 0, clear columns 0 to "endcol" +/// When false and "clear_width" > 0, clear columns "endcol" to "clear_width" /// If "wrap" is true, then hint to the UI that "row" contains a line /// which has wrapped into the next row. void grid_put_linebuf(ScreenGrid *grid, int row, int coloff, int endcol, int clear_width, @@ -780,3 +790,123 @@ void grid_assign_handle(ScreenGrid *grid) grid->handle = ++last_grid_handle; } } + +/// insert lines on the screen and move the existing lines down +/// 'line_count' is the number of lines to be inserted. +/// 'end' is the line after the scrolled part. Normally it is Rows. +/// 'col' is the column from with we start inserting. +// +/// 'row', 'col' and 'end' are relative to the start of the region. +void grid_ins_lines(ScreenGrid *grid, int row, int line_count, int end, int col, int width) +{ + int i; + int j; + unsigned temp; + + int row_off = 0; + grid_adjust(&grid, &row_off, &col); + row += row_off; + end += row_off; + + if (line_count <= 0) { + return; + } + + // Shift line_offset[] line_count down to reflect the inserted lines. + // Clear the inserted lines. + for (i = 0; i < line_count; i++) { + if (width != grid->cols) { + // need to copy part of a line + j = end - 1 - i; + while ((j -= line_count) >= row) { + linecopy(grid, j + line_count, j, col, width); + } + j += line_count; + grid_clear_line(grid, grid->line_offset[j] + (size_t)col, width, false); + grid->line_wraps[j] = false; + } else { + j = end - 1 - i; + temp = (unsigned)grid->line_offset[j]; + while ((j -= line_count) >= row) { + grid->line_offset[j + line_count] = grid->line_offset[j]; + grid->line_wraps[j + line_count] = grid->line_wraps[j]; + } + grid->line_offset[j + line_count] = temp; + grid->line_wraps[j + line_count] = false; + grid_clear_line(grid, temp, grid->cols, false); + } + } + + if (!grid->throttled) { + ui_call_grid_scroll(grid->handle, row, end, col, col + width, -line_count, 0); + } +} + +/// delete lines on the screen and move lines up. +/// 'end' is the line after the scrolled part. Normally it is Rows. +/// When scrolling region used 'off' is the offset from the top for the region. +/// 'row' and 'end' are relative to the start of the region. +void grid_del_lines(ScreenGrid *grid, int row, int line_count, int end, int col, int width) +{ + int j; + int i; + unsigned temp; + + int row_off = 0; + grid_adjust(&grid, &row_off, &col); + row += row_off; + end += row_off; + + if (line_count <= 0) { + return; + } + + // Now shift line_offset[] line_count up to reflect the deleted lines. + // Clear the inserted lines. + for (i = 0; i < line_count; i++) { + if (width != grid->cols) { + // need to copy part of a line + j = row + i; + while ((j += line_count) <= end - 1) { + linecopy(grid, j - line_count, j, col, width); + } + j -= line_count; + grid_clear_line(grid, grid->line_offset[j] + (size_t)col, width, false); + grid->line_wraps[j] = false; + } else { + // whole width, moving the line pointers is faster + j = row + i; + temp = (unsigned)grid->line_offset[j]; + while ((j += line_count) <= end - 1) { + grid->line_offset[j - line_count] = grid->line_offset[j]; + grid->line_wraps[j - line_count] = grid->line_wraps[j]; + } + grid->line_offset[j - line_count] = temp; + grid->line_wraps[j - line_count] = false; + grid_clear_line(grid, temp, grid->cols, false); + } + } + + if (!grid->throttled) { + ui_call_grid_scroll(grid->handle, row, end, col, col + width, line_count, 0); + } +} + +static void linecopy(ScreenGrid *grid, int to, int from, int col, int width) +{ + unsigned off_to = (unsigned)(grid->line_offset[to] + (size_t)col); + unsigned off_from = (unsigned)(grid->line_offset[from] + (size_t)col); + + memmove(grid->chars + off_to, grid->chars + off_from, (size_t)width * sizeof(schar_T)); + memmove(grid->attrs + off_to, grid->attrs + off_from, (size_t)width * sizeof(sattr_T)); +} + +win_T *get_win_by_grid_handle(handle_T handle) +{ + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_grid_alloc.handle == handle) { + return wp; + } + } + return NULL; +} diff --git a/src/nvim/grid.h b/src/nvim/grid.h index c38748940d..6a93bf3d90 100644 --- a/src/nvim/grid.h +++ b/src/nvim/grid.h @@ -6,6 +6,7 @@ #include "nvim/ascii.h" #include "nvim/buffer_defs.h" #include "nvim/grid_defs.h" +#include "nvim/mbyte.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 @@ -18,6 +19,9 @@ EXTERN ScreenGrid default_grid INIT(= SCREEN_GRID_INIT); #define DEFAULT_GRID_HANDLE 1 // handle for the default_grid +/// While resizing the screen this flag is set. +EXTERN bool resizing_screen INIT(= 0); + EXTERN schar_T *linebuf_char INIT(= NULL); EXTERN sattr_T *linebuf_attr INIT(= NULL); diff --git a/src/nvim/hardcopy.c b/src/nvim/hardcopy.c index d7f7b8eb92..602fa8b71d 100644 --- a/src/nvim/hardcopy.c +++ b/src/nvim/hardcopy.c @@ -10,19 +10,16 @@ #include <string.h> #include "nvim/ascii.h" -#include "nvim/vim.h" -#ifdef HAVE_LOCALE_H -# include <locale.h> -#endif #include "nvim/buffer.h" #include "nvim/charset.h" #include "nvim/eval.h" -#include "nvim/ex_cmds2.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" @@ -31,11 +28,13 @@ #include "nvim/os/input.h" #include "nvim/os/os.h" #include "nvim/path.h" -#include "nvim/screen.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 @@ -49,20 +48,20 @@ * * int mch_print_begin(prt_settings_T *settings) * Called to start the print job. - * Return FALSE to abort. + * 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. + * Return false to abort. * * int mch_print_end_page() * Called at the end of each page. - * Return FALSE to abort. + * 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. + * document. Return false to abort. * * void mch_print_end(prt_settings_T *psettings) * Called at normal end of print job. @@ -85,36 +84,33 @@ * * 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). + * 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. + * 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 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, @@ -150,12 +146,12 @@ static int page_count; 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 }, + { "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 }, }; /* @@ -265,7 +261,7 @@ struct prt_resfile_buffer_S { */ char *parse_printoptions(void) { - return parse_list_options(p_popt, printer_opts, OPT_PRINT_NUM_OPTIONS); + return parse_list_options((char_u *)p_popt, printer_opts, OPT_PRINT_NUM_OPTIONS); } /* @@ -274,7 +270,7 @@ char *parse_printoptions(void) */ char *parse_printmbfont(void) { - return parse_list_options(p_pmfn, mbfont_opts, OPT_MBFONT_NUM_OPTIONS); + return parse_list_options((char_u *)p_pmfn, mbfont_opts, OPT_MBFONT_NUM_OPTIONS); } /* @@ -292,8 +288,8 @@ static char *parse_list_options(char_u *option_str, option_table_T *table, size_ char *ret = NULL; char_u *stringp; char_u *colonp; - char_u *commap; - char_u *p; + char *commap; + char *p; size_t idx = 0; // init for GCC int len; @@ -315,14 +311,14 @@ static char *parse_list_options(char_u *option_str, option_table_T *table, size_ ret = N_("E550: Missing colon"); break; } - commap = (char_u *)vim_strchr((char *)stringp, ','); + commap = vim_strchr((char *)stringp, ','); if (commap == NULL) { - commap = option_str + STRLEN(option_str); + commap = (char *)option_str + STRLEN(option_str); } len = (int)(colonp - stringp); - for (idx = 0; idx < table_size; ++idx) { + for (idx = 0; idx < table_size; idx++) { if (STRNICMP(stringp, table[idx].name, len) == 0) { break; } @@ -333,8 +329,8 @@ static char *parse_list_options(char_u *option_str, option_table_T *table, size_ break; } - p = colonp + 1; - table[idx].present = TRUE; + p = (char *)colonp + 1; + table[idx].present = true; if (table[idx].hasnum) { if (!ascii_isdigit(*p)) { @@ -342,15 +338,15 @@ static char *parse_list_options(char_u *option_str, option_table_T *table, size_ break; } - table[idx].number = getdigits_int((char **)&p, false, 0); + table[idx].number = getdigits_int(&p, false, 0); } - table[idx].string = p; + table[idx].string = (char_u *)p; table[idx].strlen = (int)(commap - p); - stringp = commap; + stringp = (char_u *)commap; if (*stringp == ',') { - ++stringp; + stringp++; } } @@ -504,9 +500,7 @@ int prt_header_height(void) return 2; } -/* - * Return TRUE if using a line number for printing. - */ +// Return true if using a line number for printing. int prt_use_number(void) { return printer_opts[OPT_PRINT_NUMBER].present @@ -524,7 +518,7 @@ int prt_get_unit(int idx) static char *(units[4]) = PRT_UNIT_NAMES; if (printer_opts[idx].present) { - for (i = 0; i < 4; ++i) { + for (i = 0; i < 4; i++) { if (STRNICMP(printer_opts[idx].string, units[i], 2) == 0) { u = i; break; @@ -550,7 +544,7 @@ static void prt_header(prt_settings_T *const psettings, const int pagenum, const if (*p_header != NUL) { linenr_T tmp_lnum, tmp_topline, tmp_botline; - int use_sandbox = FALSE; + int use_sandbox = false; /* * Need to (temporarily) set current line number and first/last line @@ -619,7 +613,7 @@ 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, s, Rows - 1, 0, HL_ATTR(HLF_R)); + grid_puts(&default_grid, (char *)s, Rows - 1, 0, HL_ATTR(HLF_R)); ui_flush(); } @@ -632,8 +626,8 @@ void ex_hardcopy(exarg_T *eap) int page_line; int jobsplit; - memset(&settings, 0, sizeof(prt_settings_T)); - settings.has_color = TRUE; + CLEAR_FIELD(settings); + settings.has_color = true; if (*eap->arg == '>') { char *errormsg = NULL; @@ -667,7 +661,7 @@ void ex_hardcopy(exarg_T *eap) settings.modec = 'c'; if (!syntax_present(curwin)) { - settings.do_syntax = FALSE; + 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 = @@ -734,7 +728,7 @@ void ex_hardcopy(exarg_T *eap) prt_pos_T page_prtpos; // print position at page start int side; - memset(&page_prtpos, 0, sizeof(prt_pos_T)); + CLEAR_FIELD(page_prtpos); page_prtpos.file_line = eap->line1; prtpos = page_prtpos; @@ -749,11 +743,9 @@ void ex_hardcopy(exarg_T *eap) /* * 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 (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++) { @@ -763,10 +755,8 @@ void ex_hardcopy(exarg_T *eap) /* * Do front and rear side of a page. */ - for (side = 0; side <= settings.duplex; ++side) { - /* - * Print one page. - */ + for (side = 0; side <= settings.duplex; side++) { + // Print one page. // Check for interrupt character every page. os_breakcheck(); @@ -837,7 +827,7 @@ void ex_hardcopy(exarg_T *eap) } } if (settings.duplex && prtpos.file_line <= eap->line2) { - ++page_count; + page_count++; } // Remember the position where the next page starts. @@ -868,7 +858,7 @@ static colnr_T hardcopy_line(prt_settings_T *psettings, int page_line, prt_pos_T { colnr_T col; char_u *line; - int need_break = FALSE; + int need_break = false; int outputlen; int tab_spaces; int print_pos; @@ -881,7 +871,7 @@ static colnr_T hardcopy_line(prt_settings_T *psettings, int page_line, prt_pos_T if (!ppos->ff && prt_use_number()) { prt_line_number(psettings, page_line, ppos->file_line); } - ppos->ff = FALSE; + ppos->ff = false; } else { // left over from wrap halfway through a tab print_pos = ppos->print_pos; @@ -900,7 +890,7 @@ static colnr_T hardcopy_line(prt_settings_T *psettings, int page_line, prt_pos_T } // syntax highlighting stuff. if (psettings->do_syntax) { - id = syn_get_id(curwin, ppos->file_line, col, 1, NULL, FALSE); + id = syn_get_id(curwin, ppos->file_line, col, 1, NULL, false); if (id > 0) { id = syn_get_final_id(id); } else { @@ -944,7 +934,7 @@ static colnr_T hardcopy_line(prt_settings_T *psettings, int page_line, prt_pos_T && printer_opts[OPT_PRINT_FORMFEED].present && TOLOWER_ASC(printer_opts[OPT_PRINT_FORMFEED].string[0]) == 'y') { - ppos->ff = TRUE; + ppos->ff = true; need_break = 1; } else { need_break = mch_print_text_out(line + col, (size_t)outputlen); @@ -1718,7 +1708,7 @@ static bool prt_open_resource(struct prt_ps_resource_S *resource) semsg(_("E624: Can't open file \"%s\""), resource->filename); return false; } - memset(prt_resfile.buffer, NUL, PRT_FILE_BUFFER_LEN); + 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), @@ -1986,7 +1976,7 @@ void mch_print_cleanup(void) if (prt_do_conv) { convert_setup(&prt_conv, NULL, NULL); - prt_do_conv = FALSE; + prt_do_conv = false; } if (prt_ps_fd != NULL) { fclose(prt_ps_fd); @@ -2118,11 +2108,11 @@ static int prt_match_encoding(char *p_encoding, struct prt_ps_mbfont_S *p_cmap, 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; + return true; } p_mbenc++; } - return FALSE; + return false; } static int prt_match_charset(char *p_charset, struct prt_ps_mbfont_S *p_cmap, @@ -2141,11 +2131,11 @@ static int prt_match_charset(char *p_charset, struct prt_ps_mbfont_S *p_cmap, 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; + return true; } p_mbchar++; } - return FALSE; + return false; } int mch_print_init(prt_settings_T *psettings, char_u *jobname, int forceit) @@ -2157,17 +2147,14 @@ int mch_print_init(prt_settings_T *psettings, char_u *jobname, int forceit) char_u *p; int props; int cmap = 0; - char_u *p_encoding; 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. - */ - p_encoding = enc_skip(p_penc); + // Set up font and encoding. + char_u *p_encoding = (char_u *)enc_skip(p_penc); if (*p_encoding == NUL) { - p_encoding = enc_skip(p_enc); + p_encoding = (char_u *)enc_skip(p_enc); } // Look for a multi-byte font that matches the encoding and character set. @@ -2186,7 +2173,7 @@ int mch_print_init(prt_settings_T *psettings, char_u *jobname, int forceit) p_mbenc_first = p_mbenc; effective_cmap = cmap; } - if (prt_match_charset((char *)p_pmcs, &prt_ps_mbfonts[cmap], &p_mbchar)) { + if (prt_match_charset(p_pmcs, &prt_ps_mbfonts[cmap], &p_mbchar)) { break; } } @@ -2279,7 +2266,7 @@ int mch_print_init(prt_settings_T *psettings, char_u *jobname, int forceit) prt_ps_font = &prt_ps_mb_font; } else { - prt_use_courier = FALSE; + prt_use_courier = false; prt_ps_font = &prt_ps_courier_font; } @@ -2296,7 +2283,7 @@ int mch_print_init(prt_settings_T *psettings, char_u *jobname, int forceit) paper_name = "A4"; paper_strlen = 2; } - for (i = 0; i < (int)PRT_MEDIASIZE_LEN; ++i) { + 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) { @@ -2337,7 +2324,7 @@ int mch_print_init(prt_settings_T *psettings, char_u *jobname, int forceit) * Set up the font size. */ fontsize = PRT_PS_DEFAULT_FONTSIZE; - for (p = p_pfn; (p = (char_u *)vim_strchr((char *)p, ':')) != NULL; p++) { + 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); } @@ -2381,16 +2368,16 @@ int mch_print_init(prt_settings_T *psettings, char_u *jobname, int forceit) * 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; + 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; + prt_duplex = false; psettings->duplex = 0; } else if (STRNICMP(printer_opts[OPT_PRINT_DUPLEX].string, "short", 5) == 0) { - prt_tumble = TRUE; + prt_tumble = true; } } @@ -2601,13 +2588,13 @@ bool mch_print_begin(prt_settings_T *psettings) // that cannot be found then default to "latin1". // Note: VIM specific encoding header is always skipped. if (!prt_out_mbyte) { - p_encoding = (char *)enc_skip(p_penc); + 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 = (char *)enc_skip(p_enc); + 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)) { @@ -2627,9 +2614,9 @@ bool mch_print_begin(prt_settings_T *psettings) // For the moment there are no checks on encoding resource files to // perform } else { - p_encoding = (char *)enc_skip(p_penc); + p_encoding = enc_skip(p_penc); if (*p_encoding == NUL) { - p_encoding = (char *)enc_skip(p_enc); + p_encoding = enc_skip(p_enc); } if (prt_use_courier) { // Include ASCII range encoding vector @@ -2647,9 +2634,9 @@ bool mch_print_begin(prt_settings_T *psettings) } prt_conv.vc_type = CONV_NONE; - if (!(enc_canon_props(p_enc) & enc_canon_props((char_u *)p_encoding) & ENC_8BIT)) { + 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, (char_u *)p_encoding) == FAIL) { + if (convert_setup(&prt_conv, p_enc, p_encoding) == FAIL) { semsg(_("E620: Unable to convert to print encoding \"%s\""), p_encoding); return false; @@ -2943,7 +2930,7 @@ int mch_print_begin_page(char_u *str) int mch_print_blank_page(void) { - return mch_print_begin_page(NULL) ? (mch_print_end_page()) : FALSE; + return mch_print_begin_page(NULL) ? (mch_print_end_page()) : false; } static double prt_pos_x = 0; @@ -3075,7 +3062,8 @@ int mch_print_text_out(char_u *const textp, size_t len) if (prt_do_conv) { // Convert from multi-byte to 8-bit encoding - tofree = p = string_convert(&prt_conv, p, &len); + p = (char_u *)string_convert(&prt_conv, (char *)p, &len); + tofree = p; if (p == NULL) { p = (char_u *)""; len = 0; diff --git a/src/nvim/hashtab.c b/src/nvim/hashtab.c index 95ae7a152c..308f64f011 100644 --- a/src/nvim/hashtab.c +++ b/src/nvim/hashtab.c @@ -45,7 +45,7 @@ char hash_removed; void hash_init(hashtab_T *ht) { // This zeroes all "ht_" entries and all the "hi_key" in "ht_smallarray". - memset(ht, 0, sizeof(hashtab_T)); + CLEAR_POINTER(ht); ht->ht_array = ht->ht_smallarray; ht->ht_mask = HT_INIT_SIZE - 1; } @@ -67,7 +67,7 @@ void hash_clear(hashtab_T *ht) void hash_clear_all(hashtab_T *ht, unsigned int off) { size_t todo = ht->ht_used; - for (hashitem_T *hi = ht->ht_array; todo > 0; ++hi) { + for (hashitem_T *hi = ht->ht_array; todo > 0; hi++) { if (!HASHITEM_EMPTY(hi)) { xfree(hi->hi_key - off); todo--; @@ -342,11 +342,13 @@ static void hash_may_resize(hashtab_T *ht, size_t minitems) hashitem_T *oldarray = keep_smallarray ? memcpy(temparray, ht->ht_smallarray, sizeof(temparray)) : ht->ht_array; + + if (newarray_is_small) { + CLEAR_FIELD(ht->ht_smallarray); + } hashitem_T *newarray = newarray_is_small ? ht->ht_smallarray - : xmalloc(sizeof(hashitem_T) * newsize); - - memset(newarray, 0, sizeof(hashitem_T) * newsize); + : xcalloc(newsize, sizeof(hashitem_T)); // Move all the items from the old array to the new one, placing them in // the right spot. The new array won't have any removed items, thus this @@ -354,7 +356,7 @@ static void hash_may_resize(hashtab_T *ht, size_t minitems) hash_T newmask = newsize - 1; size_t todo = ht->ht_used; - for (hashitem_T *olditem = oldarray; todo > 0; ++olditem) { + for (hashitem_T *olditem = oldarray; todo > 0; olditem++) { if (HASHITEM_EMPTY(olditem)) { continue; } diff --git a/src/nvim/help.c b/src/nvim/help.c new file mode 100644 index 0000000000..245d80489f --- /dev/null +++ b/src/nvim/help.c @@ -0,0 +1,1179 @@ +// 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 + +// help.c: functions for Vim help + +#include <stdio.h> +#include <stdlib.h> + +#include "nvim/buffer.h" +#include "nvim/charset.h" +#include "nvim/cmdexpand.h" +#include "nvim/ex_cmds.h" +#include "nvim/ex_docmd.h" +#include "nvim/fileio.h" +#include "nvim/garray.h" +#include "nvim/globals.h" +#include "nvim/help.h" +#include "nvim/memory.h" +#include "nvim/option.h" +#include "nvim/optionstr.h" +#include "nvim/os/input.h" +#include "nvim/path.h" +#include "nvim/strings.h" +#include "nvim/syntax.h" +#include "nvim/tag.h" +#include "nvim/vim.h" +#include "nvim/window.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "help.c.generated.h" +#endif + +/// ":help": open a read-only window on a help file +void ex_help(exarg_T *eap) +{ + char *arg; + char *tag; + FILE *helpfd; // file descriptor of help file + int n; + int i; + win_T *wp; + int num_matches; + char **matches; + char *p; + int empty_fnum = 0; + int alt_fnum = 0; + buf_T *buf; + int len; + char *lang; + const bool old_KeyTyped = KeyTyped; + + if (eap != NULL) { + // A ":help" command ends at the first LF, or at a '|' that is + // followed by some text. Set nextcmd to the following command. + for (arg = eap->arg; *arg; arg++) { + if (*arg == '\n' || *arg == '\r' + || (*arg == '|' && arg[1] != NUL && arg[1] != '|')) { + *arg++ = NUL; + eap->nextcmd = arg; + break; + } + } + arg = eap->arg; + + if (eap->forceit && *arg == NUL && !curbuf->b_help) { + emsg(_("E478: Don't panic!")); + return; + } + + if (eap->skip) { // not executing commands + return; + } + } else { + arg = ""; + } + + // remove trailing blanks + p = arg + STRLEN(arg) - 1; + while (p > arg && ascii_iswhite(*p) && p[-1] != '\\') { + *p-- = NUL; + } + + // Check for a specified language + lang = check_help_lang(arg); + + // When no argument given go to the index. + if (*arg == NUL) { + arg = "help.txt"; + } + + // Check if there is a match for the argument. + n = find_help_tags(arg, &num_matches, &matches, eap != NULL && eap->forceit); + + i = 0; + if (n != FAIL && lang != NULL) { + // Find first item with the requested language. + for (i = 0; i < num_matches; i++) { + len = (int)STRLEN(matches[i]); + if (len > 3 && matches[i][len - 3] == '@' + && STRICMP(matches[i] + len - 2, lang) == 0) { + break; + } + } + } + if (i >= num_matches || n == FAIL) { + if (lang != NULL) { + semsg(_("E661: Sorry, no '%s' help for %s"), lang, arg); + } else { + semsg(_("E149: Sorry, no help for %s"), arg); + } + if (n != FAIL) { + FreeWild(num_matches, matches); + } + return; + } + + // The first match (in the requested language) is the best match. + tag = xstrdup(matches[i]); + FreeWild(num_matches, matches); + + // Re-use an existing help window or open a new one. + // Always open a new one for ":tab help". + if (!bt_help(curwin->w_buffer) || cmdmod.cmod_tab != 0) { + if (cmdmod.cmod_tab != 0) { + wp = NULL; + } else { + wp = NULL; + FOR_ALL_WINDOWS_IN_TAB(wp2, curtab) { + if (bt_help(wp2->w_buffer)) { + wp = wp2; + break; + } + } + } + if (wp != NULL && wp->w_buffer->b_nwindows > 0) { + win_enter(wp, true); + } else { + // There is no help window yet. + // Try to open the file specified by the "helpfile" option. + if ((helpfd = os_fopen(p_hf, READBIN)) == NULL) { + smsg(_("Sorry, help file \"%s\" not found"), p_hf); + goto erret; + } + fclose(helpfd); + + // Split off help window; put it at far top if no position + // specified, the current window is vertically split and + // narrow. + n = WSP_HELP; + if (cmdmod.cmod_split == 0 && curwin->w_width != Columns + && curwin->w_width < 80) { + n |= WSP_TOP; + } + if (win_split(0, n) == FAIL) { + goto erret; + } + + if (curwin->w_height < p_hh) { + win_setheight((int)p_hh); + } + + // Open help file (do_ecmd() will set b_help flag, readfile() will + // set b_p_ro flag). + // Set the alternate file to the previously edited file. + alt_fnum = curbuf->b_fnum; + (void)do_ecmd(0, NULL, NULL, NULL, ECMD_LASTL, + ECMD_HIDE + ECMD_SET_HELP, + NULL); // buffer is still open, don't store info + + if ((cmdmod.cmod_flags & CMOD_KEEPALT) == 0) { + curwin->w_alt_fnum = alt_fnum; + } + empty_fnum = curbuf->b_fnum; + } + } + + restart_edit = 0; // don't want insert mode in help file + + // Restore KeyTyped, setting 'filetype=help' may reset it. + // It is needed for do_tag top open folds under the cursor. + KeyTyped = old_KeyTyped; + + do_tag((char_u *)tag, DT_HELP, 1, false, true); + + // Delete the empty buffer if we're not using it. Careful: autocommands + // may have jumped to another window, check that the buffer is not in a + // window. + if (empty_fnum != 0 && curbuf->b_fnum != empty_fnum) { + buf = buflist_findnr(empty_fnum); + if (buf != NULL && buf->b_nwindows == 0) { + wipe_buffer(buf, true); + } + } + + // keep the previous alternate file + if (alt_fnum != 0 && curwin->w_alt_fnum == empty_fnum + && (cmdmod.cmod_flags & CMOD_KEEPALT) == 0) { + curwin->w_alt_fnum = alt_fnum; + } + +erret: + xfree(tag); +} + +/// ":helpclose": Close one help window +void ex_helpclose(exarg_T *eap) +{ + FOR_ALL_WINDOWS_IN_TAB(win, curtab) { + if (bt_help(win->w_buffer)) { + win_close(win, false, eap->forceit); + return; + } + } +} + +/// In an argument search for a language specifiers in the form "@xx". +/// Changes the "@" to NUL if found, and returns a pointer to "xx". +/// +/// @return NULL if not found. +char *check_help_lang(char *arg) +{ + int len = (int)STRLEN(arg); + + if (len >= 3 && arg[len - 3] == '@' && ASCII_ISALPHA(arg[len - 2]) + && ASCII_ISALPHA(arg[len - 1])) { + arg[len - 3] = NUL; // remove the '@' + return arg + len - 2; + } + return NULL; +} + +/// Return a heuristic indicating how well the given string matches. The +/// smaller the number, the better the match. This is the order of priorities, +/// from best match to worst match: +/// - Match with least alphanumeric characters is better. +/// - Match with least total characters is better. +/// - Match towards the start is better. +/// - Match starting with "+" is worse (feature instead of command) +/// Assumption is made that the matched_string passed has already been found to +/// match some string for which help is requested. webb. +/// +/// @param offset offset for match +/// @param wrong_case no matching case +/// +/// @return a heuristic indicating how well the given string matches. +int help_heuristic(char *matched_string, int offset, int wrong_case) + FUNC_ATTR_PURE +{ + int num_letters; + char *p; + + num_letters = 0; + for (p = matched_string; *p; p++) { + if (ASCII_ISALNUM(*p)) { + num_letters++; + } + } + + // Multiply the number of letters by 100 to give it a much bigger + // weighting than the number of characters. + // If there only is a match while ignoring case, add 5000. + // If the match starts in the middle of a word, add 10000 to put it + // somewhere in the last half. + // If the match is more than 2 chars from the start, multiply by 200 to + // put it after matches at the start. + if (offset > 0 + && ASCII_ISALNUM(matched_string[offset]) + && ASCII_ISALNUM(matched_string[offset - 1])) { + offset += 10000; + } else if (offset > 2) { + offset *= 200; + } + if (wrong_case) { + offset += 5000; + } + // Features are less interesting than the subjects themselves, but "+" + // alone is not a feature. + if (matched_string[0] == '+' && matched_string[1] != NUL) { + offset += 100; + } + return 100 * num_letters + (int)STRLEN(matched_string) + offset; +} + +/// Compare functions for qsort() below, that checks the help heuristics number +/// that has been put after the tagname by find_tags(). +static int help_compare(const void *s1, const void *s2) +{ + char *p1; + char *p2; + + p1 = *(char **)s1 + strlen(*(char **)s1) + 1; + p2 = *(char **)s2 + strlen(*(char **)s2) + 1; + + // Compare by help heuristic number first. + int cmp = strcmp(p1, p2); + if (cmp != 0) { + return cmp; + } + + // Compare by strings as tie-breaker when same heuristic number. + return strcmp(*(char **)s1, *(char **)s2); +} + +/// Find all help tags matching "arg", sort them and return in matches[], with +/// the number of matches in num_matches. +/// The matches will be sorted with a "best" match algorithm. +/// When "keep_lang" is true try keeping the language of the current buffer. +int find_help_tags(const char *arg, int *num_matches, char ***matches, bool keep_lang) +{ + int i; + + // Specific tags that either have a specific replacement or won't go + // through the generic rules. + static char *(except_tbl[][2]) = { + { "*", "star" }, + { "g*", "gstar" }, + { "[*", "[star" }, + { "]*", "]star" }, + { ":*", ":star" }, + { "/*", "/star" }, // NOLINT + { "/\\*", "/\\\\star" }, + { "\"*", "quotestar" }, + { "**", "starstar" }, + { "cpo-*", "cpo-star" }, + { "/\\(\\)", "/\\\\(\\\\)" }, + { "/\\%(\\)", "/\\\\%(\\\\)" }, + { "?", "?" }, + { "??", "??" }, + { ":?", ":?" }, + { "?<CR>", "?<CR>" }, + { "g?", "g?" }, + { "g?g?", "g?g?" }, + { "g??", "g??" }, + { "-?", "-?" }, + { "q?", "q?" }, + { "v_g?", "v_g?" }, + { "/\\?", "/\\\\?" }, + { "/\\z(\\)", "/\\\\z(\\\\)" }, + { "\\=", "\\\\=" }, + { ":s\\=", ":s\\\\=" }, + { "[count]", "\\[count]" }, + { "[quotex]", "\\[quotex]" }, + { "[range]", "\\[range]" }, + { ":[range]", ":\\[range]" }, + { "[pattern]", "\\[pattern]" }, + { "\\|", "\\\\bar" }, + { "\\%$", "/\\\\%\\$" }, + { "s/\\~", "s/\\\\\\~" }, + { "s/\\U", "s/\\\\U" }, + { "s/\\L", "s/\\\\L" }, + { "s/\\1", "s/\\\\1" }, + { "s/\\2", "s/\\\\2" }, + { "s/\\3", "s/\\\\3" }, + { "s/\\9", "s/\\\\9" }, + { NULL, NULL } + }; + + static const char *(expr_table[]) = { + "!=?", "!~?", "<=?", "<?", "==?", "=~?", + ">=?", ">?", "is?", "isnot?" + }; + char *d = (char *)IObuff; // assume IObuff is long enough! + d[0] = NUL; + + if (STRNICMP(arg, "expr-", 5) == 0) { + // When the string starting with "expr-" and containing '?' and matches + // the table, it is taken literally (but ~ is escaped). Otherwise '?' + // is recognized as a wildcard. + for (i = (int)ARRAY_SIZE(expr_table); --i >= 0;) { + if (STRCMP(arg + 5, expr_table[i]) == 0) { + for (int si = 0, di = 0;; si++) { + if (arg[si] == '~') { + d[di++] = '\\'; + } + d[di++] = arg[si]; + if (arg[si] == NUL) { + break; + } + } + break; + } + } + } else { + // Recognize a few exceptions to the rule. Some strings that contain + // '*'are changed to "star", otherwise '*' is recognized as a wildcard. + for (i = 0; except_tbl[i][0] != NULL; i++) { + if (STRCMP(arg, except_tbl[i][0]) == 0) { + STRCPY(d, except_tbl[i][1]); + break; + } + } + } + + if (d[0] == NUL) { // no match in table + // Replace "\S" with "/\\S", etc. Otherwise every tag is matched. + // Also replace "\%^" and "\%(", they match every tag too. + // Also "\zs", "\z1", etc. + // Also "\@<", "\@=", "\@<=", etc. + // And also "\_$" and "\_^". + if (arg[0] == '\\' + && ((arg[1] != NUL && arg[2] == NUL) + || (vim_strchr("%_z@", arg[1]) != NULL + && arg[2] != NUL))) { + vim_snprintf(d, IOSIZE, "/\\\\%s", arg + 1); + // Check for "/\\_$", should be "/\\_\$" + if (d[3] == '_' && d[4] == '$') { + STRCPY(d + 4, "\\$"); + } + } else { + // Replace: + // "[:...:]" with "\[:...:]" + // "[++...]" with "\[++...]" + // "\{" with "\\{" -- matching "} \}" + if ((arg[0] == '[' && (arg[1] == ':' + || (arg[1] == '+' && arg[2] == '+'))) + || (arg[0] == '\\' && arg[1] == '{')) { + *d++ = '\\'; + } + + // If tag starts with "('", skip the "(". Fixes CTRL-] on ('option'. + if (*arg == '(' && arg[1] == '\'') { + arg++; + } + for (const char *s = arg; *s; s++) { + // Replace "|" with "bar" and '"' with "quote" to match the name of + // the tags for these commands. + // Replace "*" with ".*" and "?" with "." to match command line + // completion. + // Insert a backslash before '~', '$' and '.' to avoid their + // special meaning. + if ((char_u *)d - IObuff > IOSIZE - 10) { // getting too long!? + break; + } + switch (*s) { + case '|': + STRCPY(d, "bar"); + d += 3; + continue; + case '"': + STRCPY(d, "quote"); + d += 5; + continue; + case '*': + *d++ = '.'; + break; + case '?': + *d++ = '.'; + continue; + case '$': + case '.': + case '~': + *d++ = '\\'; + break; + } + + // Replace "^x" by "CTRL-X". Don't do this for "^_" to make + // ":help i_^_CTRL-D" work. + // Insert '-' before and after "CTRL-X" when applicable. + if (*s < ' ' + || (*s == '^' && s[1] + && (ASCII_ISALPHA(s[1]) || vim_strchr("?@[\\]^", s[1]) != NULL))) { + if ((char_u *)d > IObuff && d[-1] != '_' && d[-1] != '\\') { + *d++ = '_'; // prepend a '_' to make x_CTRL-x + } + STRCPY(d, "CTRL-"); + d += 5; + if (*s < ' ') { + *d++ = (char)(*s + '@'); + if (d[-1] == '\\') { + *d++ = '\\'; // double a backslash + } + } else { + *d++ = *++s; + } + if (s[1] != NUL && s[1] != '_') { + *d++ = '_'; // append a '_' + } + continue; + } else if (*s == '^') { // "^" or "CTRL-^" or "^_" + *d++ = '\\'; + } else if (s[0] == '\\' && s[1] != '\\' && *arg == '/' && s == arg + 1) { + // Insert a backslash before a backslash after a slash, for search + // pattern tags: "/\|" --> "/\\|". + *d++ = '\\'; + } + + // "CTRL-\_" -> "CTRL-\\_" to avoid the special meaning of "\_" in + // "CTRL-\_CTRL-N" + if (STRNICMP(s, "CTRL-\\_", 7) == 0) { + STRCPY(d, "CTRL-\\\\"); + d += 7; + s += 6; + } + + *d++ = *s; + + // If tag contains "({" or "([", tag terminates at the "(". + // This is for help on functions, e.g.: abs({expr}). + if (*s == '(' && (s[1] == '{' || s[1] == '[')) { + break; + } + + // If tag starts with ', toss everything after a second '. Fixes + // CTRL-] on 'option'. (would include the trailing '.'). + if (*s == '\'' && s > arg && *arg == '\'') { + break; + } + // Also '{' and '}'. Fixes CTRL-] on '{address}'. + if (*s == '}' && s > arg && *arg == '{') { + break; + } + } + *d = NUL; + + if (*IObuff == '`') { + if ((char_u *)d > IObuff + 2 && d[-1] == '`') { + // remove the backticks from `command` + memmove(IObuff, IObuff + 1, STRLEN(IObuff)); + d[-2] = NUL; + } else if ((char_u *)d > IObuff + 3 && d[-2] == '`' && d[-1] == ',') { + // remove the backticks and comma from `command`, + memmove(IObuff, IObuff + 1, STRLEN(IObuff)); + d[-3] = NUL; + } else if ((char_u *)d > IObuff + 4 && d[-3] == '`' + && d[-2] == '\\' && d[-1] == '.') { + // remove the backticks and dot from `command`\. + memmove(IObuff, IObuff + 1, STRLEN(IObuff)); + d[-4] = NUL; + } + } + } + } + + *matches = NULL; + *num_matches = 0; + int flags = TAG_HELP | TAG_REGEXP | TAG_NAMES | TAG_VERBOSE | TAG_NO_TAGFUNC; + if (keep_lang) { + flags |= TAG_KEEP_LANG; + } + if (find_tags((char *)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); + // Delete more than TAG_MANY to reduce the size of the listing. + while (*num_matches > TAG_MANY) { + xfree((*matches)[--*num_matches]); + } + } + return OK; +} + +/// Cleanup matches for help tags: +/// Remove "@ab" if the top of 'helplang' is "ab" and the language of the first +/// tag matches it. Otherwise remove "@en" if "en" is the only language. +void cleanup_help_tags(int num_file, char **file) +{ + char_u buf[4]; + char_u *p = buf; + + if (p_hlg[0] != NUL && (p_hlg[0] != 'e' || p_hlg[1] != 'n')) { + *p++ = '@'; + *p++ = p_hlg[0]; + *p++ = p_hlg[1]; + } + *p = NUL; + + for (int i = 0; i < num_file; i++) { + int len = (int)STRLEN(file[i]) - 3; + if (len <= 0) { + continue; + } + if (STRCMP(file[i] + len, "@en") == 0) { + // Sorting on priority means the same item in another language may + // be anywhere. Search all items for a match up to the "@en". + int j; + for (j = 0; j < num_file; j++) { + if (j != i + && (int)STRLEN(file[j]) == len + 3 + && STRNCMP(file[i], file[j], len + 1) == 0) { + break; + } + } + if (j == num_file) { + // item only exists with @en, remove it + file[i][len] = NUL; + } + } + } + + if (*buf != NUL) { + for (int i = 0; i < num_file; i++) { + int len = (int)STRLEN(file[i]) - 3; + if (len <= 0) { + continue; + } + if (STRCMP(file[i] + len, buf) == 0) { + // remove the default language + file[i][len] = NUL; + } + } + } +} + +/// Called when starting to edit a buffer for a help file. +void prepare_help_buffer(void) +{ + curbuf->b_help = true; + set_string_option_direct("buftype", -1, "help", OPT_FREE|OPT_LOCAL, 0); + + // Always set these options after jumping to a help tag, because the + // user may have an autocommand that gets in the way. + // Accept all ASCII chars for keywords, except ' ', '*', '"', '|', and + // latin1 word characters (for translated help files). + // Only set it when needed, buf_init_chartab() is some work. + char *p = "!-~,^*,^|,^\",192-255"; + if (STRCMP(curbuf->b_p_isk, p) != 0) { + set_string_option_direct("isk", -1, p, OPT_FREE|OPT_LOCAL, 0); + check_buf_options(curbuf); + (void)buf_init_chartab(curbuf, false); + } + + // Don't use the global foldmethod. + set_string_option_direct("fdm", -1, "manual", OPT_FREE|OPT_LOCAL, 0); + + curbuf->b_p_ts = 8; // 'tabstop' is 8. + curwin->w_p_list = false; // No list mode. + + curbuf->b_p_ma = false; // Not modifiable. + curbuf->b_p_bin = false; // Reset 'bin' before reading file. + curwin->w_p_nu = 0; // No line numbers. + curwin->w_p_rnu = 0; // No relative line numbers. + RESET_BINDING(curwin); // No scroll or cursor binding. + curwin->w_p_arab = false; // No arabic mode. + curwin->w_p_rl = false; // Help window is left-to-right. + curwin->w_p_fen = false; // No folding in the help window. + curwin->w_p_diff = false; // No 'diff'. + curwin->w_p_spell = false; // No spell checking. + + set_buflisted(false); +} + +/// After reading a help file: May cleanup a help buffer when syntax +/// highlighting is not used. +void fix_help_buffer(void) +{ + linenr_T lnum; + char *line; + bool in_example = false; + + // Set filetype to "help". + if (STRCMP(curbuf->b_p_ft, "help") != 0) { + curbuf->b_ro_locked++; + set_option_value_give_err("ft", 0L, "help", OPT_LOCAL); + curbuf->b_ro_locked--; + } + + if (!syntax_present(curwin)) { + for (lnum = 1; lnum <= curbuf->b_ml.ml_line_count; lnum++) { + line = (char *)ml_get_buf(curbuf, lnum, false); + 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] == '<') { + // blank-out a '<' in the first column + line = (char *)ml_get_buf(curbuf, lnum, true); + line[0] = ' '; + } + in_example = false; + } + if (!in_example && len > 0) { + if (line[len - 1] == '>' && (len == 1 || line[len - 2] == ' ')) { + // blank-out a '>' in the last column (start of example) + line = (char *)ml_get_buf(curbuf, lnum, true); + line[len - 1] = ' '; + in_example = true; + } else if (line[len - 1] == '~') { + // blank-out a '~' at the end of line (header marker) + line = (char *)ml_get_buf(curbuf, lnum, true); + line[len - 1] = ' '; + } + } + } + } + + // In the "help.txt" and "help.abx" file, add the locally added help + // files. This uses the very first line in the help file. + char *const fname = path_tail(curbuf->b_fname); + if (FNAMECMP(fname, "help.txt") == 0 + || (FNAMENCMP(fname, "help.", 5) == 0 + && ASCII_ISALPHA(fname[5]) + && ASCII_ISALPHA(fname[6]) + && TOLOWER_ASC(fname[7]) == 'x' + && fname[8] == NUL)) { + for (lnum = 1; lnum < curbuf->b_ml.ml_line_count; lnum++) { + line = (char *)ml_get_buf(curbuf, lnum, false); + if (strstr(line, "*local-additions*") == NULL) { + continue; + } + + // Go through all directories in 'runtimepath', skipping + // $VIMRUNTIME. + char *p = p_rtp; + while (*p != NUL) { + copy_option_part(&p, (char *)NameBuff, MAXPATHL, ","); + char *const rt = vim_getenv("VIMRUNTIME"); + if (rt != NULL + && path_full_compare(rt, (char *)NameBuff, false, true) != kEqualFiles) { + int fcount; + char **fnames; + char *s; + vimconv_T vc; + char *cp; + + // Find all "doc/ *.txt" files in this directory. + if (!add_pathsep((char *)NameBuff) + || STRLCAT(NameBuff, "doc/*.??[tx]", // NOLINT + sizeof(NameBuff)) >= MAXPATHL) { + emsg(_(e_fnametoolong)); + continue; + } + + // 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 }; + 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) { + 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 (FNAMECMP(e1, ".txt") != 0 + && FNAMECMP(e1, fname + 4) != 0) { + // Not .txt and not .abx, remove it. + XFREE_CLEAR(fnames[i1]); + continue; + } + if (e1 - f1 != e2 - f2 + || FNAMENCMP(f1, f2, e1 - f1) != 0) { + continue; + } + if (FNAMECMP(e1, ".txt") == 0 + && FNAMECMP(e2, fname + 4) == 0) { + // use .abx instead of .txt + XFREE_CLEAR(fnames[i1]); + } + } + } + for (int fi = 0; fi < fcount; fi++) { + if (fnames[fi] == NULL) { + continue; + } + + FILE *const fd = os_fopen(fnames[fi], "r"); + if (fd == NULL) { + continue; + } + vim_fgets(IObuff, IOSIZE, fd); + if (IObuff[0] == '*' + && (s = vim_strchr((char *)IObuff + 1, '*')) + != NULL) { + TriState this_utf = kNone; + // Change tag definition to a + // reference and remove <CR>/<NL>. + IObuff[0] = '|'; + *s = '|'; + while (*s != NUL) { + if (*s == '\r' || *s == '\n') { + *s = NUL; + } + // 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) { + this_utf = kTrue; + const int l = utf_ptr2len(s); + if (l == 1) { + this_utf = kFalse; + } + s += l - 1; + } + s++; + } + // The help file is latin1 or utf-8; + // conversion to the current + // 'encoding' may be required. + vc.vc_type = CONV_NONE; + convert_setup(&vc, + (this_utf == kTrue ? "utf-8" : "latin1"), + p_enc); + if (vc.vc_type == CONV_NONE) { + // No conversion needed. + cp = (char *)IObuff; + } else { + // Do the conversion. If it fails + // use the unconverted text. + cp = string_convert(&vc, (char *)IObuff, NULL); + if (cp == NULL) { + cp = (char *)IObuff; + } + } + convert_setup(&vc, NULL, NULL); + + ml_append(lnum, cp, (colnr_T)0, false); + if ((char_u *)cp != IObuff) { + xfree(cp); + } + lnum++; + } + fclose(fd); + } + FreeWild(fcount, fnames); + } + } + xfree(rt); + } + break; + } + } +} + +/// ":exusage" +void ex_exusage(exarg_T *eap) +{ + do_cmdline_cmd("help ex-cmd-index"); +} + +/// ":viusage" +void ex_viusage(exarg_T *eap) +{ + do_cmdline_cmd("help normal-index"); +} + +/// Generate tags in one help directory +/// +/// @param dir Path to the doc directory +/// @param ext Suffix of the help files (".txt", ".itx", ".frx", etc.) +/// @param tagname Name of the tags file ("tags" for English, "tags-fr" for +/// French) +/// @param add_help_tags Whether to add the "help-tags" tag +/// @param ignore_writeerr ignore write error +static void helptags_one(char *dir, const char *ext, const char *tagfname, bool add_help_tags, + bool ignore_writeerr) + FUNC_ATTR_NONNULL_ALL +{ + garray_T ga; + int filecount; + char **files; + char *p1, *p2; + char *s; + TriState utf8 = kNone; + bool mix = false; // detected mixed encodings + + // Find all *.txt files. + size_t dirlen = STRLCPY(NameBuff, dir, sizeof(NameBuff)); + if (dirlen >= MAXPATHL + || STRLCAT(NameBuff, "/**/*", sizeof(NameBuff)) >= MAXPATHL // NOLINT + || STRLCAT(NameBuff, ext, sizeof(NameBuff)) >= MAXPATHL) { + emsg(_(e_fnametoolong)); + return; + } + + // 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 }; + const int res = gen_expand_wildcards(1, buff_list, &filecount, &files, + EW_FILE|EW_SILENT); + if (res == FAIL || filecount == 0) { + if (!got_int) { + semsg(_("E151: No match: %s"), NameBuff); + } + if (res != FAIL) { + FreeWild(filecount, files); + } + return; + } + + // Open the tags file for writing. + // Do this before scanning through all the files. + memcpy(NameBuff, dir, dirlen + 1); + if (!add_pathsep((char *)NameBuff) + || STRLCAT(NameBuff, tagfname, sizeof(NameBuff)) >= MAXPATHL) { + emsg(_(e_fnametoolong)); + return; + } + + FILE *const fd_tags = os_fopen((char *)NameBuff, "w"); + if (fd_tags == NULL) { + if (!ignore_writeerr) { + semsg(_("E152: Cannot open %s for writing"), NameBuff); + } + FreeWild(filecount, files); + return; + } + + // If using the "++t" argument or generating tags for "$VIMRUNTIME/doc" + // add the "help-tags" tag. + ga_init(&ga, (int)sizeof(char_u *), 100); + if (add_help_tags + || path_full_compare("$VIMRUNTIME/doc", dir, false, true) == kEqualFiles) { + size_t s_len = 18 + STRLEN(tagfname); + s = xmalloc(s_len); + snprintf(s, s_len, "help-tags\t%s\t1\n", tagfname); + GA_APPEND(char *, &ga, s); + } + + // Go over all the files and extract the tags. + for (int fi = 0; fi < filecount && !got_int; fi++) { + FILE *const fd = os_fopen(files[fi], "r"); + if (fd == NULL) { + semsg(_("E153: Unable to open %s for reading"), files[fi]); + continue; + } + const char *const fname = files[fi] + dirlen + 1; + + bool firstline = true; + 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) { + this_utf8 = kTrue; + const int l = utf_ptr2len(s); + if (l == 1) { + // Illegal UTF-8 byte sequence. + this_utf8 = kFalse; + break; + } + s += l - 1; + } + } + if (this_utf8 == kNone) { // only ASCII characters found + this_utf8 = kFalse; + } + if (utf8 == kNone) { // first file + utf8 = this_utf8; + } else if (utf8 != this_utf8) { + semsg(_("E670: Mix of help file encodings within a language: %s"), + files[fi]); + mix = !got_int; + got_int = true; + } + firstline = false; + } + p1 = vim_strchr((char *)IObuff, '*'); // find first '*' + while (p1 != NULL) { + p2 = strchr((const char *)p1 + 1, '*'); // Find second '*'. + if (p2 != NULL && p2 > p1 + 1) { // Skip "*" and "**". + for (s = p1 + 1; s < p2; s++) { + if (*s == ' ' || *s == '\t' || *s == '|') { + break; + } + } + + // Only accept a *tag* when it consists of valid + // characters, there is white space before it and is + // followed by a white character or end-of-line. + if (s == p2 + && ((char_u *)p1 == IObuff || p1[-1] == ' ' || p1[-1] == '\t') + && (vim_strchr(" \t\n\r", s[1]) != NULL + || s[1] == '\0')) { + *p2 = '\0'; + p1++; + 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); + + // find next '*' + p2 = vim_strchr(p2 + 1, '*'); + } + } + p1 = p2; + } + line_breakcheck(); + } + + fclose(fd); + } + + FreeWild(filecount, files); + + if (!got_int && ga.ga_data != NULL) { + // Sort the tags. + sort_strings(ga.ga_data, ga.ga_len); + + // Check for duplicates. + for (int i = 1; i < ga.ga_len; i++) { + p1 = ((char **)ga.ga_data)[i - 1]; + p2 = ((char **)ga.ga_data)[i]; + while (*p1 == *p2) { + if (*p2 == '\t') { + *p2 = NUL; + vim_snprintf((char *)NameBuff, MAXPATHL, + _("E154: Duplicate tag \"%s\" in file %s/%s"), + ((char_u **)ga.ga_data)[i], dir, p2 + 1); + emsg((char *)NameBuff); + *p2 = '\t'; + break; + } + p1++; + p2++; + } + } + + if (utf8 == kTrue) { + fprintf(fd_tags, "!_TAG_FILE_ENCODING\tutf-8\t//\n"); + } + + // 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) { + // help-tags entry was added in formatted form + fputs(s, fd_tags); + } else { + fprintf(fd_tags, "%s\t/" "*", s); + for (p1 = s; *p1 != '\t'; p1++) { + // insert backslash before '\\' and '/' + if (*p1 == '\\' || *p1 == '/') { + putc('\\', fd_tags); + } + putc(*p1, fd_tags); + } + fprintf(fd_tags, "*\n"); + } + } + } + if (mix) { + got_int = false; // continue with other languages + } + + GA_DEEP_CLEAR_PTR(&ga); + fclose(fd_tags); // there is no check for an error... +} + +/// Generate tags in one help directory, taking care of translations. +static void do_helptags(char *dirname, bool add_help_tags, bool ignore_writeerr) + FUNC_ATTR_NONNULL_ALL +{ + int len; + garray_T ga; + char lang[2]; + char ext[5]; + char fname[8]; + int filecount; + char **files; + + // Get a list of all files in the help directory and in subdirectories. + STRLCPY(NameBuff, dirname, sizeof(NameBuff)); + if (!add_pathsep((char *)NameBuff) + || STRLCAT(NameBuff, "**", sizeof(NameBuff)) >= MAXPATHL) { + emsg(_(e_fnametoolong)); + return; + } + + // 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 }; + if (gen_expand_wildcards(1, buff_list, &filecount, &files, + EW_FILE|EW_SILENT) == FAIL + || filecount == 0) { + semsg(_("E151: No match: %s"), NameBuff); + return; + } + + // Go over all files in the directory to find out what languages are + // present. + int j; + ga_init(&ga, 1, 10); + for (int i = 0; i < filecount; i++) { + len = (int)STRLEN(files[i]); + if (len <= 4) { + continue; + } + + if (STRICMP(files[i] + len - 4, ".txt") == 0) { + // ".txt" -> language "en" + lang[0] = 'e'; + lang[1] = 'n'; + } else if (files[i][len - 4] == '.' + && ASCII_ISALPHA(files[i][len - 3]) + && ASCII_ISALPHA(files[i][len - 2]) + && TOLOWER_ASC(files[i][len - 1]) == 'x') { + // ".abx" -> language "ab" + lang[0] = (char)TOLOWER_ASC(files[i][len - 3]); + lang[1] = (char)TOLOWER_ASC(files[i][len - 2]); + } else { + continue; + } + + // 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) { + break; + } + } + if (j == ga.ga_len) { + // New language, add it. + ga_grow(&ga, 2); + ((char *)ga.ga_data)[ga.ga_len++] = lang[0]; + ((char *)ga.ga_data)[ga.ga_len++] = lang[1]; + } + } + + // Loop over the found languages to generate a tags file for each one. + for (j = 0; j < ga.ga_len; j += 2) { + STRCPY(fname, "tags-xx"); + fname[5] = ((char *)ga.ga_data)[j]; + fname[6] = ((char *)ga.ga_data)[j + 1]; + if (fname[5] == 'e' && fname[6] == 'n') { + // English is an exception: use ".txt" and "tags". + fname[4] = NUL; + STRCPY(ext, ".txt"); + } else { + // Language "ab" uses ".abx" and "tags-ab". + STRCPY(ext, ".xxx"); + ext[1] = fname[5]; + ext[2] = fname[6]; + } + helptags_one(dirname, (char *)ext, (char *)fname, add_help_tags, ignore_writeerr); + } + + ga_clear(&ga); + FreeWild(filecount, files); +} + +static void helptags_cb(char *fname, void *cookie) + FUNC_ATTR_NONNULL_ALL +{ + do_helptags(fname, *(bool *)cookie, true); +} + +/// ":helptags" +void ex_helptags(exarg_T *eap) +{ + expand_T xpc; + char *dirname; + bool add_help_tags = false; + + // Check for ":helptags ++t {dir}". + if (STRNCMP(eap->arg, "++t", 3) == 0 && ascii_iswhite(eap->arg[3])) { + add_help_tags = true; + eap->arg = skipwhite(eap->arg + 3); + } + + if (STRCMP(eap->arg, "ALL") == 0) { + do_in_path(p_rtp, "doc", DIP_ALL + DIP_DIR, helptags_cb, &add_help_tags); + } else { + ExpandInit(&xpc); + xpc.xp_context = EXPAND_DIRECTORIES; + dirname = (char *)ExpandOne(&xpc, (char_u *)eap->arg, NULL, + WILD_LIST_NOTFOUND|WILD_SILENT, WILD_EXPAND_FREE); + if (dirname == NULL || !os_isdir(dirname)) { + semsg(_("E150: Not a directory: %s"), eap->arg); + } else { + do_helptags(dirname, add_help_tags, false); + } + xfree(dirname); + } +} diff --git a/src/nvim/help.h b/src/nvim/help.h new file mode 100644 index 0000000000..21e11392ee --- /dev/null +++ b/src/nvim/help.h @@ -0,0 +1,11 @@ +#ifndef NVIM_HELP_H +#define NVIM_HELP_H + +#include <stdbool.h> + +#include "nvim/ex_cmds_defs.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "help.h.generated.h" +#endif +#endif // NVIM_HELP_H diff --git a/src/nvim/highlight.c b/src/nvim/highlight.c index 71c7194479..a78b933108 100644 --- a/src/nvim/highlight.c +++ b/src/nvim/highlight.c @@ -6,6 +6,7 @@ #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" #include "nvim/decoration_provider.h" +#include "nvim/drawscreen.h" #include "nvim/highlight.h" #include "nvim/highlight_defs.h" #include "nvim/highlight_group.h" @@ -13,8 +14,7 @@ #include "nvim/map.h" #include "nvim/message.h" #include "nvim/option.h" -#include "nvim/popupmnu.h" -#include "nvim/screen.h" +#include "nvim/popupmenu.h" #include "nvim/ui.h" #include "nvim/vim.h" @@ -32,7 +32,9 @@ static Map(int, int) blend_attr_entries = MAP_INIT; static Map(int, int) blendthrough_attr_entries = MAP_INIT; /// highlight entries private to a namespace -static Map(ColorKey, ColorItem) ns_hl; +static Map(ColorKey, ColorItem) ns_hls; +typedef int NSHlAttr[HLF_COUNT + 1]; +static PMap(handle_T) ns_hl_attr; void highlight_init(void) { @@ -147,42 +149,51 @@ int hl_get_syn_attr(int ns_id, int idx, HlAttrs at_en) void ns_hl_def(NS ns_id, int hl_id, HlAttrs attrs, int link_id, Dict(highlight) *dict) { - if ((attrs.rgb_ae_attr & HL_DEFAULT) - && map_has(ColorKey, ColorItem)(&ns_hl, ColorKey(ns_id, hl_id))) { - return; - } if (ns_id == 0) { assert(dict); // set in global (':highlight') namespace set_hl_group(hl_id, attrs, dict, link_id); return; } + if ((attrs.rgb_ae_attr & HL_DEFAULT) + && map_has(ColorKey, ColorItem)(&ns_hls, ColorKey(ns_id, hl_id))) { + return; + } DecorProvider *p = get_decor_provider(ns_id, true); int attr_id = link_id > 0 ? -1 : hl_get_syn_attr(ns_id, hl_id, attrs); ColorItem it = { .attr_id = attr_id, .link_id = link_id, .version = p->hl_valid, - .is_default = (attrs.rgb_ae_attr & HL_DEFAULT) }; - map_put(ColorKey, ColorItem)(&ns_hl, ColorKey(ns_id, hl_id), it); + .is_default = (attrs.rgb_ae_attr & HL_DEFAULT), + .link_global = (attrs.rgb_ae_attr & HL_GLOBAL) }; + map_put(ColorKey, ColorItem)(&ns_hls, ColorKey(ns_id, hl_id), it); + p->hl_cached = false; } -int ns_get_hl(NS ns_id, int hl_id, bool link, bool nodefault) +int ns_get_hl(NS *ns_hl, int hl_id, bool link, bool nodefault) { static int recursive = 0; - if (ns_id < 0) { + if (*ns_hl == 0) { + // ns=0 (the default namespace) does not have a provider so stop here + return -1; + } + + if (*ns_hl < 0) { if (ns_hl_active <= 0) { return -1; } - ns_id = ns_hl_active; + *ns_hl = ns_hl_active; } + int ns_id = *ns_hl; + DecorProvider *p = get_decor_provider(ns_id, true); - ColorItem it = map_get(ColorKey, ColorItem)(&ns_hl, ColorKey(ns_id, hl_id)); + ColorItem it = map_get(ColorKey, ColorItem)(&ns_hls, ColorKey(ns_id, hl_id)); // TODO(bfredl): map_ref true even this? - bool valid_cache = it.version >= p->hl_valid; + bool valid_item = it.version >= p->hl_valid; - if (!valid_cache && p->hl_def != LUA_NOREF && !recursive) { + if (!valid_item && p->hl_def != LUA_NOREF && !recursive) { MAXSIZE_TEMP_ARRAY(args, 3); ADD_C(args, INTEGER_OBJ((Integer)ns_id)); ADD_C(args, STRING_OBJ(cstr_to_string((char *)syn_id2name(hl_id)))); @@ -212,47 +223,79 @@ int ns_get_hl(NS ns_id, int hl_id, bool link, bool nodefault) } } - it.attr_id = fallback ? -1 : hl_get_syn_attr((int)ns_id, hl_id, attrs); + it.attr_id = fallback ? -1 : hl_get_syn_attr(ns_id, hl_id, attrs); it.version = p->hl_valid - tmp; it.is_default = attrs.rgb_ae_attr & HL_DEFAULT; - map_put(ColorKey, ColorItem)(&ns_hl, ColorKey(ns_id, hl_id), it); + it.link_global = attrs.rgb_ae_attr & HL_GLOBAL; + map_put(ColorKey, ColorItem)(&ns_hls, ColorKey(ns_id, hl_id), it); + valid_item = true; } - if (it.is_default && nodefault) { + if ((it.is_default && nodefault) || !valid_item) { return -1; } if (link) { - return it.attr_id >= 0 ? 0 : it.link_id; + if (it.attr_id >= 0) { + return 0; + } else { + if (it.link_global) { + *ns_hl = 0; + } + return it.link_id; + } } else { return it.attr_id; } } -bool win_check_ns_hl(win_T *wp) +bool hl_check_ns(void) { - if (ns_hl_changed) { - highlight_changed(); - if (wp) { - update_window_hl(wp, true); + int ns = 0; + if (ns_hl_fast > 0) { + ns = ns_hl_fast; + } else if (ns_hl_win >= 0) { + ns = ns_hl_win; + } else { + ns = ns_hl_global; + } + if (ns_hl_active == ns) { + return false; + } + + ns_hl_active = ns; + hl_attr_active = highlight_attr; + if (ns > 0) { + update_ns_hl(ns); + NSHlAttr *hl_def = (NSHlAttr *)pmap_get(handle_T)(&ns_hl_attr, ns); + if (hl_def) { + hl_attr_active = *hl_def; } - ns_hl_changed = false; - return true; } - return false; + need_highlight_changed = true; + return true; +} + +/// prepare for drawing window `wp` or global elements if NULL +/// +/// Note: pum should be drawn in the context of the current window! +bool win_check_ns_hl(win_T *wp) +{ + ns_hl_win = wp ? wp->w_ns_hl : -1; + return hl_check_ns(); } /// Get attribute code for a builtin highlight group. /// /// The final syntax group could be modified by hi-link or 'winhighlight'. -int hl_get_ui_attr(int idx, int final_id, bool optional) +int hl_get_ui_attr(int ns_id, int idx, int final_id, bool optional) { HlAttrs attrs = HLATTRS_INIT; bool available = false; if (final_id > 0) { - int syn_attr = syn_id2attr(final_id); - if (syn_attr != 0) { + int syn_attr = syn_ns_id2attr(ns_id, final_id, optional); + if (syn_attr > 0) { attrs = syn_attr2entry(syn_attr); available = true; } @@ -265,8 +308,6 @@ int hl_get_ui_attr(int idx, int final_id, bool optional) if (pum_drawn()) { must_redraw_pum = true; } - } else if (idx == HLF_MSG) { - msg_grid.blending = attrs.hl_blend > -1; } if (optional && !available) { @@ -278,6 +319,21 @@ int hl_get_ui_attr(int idx, int final_id, bool optional) void update_window_hl(win_T *wp, bool invalid) { + int ns_id = wp->w_ns_hl; + + update_ns_hl(ns_id); + if (ns_id != wp->w_ns_hl_active || wp->w_ns_hl_attr == NULL) { + wp->w_ns_hl_active = ns_id; + + wp->w_ns_hl_attr = *(NSHlAttr *)pmap_get(handle_T)(&ns_hl_attr, ns_id); + if (!wp->w_ns_hl_attr) { + // No specific highlights, use the defaults. + wp->w_ns_hl_attr = highlight_attr; + } + } + + int *hl_def = wp->w_ns_hl_attr; + if (!wp->w_hl_needs_update && !invalid) { return; } @@ -285,34 +341,17 @@ void update_window_hl(win_T *wp, bool invalid) // If a floating window is blending it always have a named // wp->w_hl_attr_normal group. HL_ATTR(HLF_NFLOAT) is always named. - bool has_blend = wp->w_floating && wp->w_p_winbl != 0; // determine window specific background set in 'winhighlight' bool float_win = wp->w_floating && !wp->w_float_config.external; - if (wp != curwin && wp->w_hl_ids[HLF_INACTIVE] != 0) { - wp->w_hl_attr_normal = hl_get_ui_attr(HLF_INACTIVE, - wp->w_hl_ids[HLF_INACTIVE], - !has_blend); - } else if (float_win && wp->w_hl_ids[HLF_NFLOAT] != 0) { - wp->w_hl_attr_normal = hl_get_ui_attr(HLF_NFLOAT, - wp->w_hl_ids[HLF_NFLOAT], !has_blend); - } else if (wp->w_hl_id_normal != 0) { - wp->w_hl_attr_normal = hl_get_ui_attr(-1, wp->w_hl_id_normal, !has_blend); + if (float_win && hl_def[HLF_NFLOAT] != 0) { + wp->w_hl_attr_normal = hl_def[HLF_NFLOAT]; + } else if (hl_def[HLF_COUNT] > 0) { + wp->w_hl_attr_normal = hl_def[HLF_COUNT]; } else { wp->w_hl_attr_normal = float_win ? HL_ATTR(HLF_NFLOAT) : 0; } - // NOOOO! You cannot just pretend that "Normal" is just like any other - // syntax group! It needs at least 10 layers of special casing! Noooooo! - // - // haha, theme engine go brrr - int normality = syn_check_group(S_LEN("Normal")); - int ns_attr = ns_get_hl(-1, normality, false, false); - if (ns_attr > 0) { - // TODO(bfredl): hantera NormalNC and so on - wp->w_hl_attr_normal = ns_attr; - } - // if blend= attribute is not set, 'winblend' value overrides it. if (wp->w_floating && wp->w_p_winbl > 0) { HlEntry entry = kv_A(attr_entries, wp->w_hl_attr_normal); @@ -322,28 +361,13 @@ void update_window_hl(win_T *wp, bool invalid) } } - if (wp != curwin && wp->w_hl_ids[HLF_INACTIVE] == 0) { - wp->w_hl_attr_normal = hl_combine_attr(HL_ATTR(HLF_INACTIVE), - wp->w_hl_attr_normal); - } - - for (int hlf = 0; hlf < HLF_COUNT; hlf++) { - int attr; - if (wp->w_hl_ids[hlf] != 0) { - attr = hl_get_ui_attr(hlf, wp->w_hl_ids[hlf], false); - } else { - attr = HL_ATTR(hlf); - } - wp->w_hl_attrs[hlf] = attr; - } - wp->w_float_config.shadow = false; if (wp->w_floating && wp->w_float_config.border) { for (int i = 0; i < 8; i++) { - int attr = wp->w_hl_attrs[HLF_BORDER]; + int attr = hl_def[HLF_BORDER]; if (wp->w_float_config.border_hl_ids[i]) { - attr = hl_get_ui_attr(HLF_BORDER, wp->w_float_config.border_hl_ids[i], - false); + attr = hl_get_ui_attr(ns_id, HLF_BORDER, + wp->w_float_config.border_hl_ids[i], false); HlAttrs a = syn_attr2entry(attr); if (a.hl_blend) { wp->w_float_config.shadow = true; @@ -355,6 +379,65 @@ void update_window_hl(win_T *wp, bool invalid) // shadow might cause blending check_blending(wp); + + // TODO(bfredl): this a bit ad-hoc. move it from highlight ns logic to 'winhl' + // implementation? + if (hl_def[HLF_INACTIVE] == 0) { + wp->w_hl_attr_normalnc = hl_combine_attr(HL_ATTR(HLF_INACTIVE), + wp->w_hl_attr_normal); + } else { + wp->w_hl_attr_normalnc = hl_def[HLF_INACTIVE]; + } +} + +void update_ns_hl(int ns_id) +{ + if (ns_id <= 0) { + return; + } + DecorProvider *p = get_decor_provider(ns_id, true); + if (p->hl_cached) { + return; + } + + NSHlAttr **alloc = (NSHlAttr **)pmap_ref(handle_T)(&ns_hl_attr, ns_id, true); + if (*alloc == NULL) { + *alloc = xmalloc(sizeof(**alloc)); + } + int *hl_attrs = **alloc; + + for (int hlf = 0; hlf < HLF_COUNT; hlf++) { + int id = syn_check_group(hlf_names[hlf], STRLEN(hlf_names[hlf])); + bool optional = (hlf == HLF_INACTIVE || hlf == HLF_NFLOAT); + hl_attrs[hlf] = hl_get_ui_attr(ns_id, hlf, id, optional); + } + + // NOOOO! You cannot just pretend that "Normal" is just like any other + // syntax group! It needs at least 10 layers of special casing! Noooooo! + // + // haha, tema engine go brrr + int normality = syn_check_group(S_LEN("Normal")); + hl_attrs[HLF_COUNT] = hl_get_ui_attr(ns_id, -1, normality, true); + + // hl_get_ui_attr might have invalidated the decor provider + p = get_decor_provider(ns_id, true); + p->hl_cached = true; +} + +int win_bg_attr(win_T *wp) +{ + if (ns_hl_fast < 0) { + int local = (wp == curwin) ? wp->w_hl_attr_normal : wp->w_hl_attr_normalnc; + if (local) { + return local; + } + } + + if (wp == curwin || hl_attr_active[HLF_INACTIVE] == 0) { + return hl_attr_active[HLF_COUNT]; + } else { + return hl_attr_active[HLF_INACTIVE]; + } } /// Gets HL_UNDERLINE highlight. @@ -403,7 +486,7 @@ void clear_hl_tables(bool reinit) map_destroy(int, int)(&combine_attr_entries); map_destroy(int, int)(&blend_attr_entries); map_destroy(int, int)(&blendthrough_attr_entries); - map_destroy(ColorKey, ColorItem)(&ns_hl); + map_destroy(ColorKey, ColorItem)(&ns_hls); } } @@ -437,52 +520,52 @@ int hl_combine_attr(int char_attr, int prim_attr) } HlAttrs char_aep = syn_attr2entry(char_attr); - HlAttrs spell_aep = syn_attr2entry(prim_attr); + HlAttrs prim_aep = syn_attr2entry(prim_attr); // start with low-priority attribute, and override colors if present below. HlAttrs new_en = char_aep; - if (spell_aep.cterm_ae_attr & HL_NOCOMBINE) { - new_en.cterm_ae_attr = spell_aep.cterm_ae_attr; + if (prim_aep.cterm_ae_attr & HL_NOCOMBINE) { + new_en.cterm_ae_attr = prim_aep.cterm_ae_attr; } else { - new_en.cterm_ae_attr |= spell_aep.cterm_ae_attr; + new_en.cterm_ae_attr |= prim_aep.cterm_ae_attr; } - if (spell_aep.rgb_ae_attr & HL_NOCOMBINE) { - new_en.rgb_ae_attr = spell_aep.rgb_ae_attr; + if (prim_aep.rgb_ae_attr & HL_NOCOMBINE) { + new_en.rgb_ae_attr = prim_aep.rgb_ae_attr; } else { - new_en.rgb_ae_attr |= spell_aep.rgb_ae_attr; + new_en.rgb_ae_attr |= prim_aep.rgb_ae_attr; } - if (spell_aep.cterm_fg_color > 0) { - new_en.cterm_fg_color = spell_aep.cterm_fg_color; + if (prim_aep.cterm_fg_color > 0) { + new_en.cterm_fg_color = prim_aep.cterm_fg_color; new_en.rgb_ae_attr &= ((~HL_FG_INDEXED) - | (spell_aep.rgb_ae_attr & HL_FG_INDEXED)); + | (prim_aep.rgb_ae_attr & HL_FG_INDEXED)); } - if (spell_aep.cterm_bg_color > 0) { - new_en.cterm_bg_color = spell_aep.cterm_bg_color; + if (prim_aep.cterm_bg_color > 0) { + new_en.cterm_bg_color = prim_aep.cterm_bg_color; new_en.rgb_ae_attr &= ((~HL_BG_INDEXED) - | (spell_aep.rgb_ae_attr & HL_BG_INDEXED)); + | (prim_aep.rgb_ae_attr & HL_BG_INDEXED)); } - if (spell_aep.rgb_fg_color >= 0) { - new_en.rgb_fg_color = spell_aep.rgb_fg_color; + if (prim_aep.rgb_fg_color >= 0) { + new_en.rgb_fg_color = prim_aep.rgb_fg_color; new_en.rgb_ae_attr &= ((~HL_FG_INDEXED) - | (spell_aep.rgb_ae_attr & HL_FG_INDEXED)); + | (prim_aep.rgb_ae_attr & HL_FG_INDEXED)); } - if (spell_aep.rgb_bg_color >= 0) { - new_en.rgb_bg_color = spell_aep.rgb_bg_color; + if (prim_aep.rgb_bg_color >= 0) { + new_en.rgb_bg_color = prim_aep.rgb_bg_color; new_en.rgb_ae_attr &= ((~HL_BG_INDEXED) - | (spell_aep.rgb_ae_attr & HL_BG_INDEXED)); + | (prim_aep.rgb_ae_attr & HL_BG_INDEXED)); } - if (spell_aep.rgb_sp_color >= 0) { - new_en.rgb_sp_color = spell_aep.rgb_sp_color; + if (prim_aep.rgb_sp_color >= 0) { + new_en.rgb_sp_color = prim_aep.rgb_sp_color; } - if (spell_aep.hl_blend >= 0) { - new_en.hl_blend = spell_aep.hl_blend; + if (prim_aep.hl_blend >= 0) { + new_en.hl_blend = prim_aep.hl_blend; } id = get_attr_entry((HlEntry){ .attr = new_en, .kind = kHlCombine, @@ -646,7 +729,7 @@ static int hl_cterm2rgb_color(int nr) 0x08, 0x12, 0x1C, 0x26, 0x30, 0x3A, 0x44, 0x4E, 0x58, 0x62, 0x6C, 0x76, 0x80, 0x8A, 0x94, 0x9E, 0xA8, 0xB2, 0xBC, 0xC6, 0xD0, 0xDA, 0xE4, 0xEE }; - static char_u ansi_table[16][4] = { + static uint8_t ansi_table[16][4] = { // R G B idx { 0, 0, 0, 1 }, // black { 224, 0, 0, 2 }, // dark red @@ -820,7 +903,7 @@ Dictionary hlattrs2dict(Dictionary *hl_alloc, HlAttrs ae, bool use_rgb) *hl_alloc = hl; return hl; } else { - Dictionary allocated = copy_dictionary(hl); + Dictionary allocated = copy_dictionary(hl, NULL); kv_destroy(hl); return allocated; } @@ -852,7 +935,6 @@ HlAttrs dict2hlattrs(Dict(highlight) *dict, bool use_rgb, int *link_id, Error *e CHECK_FLAG(dict, mask, strikethrough, , HL_STRIKETHROUGH); CHECK_FLAG(dict, mask, nocombine, , HL_NOCOMBINE); CHECK_FLAG(dict, mask, default, _, HL_DEFAULT); - CHECK_FLAG(dict, mask, global, , HL_GLOBAL); if (HAS_KEY(dict->fg)) { fg = object_to_color(dict->fg, "fg", true, err); @@ -895,14 +977,21 @@ HlAttrs dict2hlattrs(Dict(highlight) *dict, bool use_rgb, int *link_id, Error *e return hlattrs; } - if (HAS_KEY(dict->link)) { + if (HAS_KEY(dict->link) || HAS_KEY(dict->global_link)) { if (link_id) { - *link_id = object_to_hl_id(dict->link, "link", err); + if (HAS_KEY(dict->global_link)) { + *link_id = object_to_hl_id(dict->global_link, "link", err); + mask |= HL_GLOBAL; + } else { + *link_id = object_to_hl_id(dict->link, "link", err); + } + if (ERROR_SET(err)) { return hlattrs; } } else { - api_set_error(err, kErrorTypeValidation, "Invalid Key: 'link'"); + api_set_error(err, kErrorTypeValidation, "Invalid Key: '%s'", + HAS_KEY(dict->global_link) ? "global_link" : "link"); } } diff --git a/src/nvim/highlight.h b/src/nvim/highlight.h index ae63f83d65..50299bb91c 100644 --- a/src/nvim/highlight.h +++ b/src/nvim/highlight.h @@ -4,6 +4,7 @@ #include <stdbool.h> #include "nvim/api/private/defs.h" +#include "nvim/buffer_defs.h" #include "nvim/highlight_defs.h" #include "nvim/ui.h" @@ -11,6 +12,13 @@ # include "highlight.h.generated.h" #endif +static inline int win_hl_attr(win_T *wp, int hlf) +{ + // wp->w_ns_hl_attr might be null if we check highlights + // prior to entering redraw + return ((wp->w_ns_hl_attr && ns_hl_fast < 0) ? wp->w_ns_hl_attr : hl_attr_active)[hlf]; +} + #define HL_SET_DEFAULT_COLORS(rgb_fg, rgb_bg, rgb_sp) \ do { \ bool dark_ = (*p_bg == 'd'); \ diff --git a/src/nvim/highlight_defs.h b/src/nvim/highlight_defs.h index f41f980054..ffcb0f3f22 100644 --- a/src/nvim/highlight_defs.h +++ b/src/nvim/highlight_defs.h @@ -180,7 +180,7 @@ EXTERN const char *hlf_names[] INIT(= { [HLF_CU] = "Cursor", }); -EXTERN int highlight_attr[HLF_COUNT]; // Highl. attr for each context. +EXTERN int highlight_attr[HLF_COUNT + 1]; // Highl. attr for each context. EXTERN int highlight_attr_last[HLF_COUNT]; // copy for detecting changed groups EXTERN int highlight_user[9]; // User[1-9] attributes EXTERN int highlight_stlnc[9]; // On top of user @@ -190,6 +190,13 @@ EXTERN RgbValue normal_fg INIT(= -1); EXTERN RgbValue normal_bg INIT(= -1); EXTERN RgbValue normal_sp INIT(= -1); +EXTERN NS ns_hl_global INIT(= 0); // global highlight namespace +EXTERN NS ns_hl_win INIT(= -1); // highlight namespace for the current window +EXTERN NS ns_hl_fast INIT(= -1); // highlight namespace specified in a fast callback +EXTERN NS ns_hl_active INIT(= 0); // currently active/cached namespace + +EXTERN int *hl_attr_active INIT(= highlight_attr); + typedef enum { kHlUnknown, kHlUI, @@ -219,8 +226,15 @@ typedef struct { int link_id; int version; bool is_default; + bool link_global; } ColorItem; -#define COLOR_ITEM_INITIALIZER { .attr_id = -1, .link_id = -1, \ - .version = -1, .is_default = false } +#define COLOR_ITEM_INITIALIZER { .attr_id = -1, .link_id = -1, .version = -1, \ + .is_default = false, .link_global = false } + +/// highlight attributes with associated priorities +typedef struct { + int attr_id; + int priority; +} HlPriAttr; #endif // NVIM_HIGHLIGHT_DEFS_H diff --git a/src/nvim/highlight_group.c b/src/nvim/highlight_group.c index f6ec03fb14..ed1f0185b7 100644 --- a/src/nvim/highlight_group.c +++ b/src/nvim/highlight_group.c @@ -9,7 +9,10 @@ #include "nvim/autocmd.h" #include "nvim/charset.h" #include "nvim/cursor_shape.h" +#include "nvim/drawscreen.h" +#include "nvim/eval.h" #include "nvim/eval/vars.h" +#include "nvim/ex_docmd.h" #include "nvim/fold.h" #include "nvim/highlight.h" #include "nvim/highlight_group.h" @@ -17,7 +20,6 @@ #include "nvim/match.h" #include "nvim/option.h" #include "nvim/runtime.h" -#include "nvim/screen.h" /// \addtogroup SG_SET /// @{ @@ -77,6 +79,8 @@ typedef struct { int sg_rgb_sp_idx; ///< RGB special color index int sg_blend; ///< blend level (0-100 inclusive), -1 if unset + + int sg_parent; ///< parent of @nested.group } HlGroup; enum { @@ -181,6 +185,54 @@ static const char *highlight_init_both[] = { "default link DiagnosticSignWarn DiagnosticWarn", "default link DiagnosticSignInfo DiagnosticInfo", "default link DiagnosticSignHint DiagnosticHint", + + "default link @error Error", + "default link @text.underline Underlined", + "default link @todo Todo", + "default link @debug Debug", + + // Miscs + "default link @comment Comment", + "default link @punctuation Delimiter", + + // Constants + "default link @constant Constant", + "default link @constant.builtin Special", + "default link @constant.macro Define", + "default link @define Define", + "default link @macro Macro", + "default link @string String", + "default link @string.escape SpecialChar", + "default link @character Character", + "default link @character.special SpecialChar", + "default link @number Number", + "default link @boolean Boolean", + "default link @float Float", + + // Functions + "default link @function Function", + "default link @function.builtin Special", + "default link @function.macro Macro", + "default link @parameter Identifier", + "default link @method Function", + "default link @field Identifier", + "default link @property Identifier", + "default link @constructor Special", + + // Keywords + "default link @conditional Conditional", + "default link @repeat Repeat", + "default link @label Label", + "default link @operator Operator", + "default link @keyword Keyword", + "default link @exception Exception", + + "default link @type Type", + "default link @type.definition Typedef", + "default link @storageclass StorageClass", + "default link @structure Structure", + "default link @include Include", + "default link @preproc PreProc", NULL }; @@ -638,7 +690,7 @@ static int color_numbers_8[28] = { 0, 4, 2, 6, // Lookup the "cterm" value to be used for color with index "idx" in // color_names[]. -// "boldp" will be set to TRUE or FALSE for a foreground color when using 8 +// "boldp" will be set to kTrue or kFalse for a foreground color when using 8 // colors, otherwise it will be unchanged. int lookup_color(const int idx, const bool foreground, TriState *const boldp) { @@ -689,14 +741,14 @@ void set_hl_group(int id, HlAttrs attrs, Dict(highlight) *dict, int link_id) g->sg_cleared = false; g->sg_link = link_id; g->sg_script_ctx = current_sctx; - g->sg_script_ctx.sc_lnum += sourcing_lnum; + g->sg_script_ctx.sc_lnum += SOURCING_LNUM; g->sg_set |= SG_LINK; if (is_default) { g->sg_deflink = link_id; g->sg_deflink_sctx = current_sctx; - g->sg_deflink_sctx.sc_lnum += sourcing_lnum; + g->sg_deflink_sctx.sc_lnum += SOURCING_LNUM; } - return; + goto update; } g->sg_cleared = false; @@ -733,7 +785,7 @@ void set_hl_group(int id, HlAttrs attrs, Dict(highlight) *dict, int link_id) g->sg_blend = attrs.hl_blend; g->sg_script_ctx = current_sctx; - g->sg_script_ctx.sc_lnum += sourcing_lnum; + g->sg_script_ctx.sc_lnum += SOURCING_LNUM; g->sg_attr = hl_get_syn_attr(0, id, attrs); @@ -751,6 +803,12 @@ void set_hl_group(int id, HlAttrs attrs, Dict(highlight) *dict, int link_id) ui_mode_info_set(); } } + +update: + if (!updating_screen) { + redraw_all_later(UPD_NOT_VALID); + } + need_highlight_changed = true; } /// Handle ":highlight" command @@ -794,14 +852,14 @@ void do_highlight(const char *line, const bool forceit, const bool init) } // Isolate the name. - name_end = (const char *)skiptowhite((const char_u *)line); + name_end = (const char *)skiptowhite(line); linep = (const char *)skipwhite(name_end); // Check for "default" argument. if (strncmp(line, "default", (size_t)(name_end - line)) == 0) { dodefault = true; line = linep; - name_end = (const char *)skiptowhite((const char_u *)line); + name_end = (const char *)skiptowhite(line); linep = (const char *)skipwhite(name_end); } @@ -833,9 +891,9 @@ void do_highlight(const char *line, const bool forceit, const bool init) int to_id; HlGroup *hlgroup = NULL; - from_end = (const char *)skiptowhite((const char_u *)from_start); + from_end = (const char *)skiptowhite(from_start); to_start = (const char *)skipwhite(from_end); - to_end = (const char *)skiptowhite((const char_u *)to_start); + to_end = (const char *)skiptowhite(to_start); if (ends_excmd((uint8_t)(*from_start)) || ends_excmd((uint8_t)(*to_start))) { @@ -861,7 +919,7 @@ void do_highlight(const char *line, const bool forceit, const bool init) if (dodefault && (forceit || hlgroup->sg_deflink == 0)) { hlgroup->sg_deflink = to_id; hlgroup->sg_deflink_sctx = current_sctx; - hlgroup->sg_deflink_sctx.sc_lnum += sourcing_lnum; + hlgroup->sg_deflink_sctx.sc_lnum += SOURCING_LNUM; nlua_set_sctx(&hlgroup->sg_deflink_sctx); } } @@ -871,7 +929,7 @@ void do_highlight(const char *line, const bool forceit, const bool init) // for the group, unless '!' is used if (to_id > 0 && !forceit && !init && hl_has_settings(from_id - 1, dodefault)) { - if (sourcing_name == NULL && !dodefault) { + if (SOURCING_NAME == NULL && !dodefault) { emsg(_("E414: group has settings, highlight link ignored")); } } else if (hlgroup->sg_link != to_id @@ -882,10 +940,10 @@ void do_highlight(const char *line, const bool forceit, const bool init) } hlgroup->sg_link = to_id; hlgroup->sg_script_ctx = current_sctx; - hlgroup->sg_script_ctx.sc_lnum += sourcing_lnum; + hlgroup->sg_script_ctx.sc_lnum += SOURCING_LNUM; nlua_set_sctx(&hlgroup->sg_script_ctx); hlgroup->sg_cleared = false; - redraw_all_later(SOME_VALID); + redraw_all_later(UPD_SOME_VALID); // Only call highlight changed() once after multiple changes need_highlight_changed = true; @@ -908,10 +966,10 @@ void do_highlight(const char *line, const bool forceit, const bool init) } init_highlight(true, true); highlight_changed(); - redraw_all_later(NOT_VALID); + redraw_all_later(UPD_NOT_VALID); return; } - name_end = (const char *)skiptowhite((const char_u *)line); + name_end = (const char *)skiptowhite(line); linep = (const char *)skipwhite(name_end); } @@ -996,7 +1054,7 @@ void do_highlight(const char *line, const bool forceit, const bool init) } } else { arg_start = linep; - linep = (const char *)skiptowhite((const char_u *)linep); + linep = (const char *)skiptowhite(linep); } if (linep == arg_start) { semsg(_("E417: missing argument: %s"), key_start); @@ -1148,7 +1206,7 @@ void do_highlight(const char *line, const bool forceit, const bool init) if (dark != -1 && dark != (*p_bg == 'd') && !option_was_set("bg")) { - set_option_value("bg", 0L, (dark ? "dark" : "light"), 0); + set_option_value_give_err("bg", 0L, (dark ? "dark" : "light"), 0); reset_option_was_set("bg"); } } @@ -1262,17 +1320,17 @@ void do_highlight(const char *line, const bool forceit, const bool init) // changed ui_refresh(); } else { - // TUI and newer UIs will repaint the screen themselves. NOT_VALID + // TUI and newer UIs will repaint the screen themselves. UPD_NOT_VALID // redraw below will still handle usages of guibg=fg etc. ui_default_colors_set(); } did_highlight_changed = true; - redraw_all_later(NOT_VALID); + redraw_all_later(UPD_NOT_VALID); } else { set_hl_attr(idx); } hl_table[idx].sg_script_ctx = current_sctx; - hl_table[idx].sg_script_ctx.sc_lnum += sourcing_lnum; + hl_table[idx].sg_script_ctx.sc_lnum += SOURCING_LNUM; nlua_set_sctx(&hl_table[idx].sg_script_ctx); // Only call highlight_changed() once, after a sequence of highlight @@ -1284,7 +1342,7 @@ void do_highlight(const char *line, const bool forceit, const bool init) // redrawing. This may happen when evaluating 'statusline' changes the // StatusLine group. if (!updating_screen) { - redraw_all_later(NOT_VALID); + redraw_all_later(UPD_NOT_VALID); } need_highlight_changed = true; } @@ -1295,7 +1353,7 @@ void free_highlight(void) { ga_clear(&highlight_ga); map_destroy(cstr_t, int)(&highlight_unames); - arena_mem_free(arena_finish(&highlight_arena), NULL); + arena_mem_free(arena_finish(&highlight_arena)); } #endif @@ -1313,7 +1371,7 @@ void restore_cterm_colors(void) /// @param check_link if true also check for an existing link. /// -/// @return TRUE if highlight group "idx" has any settings. +/// @return true if highlight group "idx" has any settings. static int hl_has_settings(int idx, bool check_link) { return hl_table[idx].sg_cleared == 0 @@ -1363,7 +1421,12 @@ static void highlight_list_one(const int id) const HlGroup *sgp = &hl_table[id - 1]; // index is ID minus one bool didh = false; - if (message_filtered(sgp->sg_name)) { + if (message_filtered((char *)sgp->sg_name)) { + return; + } + + // don't list specialized groups if a parent is used instead + if (sgp->sg_parent && sgp->sg_cleared) { return; } @@ -1653,7 +1716,12 @@ static void set_hl_attr(int idx) int syn_name2id(const char *name) FUNC_ATTR_NONNULL_ALL { - return syn_name2id_len(name, STRLEN(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)); + } } /// Lookup a highlight group name and return its ID. @@ -1693,7 +1761,7 @@ int syn_name2attr(const char_u *name) return 0; } -/// Return TRUE if highlight group "name" exists. +/// Return true if highlight group "name" exists. int highlight_exists(const char *name) { return syn_name2id(name) > 0; @@ -1742,11 +1810,19 @@ static int syn_add_group(const char *name, size_t len) if (!vim_isprintc(c)) { emsg(_("E669: Unprintable character in group name")); return 0; - } else if (!ASCII_ISALNUM(c) && c != '_') { - // This is an error, but since there previously was no check only give a warning. + } else if (!ASCII_ISALNUM(c) && c != '_' && c != '.' && c != '@') { + // '.' and '@' are allowed characters for use with treesitter capture names. msg_source(HL_ATTR(HLF_W)); - msg(_("W18: Invalid character in group name")); - break; + emsg(_(e_highlight_group_name_invalid_char)); + return 0; + } + } + + int scoped_parent = 0; + if (len > 1 && name[0] == '@') { + char *delim = xmemrchr(name, '.', len); + if (delim) { + scoped_parent = syn_check_group(name, (size_t)(delim - name)); } } @@ -1765,7 +1841,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); - memset(hlgp, 0, sizeof(*hlgp)); + CLEAR_POINTER(hlgp); hlgp->sg_name = (char_u *)arena_memdupz(&highlight_arena, name, len); hlgp->sg_rgb_bg = -1; hlgp->sg_rgb_fg = -1; @@ -1775,6 +1851,9 @@ static int syn_add_group(const char *name, size_t len) hlgp->sg_rgb_sp_idx = kColorIdxNone; hlgp->sg_blend = -1; hlgp->sg_name_u = arena_memdupz(&highlight_arena, name, 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); int id = highlight_ga.ga_len; // ID is index plus one @@ -1788,11 +1867,18 @@ static int syn_add_group(const char *name, size_t len) /// @see syn_attr2entry int syn_id2attr(int hl_id) { - hl_id = syn_get_final_id(hl_id); + return syn_ns_id2attr(-1, hl_id, false); +} + +int syn_ns_id2attr(int ns_id, int hl_id, bool optional) +{ + hl_id = syn_ns_get_final_id(&ns_id, hl_id); HlGroup *sgp = &hl_table[hl_id - 1]; // index is ID minus one - int attr = ns_get_hl(-1, hl_id, false, sgp->sg_set); - if (attr >= 0) { + int attr = ns_get_hl(&ns_id, hl_id, false, sgp->sg_set); + + // if a highlight group is optional, don't use the global value + if (attr >= 0 || (optional && ns_id > 0)) { return attr; } return sgp->sg_attr; @@ -1801,10 +1887,16 @@ int syn_id2attr(int hl_id) /// Translate a group ID to the final group ID (following links). int syn_get_final_id(int hl_id) { + int id = curwin->w_ns_hl_active; + return syn_ns_get_final_id(&id, hl_id); +} + +int syn_ns_get_final_id(int *ns_id, int hl_id) +{ int count; if (hl_id > highlight_ga.ga_len || hl_id < 1) { - return 0; // Can be called from eval!! + return 0; // Can be called from eval!! } // Follow links until there is no more. @@ -1812,10 +1904,10 @@ int syn_get_final_id(int hl_id) for (count = 100; --count >= 0;) { HlGroup *sgp = &hl_table[hl_id - 1]; // index is ID minus one - // ACHTUNG: when using "tmp" attribute (no link) the function might be + // TODO(bfredl): when using "tmp" attribute (no link) the function might be // called twice. it needs be smart enough to remember attr only to // syn_id2attr time - int check = ns_get_hl(-1, hl_id, true, sgp->sg_set); + int check = ns_get_hl(ns_id, hl_id, true, sgp->sg_set); if (check == 0) { return hl_id; // how dare! it broke the link! } else if (check > 0) { @@ -1823,10 +1915,13 @@ int syn_get_final_id(int hl_id) continue; } - if (sgp->sg_link == 0 || sgp->sg_link > highlight_ga.ga_len) { + if (sgp->sg_link > 0 && sgp->sg_link <= highlight_ga.ga_len) { + hl_id = sgp->sg_link; + } else if (sgp->sg_cleared && sgp->sg_parent > 0) { + hl_id = sgp->sg_parent; + } else { break; } - hl_id = sgp->sg_link; } return hl_id; @@ -1863,7 +1958,7 @@ static void combine_stl_hlt(int id, int id_S, int id_alt, int hlcnt, int i, int HlGroup *const hlt = hl_table; if (id_alt == 0) { - memset(&hlt[hlcnt + i], 0, sizeof(HlGroup)); + CLEAR_POINTER(&hlt[hlcnt + i]); hlt[hlcnt + i].sg_cterm = highlight_attr[hlf]; hlt[hlcnt + i].sg_gui = highlight_attr[hlf]; } else { @@ -1913,19 +2008,22 @@ void highlight_changed(void) if (id == 0) { abort(); } - int final_id = syn_get_final_id(id); + int ns_id = -1; + int final_id = syn_ns_get_final_id(&ns_id, id); if (hlf == HLF_SNC) { id_SNC = final_id; } else if (hlf == HLF_S) { id_S = final_id; } - highlight_attr[hlf] = hl_get_ui_attr(hlf, final_id, + highlight_attr[hlf] = hl_get_ui_attr(ns_id, hlf, final_id, (hlf == HLF_INACTIVE || hlf == HLF_LC)); if (highlight_attr[hlf] != highlight_attr_last[hlf]) { if (hlf == HLF_MSG) { clear_cmdline = true; + HlAttrs attrs = syn_attr2entry(highlight_attr[hlf]); + msg_grid.blending = attrs.hl_blend > -1; } ui_call_hl_group_set(cstr_as_string((char *)hlf_names[hlf]), highlight_attr[hlf]); @@ -1933,6 +2031,9 @@ void highlight_changed(void) } } + // sentinel value. used when no hightlight namespace is active + highlight_attr[HLF_COUNT] = 0; + // // Setup the user highlights // @@ -1945,7 +2046,7 @@ void highlight_changed(void) hlcnt = highlight_ga.ga_len; if (id_S == -1) { // Make sure id_S is always valid to simplify code below. Use the last entry - memset(&hl_table[hlcnt + 9], 0, sizeof(HlGroup)); + CLEAR_POINTER(&hl_table[hlcnt + 9]); id_S = hlcnt + 10; } for (int i = 0; i < 9; i++) { @@ -1973,13 +2074,13 @@ void set_context_in_highlight_cmd(expand_T *xp, const char *arg) // (part of) subcommand already typed if (*arg != NUL) { - const char *p = (const char *)skiptowhite((const char_u *)arg); + 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((const char_u *)arg); + p = (const char *)skiptowhite(arg); } if (*p != NUL) { // past group name include_link = 0; @@ -1989,10 +2090,10 @@ void set_context_in_highlight_cmd(expand_T *xp, const char *arg) if (strncmp("link", arg, (unsigned)(p - arg)) == 0 || strncmp("clear", arg, (unsigned)(p - arg)) == 0) { xp->xp_pattern = skipwhite(p); - p = (const char *)skiptowhite((char_u *)xp->xp_pattern); + p = (const char *)skiptowhite(xp->xp_pattern); if (*p != NUL) { // Past first group name. xp->xp_pattern = skipwhite(p); - p = (const char *)skiptowhite((char_u *)xp->xp_pattern); + p = (const char *)skiptowhite(xp->xp_pattern); } } if (*p != NUL) { // Past group name(s). diff --git a/src/nvim/highlight_group.h b/src/nvim/highlight_group.h index 1474588889..bf6bad1a86 100644 --- a/src/nvim/highlight_group.h +++ b/src/nvim/highlight_group.h @@ -1,7 +1,8 @@ #ifndef NVIM_HIGHLIGHT_GROUP_H #define NVIM_HIGHLIGHT_GROUP_H -#include "nvim/eval.h" +#include "nvim/api/private/helpers.h" +#include "nvim/highlight_defs.h" #include "nvim/types.h" #define MAX_HL_ID 20000 // maximum value for a highlight ID. diff --git a/src/nvim/if_cscope.c b/src/nvim/if_cscope.c index 8d08c2fc19..9413abb2e1 100644 --- a/src/nvim/if_cscope.c +++ b/src/nvim/if_cscope.c @@ -18,9 +18,12 @@ #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" @@ -150,10 +153,10 @@ void set_context_in_cscope_cmd(expand_T *xp, const char *arg, cmdidx_T cmdidx) // (part of) subcommand already typed if (*arg != NUL) { - const char *p = (const char *)skiptowhite((const char_u *)arg); + const char *p = (const char *)skiptowhite(arg); if (*p != NUL) { // Past first word. xp->xp_pattern = skipwhite(p); - if (*skiptowhite((char_u *)xp->xp_pattern) != NUL) { + if (*skiptowhite(xp->xp_pattern) != NUL) { xp->xp_context = EXPAND_NOTHING; } else if (STRNICMP(arg, "add", p - arg) == 0) { xp->xp_context = EXPAND_FILES; @@ -200,19 +203,19 @@ static void do_cscope_general(exarg_T *eap, int make_split) /// Implementation of ":cscope" and ":lcscope" void ex_cscope(exarg_T *eap) { - do_cscope_general(eap, FALSE); + 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); + do_cscope_general(eap, true); } /// Implementation of ":cstag" void ex_cstag(exarg_T *eap) { - int ret = FALSE; + int ret = false; if (*eap->arg == NUL) { (void)emsg(_("E562: Usage: cstag <ident>")); @@ -275,7 +278,7 @@ void ex_cstag(exarg_T *eap) /// 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 +/// @return true if eof, false otherwise bool cs_fgets(char_u *buf, int size) FUNC_ATTR_NONNULL_ALL { @@ -415,16 +418,15 @@ static int cs_add_common(char *arg1, char *arg2, char *flags) char *fname2 = NULL; char *ppath = NULL; size_t usedlen = 0; - char_u *fbuf = NULL; + char *fbuf = NULL; // get the filename (arg1), expand it, and try to stat it fname = xmalloc(MAXPATHL + 1); - expand_env((char_u *)arg1, (char_u *)fname, MAXPATHL); + expand_env(arg1, fname, MAXPATHL); size_t len = STRLEN(fname); - fbuf = (char_u *)fname; - (void)modify_fname(":p", false, &usedlen, - &fname, (char **)&fbuf, &len); + fbuf = fname; + (void)modify_fname(":p", false, &usedlen, &fname, &fbuf, &len); if (fname == NULL) { goto add_err; } @@ -443,7 +445,7 @@ staterr: // get the prepend path (arg2), expand it, and see if it exists if (arg2 != NULL) { ppath = xmalloc(MAXPATHL + 1); - expand_env((char_u *)arg2, (char_u *)ppath, MAXPATHL); + expand_env(arg2, ppath, MAXPATHL); if (!os_path_exists((char_u *)ppath)) { goto staterr; } @@ -661,7 +663,7 @@ static char *cs_create_cmd(char *csoption, char *pattern) pat = pattern; if (search != 4 && search != 6) { while (ascii_iswhite(*pat)) { - ++pat; + pat++; } } @@ -755,14 +757,14 @@ err_closing: #endif // expand the cscope exec for env var's prog = xmalloc(MAXPATHL + 1); - expand_env(p_csprg, (char_u *)prog, MAXPATHL); + 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((char_u *)csinfo[i].ppath, (char_u *)ppath, MAXPATHL); + expand_env(csinfo[i].ppath, ppath, MAXPATHL); len += strlen(ppath); } @@ -837,7 +839,7 @@ err_closing: si.hStdOutput = stdout_wr; si.hStdError = stdout_wr; si.hStdInput = stdin_rd; - created = CreateProcess(NULL, cmd, NULL, NULL, TRUE, CREATE_NEW_CONSOLE, + created = CreateProcess(NULL, cmd, NULL, NULL, true, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi); xfree(prog); xfree(cmd); @@ -873,7 +875,7 @@ err_closing: /// 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. +/// @return true if we jump to a tag or abort, false if not. static int cs_find(exarg_T *eap) { char *opt, *pat; @@ -898,7 +900,7 @@ static int cs_find(exarg_T *eap) * 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) { + for (int i = 0; i < eap_arg_len; i++) { if (NUL == eap->arg[i]) { eap->arg[i] = ' '; } @@ -951,7 +953,7 @@ static bool cs_find_common(char *opt, char *pat, int forceit, int verbose, bool cmdletter = opt[0]; } - qfpos = vim_strchr((char *)p_csqf, cmdletter); + qfpos = vim_strchr(p_csqf, cmdletter); if (qfpos != NULL) { qfpos++; // next symbol must be + or - @@ -1104,7 +1106,7 @@ static int cs_help(exarg_T *eap) cmdp++; } - wait_return(TRUE); + wait_return(true); return CSCOPE_SUCCESS; } @@ -1278,7 +1280,7 @@ static void cs_kill_execute(size_t i, char *cname) (void)smsg_attr(HL_ATTR(HLF_R) | MSG_HIST, _("cscope connection %s closed"), cname); } - cs_release_csp(i, TRUE); + cs_release_csp(i, true); } /// Convert the cscope output into a ctags style entry (as might be found @@ -1836,7 +1838,7 @@ static void cs_release_csp(size_t i, bool freefnpp) // 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) { + for (waited = 0; waited < 40; waited++) { pid = waitpid(csinfo[i].pid, &pstat, WNOHANG); waitpid_errno = errno; if (pid != 0) { @@ -1930,7 +1932,7 @@ static int cs_reset(exarg_T *eap) pplist[i] = csinfo[i].ppath; fllist[i] = csinfo[i].flags; if (csinfo[i].fname != NULL) { - cs_release_csp(i, FALSE); + cs_release_csp(i, false); } } @@ -2033,7 +2035,7 @@ static int cs_show(exarg_T *eap) } } - wait_return(TRUE); + wait_return(false); return CSCOPE_SUCCESS; } diff --git a/src/nvim/indent.c b/src/nvim/indent.c index 271498d41a..792f4d93c4 100644 --- a/src/nvim/indent.c +++ b/src/nvim/indent.c @@ -11,6 +11,7 @@ #include "nvim/change.h" #include "nvim/charset.h" #include "nvim/cursor.h" +#include "nvim/edit.h" #include "nvim/eval.h" #include "nvim/extmark.h" #include "nvim/indent.h" @@ -24,16 +25,325 @@ #include "nvim/screen.h" #include "nvim/search.h" #include "nvim/strings.h" +#include "nvim/textformat.h" #include "nvim/undo.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "indent.c.generated.h" #endif +/// Set the integer values corresponding to the string setting of 'vartabstop'. +/// "array" will be set, caller must free it if needed. +/// +/// @return false for an error. +bool tabstop_set(char *var, long **array) +{ + long valcount = 1; + int t; + char *cp; + + if (var[0] == NUL || (var[0] == '0' && var[1] == NUL)) { + *array = NULL; + return true; + } + + for (cp = var; *cp != NUL; cp++) { + if (cp == var || cp[-1] == ',') { + char *end; + + if (strtol(cp, &end, 10) <= 0) { + if (cp != end) { + emsg(_(e_positive)); + } else { + semsg(_(e_invarg2), cp); + } + return false; + } + } + + if (ascii_isdigit(*cp)) { + continue; + } + if (cp[0] == ',' && cp > var && cp[-1] != ',' && cp[1] != NUL) { + valcount++; + continue; + } + semsg(_(e_invarg2), var); + return false; + } + + *array = (long *)xmalloc((unsigned)(valcount + 1) * sizeof(long)); + (*array)[0] = valcount; + + t = 1; + for (cp = var; *cp != NUL;) { + int n = atoi(cp); + + // Catch negative values, overflow and ridiculous big values. + if (n <= 0 || n > TABSTOP_MAX) { + semsg(_(e_invarg2), cp); + XFREE_CLEAR(*array); + return false; + } + (*array)[t++] = n; + while (*cp != NUL && *cp != ',') { + cp++; + } + if (*cp != NUL) { + cp++; + } + } + + return true; +} + +/// 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) +{ + long ts = ts_arg == 0 ? 8 : ts_arg; + colnr_T tabcol = 0; + int t; + long padding = 0; + + if (vts == NULL || vts[0] == 0) { + return (int)(ts - (col % ts)); + } + + const long tabcount = vts[0]; + + for (t = 1; t <= tabcount; t++) { + tabcol += (colnr_T)vts[t]; + if (tabcol > col) { + padding = tabcol - col; + break; + } + } + if (t > tabcount) { + padding = vts[tabcount] - ((col - tabcol) % vts[tabcount]); + } + + return (int)padding; +} + +/// Find the size of the tab that covers a particular column. +int tabstop_at(colnr_T col, long ts, long *vts) +{ + colnr_T tabcol = 0; + int t; + long tab_size = 0; + + if (vts == NULL || vts[0] == 0) { + return (int)ts; + } + + const long tabcount = vts[0]; + for (t = 1; t <= tabcount; t++) { + tabcol += (colnr_T)vts[t]; + if (tabcol > col) { + tab_size = vts[t]; + break; + } + } + if (t > tabcount) { + tab_size = vts[tabcount]; + } + + return (int)tab_size; +} + +/// Find the column on which a tab starts. +colnr_T tabstop_start(colnr_T col, long ts, long *vts) +{ + colnr_T tabcol = 0; + int t; + + if (vts == NULL || vts[0] == 0) { + return (int)((col / ts) * ts); + } + + const long tabcount = vts[0]; + for (t = 1; t <= tabcount; t++) { + tabcol += (colnr_T)vts[t]; + if (tabcol > col) { + return (int)(tabcol - vts[t]); + } + } + + const int excess = (int)(tabcol % vts[tabcount]); + return (int)(excess + ((col - excess) / vts[tabcount]) * vts[tabcount]); +} + +/// 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, + int *nspcs) +{ + int spaces = end_col - start_col; + colnr_T tabcol = 0; + long padding = 0; + int t; + long ts = ts_arg == 0 ? curbuf->b_p_ts : ts_arg; + + if (vts == NULL || vts[0] == 0) { + int tabs = 0; + + const int initspc = (int)(ts - (start_col % ts)); + if (spaces >= initspc) { + spaces -= initspc; + tabs++; + } + tabs += (int)(spaces / ts); + spaces -= (int)((spaces / ts) * ts); + + *ntabs = tabs; + *nspcs = spaces; + return; + } + + // Find the padding needed to reach the next tabstop. + const long tabcount = vts[0]; + for (t = 1; t <= tabcount; t++) { + tabcol += (colnr_T)vts[t]; + if (tabcol > start_col) { + padding = tabcol - start_col; + break; + } + } + if (t > tabcount) { + padding = vts[tabcount] - ((start_col - tabcol) % vts[tabcount]); + } + + // If the space needed is less than the padding no tabs can be used. + if (spaces < padding) { + *ntabs = 0; + *nspcs = spaces; + return; + } + + *ntabs = 1; + spaces -= (int)padding; + + // At least one tab has been used. See if any more will fit. + while (spaces != 0 && ++t <= tabcount) { + padding = vts[t]; + if (spaces < padding) { + *nspcs = spaces; + return; + } + *ntabs += 1; + spaces -= (int)padding; + } + + *ntabs += spaces / (int)vts[tabcount]; + *nspcs = spaces % (int)vts[tabcount]; +} + +/// See if two tabstop arrays contain the same values. +bool tabstop_eq(long *ts1, long *ts2) +{ + int t; + + if ((ts1 == 0 && ts2) || (ts1 && ts2 == 0)) { + return false; + } + if (ts1 == ts2) { + return true; + } + if (ts1[0] != ts2[0]) { + return false; + } + + for (t = 1; t <= ts1[0]; t++) { + if (ts1[t] != ts2[t]) { + return false; + } + } + + return true; +} + +/// Copy a tabstop array, allocating space for the new array. +int *tabstop_copy(long *oldts) +{ + long *newts; + int t; + + if (oldts == 0) { + return 0; + } + + newts = xmalloc((unsigned)(oldts[0] + 1) * sizeof(long)); + for (t = 0; t <= oldts[0]; t++) { + newts[t] = oldts[t]; + } + + return (int *)newts; +} + +/// Return a count of the number of tabstops. +int tabstop_count(long *ts) +{ + return ts != NULL ? (int)ts[0] : 0; +} + +/// Return the first tabstop, or 8 if there are no tabstops defined. +int tabstop_first(long *ts) +{ + return ts != NULL ? (int)ts[1] : 8; +} + +/// Return the effective shiftwidth value for current buffer, using the +/// 'tabstop' value when 'shiftwidth' is zero. +int get_sw_value(buf_T *buf) +{ + long result = get_sw_value_col(buf, 0); + assert(result >= 0 && result <= INT_MAX); + return (int)result; +} + +/// Idem, using "pos". +long get_sw_value_pos(buf_T *buf, pos_T *pos) +{ + pos_T save_cursor = curwin->w_cursor; + long sw_value; + + curwin->w_cursor = *pos; + sw_value = get_sw_value_col(buf, get_nolist_virtcol()); + curwin->w_cursor = save_cursor; + return sw_value; +} + +/// Idem, using the first non-black in the current line. +long get_sw_value_indent(buf_T *buf) +{ + pos_T pos = curwin->w_cursor; + + pos.col = (colnr_T)getwhitecols_curline(); + return get_sw_value_pos(buf, &pos); +} + +/// Idem, using virtual column "col". +long get_sw_value_col(buf_T *buf, colnr_T col) +{ + return buf->b_p_sw ? buf->b_p_sw + : tabstop_at(col, buf->b_p_ts, buf->b_p_vts_array); +} + +/// Return the effective softtabstop value for the current buffer, +/// using the shiftwidth value when 'softtabstop' is negative. +int get_sts_value(void) +{ + long result = curbuf->b_p_sts < 0 ? get_sw_value(curbuf) : curbuf->b_p_sts; + assert(result >= 0 && result <= INT_MAX); + return (int)result; +} + // Count the size (in window cells) of the indent in the current line. int get_indent(void) { - return get_indent_str_vtab(get_cursor_line_ptr(), + return get_indent_str_vtab((char *)get_cursor_line_ptr(), curbuf->b_p_ts, curbuf->b_p_vts_array, false); @@ -42,7 +352,7 @@ int get_indent(void) // Count the size (in window cells) of the indent in line "lnum". int get_indent_lnum(linenr_T lnum) { - return get_indent_str_vtab(ml_get(lnum), + return get_indent_str_vtab((char *)ml_get(lnum), curbuf->b_p_ts, curbuf->b_p_vts_array, false); @@ -52,7 +362,7 @@ int get_indent_lnum(linenr_T lnum) // "buf". int get_indent_buf(buf_T *buf, linenr_T lnum) { - return get_indent_str_vtab(ml_get_buf(buf, lnum, false), + return get_indent_str_vtab((char *)ml_get_buf(buf, lnum, false), curbuf->b_p_ts, buf->b_p_vts_array, false); @@ -90,7 +400,7 @@ int get_indent_str(const char_u *ptr, int ts, bool list) /// Count the size (in window cells) of the indent in line "ptr", using /// variable tabstops. /// if "list" is true, count only screen size for tabs. -int get_indent_str_vtab(const char_u *ptr, long ts, long *vts, bool list) +int get_indent_str_vtab(const char *ptr, long ts, long *vts, bool list) { int count = 0; @@ -402,7 +712,7 @@ int get_number_indent(linenr_T lnum) if ((State & MODE_INSERT) || has_format_option(FO_Q_COMS)) { lead_len = get_leader_len((char *)ml_get(lnum), NULL, false, true); } - regmatch.regprog = vim_regcomp((char *)curbuf->b_p_flp, RE_MAGIC); + regmatch.regprog = vim_regcomp(curbuf->b_p_flp, RE_MAGIC); if (regmatch.regprog != NULL) { regmatch.rm_ic = false; @@ -424,6 +734,47 @@ int get_number_indent(linenr_T lnum) return (int)col; } +/// This is called when 'breakindentopt' is changed and when a window is +/// initialized +bool briopt_check(win_T *wp) +{ + int bri_shift = 0; + int bri_min = 20; + bool bri_sbr = false; + int bri_list = 0; + + char *p = wp->w_p_briopt; + while (*p != NUL) { + 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])) { + p += 4; + bri_min = getdigits_int(&p, true, 0); + } else if (STRNCMP(p, "sbr", 3) == 0) { + p += 3; + bri_sbr = true; + } else if (STRNCMP(p, "list:", 5) == 0) { + p += 5; + bri_list = (int)getdigits(&p, false, 0); + } + if (*p != ',' && *p != NUL) { + return false; + } + if (*p == ',') { + p++; + } + } + + wp->w_briopt_shift = bri_shift; + wp->w_briopt_min = bri_min; + wp->w_briopt_sbr = bri_sbr; + wp->w_briopt_list = bri_list; + + return true; +} + // Return appropriate space number for breakindent, taking influencing // parameters into account. Window must be specified, since it is not // necessarily always the current one. @@ -449,7 +800,7 @@ int get_breakindent_win(win_T *wp, char_u *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(line, + 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); @@ -462,7 +813,7 @@ int get_breakindent_win(win_T *wp, char_u *line) // add additional indent for numbered lists if (wp->w_briopt_list != 0) { regmatch_T regmatch = { - .regprog = vim_regcomp((char *)curbuf->b_p_flp, + .regprog = vim_regcomp(curbuf->b_p_flp, RE_MAGIC + RE_STRING + RE_AUTO + RE_STRICT), }; @@ -547,7 +898,7 @@ int get_expr_indent(void) // Need to make a copy, the 'indentexpr' option could be changed while // evaluating it. - char_u *inde_copy = vim_strsave(curbuf->b_p_inde); + char_u *inde_copy = vim_strsave((char_u *)curbuf->b_p_inde); indent = (int)eval_to_number((char *)inde_copy); xfree(inde_copy); @@ -684,13 +1035,15 @@ int get_lisp_indent(void) amount = 2; } else { char_u *line = that; - - amount = 0; - - while (*that && col) { - amount += lbr_chartabsize_adv(line, &that, (colnr_T)amount); + chartabsize_T cts; + 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; + clear_chartabsize_arg(&cts); // Some keywords require "body" indenting rules (the // non-standard-lisp ones are Scheme special forms): @@ -706,10 +1059,15 @@ int get_lisp_indent(void) } firsttry = amount; - while (ascii_iswhite(*that)) { - amount += lbr_chartabsize(line, that, (colnr_T)amount); - that++; + init_chartabsize_arg(&cts, curwin, (colnr_T)(that - line), + amount, line, that); + while (ascii_iswhite(*cts.cts_ptr)) { + cts.cts_vcol += lbr_chartabsize(&cts); + cts.cts_ptr++; } + that = (char_u *)cts.cts_ptr; + amount = cts.cts_vcol; + clear_chartabsize_arg(&cts); if (*that && (*that != ';')) { // Not a comment line. @@ -722,33 +1080,38 @@ int get_lisp_indent(void) parencount = 0; quotecount = 0; + init_chartabsize_arg(&cts, curwin, + (colnr_T)(that - line), amount, line, that); if (vi_lisp || ((*that != '"') && (*that != '\'') && (*that != '#') && ((*that < '0') || (*that > '9')))) { - while (*that - && (!ascii_iswhite(*that) || quotecount || parencount) - && (!((*that == '(' || *that == '[') + while (*cts.cts_ptr + && (!ascii_iswhite(*cts.cts_ptr) || quotecount || parencount) + && (!((*cts.cts_ptr == '(' || *cts.cts_ptr == '[') && !quotecount && !parencount && vi_lisp))) { - if (*that == '"') { + if (*cts.cts_ptr == '"') { quotecount = !quotecount; } - if (((*that == '(') || (*that == '[')) && !quotecount) { + if (((*cts.cts_ptr == '(') || (*cts.cts_ptr == '[')) && !quotecount) { parencount++; } - if (((*that == ')') || (*that == ']')) && !quotecount) { + if (((*cts.cts_ptr == ')') || (*cts.cts_ptr == ']')) && !quotecount) { parencount--; } - if ((*that == '\\') && (*(that + 1) != NUL)) { - amount += lbr_chartabsize_adv(line, &that, (colnr_T)amount); + if ((*cts.cts_ptr == '\\') && (*(cts.cts_ptr + 1) != NUL)) { + cts.cts_vcol += lbr_chartabsize_adv(&cts); } - amount += lbr_chartabsize_adv(line, &that, (colnr_T)amount); + cts.cts_vcol += lbr_chartabsize_adv(&cts); } } - while (ascii_iswhite(*that)) { - amount += lbr_chartabsize(line, that, (colnr_T)amount); - that++; + while (ascii_iswhite(*cts.cts_ptr)) { + cts.cts_vcol += lbr_chartabsize(&cts); + cts.cts_ptr++; } + that = (char_u *)cts.cts_ptr; + amount = cts.cts_vcol; + clear_chartabsize_arg(&cts); if (!*that || (*that == ';')) { amount = firsttry; @@ -769,10 +1132,10 @@ static int lisp_match(char_u *p) { char_u buf[LSIZE]; int len; - char_u *word = *curbuf->b_p_lw != NUL ? curbuf->b_p_lw : p_lispwords; + char *word = (char *)(*curbuf->b_p_lw != NUL ? (char_u *)curbuf->b_p_lw : p_lispwords); while (*word != NUL) { - (void)copy_option_part((char **)&word, (char *)buf, LSIZE, ","); + (void)copy_option_part(&word, (char *)buf, LSIZE, ","); len = (int)STRLEN(buf); if ((STRNCMP(buf, p, len) == 0) && (p[len] == ' ')) { diff --git a/src/nvim/indent_c.c b/src/nvim/indent_c.c index c5e030ce25..166695ef5b 100644 --- a/src/nvim/indent_c.c +++ b/src/nvim/indent_c.c @@ -212,19 +212,17 @@ int is_pos_in_string(const char_u *line, colnr_T col) * Below "XXX" means that this function may unlock the current line. */ -/* - * Return true if the string "line" starts with a word from 'cinwords'. - */ -bool cin_is_cinword(const char_u *line) +/// @return true if the string "line" starts with a word from 'cinwords'. +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 = (char_u *)skipwhite((char *)line); + line = skipwhite((char *)line); - for (char_u *cinw = curbuf->b_p_cinw; *cinw;) { - size_t len = copy_option_part((char **)&cinw, (char *)cinw_buf, cinw_len, ","); + 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]))) { retval = true; @@ -275,10 +273,8 @@ static const char_u *cin_skipcomment(const char_u *s) return s; } -/* - * Return TRUE if there is no code at *s. White space and comments are - * not considered code. - */ +/// 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) { return *cin_skipcomment(s) == NUL; @@ -317,17 +313,17 @@ static bool cin_has_js_key(const char_u *text) if (*s == '\'' || *s == '"') { // can be 'key': or "key": quote = *s; - ++s; + s++; } if (!vim_isIDc(*s)) { // need at least one ID character return false; } while (vim_isIDc(*s)) { - ++s; + s++; } if (*s && *s == quote) { - ++s; + s++; } s = cin_skipcomment(s); @@ -383,7 +379,7 @@ bool cin_islabel(void) // XXX cursor_save = curwin->w_cursor; while (curwin->w_cursor.lnum > 1) { - --curwin->w_cursor.lnum; + curwin->w_cursor.lnum--; /* * If we're in a comment or raw string now, skip to the start of @@ -403,7 +399,7 @@ bool cin_islabel(void) // XXX } curwin->w_cursor = cursor_save; - if (cin_isterminated(line, TRUE, FALSE) + if (cin_isterminated(line, true, false) || cin_isscopedecl(line) || cin_iscase(line, true) || (cin_islabel_skip(&line) && cin_nocode(line))) { @@ -434,7 +430,7 @@ static int cin_isinit(void) for (;;) { int i, l; - for (i = 0; i < (int)ARRAY_SIZE(skip); ++i) { + for (i = 0; i < (int)ARRAY_SIZE(skip); i++) { l = (int)strlen(skip[i]); if (cin_starts_with(s, skip[i])) { s = cin_skipcomment(s + l); @@ -455,7 +451,7 @@ static int cin_isinit(void) return true; } - return FALSE; + return false; } /// Recognize a switch label: "case .*:" or "default:". @@ -465,7 +461,7 @@ bool cin_iscase(const char_u *s, bool strict) { s = cin_skipcomment(s); if (cin_starts_with(s, "case")) { - for (s += 4; *s; ++s) { + for (s += 4; *s; s++) { s = cin_skipcomment(s); if (*s == NUL) { break; @@ -519,8 +515,8 @@ bool cin_isscopedecl(const char_u *p) bool found = false; - for (char_u *cinsd = curbuf->b_p_cinsd; *cinsd;) { - const size_t len = copy_option_part((char **)&cinsd, (char *)cinsd_buf, cinsd_len, ","); + 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); if (*skip == ':' && skip[1] != ':') { @@ -588,7 +584,7 @@ static bool cin_is_cpp_namespace(const char_u *s) */ static const char_u *after_label(const char_u *l) { - for (; *l; ++l) { + for (; *l; l++) { if (*l == ':') { if (l[1] == ':') { // skip over "::" for C++ l++; @@ -680,10 +676,10 @@ static int cin_first_id_amount(void) line = get_cursor_line_ptr(); p = (char_u *)skipwhite((char *)line); - len = (int)(skiptowhite(p) - p); + 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)(skiptowhite(p) - p); + len = (int)((char_u *)skiptowhite((char *)p) - p); } if (len == 6 && STRNCMP(p, "struct", 6) == 0) { p = (char_u *)skipwhite((char *)p + 6); @@ -769,10 +765,10 @@ static int cin_ispreproc(const char_u *s) if (*skipwhite((char *)s) == '#') { return true; } - return FALSE; + return false; } -/// Return TRUE if line "*pp" at "*lnump" is a preprocessor statement or a +/// Return true if line "*pp" at "*lnump" is a preprocessor statement or a /// continuation line of a preprocessor statement. Decrease "*lnump" to the /// start and return the line in "*pp". /// Put the amount of indent in "*amount". @@ -789,7 +785,7 @@ static int cin_ispreproc_cont(const char_u **pp, linenr_T *lnump, int *amount) for (;;) { if (cin_ispreproc(line)) { - retval = TRUE; + retval = true; *lnump = lnum; break; } @@ -842,7 +838,7 @@ static char_u cin_isterminated(const char_u *s, int incl_open, int incl_comma) { char_u found_start = 0; unsigned n_open = 0; - int is_else = FALSE; + int is_else = false; s = cin_skipcomment(s); @@ -1095,14 +1091,12 @@ probablyFound: return 0; } -/* - * Return TRUE if we are at the end of a do-while. - * do - * nothing; - * while (foo - * && bar); <-- here - * Adjust the cursor to the line with "while". - */ +/// Return true if we are at the end of a do-while. +/// do +/// nothing; +/// while (foo +/// && bar); <-- here +/// Adjust the cursor to the line with "while". static int cin_iswhileofdo_end(int terminated) { const char_u *line; @@ -1133,7 +1127,7 @@ static int cin_iswhileofdo_end(int terminated) } if (cin_starts_with(s, "while")) { curwin->w_cursor.lnum = trypos->lnum; - return TRUE; + return true; } } @@ -1146,7 +1140,7 @@ static int cin_iswhileofdo_end(int terminated) p++; } } - return FALSE; + return false; } static int cin_isbreak(const char_u *p) @@ -1190,7 +1184,7 @@ static int cin_is_cpp_baseclass(cpp_baseclass_cache_T *cached) return false; } - cpp_base_class = lookfor_ctor_init = class_or_struct = FALSE; + cpp_base_class = lookfor_ctor_init = class_or_struct = false; /* Search for a line starting with '#', empty, ending in ';' or containing * '{' or '}' and start below it. This handles the following situations: @@ -1256,7 +1250,7 @@ static int cin_is_cpp_baseclass(cpp_baseclass_cache_T *cached) if (s[1] == ':') { /* skip double colon. It can't be a constructor * initialization any more */ - lookfor_ctor_init = FALSE; + lookfor_ctor_init = false; s = cin_skipcomment(s + 2); } else if (lookfor_ctor_init || class_or_struct) { /* we have something found, that looks like the start of @@ -1270,8 +1264,8 @@ static int cin_is_cpp_baseclass(cpp_baseclass_cache_T *cached) } } else if ((STRNCMP(s, "class", 5) == 0 && !vim_isIDc(s[5])) || (STRNCMP(s, "struct", 6) == 0 && !vim_isIDc(s[6]))) { - class_or_struct = TRUE; - lookfor_ctor_init = FALSE; + class_or_struct = true; + lookfor_ctor_init = false; if (*s == 'c') { s = cin_skipcomment(s + 5); @@ -1280,12 +1274,12 @@ static int cin_is_cpp_baseclass(cpp_baseclass_cache_T *cached) } } else { if (s[0] == '{' || s[0] == '}' || s[0] == ';') { - cpp_base_class = lookfor_ctor_init = class_or_struct = FALSE; + cpp_base_class = lookfor_ctor_init = class_or_struct = false; } else if (s[0] == ')') { /* Constructor-initialization is assumed if we come across * something like "):" */ - class_or_struct = FALSE; - lookfor_ctor_init = TRUE; + class_or_struct = false; + lookfor_ctor_init = true; } else if (s[0] == '?') { // Avoid seeing '() :' after '?' as constructor init. return false; @@ -1345,11 +1339,9 @@ static int get_baseclass_amount(int col) return amount; } -/* - * 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. - */ +/// 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) { const char_u *p = s; @@ -1371,12 +1363,10 @@ static int cin_ends_in(const char_u *s, const char_u *find, const char_u *ignore p++; } } - return FALSE; + return false; } -/* - * Return TRUE when "s" starts with "word" and then a non-ID character. - */ +/// 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) { int l = (int)STRLEN(word); @@ -1573,7 +1563,7 @@ static int corr_ind_maxparen(pos_T *startpos) static int find_last_paren(const char_u *l, int start, int end) { int i; - int retval = FALSE; + int retval = false; int open_count = 0; curwin->w_cursor.col = 0; // default is start of line @@ -1588,7 +1578,7 @@ static int find_last_paren(const char_u *l, int start, int end) open_count--; } else { curwin->w_cursor.col = i; - retval = TRUE; + retval = true; } } } @@ -1601,8 +1591,8 @@ static int find_last_paren(const char_u *l, int start, int end) */ void parse_cino(buf_T *buf) { - char_u *p; - char_u *l; + char *p; + char *l; int divider; int fraction = 0; int sw = get_sw_value(buf); @@ -1745,11 +1735,11 @@ void parse_cino(buf_T *buf) if (*p == '-') { p++; } - char_u *digits_start = p; // remember where the digits start - int n = getdigits_int((char **)&p, true, 0); + char *digits_start = p; // remember where the digits start + int n = getdigits_int(&p, true, 0); divider = 0; if (*p == '.') { // ".5s" means a fraction. - fraction = atoi((char *)++p); + fraction = atoi(++p); while (ascii_isdigit(*p)) { p++; if (divider) { @@ -1768,7 +1758,7 @@ void parse_cino(buf_T *buf) n += (sw * fraction + divider / 2) / divider; } } - ++p; + p++; } if (l[1] == '-') { n = -n; @@ -2029,7 +2019,7 @@ int get_c_indent(void) if (trypos == NULL && curwin->w_cursor.lnum > 1) { // There may be a statement before the comment, search from the end // of the line for a comment start. - linecomment_pos.col = check_linecomment(ml_get(curwin->w_cursor.lnum - 1)); + linecomment_pos.col = check_linecomment((char *)ml_get(curwin->w_cursor.lnum - 1)); if (linecomment_pos.col != MAXCOL) { trypos = &linecomment_pos; trypos->lnum = curwin->w_cursor.lnum - 1; @@ -2052,10 +2042,10 @@ int get_c_indent(void) char lead_start[COM_MAX_LEN]; // start-comment string char lead_middle[COM_MAX_LEN]; // middle-comment string char lead_end[COM_MAX_LEN]; // end-comment string - char_u *p; + char *p; int start_align = 0; int start_off = 0; - int done = FALSE; + int done = false; // find how indented the line beginning the comment is getvcol(curwin, comment_pos, &col, NULL, NULL); @@ -2071,11 +2061,11 @@ int get_c_indent(void) while (*p != NUL && *p != ':') { if (*p == COM_START || *p == COM_END || *p == COM_MIDDLE) { - what = *p++; + what = (unsigned char)(*p++); } else if (*p == COM_LEFT || *p == COM_RIGHT) { - align = *p++; + align = (unsigned char)(*p++); } else if (ascii_isdigit(*p) || *p == '-') { - off = getdigits_int((char **)&p, true, 0); + off = getdigits_int(&p, true, 0); } else { p++; } @@ -2084,7 +2074,7 @@ int get_c_indent(void) if (*p == ':') { p++; } - (void)copy_option_part((char **)&p, lead_end, COM_MAX_LEN, ","); + (void)copy_option_part(&p, lead_end, COM_MAX_LEN, ","); if (what == COM_START) { STRCPY(lead_start, lead_end); lead_start_len = (int)STRLEN(lead_start); @@ -2098,7 +2088,7 @@ int get_c_indent(void) * 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) { - done = TRUE; + 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 @@ -2330,7 +2320,7 @@ int get_c_indent(void) /* look for opening unmatched paren, indent one level * for each additional level */ n = 1; - for (col = 0; col < our_paren_pos.col; ++col) { + for (col = 0; col < our_paren_pos.col; col++) { switch (l[col]) { case '(': case '{': @@ -2388,7 +2378,7 @@ int get_c_indent(void) * but ignore (void) before the line (ignore_paren_col). */ col = our_paren_pos.col; while ((int)our_paren_pos.col > ignore_paren_col) { - --our_paren_pos.col; + our_paren_pos.col--; switch (*ml_get_pos(&our_paren_pos)) { case '(': amount += curbuf->b_ind_unclosed2; @@ -2562,7 +2552,7 @@ int get_c_indent(void) } } - lookfor_break = FALSE; + lookfor_break = false; if (cin_iscase(theline, false)) { // it's a switch() label lookfor = LOOKFOR_CASE; // find a previous switch() label @@ -2646,7 +2636,7 @@ int get_c_indent(void) continue; } - terminated = cin_isterminated(l, FALSE, TRUE); + terminated = cin_isterminated(l, false, true); /* * If we are at top level and the line looks like a @@ -2660,7 +2650,7 @@ int get_c_indent(void) * don't add extra indent. * TODO: does not work, if a function * declaration is split over multiple lines: - * cin_isfuncdecl returns FALSE then. + * cin_isfuncdecl returns false then. */ if (terminated == ',') { break; @@ -2862,7 +2852,7 @@ int get_c_indent(void) if (n) { amount = n; l = after_label(get_cursor_line_ptr()); - if (l != NULL && cin_is_cinword(l)) { + if (l != NULL && cin_is_cinword((char *)l)) { if (theline[0] == '{') { amount += curbuf->b_ind_open_extra; } else { @@ -2971,7 +2961,7 @@ int get_c_indent(void) * initialisation (not indented) or a variable declaration * (indented). */ - terminated = cin_isterminated(l, FALSE, TRUE); + terminated = cin_isterminated(l, false, true); if (js_cur_has_key) { js_cur_has_key = false; // only check the first line @@ -3116,7 +3106,7 @@ int get_c_indent(void) * Check if we are after an "if", "while", etc. * Also allow " } else". */ - if (cin_is_cinword(l) || cin_iselse((char_u *)skipwhite((char *)l))) { + if (cin_is_cinword((char *)l) || cin_iselse((char_u *)skipwhite((char *)l))) { // Found an unterminated line after an if (), line up // with the last one. // if (cond) @@ -3334,7 +3324,7 @@ int get_c_indent(void) amount += curbuf->b_ind_open_extra; } } - ++whilelevel; + whilelevel++; } /* * We are after a "normal" statement. @@ -3848,7 +3838,7 @@ static int find_match(int lookfor, linenr_T ourscope) * another "do", so increment whilelevel. XXX */ if (cin_iswhileofdo(look, curwin->w_cursor.lnum)) { - ++whilelevel; + whilelevel++; continue; } diff --git a/src/nvim/input.c b/src/nvim/input.c index 37c2903cfd..0aa9feaca3 100644 --- a/src/nvim/input.c +++ b/src/nvim/input.c @@ -46,7 +46,7 @@ int ask_yesno(const char *const str, const bool direct) int r = ' '; while (r != 'y' && r != 'n') { - // Same highlighting as for wait_return. + // same highlighting as for wait_return() smsg_attr(HL_ATTR(HLF_R), "%s (y/n)?", str); if (direct) { r = get_keystroke(NULL); diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c index a64d8e8f00..ba0a36cafe 100644 --- a/src/nvim/insexpand.c +++ b/src/nvim/insexpand.c @@ -12,11 +12,14 @@ #include "nvim/buffer.h" #include "nvim/change.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/ex_docmd.h" +#include "nvim/ex_eval.h" #include "nvim/ex_getln.h" #include "nvim/fileio.h" #include "nvim/getchar.h" @@ -33,14 +36,14 @@ #include "nvim/os/input.h" #include "nvim/os/time.h" #include "nvim/path.h" -#include "nvim/popupmnu.h" +#include "nvim/popupmenu.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/ui.h" #include "nvim/undo.h" #include "nvim/vim.h" @@ -138,6 +141,21 @@ struct compl_S { int cp_number; ///< sequence number }; +/// state information used for getting the next set of insert completion +/// matches. +typedef struct { + char *e_cpt; ///< current entry in 'complete' + buf_T *ins_buf; ///< buffer being scanned + pos_T *cur_match_pos; ///< current match position + pos_T prev_match_pos; ///< previous match position + bool set_match_pos; ///< save first_match_pos/last_match_pos + 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 + int dict_f; ///< "dict" is an exact file name or not +} ins_compl_next_state_T; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "insexpand.c.generated.h" #endif @@ -160,6 +178,7 @@ static char e_compldel[] = N_("E840: Completion function deleted text"); // "compl_curr_match" points to the currently selected entry. // "compl_shown_match" is different from compl_curr_match during // ins_compl_get_exp(). +// "compl_old_match" points to previous "compl_curr_match". static compl_T *compl_first_match = NULL; static compl_T *compl_curr_match = NULL; @@ -201,12 +220,15 @@ static bool compl_started = false; ///< Which Ctrl-X mode are we in? static int ctrl_x_mode = CTRL_X_NORMAL; -static int compl_matches = 0; +static int compl_matches = 0; ///< number of completion matches static char *compl_pattern = NULL; static Direction compl_direction = FORWARD; static Direction compl_shows_dir = FORWARD; static int compl_pending = 0; ///< > 1 for postponed CTRL-N static pos_T compl_startpos; +/// Length in bytes of the text being completed (this is deleted to be replaced +/// by the match.) +static int compl_length = 0; static colnr_T compl_col = 0; ///< column where the text starts ///< that is being completed static char_u *compl_orig_text = NULL; ///< text as it was before @@ -214,6 +236,20 @@ static char_u *compl_orig_text = NULL; ///< text as it was before static int compl_cont_mode = 0; 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 +#define CONT_N_ADDS 4 ///< next ^X<> will add-new or expand-current +#define CONT_S_IPOS 8 ///< next ^X<> will set initial_pos? + ///< if so, word-wise-expansion will set SOL +#define CONT_SOL 16 ///< pattern includes start of line, just for + ///< word-wise expansion, not set for ^X^L +#define CONT_LOCAL 32 ///< for ctrl_x_mode 0, ^X^P/^X^N do a local + ///< expansion, (eg use complete=.) + static bool compl_opt_refresh_always = false; static size_t spell_bad_len = 0; // length of located bad word @@ -232,7 +268,7 @@ void ins_ctrl_x(void) } // We're not sure which CTRL-X mode it will be yet ctrl_x_mode = CTRL_X_NOT_DEFINED_YET; - edit_submode = (char_u *)_(CTRL_X_MSG(ctrl_x_mode)); + edit_submode = _(CTRL_X_MSG(ctrl_x_mode)); edit_submode_pre = NULL; showmode(); } else { @@ -357,9 +393,52 @@ bool ctrl_x_mode_not_defined_yet(void) return ctrl_x_mode == CTRL_X_NOT_DEFINED_YET; } -/// Check that the "dict" or "tsr" option can be used. +/// @return true if currently in "normal" or "adding" insert completion matches state +bool compl_status_adding(void) +{ + return compl_cont_status & CONT_ADDING; +} + +/// @return true if the completion pattern includes start of line, just for +/// word-wise expansion. +bool compl_status_sol(void) +{ + return compl_cont_status & CONT_SOL; +} + +/// @return true if ^X^P/^X^N will do a local completion (i.e. use complete=.) +bool compl_status_local(void) +{ + return compl_cont_status & CONT_LOCAL; +} + +/// Clear the completion status flags +void compl_status_clear(void) +{ + compl_cont_status = 0; +} + +// @return true if completion is using the forward direction matches +static bool compl_dir_forward(void) +{ + return compl_direction == FORWARD; +} + +/// @return true if currently showing forward completion matches +static bool compl_shows_dir_forward(void) +{ + return compl_shows_dir == FORWARD; +} + +/// @return true if currently showing backward completion matches +static bool compl_shows_dir_backward(void) +{ + return compl_shows_dir == BACKWARD; +} + +/// Check that the 'dictionary' or 'thesaurus' option can be used. /// -/// @param dict_opt check "dict" when true, "tsr" when false. +/// @param dict_opt check 'dictionary' when true, 'thesaurus' when false. bool check_compl_option(bool dict_opt) { if (dict_opt @@ -443,6 +522,18 @@ bool vim_is_ctrl_x_key(int c) return false; } +/// @return true if "match" is the original text when the completion began. +static bool match_at_original_text(const compl_T *const match) +{ + return match->cp_flags & CP_ORIGINAL_TEXT; +} + +/// @return true if "match" is the first match in the completion list. +static bool is_first_match(const compl_T *const match) +{ + return match == compl_first_match; +} + /// Check that character "c" is part of the item currently being /// completed. Used to decide whether to abandon complete mode when the menu /// is visible. @@ -477,6 +568,106 @@ bool ins_compl_accept_char(int c) return vim_iswordc(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) +{ + bool has_lower = false; + bool was_letter = false; + + // 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; + for (int i = 0; i < char_len; i++) { + wca[i] = mb_ptr2char_adv(&p); + } + } + + // Rule 1: Were any chars converted to lower? + { + const char_u *p = compl_orig_text; + for (int i = 0; i < min_len; i++) { + const int c = mb_ptr2char_adv(&p); + if (mb_islower(c)) { + has_lower = true; + if (mb_isupper(wca[i])) { + // Rule 1 is satisfied. + for (i = compl_char_len; i < char_len; i++) { + wca[i] = mb_tolower(wca[i]); + } + break; + } + } + } + } + + // Rule 2: No lower case, 2nd consecutive letter converted to + // upper case. + if (!has_lower) { + const char_u *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])) { + // Rule 2 is satisfied. + for (i = compl_char_len; i < char_len; i++) { + wca[i] = mb_toupper(wca[i]); + } + break; + } + was_letter = mb_islower(c) || mb_isupper(c); + } + } + + // Copy the original case of the part we typed. + { + const char_u *p = compl_orig_text; + for (int i = 0; i < min_len; i++) { + const int c = mb_ptr2char_adv(&p); + if (mb_islower(c)) { + wca[i] = mb_tolower(wca[i]); + } else if (mb_isupper(c)) { + wca[i] = mb_toupper(wca[i]); + } + } + } + + // Generate encoding specific output from wide character array. + garray_T gap; + char *p = (char *)IObuff; + int i = 0; + ga_init(&gap, 1, 500); + while (i < char_len) { + if (gap.ga_data != NULL) { + ga_grow(&gap, 10); + 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) { + // 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 + // growarray. Add the character in the next round. + ga_grow(&gap, IOSIZE); + *p = NUL; + STRCPY(gap.ga_data, IObuff); + gap.ga_len = (int)STRLEN(IObuff); + } else { + p += utf_char2bytes(wca[i++], p); + } + } + xfree(wca); + + if (gap.ga_data != NULL) { + *tofree = gap.ga_data; + return gap.ga_data; + } + + *p = NUL; + return IObuff; +} + /// This is like ins_compl_add(), but if 'ic' and 'inf' are set, then the /// case of the originally typed text is used, and the case of the completed /// text is inferred, ie this tries to work out what case you probably wanted @@ -488,13 +679,10 @@ int ins_compl_add_infercase(char_u *str_arg, int len, bool icase, char_u *fname, FUNC_ATTR_NONNULL_ARG(1) { char_u *str = str_arg; - int i, c; - int actual_len; // Take multi-byte characters - int actual_compl_length; // into account. - int min_len; - bool has_lower = false; - bool was_letter = false; + int char_len; // count multi-byte characters + int compl_char_len; int flags = 0; + char *tofree = NULL; if (p_ic && curbuf->b_p_inf && len > 0) { // Infer case of completed part. @@ -502,101 +690,28 @@ 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; - actual_len = 0; + char_len = 0; while (*p != NUL) { MB_PTR_ADV(p); - actual_len++; + char_len++; } } // Find actual length of original text. { const char_u *p = compl_orig_text; - actual_compl_length = 0; + compl_char_len = 0; while (*p != NUL) { MB_PTR_ADV(p); - actual_compl_length++; + compl_char_len++; } } - // "actual_len" may be smaller than "actual_compl_length" when using + // "char_len" may be smaller than "compl_char_len" when using // thesaurus, only use the minimum when comparing. - min_len = actual_len < actual_compl_length - ? actual_len : actual_compl_length; - - // Allocate wide character array for the completion and fill it. - int *const wca = xmalloc((size_t)actual_len * sizeof(*wca)); - { - const char_u *p = str; - for (i = 0; i < actual_len; i++) { - wca[i] = mb_ptr2char_adv(&p); - } - } - - // Rule 1: Were any chars converted to lower? - { - const char_u *p = compl_orig_text; - for (i = 0; i < min_len; i++) { - c = mb_ptr2char_adv(&p); - if (mb_islower(c)) { - has_lower = true; - if (mb_isupper(wca[i])) { - // Rule 1 is satisfied. - for (i = actual_compl_length; i < actual_len; i++) { - wca[i] = mb_tolower(wca[i]); - } - break; - } - } - } - } - - // Rule 2: No lower case, 2nd consecutive letter converted to - // upper case. - if (!has_lower) { - const char_u *p = compl_orig_text; - for (i = 0; i < min_len; i++) { - c = mb_ptr2char_adv(&p); - if (was_letter && mb_isupper(c) && mb_islower(wca[i])) { - // Rule 2 is satisfied. - for (i = actual_compl_length; i < actual_len; i++) { - wca[i] = mb_toupper(wca[i]); - } - break; - } - was_letter = mb_islower(c) || mb_isupper(c); - } - } + int min_len = char_len < compl_char_len ? char_len : compl_char_len; - // Copy the original case of the part we typed. - { - const char_u *p = compl_orig_text; - for (i = 0; i < min_len; i++) { - c = mb_ptr2char_adv(&p); - if (mb_islower(c)) { - wca[i] = mb_tolower(wca[i]); - } else if (mb_isupper(c)) { - wca[i] = mb_toupper(wca[i]); - } - } - } - - // Generate encoding specific output from wide character array. - // Multi-byte characters can occupy up to five bytes more than - // ASCII characters, and we also need one byte for NUL, so stay - // six bytes away from the edge of IObuff. - { - char_u *p = IObuff; - i = 0; - while (i < actual_len && (p - IObuff + 6) < IOSIZE) { - p += utf_char2bytes(wca[i++], (char *)p); - } - *p = NUL; - } - - xfree(wca); - - str = IObuff; + str = ins_compl_infercase_gettext(str, char_len, compl_char_len, min_len, &tofree); } if (cont_s_ipos) { flags |= CP_CONT_S_IPOS; @@ -605,22 +720,30 @@ int ins_compl_add_infercase(char_u *str_arg, int len, bool icase, char_u *fname, flags |= CP_ICASE; } - return ins_compl_add(str, len, 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; } /// Add a match to the list of matches /// -/// @param[in] str Match to add. -/// @param[in] len Match length, -1 to use #STRLEN. -/// @param[in] fname File name match comes from. May be NULL. -/// @param[in] cptext Extra text for popup menu. May be NULL. If not NULL, -/// must have exactly #CPT_COUNT items. +/// @param[in] str text of the match to add +/// @param[in] len length of "str". If -1, then the length of "str" is computed. +/// @param[in] fname file name to associate with this match. May be NULL. +/// @param[in] cptext list of strings to use with this match (for abbr, menu, info +/// and kind). May be NULL. +/// If not NULL, must have exactly #CPT_COUNT items. /// @param[in] cptext_allocated If true, will not copy cptext strings. /// /// @note Will free strings in case of error. /// cptext itself will not be freed. -/// @param[in] cdir Completion direction. -/// @param[in] adup True if duplicate matches are to be accepted. +/// @param[in] user_data user supplied data (any vim type) for this match +/// @param[in] cdir match direction. If 0, use "compl_direction". +/// @param[in] flags_arg match flags (cp_flags) +/// @param[in] adup accept this match even if it is already present. +/// +/// If "cdir" is FORWARD, then the match is added after the current match. +/// Otherwise, it is added before the current match. /// /// @return NOTDONE if the given string is already in the list of completions, /// otherwise it is added to the list and OK is returned. FAIL will be @@ -659,14 +782,14 @@ static int ins_compl_add(char_u *const str, int len, char_u *const fname, if (compl_first_match != NULL && !adup) { match = compl_first_match; do { - if (!(match->cp_flags & CP_ORIGINAL_TEXT) + if (!match_at_original_text(match) && STRNCMP(match->cp_str, str, len) == 0 - && match->cp_str[len] == NUL) { + && ((int)STRLEN(match->cp_str) <= len || match->cp_str[len] == NUL)) { FREE_CPTEXT(cptext, cptext_allocated); return NOTDONE; } match = match->cp_next; - } while (match != NULL && match != compl_first_match); + } while (match != NULL && !is_first_match(match)); } // Remove any popup menu before changing the list of matches. @@ -719,7 +842,8 @@ static int ins_compl_add(char_u *const str, int len, char_u *const fname, match->cp_user_data = *user_data; } - // Link the new match structure in the list of matches. + // Link the new match structure after (FORWARD) or before (BACKWARD) the + // current match in the list of matches . if (compl_first_match == NULL) { match->cp_next = match->cp_prev = NULL; } else if (dir == FORWARD) { @@ -775,9 +899,10 @@ static void ins_compl_longest_match(compl_T *match) if (compl_leader == NULL) { // First match, use it as a whole. compl_leader = vim_strsave(match->cp_str); + had_match = (curwin->w_cursor.col > compl_col); ins_compl_delete(); - ins_bytes(compl_leader + get_compl_len()); + ins_bytes((char *)compl_leader + get_compl_len()); ins_redraw(false); // When the match isn't there (to avoid matching itself) remove it @@ -786,40 +911,42 @@ static void ins_compl_longest_match(compl_T *match) ins_compl_delete(); } compl_used_match = false; - } else { - // Reduce the text if this match differs from compl_leader. - p = compl_leader; - s = match->cp_str; - while (*p != NUL) { - c1 = utf_ptr2char((char *)p); - c2 = utf_ptr2char((char *)s); - - if ((match->cp_flags & CP_ICASE) - ? (mb_tolower(c1) != mb_tolower(c2)) - : (c1 != c2)) { - break; - } - MB_PTR_ADV(p); - MB_PTR_ADV(s); - } - if (*p != NUL) { - // Leader was shortened, need to change the inserted text. - *p = NUL; - had_match = (curwin->w_cursor.col > compl_col); - ins_compl_delete(); - ins_bytes(compl_leader + get_compl_len()); - ins_redraw(false); + return; + } - // When the match isn't there (to avoid matching itself) remove it - // again after redrawing. - if (!had_match) { - ins_compl_delete(); - } + // Reduce the text if this match differs from compl_leader. + p = compl_leader; + s = match->cp_str; + while (*p != NUL) { + c1 = utf_ptr2char((char *)p); + c2 = utf_ptr2char((char *)s); + + if ((match->cp_flags & CP_ICASE) + ? (mb_tolower(c1) != mb_tolower(c2)) + : (c1 != c2)) { + break; } + MB_PTR_ADV(p); + MB_PTR_ADV(s); + } - compl_used_match = false; + if (*p != NUL) { + // Leader was shortened, need to change the inserted text. + *p = NUL; + had_match = (curwin->w_cursor.col > compl_col); + ins_compl_delete(); + ins_bytes((char *)compl_leader + get_compl_len()); + ins_redraw(false); + + // When the match isn't there (to avoid matching itself) remove it + // again after redrawing. + if (!had_match) { + ins_compl_delete(); + } } + + compl_used_match = false; } /// Add an array of matches to the list of matches. @@ -844,20 +971,21 @@ static void ins_compl_add_matches(int num_matches, char **matches, int icase) /// Return the number of matches (excluding the original). static int ins_compl_make_cyclic(void) { - compl_T *match; - int count = 0; + if (compl_first_match == NULL) { + return 0; + } - if (compl_first_match != NULL) { - // Find the end of the list. - match = compl_first_match; - // there's always an entry for the compl_orig_text, it doesn't count. - while (match->cp_next != NULL && match->cp_next != compl_first_match) { - match = match->cp_next; - count++; - } - match->cp_next = compl_first_match; - compl_first_match->cp_prev = match; + // Find the end of the list. + compl_T *match = compl_first_match; + int count = 0; + // there's always an entry for the compl_orig_text, it doesn't count. + while (match->cp_next != NULL && !is_first_match(match->cp_next)) { + match = match->cp_next; + count++; } + match->cp_next = compl_first_match; + compl_first_match->cp_prev = match; + return count; } @@ -880,10 +1008,10 @@ void completeopt_was_set(void) { compl_no_insert = false; compl_no_select = false; - if (strstr((char *)p_cot, "noselect") != NULL) { + if (strstr(p_cot, "noselect") != NULL) { compl_no_select = true; } - if (strstr((char *)p_cot, "noinsert") != NULL) { + if (strstr(p_cot, "noinsert") != NULL) { compl_no_insert = true; } } @@ -896,10 +1024,12 @@ static int compl_match_arraysize; /// Remove any popup menu. static void ins_compl_del_pum(void) { - if (compl_match_array != NULL) { - pum_undisplay(false); - XFREE_CLEAR(compl_match_array); + if (compl_match_array == NULL) { + return; } + + pum_undisplay(false); + XFREE_CLEAR(compl_match_array); } /// Check if the popup menu should be displayed. @@ -907,7 +1037,7 @@ bool pum_wanted(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { // "completeopt" must contain "menu" or "menuone" - return vim_strchr((char *)p_cot, 'm') != NULL; + return vim_strchr(p_cot, 'm') != NULL; } /// Check that there are two or more matches to be shown in the popup menu. @@ -917,17 +1047,16 @@ static bool pum_enough_matches(void) { // Don't display the popup menu if there are no matches or there is only // one (ignoring the original text). - compl_T *comp = compl_first_match; + compl_T *compl = compl_first_match; int i = 0; do { - if (comp == NULL - || ((comp->cp_flags & CP_ORIGINAL_TEXT) == 0 && ++i == 2)) { + if (compl == NULL || (!match_at_original_text(compl) && ++i == 2)) { break; } - comp = comp->cp_next; - } while (comp != compl_first_match); + compl = compl->cp_next; + } while (!is_first_match(compl)); - if (strstr((char *)p_cot, "menuone") != NULL) { + if (strstr(p_cot, "menuone") != NULL) { return i >= 1; } return i >= 2; @@ -951,6 +1080,8 @@ static dict_T *ins_compl_dict_alloc(compl_T *match) return dict; } +/// Trigger the CompleteChanged autocmd event. Invoked each time the Insert mode +/// completion menu is changed. static void trigger_complete_changed_event(int cur) { static bool recursive = false; @@ -979,141 +1110,151 @@ static void trigger_complete_changed_event(int cur) restore_v_event(v_event, &save_v_event); } -/// Show the popup menu for the list of matches. -/// Also adjusts "compl_shown_match" to an entry that is actually displayed. -void ins_compl_show_pum(void) +/// Build a popup menu to show the completion matches. +/// +/// @return the popup menu entry that should be selected, +/// -1 if nothing should be selected. +static int ins_compl_build_pum(void) { - compl_T *compl; - compl_T *shown_compl = NULL; - bool did_find_shown_match = false; - bool shown_match_ok = false; - int i; - int cur = -1; - colnr_T col; - int lead_len = 0; - bool array_changed = false; + // Need to build the popup menu list. + compl_match_arraysize = 0; + compl_T *compl = compl_first_match; - if (!pum_wanted() || !pum_enough_matches()) { - return; + // If it's user complete function and refresh_always, + // do not use "compl_leader" as prefix filter. + if (ins_compl_need_restart()) { + XFREE_CLEAR(compl_leader); } - // Dirty hard-coded hack: remove any matchparen highlighting. - do_cmdline_cmd("if exists('g:loaded_matchparen')|3match none|endif"); + const int lead_len = compl_leader != NULL ? (int)STRLEN(compl_leader) : 0; - // Update the screen before drawing the popup menu over it. - update_screen(0); - - if (compl_match_array == NULL) { - array_changed = true; - // Need to build the popup menu list. - compl_match_arraysize = 0; - compl = compl_first_match; - // If it's user complete function and refresh_always, - // do not use "compl_leader" as prefix filter. - if (ins_compl_need_restart()) { - XFREE_CLEAR(compl_leader); - } - if (compl_leader != NULL) { - lead_len = (int)STRLEN(compl_leader); - } - do { - if ((compl->cp_flags & CP_ORIGINAL_TEXT) == 0 - && (compl_leader == NULL - || ins_compl_equal(compl, compl_leader, (size_t)lead_len))) { - compl_match_arraysize++; - } - compl = compl->cp_next; - } while (compl != NULL && compl != compl_first_match); - if (compl_match_arraysize == 0) { - return; + do { + if (!match_at_original_text(compl) + && (compl_leader == NULL + || ins_compl_equal(compl, compl_leader, (size_t)lead_len))) { + compl_match_arraysize++; } + compl = compl->cp_next; + } while (compl != NULL && !is_first_match(compl)); - assert(compl_match_arraysize >= 0); - compl_match_array = xcalloc((size_t)compl_match_arraysize, sizeof(pumitem_T)); - // If the current match is the original text don't find the first - // match after it, don't highlight anything. - if (compl_shown_match->cp_flags & CP_ORIGINAL_TEXT) { - shown_match_ok = true; - } + if (compl_match_arraysize == 0) { + return -1; + } - i = 0; - compl = compl_first_match; - do { - if ((compl->cp_flags & CP_ORIGINAL_TEXT) == 0 - && (compl_leader == NULL - || 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 - // first displayed item after the shown match. - compl_shown_match = compl; - did_find_shown_match = true; - shown_match_ok = true; - } else { - // Remember this displayed match for when the - // shown match is just below it. - shown_compl = compl; - } - cur = i; - } + assert(compl_match_arraysize >= 0); + compl_match_array = xcalloc((size_t)compl_match_arraysize, sizeof(pumitem_T)); - if (compl->cp_text[CPT_ABBR] != NULL) { - compl_match_array[i].pum_text = - compl->cp_text[CPT_ABBR]; - } else { - compl_match_array[i].pum_text = compl->cp_str; - } - 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 = - compl->cp_text[CPT_MENU]; + // If the current match is the original text don't find the first + // match after it, don't highlight anything. + bool shown_match_ok = match_at_original_text(compl_shown_match); + + compl_T *shown_compl = NULL; + bool did_find_shown_match = false; + int cur = -1; + int i = 0; + compl = compl_first_match; + do { + if (!match_at_original_text(compl) + && (compl_leader == NULL + || 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 + // first displayed item after the shown match. + compl_shown_match = compl; + did_find_shown_match = true; + shown_match_ok = true; } else { - compl_match_array[i++].pum_extra = compl->cp_fname; + // Remember this displayed match for when the + // shown match is just below it. + shown_compl = compl; } + cur = i; } - if (compl == compl_shown_match) { - did_find_shown_match = true; + if (compl->cp_text[CPT_ABBR] != NULL) { + compl_match_array[i].pum_text = compl->cp_text[CPT_ABBR]; + } else { + compl_match_array[i].pum_text = compl->cp_str; + } + 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 = compl->cp_text[CPT_MENU]; + } else { + compl_match_array[i++].pum_extra = compl->cp_fname; + } + } - // When the original text is the shown match don't set - // compl_shown_match. - if (compl->cp_flags & CP_ORIGINAL_TEXT) { - shown_match_ok = true; - } + if (compl == compl_shown_match) { + did_find_shown_match = true; - if (!shown_match_ok && shown_compl != NULL) { - // The shown match isn't displayed, set it to the - // previously displayed match. - compl_shown_match = shown_compl; - shown_match_ok = true; - } + // When the original text is the shown match don't set + // compl_shown_match. + if (match_at_original_text(compl)) { + shown_match_ok = true; } - compl = compl->cp_next; - } while (compl != NULL && compl != compl_first_match); - if (!shown_match_ok) { // no displayed match at all - cur = -1; + if (!shown_match_ok && shown_compl != NULL) { + // The shown match isn't displayed, set it to the + // previously displayed match. + compl_shown_match = shown_compl; + shown_match_ok = true; + } } + compl = compl->cp_next; + } while (compl != NULL && !is_first_match(compl)); + + if (!shown_match_ok) { // no displayed match at all + cur = -1; + } + + return cur; +} + +/// Show the popup menu for the list of matches. +/// Also adjusts "compl_shown_match" to an entry that is actually displayed. +void ins_compl_show_pum(void) +{ + if (!pum_wanted() || !pum_enough_matches()) { + return; + } + + // Dirty hard-coded hack: remove any matchparen highlighting. + do_cmdline_cmd("if exists('g:loaded_matchparen')|3match none|endif"); + + // Update the screen before drawing the popup menu over it. + update_screen(0); + + int cur = -1; + bool array_changed = false; + + if (compl_match_array == NULL) { + array_changed = true; + // Need to build the popup menu list. + cur = ins_compl_build_pum(); } else { // popup menu already exists, only need to find the current item. - for (i = 0; i < compl_match_arraysize; i++) { + for (int i = 0; i < compl_match_arraysize; i++) { 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]) { + || compl_match_array[i].pum_text == compl_shown_match->cp_text[CPT_ABBR]) { cur = i; break; } } } + if (compl_match_array == NULL) { + return; + } + // In Replace mode when a $ is displayed at the end of the line only // part of the screen would be updated. We do need to redraw here. dollar_vcol = -1; // Compute the screen column of the start of the completed text. // Use the cursor to get all wrapping and other settings right. - col = curwin->w_cursor.col; + const colnr_T col = curwin->w_cursor.col; curwin->w_cursor.col = compl_col; pum_selected_item = cur; pum_display(compl_match_array, compl_match_arraysize, cur, array_changed, 0); @@ -1127,8 +1268,8 @@ void ins_compl_show_pum(void) #define DICT_FIRST (1) ///< use just first element in "dict" #define DICT_EXACT (2) ///< "dict" is the exact name of a file -/// Add any identifiers that match the given pattern in the list of dictionary -/// files "dict_start" to the list of completions. +/// Add any identifiers that match the given pattern "pat" in the list of +/// dictionary files "dict_start" to the list of completions. /// /// @param flags DICT_FIRST and/or DICT_EXACT /// @param thesaurus Thesaurus completion @@ -1229,6 +1370,54 @@ theend: xfree(buf); } +/// Add all the words in the line "*buf_arg" from the thesaurus file "fname" +/// 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) +{ + int status = OK; + + // Add the other matches on the line + char_u *ptr = *buf_arg; + while (!got_int) { + // Find start of the next word. Skip white + // space and punctuation. + ptr = find_word_start(ptr); + if (*ptr == NUL || *ptr == NL) { + break; + } + char_u *wstart = ptr; + + // Find end of the word. + // Japanese words may have characters in + // different classes, only separate words + // with single-byte non-word characters. + while (*ptr != NUL) { + const int l = utfc_ptr2len((const char *)ptr); + + if (l < 2 && !vim_iswordc(*ptr)) { + break; + } + ptr += l; + } + + // 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); + if (status == FAIL) { + break; + } + } + } + + *buf_arg = ptr; + return status; +} + +/// 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) FUNC_ATTR_NONNULL_ARG(2, 7) @@ -1253,8 +1442,7 @@ static void ins_compl_files(int count, char **files, int thesaurus, int flags, r // Read dictionary file line by line. // Check each line for a match. - while (!got_int && !compl_interrupted - && !vim_fgets(buf, LSIZE, fp)) { + while (!got_int && !compl_interrupted && !vim_fgets(buf, LSIZE, fp)) { ptr = buf; while (vim_regexec(regmatch, (char *)buf, (colnr_T)(ptr - buf))) { ptr = regmatch->startp[0]; @@ -1267,38 +1455,10 @@ static void ins_compl_files(int count, char **files, int thesaurus, int flags, r (int)(ptr - regmatch->startp[0]), p_ic, (char_u *)files[i], *dir, false); if (thesaurus) { - char_u *wstart; - - // Add the other matches on the line + // For a thesaurus, add all the words in the line ptr = buf; - while (!got_int) { - // Find start of the next word. Skip white - // space and punctuation. - ptr = find_word_start(ptr); - if (*ptr == NUL || *ptr == NL) { - break; - } - wstart = ptr; - - // Find end of the word. - // Japanese words may have characters in - // different classes, only separate words - // with single-byte non-word characters. - while (*ptr != NUL) { - const int l = utfc_ptr2len((char *)ptr); - - if (l < 2 && !vim_iswordc(*ptr)) { - break; - } - ptr += l; - } - - // Add the word. Skip the regexp match. - if (wstart != regmatch->startp[0]) { - add_r = ins_compl_add_infercase(wstart, (int)(ptr - wstart), - p_ic, (char_u *)files[i], *dir, false); - } - } + 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 @@ -1389,12 +1549,13 @@ static void ins_compl_free(void) } tv_clear(&match->cp_user_data); xfree(match); - } while (compl_curr_match != NULL && compl_curr_match != compl_first_match); + } while (compl_curr_match != NULL && !is_first_match(compl_curr_match)); compl_first_match = compl_curr_match = NULL; compl_shown_match = NULL; compl_old_match = NULL; } +/// Reset/clear the completion state. void ins_compl_clear(void) { compl_cont_status = 0; @@ -1448,6 +1609,12 @@ colnr_T ins_compl_col(void) return compl_col; } +/// Return the length in bytes of the text being completed +int ins_compl_len(void) +{ + return compl_length; +} + /// Delete one character before the cursor and show the subset of the matches /// that match the word that is now before the cursor. /// Returns the character to be used, NUL if the work is done and another char @@ -1483,12 +1650,12 @@ int ins_compl_bs(void) xfree(compl_leader); compl_leader = vim_strnsave(line + compl_col, (size_t)(p_off - (ptrdiff_t)compl_col)); + ins_compl_new_leader(); if (compl_shown_match != NULL) { // Make sure current match is not a hidden item. compl_curr_match = compl_shown_match; } - return NUL; } @@ -1511,7 +1678,7 @@ static void ins_compl_new_leader(void) { ins_compl_del_pum(); ins_compl_delete(); - ins_bytes(compl_leader + get_compl_len()); + ins_bytes((char *)compl_leader + get_compl_len()); compl_used_match = false; if (compl_started) { @@ -1565,7 +1732,7 @@ void ins_compl_addleader(int c) utf_char2bytes(c, (char *)buf); buf[cc] = NUL; - ins_char_bytes((char_u *)buf, (size_t)cc); + ins_char_bytes(buf, (size_t)cc); } else { ins_char(c); } @@ -1601,13 +1768,13 @@ static void ins_compl_set_original_text(char_u *str) FUNC_ATTR_NONNULL_ALL { // Replace the original text entry. - // The CP_ORIGINAL_TEXT flag is either at the first item or might possibly be - // at the last item for backward completion - if (compl_first_match->cp_flags & CP_ORIGINAL_TEXT) { // safety check + // The CP_ORIGINAL_TEXT flag is either at the first item or might possibly + // be at the last item for backward completion + if (match_at_original_text(compl_first_match)) { // safety check xfree(compl_first_match->cp_str); compl_first_match->cp_str = vim_strsave(str); } else if (compl_first_match->cp_prev != NULL - && (compl_first_match->cp_prev->cp_flags & CP_ORIGINAL_TEXT)) { + && match_at_original_text(compl_first_match->cp_prev)) { xfree(compl_first_match->cp_prev->cp_str); compl_first_match->cp_prev->cp_str = vim_strsave(str); } @@ -1626,20 +1793,20 @@ void ins_compl_addfrommatch(void) 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 (compl_shown_match->cp_flags & CP_ORIGINAL_TEXT) { - p = NULL; - for (cp = compl_shown_match->cp_next; cp != NULL - && cp != compl_first_match; cp = cp->cp_next) { - if (compl_leader == NULL - || ins_compl_equal(cp, compl_leader, STRLEN(compl_leader))) { - p = cp->cp_str; - break; - } - } - if (p == NULL || (int)STRLEN(p) <= len) { - return; + if (!match_at_original_text(compl_shown_match)) { + return; + } + + p = NULL; + 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, compl_leader, STRLEN(compl_leader))) { + p = cp->cp_str; + break; } - } else { + } + if (p == NULL || (int)STRLEN(p) <= len) { return; } } @@ -1648,6 +1815,249 @@ void ins_compl_addfrommatch(void) ins_compl_addleader(c); } +/// Set the CTRL-X completion mode based on the key "c" typed after a CTRL-X. +/// Uses the global variables: ctrl_x_mode, edit_submode, edit_submode_pre, +/// compl_cont_mode and compl_cont_status. +/// +/// @return true when the character is not to be inserted. +static bool set_ctrl_x_mode(const int c) +{ + bool retval = false; + + switch (c) { + case Ctrl_E: + case Ctrl_Y: + // scroll the window one line up or down + ctrl_x_mode = CTRL_X_SCROLL; + if (!(State & REPLACE_FLAG)) { + edit_submode = _(" (insert) Scroll (^E/^Y)"); + } else { + edit_submode = _(" (replace) Scroll (^E/^Y)"); + } + edit_submode_pre = NULL; + showmode(); + break; + case Ctrl_L: + // complete whole line + ctrl_x_mode = CTRL_X_WHOLE_LINE; + break; + case Ctrl_F: + // complete filenames + ctrl_x_mode = CTRL_X_FILES; + break; + case Ctrl_K: + // complete words from a dictionary + ctrl_x_mode = CTRL_X_DICTIONARY; + break; + case Ctrl_R: + // Register insertion without exiting CTRL-X mode + // Simply allow ^R to happen without affecting ^X mode + break; + case Ctrl_T: + // complete words from a thesaurus + ctrl_x_mode = CTRL_X_THESAURUS; + break; + case Ctrl_U: + // user defined completion + ctrl_x_mode = CTRL_X_FUNCTION; + break; + case Ctrl_O: + // omni completion + ctrl_x_mode = CTRL_X_OMNI; + break; + case 's': + case Ctrl_S: + // complete spelling suggestions + ctrl_x_mode = CTRL_X_SPELL; + emsg_off++; // Avoid getting the E756 error twice. + spell_back_to_badword(); + emsg_off--; + break; + case Ctrl_RSB: + // complete tag names + ctrl_x_mode = CTRL_X_TAGS; + break; + case Ctrl_I: + case K_S_TAB: + // complete keywords from included files + ctrl_x_mode = CTRL_X_PATH_PATTERNS; + break; + case Ctrl_D: + // complete definitions from included files + ctrl_x_mode = CTRL_X_PATH_DEFINES; + break; + case Ctrl_V: + case Ctrl_Q: + // complete vim commands + ctrl_x_mode = CTRL_X_CMDLINE; + break; + case Ctrl_Z: + // stop completion + ctrl_x_mode = CTRL_X_NORMAL; + edit_submode = NULL; + showmode(); + retval = true; + break; + case Ctrl_P: + case Ctrl_N: + // ^X^P means LOCAL expansion if nothing interrupted (eg we + // just started ^X mode, or there were enough ^X's to cancel + // the previous mode, say ^X^F^X^X^P or ^P^X^X^X^P, see below) + // do normal expansion when interrupting a different mode (say + // ^X^F^X^P or ^P^X^X^P, see below) + // nothing changes if interrupting mode 0, (eg, the flag + // doesn't change when going to ADDING mode -- Acevedo + if (!(compl_cont_status & CONT_INTRPT)) { + compl_cont_status |= CONT_LOCAL; + } else if (compl_cont_mode != 0) { + compl_cont_status &= ~CONT_LOCAL; + } + FALLTHROUGH; + default: + // If we have typed at least 2 ^X's... for modes != 0, we set + // compl_cont_status = 0 (eg, as if we had just started ^X + // mode). + // For mode 0, we set "compl_cont_mode" to an impossible + // value, in both cases ^X^X can be used to restart the same + // mode (avoiding ADDING mode). + // Undocumented feature: In a mode != 0 ^X^P and ^X^X^P start + // 'complete' and local ^P expansions respectively. + // In mode 0 an extra ^X is needed since ^X^P goes to ADDING + // mode -- Acevedo + if (c == Ctrl_X) { + if (compl_cont_mode != 0) { + compl_cont_status = 0; + } else { + compl_cont_mode = CTRL_X_NOT_DEFINED_YET; + } + } + ctrl_x_mode = CTRL_X_NORMAL; + edit_submode = NULL; + showmode(); + break; + } + + return retval; +} + +/// Stop insert completion mode +static bool ins_compl_stop(const int c, const int prev_mode, bool retval) +{ + // Get here when we have finished typing a sequence of ^N and + // ^P or other completion characters in CTRL-X mode. Free up + // memory that was used, and make sure we can redo the insert. + if (compl_curr_match != NULL || compl_leader != NULL || c == Ctrl_E) { + // If any of the original typed text has been changed, eg when + // ignorecase is set, we must add back-spaces to the redo + // buffer. We add as few as necessary to delete just the part + // 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; + if (compl_curr_match != NULL && compl_used_match && c != Ctrl_E) { + ptr = compl_curr_match->cp_str; + } else { + ptr = NULL; + } + ins_compl_fixRedoBufForLeader(ptr); + } + + bool want_cindent = (get_can_cindent() && cindent_on()); + + // When completing whole lines: fix indent for 'cindent'. + // Otherwise, break line if it's too long. + if (compl_cont_mode == CTRL_X_WHOLE_LINE) { + // re-indent the current line + if (want_cindent) { + do_c_expr_indent(); + want_cindent = false; // don't do it again + } + } else { + const int prev_col = curwin->w_cursor.col; + + // put the cursor on the last char, for 'tw' formatting + if (prev_col > 0) { + dec_cursor(); + } + + // only format when something was inserted + if (!arrow_used && !ins_need_undo_get() && c != Ctrl_E) { + insertchar(NUL, 0, -1); + } + + if (prev_col > 0 + && get_cursor_line_ptr()[curwin->w_cursor.col] != NUL) { + inc_cursor(); + } + } + + // If the popup menu is displayed pressing CTRL-Y means accepting + // the selection without inserting anything. When + // compl_enter_selects is set the Enter key does the same. + if ((c == Ctrl_Y || (compl_enter_selects + && (c == CAR || c == K_KENTER || c == NL))) + && pum_visible()) { + retval = true; + } + + // CTRL-E means completion is Ended, go back to the typed text. + // but only do this, if the Popup is still visible + if (c == Ctrl_E) { + ins_compl_delete(); + char_u *p = NULL; + if (compl_leader != NULL) { + p = compl_leader; + } else if (compl_first_match != NULL) { + p = compl_orig_text; + } + if (p != NULL) { + const int compl_len = get_compl_len(); + const int len = (int)STRLEN(p); + if (len > compl_len) { + ins_bytes_len((char *)p + compl_len, (size_t)(len - compl_len)); + } + } + retval = true; + } + + auto_format(false, true); + + // Trigger the CompleteDonePre event to give scripts a chance to + // act upon the completion before clearing the info, and restore + // ctrl_x_mode, so that complete_info() can be used. + ctrl_x_mode = prev_mode; + ins_apply_autocmds(EVENT_COMPLETEDONEPRE); + + ins_compl_free(); + compl_started = false; + compl_matches = 0; + if (!shortmess(SHM_COMPLETIONMENU)) { + msg_clr_cmdline(); // necessary for "noshowmode" + } + ctrl_x_mode = CTRL_X_NORMAL; + compl_enter_selects = false; + if (edit_submode != NULL) { + edit_submode = NULL; + showmode(); + } + + if (c == Ctrl_C && cmdwin_type != 0) { + // Avoid the popup menu remains displayed when leaving the + // command line window. + update_screen(0); + } + + // Indent now if a key was typed that is in 'cinkeys'. + if (want_cindent && in_cinkeys(KEY_COMPLETE, ' ', inindent(0))) { + do_c_expr_indent(); + } + // Trigger the CompleteDone event to give scripts a chance to act + // upon the end of completion. + ins_apply_autocmds(EVENT_COMPLETEDONE); + + return retval; +} + /// Prepare for Insert mode completion, or stop it. /// Called just after typing a character in Insert mode. /// @@ -1656,7 +2066,6 @@ void ins_compl_addfrommatch(void) /// @return true when the character is not to be inserted; bool ins_compl_prep(int c) { - char_u *ptr; bool retval = false; const int prev_mode = ctrl_x_mode; @@ -1696,111 +2105,14 @@ bool ins_compl_prep(int c) // Set "compl_get_longest" when finding the first matches. if (ctrl_x_mode_not_defined_yet() || (ctrl_x_mode_normal() && !compl_started)) { - compl_get_longest = (strstr((char *)p_cot, "longest") != NULL); + compl_get_longest = (strstr(p_cot, "longest") != NULL); compl_used_match = true; } if (ctrl_x_mode_not_defined_yet()) { // We have just typed CTRL-X and aren't quite sure which CTRL-X mode // it will be yet. Now we decide. - switch (c) { - case Ctrl_E: - case Ctrl_Y: - ctrl_x_mode = CTRL_X_SCROLL; - if (!(State & REPLACE_FLAG)) { - edit_submode = (char_u *)_(" (insert) Scroll (^E/^Y)"); - } else { - edit_submode = (char_u *)_(" (replace) Scroll (^E/^Y)"); - } - edit_submode_pre = NULL; - showmode(); - break; - case Ctrl_L: - ctrl_x_mode = CTRL_X_WHOLE_LINE; - break; - case Ctrl_F: - ctrl_x_mode = CTRL_X_FILES; - break; - case Ctrl_K: - ctrl_x_mode = CTRL_X_DICTIONARY; - break; - case Ctrl_R: - // Simply allow ^R to happen without affecting ^X mode - break; - case Ctrl_T: - ctrl_x_mode = CTRL_X_THESAURUS; - break; - case Ctrl_U: - ctrl_x_mode = CTRL_X_FUNCTION; - break; - case Ctrl_O: - ctrl_x_mode = CTRL_X_OMNI; - break; - case 's': - case Ctrl_S: - ctrl_x_mode = CTRL_X_SPELL; - emsg_off++; // Avoid getting the E756 error twice. - spell_back_to_badword(); - emsg_off--; - break; - case Ctrl_RSB: - ctrl_x_mode = CTRL_X_TAGS; - break; - case Ctrl_I: - case K_S_TAB: - ctrl_x_mode = CTRL_X_PATH_PATTERNS; - break; - case Ctrl_D: - ctrl_x_mode = CTRL_X_PATH_DEFINES; - break; - case Ctrl_V: - case Ctrl_Q: - ctrl_x_mode = CTRL_X_CMDLINE; - break; - case Ctrl_Z: - ctrl_x_mode = CTRL_X_NORMAL; - edit_submode = NULL; - showmode(); - retval = true; - break; - case Ctrl_P: - case Ctrl_N: - // ^X^P means LOCAL expansion if nothing interrupted (eg we - // just started ^X mode, or there were enough ^X's to cancel - // the previous mode, say ^X^F^X^X^P or ^P^X^X^X^P, see below) - // do normal expansion when interrupting a different mode (say - // ^X^F^X^P or ^P^X^X^P, see below) - // nothing changes if interrupting mode 0, (eg, the flag - // doesn't change when going to ADDING mode -- Acevedo - if (!(compl_cont_status & CONT_INTRPT)) { - compl_cont_status |= CONT_LOCAL; - } else if (compl_cont_mode != 0) { - compl_cont_status &= ~CONT_LOCAL; - } - FALLTHROUGH; - default: - // If we have typed at least 2 ^X's... for modes != 0, we set - // compl_cont_status = 0 (eg, as if we had just started ^X - // mode). - // For mode 0, we set "compl_cont_mode" to an impossible - // value, in both cases ^X^X can be used to restart the same - // mode (avoiding ADDING mode). - // Undocumented feature: In a mode != 0 ^X^P and ^X^X^P start - // 'complete' and local ^P expansions respectively. - // In mode 0 an extra ^X is needed since ^X^P goes to ADDING - // mode -- Acevedo - if (c == Ctrl_X) { - if (compl_cont_mode != 0) { - compl_cont_status = 0; - } else { - compl_cont_mode = CTRL_X_NOT_DEFINED_YET; - } - } - ctrl_x_mode = CTRL_X_NORMAL; - edit_submode = NULL; - showmode(); - break; - } + retval = set_ctrl_x_mode(c); } else if (ctrl_x_mode_not_default()) { // We're already in CTRL-X mode, do we stay in it? if (!vim_is_ctrl_x_key(c)) { @@ -1825,107 +2137,7 @@ bool ins_compl_prep(int c) && c != Ctrl_R && !ins_compl_pum_key(c)) || ctrl_x_mode == CTRL_X_FINISHED) { - // Get here when we have finished typing a sequence of ^N and - // ^P or other completion characters in CTRL-X mode. Free up - // memory that was used, and make sure we can redo the insert. - if (compl_curr_match != NULL || compl_leader != NULL || c == Ctrl_E) { - // If any of the original typed text has been changed, eg when - // ignorecase is set, we must add back-spaces to the redo - // buffer. We add as few as necessary to delete just the part - // 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. - if (compl_curr_match != NULL && compl_used_match && c != Ctrl_E) { - ptr = compl_curr_match->cp_str; - } else { - ptr = NULL; - } - ins_compl_fixRedoBufForLeader(ptr); - } - - bool want_cindent = (can_cindent_get() && cindent_on()); - - // When completing whole lines: fix indent for 'cindent'. - // Otherwise, break line if it's too long. - if (compl_cont_mode == CTRL_X_WHOLE_LINE) { - // re-indent the current line - if (want_cindent) { - do_c_expr_indent(); - want_cindent = false; // don't do it again - } - } else { - int prev_col = curwin->w_cursor.col; - - // put the cursor on the last char, for 'tw' formatting - if (prev_col > 0) { - dec_cursor(); - } - - if (!arrow_used && !ins_need_undo_get() && c != Ctrl_E) { - insertchar(NUL, 0, -1); - } - - if (prev_col > 0 - && get_cursor_line_ptr()[curwin->w_cursor.col] != NUL) { - inc_cursor(); - } - } - - // If the popup menu is displayed pressing CTRL-Y means accepting - // the selection without inserting anything. When - // compl_enter_selects is set the Enter key does the same. - if ((c == Ctrl_Y || (compl_enter_selects - && (c == CAR || c == K_KENTER || c == NL))) - && pum_visible()) { - retval = true; - } - - // CTRL-E means completion is Ended, go back to the typed text. - // but only do this, if the Popup is still visible - if (c == Ctrl_E) { - ins_compl_delete(); - if (compl_leader != NULL) { - ins_bytes(compl_leader + get_compl_len()); - } else if (compl_first_match != NULL) { - ins_bytes(compl_orig_text + get_compl_len()); - } - retval = true; - } - - auto_format(false, true); - - // Trigger the CompleteDonePre event to give scripts a chance to - // act upon the completion before clearing the info, and restore - // ctrl_x_mode, so that complete_info() can be used. - ctrl_x_mode = prev_mode; - ins_apply_autocmds(EVENT_COMPLETEDONEPRE); - - ins_compl_free(); - compl_started = false; - compl_matches = 0; - if (!shortmess(SHM_COMPLETIONMENU)) { - msg_clr_cmdline(); // necessary for "noshowmode" - } - ctrl_x_mode = CTRL_X_NORMAL; - compl_enter_selects = false; - if (edit_submode != NULL) { - edit_submode = NULL; - showmode(); - } - - // Avoid the popup menu remains displayed when leaving the - // command line window. - if (c == Ctrl_C && cmdwin_type != 0) { - update_screen(0); - } - - // Indent now if a key was typed that is in 'cinkeys'. - if (want_cindent && in_cinkeys(KEY_COMPLETE, ' ', inindent(0))) { - do_c_expr_indent(); - } - // Trigger the CompleteDone event to give scripts a chance to act - // upon the end of completion. - ins_apply_autocmds(EVENT_COMPLETEDONE); + retval = ins_compl_stop(c, prev_mode, retval); } } else if (ctrl_x_mode == CTRL_X_LOCAL_MSG) { // Trigger the CompleteDone event to give scripts a chance to act @@ -2008,16 +2220,16 @@ static buf_T *ins_compl_next_buf(buf_T *buf, int flag) return buf; } -/// Get the user-defined completion function name for completion 'type' +/// Get the user-defined completion function name for completion "type" static char_u *get_complete_funcname(int type) { switch (type) { case CTRL_X_FUNCTION: - return curbuf->b_p_cfu; + return (char_u *)curbuf->b_p_cfu; case CTRL_X_OMNI: - return curbuf->b_p_ofu; + return (char_u *)curbuf->b_p_ofu; case CTRL_X_THESAURUS: - return *curbuf->b_p_tsrfu == NUL ? p_tsrfu : curbuf->b_p_tsrfu; + return *curbuf->b_p_tsrfu == NUL ? (char_u *)p_tsrfu : (char_u *)curbuf->b_p_tsrfu; default: return (char_u *)""; } @@ -2139,16 +2351,21 @@ static int ins_compl_add_tv(typval_T *const tv, const Direction dir, bool fast) } } else { word = tv_get_string_chk(tv); - memset(cptext, 0, sizeof(cptext)); + CLEAR_FIELD(cptext); } if (word == NULL || (!empty && *word == NUL)) { for (size_t i = 0; i < CPT_COUNT; i++) { xfree(cptext[i]); } + tv_clear(&user_data); return FAIL; } - return ins_compl_add((char_u *)word, -1, NULL, - (char_u **)cptext, true, &user_data, dir, flags, dup); + int status = ins_compl_add((char_u *)word, -1, NULL, (char_u **)cptext, true, + &user_data, dir, flags, dup); + if (status != OK) { + tv_clear(&user_data); + } + return status; } /// Add completions from a list. @@ -2254,7 +2471,7 @@ static void set_completion(colnr_T startcol, list_T *list) } /// "complete()" function -void f_complete(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_complete(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if ((State & MODE_INSERT) == 0) { emsg(_("E785: complete() can only be used in Insert mode")); @@ -2278,13 +2495,13 @@ void f_complete(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "complete_add()" function -void f_complete_add(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_complete_add(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = ins_compl_add_tv(&argvars[0], 0, false); } /// "complete_check()" function -void f_complete_check(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_complete_check(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int saved = RedrawingDisabled; @@ -2303,18 +2520,19 @@ static char_u *ins_compl_mode(void) return (char_u *)""; } +/// Assign the sequence number to all the completion matches which don't have +/// one assigned yet. static void ins_compl_update_sequence_numbers(void) { int number = 0; compl_T *match; - if (compl_direction == FORWARD) { + if (compl_dir_forward()) { // search backwards for the first valid (!= -1) number. // This should normally succeed already at the first loop // cycle, so it's fast! for (match = compl_curr_match->cp_prev; - match != NULL && match != compl_first_match; - match = match->cp_prev) { + match != NULL && !is_first_match(match); match = match->cp_prev) { if (match->cp_number != -1) { number = match->cp_number; break; @@ -2334,8 +2552,7 @@ static void ins_compl_update_sequence_numbers(void) // number. This should normally succeed already at the // first loop cycle, so it's fast! for (match = compl_curr_match->cp_next; - match != NULL && match != compl_first_match; - match = match->cp_next) { + match != NULL && !is_first_match(match); match = match->cp_next) { if (match->cp_number != -1) { number = match->cp_number; break; @@ -2404,7 +2621,7 @@ static void get_complete_info(list_T *what_list, dict_T *retdict) if (ret == OK && compl_first_match != NULL) { compl_T *match = compl_first_match; do { - if (!(match->cp_flags & CP_ORIGINAL_TEXT)) { + if (!match_at_original_text(match)) { dict_T *di = tv_dict_alloc(); tv_list_append_dict(li, di); @@ -2420,7 +2637,7 @@ static void get_complete_info(list_T *what_list, dict_T *retdict) } } match = match->cp_next; - } while (match != NULL && match != compl_first_match); + } while (match != NULL && !is_first_match(match)); } } @@ -2439,7 +2656,7 @@ static void get_complete_info(list_T *what_list, dict_T *retdict) } /// "complete_info()" function -void f_complete_info(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_complete_info(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { tv_dict_alloc_ret(rettv); @@ -2462,6 +2679,481 @@ static bool thesaurus_func_complete(int type) && (*curbuf->b_p_tsrfu != NUL || *p_tsrfu != NUL); } +/// Return value of process_next_cpt_value() +enum { + INS_COMPL_CPT_OK = 1, + INS_COMPL_CPT_CONT, + INS_COMPL_CPT_END, +}; + +/// Process the next 'complete' option value in st->e_cpt. +/// +/// If successful, the arguments are set as below: +/// st->cpt - pointer to the next option value in "st->cpt" +/// compl_type_arg - type of insert mode completion to use +/// st->found_all - all matches of this type are found +/// st->ins_buf - search for completions in this buffer +/// st->first_match_pos - position of the first completion match +/// st->last_match_pos - position of the last completion match +/// st->set_match_pos - true if the first match position should be saved to +/// avoid loops after the search wraps around. +/// st->dict - name of the dictionary or thesaurus file to search +/// st->dict_f - flag specifying whether "dict" is an exact file name or not +/// +/// @return INS_COMPL_CPT_OK if the next value is processed successfully. +/// INS_COMPL_CPT_CONT to skip the current completion source matching +/// the "st->e_cpt" option value and process the next matching source. +/// INS_COMPL_CPT_END if all the values in "st->e_cpt" are processed. +static int process_next_cpt_value(ins_compl_next_state_T *st, int *compl_type_arg, + pos_T *start_match_pos) +{ + int compl_type = -1; + int status = INS_COMPL_CPT_OK; + + st->found_all = false; + + while (*st->e_cpt == ',' || *st->e_cpt == ' ') { + st->e_cpt++; + } + + if (*st->e_cpt == '.' && !curbuf->b_scanned) { + st->ins_buf = curbuf; + st->first_match_pos = *start_match_pos; + // Move the cursor back one character so that ^N can match the + // word immediately after the cursor. + if (ctrl_x_mode_normal() && dec(&st->first_match_pos) < 0) { + // Move the cursor to after the last character in the + // buffer, so that word at start of buffer is found + // correctly. + st->first_match_pos.lnum = st->ins_buf->b_ml.ml_line_count; + st->first_match_pos.col = (colnr_T)STRLEN(ml_get(st->first_match_pos.lnum)); + } + st->last_match_pos = st->first_match_pos; + compl_type = 0; + + // 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 + && (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 + compl_started = true; + st->first_match_pos.col = st->last_match_pos.col = 0; + st->first_match_pos.lnum = st->ins_buf->b_ml.ml_line_count + 1; + st->last_match_pos.lnum = 0; + compl_type = 0; + } else { // unloaded buffer, scan like dictionary + st->found_all = true; + if (st->ins_buf->b_fname == NULL) { + status = INS_COMPL_CPT_CONT; + goto done; + } + compl_type = CTRL_X_DICTIONARY; + st->dict = (char_u *)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)); + } else if (*st->e_cpt == NUL) { + status = INS_COMPL_CPT_END; + } else { + if (ctrl_x_mode_line_or_eval()) { + compl_type = -1; + } else if (*st->e_cpt == 'k' || *st->e_cpt == 's') { + if (*st->e_cpt == 'k') { + compl_type = CTRL_X_DICTIONARY; + } else { + compl_type = CTRL_X_THESAURUS; + } + if (*++st->e_cpt != ',' && *st->e_cpt != NUL) { + st->dict = (char_u *)st->e_cpt; + st->dict_f = DICT_FIRST; + } + } else if (*st->e_cpt == 'i') { + compl_type = CTRL_X_PATH_PATTERNS; + } 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)); + } 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, ","); + + st->found_all = true; + if (compl_type == -1) { + status = INS_COMPL_CPT_CONT; + } + } + +done: + *compl_type_arg = compl_type; + return status; +} + +/// Get the next set of identifiers or defines matching "compl_pattern" in +/// included files. +static void get_next_include_file_completion(int compl_type) +{ + find_pattern_in_path((char_u *)compl_pattern, compl_direction, + STRLEN(compl_pattern), false, false, + ((compl_type == CTRL_X_PATH_DEFINES + && !(compl_cont_status & CONT_SOL)) + ? FIND_DEFINE : FIND_ANY), + 1L, ACTION_EXPAND, 1, MAXLNUM); +} + +/// 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) +{ + if (thesaurus_func_complete(compl_type)) { + expand_by_function(compl_type, (char_u *)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_dict == + NUL ? (char_u *)p_dict : (char_u *)curbuf->b_p_dict)), + (char_u *)compl_pattern, + dict != NULL ? dict_f : 0, + compl_type == CTRL_X_THESAURUS); + } +} + +/// Get the next set of tag names matching "compl_pattern". +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); + + // Find up to TAG_MANY matches. Avoids that an enormous number + // of matches is found when compl_pattern is empty + g_tag_at_cursor = true; + char **matches; + int num_matches; + if (find_tags(compl_pattern, &num_matches, &matches, + TAG_REGEXP | TAG_NAMES | TAG_NOIC | TAG_INS_COMP + | (ctrl_x_mode_not_default() ? TAG_VERBOSE : 0), + TAG_MANY, curbuf->b_ffname) == OK && num_matches > 0) { + ins_compl_add_matches(num_matches, matches, p_ic); + } + g_tag_at_cursor = false; + p_ic = save_p_ic; +} + +/// Get the next set of filename matching "compl_pattern". +static void get_next_filename_completion(void) +{ + char **matches; + int num_matches; + if (expand_wildcards(1, &compl_pattern, &num_matches, &matches, + EW_FILE|EW_DIR|EW_ADDSLASH|EW_SILENT) != OK) { + return; + } + + // May change home directory back to "~". + tilde_replace((char_u *)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]; + while (*ptr != NUL) { + if (curbuf->b_p_csl[0] == 's' && *ptr == '\\') { + *ptr = '/'; + } else if (curbuf->b_p_csl[0] == 'b' && *ptr == '/') { + *ptr = '\\'; + } + ptr += utfc_ptr2len(ptr); + } + } + } +#endif + ins_compl_add_matches(num_matches, matches, p_fic || p_wic); +} + +/// Get the next set of command-line completions matching "compl_pattern". +static void get_next_cmdline_completion(void) +{ + char **matches; + int num_matches; + if (expand_cmdline(&compl_xp, (char_u *)compl_pattern, + (int)STRLEN(compl_pattern), + &num_matches, &matches) == EXPAND_OK) { + ins_compl_add_matches(num_matches, matches, false); + } +} + +/// Get the next set of spell suggestions matching "compl_pattern". +static void get_next_spell_completion(linenr_T lnum) +{ + char **matches; + int num_matches = expand_spelling(lnum, (char_u *)compl_pattern, &matches); + if (num_matches > 0) { + ins_compl_add_matches(num_matches, matches, p_ic); + } else { + xfree(matches); + } +} + +/// Return the next word or line from buffer "ins_buf" at position +/// "cur_match_pos" for completion. The length of the match is set in "len". +/// @param ins_buf buffer being scanned +/// @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) +{ + *match_len = 0; + char_u *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 = ml_get_buf(ins_buf, cur_match_pos->lnum + 1, false); + if (!p_paste) { + ptr = (char_u *)skipwhite((char *)ptr); + } + } + len = (int)STRLEN(ptr); + } else { + char_u *tmp_ptr = 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)) { + return NULL; + } + // Find start of next word. + tmp_ptr = find_word_start(tmp_ptr); + } + // Find end of this word. + tmp_ptr = find_word_end(tmp_ptr); + len = (int)(tmp_ptr - ptr); + + if (compl_status_adding() && len == compl_length) { + 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 = ml_get_buf(ins_buf, cur_match_pos->lnum + 1, false); + tmp_ptr = ptr = (char_u *)skipwhite((char *)ptr); + // Find start of next word. + tmp_ptr = find_word_start(tmp_ptr); + // Find end of next word. + tmp_ptr = find_word_end(tmp_ptr); + if (tmp_ptr > ptr) { + if (*ptr != ')' && IObuff[len - 1] != TAB) { + if (IObuff[len - 1] != ' ') { + IObuff[len++] = ' '; + } + // IObuf =~ "\k.* ", thus len >= 2 + if (p_js + && (IObuff[len - 2] == '.' + || IObuff[len - 2] == '?' + || IObuff[len - 2] == '!')) { + IObuff[len++] = ' '; + } + } + // copy as much as possible of the new word + if (tmp_ptr - ptr >= IOSIZE - len) { + tmp_ptr = ptr + IOSIZE - len - 1; + } + STRLCPY(IObuff + len, ptr, IOSIZE - len); + len += (int)(tmp_ptr - ptr); + *cont_s_ipos = true; + } + IObuff[len] = NUL; + ptr = IObuff; + } + if (len == compl_length) { + return NULL; + } + } + } + + *match_len = len; + return ptr; +} + +/// Get the next set of words matching "compl_pattern" for default completion(s) +/// (normal ^P/^N and ^X^L). +/// Search for "compl_pattern" in the buffer "st->ins_buf" starting from the +/// position "st->start_pos" in the "compl_direction" direction. If +/// "st->set_match_pos" is true, then set the "st->first_match_pos" and +/// "st->last_match_pos". +/// +/// @return OK if a new next match is found, otherwise FAIL. +static int get_next_default_completion(ins_compl_next_state_T *st, pos_T *start_pos) +{ + // If 'infercase' is set, don't use 'smartcase' here + const int save_p_scs = p_scs; + assert(st->ins_buf); + if (st->ins_buf->b_p_inf) { + p_scs = false; + } + + // Buffers other than curbuf are scanned from the beginning or the + // end but never from the middle, thus setting nowrapscan in this + // buffers is a good idea, on the other hand, we always set + // wrapscan for curbuf to avoid missing matches -- Acevedo,Webb + const int save_p_ws = p_ws; + if (st->ins_buf != curbuf) { + p_ws = false; + } else if (*st->e_cpt == '.') { + p_ws = true; + } + bool looped_around = false; + int found_new_match = FAIL; + for (;;) { + bool cont_s_ipos = false; + + msg_silent++; // Don't want messages for wrapscan. + // ctrl_x_mode_line_or_eval() || word-wise search that + // 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); + } else { + found_new_match = searchit(NULL, st->ins_buf, st->cur_match_pos, + NULL, compl_direction, (char_u *)compl_pattern, 1L, + SEARCH_KEEP + SEARCH_NFMSG, RE_LAST, NULL); + } + msg_silent--; + if (!compl_started || st->set_match_pos) { + // set "compl_started" even on fail + compl_started = true; + st->first_match_pos = *st->cur_match_pos; + st->last_match_pos = *st->cur_match_pos; + st->set_match_pos = false; + } else if (st->first_match_pos.lnum == st->last_match_pos.lnum + && st->first_match_pos.col == st->last_match_pos.col) { + found_new_match = FAIL; + } else if (compl_dir_forward() + && (st->prev_match_pos.lnum > st->cur_match_pos->lnum + || (st->prev_match_pos.lnum == st->cur_match_pos->lnum + && st->prev_match_pos.col >= st->cur_match_pos->col))) { + if (looped_around) { + found_new_match = FAIL; + } else { + looped_around = true; + } + } else if (!compl_dir_forward() + && (st->prev_match_pos.lnum < st->cur_match_pos->lnum + || (st->prev_match_pos.lnum == st->cur_match_pos->lnum + && st->prev_match_pos.col <= st->cur_match_pos->col))) { + if (looped_around) { + found_new_match = FAIL; + } else { + looped_around = true; + } + } + st->prev_match_pos = *st->cur_match_pos; + if (found_new_match == FAIL) { + break; + } + + // when ADDING, the text before the cursor matches, skip it + if (compl_status_adding() && st->ins_buf == curbuf + && start_pos->lnum == st->cur_match_pos->lnum + && start_pos->col == st->cur_match_pos->col) { + continue; + } + int len; + char_u *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, + 0, cont_s_ipos) != NOTDONE) { + found_new_match = OK; + break; + } + } + p_scs = save_p_scs; + p_ws = save_p_ws; + + return found_new_match; +} + +/// get the next set of completion matches for "type". +/// @return true if a new match is found, otherwise false. +static bool get_next_completion_match(int type, ins_compl_next_state_T *st, pos_T *ini) +{ + int found_new_match = FAIL; + + switch (type) { + case -1: + break; + case CTRL_X_PATH_PATTERNS: + case CTRL_X_PATH_DEFINES: + get_next_include_file_completion(type); + break; + + case CTRL_X_DICTIONARY: + case CTRL_X_THESAURUS: + get_next_dict_tsr_completion(type, st->dict, st->dict_f); + st->dict = NULL; + break; + + case CTRL_X_TAGS: + get_next_tag_completion(); + break; + + case CTRL_X_FILES: + get_next_filename_completion(); + break; + + case CTRL_X_CMDLINE: + case CTRL_X_CMDLINE_CTRL_X: + get_next_cmdline_completion(); + break; + + case CTRL_X_FUNCTION: + case CTRL_X_OMNI: + expand_by_function(type, (char_u *)compl_pattern); + break; + + case CTRL_X_SPELL: + get_next_spell_completion(st->first_match_pos.lnum); + break; + + default: // normal ^P/^N and ^X^L + found_new_match = get_next_default_completion(st, ini); + if (found_new_match == FAIL && st->ins_buf == curbuf) { + st->found_all = true; + } + } + + // check if compl_curr_match has changed, (e.g. other type of + // expansion added something) + if (type != 0 && compl_curr_match != compl_old_match) { + found_new_match = OK; + } + + return found_new_match; +} + /// Get the next expansion(s), using "compl_pattern". /// The search starts at position "ini" in curbuf and in the direction /// compl_direction. @@ -2471,28 +3163,10 @@ static bool thesaurus_func_complete(int type) /// Return the total number of matches or -1 if still unknown -- Acevedo static int ins_compl_get_exp(pos_T *ini) { - static pos_T first_match_pos; - static pos_T last_match_pos; - static char_u *e_cpt = (char_u *)""; // curr. entry in 'complete' - static bool found_all = false; // Found all matches of a - // certain type. - static buf_T *ins_buf = NULL; // buffer being scanned - - pos_T *pos; - char **matches; - int save_p_scs; - bool save_p_ws; - int save_p_ic; + static ins_compl_next_state_T st; int i; - int num_matches; - int len; int found_new_match; int type = ctrl_x_mode; - char_u *ptr; - char_u *dict = NULL; - int dict_f = 0; - bool set_match_pos; - pos_T prev_pos = { 0, 0, 0 }; assert(curbuf != NULL); @@ -2500,112 +3174,34 @@ static int ins_compl_get_exp(pos_T *ini) FOR_ALL_BUFFERS(buf) { buf->b_scanned = false; } - found_all = false; - ins_buf = curbuf; - e_cpt = (compl_cont_status & CONT_LOCAL) - ? (char_u *)"." : curbuf->b_p_cpt; - last_match_pos = first_match_pos = *ini; - } else if (ins_buf != curbuf && !buf_valid(ins_buf)) { - ins_buf = curbuf; // In case the buffer was wiped out. + st.found_all = false; + st.ins_buf = curbuf; + st.e_cpt = (compl_cont_status & CONT_LOCAL) ? "." : curbuf->b_p_cpt; + st.last_match_pos = st.first_match_pos = *ini; + } else if (st.ins_buf != curbuf && !buf_valid(st.ins_buf)) { + st.ins_buf = curbuf; // In case the buffer was wiped out. } - assert(ins_buf != NULL); + assert(st.ins_buf != NULL); compl_old_match = compl_curr_match; // remember the last current match - pos = (compl_direction == FORWARD) ? &last_match_pos : &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 (;;) { found_new_match = FAIL; - set_match_pos = false; + st.set_match_pos = false; // For ^N/^P pick a new entry from e_cpt if compl_started is off, // or if found_all says this entry is done. For ^X^L only use the // entries from 'complete' that look in loaded buffers. if ((ctrl_x_mode_normal() || ctrl_x_mode_line_or_eval()) - && (!compl_started || found_all)) { - found_all = false; - while (*e_cpt == ',' || *e_cpt == ' ') { - e_cpt++; - } - if (*e_cpt == '.' && !curbuf->b_scanned) { - ins_buf = curbuf; - first_match_pos = *ini; - // Move the cursor back one character so that ^N can match the - // word immediately after the cursor. - if (ctrl_x_mode_normal() && dec(&first_match_pos) < 0) { - // Move the cursor to after the last character in the - // buffer, so that word at start of buffer is found - // correctly. - first_match_pos.lnum = ins_buf->b_ml.ml_line_count; - first_match_pos.col = (colnr_T)STRLEN(ml_get(first_match_pos.lnum)); - } - last_match_pos = first_match_pos; - type = 0; - - // Remember the first match so that the loop stops when we - // wrap and come back there a second time. - set_match_pos = true; - } else if (vim_strchr("buwU", *e_cpt) != NULL - && (ins_buf = ins_compl_next_buf(ins_buf, *e_cpt)) != curbuf) { - // Scan a buffer, but not the current one. - if (ins_buf->b_ml.ml_mfp != NULL) { // loaded buffer - compl_started = true; - first_match_pos.col = last_match_pos.col = 0; - first_match_pos.lnum = ins_buf->b_ml.ml_line_count + 1; - last_match_pos.lnum = 0; - type = 0; - } else { // unloaded buffer, scan like dictionary - found_all = true; - if (ins_buf->b_fname == NULL) { - continue; - } - type = CTRL_X_DICTIONARY; - dict = (char_u *)ins_buf->b_fname; - dict_f = DICT_EXACT; - } - msg_hist_off = true; // reset in msg_trunc_attr() - vim_snprintf((char *)IObuff, IOSIZE, _("Scanning: %s"), - ins_buf->b_fname == NULL - ? buf_spname(ins_buf) - : ins_buf->b_sfname == NULL - ? ins_buf->b_fname - : ins_buf->b_sfname); - (void)msg_trunc_attr((char *)IObuff, true, HL_ATTR(HLF_R)); - } else if (*e_cpt == NUL) { + && (!compl_started || st.found_all)) { + int status = process_next_cpt_value(&st, &type, ini); + if (status == INS_COMPL_CPT_END) { break; - } else { - if (ctrl_x_mode_line_or_eval()) { - type = -1; - } else if (*e_cpt == 'k' || *e_cpt == 's') { - if (*e_cpt == 'k') { - type = CTRL_X_DICTIONARY; - } else { - type = CTRL_X_THESAURUS; - } - if (*++e_cpt != ',' && *e_cpt != NUL) { - dict = e_cpt; - dict_f = DICT_FIRST; - } - } else if (*e_cpt == 'i') { - type = CTRL_X_PATH_PATTERNS; - } else if (*e_cpt == 'd') { - type = CTRL_X_PATH_DEFINES; - } else if (*e_cpt == ']' || *e_cpt == 't') { - msg_hist_off = true; // reset in msg_trunc_attr() - type = CTRL_X_TAGS; - vim_snprintf((char *)IObuff, IOSIZE, "%s", _("Scanning tags.")); - (void)msg_trunc_attr((char *)IObuff, true, HL_ATTR(HLF_R)); - } else { - type = -1; - } - - // in any case e_cpt is advanced to the next entry - (void)copy_option_part((char **)&e_cpt, (char *)IObuff, IOSIZE, ","); - - found_all = true; - if (type == -1) { - continue; - } + } + if (status == INS_COMPL_CPT_CONT) { + continue; } } @@ -2615,271 +3211,12 @@ static int ins_compl_get_exp(pos_T *ini) break; } - switch (type) { - case -1: - break; - case CTRL_X_PATH_PATTERNS: - case CTRL_X_PATH_DEFINES: - find_pattern_in_path((char_u *)compl_pattern, compl_direction, - STRLEN(compl_pattern), false, false, - ((type == CTRL_X_PATH_DEFINES - && !(compl_cont_status & CONT_SOL)) - ? FIND_DEFINE - : FIND_ANY), - 1L, ACTION_EXPAND, 1, MAXLNUM); - break; - - case CTRL_X_DICTIONARY: - case CTRL_X_THESAURUS: - if (thesaurus_func_complete(type)) { - expand_by_function(type, (char_u *)compl_pattern); - } else { - ins_compl_dictionaries(dict != NULL ? dict - : (type == CTRL_X_THESAURUS - ? (*curbuf->b_p_tsr == NUL ? p_tsr : curbuf->b_p_tsr) - : (*curbuf->b_p_dict == - NUL ? p_dict : curbuf->b_p_dict)), - (char_u *)compl_pattern, - dict != NULL ? dict_f : 0, type == CTRL_X_THESAURUS); - } - dict = NULL; - break; - - case CTRL_X_TAGS: - // set p_ic according to p_ic, p_scs and pat for find_tags(). - save_p_ic = p_ic; - p_ic = ignorecase((char_u *)compl_pattern); - - // Find up to TAG_MANY matches. Avoids that an enormous number - // of matches is found when compl_pattern is empty - g_tag_at_cursor = true; - if (find_tags((char_u *)compl_pattern, &num_matches, &matches, - TAG_REGEXP | TAG_NAMES | TAG_NOIC | TAG_INS_COMP - | (ctrl_x_mode_not_default() ? TAG_VERBOSE : 0), - TAG_MANY, (char_u *)curbuf->b_ffname) == OK && num_matches > 0) { - ins_compl_add_matches(num_matches, matches, p_ic); - } - g_tag_at_cursor = false; - p_ic = save_p_ic; - break; - - case CTRL_X_FILES: - if (expand_wildcards(1, &compl_pattern, &num_matches, &matches, - EW_FILE|EW_DIR|EW_ADDSLASH|EW_SILENT) == OK) { - // May change home directory back to "~". - tilde_replace((char_u *)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]; - while (*ptr != NUL) { - if (curbuf->b_p_csl[0] == 's' && *ptr == '\\') { - *ptr = '/'; - } else if (curbuf->b_p_csl[0] == 'b' && *ptr == '/') { - *ptr = '\\'; - } - ptr += utfc_ptr2len(ptr); - } - } - } -#endif - ins_compl_add_matches(num_matches, matches, p_fic || p_wic); - } - break; - - case CTRL_X_CMDLINE: - case CTRL_X_CMDLINE_CTRL_X: - if (expand_cmdline(&compl_xp, (char_u *)compl_pattern, - (int)STRLEN(compl_pattern), - &num_matches, &matches) == EXPAND_OK) { - ins_compl_add_matches(num_matches, matches, false); - } - break; - - case CTRL_X_FUNCTION: - case CTRL_X_OMNI: - expand_by_function(type, (char_u *)compl_pattern); - break; - - case CTRL_X_SPELL: - num_matches = expand_spelling(first_match_pos.lnum, - (char_u *)compl_pattern, &matches); - if (num_matches > 0) { - ins_compl_add_matches(num_matches, matches, p_ic); - } - break; - - default: // normal ^P/^N and ^X^L - // If 'infercase' is set, don't use 'smartcase' here - save_p_scs = p_scs; - assert(ins_buf); - if (ins_buf->b_p_inf) { - p_scs = false; - } - - // Buffers other than curbuf are scanned from the beginning or the - // end but never from the middle, thus setting nowrapscan in this - // buffers is a good idea, on the other hand, we always set - // wrapscan for curbuf to avoid missing matches -- Acevedo,Webb - save_p_ws = p_ws; - if (ins_buf != curbuf) { - p_ws = false; - } else if (*e_cpt == '.') { - p_ws = true; - } - bool looped_around = false; - for (;;) { - bool cont_s_ipos = false; - - msg_silent++; // Don't want messages for wrapscan. - // ctrl_x_mode_line_or_eval() || word-wise search that - // 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(ins_buf, pos, - compl_direction, - (char_u *)compl_pattern); - } else { - found_new_match = searchit(NULL, ins_buf, pos, NULL, - compl_direction, - (char_u *)compl_pattern, 1L, - SEARCH_KEEP + SEARCH_NFMSG, - RE_LAST, NULL); - } - msg_silent--; - if (!compl_started || set_match_pos) { - // set "compl_started" even on fail - compl_started = true; - first_match_pos = *pos; - last_match_pos = *pos; - set_match_pos = false; - } else if (first_match_pos.lnum == last_match_pos.lnum - && first_match_pos.col == last_match_pos.col) { - found_new_match = FAIL; - } else if ((compl_direction == FORWARD) - && (prev_pos.lnum > pos->lnum - || (prev_pos.lnum == pos->lnum - && prev_pos.col >= pos->col))) { - if (looped_around) { - found_new_match = FAIL; - } else { - looped_around = true; - } - } else if ((compl_direction != FORWARD) - && (prev_pos.lnum < pos->lnum - || (prev_pos.lnum == pos->lnum - && prev_pos.col <= pos->col))) { - if (looped_around) { - found_new_match = FAIL; - } else { - looped_around = true; - } - } - prev_pos = *pos; - if (found_new_match == FAIL) { - if (ins_buf == curbuf) { - found_all = true; - } - break; - } - - // when ADDING, the text before the cursor matches, skip it - if ((compl_cont_status & CONT_ADDING) && ins_buf == curbuf - && ini->lnum == pos->lnum - && ini->col == pos->col) { - continue; - } - ptr = ml_get_buf(ins_buf, pos->lnum, false) + pos->col; - if (ctrl_x_mode_line_or_eval()) { - if (compl_cont_status & CONT_ADDING) { - if (pos->lnum >= ins_buf->b_ml.ml_line_count) { - continue; - } - ptr = ml_get_buf(ins_buf, pos->lnum + 1, false); - if (!p_paste) { - ptr = (char_u *)skipwhite((char *)ptr); - } - } - len = (int)STRLEN(ptr); - } else { - char_u *tmp_ptr = ptr; - - if (compl_cont_status & CONT_ADDING) { - tmp_ptr += compl_length; - // Skip if already inside a word. - if (vim_iswordp(tmp_ptr)) { - continue; - } - // Find start of next word. - tmp_ptr = find_word_start(tmp_ptr); - } - // Find end of this word. - tmp_ptr = find_word_end(tmp_ptr); - len = (int)(tmp_ptr - ptr); - - if ((compl_cont_status & CONT_ADDING) - && len == compl_length) { - if (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 = ml_get_buf(ins_buf, pos->lnum + 1, false); - tmp_ptr = ptr = (char_u *)skipwhite((char *)ptr); - // Find start of next word. - tmp_ptr = find_word_start(tmp_ptr); - // Find end of next word. - tmp_ptr = find_word_end(tmp_ptr); - if (tmp_ptr > ptr) { - if (*ptr != ')' && IObuff[len - 1] != TAB) { - if (IObuff[len - 1] != ' ') { - IObuff[len++] = ' '; - } - // IObuf =~ "\k.* ", thus len >= 2 - if (p_js - && (IObuff[len - 2] == '.' - || IObuff[len - 2] == '?' - || IObuff[len - 2] == '!')) { - IObuff[len++] = ' '; - } - } - // copy as much as possible of the new word - if (tmp_ptr - ptr >= IOSIZE - len) { - tmp_ptr = ptr + IOSIZE - len - 1; - } - STRLCPY(IObuff + len, ptr, IOSIZE - len); - len += (int)(tmp_ptr - ptr); - cont_s_ipos = true; - } - IObuff[len] = NUL; - ptr = IObuff; - } - if (len == compl_length) { - continue; - } - } - } - if (ins_compl_add_infercase(ptr, len, p_ic, - ins_buf == curbuf ? NULL : (char_u *)ins_buf->b_sfname, - 0, cont_s_ipos) != NOTDONE) { - found_new_match = OK; - break; - } - } - p_scs = save_p_scs; - p_ws = save_p_ws; - } - - // check if compl_curr_match has changed, (e.g. other type of - // expansion added something) - if (type != 0 && compl_curr_match != compl_old_match) { - found_new_match = OK; - } + // get the next set of completion matches + found_new_match = get_next_completion_match(type, &st, ini); // break the loop for specialized modes (use 'complete' just for the // generic ctrl_x_mode == CTRL_X_NORMAL) or when we've found a new match - if ((ctrl_x_mode_not_default() - && !ctrl_x_mode_line_or_eval()) + if ((ctrl_x_mode_not_default() && !ctrl_x_mode_line_or_eval()) || found_new_match != FAIL) { if (got_int) { break; @@ -2889,8 +3226,7 @@ static int ins_compl_get_exp(pos_T *ini) ins_compl_check_keys(0, false); } - if ((ctrl_x_mode_not_default() - && !ctrl_x_mode_line_or_eval()) + if ((ctrl_x_mode_not_default() && !ctrl_x_mode_line_or_eval()) || compl_interrupted) { break; } @@ -2898,8 +3234,8 @@ static int ins_compl_get_exp(pos_T *ini) } else { // Mark a buffer scanned when it has been scanned completely if (type == 0 || type == CTRL_X_PATH_PATTERNS) { - assert(ins_buf); - ins_buf->b_scanned = true; + assert(st.ins_buf); + st.ins_buf->b_scanned = true; } compl_started = false; @@ -2907,16 +3243,14 @@ static int ins_compl_get_exp(pos_T *ini) } compl_started = true; - if ((ctrl_x_mode_normal() - || ctrl_x_mode_line_or_eval()) - && *e_cpt == NUL) { // Got to end of 'complete' + if ((ctrl_x_mode_normal() || ctrl_x_mode_line_or_eval()) + && *st.e_cpt == NUL) { // Got to end of 'complete' found_new_match = FAIL; } i = -1; // total of matches, unknown if (found_new_match == FAIL - || (ctrl_x_mode_not_default() - && !ctrl_x_mode_line_or_eval())) { + || (ctrl_x_mode_not_default() && !ctrl_x_mode_line_or_eval())) { i = ins_compl_make_cyclic(); } @@ -2924,7 +3258,7 @@ static int ins_compl_get_exp(pos_T *ini) // If several matches were added (FORWARD) or the search failed and has // just been made cyclic then we have to move compl_curr_match to the // next or previous entry (if any) -- Acevedo - compl_curr_match = compl_direction == FORWARD + compl_curr_match = compl_dir_forward() ? compl_old_match->cp_next : compl_old_match->cp_prev; if (compl_curr_match == NULL) { @@ -2936,6 +3270,31 @@ static int ins_compl_get_exp(pos_T *ini) return i; } +/// Update "compl_shown_match" to the actually shown match, it may differ when +/// "compl_leader" is used to omit some of the matches. +static void ins_compl_update_shown_match(void) +{ + while (!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)) { + compl_shown_match = compl_shown_match->cp_next; + } + + // 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, 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, 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; + } + } +} + /// Delete the old text being completed. void ins_compl_delete(void) { @@ -2943,7 +3302,7 @@ void ins_compl_delete(void) // In insert mode: Delete the typed part. // In replace mode: Put the old characters back, if any. - col = compl_col + (compl_cont_status & CONT_ADDING ? compl_length : 0); + col = compl_col + (compl_status_adding() ? compl_length : 0); if ((int)curwin->w_cursor.col > col) { if (stop_arrow() == FAIL) { return; @@ -2962,8 +3321,8 @@ void ins_compl_delete(void) /// "in_compl_func" is true when called from complete_check(). void ins_compl_insert(bool in_compl_func) { - ins_bytes(compl_shown_match->cp_str + get_compl_len()); - compl_used_match = !(compl_shown_match->cp_flags & CP_ORIGINAL_TEXT); + ins_bytes((char *)compl_shown_match->cp_str + get_compl_len()); + compl_used_match = !match_at_original_text(compl_shown_match); dict_T *dict = ins_compl_dict_alloc(compl_shown_match); set_vim_var_dict(VV_COMPLETED_ITEM, dict); @@ -2972,97 +3331,69 @@ void ins_compl_insert(bool in_compl_func) } } -/// Fill in the next completion in the current direction. -/// If "allow_get_expansion" is true, then we may call ins_compl_get_exp() to -/// get more completions. If it is false, then we just do nothing when there -/// are no more completions in a given direction. The latter case is used when -/// we are still in the middle of finding completions, to allow browsing -/// through the ones found so far. -/// @return the total number of matches, or -1 if still unknown -- webb. -/// -/// compl_curr_match is currently being used by ins_compl_get_exp(), so we use -/// compl_shown_match here. -/// -/// Note that this function may be called recursively once only. First with -/// "allow_get_expansion" true, which calls ins_compl_get_exp(), which in turn -/// calls this function with "allow_get_expansion" false. -/// -/// @param count Repeat completion this many times; should be at least 1 -/// @param insert_match Insert the newly selected match -/// @param in_compl_func Called from complete_check() -static int ins_compl_next(bool allow_get_expansion, int count, bool insert_match, - bool in_compl_func) +/// show the file name for the completion match (if any). Truncate the file +/// name to avoid a wait for return. +static void ins_compl_show_filename(void) { - int num_matches = -1; - int todo = count; - compl_T *found_compl = NULL; - bool found_end = false; - const bool started = compl_started; - - // When user complete function return -1 for findstart which is next - // time of 'always', compl_shown_match become NULL. - if (compl_shown_match == NULL) { - return -1; + char *const lead = _("match in file"); + int space = sc_col - vim_strsize(lead) - 2; + if (space <= 0) { + return; } - if (compl_leader != NULL - && (compl_shown_match->cp_flags & CP_ORIGINAL_TEXT) == 0) { - // Set "compl_shown_match" to the actually shown match, it may differ - // when "compl_leader" is used to omit some of the matches. - while (!ins_compl_equal(compl_shown_match, - compl_leader, STRLEN(compl_leader)) - && compl_shown_match->cp_next != NULL - && compl_shown_match->cp_next != compl_first_match) { - compl_shown_match = compl_shown_match->cp_next; - } - - // 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, compl_leader, STRLEN(compl_leader)) - && (compl_shown_match->cp_next == NULL - || compl_shown_match->cp_next == compl_first_match)) { - while (!ins_compl_equal(compl_shown_match, compl_leader, STRLEN(compl_leader)) - && compl_shown_match->cp_prev != NULL - && compl_shown_match->cp_prev != compl_first_match) { - compl_shown_match = compl_shown_match->cp_prev; - } + // We need the tail that fits. With double-byte encoding going + // back from the end is very slow, thus go from the start and keep + // the text that fits in "space" between "s" and "e". + char *s; + char *e; + for (s = e = (char *)compl_shown_match->cp_fname; *e != NUL; MB_PTR_ADV(e)) { + space -= ptr2cells(e); + while (space < 0) { + space += ptr2cells(s); + MB_PTR_ADV(s); } } + msg_hist_off = true; + vim_snprintf((char *)IObuff, IOSIZE, "%s %s%s", lead, + (char_u *)s > compl_shown_match->cp_fname ? "<" : "", s); + msg((char *)IObuff); + msg_hist_off = false; + redraw_cmdline = false; // don't overwrite! +} - if (allow_get_expansion && insert_match - && (!(compl_get_longest || compl_restarting) || compl_used_match)) { - // Delete old text to be replaced - ins_compl_delete(); - } - - // When finding the longest common text we stick at the original text, - // don't let CTRL-N or CTRL-P move to the first match. - bool advance = count != 1 || !allow_get_expansion || !compl_get_longest; - - // When restarting the search don't insert the first match either. - if (compl_restarting) { - advance = false; - compl_restarting = false; - } +/// Find the next set of matches for completion. Repeat the completion "todo" +/// times. The number of matches found is returned in 'num_matches'. +/// +/// @param allow_get_expansion If true, then ins_compl_get_exp() may be called to +/// get more completions. +/// If false, then do nothing when there are no more +/// completions in the given direction. +/// @param todo repeat completion this many times +/// @param advance If true, then completion will move to the first match. +/// Otherwise, the original text will be shown. +/// +/// @return OK on success and -1 if the number of matches are unknown. +static int find_next_completion_match(bool allow_get_expansion, int todo, bool advance, + int *num_matches) +{ + bool found_end = false; + compl_T *found_compl = NULL; - // Repeat this for when <PageUp> or <PageDown> is typed. But don't wrap - // around. while (--todo >= 0) { - if (compl_shows_dir == FORWARD && compl_shown_match->cp_next != NULL) { + if (compl_shows_dir_forward() && compl_shown_match->cp_next != NULL) { compl_shown_match = compl_shown_match->cp_next; found_end = (compl_first_match != NULL - && (compl_shown_match->cp_next == compl_first_match - || compl_shown_match == compl_first_match)); - } else if (compl_shows_dir == BACKWARD + && (is_first_match(compl_shown_match->cp_next) + || is_first_match(compl_shown_match))); + } else if (compl_shows_dir_backward() && compl_shown_match->cp_prev != NULL) { - found_end = (compl_shown_match == compl_first_match); + found_end = is_first_match(compl_shown_match); compl_shown_match = compl_shown_match->cp_prev; - found_end |= (compl_shown_match == compl_first_match); + found_end |= is_first_match(compl_shown_match); } else { if (!allow_get_expansion) { if (advance) { - if (compl_shows_dir == BACKWARD) { + if (compl_shows_dir_backward()) { compl_pending -= todo + 1; } else { compl_pending += todo + 1; @@ -3072,7 +3403,7 @@ static int ins_compl_next(bool allow_get_expansion, int count, bool insert_match } if (!compl_no_select && advance) { - if (compl_shows_dir == BACKWARD) { + if (compl_shows_dir_backward()) { compl_pending--; } else { compl_pending++; @@ -3080,7 +3411,7 @@ static int ins_compl_next(bool allow_get_expansion, int count, bool insert_match } // Find matches. - num_matches = ins_compl_get_exp(&compl_startpos); + *num_matches = ins_compl_get_exp(&compl_startpos); // handle any pending completions while (compl_pending != 0 && compl_direction == compl_shows_dir @@ -3098,7 +3429,7 @@ static int ins_compl_next(bool allow_get_expansion, int count, bool insert_match } found_end = false; } - if ((compl_shown_match->cp_flags & CP_ORIGINAL_TEXT) == 0 + if (!match_at_original_text(compl_shown_match) && compl_leader != NULL && !ins_compl_equal(compl_shown_match, compl_leader, STRLEN(compl_leader))) { @@ -3118,15 +3449,77 @@ static int ins_compl_next(bool allow_get_expansion, int count, bool insert_match } } + return OK; +} + +/// Fill in the next completion in the current direction. +/// If "allow_get_expansion" is true, then we may call ins_compl_get_exp() to +/// get more completions. If it is false, then we just do nothing when there +/// are no more completions in a given direction. The latter case is used when +/// we are still in the middle of finding completions, to allow browsing +/// through the ones found so far. +/// @return the total number of matches, or -1 if still unknown -- webb. +/// +/// compl_curr_match is currently being used by ins_compl_get_exp(), so we use +/// compl_shown_match here. +/// +/// Note that this function may be called recursively once only. First with +/// "allow_get_expansion" true, which calls ins_compl_get_exp(), which in turn +/// calls this function with "allow_get_expansion" false. +/// +/// @param count Repeat completion this many times; should be at least 1 +/// @param insert_match Insert the newly selected match +/// @param in_compl_func Called from complete_check() +static int ins_compl_next(bool allow_get_expansion, int count, bool insert_match, + bool in_compl_func) +{ + int num_matches = -1; + int todo = count; + const bool started = compl_started; + + // When user complete function return -1 for findstart which is next + // time of 'always', compl_shown_match become NULL. + if (compl_shown_match == NULL) { + return -1; + } + + if (compl_leader != NULL && !match_at_original_text(compl_shown_match)) { + // Update "compl_shown_match" to the actually shown match + ins_compl_update_shown_match(); + } + + if (allow_get_expansion && insert_match + && (!(compl_get_longest || compl_restarting) || compl_used_match)) { + // Delete old text to be replaced + ins_compl_delete(); + } + + // When finding the longest common text we stick at the original text, + // don't let CTRL-N or CTRL-P move to the first match. + bool advance = count != 1 || !allow_get_expansion || !compl_get_longest; + + // When restarting the search don't insert the first match either. + if (compl_restarting) { + advance = false; + compl_restarting = false; + } + + // Repeat this for when <PageUp> or <PageDown> is typed. But don't wrap + // around. + if (find_next_completion_match(allow_get_expansion, todo, advance, + &num_matches) == -1) { + return -1; + } + // Insert the text of the new completion, or the compl_leader. if (compl_no_insert && !started) { - ins_bytes(compl_orig_text + get_compl_len()); + ins_bytes((char *)compl_orig_text + get_compl_len()); compl_used_match = false; } else if (insert_match) { if (!compl_get_longest || compl_used_match) { ins_compl_insert(in_compl_func); } else { - ins_bytes(compl_leader + get_compl_len()); + ins_bytes((char *)compl_leader + get_compl_len()); } } else { compl_used_match = false; @@ -3153,31 +3546,8 @@ static int ins_compl_next(bool allow_get_expansion, int count, bool insert_match } // Show the file name for the match (if any) - // Truncate the file name to avoid a wait for return. if (compl_shown_match->cp_fname != NULL) { - char *lead = _("match in file"); - int space = sc_col - vim_strsize(lead) - 2; - char *s; - char *e; - - if (space > 0) { - // We need the tail that fits. With double-byte encoding going - // back from the end is very slow, thus go from the start and keep - // the text that fits in "space" between "s" and "e". - for (s = e = (char *)compl_shown_match->cp_fname; *e != NUL; MB_PTR_ADV(e)) { - space -= ptr2cells(e); - while (space < 0) { - space += ptr2cells(s); - MB_PTR_ADV(s); - } - } - msg_hist_off = true; - vim_snprintf((char *)IObuff, IOSIZE, "%s %s%s", lead, - (char_u *)s > compl_shown_match->cp_fname ? "<" : "", s); - msg((char *)IObuff); - msg_hist_off = false; - redraw_cmdline = false; // don't overwrite! - } + ins_compl_show_filename(); } return num_matches; @@ -3330,7 +3700,7 @@ static bool ins_compl_use_match(int c) static int get_normal_compl_info(char_u *line, int startcol, colnr_T curs_col) { if ((compl_cont_status & CONT_SOL) || ctrl_x_mode_path_defines()) { - if (!(compl_cont_status & CONT_ADDING)) { + if (!compl_status_adding()) { while (--startcol >= 0 && vim_isIDc(line[startcol])) {} compl_col += ++startcol; compl_length = curs_col - startcol; @@ -3340,7 +3710,7 @@ static int get_normal_compl_info(char_u *line, int startcol, colnr_T curs_col) } else { compl_pattern = (char *)vim_strnsave(line + compl_col, (size_t)compl_length); } - } else if (compl_cont_status & CONT_ADDING) { + } else if (compl_status_adding()) { char_u *prefix = (char_u *)"\\<"; // we need up to 2 extra chars for the prefix @@ -3397,7 +3767,7 @@ static int get_normal_compl_info(char_u *line, int startcol, colnr_T curs_col) /// Sets the global variables: compl_col, compl_length and compl_pattern. static int get_wholeline_compl_info(char_u *line, colnr_T curs_col) { - compl_col = (colnr_T)getwhitecols(line); + compl_col = (colnr_T)getwhitecols((char *)line); compl_length = (int)curs_col - (int)compl_col; if (compl_length < 0) { // cursor in indent: empty pattern compl_length = 0; @@ -3509,7 +3879,7 @@ static int get_userdefined_compl_info(colnr_T curs_col) return FAIL; } - // Reset extended parameters of completion, when start new + // Reset extended parameters of completion, when starting new // completion. compl_opt_refresh_always = false; @@ -3556,6 +3926,14 @@ static int get_spell_compl_info(int startcol, colnr_T curs_col) } /// Get the completion pattern, column and length. +/// +/// @param startcol start column number of the completion pattern/text +/// @param cur_col current cursor column +/// +/// On return, "line_invalid" is set to true, if the current line may have +/// 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) { if (ctrl_x_mode_normal() @@ -3573,12 +3951,12 @@ static int compl_get_info(char_u *line, int startcol, colnr_T curs_col, bool *li if (get_userdefined_compl_info(curs_col) == FAIL) { return FAIL; } - *line_invalid = true; // 'line' may have become invalid + *line_invalid = true; // "line" may have become invalid } else if (ctrl_x_mode_spell()) { if (get_spell_compl_info(startcol, curs_col) == FAIL) { return FAIL; } - *line_invalid = true; // 'line' may have become invalid + *line_invalid = true; // "line" may have become invalid } else { internal_error("ins_complete()"); return FAIL; @@ -3587,232 +3965,191 @@ static int compl_get_info(char_u *line, int startcol, colnr_T curs_col, bool *li return OK; } -/// Do Insert mode completion. -/// Called when character "c" was typed, which has a meaning for completion. -/// Returns OK if completion was done, FAIL if something failed. -int ins_complete(int c, bool enable_pum) +/// Continue an interrupted completion mode search in "line". +/// +/// If this same ctrl_x_mode has been interrupted use the text from +/// "compl_startpos" to the cursor as a pattern to add a new word instead of +/// expand the one before the cursor, in word-wise if "compl_startpos" is not in +/// 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) { - char_u *line; - int startcol = 0; // column where searched text starts - colnr_T curs_col; // cursor column - int n; - int save_w_wrow; - int save_w_leftcol; - int insert_match; - const bool save_did_ai = did_ai; - int flags = CP_ORIGINAL_TEXT; - bool line_invalid = false; - - compl_direction = ins_compl_key2dir(c); - insert_match = ins_compl_use_match(c); - - if (!compl_started) { - // First time we hit ^N or ^P (in a row, I mean) - - did_ai = false; - did_si = false; - can_si = false; - can_si_back = false; - if (stop_arrow() == FAIL) { - return FAIL; - } - - line = ml_get(curwin->w_cursor.lnum); - curs_col = curwin->w_cursor.col; - compl_pending = 0; - - // If this same ctrl_x_mode has been interrupted use the text from - // "compl_startpos" to the cursor as a pattern to add a new word - // instead of expand the one before the cursor, in word-wise if - // "compl_startpos" is not in 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 -- Acevedo. - if ((compl_cont_status & CONT_INTRPT) == CONT_INTRPT - && compl_cont_mode == ctrl_x_mode) { - // it is a continued search - compl_cont_status &= ~CONT_INTRPT; // remove INTRPT - if (ctrl_x_mode_normal() - || ctrl_x_mode_path_patterns() - || ctrl_x_mode_path_defines()) { - if (compl_startpos.lnum != curwin->w_cursor.lnum) { - // line (probably) wrapped, set compl_startpos to the - // first non_blank in the line, if it is not a wordchar - // include it to get a better pattern, but then we don't - // want the "\\<" prefix, check it below. - 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 - } else { - // S_IPOS was set when we inserted a word that was at the - // beginning of the line, which means that we'll go to SOL - // 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_col = compl_startpos.col; - } - compl_length = curwin->w_cursor.col - (int)compl_col; - // IObuff is used to add a "word from the next line" would we - // have enough space? just being paranoid -#define MIN_SPACE 75 - if (compl_length > (IOSIZE - MIN_SPACE)) { - compl_cont_status &= ~CONT_SOL; - compl_length = (IOSIZE - MIN_SPACE); - compl_col = curwin->w_cursor.col - compl_length; - } - compl_cont_status |= CONT_ADDING | CONT_N_ADDS; - if (compl_length < 1) { - compl_cont_status &= CONT_LOCAL; - } - } else if (ctrl_x_mode_line_or_eval()) { - compl_cont_status = CONT_ADDING | CONT_N_ADDS; - } else { - compl_cont_status = 0; - } + // it is a continued search + compl_cont_status &= ~CONT_INTRPT; // remove INTRPT + if (ctrl_x_mode_normal() + || ctrl_x_mode_path_patterns() + || ctrl_x_mode_path_defines()) { + if (compl_startpos.lnum != curwin->w_cursor.lnum) { + // line (probably) wrapped, set compl_startpos to the + // first non_blank in the line, if it is not a wordchar + // include it to get a better pattern, but then we don't + // want the "\\<" prefix, check it below. + compl_col = (colnr_T)getwhitecols((char *)line); + compl_startpos.col = compl_col; + compl_startpos.lnum = curwin->w_cursor.lnum; + compl_cont_status &= ~CONT_SOL; // clear SOL if present } else { - compl_cont_status &= CONT_LOCAL; - } - - if (!(compl_cont_status & CONT_ADDING)) { // normal expansion - compl_cont_mode = ctrl_x_mode; - if (ctrl_x_mode_not_default()) { - // Remove LOCAL if ctrl_x_mode != CTRL_X_NORMAL - compl_cont_status = 0; + // S_IPOS was set when we inserted a word that was at the + // beginning of the line, which means that we'll go to SOL + // 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_cont_status |= CONT_N_ADDS; - compl_startpos = curwin->w_cursor; - startcol = (int)curs_col; - compl_col = 0; - } - - // Work out completion pattern and original text -- webb - 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 - did_ai = save_did_ai; - } - return FAIL; + compl_col = compl_startpos.col; } - // If "line" was changed while getting completion info get it again. - if (line_invalid) { - line = ml_get(curwin->w_cursor.lnum); + compl_length = curwin->w_cursor.col - (int)compl_col; + // IObuff is used to add a "word from the next line" would we + // have enough space? just being paranoid +#define MIN_SPACE 75 + if (compl_length > (IOSIZE - MIN_SPACE)) { + compl_cont_status &= ~CONT_SOL; + compl_length = (IOSIZE - MIN_SPACE); + compl_col = curwin->w_cursor.col - compl_length; + } + compl_cont_status |= CONT_ADDING | CONT_N_ADDS; + if (compl_length < 1) { + compl_cont_status &= CONT_LOCAL; } + } else if (ctrl_x_mode_line_or_eval()) { + compl_cont_status = CONT_ADDING | CONT_N_ADDS; + } else { + compl_cont_status = 0; + } +} - if (compl_cont_status & CONT_ADDING) { - edit_submode_pre = (char_u *)_(" Adding"); - if (ctrl_x_mode_line_or_eval()) { - // Insert a new line, keep indentation but ignore 'comments'. - char_u *old = curbuf->b_p_com; +/// start insert mode completion +static int ins_compl_start(void) +{ + const bool save_did_ai = did_ai; - curbuf->b_p_com = (char_u *)""; - compl_startpos.lnum = curwin->w_cursor.lnum; - compl_startpos.col = compl_col; - ins_eol('\r'); - curbuf->b_p_com = old; - compl_length = 0; - compl_col = curwin->w_cursor.col; - } - } else { - edit_submode_pre = NULL; - compl_startpos.col = compl_col; - } + // First time we hit ^N or ^P (in a row, I mean) - if (compl_cont_status & CONT_LOCAL) { - edit_submode = (char_u *)_(ctrl_x_msgs[CTRL_X_LOCAL_MSG]); - } else { - edit_submode = (char_u *)_(CTRL_X_MSG(ctrl_x_mode)); - } + did_ai = false; + did_si = false; + can_si = false; + can_si_back = false; + if (stop_arrow() == FAIL) { + return FAIL; + } - // If any of the original typed text has been changed we need to fix - // the redo buffer. - ins_compl_fixRedoBufForLeader(NULL); + char_u *line = ml_get(curwin->w_cursor.lnum); + colnr_T curs_col = curwin->w_cursor.col; + compl_pending = 0; + + if ((compl_cont_status & CONT_INTRPT) == CONT_INTRPT + && compl_cont_mode == ctrl_x_mode) { + // this same ctrl-x_mode was interrupted previously. Continue the + // completion. + ins_compl_continue_search(line); + } else { + compl_cont_status &= CONT_LOCAL; + } - // Always add completion for the original text. - xfree(compl_orig_text); - compl_orig_text = vim_strnsave(line + compl_col, (size_t)compl_length); - if (p_ic) { - flags |= CP_ICASE; - } - if (ins_compl_add(compl_orig_text, -1, NULL, NULL, false, NULL, 0, - flags, false) != OK) { - XFREE_CLEAR(compl_pattern); - XFREE_CLEAR(compl_orig_text); - return FAIL; + int startcol = 0; // column where searched text starts + if (!compl_status_adding()) { // normal expansion + compl_cont_mode = ctrl_x_mode; + if (ctrl_x_mode_not_default()) { + // Remove LOCAL if ctrl_x_mode != CTRL_X_NORMAL + compl_cont_status = 0; } + compl_cont_status |= CONT_N_ADDS; + compl_startpos = curwin->w_cursor; + startcol = (int)curs_col; + compl_col = 0; + } - // 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_submode_extra = (char_u *)_("-- Searching..."); - edit_submode_highl = HLF_COUNT; - showmode(); - edit_submode_extra = NULL; - ui_flush(); - } else if (insert_match && stop_arrow() == FAIL) { + // Work out completion pattern and original text -- webb + bool line_invalid = false; + 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 + did_ai = save_did_ai; + } return FAIL; } + // If "line" was changed while getting completion info get it again. + if (line_invalid) { + line = ml_get(curwin->w_cursor.lnum); + } - compl_shown_match = compl_curr_match; - compl_shows_dir = compl_direction; + if (compl_status_adding()) { + edit_submode_pre = _(" Adding"); + if (ctrl_x_mode_line_or_eval()) { + // Insert a new line, keep indentation but ignore 'comments'. + char *old = curbuf->b_p_com; - // Find next match (and following matches). - save_w_wrow = curwin->w_wrow; - save_w_leftcol = curwin->w_leftcol; - n = ins_compl_next(true, ins_compl_key2count(c), insert_match, false); + curbuf->b_p_com = ""; + compl_startpos.lnum = curwin->w_cursor.lnum; + compl_startpos.col = compl_col; + ins_eol('\r'); + curbuf->b_p_com = old; + compl_length = 0; + compl_col = curwin->w_cursor.col; + } + } else { + edit_submode_pre = NULL; + compl_startpos.col = compl_col; + } - if (n > 1) { // all matches have been found - compl_matches = n; + if (compl_cont_status & CONT_LOCAL) { + edit_submode = _(ctrl_x_msgs[CTRL_X_LOCAL_MSG]); + } else { + edit_submode = _(CTRL_X_MSG(ctrl_x_mode)); } - compl_curr_match = compl_shown_match; - compl_direction = compl_shows_dir; - // Eat the ESC that vgetc() returns after a CTRL-C to avoid leaving Insert - // mode. - if (got_int && !global_busy) { - (void)vgetc(); - got_int = false; + // If any of the original typed text has been changed we need to fix + // the redo buffer. + ins_compl_fixRedoBufForLeader(NULL); + + // Always add completion for the original text. + xfree(compl_orig_text); + compl_orig_text = vim_strnsave(line + compl_col, (size_t)compl_length); + int flags = CP_ORIGINAL_TEXT; + if (p_ic) { + flags |= CP_ICASE; } + if (ins_compl_add(compl_orig_text, -1, NULL, NULL, false, NULL, 0, + flags, false) != OK) { + XFREE_CLEAR(compl_pattern); + XFREE_CLEAR(compl_orig_text); + return FAIL; + } + + // 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_submode_extra = _("-- Searching..."); + edit_submode_highl = HLF_COUNT; + showmode(); + edit_submode_extra = NULL; + ui_flush(); + + return OK; +} +/// display the completion status message +static void ins_compl_show_statusmsg(void) +{ // we found no match if the list has only the "compl_orig_text"-entry - if (compl_first_match == compl_first_match->cp_next) { - edit_submode_extra = (compl_cont_status & CONT_ADDING) - && compl_length > 1 - ? (char_u *)_(e_hitend) : (char_u *)_(e_patnotf); + if (is_first_match(compl_first_match->cp_next)) { + edit_submode_extra = compl_status_adding() && compl_length > 1 ? _(e_hitend) : _(e_patnotf); edit_submode_highl = HLF_E; - // remove N_ADDS flag, so next ^X<> won't try to go to ADDING mode, - // because we couldn't expand anything at first place, but if we used - // ^P, ^N, ^X^I or ^X^D we might want to add-expand a single-char-word - // (such as M in M'exico) if not tried already. -- Acevedo - if (compl_length > 1 - || (compl_cont_status & CONT_ADDING) - || (ctrl_x_mode_not_default() - && !ctrl_x_mode_path_patterns() - && !ctrl_x_mode_path_defines())) { - compl_cont_status &= ~CONT_N_ADDS; - } - } - - if (compl_curr_match->cp_flags & CP_CONT_S_IPOS) { - compl_cont_status |= CONT_S_IPOS; - } else { - compl_cont_status &= ~CONT_S_IPOS; } if (edit_submode_extra == NULL) { - if (compl_curr_match->cp_flags & CP_ORIGINAL_TEXT) { - edit_submode_extra = (char_u *)_("Back at original"); + if (match_at_original_text(compl_curr_match)) { + edit_submode_extra = _("Back at original"); edit_submode_highl = HLF_W; } else if (compl_cont_status & CONT_S_IPOS) { - edit_submode_extra = (char_u *)_("Word from other line"); + edit_submode_extra = _("Word from other line"); edit_submode_highl = HLF_COUNT; } else if (compl_curr_match->cp_next == compl_curr_match->cp_prev) { - edit_submode_extra = (char_u *)_("The only match"); + edit_submode_extra = _("The only match"); edit_submode_highl = HLF_COUNT; compl_curr_match->cp_number = 1; } else { @@ -3837,7 +4174,7 @@ int ins_complete(int c, bool enable_pum) _("match %d"), compl_curr_match->cp_number); } - edit_submode_extra = match_ref; + edit_submode_extra = (char *)match_ref; edit_submode_highl = HLF_R; if (dollar_vcol >= 0) { curs_columns(curwin, false); @@ -3861,6 +4198,72 @@ int ins_complete(int c, bool enable_pum) msg_clr_cmdline(); // necessary for "noshowmode" } } +} + +/// Do Insert mode completion. +/// Called when character "c" was typed, which has a meaning for completion. +/// Returns OK if completion was done, FAIL if something failed. +int ins_complete(int c, bool enable_pum) +{ + int n; + int save_w_wrow; + int save_w_leftcol; + int insert_match; + + compl_direction = ins_compl_key2dir(c); + insert_match = ins_compl_use_match(c); + + if (!compl_started) { + if (ins_compl_start() == FAIL) { + return FAIL; + } + } else if (insert_match && stop_arrow() == FAIL) { + return FAIL; + } + + compl_shown_match = compl_curr_match; + compl_shows_dir = compl_direction; + + // Find next match (and following matches). + save_w_wrow = curwin->w_wrow; + save_w_leftcol = curwin->w_leftcol; + n = ins_compl_next(true, ins_compl_key2count(c), insert_match, false); + + if (n > 1) { // all matches have been found + compl_matches = n; + } + compl_curr_match = compl_shown_match; + compl_direction = compl_shows_dir; + + // Eat the ESC that vgetc() returns after a CTRL-C to avoid leaving Insert + // mode. + if (got_int && !global_busy) { + (void)vgetc(); + got_int = false; + } + + // we found no match if the list has only the "compl_orig_text"-entry + if (is_first_match(compl_first_match->cp_next)) { + // remove N_ADDS flag, so next ^X<> won't try to go to ADDING mode, + // because we couldn't expand anything at first place, but if we used + // ^P, ^N, ^X^I or ^X^D we might want to add-expand a single-char-word + // (such as M in M'exico) if not tried already. -- Acevedo + if (compl_length > 1 + || compl_status_adding() + || (ctrl_x_mode_not_default() + && !ctrl_x_mode_path_patterns() + && !ctrl_x_mode_path_defines())) { + compl_cont_status &= ~CONT_N_ADDS; + } + } + + if (compl_curr_match->cp_flags & CP_CONT_S_IPOS) { + compl_cont_status |= CONT_S_IPOS; + } else { + compl_cont_status &= ~CONT_S_IPOS; + } + + ins_compl_show_statusmsg(); // Show the popup menu, unless we got interrupted. if (enable_pum && !compl_interrupted) { @@ -3872,6 +4275,7 @@ int ins_complete(int c, bool enable_pum) return OK; } +/// Remove (if needed) and show the popup menu static void show_pum(int prev_w_wrow, int prev_w_leftcol) { // RedrawingDisabled may be set when invoked through complete(). diff --git a/src/nvim/keycodes.c b/src/nvim/keycodes.c index 5a5257efb2..2b00d371f0 100644 --- a/src/nvim/keycodes.c +++ b/src/nvim/keycodes.c @@ -57,15 +57,15 @@ static char_u modifier_keys_table[] = MOD_MASK_SHIFT, '*', '4', 'k', 'D', // delete char MOD_MASK_SHIFT, '*', '5', 'k', 'L', // delete line MOD_MASK_SHIFT, '*', '7', '@', '7', // end - MOD_MASK_CTRL, KS_EXTRA, (int)KE_C_END, '@', '7', // end + MOD_MASK_CTRL, KS_EXTRA, KE_C_END, '@', '7', // end MOD_MASK_SHIFT, '*', '9', '@', '9', // exit MOD_MASK_SHIFT, '*', '0', '@', '0', // find MOD_MASK_SHIFT, '#', '1', '%', '1', // help MOD_MASK_SHIFT, '#', '2', 'k', 'h', // home - MOD_MASK_CTRL, KS_EXTRA, (int)KE_C_HOME, 'k', 'h', // home + MOD_MASK_CTRL, KS_EXTRA, KE_C_HOME, 'k', 'h', // home MOD_MASK_SHIFT, '#', '3', 'k', 'I', // insert MOD_MASK_SHIFT, '#', '4', 'k', 'l', // left arrow - MOD_MASK_CTRL, KS_EXTRA, (int)KE_C_LEFT, 'k', 'l', // left arrow + MOD_MASK_CTRL, KS_EXTRA, KE_C_LEFT, 'k', 'l', // left arrow MOD_MASK_SHIFT, '%', 'a', '%', '3', // message MOD_MASK_SHIFT, '%', 'b', '%', '4', // move MOD_MASK_SHIFT, '%', 'c', '%', '5', // next @@ -75,63 +75,63 @@ static char_u modifier_keys_table[] = MOD_MASK_SHIFT, '%', 'g', '%', '0', // redo MOD_MASK_SHIFT, '%', 'h', '&', '3', // replace MOD_MASK_SHIFT, '%', 'i', 'k', 'r', // right arr. - MOD_MASK_CTRL, KS_EXTRA, (int)KE_C_RIGHT, 'k', 'r', // right arr. + MOD_MASK_CTRL, KS_EXTRA, KE_C_RIGHT, 'k', 'r', // right arr. MOD_MASK_SHIFT, '%', 'j', '&', '5', // resume MOD_MASK_SHIFT, '!', '1', '&', '6', // save MOD_MASK_SHIFT, '!', '2', '&', '7', // suspend MOD_MASK_SHIFT, '!', '3', '&', '8', // undo - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_UP, 'k', 'u', // up arrow - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_DOWN, 'k', 'd', // down arrow + MOD_MASK_SHIFT, KS_EXTRA, KE_S_UP, 'k', 'u', // up arrow + MOD_MASK_SHIFT, KS_EXTRA, KE_S_DOWN, 'k', 'd', // down arrow // vt100 F1 - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_XF1, KS_EXTRA, (int)KE_XF1, - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_XF2, KS_EXTRA, (int)KE_XF2, - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_XF3, KS_EXTRA, (int)KE_XF3, - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_XF4, KS_EXTRA, (int)KE_XF4, - - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F1, 'k', '1', // F1 - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F2, 'k', '2', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F3, 'k', '3', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F4, 'k', '4', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F5, 'k', '5', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F6, 'k', '6', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F7, 'k', '7', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F8, 'k', '8', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F9, 'k', '9', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F10, 'k', ';', // F10 - - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F11, 'F', '1', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F12, 'F', '2', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F13, 'F', '3', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F14, 'F', '4', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F15, 'F', '5', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F16, 'F', '6', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F17, 'F', '7', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F18, 'F', '8', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F19, 'F', '9', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F20, 'F', 'A', - - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F21, 'F', 'B', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F22, 'F', 'C', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F23, 'F', 'D', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F24, 'F', 'E', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F25, 'F', 'F', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F26, 'F', 'G', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F27, 'F', 'H', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F28, 'F', 'I', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F29, 'F', 'J', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F30, 'F', 'K', - - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F31, 'F', 'L', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F32, 'F', 'M', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F33, 'F', 'N', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F34, 'F', 'O', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F35, 'F', 'P', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F36, 'F', 'Q', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F37, 'F', 'R', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_XF1, KS_EXTRA, KE_XF1, + MOD_MASK_SHIFT, KS_EXTRA, KE_S_XF2, KS_EXTRA, KE_XF2, + MOD_MASK_SHIFT, KS_EXTRA, KE_S_XF3, KS_EXTRA, KE_XF3, + MOD_MASK_SHIFT, KS_EXTRA, KE_S_XF4, KS_EXTRA, KE_XF4, + + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F1, 'k', '1', // F1 + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F2, 'k', '2', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F3, 'k', '3', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F4, 'k', '4', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F5, 'k', '5', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F6, 'k', '6', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F7, 'k', '7', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F8, 'k', '8', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F9, 'k', '9', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F10, 'k', ';', // F10 + + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F11, 'F', '1', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F12, 'F', '2', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F13, 'F', '3', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F14, 'F', '4', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F15, 'F', '5', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F16, 'F', '6', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F17, 'F', '7', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F18, 'F', '8', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F19, 'F', '9', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F20, 'F', 'A', + + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F21, 'F', 'B', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F22, 'F', 'C', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F23, 'F', 'D', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F24, 'F', 'E', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F25, 'F', 'F', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F26, 'F', 'G', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F27, 'F', 'H', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F28, 'F', 'I', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F29, 'F', 'J', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F30, 'F', 'K', + + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F31, 'F', 'L', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F32, 'F', 'M', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F33, 'F', 'N', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F34, 'F', 'O', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F35, 'F', 'P', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F36, 'F', 'Q', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F37, 'F', 'R', // TAB pseudo code - MOD_MASK_SHIFT, 'k', 'B', KS_EXTRA, (int)KE_TAB, + MOD_MASK_SHIFT, 'k', 'B', KS_EXTRA, KE_TAB, NUL }; @@ -349,26 +349,26 @@ static struct mousetable { bool is_drag; // Is it a mouse drag event? } mouse_table[] = { - { (int)KE_LEFTMOUSE, MOUSE_LEFT, true, false }, - { (int)KE_LEFTDRAG, MOUSE_LEFT, false, true }, - { (int)KE_LEFTRELEASE, MOUSE_LEFT, false, false }, - { (int)KE_MIDDLEMOUSE, MOUSE_MIDDLE, true, false }, - { (int)KE_MIDDLEDRAG, MOUSE_MIDDLE, false, true }, - { (int)KE_MIDDLERELEASE, MOUSE_MIDDLE, false, false }, - { (int)KE_RIGHTMOUSE, MOUSE_RIGHT, true, false }, - { (int)KE_RIGHTDRAG, MOUSE_RIGHT, false, true }, - { (int)KE_RIGHTRELEASE, MOUSE_RIGHT, false, false }, - { (int)KE_X1MOUSE, MOUSE_X1, true, false }, - { (int)KE_X1DRAG, MOUSE_X1, false, true }, - { (int)KE_X1RELEASE, MOUSE_X1, false, false }, - { (int)KE_X2MOUSE, MOUSE_X2, true, false }, - { (int)KE_X2DRAG, MOUSE_X2, false, true }, - { (int)KE_X2RELEASE, MOUSE_X2, false, false }, + { KE_LEFTMOUSE, MOUSE_LEFT, true, false }, + { KE_LEFTDRAG, MOUSE_LEFT, false, true }, + { KE_LEFTRELEASE, MOUSE_LEFT, false, false }, + { KE_MIDDLEMOUSE, MOUSE_MIDDLE, true, false }, + { KE_MIDDLEDRAG, MOUSE_MIDDLE, false, true }, + { KE_MIDDLERELEASE, MOUSE_MIDDLE, false, false }, + { KE_RIGHTMOUSE, MOUSE_RIGHT, true, false }, + { KE_RIGHTDRAG, MOUSE_RIGHT, false, true }, + { KE_RIGHTRELEASE, MOUSE_RIGHT, false, false }, + { KE_X1MOUSE, MOUSE_X1, true, false }, + { KE_X1DRAG, MOUSE_X1, false, true }, + { KE_X1RELEASE, MOUSE_X1, false, false }, + { KE_X2MOUSE, MOUSE_X2, true, false }, + { KE_X2DRAG, MOUSE_X2, false, true }, + { KE_X2RELEASE, MOUSE_X2, false, false }, // DRAG without CLICK - { (int)KE_MOUSEMOVE, MOUSE_RELEASE, false, true }, + { KE_MOUSEMOVE, MOUSE_RELEASE, false, true }, // RELEASE without CLICK - { (int)KE_IGNORE, MOUSE_RELEASE, false, false }, - { 0, 0, 0, 0 }, + { KE_IGNORE, MOUSE_RELEASE, false, false }, + { 0, 0, 0, 0 }, }; /// Return the modifier mask bit (#MOD_MASK_*) corresponding to mod name @@ -926,8 +926,8 @@ char *replace_termcodes(const char *const from, const size_t from_len, char **co } else { src += 5; result[dlen++] = K_SPECIAL; - result[dlen++] = (int)KS_EXTRA; - result[dlen++] = (int)KE_SNR; + result[dlen++] = KS_EXTRA; + result[dlen++] = KE_SNR; snprintf((char *)result + dlen, buf_len - dlen, "%" PRId64, (int64_t)current_sctx.sc_sid); dlen += STRLEN(result + dlen); diff --git a/src/nvim/locale.c b/src/nvim/locale.c new file mode 100644 index 0000000000..3e0774c096 --- /dev/null +++ b/src/nvim/locale.c @@ -0,0 +1,369 @@ +// 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 + +// locale.c: functions for language/locale configuration + +#include "auto/config.h" + +#ifdef HAVE_LOCALE_H +# include <locale.h> +#endif + +#include "nvim/ascii.h" +#include "nvim/buffer.h" +#include "nvim/charset.h" +#include "nvim/eval.h" +#include "nvim/garray.h" +#include "nvim/locale.h" +#include "nvim/memory.h" +#include "nvim/message.h" +#include "nvim/option.h" +#include "nvim/os/os.h" +#include "nvim/os/shell.h" +#include "nvim/path.h" +#include "nvim/profile.h" +#include "nvim/types.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "locale.c.generated.h" +#endif + +#if defined(HAVE_LOCALE_H) +# define HAVE_GET_LOCALE_VAL + +static char *get_locale_val(int what) +{ + // Obtain the locale value from the libraries. + char *loc = setlocale(what, NULL); + + return loc; +} +#endif + +/// @return true when "lang" starts with a valid language name. +/// Rejects NULL, empty string, "C", "C.UTF-8" and others. +static bool is_valid_mess_lang(char *lang) +{ + return lang != NULL && ASCII_ISALPHA(lang[0]) && ASCII_ISALPHA(lang[1]); +} + +/// Obtain the current messages language. Used to set the default for +/// 'helplang'. May return NULL or an empty string. +char *get_mess_lang(void) +{ + char *p; + +#ifdef HAVE_GET_LOCALE_VAL +# if defined(LC_MESSAGES) + p = get_locale_val(LC_MESSAGES); +# else + // This is necessary for Win32, where LC_MESSAGES is not defined and $LANG + // may be set to the LCID number. LC_COLLATE is the best guess, LC_TIME + // and LC_MONETARY may be set differently for a Japanese working in the + // US. + p = get_locale_val(LC_COLLATE); +# endif +#else + p = os_getenv("LC_ALL"); + if (!is_valid_mess_lang(p)) { + p = os_getenv("LC_MESSAGES"); + if (!is_valid_mess_lang(p)) { + p = os_getenv("LANG"); + } + } +#endif + return is_valid_mess_lang(p) ? p : NULL; +} + +// Complicated #if; matches with where get_mess_env() is used below. +#ifdef HAVE_WORKING_LIBINTL +/// Get the language used for messages from the environment. +static char *get_mess_env(void) +{ + char *p; + + p = (char *)os_getenv("LC_ALL"); + if (p == NULL) { + p = (char *)os_getenv("LC_MESSAGES"); + if (p == NULL) { + p = (char *)os_getenv("LANG"); + if (p != NULL && ascii_isdigit(*p)) { + p = NULL; // ignore something like "1043" + } +# ifdef HAVE_GET_LOCALE_VAL + if (p == NULL) { + p = get_locale_val(LC_CTYPE); + } +# endif + } + } + return p; +} +#endif + +/// Set the "v:lang" variable according to the current locale setting. +/// Also do "v:lc_time"and "v:ctype". +void set_lang_var(void) +{ + const char *loc; + +#ifdef HAVE_GET_LOCALE_VAL + loc = get_locale_val(LC_CTYPE); +#else + // setlocale() not supported: use the default value + loc = "C"; +#endif + set_vim_var_string(VV_CTYPE, loc, -1); + + // When LC_MESSAGES isn't defined use the value from $LC_MESSAGES, fall + // back to LC_CTYPE if it's empty. +#ifdef HAVE_WORKING_LIBINTL + loc = get_mess_env(); +#elif defined(LC_MESSAGES) + loc = get_locale_val(LC_MESSAGES); +#else + // In Windows LC_MESSAGES is not defined fallback to LC_CTYPE + loc = get_locale_val(LC_CTYPE); +#endif + set_vim_var_string(VV_LANG, loc, -1); + +#ifdef HAVE_GET_LOCALE_VAL + loc = get_locale_val(LC_TIME); +#endif + set_vim_var_string(VV_LC_TIME, loc, -1); + +#ifdef HAVE_GET_LOCALE_VAL + loc = get_locale_val(LC_COLLATE); +#else + // setlocale() not supported: use the default value + loc = "C"; +#endif + set_vim_var_string(VV_COLLATE, loc, -1); +} + +#if defined(HAVE_LOCALE_H) +/// Setup to use the current locale (for ctype() and many other things). +void init_locale(void) +{ + setlocale(LC_ALL, ""); + +# ifdef LC_NUMERIC + // Make sure strtod() uses a decimal point, not a comma. + setlocale(LC_NUMERIC, "C"); +# endif + + char localepath[MAXPATHL] = { 0 }; + snprintf(localepath, sizeof(localepath), "%s", get_vim_var_str(VV_PROGPATH)); + char *tail = path_tail_with_sep(localepath); + *tail = NUL; + tail = path_tail(localepath); + xstrlcpy(tail, "share/locale", + sizeof(localepath) - (size_t)(tail - localepath)); + bindtextdomain(PROJECT_NAME, localepath); + textdomain(PROJECT_NAME); + TIME_MSG("locale set"); +} +#endif + +#ifdef HAVE_WORKING_LIBINTL + +/// ":language": Set the language (locale). +/// +/// @param eap +void ex_language(exarg_T *eap) +{ + char *loc; + char *p; + char *name; + int what = LC_ALL; + char *whatstr = ""; +# ifdef LC_MESSAGES +# define VIM_LC_MESSAGES LC_MESSAGES +# else +# define VIM_LC_MESSAGES 6789 +# endif + + name = eap->arg; + + // Check for "messages {name}", "ctype {name}" or "time {name}" argument. + // Allow abbreviation, but require at least 3 characters to avoid + // confusion with a two letter language name "me" or "ct". + p = skiptowhite(eap->arg); + if ((*p == NUL || ascii_iswhite(*p)) && p - eap->arg >= 3) { + if (STRNICMP(eap->arg, "messages", p - eap->arg) == 0) { + what = VIM_LC_MESSAGES; + name = skipwhite(p); + whatstr = "messages "; + } else if (STRNICMP(eap->arg, "ctype", p - eap->arg) == 0) { + what = LC_CTYPE; + name = skipwhite(p); + whatstr = "ctype "; + } else if (STRNICMP(eap->arg, "time", p - eap->arg) == 0) { + what = LC_TIME; + name = skipwhite(p); + whatstr = "time "; + } else if (STRNICMP(eap->arg, "collate", p - eap->arg) == 0) { + what = LC_COLLATE; + name = skipwhite(p); + whatstr = "collate "; + } + } + + if (*name == NUL) { + if (what == VIM_LC_MESSAGES) { + p = get_mess_env(); + } else { + p = setlocale(what, NULL); + } + if (p == NULL || *p == NUL) { + p = "Unknown"; + } + smsg(_("Current %slanguage: \"%s\""), whatstr, p); + } else { +# ifndef LC_MESSAGES + if (what == VIM_LC_MESSAGES) { + loc = ""; + } else { +# endif + loc = setlocale(what, name); +# ifdef LC_NUMERIC + // Make sure strtod() uses a decimal point, not a comma. + setlocale(LC_NUMERIC, "C"); +# endif +# ifndef LC_MESSAGES + } +# endif + if (loc == NULL) { + semsg(_("E197: Cannot set language to \"%s\""), name); + } else { +# ifdef HAVE_NL_MSG_CAT_CNTR + // Need to do this for GNU gettext, otherwise cached translations + // will be used again. + extern int _nl_msg_cat_cntr; + + _nl_msg_cat_cntr++; +# endif + // Reset $LC_ALL, otherwise it would overrule everything. + os_setenv("LC_ALL", "", 1); + + if (what != LC_TIME && what != LC_COLLATE) { + // Tell gettext() what to translate to. It apparently doesn't + // use the currently effective locale. + if (what == LC_ALL) { + os_setenv("LANG", name, 1); + + // Clear $LANGUAGE because GNU gettext uses it. + os_setenv("LANGUAGE", "", 1); + } + if (what != LC_CTYPE) { + os_setenv("LC_MESSAGES", name, 1); + set_helplang_default(name); + } + } + + // Set v:lang, v:lc_time, v:collate and v:ctype to the final result. + set_lang_var(); + maketitle(); + } + } +} + +static char **locales = NULL; // Array of all available locales + +# ifndef WIN32 +static bool did_init_locales = false; + +/// @return an array of strings for all available locales + NULL for the +/// last element or, +/// NULL in case of error. +static char **find_locales(void) +{ + garray_T locales_ga; + char *loc; + char *saveptr = NULL; + + // 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); + if (locale_a == NULL) { + return NULL; + } + ga_init(&locales_ga, sizeof(char_u *), 20); + + // Transform locale_a string where each locale is separated by "\n" + // into an array of locale strings. + loc = os_strtok(locale_a, "\n", &saveptr); + + while (loc != NULL) { + loc = xstrdup(loc); + GA_APPEND(char *, &locales_ga, loc); + loc = os_strtok(NULL, "\n", &saveptr); + } + 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; + return locales_ga.ga_data; +} +# endif + +/// Lazy initialization of all available locales. +static void init_locales(void) +{ +# ifndef WIN32 + if (!did_init_locales) { + 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); + } +} +# endif + +/// Function given to ExpandGeneric() to obtain the possible arguments of the +/// ":language" command. +char *get_lang_arg(expand_T *xp, int idx) +{ + if (idx == 0) { + return "messages"; + } + if (idx == 1) { + return "ctype"; + } + if (idx == 2) { + return "time"; + } + if (idx == 3) { + return "collate"; + } + + init_locales(); + if (locales == NULL) { + return NULL; + } + return locales[idx - 4]; +} + +/// Function given to ExpandGeneric() to obtain the available locales. +char *get_locales(expand_T *xp, int idx) +{ + init_locales(); + if (locales == NULL) { + return NULL; + } + return locales[idx]; +} + +#endif diff --git a/src/nvim/locale.h b/src/nvim/locale.h new file mode 100644 index 0000000000..39735d371f --- /dev/null +++ b/src/nvim/locale.h @@ -0,0 +1,10 @@ +#ifndef NVIM_LOCALE_H +#define NVIM_LOCALE_H + +#include "nvim/ex_cmds_defs.h" +#include "nvim/types.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "locale.h.generated.h" +#endif +#endif // NVIM_LOCALE_H diff --git a/src/nvim/log.c b/src/nvim/log.c index 99b17a612b..9bdf327430 100644 --- a/src/nvim/log.c +++ b/src/nvim/log.c @@ -60,16 +60,16 @@ static bool log_try_create(char *fname) static void log_path_init(void) { size_t size = sizeof(log_file_path); - expand_env((char_u *)"$" ENV_LOGFILE, (char_u *)log_file_path, (int)size - 1); + expand_env("$" ENV_LOGFILE, log_file_path, (int)size - 1); if (strequal("$" ENV_LOGFILE, log_file_path) || log_file_path[0] == '\0' - || os_isdir((char_u *)log_file_path) + || os_isdir(log_file_path) || !log_try_create(log_file_path)) { // Make $XDG_STATE_HOME if it does not exist. char *loghome = get_xdg_home(kXDGStateHome); char *failed_dir = NULL; bool log_dir_failure = false; - if (!os_isdir((char_u *)loghome)) { + if (!os_isdir(loghome)) { log_dir_failure = (os_mkdir_recurse(loghome, 0700, &failed_dir) != 0); } XFREE_CLEAR(loghome); diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c index 4d6e6090b8..49d49f76b9 100644 --- a/src/nvim/lua/converter.c +++ b/src/nvim/lua/converter.c @@ -59,7 +59,7 @@ static LuaTableProps nlua_traverse_table(lua_State *const lstate) size_t other_keys_num = 0; // Number of keys that are not string, integral // or type keys. LuaTableProps ret; - memset(&ret, 0, sizeof(ret)); + CLEAR_FIELD(ret); if (!lua_checkstack(lstate, lua_gettop(lstate) + 3)) { semsg(_("E1502: Lua failed to grow stack to %i"), lua_gettop(lstate) + 2); ret.type = kObjectTypeNil; diff --git a/src/nvim/lua/converter.h b/src/nvim/lua/converter.h index 1c9e60e4b2..f6a85900ba 100644 --- a/src/nvim/lua/converter.h +++ b/src/nvim/lua/converter.h @@ -6,7 +6,7 @@ #include <stdint.h> #include "nvim/api/private/defs.h" -#include "nvim/eval.h" +#include "nvim/eval/typval.h" #include "nvim/func_attr.h" typedef struct { diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 661dbfc4c2..42aa13cfc1 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -15,12 +15,14 @@ #include "nvim/buffer_defs.h" #include "nvim/change.h" #include "nvim/cursor.h" +#include "nvim/drawscreen.h" +#include "nvim/eval.h" #include "nvim/eval/funcs.h" #include "nvim/eval/typval.h" #include "nvim/eval/userfunc.h" #include "nvim/event/loop.h" #include "nvim/event/time.h" -#include "nvim/ex_cmds2.h" +#include "nvim/ex_cmds.h" #include "nvim/ex_getln.h" #include "nvim/extmark.h" #include "nvim/func_attr.h" @@ -37,7 +39,7 @@ #include "nvim/msgpack_rpc/channel.h" #include "nvim/os/os.h" #include "nvim/profile.h" -#include "nvim/screen.h" +#include "nvim/runtime.h" #include "nvim/undo.h" #include "nvim/usercmd.h" #include "nvim/version.h" @@ -447,7 +449,7 @@ static nlua_ref_state_t *nlua_new_ref_state(lua_State *lstate, bool is_thread) FUNC_ATTR_NONNULL_ALL { nlua_ref_state_t *ref_state = lua_newuserdata(lstate, sizeof(*ref_state)); - memset(ref_state, 0, sizeof(*ref_state)); + CLEAR_POINTER(ref_state); ref_state->nil_ref = LUA_NOREF; ref_state->empty_dict_ref = LUA_NOREF; if (!is_thread) { @@ -988,7 +990,7 @@ int nlua_in_fast_event(lua_State *lstate) static bool viml_func_is_fast(const char *name) { - const EvalFuncDef *const fdef = find_internal_func((const char *)name); + const EvalFuncDef *const fdef = find_internal_func(name); if (fdef) { return fdef->fast; } @@ -1025,7 +1027,7 @@ int nlua_call(lua_State *lstate) // TODO(bfredl): this should be simplified in error handling refactor force_abort = false; suppress_errthrow = false; - current_exception = NULL; + did_throw = false; did_emsg = false; try_start(); @@ -1091,7 +1093,7 @@ static int nlua_rpc(lua_State *lstate, bool request) Object result = rpc_send_call(chan_id, name, args, &res_mem, &err); if (!ERROR_SET(&err)) { nlua_push_Object(lstate, result, false); - arena_mem_free(res_mem, NULL); + arena_mem_free(res_mem); } } else { if (!rpc_send_event(chan_id, name, args)) { @@ -1313,12 +1315,11 @@ static void nlua_typval_exec(const char *lcmd, size_t lcmd_len, const char *name int nlua_source_using_linegetter(LineGetter fgetline, void *cookie, char *name) { - const linenr_T save_sourcing_lnum = sourcing_lnum; const sctx_T save_current_sctx = current_sctx; current_sctx.sc_sid = SID_STR; current_sctx.sc_seq = 0; current_sctx.sc_lnum = 0; - sourcing_lnum = 0; + estack_push(ETYPE_SCRIPT, name, 0); garray_T ga; char_u *line = NULL; @@ -1331,7 +1332,7 @@ int nlua_source_using_linegetter(LineGetter fgetline, void *cookie, char *name) size_t len = strlen(code); nlua_typval_exec(code, len, name, NULL, 0, false, NULL); - sourcing_lnum = save_sourcing_lnum; + estack_pop(); current_sctx = save_current_sctx; ga_clear_strings(&ga); xfree(code); @@ -1577,7 +1578,7 @@ void ex_luado(exarg_T *const eap) } lua_pop(lstate, 1); check_cursor(); - update_screen(NOT_VALID); + update_screen(UPD_NOT_VALID); } /// Run lua file @@ -1661,6 +1662,9 @@ static void nlua_add_treesitter(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL lua_pushcfunction(lstate, tslua_has_language); lua_setfield(lstate, -2, "_ts_has_language"); + lua_pushcfunction(lstate, tslua_remove_lang); + lua_setfield(lstate, -2, "_ts_remove_language"); + lua_pushcfunction(lstate, tslua_inspect_lang); lua_setfield(lstate, -2, "_ts_inspect_language"); @@ -1906,7 +1910,7 @@ void nlua_set_sctx(sctx_T *current) break; } char *source_path = fix_fname(info->source + 1); - get_current_script_id((char_u *)source_path, current); + get_current_script_id(&source_path, current); xfree(source_path); current->sc_lnum = info->currentline; current->sc_seq = -1; @@ -1939,8 +1943,13 @@ int nlua_do_ucmd(ucmd_T *cmd, exarg_T *eap, bool preview) // Split args by unescaped whitespace |<f-args>| (nargs dependent) if (cmd->uc_argt & EX_NOSPC) { - // Commands where nargs = 1 or "?" fargs is the same as args - lua_rawseti(lstate, -2, 1); + if ((cmd->uc_argt & EX_NEEDARG) || STRLEN(eap->arg)) { + // For commands where nargs is 1 or "?" and argument is passed, fargs = { args } + lua_rawseti(lstate, -2, 1); + } else { + // if nargs = "?" and no argument is passed, fargs = {} + lua_pop(lstate, 1); // Pop the reference of opts.args + } } else if (eap->args == NULL) { // For commands with more than one possible argument, split if argument list isn't available. lua_pop(lstate, 1); // Pop the reference of opts.args diff --git a/src/nvim/lua/executor.h b/src/nvim/lua/executor.h index 2afbbebfe7..78346fd81f 100644 --- a/src/nvim/lua/executor.h +++ b/src/nvim/lua/executor.h @@ -24,6 +24,8 @@ typedef struct { #endif } nlua_ref_state_t; +#define NLUA_EXEC_STATIC(cstr, arg, err) nlua_exec(STATIC_CSTR_AS_STRING(cstr), arg, err) + #define NLUA_CLEAR_REF(x) \ do { \ /* Take the address to avoid double evaluation. #1375 */ \ diff --git a/src/nvim/lua/stdlib.c b/src/nvim/lua/stdlib.c index 6ba0056f48..1b874e673a 100644 --- a/src/nvim/lua/stdlib.c +++ b/src/nvim/lua/stdlib.c @@ -16,10 +16,11 @@ #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/ex_cmds2.h" +#include "nvim/ex_eval.h" #include "nvim/ex_getln.h" #include "nvim/extmark.h" #include "nvim/func_attr.h" @@ -473,6 +474,52 @@ 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) +{ + int narg = lua_gettop(lstate); + + if (narg < 3) { + return luaL_error(lstate, "Expected at least 3 arguments"); + } + + for (int i = 1; i <= 3; i++) { + if (lua_type(lstate, i) != LUA_TSTRING) { + return luaL_argerror(lstate, i, "expected string"); + } + } + + 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))); + + vimconv_T vimconv; + vimconv.vc_type = CONV_NONE; + convert_setup_ext(&vimconv, from, false, to, false); + + char_u *ret = (char_u *)string_convert(&vimconv, (char *)str, &str_len); + + convert_setup(&vimconv, NULL, NULL); + + xfree(from); + xfree(to); + + if (ret == NULL) { + lua_pushnil(lstate); + } else { + lua_pushlstring(lstate, (char *)ret, str_len); + xfree(ret); + } + + return 1; +} + +#endif + void nlua_state_add_stdlib(lua_State *const lstate, bool is_thread) { if (!is_thread) { @@ -518,6 +565,13 @@ void nlua_state_add_stdlib(lua_State *const lstate, bool is_thread) // vim.spell 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 f0d847e352..7ff4fbbff4 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -14,11 +14,14 @@ #include <stdint.h> #include <stdlib.h> #include <string.h> +#include <uv.h> #include "nvim/api/private/helpers.h" #include "nvim/buffer.h" #include "nvim/lib/kvec.h" +#include "nvim/log.h" #include "nvim/lua/treesitter.h" +#include "nvim/map.h" #include "nvim/memline.h" #include "tree_sitter/api.h" @@ -85,6 +88,10 @@ static struct luaL_Reg node_meta[] = { { "prev_sibling", node_prev_sibling }, { "next_named_sibling", node_next_named_sibling }, { "prev_named_sibling", node_prev_named_sibling }, + { "named_children", node_named_children }, + { "root", node_root }, + { "byte_length", node_byte_length }, + { NULL, NULL } }; @@ -145,18 +152,27 @@ int tslua_has_language(lua_State *L) return 1; } +// Creates the language into the internal language map. +// +// Returns true if the language is correctly loaded in the language map int tslua_add_language(lua_State *L) { const char *path = luaL_checkstring(L, 1); const char *lang_name = luaL_checkstring(L, 2); + const char *symbol_name = lang_name; + + if (lua_gettop(L) >= 3 && !lua_isnil(L, 3)) { + symbol_name = luaL_checkstring(L, 3); + } if (pmap_has(cstr_t)(&langs, lang_name)) { - return 0; + lua_pushboolean(L, true); + return 1; } #define BUFSIZE 128 char symbol_buf[BUFSIZE]; - snprintf(symbol_buf, BUFSIZE, "tree_sitter_%s", lang_name); + snprintf(symbol_buf, BUFSIZE, "tree_sitter_%s", symbol_name); #undef BUFSIZE uv_lib_t lib; @@ -179,6 +195,7 @@ int tslua_add_language(lua_State *L) TSLanguage *lang = lang_parser(); if (lang == NULL) { + uv_dlclose(&lib); return luaL_error(L, "Failed to load parser %s: internal error", path); } @@ -198,6 +215,19 @@ int tslua_add_language(lua_State *L) return 1; } +int tslua_remove_lang(lua_State *L) +{ + const char *lang_name = luaL_checkstring(L, 1); + bool present = pmap_has(cstr_t)(&langs, lang_name); + if (present) { + char *key = (char *)pmap_key(cstr_t)(&langs, lang_name); + pmap_del(cstr_t)(&langs, lang_name); + xfree(key); + } + lua_pushboolean(L, present); + return 1; +} + int tslua_inspect_lang(lua_State *L) { const char *lang_name = luaL_checkstring(L, 1); @@ -593,7 +623,7 @@ void push_tree(lua_State *L, TSTree *tree, bool do_copy) lua_setfenv(L, -2); // [udata] } -static TSTree **tree_check(lua_State *L, uint16_t index) +static TSTree **tree_check(lua_State *L, int index) { TSTree **ud = luaL_checkudata(L, index, TS_META_TREE); return ud; @@ -1036,6 +1066,57 @@ static int node_prev_named_sibling(lua_State *L) return 1; } +static int node_named_children(lua_State *L) +{ + TSNode source; + if (!node_check(L, 1, &source)) { + return 0; + } + TSTreeCursor cursor = ts_tree_cursor_new(source); + + lua_newtable(L); + int curr_index = 0; + + if (ts_tree_cursor_goto_first_child(&cursor)) { + do { + TSNode node = ts_tree_cursor_current_node(&cursor); + if (ts_node_is_named(node)) { + push_node(L, node, 1); + lua_rawseti(L, -2, ++curr_index); + } + } while (ts_tree_cursor_goto_next_sibling(&cursor)); + } + + ts_tree_cursor_delete(&cursor); + return 1; +} + +static int node_root(lua_State *L) +{ + TSNode node; + if (!node_check(L, 1, &node)) { + return 0; + } + + TSNode root = ts_tree_root_node(node.tree); + push_node(L, root, 1); + return 1; +} + +static int node_byte_length(lua_State *L) +{ + TSNode node; + if (!node_check(L, 1, &node)) { + return 0; + } + + uint32_t start_byte = ts_node_start_byte(node); + uint32_t end_byte = ts_node_end_byte(node); + + lua_pushnumber(L, end_byte - start_byte); + return 1; +} + /// assumes the match table being on top of the stack static void set_match(lua_State *L, TSQueryMatch *match, int nodeidx) { diff --git a/src/nvim/lua/xdiff.c b/src/nvim/lua/xdiff.c index 71f85385b6..b2b5dfedee 100644 --- a/src/nvim/lua/xdiff.c +++ b/src/nvim/lua/xdiff.c @@ -269,9 +269,9 @@ int nlua_xdl_diff(lua_State *lstate) xpparam_t params; xdemitcb_t ecb; - memset(&cfg, 0, sizeof(cfg)); - memset(¶ms, 0, sizeof(params)); - memset(&ecb, 0, sizeof(ecb)); + CLEAR_FIELD(cfg); + CLEAR_FIELD(params); + CLEAR_FIELD(ecb); NluaXdiffMode mode = kNluaXdiffModeUnified; diff --git a/src/nvim/macros.h b/src/nvim/macros.h index a896a406d1..5601db274b 100644 --- a/src/nvim/macros.h +++ b/src/nvim/macros.h @@ -31,7 +31,7 @@ /// @return `s, sizeof(s) - 1` #define S_LEN(s) (s), (sizeof(s) - 1) -/// LINEEMPTY() - return TRUE if the line is empty +/// LINEEMPTY() - return true if the line is empty #define LINEEMPTY(p) (*ml_get(p) == NUL) // toupper() and tolower() that use the current locale. diff --git a/src/nvim/main.c b/src/nvim/main.c index 494ff0b4af..883f946cad 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -8,6 +8,7 @@ #include <stdint.h> #include <string.h> +#include "nvim/arglist.h" #include "nvim/ascii.h" #include "nvim/autocmd.h" #include "nvim/buffer.h" @@ -16,6 +17,7 @@ #include "nvim/decoration.h" #include "nvim/decoration_provider.h" #include "nvim/diff.h" +#include "nvim/drawscreen.h" #include "nvim/eval.h" #include "nvim/ex_cmds.h" #include "nvim/ex_cmds2.h" @@ -23,23 +25,19 @@ #include "nvim/ex_getln.h" #include "nvim/fileio.h" #include "nvim/fold.h" +#include "nvim/garray.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/locale.h" +#include "nvim/log.h" #include "nvim/lua/executor.h" #include "nvim/main.h" #include "nvim/mapping.h" -#include "nvim/ui_client.h" -#include "nvim/vim.h" -#ifdef HAVE_LOCALE_H -# include <locale.h> -#endif -#include "nvim/garray.h" -#include "nvim/grid.h" -#include "nvim/log.h" #include "nvim/mark.h" #include "nvim/mbyte.h" #include "nvim/memline.h" @@ -50,6 +48,7 @@ #include "nvim/normal.h" #include "nvim/ops.h" #include "nvim/option.h" +#include "nvim/optionstr.h" #include "nvim/os/fileio.h" #include "nvim/os/input.h" #include "nvim/os/os.h" @@ -57,18 +56,20 @@ #include "nvim/os/time.h" #include "nvim/os_unix.h" #include "nvim/path.h" -#include "nvim/popupmnu.h" +#include "nvim/popupmenu.h" #include "nvim/profile.h" #include "nvim/quickfix.h" -#include "nvim/screen.h" +#include "nvim/runtime.h" #include "nvim/shada.h" #include "nvim/sign.h" #include "nvim/state.h" #include "nvim/strings.h" #include "nvim/syntax.h" #include "nvim/ui.h" +#include "nvim/ui_client.h" #include "nvim/ui_compositor.h" #include "nvim/version.h" +#include "nvim/vim.h" #include "nvim/window.h" #ifdef WIN32 # include "nvim/os/os_win_console.h" @@ -159,6 +160,7 @@ bool event_teardown(void) void early_init(mparm_T *paramp) { env_init(); + estack_init(); cmdline_init(); eval_init(); // init global variables init_path(argv0 ? argv0 : "nvim"); @@ -475,7 +477,7 @@ int main(int argc, char **argv) if (exmode_active || use_remote_ui || use_builtin_ui) { // Don't clear the screen when starting in Ex mode, or when a UI might have // displayed messages. - redraw_later(curwin, VALID); + redraw_later(curwin, UPD_VALID); } else { screenclear(); // clear screen TIME_MSG("clearing screen"); @@ -504,7 +506,7 @@ int main(int argc, char **argv) // When started with "-q errorfile" jump to first error now. if (params.edit_type == EDIT_QF) { - qf_jump(NULL, 0, 0, FALSE); + qf_jump(NULL, 0, 0, false); TIME_MSG("jump to first error"); } @@ -516,7 +518,7 @@ 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); + diff_win_options(wp, true); } } @@ -535,7 +537,7 @@ int main(int argc, char **argv) starting = 0; RedrawingDisabled = 0; - redraw_all_later(NOT_VALID); + redraw_all_later(UPD_NOT_VALID); no_wait_return = false; // 'autochdir' has been postponed. @@ -709,8 +711,8 @@ void getout(int exitval) if (did_emsg) { // give the user a chance to read the (error) message - no_wait_return = FALSE; - wait_return(FALSE); + no_wait_return = false; + wait_return(false); } // Position the cursor again, the autocommands may have moved it @@ -797,30 +799,6 @@ static int get_number_arg(const char *p, int *idx, int def) return def; } -#if defined(HAVE_LOCALE_H) -/// Setup to use the current locale (for ctype() and many other things). -static void init_locale(void) -{ - setlocale(LC_ALL, ""); - -# ifdef LC_NUMERIC - // Make sure strtod() uses a decimal point, not a comma. - setlocale(LC_NUMERIC, "C"); -# endif - - char localepath[MAXPATHL] = { 0 }; - snprintf(localepath, sizeof(localepath), "%s", get_vim_var_str(VV_PROGPATH)); - char *tail = path_tail_with_sep(localepath); - *tail = NUL; - tail = path_tail(localepath); - xstrlcpy(tail, "share/locale", - sizeof(localepath) - (size_t)(tail - localepath)); - bindtextdomain(PROJECT_NAME, localepath); - textdomain(PROJECT_NAME); - TIME_MSG("locale set"); -} -#endif - static uint64_t server_connect(char *server_addr, const char **errmsg) { if (server_addr == NULL) { @@ -1048,7 +1026,7 @@ static void command_line_scan(mparm_T *parmp) } else if (STRNICMP(argv[0] + argv_idx, "clean", 5) == 0) { parmp->use_vimrc = "NONE"; parmp->clean = true; - set_option_value("shadafile", 0L, "NONE", 0); + set_option_value_give_err("shadafile", 0L, "NONE", 0); } else if (STRNICMP(argv[0] + argv_idx, "luamod-dev", 9) == 0) { nlua_disable_preload = true; } else { @@ -1062,7 +1040,7 @@ static void command_line_scan(mparm_T *parmp) } break; case 'A': // "-A" start in Arabic mode. - set_option_value("arabic", 1L, NULL, 0); + set_option_value_give_err("arabic", 1L, NULL, 0); break; case 'b': // "-b" binary mode. // Needs to be effective before expanding file names, because @@ -1093,10 +1071,10 @@ static void command_line_scan(mparm_T *parmp) os_exit(0); case 'H': // "-H" start in Hebrew mode: rl + hkmap set. p_hkmap = true; - set_option_value("rl", 1L, NULL, 0); + set_option_value_give_err("rl", 1L, NULL, 0); break; case 'l': // "-l" lisp mode, 'lisp' and 'showmatch' on. - set_option_value("lisp", 1L, NULL, 0); + set_option_value_give_err("lisp", 1L, NULL, 0); p_sm = true; break; case 'M': // "-M" no changes or writing of files @@ -1177,7 +1155,7 @@ static void command_line_scan(mparm_T *parmp) // default is 10: a little bit verbose p_verbose = get_number_arg(argv[0], &argv_idx, 10); if (argv[0][argv_idx] != NUL) { - set_option_value("verbosefile", 0L, argv[0] + argv_idx, 0); + set_option_value_give_err("verbosefile", 0L, argv[0] + argv_idx, 0); argv_idx = (int)STRLEN(argv[0]); } break; @@ -1185,7 +1163,7 @@ static void command_line_scan(mparm_T *parmp) // "-w {scriptout}" write to script if (ascii_isdigit(((char_u *)argv[0])[argv_idx])) { n = get_number_arg(argv[0], &argv_idx, 10); - set_option_value("window", n, NULL, 0); + set_option_value_give_err("window", n, NULL, 0); break; } want_argument = true; @@ -1242,8 +1220,8 @@ static void command_line_scan(mparm_T *parmp) } else if (argv[0][0] == '-') { // "-S" followed by another option: use default session file. a = SESSION_FILE; - ++argc; - --argv; + argc++; + argv--; } else { a = argv[0]; } @@ -1280,7 +1258,7 @@ static void command_line_scan(mparm_T *parmp) break; case 'i': // "-i {shada}" use for shada - set_option_value("shadafile", 0L, argv[0], 0); + set_option_value_give_err("shadafile", 0L, argv[0], 0); break; case 's': { // "-s {scriptin}" read from script file @@ -1330,7 +1308,7 @@ scripterror: if (ascii_isdigit(*((char_u *)argv[0]))) { argv_idx = 0; n = get_number_arg(argv[0], &argv_idx, 10); - set_option_value("window", n, NULL, 0); + set_option_value_give_err("window", n, NULL, 0); argv_idx = -1; break; } @@ -1364,8 +1342,8 @@ scripterror: ga_grow(&global_alist.al_ga, 1); char *p = xstrdup(argv[0]); - if (parmp->diff_mode && os_isdir((char_u *)p) && GARGCOUNT > 0 - && !os_isdir((char_u *)alist_name(&GARGLIST[0]))) { + if (parmp->diff_mode && os_isdir(p) && GARGCOUNT > 0 + && !os_isdir(alist_name(&GARGLIST[0]))) { char *r = concat_fnames(p, path_tail(alist_name(&GARGLIST[0])), true); xfree(p); p = r; @@ -1418,7 +1396,7 @@ scripterror: * copied, so that they can be changed. */ static void init_params(mparm_T *paramp, int argc, char **argv) { - memset(paramp, 0, sizeof(*paramp)); + CLEAR_POINTER(paramp); paramp->argc = argc; paramp->argv = argv; paramp->use_debug_break_level = -1; @@ -1512,7 +1490,7 @@ static void handle_quickfix(mparm_T *paramp) 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, (char *)p_menc) < 0) { + if (qf_init(NULL, (char *)p_ef, p_efm, true, (char *)IObuff, p_menc) < 0) { msg_putchar('\n'); os_exit(3); } @@ -1613,9 +1591,9 @@ static void create_windows(mparm_T *parmp) // Watch out for autocommands that delete a window. // // Don't execute Win/Buf Enter/Leave autocommands here - ++autocmd_no_enter; - ++autocmd_no_leave; - dorewind = TRUE; + autocmd_no_enter++; + autocmd_no_leave++; + dorewind = true; while (done++ < 1000) { if (dorewind) { if (parmp->window_layout == WIN_TABS) { @@ -1634,7 +1612,7 @@ static void create_windows(mparm_T *parmp) } curwin = curwin->w_next; } - dorewind = FALSE; + dorewind = false; curbuf = curwin->w_buffer; if (curbuf->b_ml.ml_mfp == NULL) { // Set 'foldlevel' to 'foldlevelstart' if it's not negative.. @@ -1643,15 +1621,15 @@ static void create_windows(mparm_T *parmp) } // When getting the ATTENTION prompt here, use a dialog. swap_exists_action = SEA_DIALOG; - set_buflisted(TRUE); + set_buflisted(true); // create memfile, read file - (void)open_buffer(FALSE, NULL, 0); + (void)open_buffer(false, NULL, 0); if (swap_exists_action == SEA_QUIT) { if (got_int || only_one_window()) { // abort selected or quit and only one window - did_emsg = FALSE; // avoid hit-enter prompt + did_emsg = false; // avoid hit-enter prompt getout(1); } // We can't close the window, it would disturb what @@ -1663,7 +1641,7 @@ static void create_windows(mparm_T *parmp) } else { handle_swap_exists(NULL); } - dorewind = TRUE; // start again + dorewind = true; // start again } os_breakcheck(); if (got_int) { @@ -1677,8 +1655,8 @@ static void create_windows(mparm_T *parmp) curwin = firstwin; } curbuf = curwin->w_buffer; - --autocmd_no_enter; - --autocmd_no_leave; + autocmd_no_enter--; + autocmd_no_leave--; } } @@ -1695,8 +1673,8 @@ static void edit_buffers(mparm_T *parmp, char_u *cwd) /* * Don't execute Win/Buf Enter/Leave autocommands here */ - ++autocmd_no_enter; - ++autocmd_no_leave; + autocmd_no_enter++; + autocmd_no_leave++; // When w_arg_idx is -1 remove the window (see create_windows()). if (curwin->w_arg_idx == -1) { @@ -1705,7 +1683,7 @@ static void edit_buffers(mparm_T *parmp, char_u *cwd) } arg_idx = 1; - for (i = 1; i < parmp->window_count; ++i) { + for (i = 1; i < parmp->window_count; i++) { if (cwd != NULL) { os_chdir((char *)cwd); } @@ -1729,9 +1707,9 @@ static void edit_buffers(mparm_T *parmp, char_u *cwd) if (i == 1) { char buf[100]; - p_shm_save = xstrdup((char *)p_shm); + p_shm_save = xstrdup(p_shm); snprintf(buf, sizeof(buf), "F%s", p_shm); - set_option_value("shm", 0L, buf, 0); + set_option_value_give_err("shm", 0L, buf, 0); } } else { if (curwin->w_next == NULL) { // just checking @@ -1756,7 +1734,7 @@ static void edit_buffers(mparm_T *parmp, char_u *cwd) // abort or quit selected if (got_int || only_one_window()) { // abort selected and only one window - did_emsg = FALSE; // avoid hit-enter prompt + did_emsg = false; // avoid hit-enter prompt getout(1); } win_close(curwin, true, false); @@ -1775,14 +1753,14 @@ static void edit_buffers(mparm_T *parmp, char_u *cwd) } if (p_shm_save != NULL) { - set_option_value("shm", 0L, p_shm_save, 0); + set_option_value_give_err("shm", 0L, p_shm_save, 0); xfree(p_shm_save); } if (parmp->window_layout == WIN_TABS) { goto_tabpage(1); } - --autocmd_no_enter; + autocmd_no_enter--; // make the first window the current window win = firstwin; @@ -1796,7 +1774,7 @@ static void edit_buffers(mparm_T *parmp, char_u *cwd) } win_enter(win, false); - --autocmd_no_leave; + autocmd_no_leave--; TIME_MSG("editing files in windows"); if (parmp->window_count > 1 && parmp->window_layout != WIN_TABS) { win_equal(curwin, false, 'b'); // adjust heights @@ -1814,12 +1792,12 @@ static void exe_pre_commands(mparm_T *parmp) if (cnt > 0) { curwin->w_cursor.lnum = 0; // just in case.. - sourcing_name = _("pre-vimrc command line"); + 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]); } - sourcing_name = NULL; + estack_pop(); current_sctx.sc_sid = 0; TIME_MSG("--cmd commands"); } @@ -1837,11 +1815,11 @@ static void exe_commands(mparm_T *parmp) * pattern on line 1. But don't move the cursor when an autocommand * with g`" was used. */ - msg_scroll = TRUE; + msg_scroll = true; if (parmp->tagname == NULL && curwin->w_cursor.lnum <= 1) { curwin->w_cursor.lnum = 0; } - sourcing_name = "command line"; + estack_push(ETYPE_ARGS, "command line", 0); current_sctx.sc_sid = SID_CARG; current_sctx.sc_seq = 0; for (i = 0; i < parmp->n_commands; i++) { @@ -1850,19 +1828,19 @@ static void exe_commands(mparm_T *parmp) xfree(parmp->commands[i]); } } - sourcing_name = NULL; + estack_pop(); current_sctx.sc_sid = 0; if (curwin->w_cursor.lnum == 0) { curwin->w_cursor.lnum = 1; } if (!exmode_active) { - msg_scroll = FALSE; + msg_scroll = false; } // When started with "-q errorfile" jump to first error again. if (parmp->edit_type == EDIT_QF) { - qf_jump(NULL, 0, 0, FALSE); + qf_jump(NULL, 0, 0, false); } TIME_MSG("executing command arguments"); } @@ -2059,17 +2037,14 @@ static int execute_env(char *env) { const char *initstr = os_getenv(env); if (initstr != NULL) { - char_u *save_sourcing_name = (char_u *)sourcing_name; - linenr_T save_sourcing_lnum = sourcing_lnum; - sourcing_name = env; - sourcing_lnum = 0; + 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); - sourcing_name = (char *)save_sourcing_name; - sourcing_lnum = save_sourcing_lnum; + + estack_pop(); current_sctx = save_current_sctx; return OK; } diff --git a/src/nvim/main.h b/src/nvim/main.h index d5384ecc95..0c497a7c0e 100644 --- a/src/nvim/main.h +++ b/src/nvim/main.h @@ -2,7 +2,6 @@ #define NVIM_MAIN_H #include "nvim/event/loop.h" -#include "nvim/normal.h" // Maximum number of commands from + or -c arguments. #define MAX_ARG_CMDS 10 diff --git a/src/nvim/mapping.c b/src/nvim/mapping.c index 342b1b0d47..333fb847f6 100644 --- a/src/nvim/mapping.c +++ b/src/nvim/mapping.c @@ -29,6 +29,7 @@ #include "nvim/message.h" #include "nvim/option.h" #include "nvim/regexp.h" +#include "nvim/runtime.h" #include "nvim/ui.h" #include "nvim/vim.h" @@ -149,8 +150,8 @@ static void showmap(mapblock_T *mp, bool local) { size_t len = 1; - if (message_filtered(mp->m_keys) && message_filtered(mp->m_str) - && (mp->m_desc == NULL || message_filtered((char_u *)mp->m_desc))) { + if (message_filtered((char *)mp->m_keys) && message_filtered(mp->m_str) + && (mp->m_desc == NULL || message_filtered(mp->m_desc))) { return; } @@ -202,7 +203,7 @@ static void showmap(mapblock_T *mp, bool local) } else if (mp->m_str[0] == NUL) { msg_puts_attr("<Nop>", HL_ATTR(HLF_8)); } else { - msg_outtrans_special(mp->m_str, false, 0); + msg_outtrans_special((char_u *)mp->m_str, false, 0); } if (mp->m_desc != NULL) { @@ -292,7 +293,7 @@ static bool set_maparg_lhs_rhs(const char *const orig_lhs, const size_t orig_lhs replaced = replace_termcodes(orig_rhs, orig_rhs_len, &rhs_buf, REPTERM_DO_LT, NULL, cpo_flags); mapargs->rhs_len = STRLEN(replaced); - // XXX: replace_termcodes may produce an empty string even if orig_rhs is non-empty + // NB: replace_termcodes may produce an empty string even if orig_rhs is non-empty // (e.g. a single ^V, see :h map-empty-rhs) mapargs->rhs_is_noop = orig_rhs_len != 0 && mapargs->rhs_len == 0; mapargs->rhs = (char_u *)replaced; @@ -333,7 +334,7 @@ static int str_to_mapargs(const char_u *strargs, bool is_unmap, MapArguments *ma { const char_u *to_parse = strargs; to_parse = (char_u *)skipwhite((char *)to_parse); - memset(mapargs, 0, sizeof(*mapargs)); + CLEAR_POINTER(mapargs); // Accept <buffer>, <nowait>, <silent>, <expr>, <script>, and <unique> in // any order. @@ -448,8 +449,8 @@ static void map_add(buf_T *buf, mapblock_T **map_table, mapblock_T **abbr_table, } mp->m_keys = vim_strsave(keys); - mp->m_str = args->rhs; - mp->m_orig_str = args->orig_rhs; + mp->m_str = (char *)args->rhs; + mp->m_orig_str = (char *)args->orig_rhs; mp->m_luaref = args->rhs_lua; if (!simplified) { args->rhs = NULL; @@ -469,7 +470,7 @@ static void map_add(buf_T *buf, mapblock_T **map_table, mapblock_T **abbr_table, mp->m_script_ctx.sc_lnum = lnum; } else { mp->m_script_ctx = current_sctx; - mp->m_script_ctx.sc_lnum += sourcing_lnum; + mp->m_script_ctx.sc_lnum += SOURCING_LNUM; nlua_set_sctx(&mp->m_script_ctx); } mp->m_desc = NULL; @@ -703,7 +704,7 @@ 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 = mp->m_str; + p = (char_u *)mp->m_str; } else { n = mp->m_keylen; p = mp->m_keys; @@ -760,8 +761,8 @@ static int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev, XFREE_CLEAR(mp->m_str); XFREE_CLEAR(mp->m_orig_str); } - mp->m_str = args->rhs; - mp->m_orig_str = args->orig_rhs; + mp->m_str = (char *)args->rhs; + mp->m_orig_str = (char *)args->orig_rhs; mp->m_luaref = args->rhs_lua; if (!keyround1_simplified) { args->rhs = NULL; @@ -776,7 +777,7 @@ static int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev, mp->m_expr = args->expr; mp->m_replace_keycodes = args->replace_keycodes; mp->m_script_ctx = current_sctx; - mp->m_script_ctx.sc_lnum += sourcing_lnum; + mp->m_script_ctx.sc_lnum += SOURCING_LNUM; nlua_set_sctx(&mp->m_script_ctx); if (args->desc != NULL) { mp->m_desc = xstrdup(args->desc); @@ -964,7 +965,7 @@ static int get_map_mode(char **cmdp, bool forceit) /// Clear all mappings (":mapclear") or abbreviations (":abclear"). /// "abbr" should be false for mappings, true for abbreviations. /// This function used to be called map_clear(). -static void do_mapclear(char_u *cmdp, char_u *arg, int forceit, int abbr) +static void do_mapclear(char *cmdp, char_u *arg, int forceit, int abbr) { int mode; int local; @@ -975,7 +976,7 @@ static void do_mapclear(char_u *cmdp, char_u *arg, int forceit, int abbr) return; } - mode = get_map_mode((char **)&cmdp, forceit); + mode = get_map_mode(&cmdp, forceit); map_clear_mode(curbuf, mode, local, abbr); } @@ -1051,9 +1052,9 @@ bool map_to_exists(const char *const str, const char *const modechars, const boo int mode = 0; int retval; - char_u *buf = NULL; + char *buf = NULL; const char_u *const rhs = (char_u *)replace_termcodes(str, strlen(str), - (char **)&buf, REPTERM_DO_LT, + &buf, REPTERM_DO_LT, NULL, CPO_TO_CPO_FLAGS); #define MAPMODE(mode, modechars, chr, modeflags) \ @@ -1112,7 +1113,7 @@ int map_to_exists_mode(const char *const rhs, const int mode, const bool abbr) mp = maphash[hash]; } for (; mp; mp = mp->m_next) { - if ((mp->m_mode & mode) && strstr((char *)mp->m_str, rhs) != NULL) { + if ((mp->m_mode & mode) && strstr(mp->m_str, rhs) != NULL) { return true; } } @@ -1194,14 +1195,14 @@ 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_u *cmd, char_u *arg, bool forceit, bool isabbrev, +char_u *set_context_in_map_cmd(expand_T *xp, char *cmd, char_u *arg, bool forceit, bool isabbrev, bool isunmap, cmdidx_T cmdidx) { if (forceit && cmdidx != CMD_map && cmdidx != CMD_unmap) { xp->xp_context = EXPAND_NOTHING; } else { if (isunmap) { - expand_mapmodes = get_map_mode((char **)&cmd, forceit || isabbrev); + expand_mapmodes = get_map_mode(&cmd, forceit || isabbrev); } else { expand_mapmodes = MODE_INSERT | MODE_CMDLINE; if (!isabbrev) { @@ -1505,7 +1506,7 @@ bool check_abbr(int c, char_u *ptr, int col, int mincol) if (mp->m_expr) { s = eval_map_expr(mp, c); } else { - s = mp->m_str; + s = (char_u *)mp->m_str; } if (s != NULL) { // insert the to string @@ -1541,7 +1542,7 @@ char_u *eval_map_expr(mapblock_T *mp, int c) // Remove escaping of K_SPECIAL, because "str" is in a format to be used as // typeahead. if (mp->m_luaref == LUA_NOREF) { - expr = vim_strsave(mp->m_str); + expr = vim_strsave((char_u *)mp->m_str); vim_unescape_ks(expr); } @@ -1638,7 +1639,7 @@ int makemap(FILE *fd, buf_T *buf) if (mp->m_luaref != LUA_NOREF) { continue; } - for (p = mp->m_str; *p != NUL; p++) { + for (p = (char_u *)mp->m_str; *p != NUL; p++) { if (p[0] == K_SPECIAL && p[1] == KS_EXTRA && p[2] == KE_SNR) { break; @@ -1784,7 +1785,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, mp->m_str, 1) == FAIL + || put_escstr(fd, (char_u *)mp->m_str, 1) == FAIL || put_eol(fd) < 0) { return FAIL; } @@ -1955,7 +1956,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 ? mp->m_str : NULL; + return mp->m_luaref == LUA_NOREF ? (char_u *)mp->m_str : NULL; } } } @@ -1966,7 +1967,7 @@ char_u *check_map(char_u *keys, int mode, int exact, int ign_mod, int abbr, mapb } /// "hasmapto()" function -void f_hasmapto(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_hasmapto(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const char *mode; const char *const name = tv_get_string(&argvars[0]); @@ -2079,7 +2080,7 @@ static void get_maparg(typval_T *argvars, typval_T *rettv, int exact) } char *keys_buf = NULL; - char_u *alt_keys_buf = NULL; + char *alt_keys_buf = NULL; bool did_simplify = false; const int flags = REPTERM_FROM_PART | REPTERM_DO_LT; const int mode = get_map_mode((char **)&which, 0); @@ -2096,9 +2097,9 @@ static void get_maparg(typval_T *argvars, typval_T *rettv, int exact) // preferred for printing, like in do_map(). (void)replace_termcodes(keys, STRLEN(keys), - (char **)&alt_keys_buf, flags | REPTERM_NO_SIMPLIFY, NULL, + &alt_keys_buf, flags | REPTERM_NO_SIMPLIFY, NULL, CPO_TO_CPO_FLAGS); - rhs = check_map(alt_keys_buf, mode, exact, false, abbr, &mp, &buffer_local, &rhs_lua); + rhs = check_map((char_u *)alt_keys_buf, mode, exact, false, abbr, &mp, &buffer_local, &rhs_lua); } if (!get_dict) { @@ -2126,7 +2127,7 @@ static void get_maparg(typval_T *argvars, typval_T *rettv, int exact) } /// "mapset()" function -void f_mapset(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_mapset(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { char buf[NUMBUFLEN]; const char *which = tv_get_string_buf_chk(&argvars[0], buf); @@ -2194,13 +2195,13 @@ void f_mapset(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "maparg()" function -void f_maparg(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_maparg(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { get_maparg(argvars, rettv, true); } /// "mapcheck()" function -void f_mapcheck(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_mapcheck(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { get_maparg(argvars, rettv, false); } @@ -2321,7 +2322,7 @@ void langmap_set(void) ga_clear(&langmap_mapga); // clear the previous map first langmap_init(); // back to one-to-one map - for (p = p_langmap; p[0] != NUL;) { + for (p = (char_u *)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) { @@ -2439,13 +2440,13 @@ void ex_unmap(exarg_T *eap) /// ":mapclear" and friends. void ex_mapclear(exarg_T *eap) { - do_mapclear((char_u *)eap->cmd, (char_u *)eap->arg, eap->forceit, false); + do_mapclear(eap->cmd, (char_u *)eap->arg, eap->forceit, false); } /// ":abclear" and friends. void ex_abclear(exarg_T *eap) { - do_mapclear((char_u *)eap->cmd, (char_u *)eap->arg, true, true); + do_mapclear(eap->cmd, (char_u *)eap->arg, true, true); } /// Set, tweak, or remove a mapping in a mode. Acts as the implementation for diff --git a/src/nvim/mark.c b/src/nvim/mark.c index 344d55a473..b430c167ce 100644 --- a/src/nvim/mark.c +++ b/src/nvim/mark.c @@ -20,7 +20,6 @@ #include "nvim/eval/typval.h" #include "nvim/ex_cmds.h" #include "nvim/extmark.h" -#include "nvim/fileio.h" #include "nvim/fold.h" #include "nvim/mark.h" #include "nvim/mark_defs.h" @@ -36,9 +35,9 @@ #include "nvim/os/time.h" #include "nvim/path.h" #include "nvim/quickfix.h" -#include "nvim/search.h" #include "nvim/sign.h" #include "nvim/strings.h" +#include "nvim/textobject.h" #include "nvim/ui.h" #include "nvim/vim.h" #include "nvim/map.h" @@ -116,7 +115,7 @@ void clear_fmark(fmark_T *fm) FUNC_ATTR_NONNULL_ALL { free_fmark(*fm); - memset(fm, 0, sizeof(*fm)); + CLEAR_POINTER(fm); } /* @@ -832,7 +831,7 @@ static void fname2fnum(xfmark_T *fm) )) { int len; - expand_env((char_u *)"~/", NameBuff, MAXPATHL); + expand_env("~/", NameBuff, MAXPATHL); len = (int)STRLEN(NameBuff); STRLCPY(NameBuff + len, fm->fname + 2, MAXPATHL - len); } else { @@ -841,7 +840,7 @@ static void fname2fnum(xfmark_T *fm) // Try to shorten the file name. os_dirname(IObuff, IOSIZE); - p = path_shorten_fname(NameBuff, IObuff); + p = path_shorten_fname((char_u *)NameBuff, IObuff); // buflist_new() will call fmarks_check_names() (void)buflist_new((char *)NameBuff, (char *)p, (linenr_T)1, 0); @@ -862,12 +861,12 @@ void fmarks_check_names(buf_T *buf) return; } - for (i = 0; i < NGLOBALMARKS; ++i) { + for (i = 0; i < NGLOBALMARKS; i++) { fmarks_check_one(&namedfm[i], name, buf); } FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - for (i = 0; i < wp->w_jumplistlen; ++i) { + for (i = 0; i < wp->w_jumplistlen; i++) { fmarks_check_one(&wp->w_jumplist[i], name, buf); } } @@ -1006,10 +1005,10 @@ void ex_marks(exarg_T *eap) } show_one_mark('\'', arg, &curwin->w_pcmark, NULL, true); - for (i = 0; i < NMARKS; ++i) { + for (i = 0; i < NMARKS; i++) { show_one_mark(i + 'a', arg, &curbuf->b_namedm[i].mark, NULL, true); } - for (i = 0; i < NGLOBALMARKS; ++i) { + for (i = 0; i < NGLOBALMARKS; i++) { if (namedfm[i].fmark.fnum != 0) { name = fm_getname(&namedfm[i].fmark, 15); } else { @@ -1069,7 +1068,7 @@ static void show_one_mark(int c, char_u *arg, pos_T *p, char_u *name_arg, int cu name = mark_line(p, 15); mustfree = true; } - if (!message_filtered(name)) { + if (!message_filtered((char *)name)) { if (!did_title) { // Highlight title msg_puts_title(_("\nmark line col file/text")); @@ -1080,7 +1079,7 @@ static void show_one_mark(int c, char_u *arg, pos_T *p, char_u *name_arg, int cu snprintf((char *)IObuff, IOSIZE, " %c %6" PRIdLINENR " %4d ", c, p->lnum, p->col); msg_outtrans((char *)IObuff); if (name != NULL) { - msg_outtrans_attr(name, current ? HL_ATTR(HLF_D) : 0); + msg_outtrans_attr((char *)name, current ? HL_ATTR(HLF_D) : 0); } } ui_flush(); // show one line at a time @@ -1133,7 +1132,7 @@ void ex_delmarks(exarg_T *eap) from = to = *p; } - for (i = from; i <= to; ++i) { + for (i = from; i <= to; i++) { if (lower) { curbuf->b_namedm[i - 'a'].mark.lnum = 0; } else { @@ -1185,7 +1184,7 @@ void ex_jumps(exarg_T *eap) cleanup_jumplist(curwin, true); // Highlight title msg_puts_title(_("\n jump line col file/text")); - for (i = 0; i < curwin->w_jumplistlen && !got_int; ++i) { + for (i = 0; i < curwin->w_jumplistlen && !got_int; i++) { if (curwin->w_jumplist[i].fmark.mark.lnum != 0) { name = fm_getname(&curwin->w_jumplist[i].fmark, 16); @@ -1195,7 +1194,7 @@ void ex_jumps(exarg_T *eap) name = vim_strsave((char_u *)"-invalid-"); } // apply :filter /pat/ or file name not available - if (name == NULL || message_filtered(name)) { + if (name == NULL || message_filtered((char *)name)) { xfree(name); continue; } @@ -1210,7 +1209,7 @@ void ex_jumps(exarg_T *eap) 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_attr(name, + msg_outtrans_attr((char *)name, curwin->w_jumplist[i].fmark.fnum == curbuf->b_fnum ? HL_ATTR(HLF_D) : 0); xfree(name); @@ -1241,7 +1240,7 @@ void ex_changes(exarg_T *eap) // Highlight title msg_puts_title(_("\nchange line col text")); - for (i = 0; i < curbuf->b_changelistlen && !got_int; ++i) { + for (i = 0; i < curbuf->b_changelistlen && !got_int; i++) { if (curbuf->b_changelist[i].mark.lnum != 0) { msg_putchar('\n'); if (got_int) { @@ -1255,7 +1254,7 @@ void ex_changes(exarg_T *eap) curbuf->b_changelist[i].mark.col); msg_outtrans((char *)IObuff); name = mark_line(&curbuf->b_changelist[i].mark, 17); - msg_outtrans_attr(name, HL_ATTR(HLF_D)); + msg_outtrans_attr((char *)name, HL_ATTR(HLF_D)); xfree(name); os_breakcheck(); } @@ -1549,7 +1548,7 @@ void mark_col_adjust(linenr_T lnum, colnr_T mincol, linenr_T lnum_amount, long c */ FOR_ALL_WINDOWS_IN_TAB(win, curtab) { // marks in the jumplist - for (i = 0; i < win->w_jumplistlen; ++i) { + for (i = 0; i < win->w_jumplistlen; i++) { if (win->w_jumplist[i].fmark.fnum == fnum) { COL_ADJUST(&(win->w_jumplist[i].fmark.mark)); } @@ -1651,7 +1650,7 @@ void copy_jumplist(win_T *from, win_T *to) { int i; - for (i = 0; i < from->w_jumplistlen; ++i) { + for (i = 0; i < from->w_jumplistlen; i++) { to->w_jumplist[i] = from->w_jumplist[i]; if (from->w_jumplist[i].fname != NULL) { to->w_jumplist[i].fname = xstrdup(from->w_jumplist[i].fname); @@ -1875,7 +1874,7 @@ void free_jumplist(win_T *wp) { int i; - for (i = 0; i < wp->w_jumplistlen; ++i) { + for (i = 0; i < wp->w_jumplistlen; i++) { free_xfmark(wp->w_jumplist[i]); } wp->w_jumplistlen = 0; @@ -1898,7 +1897,7 @@ void free_all_marks(void) free_xfmark(namedfm[i]); } } - memset(&namedfm[0], 0, sizeof(namedfm)); + CLEAR_FIELD(namedfm); } #endif diff --git a/src/nvim/match.c b/src/nvim/match.c index ba587c4141..48f0d0fc05 100644 --- a/src/nvim/match.c +++ b/src/nvim/match.c @@ -7,14 +7,19 @@ #include "nvim/buffer_defs.h" #include "nvim/charset.h" +#include "nvim/drawscreen.h" +#include "nvim/eval.h" #include "nvim/eval/funcs.h" +#include "nvim/ex_docmd.h" #include "nvim/fold.h" +#include "nvim/highlight.h" #include "nvim/highlight_group.h" #include "nvim/match.h" #include "nvim/memline.h" +#include "nvim/profile.h" #include "nvim/regexp.h" #include "nvim/runtime.h" -#include "nvim/screen.h" +#include "nvim/vim.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "match.c.generated.h" @@ -42,7 +47,7 @@ static int match_add(win_T *wp, const char *const grp, const char *const pat, in matchitem_T *m; int hlg_id; regprog_T *regprog = NULL; - int rtype = SOME_VALID; + int rtype = UPD_SOME_VALID; if (*grp == NUL || (pat != NULL && *pat == NUL)) { return -1; @@ -188,7 +193,7 @@ static int match_add(win_T *wp, const char *const grp, const char *const pat, in } m->pos.toplnum = toplnum; m->pos.botlnum = botlnum; - rtype = VALID; + rtype = UPD_VALID; } } @@ -222,7 +227,7 @@ static int match_delete(win_T *wp, int id, bool perr) { matchitem_T *cur = wp->w_match_head; matchitem_T *prev = cur; - int rtype = SOME_VALID; + int rtype = UPD_SOME_VALID; if (id < 1) { if (perr) { @@ -263,7 +268,7 @@ static int match_delete(win_T *wp, int id, bool perr) wp->w_buffer->b_mod_bot = cur->pos.botlnum; wp->w_buffer->b_mod_xlines = 0; } - rtype = VALID; + rtype = UPD_VALID; } xfree(cur); redraw_later(wp, rtype); @@ -282,7 +287,7 @@ void clear_matches(win_T *wp) xfree(wp->w_match_head); wp->w_match_head = m; } - redraw_later(wp, SOME_VALID); + redraw_later(wp, UPD_SOME_VALID); } /// Get match from ID 'id' in window 'wp'. @@ -692,7 +697,7 @@ int update_search_hl(win_T *wp, linenr_T lnum, colnr_T col, char_u **line, match } // Highlight the match were the cursor is using the CurSearch // group. - if (shl == search_hl && shl->has_cursor && (HL_ATTR(HLF_LC) || wp->w_hl_ids[HLF_LC])) { + if (shl == search_hl && shl->has_cursor && (HL_ATTR(HLF_LC) || win_hl_attr(wp, HLF_LC))) { shl->attr_cur = win_hl_attr(wp, HLF_LC) ? win_hl_attr(wp, HLF_LC) : HL_ATTR(HLF_LC); } else { shl->attr_cur = shl->attr; @@ -854,7 +859,7 @@ static int matchadd_dict_arg(typval_T *tv, const char **conceal_char, win_T **wi } /// "clearmatches()" function -void f_clearmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_clearmatches(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { win_T *win = get_optional_window(argvars, 0); @@ -864,7 +869,7 @@ void f_clearmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "getmatches()" function -void f_getmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_getmatches(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { matchitem_T *cur; int i; @@ -919,7 +924,7 @@ void f_getmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "setmatches()" function -void f_setmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_setmatches(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { dict_T *d; list_T *s = NULL; @@ -1022,7 +1027,7 @@ void f_setmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "matchadd()" function -void f_matchadd(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_matchadd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { char grpbuf[NUMBUFLEN]; char patbuf[NUMBUFLEN]; @@ -1064,7 +1069,7 @@ void f_matchadd(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "matchaddpo()" function -void f_matchaddpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_matchaddpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = -1; @@ -1115,7 +1120,7 @@ void f_matchaddpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "matcharg()" function -void f_matcharg(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_matcharg(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const int id = (int)tv_get_number(&argvars[0]); @@ -1138,7 +1143,7 @@ void f_matcharg(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "matchdelete()" function -void f_matchdelete(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_matchdelete(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { win_T *win = get_optional_window(argvars, 1); if (win == NULL) { @@ -1178,7 +1183,7 @@ void ex_match(exarg_T *eap) && (ascii_iswhite(eap->arg[4]) || ends_excmd(eap->arg[4])))) { end = (char_u *)eap->arg + 4; } else { - p = skiptowhite((char_u *)eap->arg); + p = (char_u *)skiptowhite(eap->arg); if (!eap->skip) { g = vim_strnsave((char_u *)eap->arg, (size_t)(p - (char_u *)eap->arg)); } @@ -1189,7 +1194,7 @@ void ex_match(exarg_T *eap) semsg(_(e_invarg2), eap->arg); return; } - end = skip_regexp(p + 1, *p, true, NULL); + end = (char_u *)skip_regexp((char *)p + 1, *p, true, NULL); if (!eap->skip) { if (*end != NUL && !ends_excmd(*skipwhite((char *)end + 1))) { xfree(g); @@ -1210,5 +1215,5 @@ void ex_match(exarg_T *eap) *end = (char_u)c; } } - eap->nextcmd = (char *)find_nextcmd(end); + eap->nextcmd = find_nextcmd((char *)end); } diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c index 223b4d6845..b874f0dc94 100644 --- a/src/nvim/mbyte.c +++ b/src/nvim/mbyte.c @@ -39,6 +39,7 @@ #include "nvim/arabic.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" @@ -49,7 +50,6 @@ #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/option.h" #include "nvim/os/os.h" #include "nvim/path.h" #include "nvim/screen.h" @@ -74,6 +74,19 @@ struct interval { # include "unicode_tables.generated.h" #endif +static char e_list_item_nr_is_not_list[] + = N_("E1109: List item %d is not a List"); +static char e_list_item_nr_does_not_contain_3_numbers[] + = N_("E1110: List item %d does not contain 3 numbers"); +static char e_list_item_nr_range_invalid[] + = N_("E1111: List item %d range invalid"); +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"); + // 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 // which are illegal when used as the first byte have a 1. The NUL byte has @@ -472,13 +485,18 @@ static bool intable(const struct interval *table, size_t n_items, int c) int utf_char2cells(int c) { if (c >= 0x100) { + int n = cw_value(c); + if (n != 0) { + return n; + } + if (!utf_printable(c)) { return 6; // unprintable, displays <xxxx> } if (intable(doublewidth, ARRAY_SIZE(doublewidth), c)) { return 2; } - if (p_emoji && intable(emoji_width, ARRAY_SIZE(emoji_width), c)) { + if (p_emoji && intable(emoji_wide, ARRAY_SIZE(emoji_wide), c)) { return 2; } } else if (c >= 0x80 && !vim_isprintc(c)) { @@ -736,21 +754,19 @@ bool utf_composinglike(const char_u *p1, const char_u *p2) /// space at least for #MAX_MCO + 1 elements. /// /// @return leading character. -int utfc_ptr2char(const char_u *p, int *pcc) +int utfc_ptr2char(const char *p_in, int *pcc) { - int len; - int c; - int cc; + uint8_t *p = (uint8_t *)p_in; int i = 0; - c = utf_ptr2char((char *)p); - len = utf_ptr2len((char *)p); + int c = utf_ptr2char((char *)p); + int len = utf_ptr2len((char *)p); // Only accept a composing char when the first char isn't illegal. if ((len > 1 || *p < 0x80) && p[len] >= 0x80 && utf_composinglike(p, p + len)) { - cc = utf_ptr2char((char *)p + len); + int cc = utf_ptr2char((char *)p + len); for (;;) { pcc[i++] = cc; if (i == MAX_MCO) { @@ -864,7 +880,7 @@ int utf_ptr2len_len(const char_u *p, int size) } else { m = len; } - for (i = 1; i < m; ++i) { + for (i = 1; i < m; i++) { if ((p[i] & 0xc0) != 0x80) { return 1; } @@ -872,9 +888,9 @@ int utf_ptr2len_len(const char_u *p, int size) return len; } -/// Return the number of bytes occupied by a UTF-8 character in a string -/// +/// 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) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { @@ -988,8 +1004,9 @@ int utf_char2len(const int c) /// Convert Unicode character to UTF-8 string /// -/// @param c character to convert to \p buf -/// @param[out] buf UTF-8 string generated from \p c, does not add \0 +/// @param c character to convert to UTF-8 string in \p buf +/// @param[out] buf UTF-8 string generated from \p c, does not add \0 +/// must have room for at least 6 bytes /// @return Number of bytes (1-6). int utf_char2bytes(const int c, char *const buf) { @@ -1164,6 +1181,11 @@ int utf_class_tab(const int c, const uint64_t *const chartab) return 1; // punctuation } + // emoji + if (intable(emoji_all, ARRAY_SIZE(emoji_all), c)) { + return 3; + } + // binary search in table while (top >= bot) { mid = (bot + top) / 2; @@ -1176,11 +1198,6 @@ int utf_class_tab(const int c, const uint64_t *const chartab) } } - // emoji - if (intable(emoji_all, ARRAY_SIZE(emoji_all), c)) { - return 3; - } - // most other characters are "word" characters return 2; } @@ -1576,7 +1593,7 @@ void show_utf8(void) } clen = 0; - for (i = 0; i < len; ++i) { + for (i = 0; i < len; i++) { if (clen == 0) { // start of (composing) character, get its length if (i > 0) { @@ -1587,7 +1604,7 @@ void show_utf8(void) } sprintf((char *)IObuff + rlen, "%02x ", (line[i] == NL) ? NUL : line[i]); // NUL is stored as NL - --clen; + clen--; rlen += (int)STRLEN(IObuff + rlen); if (rlen > IOSIZE - 20) { break; @@ -1613,14 +1630,14 @@ int utf_head_off(const char_u *base, const char_u *p) // Skip backwards over trailing bytes: 10xx.xxxx // Skip backwards again if on a composing char. const char_u *q; - for (q = p;; --q) { + for (q = p;; q--) { // Move s to the last byte of this char. const char_u *s; - for (s = q; (s[1] & 0xc0) == 0x80; ++s) {} + for (s = q; (s[1] & 0xc0) == 0x80; s++) {} // Move q to the first byte of this char. while (q > base && (*q & 0xc0) == 0x80) { - --q; + q--; } // Check for illegal sequence. Do allow an illegal byte after where we // started. @@ -1641,10 +1658,10 @@ int utf_head_off(const char_u *base, const char_u *p) if (arabic_maycombine(c)) { // Advance to get a sneak-peak at the next char const char_u *j = q; - --j; + j--; // Move j to the first byte of this char. while (j > base && (*j & 0xc0) == 0x80) { - --j; + j--; } if (arabic_combine(utf_ptr2char((char *)j), c)) { continue; @@ -1800,9 +1817,9 @@ bool utf_allow_break(int cc, int ncc) /// /// @param[in,out] fp Source of the character to copy. /// @param[in,out] tp Destination to copy to. -void mb_copy_char(const char_u **const fp, char_u **const tp) +void mb_copy_char(const char **const fp, char **const tp) { - const size_t l = (size_t)utfc_ptr2len((char *)(*fp)); + const size_t l = (size_t)utfc_ptr2len(*fp); memmove(*tp, *fp, l); *tp += l; @@ -1913,7 +1930,7 @@ void utf_find_illegal(void) char_u *tofree = NULL; vimconv.vc_type = CONV_NONE; - if (enc_canon_props(curbuf->b_p_fenc) & ENC_8BIT) { + if (enc_canon_props((char_u *)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'. @@ -1925,7 +1942,7 @@ void utf_find_illegal(void) p = get_cursor_pos_ptr(); if (vimconv.vc_type != CONV_NONE) { xfree(tofree); - tofree = string_convert(&vimconv, p, NULL); + tofree = (char_u *)string_convert(&vimconv, (char *)p, NULL); if (tofree == NULL) { break; } @@ -1956,7 +1973,7 @@ void utf_find_illegal(void) if (curwin->w_cursor.lnum == curbuf->b_ml.ml_line_count) { break; } - ++curwin->w_cursor.lnum; + curwin->w_cursor.lnum++; curwin->w_cursor.col = 0; } @@ -1970,8 +1987,7 @@ theend: } /// @return true if string "s" is a valid utf-8 string. -/// When "end" is NULL stop at the first NUL. -/// When "end" is positive stop there. +/// When "end" is NULL stop at the first NUL. Otherwise stop at "end". bool utf_valid_string(const char_u *s, const char_u *end) { const char_u *p = s; @@ -2128,10 +2144,8 @@ const char *mb_unescape(const char **const pp) return NULL; } -/* - * Skip the Vim specific head of a 'encoding' name. - */ -char_u *enc_skip(char_u *p) +/// Skip the Vim specific head of a 'encoding' name. +char *enc_skip(char *p) { if (STRNCMP(p, "2byte-", 6) == 0) { return p + 6; @@ -2142,27 +2156,25 @@ char_u *enc_skip(char_u *p) return p; } -/* - * Find the canonical name for encoding "enc". - * When the name isn't recognized, returns "enc" itself, but with all lower - * case characters and '_' replaced with '-'. - * Returns an allocated string. - */ -char_u *enc_canonize(char_u *enc) FUNC_ATTR_NONNULL_RET +/// Find the canonical name for encoding "enc". +/// When the name isn't recognized, returns "enc" itself, but with all lower +/// case characters and '_' replaced with '-'. +/// +/// @return an allocated string. +char *enc_canonize(char *enc) + FUNC_ATTR_NONNULL_RET { char_u *p, *s; - int i; - if (STRCMP(enc, "default") == 0) { // Use the default encoding as found by set_init_1(). - return vim_strsave(fenc_default); + return (char *)vim_strsave(fenc_default); } // copy "enc" to allocated memory, with room for two '-' char_u *r = xmalloc(STRLEN(enc) + 3); // Make it all lower case and replace '_' with '-'. p = r; - for (s = enc; *s != NUL; ++s) { + for (s = (char_u *)enc; *s != NUL; s++) { if (*s == '_') { *p++ = '-'; } else { @@ -2172,7 +2184,7 @@ char_u *enc_canonize(char_u *enc) FUNC_ATTR_NONNULL_RET *p = NUL; // Skip "2byte-" and "8bit-". - p = enc_skip(r); + p = (char_u *)enc_skip((char *)r); // Change "microsoft-cp" to "cp". Used in some spell files. if (STRNCMP(p, "microsoft-cp", 12) == 0) { @@ -2196,6 +2208,7 @@ char_u *enc_canonize(char_u *enc) FUNC_ATTR_NONNULL_RET STRMOVE(p + 5, p + 6); } + int i; if (enc_canon_search(p) >= 0) { // canonical name can be used unmodified if (p != r) { @@ -2206,7 +2219,7 @@ char_u *enc_canonize(char_u *enc) FUNC_ATTR_NONNULL_RET xfree(r); r = vim_strsave((char_u *)enc_canon_table[i].name); } - return r; + return (char *)r; } /// Search for an encoding alias of "name". @@ -2215,7 +2228,7 @@ static int enc_alias_search(const char_u *name) { int i; - for (i = 0; enc_alias_table[i].name != NULL; ++i) { + for (i = 0; enc_alias_table[i].name != NULL; i++) { if (STRCMP(name, enc_alias_table[i].name) == 0) { return enc_alias_table[i].canon; } @@ -2291,7 +2304,7 @@ enc_locale_copy_enc: buf[i] = NUL; } - return enc_canonize((char_u *)buf); + return (char_u *)enc_canonize(buf); } #if defined(HAVE_ICONV) @@ -2314,7 +2327,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((char *)enc_skip(to), (char *)enc_skip(from)); + fd = iconv_open(enc_skip((char *)to), enc_skip((char *)from)); if (fd != (iconv_t)-1 && iconv_working == kUnknown) { /* @@ -2425,18 +2438,17 @@ static char_u *iconv_string(const vimconv_T *const vcp, char_u *str, size_t slen #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. - * Note: cannot be used for conversion from/to ucs-2 and ucs-4 (will use utf-8 - * instead). - * Afterwards invoke with "from" and "to" equal to NULL to cleanup. - * Return FAIL when conversion is not supported, OK otherwise. - */ -int convert_setup(vimconv_T *vcp, char_u *from, char_u *to) +/// 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. +/// Note: cannot be used for conversion from/to ucs-2 and ucs-4 (will use utf-8 +/// instead). +/// Afterwards invoke with "from" and "to" equal to NULL to cleanup. +/// +/// @return FAIL when conversion is not supported, OK otherwise. +int convert_setup(vimconv_T *vcp, char *from, char *to) { - return convert_setup_ext(vcp, from, true, to, true); + return convert_setup_ext(vcp, (char_u *)from, true, (char_u *)to, true); } /// As convert_setup(), but only when from_unicode_is_utf8 is true will all @@ -2509,16 +2521,14 @@ int convert_setup_ext(vimconv_T *vcp, char_u *from, bool from_unicode_is_utf8, c return OK; } -/* - * Convert text "ptr[*lenp]" according to "vcp". - * Returns the result in allocated memory and sets "*lenp". - * When "lenp" is NULL, use NUL terminated strings. - * Illegal chars are often changed to "?", unless vcp->vc_fail is set. - * When something goes wrong, NULL is returned and "*lenp" is unchanged. - */ -char_u *string_convert(const vimconv_T *const vcp, char_u *ptr, size_t *lenp) +/// Convert text "ptr[*lenp]" according to "vcp". +/// Returns the result in allocated memory and sets "*lenp". +/// When "lenp" is NULL, use NUL terminated strings. +/// Illegal chars are often changed to "?", unless vcp->vc_fail is set. +/// 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 string_convert_ext(vcp, ptr, lenp, NULL); + return (char *)string_convert_ext(vcp, (char_u *)ptr, lenp, NULL); } /* @@ -2548,7 +2558,7 @@ char_u *string_convert_ext(const vimconv_T *const vcp, char_u *ptr, size_t *lenp case CONV_TO_UTF8: // latin1 to utf-8 conversion retval = xmalloc(len * 2 + 1); d = retval; - for (size_t i = 0; i < len; ++i) { + for (size_t i = 0; i < len; i++) { c = ptr[i]; if (c < 0x80) { *d++ = (char_u)c; @@ -2566,7 +2576,7 @@ char_u *string_convert_ext(const vimconv_T *const vcp, char_u *ptr, size_t *lenp case CONV_9_TO_UTF8: // latin9 to utf-8 conversion retval = xmalloc(len * 3 + 1); d = retval; - for (size_t i = 0; i < len; ++i) { + for (size_t i = 0; i < len; i++) { c = ptr[i]; switch (c) { case 0xa4: @@ -2678,3 +2688,174 @@ char_u *string_convert_ext(const vimconv_T *const vcp, char_u *ptr, size_t *lenp return retval; } + +/// Table set by setcellwidths(). +typedef struct { + long first; + long last; + char width; +} cw_interval_T; + +static cw_interval_T *cw_table = NULL; +static size_t cw_table_size = 0; + +/// Return the value of the cellwidth table for the character `c`. +/// +/// @param c The source character. +/// @return 1 or 2 when `c` is in the cellwidth table, 0 if not. +static int cw_value(int c) +{ + if (cw_table == NULL) { + return 0; + } + + // first quick check for Latin1 etc. characters + if (c < cw_table[0].first) { + return 0; + } + + // binary search in table + int bot = 0; + int top = (int)cw_table_size - 1; + while (top >= bot) { + int mid = (bot + top) / 2; + if (cw_table[mid].last < c) { + bot = mid + 1; + } else if (cw_table[mid].first > c) { + top = mid - 1; + } else { + return cw_table[mid].width; + } + } + return 0; +} + +static int tv_nr_compare(const void *a1, const void *a2) +{ + const listitem_T *const li1 = tv_list_first(*(const list_T **)a1); + const listitem_T *const li2 = tv_list_first(*(const list_T **)a2); + + return (int)(TV_LIST_ITEM_TV(li1)->vval.v_number - TV_LIST_ITEM_TV(li2)->vval.v_number); +} + +/// "setcellwidths()" function +void f_setcellwidths(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + if (argvars[0].v_type != VAR_LIST || argvars[0].vval.v_list == NULL) { + emsg(_(e_listreq)); + return; + } + const list_T *const l = argvars[0].vval.v_list; + if (tv_list_len(l) == 0) { + // Clearing the table. + xfree(cw_table); + cw_table = NULL; + cw_table_size = 0; + return; + } + + // Note: use list_T instead of listitem_T so that TV_LIST_ITEM_NEXT can be used properly below. + const list_T **ptrs = xmalloc(sizeof(const list_T *) * (size_t)tv_list_len(l)); + + // Check that all entries are a list with three numbers, the range is + // valid and the cell width is valid. + int item = 0; + TV_LIST_ITER_CONST(l, li, { + const typval_T *const li_tv = TV_LIST_ITEM_TV(li); + + if (li_tv->v_type != VAR_LIST || li_tv->vval.v_list == NULL) { + semsg(_(e_list_item_nr_is_not_list), item); + xfree(ptrs); + return; + } + + const list_T *const li_l = li_tv->vval.v_list; + ptrs[item] = li_l; + const listitem_T *lili = tv_list_first(li_l); + int i; + varnumber_T n1; + for (i = 0; lili != NULL; lili = TV_LIST_ITEM_NEXT(li_l, lili), i++) { + const typval_T *const lili_tv = TV_LIST_ITEM_TV(lili); + if (lili_tv->v_type != VAR_NUMBER) { + break; + } + if (i == 0) { + n1 = lili_tv->vval.v_number; + if (n1 < 0x100) { + emsg(_(e_only_values_of_0x100_and_higher_supported)); + xfree(ptrs); + return; + } + } else if (i == 1 && lili_tv->vval.v_number < n1) { + semsg(_(e_list_item_nr_range_invalid), item); + xfree(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); + return; + } + } + + if (i != 3) { + semsg(_(e_list_item_nr_does_not_contain_3_numbers), item); + xfree(ptrs); + return; + } + + item++; + }); + + // Sort the list on the first number. + qsort((void *)ptrs, (size_t)tv_list_len(l), sizeof(const list_T *), tv_nr_compare); + + cw_interval_T *table = xmalloc(sizeof(cw_interval_T) * (size_t)tv_list_len(l)); + + // Store the items in the new table. + for (item = 0; item < tv_list_len(l); item++) { + const list_T *const li_l = ptrs[item]; + const listitem_T *lili = tv_list_first(li_l); + 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(table); + return; + } + table[item].first = n1; + lili = TV_LIST_ITEM_NEXT(li_l, lili); + table[item].last = TV_LIST_ITEM_TV(lili)->vval.v_number; + lili = TV_LIST_ITEM_NEXT(li_l, lili); + table[item].width = (char)TV_LIST_ITEM_TV(lili)->vval.v_number; + } + + xfree(ptrs); + + cw_interval_T *const cw_table_save = cw_table; + const size_t cw_table_size_save = cw_table_size; + cw_table = table; + cw_table_size = (size_t)tv_list_len(l); + + // Check that the new value does not conflict with 'listchars' or + // 'fillchars'. + const char *const error = check_chars_options(); + if (error != NULL) { + emsg(_(error)); + cw_table = cw_table_save; + cw_table_size = cw_table_size_save; + xfree(table); + return; + } + + xfree(cw_table_save); + redraw_all_later(UPD_NOT_VALID); +} + +void f_charclass(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + if (tv_check_for_string(&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); +} diff --git a/src/nvim/mbyte.h b/src/nvim/mbyte.h index 1e5e332ad9..2a9afcbd03 100644 --- a/src/nvim/mbyte.h +++ b/src/nvim/mbyte.h @@ -5,8 +5,9 @@ #include <stdint.h> #include <string.h> +#include "nvim/eval/typval.h" #include "nvim/func_attr.h" -#include "nvim/iconv.h" +#include "nvim/mbyte_defs.h" #include "nvim/os/os_defs.h" // For indirect #include "nvim/types.h" // for char_u @@ -19,52 +20,6 @@ #define MB_BYTE2LEN(b) utf8len_tab[b] #define MB_BYTE2LEN_CHECK(b) (((b) < 0 || (b) > 255) ? 1 : utf8len_tab[b]) -// max length of an unicode char -#define MB_MAXCHAR 6 - -// properties used in enc_canon_table[] (first three mutually exclusive) -#define ENC_8BIT 0x01 -#define ENC_DBCS 0x02 -#define ENC_UNICODE 0x04 - -#define ENC_ENDIAN_B 0x10 // Unicode: Big endian -#define ENC_ENDIAN_L 0x20 // Unicode: Little endian - -#define ENC_2BYTE 0x40 // Unicode: UCS-2 -#define ENC_4BYTE 0x80 // Unicode: UCS-4 -#define ENC_2WORD 0x100 // Unicode: UTF-16 - -#define ENC_LATIN1 0x200 // Latin1 -#define ENC_LATIN9 0x400 // Latin9 -#define ENC_MACROMAN 0x800 // Mac Roman (not Macro Man! :-) - -/// Flags for vimconv_T -typedef enum { - CONV_NONE = 0, - CONV_TO_UTF8 = 1, - CONV_9_TO_UTF8 = 2, - CONV_TO_LATIN1 = 3, - CONV_TO_LATIN9 = 4, - CONV_ICONV = 5, -} ConvFlags; - -#define MBYTE_NONE_CONV { \ - .vc_type = CONV_NONE, \ - .vc_factor = 1, \ - .vc_fail = false, \ -} - -/// Structure used for string conversions -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; - extern const uint8_t utf8len_tab_zero[256]; extern const uint8_t utf8len_tab[256]; diff --git a/src/nvim/mbyte_defs.h b/src/nvim/mbyte_defs.h new file mode 100644 index 0000000000..53b01a211f --- /dev/null +++ b/src/nvim/mbyte_defs.h @@ -0,0 +1,56 @@ +#ifndef NVIM_MBYTE_DEFS_H +#define NVIM_MBYTE_DEFS_H + +#include <stdbool.h> + +#include "nvim/iconv.h" + +/// max length of an unicode char +enum { MB_MAXCHAR = 6, }; + +/// properties used in enc_canon_table[] (first three mutually exclusive) +enum { + ENC_8BIT = 0x01, + ENC_DBCS = 0x02, + ENC_UNICODE = 0x04, + + ENC_ENDIAN_B = 0x10, ///< Unicode: Big endian + ENC_ENDIAN_L = 0x20, ///< Unicode: Little endian + + ENC_2BYTE = 0x40, ///< Unicode: UCS-2 + ENC_4BYTE = 0x80, ///< Unicode: UCS-4 + ENC_2WORD = 0x100, ///< Unicode: UTF-16 + + ENC_LATIN1 = 0x200, ///< Latin1 + ENC_LATIN9 = 0x400, ///< Latin9 + ENC_MACROMAN = 0x800, ///< Mac Roman (not Macro Man! :-) +}; + +/// Flags for vimconv_T +typedef enum { + CONV_NONE = 0, + CONV_TO_UTF8 = 1, + CONV_9_TO_UTF8 = 2, + CONV_TO_LATIN1 = 3, + CONV_TO_LATIN9 = 4, + CONV_ICONV = 5, +} ConvFlags; + +#define MBYTE_NONE_CONV { \ + .vc_type = CONV_NONE, \ + .vc_factor = 1, \ + .vc_fail = false, \ +} + +/// Structure used for string conversions +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; + +#endif // NVIM_MBYTE_DEFS_H diff --git a/src/nvim/memfile.c b/src/nvim/memfile.c index c828334eaf..9446aaee4f 100644 --- a/src/nvim/memfile.c +++ b/src/nvim/memfile.c @@ -77,7 +77,7 @@ /// /// @return - The open memory file, on success. /// - NULL, on failure (e.g. file does not exist). -memfile_T *mf_open(char_u *fname, int flags) +memfile_T *mf_open(char *fname, int flags) { memfile_T *mfp = xmalloc(sizeof(memfile_T)); @@ -148,7 +148,7 @@ memfile_T *mf_open(char_u *fname, int flags) /// /// @return OK On success. /// FAIL If file could not be opened. -int mf_open_file(memfile_T *mfp, char_u *fname) +int mf_open_file(memfile_T *mfp, char *fname) { if (mf_do_open(mfp, fname, O_RDWR | O_CREAT | O_EXCL)) { mfp->mf_dirty = true; @@ -749,9 +749,9 @@ void mf_free_fnames(memfile_T *mfp) /// /// Only called when creating or renaming the swapfile. Either way it's a new /// name so we must work out the full path name. -void mf_set_fnames(memfile_T *mfp, char_u *fname) +void mf_set_fnames(memfile_T *mfp, char *fname) { - mfp->mf_fname = fname; + mfp->mf_fname = (char_u *)fname; mfp->mf_ffname = (char_u *)FullName_save((char *)mfp->mf_fname, false); } @@ -779,7 +779,7 @@ bool mf_need_trans(memfile_T *mfp) /// /// @param flags Flags for open(). /// @return A bool indicating success of the `open` call. -static bool mf_do_open(memfile_T *mfp, char_u *fname, int flags) +static bool mf_do_open(memfile_T *mfp, char *fname, int flags) { // fname cannot be NameBuff, because it must have been allocated. mf_set_fnames(mfp, fname); @@ -821,7 +821,7 @@ static bool mf_do_open(memfile_T *mfp, char_u *fname, int flags) /// Initialize an empty hash table. static void mf_hash_init(mf_hashtab_T *mht) { - memset(mht, 0, sizeof(mf_hashtab_T)); + CLEAR_POINTER(mht); mht->mht_buckets = mht->mht_small_buckets; mht->mht_mask = MHT_INIT_SIZE - 1; } @@ -924,7 +924,7 @@ static void mf_hash_grow(mf_hashtab_T *mht) /// a power of two. mf_hashitem_T *tails[MHT_GROWTH_FACTOR]; - memset(tails, 0, sizeof(tails)); + CLEAR_FIELD(tails); for (mf_hashitem_T *mhi = mht->mht_buckets[i]; mhi != NULL; mhi = mhi->mhi_next) { diff --git a/src/nvim/memline.c b/src/nvim/memline.c index fa3a400a68..b0b6b675cb 100644 --- a/src/nvim/memline.c +++ b/src/nvim/memline.c @@ -44,9 +44,11 @@ #include <string.h> #include "nvim/ascii.h" +#include "nvim/autocmd.h" #include "nvim/buffer.h" #include "nvim/change.h" #include "nvim/cursor.h" +#include "nvim/drawscreen.h" #include "nvim/eval.h" #include "nvim/fileio.h" #include "nvim/func_attr.h" @@ -65,7 +67,6 @@ #include "nvim/os/process.h" #include "nvim/os_unix.h" #include "nvim/path.h" -#include "nvim/screen.h" #include "nvim/sha256.h" #include "nvim/spell.h" #include "nvim/strings.h" @@ -383,7 +384,7 @@ void ml_setname(buf_T *buf) bool success = false; memfile_T *mfp; char_u *fname; - char_u *dirp; + char *dirp; mfp = buf->b_ml.ml_mfp; if (mfp->mf_fd < 0) { // there is no swap file yet @@ -397,17 +398,14 @@ void ml_setname(buf_T *buf) return; } - /* - * Try all directories in the 'directory' option. - */ + // Try all directories in the 'directory' option. dirp = p_dir; bool found_existing_dir = false; for (;;) { if (*dirp == NUL) { // tried all directories, fail break; } - fname = (char_u *)findswapname(buf, (char **)&dirp, (char *)mfp->mf_fname, - &found_existing_dir); + fname = (char_u *)findswapname(buf, &dirp, (char *)mfp->mf_fname, &found_existing_dir); // alloc's fname if (dirp == NULL) { // out of memory break; @@ -432,7 +430,7 @@ void ml_setname(buf_T *buf) if (vim_rename(mfp->mf_fname, fname) == 0) { success = true; mf_free_fnames(mfp); - mf_set_fnames(mfp, fname); + mf_set_fnames(mfp, (char *)fname); ml_upd_block0(buf, UB_SAME_DIR); break; } @@ -472,7 +470,7 @@ void ml_open_file(buf_T *buf) { memfile_T *mfp; char_u *fname; - char_u *dirp; + char *dirp; mfp = buf->b_ml.ml_mfp; if (mfp == NULL || mfp->mf_fd >= 0 || !buf->b_p_swf @@ -485,15 +483,13 @@ void ml_open_file(buf_T *buf) if (buf->b_spell) { fname = vim_tempname(); if (fname != NULL) { - (void)mf_open_file(mfp, fname); // consumes fname! + (void)mf_open_file(mfp, (char *)fname); // consumes fname! } buf->b_may_swap = false; return; } - /* - * Try all directories in 'directory' option. - */ + // Try all directories in 'directory' option. dirp = p_dir; bool found_existing_dir = false; for (;;) { @@ -503,15 +499,14 @@ void ml_open_file(buf_T *buf) // There is a small chance that between choosing the swap file name // and creating it, another Vim creates the file. In that case the // creation will fail and we will use another directory. - fname = (char_u *)findswapname(buf, (char **)&dirp, NULL, - &found_existing_dir); + fname = (char_u *)findswapname(buf, &dirp, NULL, &found_existing_dir); if (dirp == NULL) { break; // out of memory } if (fname == NULL) { continue; } - if (mf_open_file(mfp, fname) == OK) { // consumes fname! + if (mf_open_file(mfp, (char *)fname) == OK) { // consumes fname! ml_upd_block0(buf, UB_SAME_DIR); // Flush block zero, so others can read it @@ -528,7 +523,7 @@ void ml_open_file(buf_T *buf) } if (*p_dir != NUL && mfp->mf_fname == NULL) { - need_wait_return = true; // call wait_return later + need_wait_return = true; // call wait_return() later no_wait_return++; (void)semsg(_("E303: Unable to open swap file for \"%s\", recovery impossible"), buf_spname(buf) != NULL ? buf_spname(buf) : buf->b_fname); @@ -557,7 +552,7 @@ void check_need_swap(bool newfile) /// Close memline for buffer 'buf'. /// -/// @param del_file if TRUE, delete the swap file +/// @param del_file if true, delete the swap file void ml_close(buf_T *buf, int del_file) { if (buf->b_ml.ml_mfp == NULL) { // not open @@ -595,7 +590,7 @@ void ml_close_notmod(void) { FOR_ALL_BUFFERS(buf) { if (!bufIsChanged(buf)) { - ml_close(buf, TRUE); // close all not-modified buffers + ml_close(buf, true); // close all not-modified buffers } } } @@ -726,7 +721,7 @@ static void add_b0_fenc(ZERO_BL *b0p, buf_T *buf) b0p->b0_flags &= (uint8_t) ~B0_HAS_FENC; } else { memmove((char *)b0p->b0_fname + size - n, - (char *)buf->b_p_fenc, (size_t)n); + buf->b_p_fenc, (size_t)n); *(b0p->b0_fname + size - n - 1) = NUL; b0p->b0_flags |= B0_HAS_FENC; } @@ -769,7 +764,7 @@ void ml_recover(bool checkext) int attr; int orig_file_status = NOTDONE; - recoverymode = TRUE; + recoverymode = true; called_from_main = (curbuf->b_ml.ml_mfp == NULL); attr = HL_ATTR(HLF_E); @@ -790,7 +785,7 @@ void ml_recover(bool checkext) directly = false; // count the number of matching swap files - len = recover_names(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; @@ -799,16 +794,16 @@ void ml_recover(bool checkext) i = 1; } else { // several swap files found, choose // list the names of the swap files - (void)recover_names(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); + i = get_number(false, NULL); if (i < 1 || i > len) { goto theend; } } // get the swap file name that will be used - (void)recover_names(fname, FALSE, i, &fname_used); + (void)recover_names(fname, false, i, &fname_used); } if (fname_used == NULL) { goto theend; // user chose invalid number. @@ -840,7 +835,7 @@ void ml_recover(bool checkext) */ p = vim_strsave(fname_used); // save "fname_used" for the message: // mf_open() will consume "fname_used"! - mfp = mf_open(fname_used, O_RDONLY); + mfp = mf_open((char *)fname_used, O_RDONLY); fname_used = p; if (mfp == NULL || mfp->mf_fd < 0) { semsg(_("E306: Cannot open %s"), fname_used); @@ -862,7 +857,7 @@ void ml_recover(bool checkext) if ((hp = mf_get(mfp, 0, 1)) == NULL) { msg_start(); msg_puts_attr(_("Unable to read block 0 from "), attr | MSG_HIST); - msg_outtrans_attr(mfp->mf_fname, attr | MSG_HIST); + msg_outtrans_attr((char *)mfp->mf_fname, attr | MSG_HIST); msg_puts_attr(_("\nMaybe no changes were made or Vim did not update the swap file."), attr | MSG_HIST); msg_end(); @@ -871,7 +866,7 @@ void ml_recover(bool checkext) b0p = hp->bh_data; if (STRNCMP(b0p->b0_version, "VIM 3.0", 7) == 0) { msg_start(); - msg_outtrans_attr(mfp->mf_fname, MSG_HIST); + msg_outtrans_attr((char *)mfp->mf_fname, MSG_HIST); msg_puts_attr(_(" cannot be used with this version of Vim.\n"), MSG_HIST); msg_puts_attr(_("Use Vim version 3.0.\n"), MSG_HIST); @@ -884,7 +879,7 @@ void ml_recover(bool checkext) } if (b0_magic_wrong(b0p)) { msg_start(); - msg_outtrans_attr(mfp->mf_fname, attr | MSG_HIST); + msg_outtrans_attr((char *)mfp->mf_fname, attr | MSG_HIST); msg_puts_attr(_(" cannot be used on this computer.\n"), attr | MSG_HIST); msg_puts_attr(_("The file was created on "), attr | MSG_HIST); @@ -906,7 +901,7 @@ void ml_recover(bool checkext) mf_new_page_size(mfp, (unsigned)char_to_long(b0p->b0_page_size)); if (mfp->mf_page_size < previous_page_size) { msg_start(); - msg_outtrans_attr(mfp->mf_fname, attr | MSG_HIST); + msg_outtrans_attr((char *)mfp->mf_fname, attr | MSG_HIST); msg_puts_attr(_(" has been damaged (page size is smaller than minimum value).\n"), attr | MSG_HIST); msg_end(); @@ -927,11 +922,9 @@ void ml_recover(bool checkext) b0p = hp->bh_data; } - /* - * If .swp file name given directly, use name from swap file for buffer. - */ + // If .swp file name given directly, use name from swap file for buffer. if (directly) { - expand_env(b0p->b0_fname, NameBuff, MAXPATHL); + expand_env((char *)b0p->b0_fname, NameBuff, MAXPATHL); if (setfname(curbuf, (char *)NameBuff, NULL, true) == FAIL) { goto theend; } @@ -998,7 +991,7 @@ void ml_recover(bool checkext) set_fileformat(b0_ff - 1, OPT_LOCAL); } if (b0_fenc != NULL) { - set_option_value("fenc", 0L, (char *)b0_fenc, OPT_LOCAL); + set_option_value_give_err("fenc", 0L, (char *)b0_fenc, OPT_LOCAL); xfree(b0_fenc); } unchanged(curbuf, true, true); @@ -1040,7 +1033,7 @@ void ml_recover(bool checkext) if (pp->pb_id == PTR_ID) { // it is a pointer block // check line count when using pointer block first time if (idx == 0 && line_count != 0) { - for (i = 0; i < (int)pp->pb_count; ++i) { + for (i = 0; i < (int)pp->pb_count; i++) { line_count -= pp->pb_pointer[i].pe_line_count; } if (line_count != 0) { @@ -1076,7 +1069,7 @@ void ml_recover(bool checkext) ml_append(lnum++, _("???LINES MISSING"), (colnr_T)0, true); } - ++idx; // get same block again for next index + idx++; // get same block again for next index continue; } @@ -1136,12 +1129,12 @@ void ml_recover(bool checkext) has_error = true; } - for (i = 0; i < dp->db_line_count; ++i) { + for (i = 0; i < dp->db_line_count; i++) { txt_start = (dp->db_index[i] & DB_INDEX_MASK); if (txt_start <= (int)HEADER_SIZE || txt_start >= (int)dp->db_txt_end) { p = (char_u *)"???"; - ++error; + error++; } else { p = (char_u *)dp + txt_start; } @@ -1182,7 +1175,7 @@ void ml_recover(bool checkext) buf_inc_changedtick(curbuf); } } else { - for (idx = 1; idx <= lnum; ++idx) { + for (idx = 1; idx <= lnum; idx++) { // Need to copy one line, fetching the other one may flush it. p = vim_strsave(ml_get(idx)); i = STRCMP(p, ml_get(idx + lnum)); @@ -1206,14 +1199,14 @@ void ml_recover(bool checkext) curbuf->b_flags |= BF_RECOVERED; check_cursor(); - recoverymode = FALSE; + recoverymode = false; if (got_int) { emsg(_("E311: Recovery Interrupted")); } else if (error) { - ++no_wait_return; + no_wait_return++; msg(">>>>>>>>>>>>>"); emsg(_("E312: Errors detected while recovering; look for lines starting with ???")); - --no_wait_return; + no_wait_return--; msg(_("See \":help E312\" for more information.")); msg(">>>>>>>>>>>>>"); } else { @@ -1227,11 +1220,11 @@ void ml_recover(bool checkext) msg_puts(_("\nYou may want to delete the .swp file now.\n\n")); cmdline_row = msg_row; } - redraw_curbuf_later(NOT_VALID); + redraw_curbuf_later(UPD_NOT_VALID); theend: xfree(fname_used); - recoverymode = FALSE; + recoverymode = false; if (mfp != NULL) { if (hp != NULL) { mf_put(mfp, hp, false, false); @@ -1243,7 +1236,7 @@ theend: xfree(buf); } if (serious_error && called_from_main) { - ml_close(curbuf, TRUE); + ml_close(curbuf, true); } else { apply_autocmds(EVENT_BUFREADPOST, NULL, curbuf->b_fname, false, curbuf); apply_autocmds(EVENT_BUFWINENTER, NULL, curbuf->b_fname, false, curbuf); @@ -1260,7 +1253,7 @@ theend: /// - find the name of the n'th swap file when recovering /// /// @param fname base for swap file name -/// @param list when TRUE, list the swap file names +/// @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_u **fname_out) @@ -1272,7 +1265,7 @@ int recover_names(char_u *fname, int list, int nr, char_u **fname_out) int num_files; int file_count = 0; char **files; - char_u *dirp; + char *dirp; char_u *dir_name; char_u *fname_res = NULL; #ifdef HAVE_READLINK @@ -1304,7 +1297,7 @@ int recover_names(char_u *fname, int list, int nr, char_u **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((char **)&dirp, (char *)dir_name, 31000, ","); + (void)copy_option_part(&dirp, (char *)dir_name, 31000, ","); if (dir_name[0] == '.' && dir_name[1] == NUL) { // check current dir if (fname == NULL) { @@ -1315,7 +1308,7 @@ int recover_names(char_u *fname, int list, int nr, char_u **fname_out) names[2] = xstrdup(".sw?"); num_names = 3; } else { - num_names = recov_file_names(names, fname_res, TRUE); + num_names = recov_file_names(names, fname_res, true); } } else { // check directory dir_name if (fname == NULL) { @@ -1338,7 +1331,7 @@ int recover_names(char_u *fname, int list, int nr, char_u **fname_out) tail = (char_u *)path_tail((char *)fname_res); tail = (char_u *)concat_fnames((char *)dir_name, (char *)tail, true); } - num_names = recov_file_names(names, tail, FALSE); + num_names = recov_file_names(names, tail, false); xfree(tail); } } @@ -1384,7 +1377,7 @@ int recover_names(char_u *fname, int list, int nr, char_u **fname_out) if (--num_files == 0) { xfree(files); } else { - for (; i < num_files; ++i) { + for (; i < num_files; i++) { files[i] = files[i + 1]; } } @@ -1395,7 +1388,7 @@ int recover_names(char_u *fname, int list, int nr, char_u **fname_out) file_count += num_files; if (nr <= file_count) { *fname_out = vim_strsave((char_u *)files[nr - 1 + num_files - file_count]); - dirp = (char_u *)""; // stop searching + dirp = ""; // stop searching } } else if (list) { if (dir_name[0] == '.' && dir_name[1] == NUL) { @@ -1411,7 +1404,7 @@ int recover_names(char_u *fname, int list, int nr, char_u **fname_out) } if (num_files) { - for (int i = 0; i < num_files; ++i) { + for (int i = 0; i < num_files; i++) { // print the swap file name msg_outnum((long)++file_count); msg_puts(". "); @@ -1427,7 +1420,7 @@ int recover_names(char_u *fname, int list, int nr, char_u **fname_out) file_count += num_files; } - for (int i = 0; i < num_names; ++i) { + for (int i = 0; i < num_names; i++) { xfree(names[i]); } if (num_files > 0) { @@ -1452,7 +1445,7 @@ char *make_percent_swname(const char *dir, const char *name) *d = '%'; } } - d = concat_fnames(dir, s, TRUE); + d = concat_fnames(dir, s, true); xfree(s); xfree(f); } @@ -1660,12 +1653,12 @@ static int recov_file_names(char **names, char_u *path, int prepend_dot) p += i; // file name has been expanded to full path } if (STRCMP(p, names[num_names]) != 0) { - ++num_names; + num_names++; } else { xfree(names[num_names]); } } else { - ++num_names; + num_names++; } return num_names; @@ -1673,8 +1666,8 @@ static int recov_file_names(char **names, char_u *path, int prepend_dot) /// sync all memlines /// -/// @param check_file if TRUE, check if original file exists and was not changed. -/// @param check_char if TRUE, stop syncing when character becomes available, but +/// @param check_file if true, check if original file exists and was not changed. +/// @param check_char if true, stop syncing when character becomes available, but /// /// always sync at least one block. void ml_sync_all(int check_file, int check_char, bool do_fsync) @@ -1719,7 +1712,7 @@ void ml_sync_all(int check_file, int check_char, bool do_fsync) /// Used for the :preserve command and when the original file has been /// changed or deleted. /// -/// @param message if TRUE, the success of preserving is reported. +/// @param message if true, the success of preserving is reported. void ml_preserve(buf_T *buf, int message, bool do_fsync) { bhdr_T *hp; @@ -1913,7 +1906,7 @@ int ml_line_alloced(void) /// "line" does not need to be allocated, but can't be another line in a /// buffer, unlocking may make it invalid. /// -/// newfile: TRUE when starting to edit a new file, meaning that pe_old_lnum +/// newfile: true when starting to edit a new file, meaning that pe_old_lnum /// will be set for recovery /// Check: The caller of this function should probably also call /// appended_lines(). @@ -1927,7 +1920,7 @@ int ml_line_alloced(void) int ml_append(linenr_T lnum, char *line, colnr_T len, bool newfile) { // When starting up, we might still need to create the memfile - if (curbuf->b_ml.ml_mfp == NULL && open_buffer(FALSE, NULL, 0) == FAIL) { + if (curbuf->b_ml.ml_mfp == NULL && open_buffer(false, NULL, 0) == FAIL) { return FAIL; } @@ -1954,7 +1947,7 @@ int ml_append_buf(buf_T *buf, linenr_T lnum, char_u *line, colnr_T len, bool new if (buf->b_ml.ml_line_lnum != 0) { ml_flush_line(buf); } - return ml_append_int(buf, lnum, line, len, newfile, FALSE); + return ml_append_int(buf, lnum, line, len, newfile, false); } /// @param lnum append after this line (can be 0) @@ -2043,7 +2036,7 @@ static int ml_append_int(buf_T *buf, linenr_T lnum, char_u *line, colnr_T len, b dp = hp->bh_data; } - ++buf->b_ml.ml_line_count; + buf->b_ml.ml_line_count++; if ((int)dp->db_free >= space_needed) { // enough room in data block /* @@ -2184,7 +2177,7 @@ static int ml_append_int(buf_T *buf, linenr_T lnum, char_u *line, colnr_T len, b memmove((char *)dp_right + dp_right->db_txt_start, line, (size_t)len); - ++line_count_right; + line_count_right++; } /* * may move lines from the left/old block to the right/new one. @@ -2224,7 +2217,7 @@ static int ml_append_int(buf_T *buf, linenr_T lnum, char_u *line, colnr_T len, b } memmove((char *)dp_left + dp_left->db_txt_start, line, (size_t)len); - ++line_count_left; + line_count_left++; } if (db_idx < 0) { // left block is new @@ -2291,7 +2284,7 @@ static int ml_append_int(buf_T *buf, linenr_T lnum, char_u *line, colnr_T len, b &pp->pb_pointer[pb_idx + 1], (size_t)(pp->pb_count - pb_idx - 1) * sizeof(PTR_EN)); } - ++pp->pb_count; + pp->pb_count++; pp->pb_pointer[pb_idx].pe_line_count = line_count_left; pp->pb_pointer[pb_idx].pe_bnum = bnum_left; pp->pb_pointer[pb_idx].pe_page_count = page_count_left; @@ -2458,7 +2451,7 @@ int ml_replace(linenr_T lnum, char *line, bool copy) /// Do not use it after calling ml_replace(). /// /// Check: The caller of this function should probably also call -/// changed_lines(), unless update_screen(NOT_VALID) is used. +/// changed_lines(), unless update_screen(UPD_NOT_VALID) is used. /// /// @return FAIL for failure, OK otherwise int ml_replace_buf(buf_T *buf, linenr_T lnum, char_u *line, bool copy) @@ -2566,7 +2559,7 @@ static int ml_delete_int(buf_T *buf, linenr_T lnum, bool message) count = buf->b_ml.ml_locked_high - buf->b_ml.ml_locked_low + 2; idx = lnum - buf->b_ml.ml_locked_low; - --buf->b_ml.ml_line_count; + buf->b_ml.ml_line_count--; line_start = ((dp->db_index[idx]) & DB_INDEX_MASK); if (idx == 0) { // first line in block, text at the end @@ -2710,7 +2703,7 @@ linenr_T ml_firstmarked(void) dp = hp->bh_data; for (i = lnum - curbuf->b_ml.ml_locked_low; - lnum <= curbuf->b_ml.ml_locked_high; ++i, ++lnum) { + lnum <= curbuf->b_ml.ml_locked_high; i++, lnum++) { if ((dp->db_index[i]) & DB_MARKED) { (dp->db_index[i]) &= DB_INDEX_MASK; curbuf->b_ml.ml_flags |= ML_LOCKED_DIRTY; @@ -2750,7 +2743,7 @@ void ml_clearmarked(void) dp = hp->bh_data; for (i = lnum - curbuf->b_ml.ml_locked_low; - lnum <= curbuf->b_ml.ml_locked_high; ++i, ++lnum) { + lnum <= curbuf->b_ml.ml_locked_high; i++, lnum++) { if ((dp->db_index[i]) & DB_MARKED) { (dp->db_index[i]) &= DB_INDEX_MASK; curbuf->b_ml.ml_flags |= ML_LOCKED_DIRTY; @@ -2970,7 +2963,7 @@ static bhdr_T *ml_find_line(buf_T *buf, linenr_T lnum, int action) high = buf->b_ml.ml_line_count; if (action == ML_FIND) { // first try stack entries - for (top = buf->b_ml.ml_stack_top - 1; top >= 0; --top) { + for (top = buf->b_ml.ml_stack_top - 1; top >= 0; top--) { ip = &(buf->b_ml.ml_stack[top]); if (ip->ip_low <= lnum && ip->ip_high >= lnum) { bnum = ip->ip_bnum; @@ -2998,9 +2991,9 @@ static bhdr_T *ml_find_line(buf_T *buf, linenr_T lnum, int action) * update high for insert/delete */ if (action == ML_INSERT) { - ++high; + high++; } else if (action == ML_DELETE) { - --high; + high--; } dp = hp->bh_data; @@ -3026,8 +3019,8 @@ static bhdr_T *ml_find_line(buf_T *buf, linenr_T lnum, int action) ip->ip_high = high; ip->ip_index = -1; // index not known yet - dirty = FALSE; - for (idx = 0; idx < (int)pp->pb_count; ++idx) { + dirty = false; + for (idx = 0; idx < (int)pp->pb_count; idx++) { t = pp->pb_pointer[idx].pe_line_count; CHECK(t == 0, _("pe_line_count is zero")); if ((low += t) > lnum) { @@ -3045,7 +3038,7 @@ static bhdr_T *ml_find_line(buf_T *buf, linenr_T lnum, int action) if (bnum != bnum2) { bnum = bnum2; pp->pb_pointer[idx].pe_bnum = bnum; - dirty = TRUE; + dirty = true; } } @@ -3063,10 +3056,10 @@ static bhdr_T *ml_find_line(buf_T *buf, linenr_T lnum, int action) } if (action == ML_DELETE) { pp->pb_pointer[idx].pe_line_count--; - dirty = TRUE; + dirty = true; } else if (action == ML_INSERT) { pp->pb_pointer[idx].pe_line_count++; - dirty = TRUE; + dirty = true; } mf_put(mfp, hp, dirty, false); } @@ -3124,7 +3117,7 @@ static void ml_lineadd(buf_T *buf, int count) memfile_T *mfp = buf->b_ml.ml_mfp; bhdr_T *hp; - for (idx = buf->b_ml.ml_stack_top - 1; idx >= 0; --idx) { + for (idx = buf->b_ml.ml_stack_top - 1; idx >= 0; idx--) { ip = &(buf->b_ml.ml_stack[idx]); if ((hp = mf_get(mfp, ip->ip_bnum, 1)) == NULL) { break; @@ -3208,7 +3201,7 @@ int resolve_symlink(const char_u *fname, char_u *buf) * be consistent even when opening a relative symlink from different * working directories. */ - return vim_FullName((char *)tmp, (char *)buf, MAXPATHL, TRUE); + return vim_FullName((char *)tmp, (char *)buf, MAXPATHL, true); } #endif @@ -3280,7 +3273,7 @@ char_u *get_file_in_dir(char_u *fname, char_u *dname) retval = vim_strsave(fname); } else if (dname[0] == '.' && vim_ispathsep(dname[1])) { if (tail == fname) { // no path before file name - retval = (char_u *)concat_fnames((char *)dname + 2, (char *)tail, TRUE); + retval = (char_u *)concat_fnames((char *)dname + 2, (char *)tail, true); } else { save_char = *tail; *tail = NUL; @@ -3290,7 +3283,7 @@ char_u *get_file_in_dir(char_u *fname, char_u *dname) xfree(t); } } else { - retval = (char_u *)concat_fnames((char *)dname, (char *)tail, TRUE); + retval = (char_u *)concat_fnames((char *)dname, (char *)tail, true); } return retval; @@ -3304,7 +3297,7 @@ static void attention_message(buf_T *buf, char_u *fname) { assert(buf->b_fname != NULL); - ++no_wait_return; + no_wait_return++; (void)emsg(_("E325: ATTENTION")); msg_puts(_("\nFound a swap file by the name \"")); msg_home_replace(fname); @@ -3339,7 +3332,7 @@ static void attention_message(buf_T *buf, char_u *fname) msg_outtrans((char *)fname); msg_puts(_("\"\n to avoid this message.\n")); cmdline_row = msg_row; - --no_wait_return; + no_wait_return--; } /// Trigger the SwapExists autocommands. @@ -3460,7 +3453,7 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_ && !buf->b_help && !(buf->b_flags & BF_DUMMY)) { int fd; struct block0 b0; - int differ = FALSE; + int differ = false; // Try to read block 0 from the swap file to get the original // file name (and inode number). @@ -3477,19 +3470,19 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_ // Symlinks may point to the same file even // when the name differs, need to check the // inode too. - expand_env(b0.b0_fname, NameBuff, MAXPATHL); - if (fnamecmp_ino((char_u *)buf->b_ffname, NameBuff, + expand_env((char *)b0.b0_fname, NameBuff, MAXPATHL); + if (fnamecmp_ino((char_u *)buf->b_ffname, (char_u *)NameBuff, char_to_long(b0.b0_ino))) { - differ = TRUE; + differ = true; } } } else { // The name in the swap file may be // "~user/path/file". Expand it first. - expand_env(b0.b0_fname, NameBuff, MAXPATHL); - if (fnamecmp_ino((char_u *)buf->b_ffname, NameBuff, + expand_env((char *)b0.b0_fname, NameBuff, MAXPATHL); + if (fnamecmp_ino((char_u *)buf->b_ffname, (char_u *)NameBuff, char_to_long(b0.b0_ino))) { - differ = TRUE; + differ = true; } } } @@ -3499,7 +3492,7 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_ // give the ATTENTION message when there is an old swap file // for the current file, and the buffer was not recovered. if (differ == false && !(curbuf->b_flags & BF_RECOVERED) - && vim_strchr((char *)p_shm, SHM_ATTENTION) == NULL) { + && vim_strchr(p_shm, SHM_ATTENTION) == NULL) { int choice = 0; process_still_running = false; @@ -3569,7 +3562,7 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_ if (choice > 0) { switch (choice) { case 1: - buf->b_p_ro = TRUE; + buf->b_p_ro = true; break; case 2: break; @@ -3584,7 +3577,7 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_ break; case 6: swap_exists_action = SEA_QUIT; - got_int = TRUE; + got_int = true; break; } @@ -3615,13 +3608,13 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_ XFREE_CLEAR(fname); break; } - --fname[n - 2]; // ".svz", ".suz", etc. + fname[n - 2]--; // ".svz", ".suz", etc. fname[n - 1] = 'z' + 1; } - --fname[n - 1]; // ".swo", ".swn", etc. + fname[n - 1]--; // ".swo", ".swn", etc. } - if (os_isdir((char_u *)dir_name)) { + if (os_isdir(dir_name)) { *found_existing_dir = true; } else if (!*found_existing_dir && **dirp == NUL) { int ret; @@ -3672,18 +3665,18 @@ static int b0_magic_wrong(ZERO_BL *b0p) /// /// current file doesn't exist, file for swap file exist, file name(s) not /// available -> probably different -/// == 0 != 0 FAIL X TRUE -/// == 0 != 0 X FAIL TRUE +/// == 0 != 0 FAIL X true +/// == 0 != 0 X FAIL true /// /// current file exists, inode for swap unknown, file name(s) not /// available -> probably different -/// != 0 == 0 FAIL X TRUE -/// != 0 == 0 X FAIL TRUE +/// != 0 == 0 FAIL X true +/// != 0 == 0 X FAIL true /// /// current file doesn't exist, inode for swap unknown, one file name not /// available -> probably different -/// == 0 == 0 FAIL OK TRUE -/// == 0 == 0 OK FAIL TRUE +/// == 0 == 0 FAIL OK true +/// == 0 == 0 OK FAIL true /// /// current file doesn't exist, inode for swap unknown, both file names not /// available -> compare file names @@ -3727,8 +3720,8 @@ static bool fnamecmp_ino(char_u *fname_c, char_u *fname_s, long ino_block0) * One of the inode numbers is unknown, try a forced vim_FullName() and * compare the file names. */ - retval_c = vim_FullName((char *)fname_c, (char *)buf_c, MAXPATHL, TRUE); - retval_s = vim_FullName((char *)fname_s, (char *)buf_s, MAXPATHL, TRUE); + retval_c = vim_FullName((char *)fname_c, (char *)buf_c, MAXPATHL, true); + retval_s = vim_FullName((char *)fname_s, (char *)buf_s, MAXPATHL, true); if (retval_c == OK && retval_s == OK) { return STRCMP(buf_c, buf_s) != 0; } @@ -4168,7 +4161,7 @@ void goto_byte(long cnt) curwin->w_cursor.lnum = lnum; curwin->w_cursor.col = (colnr_T)boff; curwin->w_cursor.coladd = 0; - curwin->w_set_curswant = TRUE; + curwin->w_set_curswant = true; } check_cursor(); diff --git a/src/nvim/memory.c b/src/nvim/memory.c index 5ae7f7bbe3..a9785fcb7c 100644 --- a/src/nvim/memory.c +++ b/src/nvim/memory.c @@ -9,6 +9,7 @@ #include <string.h> #include "nvim/api/extmark.h" +#include "nvim/arglist.h" #include "nvim/context.h" #include "nvim/decoration_provider.h" #include "nvim/eval.h" @@ -59,6 +60,8 @@ void try_to_free_memory(void) // Try to save all buffers and release as many blocks as possible mf_release_all(); + arena_free_reuse_blks(); + trying_to_free = false; } @@ -527,21 +530,19 @@ void time_to_bytes(time_t time_, uint8_t buf[8]) } #define ARENA_BLOCK_SIZE 4096 +#define REUSE_MAX 4 + +static struct consumed_blk *arena_reuse_blk; +static size_t arena_reuse_blk_count = 0; -void arena_start(Arena *arena, ArenaMem *reuse_blk) +static void arena_free_reuse_blks(void) { - if (reuse_blk && *reuse_blk) { - arena->cur_blk = (char *)(*reuse_blk); - *reuse_blk = NULL; - } else { - arena->cur_blk = xmalloc(ARENA_BLOCK_SIZE); + while (arena_reuse_blk_count > 0) { + struct consumed_blk *blk = arena_reuse_blk; + arena_reuse_blk = arena_reuse_blk->prev; + xfree(blk); + arena_reuse_blk_count--; } - arena->pos = 0; - arena->size = ARENA_BLOCK_SIZE; - // address is the same as as (struct consumed_blk *)arena->cur_blk - struct consumed_blk *blk = arena_alloc(arena, sizeof(struct consumed_blk), true); - assert((char *)blk == (char *)arena->cur_blk); - blk->prev = NULL; } /// Finnish the allocations in an arena. @@ -557,17 +558,45 @@ ArenaMem arena_finish(Arena *arena) return res; } +void alloc_block(Arena *arena) +{ + struct consumed_blk *prev_blk = (struct consumed_blk *)arena->cur_blk; + if (arena_reuse_blk_count > 0) { + arena->cur_blk = (char *)arena_reuse_blk; + arena_reuse_blk = arena_reuse_blk->prev; + arena_reuse_blk_count--; + } else { + arena_alloc_count++; + arena->cur_blk = xmalloc(ARENA_BLOCK_SIZE); + } + arena->pos = 0; + arena->size = ARENA_BLOCK_SIZE; + struct consumed_blk *blk = arena_alloc(arena, sizeof(struct consumed_blk), true); + blk->prev = prev_blk; +} + +/// @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 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->pos + size > arena->size) { - if (size > (arena->size - sizeof(struct consumed_blk)) >> 1) { + if (arena->pos + size > arena->size || !arena->cur_blk) { + 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)); struct consumed_blk *cur_blk = (struct consumed_blk *)arena->cur_blk; struct consumed_blk *fix_blk = (struct consumed_blk *)alloc; @@ -575,12 +604,7 @@ void *arena_alloc(Arena *arena, size_t size, bool align) cur_blk->prev = fix_blk; return (alloc + sizeof(struct consumed_blk)); } else { - struct consumed_blk *prev_blk = (struct consumed_blk *)arena->cur_blk; - arena->cur_blk = xmalloc(ARENA_BLOCK_SIZE); - arena->pos = 0; - arena->size = ARENA_BLOCK_SIZE; - struct consumed_blk *blk = arena_alloc(arena, sizeof(struct consumed_blk), true); - blk->prev = prev_blk; + alloc_block(arena); } } @@ -589,15 +613,17 @@ void *arena_alloc(Arena *arena, size_t size, bool align) return mem; } -void arena_mem_free(ArenaMem mem, ArenaMem *reuse_blk) +void arena_mem_free(ArenaMem mem) { struct consumed_blk *b = mem; // peel of the first block, as it is guaranteed to be ARENA_BLOCK_SIZE, // not a custom fix_blk - if (reuse_blk && *reuse_blk == NULL && b != NULL) { - *reuse_blk = b; + if (arena_reuse_blk_count < REUSE_MAX && b != NULL) { + struct consumed_blk *reuse_blk = b; b = b->prev; - (*reuse_blk)->prev = NULL; + reuse_blk->prev = arena_reuse_blk; + arena_reuse_blk = reuse_blk; + arena_reuse_blk_count++; } while (b) { @@ -617,8 +643,10 @@ char *arena_memdupz(Arena *arena, const char *buf, size_t size) #if defined(EXITFREE) +# include "nvim/autocmd.h" # include "nvim/buffer.h" # include "nvim/charset.h" +# include "nvim/cmdhist.h" # include "nvim/diff.h" # include "nvim/edit.h" # include "nvim/eval/typval.h" @@ -626,9 +654,9 @@ 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/fileio.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" @@ -640,7 +668,6 @@ char *arena_memdupz(Arena *arena, const char *buf, size_t size) # 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/syntax.h" @@ -797,6 +824,9 @@ void free_all_mem(void) nlua_free_all_mem(); ui_free_all_mem(); + + // should be last, in case earlier free functions deallocates arenas + arena_free_reuse_blks(); } #endif diff --git a/src/nvim/memory.h b/src/nvim/memory.h index 63d607c2ce..f407192331 100644 --- a/src/nvim/memory.h +++ b/src/nvim/memory.h @@ -6,6 +6,8 @@ #include <stdint.h> // for uint8_t #include <time.h> // for time_t +#include "nvim/macros.h" + /// `malloc()` function signature typedef void *(*MemMalloc)(size_t); @@ -37,6 +39,8 @@ extern MemRealloc mem_realloc; extern bool entered_free_all_mem; #endif +EXTERN size_t arena_alloc_count INIT(=0); + typedef struct consumed_blk { struct consumed_blk *prev; } *ArenaMem; @@ -48,7 +52,7 @@ typedef struct { size_t pos, size; } Arena; -// inits an empty arena. use arena_start() to actually allocate space! +// inits an empty arena. #define ARENA_EMPTY { .cur_blk = NULL, .pos = 0, .size = 0 } #define kv_fixsize_arena(a, v, s) \ diff --git a/src/nvim/menu.c b/src/nvim/menu.c index 16802a4e50..cb6918cd27 100644 --- a/src/nvim/menu.c +++ b/src/nvim/menu.c @@ -23,7 +23,7 @@ #include "nvim/memory.h" #include "nvim/menu.h" #include "nvim/message.h" -#include "nvim/popupmnu.h" +#include "nvim/popupmenu.h" #include "nvim/screen.h" #include "nvim/state.h" #include "nvim/strings.h" @@ -419,7 +419,7 @@ static int add_menu_path(const char *const menu_path, vimmenu_T *menuarg, const p = (call_data == NULL) ? NULL : xstrdup(call_data); // loop over all modes, may add more than one - for (i = 0; i < MENU_MODES; ++i) { + for (i = 0; i < MENU_MODES; i++) { if (modes & (1 << i)) { // free any old menu free_menu_string(menu, i); @@ -853,7 +853,7 @@ static void show_menus_recursive(vimmenu_T *menu, int modes, int depth) msg_puts(" "); } // Same highlighting as for directories!? - msg_outtrans_attr((char_u *)menu->name, HL_ATTR(HLF_D)); + msg_outtrans_attr(menu->name, HL_ATTR(HLF_D)); } if (menu != NULL && menu->children == NULL) { @@ -914,7 +914,7 @@ static void show_menus_recursive(vimmenu_T *menu, int modes, int depth) */ static vimmenu_T *expand_menu = NULL; static int expand_modes = 0x0; -static int expand_emenu; // TRUE for ":emenu" command +static int expand_emenu; // true for ":emenu" command /* * Work out what to complete when doing command line completion of menu names. @@ -933,7 +933,7 @@ char *set_context_in_menu_cmd(expand_T *xp, const char *cmd, char *arg, bool for xp->xp_context = EXPAND_UNSUCCESSFUL; // Check for priority numbers, enable and disable - for (p = arg; *p; ++p) { + for (p = arg; *p; p++) { if (!ascii_isdigit(*p) && *p != '.') { break; } @@ -952,12 +952,12 @@ char *set_context_in_menu_cmd(expand_T *xp, const char *cmd, char *arg, bool for } while (*p != NUL && ascii_iswhite(*p)) { - ++p; + p++; } arg = after_dot = p; - for (; *p && !ascii_iswhite(*p); ++p) { + for (; *p && !ascii_iswhite(*p); p++) { if ((*p == '\\' || *p == Ctrl_V) && p[1] != NUL) { p++; } else if (*p == '.') { @@ -1056,7 +1056,7 @@ char *get_menu_name(expand_T *xp, int idx) } else { str = menu->dname; if (menu->en_dname == NULL) { - should_advance = TRUE; + should_advance = true; } } } else { @@ -1163,10 +1163,8 @@ char *menu_name_skip(char *const name) return p; } -/* - * Return TRUE when "name" matches with menu "menu". The name is compared in - * two ways: raw menu name and menu name without '&'. ignore part after a TAB. - */ +/// Return true when "name" matches with menu "menu". The name is compared in +/// two ways: raw menu name and menu name without '&'. ignore part after a TAB. static bool menu_name_equal(const char *const name, const vimmenu_T *const menu) { if (menu->en_name != NULL @@ -1181,7 +1179,7 @@ static bool menu_namecmp(const char *const name, const char *const mname) { int i; - for (i = 0; name[i] != NUL && name[i] != TAB; ++i) { + for (i = 0; name[i] != NUL && name[i] != TAB; i++) { if (name[i] != mname[i]) { break; } @@ -1405,10 +1403,8 @@ bool menu_is_toolbar(const char *const name) return STRNCMP(name, "ToolBar", 7) == 0; } -/* - * Return TRUE if the name is a menu separator identifier: Starts and ends - * with '-' - */ +/// Return true if the name is a menu separator identifier: Starts and ends +/// with '-' int menu_is_separator(char *name) { return name[0] == '-' && name[STRLEN(name) - 1] == '-'; @@ -1659,26 +1655,19 @@ void ex_emenu(exarg_T *eap) if (arg[0] && ascii_iswhite(arg[1])) { switch (arg[0]) { case 'n': - mode_idx = MENU_INDEX_NORMAL; - break; + mode_idx = MENU_INDEX_NORMAL; break; case 'v': - mode_idx = MENU_INDEX_VISUAL; - break; + mode_idx = MENU_INDEX_VISUAL; break; case 's': - mode_idx = MENU_INDEX_SELECT; - break; + mode_idx = MENU_INDEX_SELECT; break; case 'o': - mode_idx = MENU_INDEX_OP_PENDING; - break; + mode_idx = MENU_INDEX_OP_PENDING; break; case 't': - mode_idx = MENU_INDEX_TERMINAL; - break; + mode_idx = MENU_INDEX_TERMINAL; break; case 'i': - mode_idx = MENU_INDEX_INSERT; - break; + mode_idx = MENU_INDEX_INSERT; break; case 'c': - mode_idx = MENU_INDEX_CMDLINE; - break; + mode_idx = MENU_INDEX_CMDLINE; break; default: semsg(_(e_invarg2), arg); return; @@ -1814,9 +1803,9 @@ static char *menu_skip_part(char *p) { while (*p != NUL && *p != '.' && !ascii_iswhite(*p)) { if ((*p == '\\' || *p == Ctrl_V) && p[1] != NUL) { - ++p; + p++; } - ++p; + p++; } return p; } @@ -1952,7 +1941,7 @@ static void menuitem_getinfo(const char *menu_name, const vimmenu_T *menu, int m /// "menu_info()" function /// Return information about a menu (including all the child menus) -void f_menu_info(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_menu_info(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { tv_dict_alloc_ret(rettv); dict_T *const retdict = rettv->vval.v_dict; diff --git a/src/nvim/menu.h b/src/nvim/menu.h index 9a60ebfb83..be294a1831 100644 --- a/src/nvim/menu.h +++ b/src/nvim/menu.h @@ -4,28 +4,9 @@ #include <stdbool.h> // for bool #include "nvim/ex_cmds_defs.h" // for exarg_T +#include "nvim/menu_defs.h" #include "nvim/types.h" // for char_u and expand_T -/// @} -/// note MENU_INDEX_TIP is not a 'real' mode - -/// Menu modes -/// \addtogroup MENU_MODES -/// @{ -#define MENU_NORMAL_MODE (1 << MENU_INDEX_NORMAL) -#define MENU_VISUAL_MODE (1 << MENU_INDEX_VISUAL) -#define MENU_SELECT_MODE (1 << MENU_INDEX_SELECT) -#define MENU_OP_PENDING_MODE (1 << MENU_INDEX_OP_PENDING) -#define MENU_INSERT_MODE (1 << MENU_INDEX_INSERT) -#define MENU_CMDLINE_MODE (1 << MENU_INDEX_CMDLINE) -#define MENU_TERMINAL_MODE (1 << MENU_INDEX_TERMINAL) -#define MENU_TIP_MODE (1 << MENU_INDEX_TIP) -#define MENU_ALL_MODES ((1 << MENU_INDEX_TIP) - 1) -/// @} - -/// Start a menu name with this to not include it on the main menu bar -#define MNU_HIDDEN_CHAR ']' - #ifdef INCLUDE_GENERATED_DECLARATIONS # include "menu.h.generated.h" #endif diff --git a/src/nvim/menu_defs.h b/src/nvim/menu_defs.h new file mode 100644 index 0000000000..5fdb222bde --- /dev/null +++ b/src/nvim/menu_defs.h @@ -0,0 +1,64 @@ +#ifndef NVIM_MENU_DEFS_H +#define NVIM_MENU_DEFS_H + +#include <stdbool.h> // for bool + +/// Indices into vimmenu_T->strings[] and vimmenu_T->noremap[] for each mode +/// \addtogroup MENU_INDEX +/// @{ +enum { + MENU_INDEX_INVALID = -1, + MENU_INDEX_NORMAL = 0, + MENU_INDEX_VISUAL = 1, + MENU_INDEX_SELECT = 2, + MENU_INDEX_OP_PENDING = 3, + MENU_INDEX_INSERT = 4, + MENU_INDEX_CMDLINE = 5, + MENU_INDEX_TERMINAL = 6, + MENU_INDEX_TIP = 7, + MENU_MODES = 8, +}; +/// @} + +/// Menu modes +/// \addtogroup MENU_MODES +/// @{ +enum { + MENU_NORMAL_MODE = 1 << MENU_INDEX_NORMAL, + MENU_VISUAL_MODE = 1 << MENU_INDEX_VISUAL, + MENU_SELECT_MODE = 1 << MENU_INDEX_SELECT, + MENU_OP_PENDING_MODE = 1 << MENU_INDEX_OP_PENDING, + MENU_INSERT_MODE = 1 << MENU_INDEX_INSERT, + MENU_CMDLINE_MODE = 1 << MENU_INDEX_CMDLINE, + MENU_TERMINAL_MODE = 1 << MENU_INDEX_TERMINAL, + MENU_TIP_MODE = 1 << MENU_INDEX_TIP, + MENU_ALL_MODES = (1 << MENU_INDEX_TIP) - 1, +}; +/// @} +/// note MENU_INDEX_TIP is not a 'real' mode + +/// Start a menu name with this to not include it on the main menu bar +#define MNU_HIDDEN_CHAR ']' + +typedef struct VimMenu vimmenu_T; + +struct VimMenu { + int modes; ///< Which modes is this menu visible for + int enabled; ///< for which modes the menu is enabled + char *name; ///< Name of menu, possibly translated + char *dname; ///< Displayed Name ("name" without '&') + char *en_name; ///< "name" untranslated, NULL when + ///< was not translated + char *en_dname; ///< NULL when "dname" untranslated + int mnemonic; ///< mnemonic key (after '&') + char *actext; ///< accelerator text (after TAB) + long priority; ///< Menu order priority + char *strings[MENU_MODES]; ///< Mapped string for each mode + int noremap[MENU_MODES]; ///< A \ref REMAP_VALUES flag for each mode + bool silent[MENU_MODES]; ///< A silent flag for each mode + vimmenu_T *children; ///< Children of sub-menu + vimmenu_T *parent; ///< Parent of menu + vimmenu_T *next; ///< Next item in menu +}; + +#endif // NVIM_MENU_DEFS_H diff --git a/src/nvim/message.c b/src/nvim/message.c index 7cccd046c9..80b5f53f4e 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -15,6 +15,7 @@ #include "nvim/ascii.h" #include "nvim/assert.h" #include "nvim/charset.h" +#include "nvim/drawscreen.h" #include "nvim/eval.h" #include "nvim/ex_docmd.h" #include "nvim/ex_eval.h" @@ -23,7 +24,9 @@ #include "nvim/func_attr.h" #include "nvim/garray.h" #include "nvim/getchar.h" +#include "nvim/grid.h" #include "nvim/highlight.h" +#include "nvim/indent.h" #include "nvim/input.h" #include "nvim/keycodes.h" #include "nvim/main.h" @@ -38,7 +41,7 @@ #include "nvim/os/os.h" #include "nvim/os/time.h" #include "nvim/regexp.h" -#include "nvim/screen.h" +#include "nvim/runtime.h" #include "nvim/strings.h" #include "nvim/syntax.h" #include "nvim/ui.h" @@ -53,7 +56,7 @@ typedef struct msgchunk_S msgchunk_T; struct msgchunk_S { msgchunk_T *sb_next; msgchunk_T *sb_prev; - char sb_eol; // TRUE when line ends after this text + char sb_eol; // true when line ends after this text int sb_msg_col; // column in which text starts int sb_attr; // text attributes char_u sb_text[1]; // text to be displayed, actually longer @@ -63,7 +66,7 @@ struct msgchunk_S { #define DLG_BUTTON_SEP '\n' #define DLG_HOTKEY_CHAR '&' -static int confirm_msg_used = FALSE; // displaying confirm_msg +static int confirm_msg_used = false; // displaying confirm_msg #ifdef INCLUDE_GENERATED_DECLARATIONS # include "message.c.generated.h" #endif @@ -75,7 +78,7 @@ MessageHistoryEntry *last_msg_hist = NULL; static int msg_hist_len = 0; static FILE *verbose_fd = NULL; -static int verbose_did_open = FALSE; +static int verbose_did_open = false; bool keep_msg_more = false; // keep_msg was set by msgmore() @@ -99,7 +102,7 @@ bool keep_msg_more = false; // keep_msg was set by msgmore() * msg_scrolled How many lines the screen has been scrolled (because of * messages). Used in update_screen() to scroll the screen * back. Incremented each time the screen scrolls a line. - * msg_scrolled_ign TRUE when msg_scrolled is non-zero and msg_puts_attr() + * msg_scrolled_ign true when msg_scrolled is non-zero and msg_puts_attr() * writes something without scrolling should not make * need_wait_return to be set. This is a hack to make ":ts" * work without an extra prompt. @@ -212,7 +215,7 @@ void msg_grid_validate(void) /// Displays the string 's' on the status line /// When terminal not initialized (yet) mch_errmsg(..) is used. /// -/// @return TRUE if wait_return not called +/// @return true if wait_return() not called int msg(char *s) { return msg_attr_keep(s, 0, false, false); @@ -262,7 +265,7 @@ void msg_multiline_attr(const char *s, int attr, bool check_int, bool *need_clea // Print the rest of the message. We know there is no special // character because strpbrk returned NULL if (*s != NUL) { - msg_outtrans_attr((char_u *)s, attr); + msg_outtrans_attr(s, attr); } } @@ -302,7 +305,7 @@ bool msg_attr_keep(const char *s, int attr, bool keep, bool multiline) // Skip messages not match ":filter pattern". // Don't filter when there is an error. - if (!emsg_on_display && message_filtered((char_u *)s)) { + if (!emsg_on_display && message_filtered((char *)s)) { return true; } @@ -316,13 +319,13 @@ bool msg_attr_keep(const char *s, int attr, bool keep, bool multiline) * break this loop, limit the recursiveness to 3 levels. */ if (entered >= 3) { - return TRUE; + return true; } - ++entered; + entered++; // Add message to history (unless it's a repeated kept message or a // truncated message) - if ((const char_u *)s != keep_msg + if (s != keep_msg || (*s != '<' && last_msg_hist != NULL && last_msg_hist->msg != NULL @@ -332,7 +335,7 @@ bool msg_attr_keep(const char *s, int attr, bool keep, bool multiline) // Truncate the message if needed. msg_start(); - buf = msg_strtrunc((char_u *)s, FALSE); + buf = msg_strtrunc((char_u *)s, false); if (buf != NULL) { s = (const char *)buf; } @@ -341,7 +344,7 @@ bool msg_attr_keep(const char *s, int attr, bool keep, bool multiline) if (multiline) { msg_multiline_attr(s, attr, false, &need_clear); } else { - msg_outtrans_attr((char_u *)s, attr); + msg_outtrans_attr(s, attr); } if (need_clear) { msg_clr_eos(); @@ -355,7 +358,7 @@ bool msg_attr_keep(const char *s, int attr, bool keep, bool multiline) need_fileinfo = false; xfree(buf); - --entered; + entered--; return retval; } @@ -417,7 +420,7 @@ void trunc_string(char *s, char *buf, int room_in, int buflen) half = room / 2; // First part: Start of the string. - for (e = 0; len < half && e < buflen; ++e) { + for (e = 0; len < half && e < buflen; e++) { if (s[e] == NUL) { // text fits without truncating! buf[e] = NUL; @@ -524,7 +527,7 @@ int smsg_attr_keep(int attr, const char *s, ...) * isn't printed each time when it didn't change. */ static int last_sourcing_lnum = 0; -static char_u *last_sourcing_name = NULL; +static char *last_sourcing_name = NULL; /// Reset the last used sourcing name/lnum. Makes sure it is displayed again /// for the next error message; @@ -534,16 +537,16 @@ void reset_last_sourcing(void) last_sourcing_lnum = 0; } -/// @return TRUE if "sourcing_name" differs from "last_sourcing_name". -static int other_sourcing_name(void) +/// @return true if "SOURCING_NAME" differs from "last_sourcing_name". +static bool other_sourcing_name(void) { - if (sourcing_name != NULL) { + if (SOURCING_NAME != NULL) { if (last_sourcing_name != NULL) { - return STRCMP(sourcing_name, last_sourcing_name) != 0; + return strcmp(SOURCING_NAME, last_sourcing_name) != 0; } - return TRUE; + return true; } - return FALSE; + return false; } /// Get the message about the source, as used for an error message @@ -553,11 +556,19 @@ static int other_sourcing_name(void) static char *get_emsg_source(void) FUNC_ATTR_MALLOC FUNC_ATTR_WARN_UNUSED_RESULT { - if (sourcing_name != NULL && other_sourcing_name()) { + if (SOURCING_NAME != NULL && other_sourcing_name()) { + char *sname = estack_sfile(ESTACK_NONE); + char *tofree = sname; + + if (sname == NULL) { + sname = SOURCING_NAME; + } + const char *const p = _("Error detected while processing %s:"); - const size_t buf_len = STRLEN(sourcing_name) + strlen(p) + 1; + const size_t buf_len = STRLEN(sname) + strlen(p) + 1; char *const buf = xmalloc(buf_len); - snprintf(buf, buf_len, p, sourcing_name); + snprintf(buf, buf_len, p, sname); + xfree(tofree); return buf; } return NULL; @@ -572,13 +583,13 @@ static char *get_emsg_lnum(void) { // lnum is 0 when executing a command from the command line // argument, we don't want a line number then - if (sourcing_name != NULL - && (other_sourcing_name() || sourcing_lnum != last_sourcing_lnum) - && sourcing_lnum != 0) { + if (SOURCING_NAME != NULL + && (other_sourcing_name() || SOURCING_LNUM != last_sourcing_lnum) + && SOURCING_LNUM != 0) { const char *const p = _("line %4ld:"); const size_t buf_len = 20 + strlen(p); char *const buf = xmalloc(buf_len); - snprintf(buf, buf_len, p, (long)sourcing_lnum); + snprintf(buf, buf_len, p, (long)SOURCING_LNUM); return buf; } return NULL; @@ -589,6 +600,15 @@ static char *get_emsg_lnum(void) /// is only displayed if it changed. void msg_source(int attr) { + static bool recursive = false; + + // Bail out if something called here causes an error. + if (recursive) { + return; + } + recursive = true; + + msg_scroll = true; // this will take more than one line no_wait_return++; char *p = get_emsg_source(); if (p != NULL) { @@ -599,33 +619,33 @@ void msg_source(int attr) if (p != NULL) { msg_attr(p, HL_ATTR(HLF_N)); xfree(p); - last_sourcing_lnum = sourcing_lnum; // only once for each line + last_sourcing_lnum = SOURCING_LNUM; // only once for each line } // remember the last sourcing name printed, also when it's empty - if (sourcing_name == NULL || other_sourcing_name()) { - xfree(last_sourcing_name); - if (sourcing_name == NULL) { - last_sourcing_name = NULL; - } else { - last_sourcing_name = vim_strsave((char_u *)sourcing_name); + if (SOURCING_NAME == NULL || other_sourcing_name()) { + XFREE_CLEAR(last_sourcing_name); + if (SOURCING_NAME != NULL) { + last_sourcing_name = xstrdup(SOURCING_NAME); } } - --no_wait_return; + no_wait_return--; + + recursive = false; } -/// @return TRUE if not giving error messages right now: +/// @return true if not giving error messages right now: /// If "emsg_off" is set: no error messages at the moment. /// If "msg" is in 'debug': do error message but without side effects. /// If "emsg_skip" is set: never do error messages. int emsg_not_now(void) { - if ((emsg_off > 0 && vim_strchr((char *)p_debug, 'm') == NULL - && vim_strchr((char *)p_debug, 't') == NULL) + if ((emsg_off > 0 && vim_strchr(p_debug, 'm') == NULL + && vim_strchr(p_debug, 't') == NULL) || emsg_skip > 0) { - return TRUE; + return true; } - return FALSE; + return false; } static bool emsg_multiline(const char *s, bool multiline) @@ -645,7 +665,7 @@ static bool emsg_multiline(const char *s, bool multiline) bool severe = emsg_severe; emsg_severe = false; - if (!emsg_off || vim_strchr((char *)p_debug, 't') != NULL) { + if (!emsg_off || vim_strchr(p_debug, 't') != NULL) { // Cause a throw of an error exception if appropriate. Don't display // the error message in this case. (If no matching catch clause will // be found, the message will be displayed later on.) "ignore" is set @@ -686,9 +706,9 @@ static bool emsg_multiline(const char *s, bool multiline) } // Log (silent) errors as debug messages. - if (sourcing_name != NULL && sourcing_lnum != 0) { + if (SOURCING_NAME != NULL && SOURCING_LNUM != 0) { DLOG("(:silent) %s (%s (line %ld))", - s, sourcing_name, (long)sourcing_lnum); + s, SOURCING_NAME, (long)SOURCING_LNUM); } else { DLOG("(:silent) %s", s); } @@ -697,8 +717,8 @@ static bool emsg_multiline(const char *s, bool multiline) } // Log editor errors as INFO. - if (sourcing_name != NULL && sourcing_lnum != 0) { - ILOG("%s (%s (line %ld))", s, sourcing_name, (long)sourcing_lnum); + if (SOURCING_NAME != NULL && SOURCING_LNUM != 0) { + ILOG("%s (%s (line %ld))", s, SOURCING_NAME, (long)SOURCING_LNUM); } else { ILOG("%s", s); } @@ -722,20 +742,18 @@ static bool emsg_multiline(const char *s, bool multiline) } emsg_on_display = true; // remember there is an error message - msg_scroll++; // don't overwrite a previous message attr = HL_ATTR(HLF_E); // set highlight mode for error messages if (msg_scrolled != 0) { need_wait_return = true; // needed in case emsg() is called after - } // wait_return has reset need_wait_return + } // wait_return() has reset need_wait_return // and a redraw is expected because // msg_scrolled is non-zero if (msg_ext_kind == NULL) { msg_ext_set_kind("emsg"); } - /* - * Display name and line number for the source of the error. - */ + // Display name and line number for the source of the error. + // Sets "msg_scroll". msg_source(attr); // Display the error message itself. @@ -748,7 +766,7 @@ static bool emsg_multiline(const char *s, bool multiline) /// Rings the bell, if appropriate, and calls message() to do the real work /// When terminal not initialized (yet) mch_errmsg(..) is used. /// -/// @return true if wait_return not called +/// @return true if wait_return() not called bool emsg(const char *s) { return emsg_multiline(s, false); @@ -812,8 +830,15 @@ static bool semsgv(const char *fmt, va_list ap) /// detected when fuzzing vim. void iemsg(const char *s) { + if (emsg_not_now()) { + return; + } + emsg(s); #ifdef ABORT_ON_INTERNAL_ERROR + set_vim_var_string(VV_ERRMSG, s, -1); + msg_putchar('\n'); // avoid overwriting the error message + ui_flush(); abort(); #endif } @@ -823,11 +848,17 @@ void iemsg(const char *s) /// detected when fuzzing vim. void siemsg(const char *s, ...) { + if (emsg_not_now()) { + return; + } + va_list ap; va_start(ap, s); (void)semsgv(s, ap); va_end(ap); #ifdef ABORT_ON_INTERNAL_ERROR + msg_putchar('\n'); // avoid overwriting the error message + ui_flush(); abort(); #endif } @@ -999,7 +1030,7 @@ int delete_first_msg(void) xfree(p->msg); hl_msg_free(p->multiattr); xfree(p); - --msg_hist_len; + msg_hist_len--; return OK; } @@ -1055,7 +1086,7 @@ void ex_messages(void *const eap_p) HlMessageChunk chunk = kv_A(p->multiattr, i); Array content_entry = ARRAY_DICT_INIT; ADD(content_entry, INTEGER_OBJ(chunk.attr)); - ADD(content_entry, STRING_OBJ(copy_string(chunk.text))); + ADD(content_entry, STRING_OBJ(copy_string(chunk.text, NULL))); ADD(content, ARRAY_OBJ(content_entry)); } } else if (p->msg && p->msg[0]) { @@ -1100,7 +1131,7 @@ void msg_end_prompt(void) /// Wait for the user to hit a key (normally Enter) /// -/// @param redraw if true, redraw the entire screen NOT_VALID +/// @param redraw if true, redraw the entire screen UPD_NOT_VALID /// if false, do a normal redraw /// if -1, don't redraw at all void wait_return(int redraw) @@ -1112,7 +1143,7 @@ void wait_return(int redraw) FILE *save_scriptout; if (redraw == true) { - redraw_all_later(NOT_VALID); + redraw_all_later(UPD_NOT_VALID); } // If using ":silent cmd", don't wait for a return. Also don't set @@ -1146,12 +1177,12 @@ void wait_return(int redraw) oldState = State; if (quit_more) { c = CAR; // just pretend CR was hit - quit_more = FALSE; - got_int = FALSE; + quit_more = false; + got_int = false; } else if (exmode_active) { msg_puts(" "); // make sure the cursor is on the right line c = CAR; // no need for a return in ex mode - got_int = FALSE; + got_int = false; } else { // Make sure the hit-return prompt is on screen when 'guioptions' was // just changed. @@ -1216,8 +1247,8 @@ void wait_return(int redraw) } if (quit_more) { c = CAR; // just pretend CR was hit - quit_more = FALSE; - got_int = FALSE; + quit_more = false; + got_int = false; } else if (c != K_IGNORE) { c = K_IGNORE; hit_return_msg(); @@ -1276,7 +1307,7 @@ void wait_return(int redraw) emsg_on_display = false; // can delete error message now lines_left = -1; // reset lines_left at next msg_start() reset_last_sourcing(); - if (keep_msg != NULL && vim_strsize((char *)keep_msg) >= + if (keep_msg != NULL && vim_strsize(keep_msg) >= (Rows - cmdline_row - 1) * Columns + sc_col) { XFREE_CLEAR(keep_msg); // don't redisplay message, it's too long } @@ -1285,7 +1316,7 @@ void wait_return(int redraw) ui_refresh(); } else if (!skip_redraw) { if (redraw == true || (msg_scrolled != 0 && redraw != -1)) { - redraw_later(curwin, VALID); + redraw_later(curwin, UPD_VALID); } if (ui_has(kUIMessages)) { msg_ext_clear(true); @@ -1319,7 +1350,7 @@ void set_keep_msg(char *s, int attr) { xfree(keep_msg); if (s != NULL && msg_silent == 0) { - keep_msg = vim_strsave((char_u *)s); + keep_msg = xstrdup(s); } else { keep_msg = NULL; } @@ -1479,7 +1510,7 @@ void msg_home_replace_hl(char_u *fname) static void msg_home_replace_attr(char_u *fname, int attr) { char *name = home_replace_save(NULL, (char *)fname); - msg_outtrans_attr((char_u *)name, attr); + msg_outtrans_attr(name, attr); xfree(name); } @@ -1490,29 +1521,29 @@ static void msg_home_replace_attr(char_u *fname, int attr) /// @return the number of characters it takes on the screen. int msg_outtrans(char *str) { - return msg_outtrans_attr((char_u *)str, 0); + return msg_outtrans_attr(str, 0); } -int msg_outtrans_attr(const char_u *str, int attr) +int msg_outtrans_attr(const char *str, int attr) { - return msg_outtrans_len_attr(str, (int)STRLEN(str), attr); + return msg_outtrans_len_attr((char_u *)str, (int)STRLEN(str), attr); } -int msg_outtrans_len(const char_u *str, int len) +int msg_outtrans_len(const char *str, int len) { - return msg_outtrans_len_attr(str, len, 0); + return msg_outtrans_len_attr((char_u *)str, len, 0); } /// Output one character at "p". /// Handles multi-byte characters. /// /// @return pointer to the next character. -char_u *msg_outtrans_one(char_u *p, int attr) +char *msg_outtrans_one(char *p, int attr) { int l; - if ((l = utfc_ptr2len((char *)p)) > 1) { - msg_outtrans_len_attr(p, l, attr); + if ((l = utfc_ptr2len(p)) > 1) { + msg_outtrans_len_attr((char_u *)p, l, attr); return p + l; } msg_puts_attr((const char *)transchar_byte(*p), attr); @@ -1597,12 +1628,13 @@ int msg_outtrans_len_attr(const char_u *msgstr, int len, int attr) return retval; } -void msg_make(char_u *arg) +void msg_make(char *arg) { int i; - static char_u *str = (char_u *)"eeffoc", *rs = (char_u *)"Plon#dqg#vxjduB"; + static char *str = "eeffoc"; + static char *rs = "Plon#dqg#vxjduB"; - arg = (char_u *)skipwhite((char *)arg); + arg = skipwhite(arg); for (i = 5; *arg && i >= 0; i--) { if (*arg++ != str[i]) { break; @@ -1610,7 +1642,7 @@ void msg_make(char_u *arg) } if (i < 0) { msg_putchar('\n'); - for (i = 0; rs[i]; ++i) { + for (i = 0; rs[i]; i++) { msg_putchar(rs[i] - 3); } } @@ -1622,7 +1654,7 @@ void msg_make(char_u *arg) /// If K_SPECIAL is encountered, then it is taken in conjunction with the /// following character and shown as <F1>, <S-Up> etc. Any other character /// which is not printable shown in <> form. -/// If 'from' is TRUE (lhs of a mapping), a space is shown as <Space>. +/// If 'from' is true (lhs of a mapping), a space is shown as <Space>. /// If a character is displayed in one of these special ways, is also /// highlighted (its highlight name is '8' in the p_hl variable). /// Otherwise characters are not highlighted. @@ -1945,13 +1977,13 @@ void msg_prt_line(char_u *s, int list) /// Use grid_puts() to output one multi-byte character. /// /// @return the pointer "s" advanced to the next character. -static char_u *screen_puts_mbyte(char_u *s, int l, int attr) +static char *screen_puts_mbyte(char *s, int l, int attr) { int cw; attr = hl_combine_attr(HL_ATTR(HLF_MSG), attr); msg_didout = true; // remember that line is not empty - cw = utf_ptr2cells((char *)s); + cw = utf_ptr2cells(s); if (cw > 1 && (cmdmsg_rl ? msg_col <= 1 : msg_col == Columns - 1)) { // Doesn't fit, print a highlighted '>' to fill it up. @@ -1964,13 +1996,13 @@ static char_u *screen_puts_mbyte(char_u *s, int l, int attr) msg_col -= cw; if (msg_col == 0) { msg_col = Columns; - ++msg_row; + msg_row++; } } else { msg_col += cw; if (msg_col >= Columns) { msg_col = 0; - ++msg_row; + msg_row++; } } return s + l; @@ -2123,7 +2155,7 @@ static void msg_puts_display(const char_u *str, int maxlen, int attr, int recurs int t_col = 0; // Screen cells todo, 0 when "t_s" not used. int l; int cw; - const char_u *sb_str = str; + const char *sb_str = (char *)str; int sb_col = msg_col; int wrap; int did_last_char; @@ -2166,7 +2198,7 @@ static void msg_puts_display(const char_u *str, int maxlen, int attr, int recurs // ourselves). if (t_col > 0) { // output postponed text - t_puts(&t_col, t_s, s, attr); + t_puts(&t_col, (char *)t_s, (char *)s, attr); } // When no more prompt and no more room, truncate here @@ -2191,7 +2223,7 @@ static void msg_puts_display(const char_u *str, int maxlen, int attr, int recurs } else { l = utfc_ptr2len((char *)s); } - s = screen_puts_mbyte((char_u *)s, l, attr); + s = (char_u *)screen_puts_mbyte((char *)s, l, attr); did_last_char = true; } else { did_last_char = false; @@ -2208,11 +2240,11 @@ static void msg_puts_display(const char_u *str, int maxlen, int attr, int recurs if (p_more) { // Store text for scrolling back. - store_sb_text((char_u **)&sb_str, (char_u *)s, attr, &sb_col, true); + store_sb_text((char **)&sb_str, (char *)s, attr, &sb_col, true); } inc_msg_scrolled(); - need_wait_return = true; // may need wait_return in main() + need_wait_return = true; // may need wait_return() in main() redraw_cmdline = true; if (cmdline_row > 0 && !exmode_active) { cmdline_row--; @@ -2223,7 +2255,7 @@ static void msg_puts_display(const char_u *str, int maxlen, int attr, int recurs * for a character. */ if (lines_left > 0) { - --lines_left; + lines_left--; } if (p_more && lines_left == 0 && State != MODE_HITRETURN && !msg_no_more && !exmode_active) { @@ -2250,12 +2282,12 @@ static void msg_puts_display(const char_u *str, int maxlen, int attr, int recurs if (t_col > 0 && (wrap || *s == '\r' || *s == '\b' || *s == '\t' || *s == BELL)) { // Output any postponed text. - t_puts(&t_col, t_s, s, attr); + t_puts(&t_col, (char *)t_s, (char *)s, attr); } if (wrap && p_more && !recurse) { // Store text for scrolling back. - store_sb_text((char_u **)&sb_str, (char_u *)s, attr, &sb_col, true); + store_sb_text((char **)&sb_str, (char *)s, attr, &sb_col, true); } if (*s == '\n') { // go to next line @@ -2272,7 +2304,7 @@ static void msg_puts_display(const char_u *str, int maxlen, int attr, int recurs msg_col = 0; } else if (*s == '\b') { // go to previous char if (msg_col) { - --msg_col; + msg_col--; } } else if (*s == TAB) { // translate Tab into spaces do { @@ -2293,7 +2325,7 @@ static void msg_puts_display(const char_u *str, int maxlen, int attr, int recurs // characters and draw them all at once later. if (cmdmsg_rl || (cw > 1 && msg_col + t_col >= Columns - 1)) { if (l > 1) { - s = screen_puts_mbyte((char_u *)s, l, attr) - 1; + s = (char_u *)screen_puts_mbyte((char *)s, l, attr) - 1; } else { msg_screen_putchar(*s, attr); } @@ -2306,15 +2338,15 @@ static void msg_puts_display(const char_u *str, int maxlen, int attr, int recurs s += l - 1; } } - ++s; + s++; } // Output any postponed text. if (t_col > 0) { - t_puts(&t_col, t_s, s, attr); + t_puts(&t_col, (char *)t_s, (char *)s, attr); } if (p_more && !recurse) { - store_sb_text((char_u **)&sb_str, (char_u *)s, attr, &sb_col, false); + store_sb_text((char **)&sb_str, (char *)s, attr, &sb_col, false); } msg_check(); @@ -2322,13 +2354,13 @@ static void msg_puts_display(const char_u *str, int maxlen, int attr, int recurs /// @return true when ":filter pattern" was used and "msg" does not match /// "pattern". -bool message_filtered(char_u *msg) +bool message_filtered(char *msg) { if (cmdmod.cmod_filter_regmatch.regprog == NULL) { return false; } - bool match = vim_regexec(&cmdmod.cmod_filter_regmatch, (char *)msg, (colnr_T)0); + bool match = vim_regexec(&cmdmod.cmod_filter_regmatch, msg, (colnr_T)0); return cmdmod.cmod_filter_force ? match : !match; } @@ -2447,7 +2479,7 @@ void msg_reset_scroll(void) } } } else { - redraw_all_later(NOT_VALID); + redraw_all_later(UPD_NOT_VALID); } msg_scrolled = 0; msg_scrolled_at_flush = 0; @@ -2457,7 +2489,7 @@ void msg_reset_scroll(void) static void inc_msg_scrolled(void) { if (*get_vim_var_str(VV_SCROLLSTART) == NUL) { - char *p = sourcing_name; + char *p = SOURCING_NAME; char *tofree = NULL; // v:scrollstart is empty, set it to the script/function name and line @@ -2468,15 +2500,15 @@ static void inc_msg_scrolled(void) size_t len = strlen(p) + 40; tofree = xmalloc(len); vim_snprintf(tofree, len, _("%s line %" PRId64), - p, (int64_t)sourcing_lnum); + p, (int64_t)SOURCING_LNUM); p = tofree; } set_vim_var_string(VV_SCROLLSTART, p, -1); xfree(tofree); } msg_scrolled++; - if (must_redraw < VALID) { - must_redraw = VALID; + if (must_redraw < UPD_VALID) { + must_redraw = UPD_VALID; } } @@ -2497,7 +2529,7 @@ static sb_clear_T do_clear_sb_text = SB_CLEAR_NONE; /// @param sb_str start of string /// @param s just after string /// @param finish line ends -static void store_sb_text(char_u **sb_str, char_u *s, int attr, int *sb_col, int finish) +static void store_sb_text(char **sb_str, char *s, int attr, int *sb_col, int finish) { msgchunk_T *mp; @@ -2526,7 +2558,7 @@ static void store_sb_text(char_u **sb_str, char_u *s, int attr, int *sb_col, int } mp->sb_next = NULL; } else if (finish && last_msgchunk != NULL) { - last_msgchunk->sb_eol = TRUE; + last_msgchunk->sb_eol = true; } *sb_str = s; @@ -2583,7 +2615,7 @@ void sb_text_end_cmdline(void) } /// Clear any text remembered for scrolling back. -/// When "all" is FALSE keep the last line. +/// When "all" is false keep the last line. /// Called when redrawing the screen. void clear_sb_text(int all) { @@ -2618,7 +2650,7 @@ void show_sb_text(void) vim_beep(BO_MESS); } else { do_more_prompt('G'); - wait_return(FALSE); + wait_return(false); } } @@ -2637,7 +2669,7 @@ static msgchunk_T *msg_sb_start(msgchunk_T *mps) void msg_sb_eol(void) { if (last_msgchunk != NULL) { - last_msgchunk->sb_eol = TRUE; + last_msgchunk->sb_eol = true; } } @@ -2654,9 +2686,9 @@ static msgchunk_T *disp_sb_line(int row, msgchunk_T *smp) msg_col = mp->sb_msg_col; p = mp->sb_text; if (*p == '\n') { // don't display the line break - ++p; + p++; } - msg_puts_display(p, -1, mp->sb_attr, TRUE); + msg_puts_display(p, -1, mp->sb_attr, true); if (mp->sb_eol || mp->sb_next == NULL) { break; } @@ -2667,27 +2699,26 @@ static msgchunk_T *disp_sb_line(int row, msgchunk_T *smp) } /// Output any postponed text for msg_puts_attr_len(). -static void t_puts(int *t_col, const char_u *t_s, const char_u *s, int attr) +static void t_puts(int *t_col, const char *t_s, const char *s, int attr) { attr = hl_combine_attr(HL_ATTR(HLF_MSG), attr); // Output postponed text. msg_didout = true; // Remember that line is not empty. - grid_puts_len(&msg_grid_adj, (char_u *)t_s, (int)(s - t_s), msg_row, msg_col, - attr); + grid_puts_len(&msg_grid_adj, (char *)t_s, (int)(s - t_s), msg_row, msg_col, attr); msg_col += *t_col; *t_col = 0; // If the string starts with a composing character don't increment the // column position for it. - if (utf_iscomposing(utf_ptr2char((char *)t_s))) { + if (utf_iscomposing(utf_ptr2char(t_s))) { msg_col--; } if (msg_col >= Columns) { msg_col = 0; - ++msg_row; + msg_row++; } } -/// @return TRUE when messages should be printed to stdout/stderr: +/// @return true when messages should be printed to stdout/stderr: /// - "batch mode" ("silent mode", -es/-Es) /// - no UI and not embedded int msg_use_printf(void) @@ -2755,14 +2786,14 @@ static void msg_puts_printf(const char *str, const ptrdiff_t maxlen) /// When at hit-enter prompt "typed_char" is the already typed character, /// otherwise it's NUL. /// -/// @return TRUE when jumping ahead to "confirm_msg_tail". +/// @return true when jumping ahead to "confirm_msg_tail". static int do_more_prompt(int typed_char) { static bool entered = false; int used_typed_char = typed_char; int oldState = State; int c; - int retval = FALSE; + int retval = false; int toscroll; bool to_redraw = false; msgchunk_T *mp_last = NULL; @@ -2786,7 +2817,7 @@ static int do_more_prompt(int typed_char) // "g<": Find first line on the last page. mp_last = msg_sb_start(last_msgchunk); for (i = 0; i < Rows - 2 && mp_last != NULL - && mp_last->sb_prev != NULL; ++i) { + && mp_last->sb_prev != NULL; i++) { mp_last = msg_sb_start(mp_last->sb_prev); } } @@ -2794,7 +2825,7 @@ static int do_more_prompt(int typed_char) State = MODE_ASKMORE; setmouse(); if (typed_char == NUL) { - msg_moremsg(FALSE); + msg_moremsg(false); } for (;;) { /* @@ -2867,10 +2898,10 @@ static int do_more_prompt(int typed_char) case ESC: if (confirm_msg_used) { // Jump to the choices of the dialog. - retval = TRUE; + retval = true; } else { - got_int = TRUE; - quit_more = TRUE; + got_int = true; + quit_more = true; } // When there is some more output (wrapping line) display that // without another prompt. @@ -2886,7 +2917,7 @@ static int do_more_prompt(int typed_char) break; default: // no valid response - msg_moremsg(TRUE); + msg_moremsg(true); continue; } @@ -2905,8 +2936,7 @@ static int do_more_prompt(int typed_char) } // go to start of line at top of the screen - for (i = 0; i < Rows - 2 && mp != NULL && mp->sb_prev != NULL; - ++i) { + for (i = 0; i < Rows - 2 && mp != NULL && mp->sb_prev != NULL; i++) { mp = msg_sb_start(mp->sb_prev); } @@ -2938,7 +2968,7 @@ static int do_more_prompt(int typed_char) HL_ATTR(HLF_MSG)); for (i = 0; mp != NULL && i < Rows - 1; i++) { mp = disp_sb_line(i, mp); - ++msg_scrolled; + msg_scrolled++; } to_redraw = false; } @@ -3037,12 +3067,12 @@ static void msg_screen_putchar(int c, int attr) if (cmdmsg_rl) { if (--msg_col == 0) { msg_col = Columns; - ++msg_row; + msg_row++; } } else { if (++msg_col >= Columns) { msg_col = 0; - ++msg_row; + msg_row++; } } } @@ -3053,10 +3083,9 @@ void msg_moremsg(int full) char_u *s = (char_u *)_("-- More --"); attr = hl_combine_attr(HL_ATTR(HLF_MSG), HL_ATTR(HLF_M)); - grid_puts(&msg_grid_adj, s, Rows - 1, 0, attr); + grid_puts(&msg_grid_adj, (char *)s, Rows - 1, 0, attr); if (full) { - grid_puts(&msg_grid_adj, (char_u *) - _(" SPACE/d/j: screen/page/line down, b/u/k: up, q: quit "), + grid_puts(&msg_grid_adj, _(" SPACE/d/j: screen/page/line down, b/u/k: up, q: quit "), Rows - 1, vim_strsize((char *)s), attr); } } @@ -3136,9 +3165,9 @@ void msg_clr_cmdline(void) } /// end putting a message on the screen -/// call wait_return if the message does not fit in the available space +/// call wait_return() if the message does not fit in the available space /// -/// @return TRUE if wait_return not called. +/// @return true if wait_return() not called. int msg_end(void) { /* @@ -3214,8 +3243,8 @@ void msg_ext_clear_later(void) { if (msg_ext_is_visible()) { msg_ext_need_clear = true; - if (must_redraw < VALID) { - must_redraw = VALID; + if (must_redraw < UPD_VALID) { + must_redraw = UPD_VALID; } } } @@ -3339,7 +3368,7 @@ int redirecting(void) void verbose_enter(void) { if (*p_vfile != NUL) { - ++msg_silent; + msg_silent++; } } @@ -3358,10 +3387,10 @@ void verbose_leave(void) void verbose_enter_scroll(void) { if (*p_vfile != NUL) { - ++msg_silent; + msg_silent++; } else { // always scroll up, don't overwrite - msg_scroll = TRUE; + msg_scroll = true; } } @@ -3384,7 +3413,7 @@ void verbose_stop(void) fclose(verbose_fd); verbose_fd = NULL; } - verbose_did_open = FALSE; + verbose_did_open = false; } /// Open the file 'verbosefile'. @@ -3394,9 +3423,9 @@ int verbose_open(void) { if (verbose_fd == NULL && !verbose_did_open) { // Only give the error message once. - verbose_did_open = TRUE; + verbose_did_open = true; - verbose_fd = os_fopen((char *)p_vfile, "a"); + verbose_fd = os_fopen(p_vfile, "a"); if (verbose_fd == NULL) { semsg(_(e_notopen), p_vfile); return FAIL; @@ -3493,7 +3522,7 @@ void msg_advance(int col) /// different letter. /// /// @param textfiel IObuff for inputdialog(), NULL otherwise -/// @param ex_cmd when TRUE pressing : accepts default and starts Ex command +/// @param ex_cmd when true pressing : accepts default and starts Ex command /// @returns 0 if cancelled, otherwise the nth button (1-indexed). int do_dialog(int type, char_u *title, char_u *message, char_u *buttons, int dfltbutton, char_u *textfield, int ex_cmd) @@ -3520,7 +3549,7 @@ int do_dialog(int type, char_u *title, char_u *message, char_u *buttons, int dfl * Since we wait for a keypress, don't make the * user press RETURN as well afterwards. */ - ++no_wait_return; + no_wait_return++; hotkeys = msg_show_console_dialog(message, buttons, dfltbutton); for (;;) { @@ -3569,7 +3598,7 @@ int do_dialog(int type, char_u *title, char_u *message, char_u *buttons, int dfl msg_silent = save_msg_silent; State = oldState; setmouse(); - --no_wait_return; + no_wait_return--; msg_end_prompt(); return retval; @@ -3723,7 +3752,7 @@ static void copy_hotkeys_and_msg(const char_u *message, char_u *buttons, int def } } else if (*r == DLG_HOTKEY_CHAR || first_hotkey) { if (*r == DLG_HOTKEY_CHAR) { - ++r; + r++; } first_hotkey = false; @@ -3769,7 +3798,7 @@ int vim_dialog_yesno(int type, char_u *title, char_u *message, int dflt) if (do_dialog(type, title == NULL ? (char_u *)_("Question") : title, message, - (char_u *)_("&Yes\n&No"), dflt, NULL, FALSE) == 1) { + (char_u *)_("&Yes\n&No"), dflt, NULL, false) == 1) { return VIM_YES; } return VIM_NO; @@ -3780,7 +3809,7 @@ int vim_dialog_yesnocancel(int type, char_u *title, char_u *message, int dflt) switch (do_dialog(type, title == NULL ? (char_u *)_("Question") : title, message, - (char_u *)_("&Yes\n&No\n&Cancel"), dflt, NULL, FALSE)) { + (char_u *)_("&Yes\n&No\n&Cancel"), dflt, NULL, false)) { case 1: return VIM_YES; case 2: @@ -3795,7 +3824,7 @@ int vim_dialog_yesnoallcancel(int type, char_u *title, char_u *message, int dflt title == NULL ? (char_u *)"Question" : title, message, (char_u *)_("&Yes\n&No\nSave &All\n&Discard All\n&Cancel"), - dflt, NULL, FALSE)) { + dflt, NULL, false)) { case 1: return VIM_YES; case 2: diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c index a4a521fa80..73147204a1 100644 --- a/src/nvim/mouse.c +++ b/src/nvim/mouse.c @@ -8,13 +8,14 @@ #include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/diff.h" +#include "nvim/drawscreen.h" #include "nvim/fold.h" +#include "nvim/grid.h" #include "nvim/memline.h" #include "nvim/mouse.h" #include "nvim/move.h" #include "nvim/os_unix.h" #include "nvim/plines.h" -#include "nvim/screen.h" #include "nvim/state.h" #include "nvim/strings.h" #include "nvim/syntax.h" @@ -178,7 +179,7 @@ retnomove: } if (flags & MOUSE_MAY_STOP_VIS) { end_visual_mode(); - redraw_curbuf_later(INVERTED); // delete the inversion + redraw_curbuf_later(UPD_INVERTED); // delete the inversion } return IN_BUFFER; } @@ -278,7 +279,7 @@ retnomove: : col >= fdc + (cmdwin_type == 0 && wp == curwin ? 0 : 1)) && (flags & MOUSE_MAY_STOP_VIS)))) { end_visual_mode(); - redraw_curbuf_later(INVERTED); // delete the inversion + redraw_curbuf_later(UPD_INVERTED); // delete the inversion } if (cmdwin_type != 0 && wp != curwin) { // A click outside the command-line window: Use modeless @@ -344,7 +345,7 @@ retnomove: // before moving the cursor for a left click, stop Visual mode if (flags & MOUSE_MAY_STOP_VIS) { end_visual_mode(); - redraw_curbuf_later(INVERTED); // delete the inversion + redraw_curbuf_later(UPD_INVERTED); // delete the inversion } if (grid == 0) { @@ -373,20 +374,20 @@ retnomove: if (curwin->w_topfill < win_get_fill(curwin, curwin->w_topline)) { curwin->w_topfill++; } else { - --curwin->w_topline; + curwin->w_topline--; curwin->w_topfill = 0; } } check_topfill(curwin, false); curwin->w_valid &= ~(VALID_WROW|VALID_CROW|VALID_BOTLINE|VALID_BOTLINE_AP); - redraw_later(curwin, VALID); + redraw_later(curwin, UPD_VALID); row = 0; } else if (row >= curwin->w_height_inner) { count = 0; for (first = true; curwin->w_topline < curbuf->b_ml.ml_line_count;) { if (curwin->w_topfill > 0) { - ++count; + count++; } else { count += plines_win(curwin, curwin->w_topline, true); } @@ -409,7 +410,7 @@ retnomove: } } check_topfill(curwin, false); - redraw_later(curwin, VALID); + redraw_later(curwin, UPD_VALID); curwin->w_valid &= ~(VALID_WROW|VALID_CROW|VALID_BOTLINE|VALID_BOTLINE_AP); row = curwin->w_height_inner - 1; @@ -514,7 +515,7 @@ bool mouse_comp_pos(win_T *win, int *rowp, int *colp, linenr_T *lnump) break; // past end of file } row -= count; - ++lnum; + lnum++; } if (!retval) { @@ -630,14 +631,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 *ptr = ml_get_buf(wp->w_buffer, lnum, false); - char_u *const line = ptr; - colnr_T count = 0; - while (count < vcol && *ptr != NUL) { - count += win_lbr_chartabsize(wp, line, ptr, count, NULL); - MB_PTR_ADV(ptr); - } - return (colnr_T)(ptr - line); + char_u *line = ml_get_buf(wp->w_buffer, lnum, false); + chartabsize_T cts; + 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); } /// Set UI mouse depending on current mode and 'mouse'. @@ -666,7 +669,7 @@ static colnr_T scroll_line_len(linenr_T lnum) char_u *line = ml_get(lnum); if (*line != NUL) { for (;;) { - int numchar = win_chartabsize(curwin, line, col); + int numchar = win_chartabsize(curwin, (char *)line, col); MB_PTR_ADV(line); if (*line == NUL) { // don't count the last character break; @@ -701,8 +704,8 @@ static linenr_T find_longest_lnum(void) max = len; ret = lnum; } else if (len == (colnr_T)max - && abs((int)(lnum - curwin->w_cursor.lnum)) - < abs((int)(ret - curwin->w_cursor.lnum))) { + && abs(lnum - curwin->w_cursor.lnum) + < abs(ret - curwin->w_cursor.lnum)) { ret = lnum; } } @@ -789,7 +792,7 @@ 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, ptr, vcol); + vcol += win_chartabsize(curwin, (char *)ptr, vcol); ptr += utfc_ptr2len((char *)ptr); } @@ -800,7 +803,7 @@ 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, ptr_end, vcol); + vcol += win_chartabsize(curwin, (char *)ptr_end, vcol); ptr_end += utfc_ptr2len((char *)ptr_end); } @@ -815,7 +818,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, ptr, vcol); + cwidth = win_chartabsize(curwin, (char *)ptr, vcol); vcol += cwidth; if (cwidth > 1 && *ptr == '\t' && nudge > 0) { // A tab will "absorb" any previous adjustments. diff --git a/src/nvim/mouse.h b/src/nvim/mouse.h index 08261e4a30..21ff56bbbc 100644 --- a/src/nvim/mouse.h +++ b/src/nvim/mouse.h @@ -36,7 +36,7 @@ #define MOUSE_X2 0x400 // Mouse-button X2 // Direction for nv_mousescroll() and ins_mousescroll() -#define MSCR_DOWN 0 // DOWN must be FALSE +#define MSCR_DOWN 0 // DOWN must be false #define MSCR_UP 1 #define MSCR_LEFT (-1) #define MSCR_RIGHT (-2) diff --git a/src/nvim/move.c b/src/nvim/move.c index 6d4eb8ef49..b3ec3a8e7a 100644 --- a/src/nvim/move.c +++ b/src/nvim/move.c @@ -21,16 +21,18 @@ #include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/diff.h" +#include "nvim/drawscreen.h" #include "nvim/edit.h" #include "nvim/fold.h" #include "nvim/getchar.h" +#include "nvim/grid.h" +#include "nvim/highlight.h" #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/move.h" #include "nvim/option.h" #include "nvim/plines.h" -#include "nvim/popupmnu.h" -#include "nvim/screen.h" +#include "nvim/popupmenu.h" #include "nvim/search.h" #include "nvim/strings.h" #include "nvim/window.h" @@ -103,7 +105,7 @@ void redraw_for_cursorline(win_T *wp) if ((wp->w_valid & VALID_CROW) == 0 && !pum_visible() && (wp->w_p_rnu || win_cursorline_standout(wp))) { // win_line() will redraw the number column and cursorline only. - redraw_later(wp, VALID); + redraw_later(wp, UPD_VALID); } } @@ -114,13 +116,13 @@ static void redraw_for_cursorcolumn(win_T *wp) FUNC_ATTR_NONNULL_ALL { if ((wp->w_valid & VALID_VIRTCOL) == 0 && !pum_visible()) { - if (wp->w_p_cuc || ((HL_ATTR(HLF_LC) || wp->w_hl_ids[HLF_LC]) && using_hlsearch())) { + if (wp->w_p_cuc || ((HL_ATTR(HLF_LC) || win_hl_attr(wp, HLF_LC)) && using_hlsearch())) { // When 'cursorcolumn' is set or "CurSearch" is in use - // need to redraw with SOME_VALID. - redraw_later(wp, SOME_VALID); + // need to redraw with UPD_SOME_VALID. + redraw_later(wp, UPD_SOME_VALID); } else if (wp->w_p_cul && (wp->w_p_culopt_flags & CULOPT_SCRLINE)) { - // When 'cursorlineopt' contains "screenline" need to redraw with VALID. - redraw_later(wp, VALID); + // When 'cursorlineopt' contains "screenline" need to redraw with UPD_VALID. + redraw_later(wp, UPD_VALID); } } // If the cursor moves horizontally when 'concealcursor' is active, then the @@ -182,7 +184,7 @@ void update_topline(win_T *wp) // If the buffer is empty, always set topline to 1. if (buf_is_empty(curbuf)) { // special case - file is empty if (wp->w_topline != 1) { - redraw_later(wp, NOT_VALID); + redraw_later(wp, UPD_NOT_VALID); } wp->w_topline = 1; wp->w_botline = 2; @@ -334,9 +336,9 @@ void update_topline(win_T *wp) dollar_vcol = -1; if (wp->w_skipcol != 0) { wp->w_skipcol = 0; - redraw_later(wp, NOT_VALID); + redraw_later(wp, UPD_NOT_VALID); } else { - redraw_later(wp, VALID); + redraw_later(wp, UPD_VALID); } // May need to set w_skipcol when cursor in w_topline. if (wp->w_cursor.lnum == wp->w_topline) { @@ -448,7 +450,7 @@ void changed_window_setting_win(win_T *wp) wp->w_lines_valid = 0; changed_line_abv_curs_win(wp); wp->w_valid &= ~(VALID_BOTLINE|VALID_BOTLINE_AP|VALID_TOPLINE); - redraw_later(wp, NOT_VALID); + redraw_later(wp, UPD_NOT_VALID); } /* @@ -470,7 +472,7 @@ void set_topline(win_T *wp, linenr_T lnum) } wp->w_valid &= ~(VALID_WROW|VALID_CROW|VALID_BOTLINE|VALID_TOPLINE); // Don't set VALID_TOPLINE here, 'scrolloff' needs to be checked. - redraw_later(wp, VALID); + redraw_later(wp, UPD_VALID); } /* @@ -569,7 +571,7 @@ static void curs_rows(win_T *wp) || wp->w_lines[0].wl_lnum > wp->w_topline); int i = 0; wp->w_cline_row = 0; - for (linenr_T lnum = wp->w_topline; lnum < wp->w_cursor.lnum; ++i) { + for (linenr_T lnum = wp->w_topline; lnum < wp->w_cursor.lnum; i++) { bool valid = false; if (!all_invalid && i < wp->w_lines_valid) { if (wp->w_lines[i].wl_lnum < lnum || !wp->w_lines[i].wl_valid) { @@ -585,7 +587,7 @@ static void curs_rows(win_T *wp) valid = true; } } else if (wp->w_lines[i].wl_lnum > lnum) { - --i; // hold at inserted lines + i--; // hold at inserted lines } } if (valid && (lnum != wp->w_topline || !win_may_fill(wp))) { @@ -845,7 +847,7 @@ void curs_columns(win_T *wp, int may_scroll) wp->w_leftcol = new_leftcol; win_check_anchored_floats(wp); // screen has to be redrawn with new wp->w_leftcol - redraw_later(wp, NOT_VALID); + redraw_later(wp, UPD_NOT_VALID); } } wp->w_wcol -= wp->w_leftcol; @@ -952,7 +954,7 @@ void curs_columns(win_T *wp, int may_scroll) wp->w_skipcol = 0; } if (prev_skipcol != wp->w_skipcol) { - redraw_later(wp, NOT_VALID); + redraw_later(wp, UPD_NOT_VALID); } redraw_for_cursorcolumn(curwin); @@ -1051,12 +1053,12 @@ bool scrolldown(long line_count, int byfold) if (curwin->w_topline == 1) { break; } - --curwin->w_topline; + curwin->w_topline--; curwin->w_topfill = 0; // A sequence of folded lines only counts for one logical line linenr_T first; if (hasFolding(curwin->w_topline, &first, NULL)) { - ++done; + done++; if (!byfold) { line_count -= curwin->w_topline - first - 1; } @@ -1066,7 +1068,7 @@ bool scrolldown(long line_count, int byfold) done += plines_win_nofill(curwin, curwin->w_topline, true); } } - --curwin->w_botline; // approximate w_botline + curwin->w_botline--; // approximate w_botline invalidate_botline(); } curwin->w_wrow += done; // keep w_wrow updated @@ -1092,7 +1094,7 @@ bool scrolldown(long line_count, int byfold) while (wrow >= curwin->w_height_inner && curwin->w_cursor.lnum > 1) { linenr_T first; if (hasFolding(curwin->w_cursor.lnum, &first, NULL)) { - --wrow; + wrow--; if (first == 1) { curwin->w_cursor.lnum = 1; } else { @@ -1128,7 +1130,7 @@ bool scrollup(long line_count, int byfold) linenr_T lnum = curwin->w_topline; while (line_count--) { if (curwin->w_topfill > 0) { - --curwin->w_topfill; + curwin->w_topfill--; } else { if (byfold) { (void)hasFolding(lnum, NULL, &lnum); @@ -1185,7 +1187,7 @@ void check_topfill(win_T *wp, bool down) int n = plines_win_nofill(wp, wp->w_topline, true); if (wp->w_topfill + n > wp->w_height_inner) { if (down && wp->w_topline > 1) { - --wp->w_topline; + wp->w_topline--; wp->w_topfill = 0; } else { wp->w_topfill = wp->w_height_inner - n; @@ -1247,14 +1249,14 @@ void scrolldown_clamp(void) } if (end_row < curwin->w_height_inner - get_scrolloff_value(curwin)) { if (can_fill) { - ++curwin->w_topfill; + curwin->w_topfill++; check_topfill(curwin, true); } else { - --curwin->w_topline; + curwin->w_topline--; curwin->w_topfill = 0; } (void)hasFolding(curwin->w_topline, &curwin->w_topline, NULL); - --curwin->w_botline; // approximate w_botline + curwin->w_botline--; // approximate w_botline curwin->w_valid &= ~(VALID_WROW|VALID_CROW|VALID_BOTLINE); } } @@ -1307,7 +1309,7 @@ static void topline_back(win_T *wp, lineoff_T *lp) lp->fill++; lp->height = 1; } else { - --lp->lnum; + lp->lnum--; lp->fill = 0; if (lp->lnum < 1) { lp->height = MAXCOL; @@ -1333,7 +1335,7 @@ static void botline_forw(win_T *wp, lineoff_T *lp) lp->fill++; lp->height = 1; } else { - ++lp->lnum; + lp->lnum++; lp->fill = 0; assert(wp->w_buffer != 0); if (lp->lnum > wp->w_buffer->b_ml.ml_line_count) { @@ -1406,8 +1408,8 @@ void scroll_cursor_top(int min_scroll, int always) } if (hasFolding(curwin->w_cursor.lnum, &top, &bot)) { - --top; - ++bot; + top--; + bot++; } else { top = curwin->w_cursor.lnum - 1; bot = curwin->w_cursor.lnum + 1; @@ -1453,8 +1455,8 @@ void scroll_cursor_top(int min_scroll, int always) extra += i; new_topline = top; - --top; - ++bot; + top--; + bot++; } /* @@ -1664,7 +1666,7 @@ void scroll_cursor_bot(int min_scroll, int set_topbot) for (i = 0; i < scrolled && boff.lnum < curwin->w_botline;) { botline_forw(curwin, &boff); i += boff.height; - ++line_count; + line_count++; } if (i < scrolled) { // below curwin->w_botline, don't scroll line_count = 9999; @@ -1724,9 +1726,9 @@ void scroll_cursor_halfway(int atend) } below += boff.height; } else { - ++below; // count a "~" line + below++; // count a "~" line if (atend) { - ++used; + used++; } } } @@ -1835,7 +1837,7 @@ void cursor_correct(void) if (topline < botline) { above += win_get_fill(curwin, topline + 1); } - ++topline; + topline++; } } if (topline == botline || botline == 0) { @@ -1897,7 +1899,7 @@ int onepage(Direction dir, long count) if (ONE_WINDOW && p_window > 0 && p_window < Rows - 1) { // Vi compatible scrolling if (p_window <= 2) { - ++curwin->w_topline; + curwin->w_topline++; } else { curwin->w_topline += (linenr_T)p_window - 2; } @@ -1933,7 +1935,7 @@ int onepage(Direction dir, long count) if (ONE_WINDOW && p_window > 0 && p_window < Rows - 1) { // Vi compatible scrolling (sort of) if (p_window <= 2) { - --curwin->w_topline; + curwin->w_topline--; } else { curwin->w_topline -= (linenr_T)p_window - 2; } @@ -1998,7 +2000,7 @@ int onepage(Direction dir, long count) max_topfill(); } if (curwin->w_topfill == loff.fill) { - --curwin->w_topline; + curwin->w_topline--; curwin->w_topfill = 0; } comp_botline(curwin); @@ -2038,7 +2040,7 @@ int onepage(Direction dir, long count) } } - redraw_later(curwin, VALID); + redraw_later(curwin, UPD_VALID); return retval; } @@ -2142,7 +2144,7 @@ void halfpage(bool flag, linenr_T Prenum) curwin->w_topfill = win_get_fill(curwin, curwin->w_topline); if (curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) { - ++curwin->w_cursor.lnum; + curwin->w_cursor.lnum++; curwin->w_valid &= ~(VALID_VIRTCOL|VALID_CHEIGHT|VALID_WCOL); } @@ -2175,7 +2177,7 @@ void halfpage(bool flag, linenr_T Prenum) && curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) { (void)hasFolding(curwin->w_cursor.lnum, NULL, &curwin->w_cursor.lnum); - ++curwin->w_cursor.lnum; + curwin->w_cursor.lnum++; } } else { curwin->w_cursor.lnum += n; @@ -2197,7 +2199,7 @@ void halfpage(bool flag, linenr_T Prenum) if (n < 0 && scrolled > 0) { break; } - --curwin->w_topline; + curwin->w_topline--; (void)hasFolding(curwin->w_topline, &curwin->w_topline, NULL); curwin->w_topfill = 0; } @@ -2205,7 +2207,7 @@ void halfpage(bool flag, linenr_T Prenum) VALID_BOTLINE|VALID_BOTLINE_AP); scrolled += i; if (curwin->w_cursor.lnum > 1) { - --curwin->w_cursor.lnum; + curwin->w_cursor.lnum--; curwin->w_valid &= ~(VALID_VIRTCOL|VALID_CHEIGHT|VALID_WCOL); } } @@ -2216,7 +2218,7 @@ void halfpage(bool flag, linenr_T Prenum) curwin->w_cursor.lnum = 1; } else if (hasAnyFolding(curwin)) { while (--n >= 0 && curwin->w_cursor.lnum > 1) { - --curwin->w_cursor.lnum; + curwin->w_cursor.lnum--; (void)hasFolding(curwin->w_cursor.lnum, &curwin->w_cursor.lnum, NULL); } @@ -2230,7 +2232,7 @@ void halfpage(bool flag, linenr_T Prenum) check_topfill(curwin, !flag); cursor_correct(); beginline(BL_SOL | BL_FIX); - redraw_later(curwin, VALID); + redraw_later(curwin, UPD_VALID); } void do_check_cursorbind(void) @@ -2282,7 +2284,7 @@ void do_check_cursorbind(void) } // Correct cursor for multi-byte character. mb_adjust_cursor(); - redraw_later(curwin, VALID); + redraw_later(curwin, UPD_VALID); // Only scroll when 'scrollbind' hasn't done this. if (!curwin->w_p_scb) { diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c index 1ca68979f4..d22bcb29d5 100644 --- a/src/nvim/msgpack_rpc/channel.c +++ b/src/nvim/msgpack_rpc/channel.c @@ -158,7 +158,7 @@ Object rpc_send_call(uint64_t id, const char *method_name, Array args, ArenaMem } // frame.result was allocated in an arena - arena_mem_free(frame.result_mem, &rpc->unpacker->reuse_blk); + arena_mem_free(frame.result_mem); frame.result_mem = NULL; } @@ -244,7 +244,7 @@ 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), &p->reuse_blk); + arena_mem_free(arena_finish(&p->arena)); } } else if (p->type == kMessageTypeResponse) { ChannelCallFrame *frame = kv_last(channel->rpc.call_stack); @@ -295,7 +295,7 @@ static void handle_request(Channel *channel, Unpacker *p, Array args) if (!p->handler.fn) { send_error(channel, p->type, p->request_id, p->unpack_error.msg); api_clear_error(&p->unpack_error); - arena_mem_free(arena_finish(&p->arena), &p->reuse_blk); + arena_mem_free(arena_finish(&p->arena)); return; } @@ -304,7 +304,8 @@ static void handle_request(Channel *channel, Unpacker *p, Array args) evdata->channel = channel; evdata->handler = p->handler; evdata->args = args; - evdata->used_mem = arena_finish(&p->arena); + evdata->used_mem = p->arena; + p->arena = (Arena)ARENA_EMPTY; evdata->request_id = p->request_id; channel_incref(channel); if (p->handler.fast) { @@ -344,7 +345,8 @@ static void request_event(void **argv) // channel was closed, abort any pending requests goto free_ret; } - Object result = handler.fn(channel->id, e->args, &error); + + Object result = handler.fn(channel->id, e->args, &e->used_mem, &error); if (e->type == kMessageTypeRequest || ERROR_SET(&error)) { // Send the response. msgpack_packer response; @@ -355,13 +357,14 @@ static void request_event(void **argv) &error, result, &out_buffer)); - } else { + } + if (!handler.arena_return) { api_free_object(result); } free_ret: - // e->args is allocated in an arena - arena_mem_free(e->used_mem, &channel->rpc.unpacker->reuse_blk); + // e->args (and possibly result) are allocated in an arena + arena_mem_free(arena_finish(&e->used_mem)); channel_decref(channel); xfree(e); api_clear_error(&error); @@ -624,7 +627,6 @@ static WBuffer *serialize_response(uint64_t channel_id, MessageType type, uint32 1, // responses only go though 1 channel xfree); msgpack_sbuffer_clear(sbuffer); - api_free_object(arg); return rv; } @@ -642,7 +644,7 @@ void rpc_set_client_info(uint64_t id, Dictionary info) Dictionary rpc_client_info(Channel *chan) { - return copy_dictionary(chan->rpc.info); + return copy_dictionary(chan->rpc.info, NULL); } const char *rpc_client_name(Channel *chan) diff --git a/src/nvim/msgpack_rpc/channel_defs.h b/src/nvim/msgpack_rpc/channel_defs.h index e622ebddf5..404e68329a 100644 --- a/src/nvim/msgpack_rpc/channel_defs.h +++ b/src/nvim/msgpack_rpc/channel_defs.h @@ -27,7 +27,7 @@ typedef struct { MsgpackRpcRequestHandler handler; Array args; uint32_t request_id; - ArenaMem used_mem; + Arena used_mem; } RequestEvent; typedef struct { diff --git a/src/nvim/msgpack_rpc/server.c b/src/nvim/msgpack_rpc/server.c index b252f0998e..532e684f93 100644 --- a/src/nvim/msgpack_rpc/server.c +++ b/src/nvim/msgpack_rpc/server.c @@ -216,7 +216,7 @@ bool server_stop(char *endpoint) watchers.ga_len--; // Bump v:servername to the next available server, if any. - if (strequal(addr, (char *)get_vim_var_str(VV_SEND_SERVER))) { + if (strequal(addr, get_vim_var_str(VV_SEND_SERVER))) { set_vservername(&watchers); } diff --git a/src/nvim/msgpack_rpc/unpacker.c b/src/nvim/msgpack_rpc/unpacker.c index c8e9fdd4c3..24480835a1 100644 --- a/src/nvim/msgpack_rpc/unpacker.c +++ b/src/nvim/msgpack_rpc/unpacker.c @@ -181,13 +181,11 @@ void unpacker_init(Unpacker *p) p->unpack_error = (Error)ERROR_INIT; p->arena = (Arena)ARENA_EMPTY; - p->reuse_blk = NULL; } void unpacker_teardown(Unpacker *p) { - arena_mem_free(p->reuse_blk, NULL); - arena_mem_free(arena_finish(&p->arena), NULL); + arena_mem_free(arena_finish(&p->arena)); } bool unpacker_parse_header(Unpacker *p) @@ -308,7 +306,7 @@ bool unpacker_advance(Unpacker *p) p->state = 10; } else { p->state = p->type == kMessageTypeResponse ? 1 : 2; - arena_start(&p->arena, &p->reuse_blk); + p->arena = (Arena)ARENA_EMPTY; } } @@ -322,7 +320,7 @@ bool unpacker_advance(Unpacker *p) goto done; } else { // unpack other ui events using mpack_parse() - arena_start(&p->arena, &p->reuse_blk); + p->arena = (Arena)ARENA_EMPTY; } } @@ -416,13 +414,13 @@ redo: if (p->ui_handler.fn != ui_client_event_grid_line) { p->state = 12; if (p->grid_line_event) { - arena_mem_free(arena_finish(&p->arena), &p->reuse_blk); + arena_mem_free(arena_finish(&p->arena)); p->grid_line_event = NULL; } return true; } else { p->state = 13; - arena_start(&p->arena, &p->reuse_blk); + p->arena = (Arena)ARENA_EMPTY; p->grid_line_event = arena_alloc(&p->arena, sizeof *p->grid_line_event, true); g = p->grid_line_event; } diff --git a/src/nvim/msgpack_rpc/unpacker.h b/src/nvim/msgpack_rpc/unpacker.h index f39439be63..35048fb877 100644 --- a/src/nvim/msgpack_rpc/unpacker.h +++ b/src/nvim/msgpack_rpc/unpacker.h @@ -32,8 +32,6 @@ struct Unpacker { Error unpack_error; Arena arena; - // one length free-list of reusable blocks - ArenaMem reuse_blk; int nevents; int ncalls; diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 2d752eade7..47ad000385 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -18,10 +18,13 @@ #include "nvim/buffer.h" #include "nvim/change.h" #include "nvim/charset.h" +#include "nvim/cmdhist.h" #include "nvim/cursor.h" #include "nvim/diff.h" #include "nvim/digraph.h" +#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" @@ -32,7 +35,8 @@ #include "nvim/fold.h" #include "nvim/getchar.h" #include "nvim/globals.h" -#include "nvim/grid_defs.h" +#include "nvim/grid.h" +#include "nvim/help.h" #include "nvim/indent.h" #include "nvim/keycodes.h" #include "nvim/log.h" @@ -41,6 +45,7 @@ #include "nvim/mark.h" #include "nvim/memline.h" #include "nvim/memory.h" +#include "nvim/menu.h" #include "nvim/message.h" #include "nvim/mouse.h" #include "nvim/move.h" @@ -50,15 +55,18 @@ #include "nvim/os/input.h" #include "nvim/os/time.h" #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/strings.h" #include "nvim/syntax.h" #include "nvim/tag.h" +#include "nvim/textformat.h" +#include "nvim/textobject.h" #include "nvim/ui.h" #include "nvim/undo.h" #include "nvim/vim.h" @@ -465,7 +473,7 @@ void normal_enter(bool cmdwin, bool noexmode) static void normal_prepare(NormalState *s) { - memset(&s->ca, 0, sizeof(s->ca)); // also resets ca.retval + CLEAR_FIELD(s->ca); // also resets s->ca.retval s->ca.oap = &s->oa; // Use a count remembered from before entering an operator. After typing "3d" @@ -517,7 +525,7 @@ static bool normal_handle_special_visual_command(NormalState *s) && (nv_cmds[s->idx].cmd_flags & NV_STS) && !(mod_mask & MOD_MASK_SHIFT)) { end_visual_mode(); - redraw_curbuf_later(INVERTED); + redraw_curbuf_later(UPD_INVERTED); } // Keys that work different when 'keymodel' contains "startsel" @@ -623,16 +631,16 @@ static void normal_redraw_mode_message(NormalState *s) if (must_redraw && keep_msg != NULL && !emsg_on_display) { char_u *kmsg; - kmsg = keep_msg; + kmsg = (char_u *)keep_msg; keep_msg = NULL; // Showmode() will clear keep_msg, but we want to use it anyway. // First update w_topline. setcursor(); update_screen(0); // now reset it, otherwise it's put in the history again - keep_msg = kmsg; + keep_msg = (char *)kmsg; - kmsg = vim_strsave(keep_msg); + kmsg = vim_strsave((char_u *)keep_msg); msg_attr((const char *)kmsg, keep_msg_attr); xfree(kmsg); } @@ -1275,11 +1283,11 @@ static void normal_redraw(NormalState *s) validate_cursor(); if (VIsual_active) { - redraw_curbuf_later(INVERTED); // update inverted part - update_screen(INVERTED); + redraw_curbuf_later(UPD_INVERTED); // update inverted part + update_screen(0); } else if (must_redraw) { update_screen(0); - } else if (redraw_cmdline || clear_cmdline) { + } else if (redraw_cmdline || clear_cmdline || redraw_mode) { showmode(); } @@ -1294,7 +1302,7 @@ static void normal_redraw(NormalState *s) // Display message after redraw. If an external message is still visible, // it contains the kept message already. if (keep_msg != NULL && !msg_ext_is_visible()) { - char_u *const p = vim_strsave(keep_msg); + char *const p = xstrdup(keep_msg); // msg_start() will set keep_msg to NULL, make a copy // first. Don't reset keep_msg, msg_attr_keep() uses it to @@ -1316,7 +1324,7 @@ static void normal_redraw(NormalState *s) did_emsg = false; msg_didany = false; // reset lines_left in msg_start() may_clear_sb_text(); // clear scroll-back text on next msg - showruler(false); + show_cursor_info(false); setcursor(); } @@ -1832,7 +1840,8 @@ bool do_mouse(oparg_T *oap, int c, int dir, long count, bool fixindent) } if (jump_flags) { jump_flags = jump_to_mouse(jump_flags, NULL, which_button); - update_curbuf(VIsual_active ? INVERTED : VALID); + redraw_curbuf_later(VIsual_active ? UPD_INVERTED : UPD_VALID); + update_screen(0); setcursor(); ui_flush(); // Update before showing popup menu } @@ -2179,7 +2188,7 @@ bool do_mouse(oparg_T *oap, int c, int dir, long count, bool fixindent) curwin->w_set_curswant = true; } if (is_click) { - redraw_curbuf_later(INVERTED); // update the inversion + redraw_curbuf_later(UPD_INVERTED); // update the inversion } } else if (VIsual_active && !old_active) { if (mod_mask & MOD_MASK_ALT) { @@ -2304,7 +2313,7 @@ void reset_VIsual_and_resel(void) { if (VIsual_active) { end_visual_mode(); - redraw_curbuf_later(INVERTED); // delete the inversion later + redraw_curbuf_later(UPD_INVERTED); // delete the inversion later } VIsual_reselect = false; } @@ -2314,7 +2323,7 @@ void reset_VIsual(void) { if (VIsual_active) { end_visual_mode(); - redraw_curbuf_later(INVERTED); // delete the inversion later + redraw_curbuf_later(UPD_INVERTED); // delete the inversion later VIsual_reselect = false; } } @@ -2383,7 +2392,7 @@ static bool find_is_eval_item(const char_u *const ptr, int *const colp, int *con /// /// If text is found, a pointer to the text is put in "*text". This /// points into the current buffer line and is not always NUL terminated. -size_t find_ident_under_cursor(char_u **text, int find_type) +size_t find_ident_under_cursor(char **text, int find_type) FUNC_ATTR_NONNULL_ARG(1) { return find_ident_at_pos(curwin, curwin->w_cursor.lnum, @@ -2394,7 +2403,7 @@ size_t find_ident_under_cursor(char_u **text, int find_type) /// However: Uses 'iskeyword' from the current window!. /// /// @param textcol column where "text" starts, can be NULL -size_t find_ident_at_pos(win_T *wp, linenr_T lnum, colnr_T startcol, char_u **text, int *textcol, +size_t find_ident_at_pos(win_T *wp, linenr_T lnum, colnr_T startcol, char **text, int *textcol, int find_type) FUNC_ATTR_NONNULL_ARG(1, 4) { @@ -2470,7 +2479,7 @@ size_t find_ident_at_pos(win_T *wp, linenr_T lnum, colnr_T startcol, char_u **te return 0; } ptr += col; - *text = ptr; + *text = (char *)ptr; if (textcol != NULL) { *textcol = col; } @@ -2645,8 +2654,8 @@ void clear_showcmd(void) lines = bot - top + 1; if (VIsual_mode == Ctrl_V) { - char_u *const saved_sbr = p_sbr; - char_u *const saved_w_sbr = curwin->w_p_sbr; + char *const saved_sbr = p_sbr; + char *const saved_w_sbr = curwin->w_p_sbr; // Make 'sbr' empty for a moment to get the correct size. p_sbr = empty_option; @@ -2709,8 +2718,6 @@ void clear_showcmd(void) /// @return true if output has been written (and setcursor() has been called). bool add_to_showcmd(int c) { - char_u *p; - int i; static int ignore[] = { K_IGNORE, K_LEFTMOUSE, K_LEFTDRAG, K_LEFTRELEASE, K_MOUSEMOVE, @@ -2733,14 +2740,14 @@ bool add_to_showcmd(int c) // Ignore keys that are scrollbar updates and mouse clicks if (IS_SPECIAL(c)) { - for (i = 0; ignore[i] != 0; i++) { + for (int i = 0; ignore[i] != 0; i++) { if (ignore[i] == c) { return false; } } } - p = transchar(c); + char *p = (char *)transchar(c); if (*p == ' ') { STRCPY(p, "<20>"); } @@ -2836,12 +2843,12 @@ static void display_showcmd(void) grid_puts_line_start(&msg_grid_adj, showcmd_row); if (!showcmd_is_clear) { - grid_puts(&msg_grid_adj, showcmd_buf, showcmd_row, sc_col, + grid_puts(&msg_grid_adj, (char *)showcmd_buf, showcmd_row, sc_col, HL_ATTR(HLF_MSG)); } // clear the rest of an old message by outputting up to SHOWCMD_COLS spaces - grid_puts(&msg_grid_adj, (char_u *)" " + len, showcmd_row, + grid_puts(&msg_grid_adj, (char *)" " + len, showcmd_row, sc_col + len, HL_ATTR(HLF_MSG)); grid_puts_line_flush(false); @@ -2949,7 +2956,7 @@ void check_scrollbind(linenr_T topline_diff, long leftcol_diff) } } - redraw_later(curwin, VALID); + redraw_later(curwin, UPD_VALID); cursor_correct(); curwin->w_redr_status = true; } @@ -3035,9 +3042,9 @@ static void nv_page(cmdarg_T *cap) static void nv_gd(oparg_T *oap, int nchar, int thisblock) { size_t len; - char_u *ptr; + char *ptr; if ((len = find_ident_under_cursor(&ptr, FIND_IDENT)) == 0 - || !find_decl(ptr, len, nchar == 'd', thisblock, SEARCH_START)) { + || !find_decl((char_u *)ptr, len, nchar == 'd', thisblock, SEARCH_START)) { clearopbeep(oap); } else { if ((fdo_flags & FDO_SEARCH) && KeyTyped && oap->op_type == OP_NOP) { @@ -3483,7 +3490,7 @@ void scroll_redraw(int up, long count) if (moved) { curwin->w_viewport_invalid = true; } - redraw_later(curwin, VALID); + redraw_later(curwin, UPD_VALID); } /// Get the count specified after a 'z' command. Only the 'z<CR>', 'zl', 'zh', @@ -3557,7 +3564,7 @@ static int nv_zg_zw(cmdarg_T *cap, int nchar) if (checkclearop(cap->oap)) { return OK; } - char_u *ptr = NULL; + char *ptr = NULL; size_t len; if (VIsual_active && !get_visual_text(cap, &ptr, &len)) { return FAIL; @@ -3572,7 +3579,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 = ml_get_pos(&curwin->w_cursor); + ptr = (char *)ml_get_pos(&curwin->w_cursor); } curwin->w_cursor = pos; } @@ -3581,7 +3588,7 @@ static int nv_zg_zw(cmdarg_T *cap, int nchar) return FAIL; } assert(len <= INT_MAX); - spell_add_word(ptr, (int)len, + spell_add_word((char_u *)ptr, (int)len, nchar == 'w' || nchar == 'W' ? SPELL_ADD_BAD : SPELL_ADD_GOOD, (nchar == 'G' || nchar == 'W') ? 0 : (int)cap->count1, undo); @@ -3649,7 +3656,7 @@ static void nv_zet(cmdarg_T *cap) case 't': scroll_cursor_top(0, true); - redraw_later(curwin, VALID); + redraw_later(curwin, UPD_VALID); set_fraction(curwin); break; @@ -3660,7 +3667,7 @@ static void nv_zet(cmdarg_T *cap) case 'z': scroll_cursor_halfway(true); - redraw_later(curwin, VALID); + redraw_later(curwin, UPD_VALID); set_fraction(curwin); break; @@ -3683,7 +3690,7 @@ static void nv_zet(cmdarg_T *cap) case 'b': scroll_cursor_bot(0, true); - redraw_later(curwin, VALID); + redraw_later(curwin, UPD_VALID); set_fraction(curwin); break; @@ -3735,7 +3742,7 @@ static void nv_zet(cmdarg_T *cap) } if (curwin->w_leftcol != col) { curwin->w_leftcol = col; - redraw_later(curwin, NOT_VALID); + redraw_later(curwin, UPD_NOT_VALID); } } break; @@ -3756,7 +3763,7 @@ static void nv_zet(cmdarg_T *cap) } if (curwin->w_leftcol != col) { curwin->w_leftcol = col; - redraw_later(curwin, NOT_VALID); + redraw_later(curwin, UPD_NOT_VALID); } } break; @@ -4091,7 +4098,7 @@ static void nv_clear(cmdarg_T *cap) FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { wp->w_s->b_syn_slow = false; } - redraw_later(curwin, CLEAR); + redraw_later(curwin, UPD_CLEAR); } } @@ -4148,7 +4155,7 @@ void do_nv_ident(int c1, int c2) cmdarg_T ca; clear_oparg(&oa); - memset(&ca, 0, sizeof(ca)); + CLEAR_FIELD(ca); ca.oap = &oa; ca.cmdchar = c1; ca.nchar = c2; @@ -4157,7 +4164,7 @@ void do_nv_ident(int c1, int c2) /// 'K' normal-mode command. Get the command to lookup the keyword under the /// cursor. -static size_t nv_K_getcmd(cmdarg_T *cap, char_u *kp, bool kp_help, bool kp_ex, char_u **ptr_arg, +static size_t nv_K_getcmd(cmdarg_T *cap, char_u *kp, bool kp_help, bool kp_ex, char **ptr_arg, size_t n, char *buf, size_t buf_size) { if (kp_help) { @@ -4176,7 +4183,7 @@ static size_t nv_K_getcmd(cmdarg_T *cap, char_u *kp, bool kp_help, bool kp_ex, c return n; } - char_u *ptr = *ptr_arg; + char *ptr = *ptr_arg; // An external command will probably use an argument starting // with "-" as an option. To avoid trouble we skip the "-". @@ -4226,7 +4233,7 @@ static size_t nv_K_getcmd(cmdarg_T *cap, char_u *kp, bool kp_help, bool kp_ex, c /// g ']' :tselect for current identifier static void nv_ident(cmdarg_T *cap) { - char_u *ptr = NULL; + char *ptr = NULL; char_u *p; size_t n = 0; // init for GCC int cmdchar; @@ -4268,14 +4275,13 @@ 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_u *kp = *curbuf->b_p_kp == NUL ? p_kp : curbuf->b_p_kp; // 'keywordprg' - assert(*kp != NUL); // option.c:do_set() should default to ":help" if empty. - bool kp_ex = (*kp == ':'); // 'keywordprg' is an ex command - bool kp_help = (STRCMP(kp, ":he") == 0 || STRCMP(kp, ":help") == 0); - if (kp_help && *skipwhite((char *)ptr) == NUL) { + char_u *kp = *curbuf->b_p_kp == NUL ? p_kp : (char_u *)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 return; } + bool kp_ex = (*kp == ':'); // 'keywordprg' is an ex command size_t buf_size = n * 2 + 30 + STRLEN(kp); char *buf = xmalloc(buf_size); buf[0] = NUL; @@ -4288,9 +4294,9 @@ static void nv_ident(cmdarg_T *cap) // Call setpcmark() first, so "*``" puts the cursor back where // it was. setpcmark(); - curwin->w_cursor.col = (colnr_T)(ptr - get_cursor_line_ptr()); + curwin->w_cursor.col = (colnr_T)(ptr - (char *)get_cursor_line_ptr()); - if (!g_cmd && vim_iswordp(ptr)) { + if (!g_cmd && vim_iswordp((char_u *)ptr)) { STRCPY(buf, "\\<"); } no_smartcase = true; // don't use 'smartcase' now @@ -4327,13 +4333,13 @@ static void nv_ident(cmdarg_T *cap) // Now grab the chars in the identifier if (cmdchar == 'K' && !kp_help) { - ptr = vim_strnsave(ptr, n); + ptr = xstrnsave(ptr, n); if (kp_ex) { // Escape the argument properly for an Ex command p = (char_u *)vim_strsave_fnameescape((const char *)ptr, VSE_NONE); } else { // Escape the argument properly for a shell command - p = vim_strsave_shellescape(ptr, true, true); + p = vim_strsave_shellescape((char_u *)ptr, true, true); } xfree(ptr); char *newbuf = xrealloc(buf, STRLEN(buf) + STRLEN(p) + 1); @@ -4364,11 +4370,11 @@ static void nv_ident(cmdarg_T *cap) } // When current byte is a part of multibyte character, copy all // bytes of that character. - const size_t len = (size_t)(utfc_ptr2len((char *)ptr) - 1); + const size_t len = (size_t)(utfc_ptr2len(ptr) - 1); for (size_t i = 0; i < len && n > 0; i++, n--) { - *p++ = *ptr++; + *p++ = (char_u)(*ptr++); } - *p++ = *ptr++; + *p++ = (char_u)(*ptr++); } *p = NUL; } @@ -4376,14 +4382,15 @@ static void nv_ident(cmdarg_T *cap) // Execute the command. if (cmdchar == '*' || cmdchar == '#') { if (!g_cmd - && vim_iswordp(mb_prevptr(get_cursor_line_ptr(), ptr))) { + && vim_iswordp(mb_prevptr(get_cursor_line_ptr(), (char_u *)ptr))) { STRCAT(buf, "\\>"); } + // put pattern in search history init_history(); add_to_history(HIST_SEARCH, (char_u *)buf, true, NUL); - (void)normal_search(cap, cmdchar == '*' ? '/' : '?', (char_u *)buf, 0, - NULL); + + (void)normal_search(cap, cmdchar == '*' ? '/' : '?', buf, 0, NULL); } else { g_tag_at_cursor = true; do_cmdline_cmd(buf); @@ -4406,7 +4413,7 @@ static void nv_ident(cmdarg_T *cap) /// @param lenp return: length of selected text /// /// @return false if more than one line selected. -bool get_visual_text(cmdarg_T *cap, char_u **pp, size_t *lenp) +bool get_visual_text(cmdarg_T *cap, char **pp, size_t *lenp) { if (VIsual_mode != 'V') { unadjust_for_sel(); @@ -4418,14 +4425,14 @@ bool get_visual_text(cmdarg_T *cap, char_u **pp, size_t *lenp) return false; } if (VIsual_mode == 'V') { - *pp = get_cursor_line_ptr(); + *pp = (char *)get_cursor_line_ptr(); *lenp = STRLEN(*pp); } else { if (lt(curwin->w_cursor, VIsual)) { - *pp = ml_get_pos(&curwin->w_cursor); + *pp = (char *)ml_get_pos(&curwin->w_cursor); *lenp = (size_t)VIsual.col - (size_t)curwin->w_cursor.col + 1; } else { - *pp = ml_get_pos(&VIsual); + *pp = (char *)ml_get_pos(&VIsual); *lenp = (size_t)curwin->w_cursor.col - (size_t)VIsual.col + 1; } if (**pp == NUL) { @@ -4433,7 +4440,7 @@ bool get_visual_text(cmdarg_T *cap, char_u **pp, size_t *lenp) } if (*lenp > 0) { // Correct the length to include all bytes of the last character. - *lenp += (size_t)(utfc_ptr2len((char *)(*pp) + (*lenp - 1)) - 1); + *lenp += (size_t)(utfc_ptr2len(*pp + (*lenp - 1)) - 1); } } reset_VIsual_and_resel(); @@ -4557,9 +4564,9 @@ static void nv_right(cmdarg_T *cap) // <Space> wraps to next line if 'whichwrap' has 's'. // 'l' wraps to next line if 'whichwrap' has 'l'. // CURS_RIGHT wraps to next line if 'whichwrap' has '>'. - if (((cap->cmdchar == ' ' && vim_strchr((char *)p_ww, 's') != NULL) - || (cap->cmdchar == 'l' && vim_strchr((char *)p_ww, 'l') != NULL) - || (cap->cmdchar == K_RIGHT && vim_strchr((char *)p_ww, '>') != NULL)) + if (((cap->cmdchar == ' ' && vim_strchr(p_ww, 's') != NULL) + || (cap->cmdchar == 'l' && vim_strchr(p_ww, 'l') != NULL) + || (cap->cmdchar == K_RIGHT && vim_strchr(p_ww, '>') != NULL)) && curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) { // When deleting we also count the NL as a character. // Set cap->oap->inclusive when last char in the line is @@ -4627,9 +4634,9 @@ static void nv_left(cmdarg_T *cap) // 'h' wraps to previous line if 'whichwrap' has 'h'. // CURS_LEFT wraps to previous line if 'whichwrap' has '<'. if ((((cap->cmdchar == K_BS || cap->cmdchar == Ctrl_H) - && vim_strchr((char *)p_ww, 'b') != NULL) - || (cap->cmdchar == 'h' && vim_strchr((char *)p_ww, 'h') != NULL) - || (cap->cmdchar == K_LEFT && vim_strchr((char *)p_ww, '<') != NULL)) + && vim_strchr(p_ww, 'b') != NULL) + || (cap->cmdchar == 'h' && vim_strchr(p_ww, 'h') != NULL) + || (cap->cmdchar == K_LEFT && vim_strchr(p_ww, '<') != NULL)) && curwin->w_cursor.lnum > 1) { curwin->w_cursor.lnum--; coladvance(MAXCOL); @@ -4796,7 +4803,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 = getcmdline(cap->cmdchar, cap->count1, 0, true); + cap->searchbuf = (char *)getcmdline(cap->cmdchar, cap->count1, 0, true); if (cap->searchbuf == NULL) { clearop(oap); @@ -4832,9 +4839,8 @@ static void nv_next(cmdarg_T *cap) /// @param opt extra flags for do_search() /// /// @return 0 for failure, 1 for found, 2 for found and line offset added. -static int normal_search(cmdarg_T *cap, int dir, char_u *pat, int opt, int *wrapped) +static int normal_search(cmdarg_T *cap, int dir, char *pat, int opt, int *wrapped) { - int i; searchit_arg_T sia; cap->oap->motion_type = kMTCharWise; @@ -4842,9 +4848,9 @@ static int normal_search(cmdarg_T *cap, int dir, char_u *pat, int opt, int *wrap cap->oap->use_reg_one = true; curwin->w_set_curswant = true; - memset(&sia, 0, sizeof(sia)); - i = do_search(cap->oap, dir, dir, pat, cap->count1, - opt | SEARCH_OPT | SEARCH_ECHO | SEARCH_MSG, &sia); + CLEAR_FIELD(sia); + int i = do_search(cap->oap, dir, dir, (char_u *)pat, cap->count1, + opt | SEARCH_OPT | SEARCH_ECHO | SEARCH_MSG, &sia); if (wrapped != NULL) { *wrapped = sia.sa_wrapped; } @@ -5044,15 +5050,15 @@ static void nv_brackets(cmdarg_T *cap) // fwd bwd fwd bwd fwd bwd // identifier "]i" "[i" "]I" "[I" "]^I" "[^I" // define "]d" "[d" "]D" "[D" "]^D" "[^D" - char_u *ptr; + char *ptr; size_t len; if ((len = find_ident_under_cursor(&ptr, FIND_IDENT)) == 0) { clearop(cap->oap); } else { // Make a copy, if the line was changed it will be freed. - ptr = vim_strnsave(ptr, len); - find_pattern_in_path(ptr, 0, len, true, + ptr = xstrnsave(ptr, len); + find_pattern_in_path((char_u *)ptr, 0, len, true, cap->count0 == 0 ? !isupper(cap->nchar) : false, (((cap->nchar & 0xf) == ('d' & 0xf)) ? FIND_DEFINE @@ -5543,7 +5549,7 @@ static void n_swapchar(cmdarg_T *cap) return; } - if (LINEEMPTY(curwin->w_cursor.lnum) && vim_strchr((char *)p_ww, '~') == NULL) { + if (LINEEMPTY(curwin->w_cursor.lnum) && vim_strchr(p_ww, '~') == NULL) { clearopbeep(cap->oap); return; } @@ -5559,7 +5565,7 @@ static void n_swapchar(cmdarg_T *cap) did_change |= swapchar(cap->oap->op_type, &curwin->w_cursor); inc_cursor(); if (gchar_cursor() == NUL) { - if (vim_strchr((char *)p_ww, '~') != NULL + if (vim_strchr(p_ww, '~') != NULL && curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) { curwin->w_cursor.lnum++; curwin->w_cursor.col = 0; @@ -5615,7 +5621,7 @@ static MarkMoveRes nv_mark_move_to(cmdarg_T *cap, MarkMove flags, fmark_T *fm) /// Handle commands that are operators in Visual mode. static void v_visop(cmdarg_T *cap) { - static char_u trans[] = "YyDdCcxdXdAAIIrr"; + static char trans[] = "YyDdCcxdXdAAIIrr"; // Uppercase means linewise, except in block mode, then "D" deletes till // the end of the line, and "C" replaces till EOL @@ -5627,7 +5633,7 @@ static void v_visop(cmdarg_T *cap) curwin->w_curswant = MAXCOL; } } - cap->cmdchar = (uint8_t)(*(vim_strchr((char *)trans, cap->cmdchar) + 1)); + cap->cmdchar = (uint8_t)(*(vim_strchr(trans, cap->cmdchar) + 1)); nv_operator(cap); } @@ -5810,7 +5816,7 @@ static void nv_visual(cmdarg_T *cap) showmode(); may_trigger_modechanged(); } - redraw_curbuf_later(INVERTED); // update the inversion + redraw_curbuf_later(UPD_INVERTED); // update the inversion } else { // start Visual mode if (cap->count0 > 0 && resel_VIsual_mode != NUL) { // use previously selected part @@ -5856,7 +5862,7 @@ static void nv_visual(cmdarg_T *cap) } else { curwin->w_set_curswant = true; } - redraw_curbuf_later(INVERTED); // show the inversion + redraw_curbuf_later(UPD_INVERTED); // show the inversion } else { if (!cap->arg) { // start Select mode when 'selectmode' contains "cmd" @@ -5891,7 +5897,7 @@ void start_selection(void) void may_start_select(int c) { VIsual_select = (c == 'o' || (stuff_empty() && typebuf_typed())) - && vim_strchr((char *)p_slm, c) != NULL; + && vim_strchr(p_slm, c) != NULL; } /// Start Visual mode "c". @@ -5922,7 +5928,7 @@ static void n_start_visual_mode(int c) } // Only need to redraw this line, unless still need to redraw an old // Visual area (when 'lazyredraw' is set). - if (curwin->w_redr_type < INVERTED) { + if (curwin->w_redr_type < UPD_INVERTED) { curwin->w_old_cursor_lnum = curwin->w_cursor.lnum; curwin->w_old_visual_lnum = curwin->w_cursor.lnum; } @@ -6009,7 +6015,7 @@ static void nv_gv_cmd(cmdarg_T *cap) may_start_select('c'); } setmouse(); - redraw_curbuf_later(INVERTED); + redraw_curbuf_later(UPD_INVERTED); showmode(); } @@ -6892,7 +6898,7 @@ static void nv_normal(cmdarg_T *cap) } if (VIsual_active) { end_visual_mode(); // stop Visual - redraw_curbuf_later(INVERTED); + redraw_curbuf_later(UPD_INVERTED); } } else { clearopbeep(cap->oap); @@ -6923,6 +6929,10 @@ static void nv_esc(cmdarg_T *cap) } } + if (restart_edit != 0) { + redraw_mode = true; // remove "-- (insert) --" + } + restart_edit = 0; if (cmdwin_type != 0) { @@ -6930,10 +6940,10 @@ static void nv_esc(cmdarg_T *cap) got_int = false; // don't stop executing autocommands et al. return; } - } else if (cmdwin_type != 0 && ex_normal_busy) { + } else if (cmdwin_type != 0 && ex_normal_busy && typebuf_was_empty) { // When :normal runs out of characters while in the command line window - // vgetorpeek() will return ESC. Exit the cmdline window to break the - // loop. + // vgetorpeek() will repeatedly return ESC. Exit the cmdline window to + // break the loop. cmdwin_result = K_IGNORE; return; } @@ -6942,7 +6952,7 @@ static void nv_esc(cmdarg_T *cap) end_visual_mode(); // stop Visual check_cursor_col(); // make sure cursor is not beyond EOL curwin->w_set_curswant = true; - redraw_curbuf_later(INVERTED); + redraw_curbuf_later(UPD_INVERTED); } else if (no_reason) { vim_beep(BO_ESC); } @@ -7062,8 +7072,8 @@ static void nv_object(cmdarg_T *cap) include = true; // "ax" = an object: include white space } // Make sure (), [], {} and <> are in 'matchpairs' - mps_save = curbuf->b_p_mps; - curbuf->b_p_mps = (char_u *)"(:),{:},[:],<:>"; + mps_save = (char_u *)curbuf->b_p_mps; + curbuf->b_p_mps = "(:),{:},[:],<:>"; switch (cap->nchar) { case 'w': // "aw" = a word @@ -7117,7 +7127,7 @@ static void nv_object(cmdarg_T *cap) break; } - curbuf->b_p_mps = mps_save; + curbuf->b_p_mps = (char *)mps_save; if (!flag) { clearopbeep(cap->oap); } diff --git a/src/nvim/normal.h b/src/nvim/normal.h index 9bda70eacd..13ea233658 100644 --- a/src/nvim/normal.h +++ b/src/nvim/normal.h @@ -57,7 +57,7 @@ typedef struct oparg_S { * Arguments for Normal mode commands. */ typedef struct cmdarg_S { - oparg_T *oap; // Operator arguments + oparg_T *oap; // Operator arguments int prechar; // prefix character (optional, always 'g') int cmdchar; // command character int nchar; // next command character (optional) @@ -69,7 +69,7 @@ typedef struct cmdarg_S { long count1; // count before command, default 1 int arg; // extra argument from nv_cmds[] int retval; // return: CA_* values - char_u *searchbuf; // return: pointer to search pattern or NULL + char *searchbuf; // return: pointer to search pattern or NULL } cmdarg_T; // values for retval: diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 53612be697..4f2b84d20f 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -17,6 +17,7 @@ #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.h" @@ -24,7 +25,6 @@ #include "nvim/ex_cmds2.h" #include "nvim/ex_getln.h" #include "nvim/extmark.h" -#include "nvim/fileio.h" #include "nvim/fold.h" #include "nvim/getchar.h" #include "nvim/globals.h" @@ -47,11 +47,11 @@ #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/ui.h" #include "nvim/undo.h" #include "nvim/vim.h" @@ -79,9 +79,9 @@ struct block_def { 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 - int is_short; // TRUE if line is too short to fit in block - int is_MAX; // TRUE if curswant==MAXCOL when starting - int is_oneChar; // TRUE if block within one character + int is_short; // true if line is too short to fit in block + int is_MAX; // true if curswant==MAXCOL when starting + int is_oneChar; // true if block within one character 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 @@ -182,13 +182,13 @@ int get_op_type(int char1, int char2) return i; } -/// @return TRUE if operator "op" always works on whole lines. +/// @return true if operator "op" always works on whole lines. int op_on_lines(int op) { return opchars[op][2] & OPF_LINES; } -/// @return TRUE if operator "op" changes text. +/// @return true if operator "op" changes text. int op_is_change(int op) { return opchars[op][2] & OPF_CHANGE; @@ -235,7 +235,7 @@ void op_shift(oparg_T *oap, int curs_top, int amount) // isn't set or 'cindent' isn't set or '#' isn't in 'cino'. shift_line(oap->op_type == OP_LSHIFT, p_sr, amount, false); } - ++curwin->w_cursor.lnum; + curwin->w_cursor.lnum++; } if (oap->motion_type == kMTBlockWise) { @@ -245,7 +245,7 @@ void op_shift(oparg_T *oap, int curs_top, int amount) curwin->w_cursor.lnum = oap->start.lnum; beginline(BL_SOL | BL_FIX); // shift_line() may have set cursor.col } else { - --curwin->w_cursor.lnum; // put cursor on last line, for ":>" + curwin->w_cursor.lnum--; // put cursor on last line, for ":>" } // The cursor line is not in a closed fold foldOpenCursor(); @@ -289,13 +289,13 @@ void shift_line(int left, int round, int amount, int call_changed_bytes) { int count; int i, j; - int p_sw = (int)get_sw_value_indent(curbuf); + const int sw_val = (int)get_sw_value_indent(curbuf); count = get_indent(); // get current indent if (round) { // round off indent - i = count / p_sw; // number of p_sw rounded down - j = count % p_sw; // extra spaces + i = count / sw_val; // number of 'shiftwidth' rounded down + j = count % sw_val; // extra spaces if (j && left) { // first remove extra spaces amount--; } @@ -307,15 +307,15 @@ void shift_line(int left, int round, int amount, int call_changed_bytes) } else { i += amount; } - count = i * p_sw; + count = i * sw_val; } else { // original vi indent if (left) { - count -= p_sw * amount; + count -= sw_val * amount; if (count < 0) { count = 0; } } else { - count += p_sw * amount; + count += sw_val * amount; } } @@ -335,9 +335,8 @@ static void shift_block(oparg_T *oap, int amount) const int oldstate = State; char_u *newp; const int oldcol = curwin->w_cursor.col; - int p_sw = (int)get_sw_value_indent(curbuf); - long *p_vts = curbuf->b_p_vts_array; - const long p_ts = curbuf->b_p_ts; + const int sw_val = (int)get_sw_value_indent(curbuf); + const int ts_val = (int)curbuf->b_p_ts; struct block_def bd; int incr; int i = 0, j = 0; @@ -352,8 +351,8 @@ static void shift_block(oparg_T *oap, int amount) } // total is number of screen columns to be inserted/removed - int total = (int)((unsigned)amount * (unsigned)p_sw); - if ((total / p_sw) != amount) { + int total = (int)((unsigned)amount * (unsigned)sw_val); + if ((total / sw_val) != amount) { return; // multiplication overflow } @@ -379,16 +378,24 @@ static void shift_block(oparg_T *oap, int amount) bd.startspaces = 0; } } - for (; ascii_iswhite(*bd.textstart);) { - // TODO(fmoralesc): is passing bd.textstart for start of the line OK? - incr = lbr_chartabsize_adv(bd.textstart, &bd.textstart, bd.start_vcol); + + // 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, bd.textstart, bd.textstart); + while (ascii_iswhite(*cts.cts_ptr)) { + incr = lbr_chartabsize_adv(&cts); total += incr; - bd.start_vcol += incr; + cts.cts_vcol += incr; } + bd.textstart = (char_u *)cts.cts_ptr; + bd.start_vcol = cts.cts_vcol; + clear_chartabsize_arg(&cts); + // OK, now total=all the VWS reqd, and textstart points at the 1st // non-ws char in the block. if (!curbuf->b_p_et) { - tabstop_fromto(ws_vcol, ws_vcol + total, p_ts, p_vts, &i, &j); + tabstop_fromto(ws_vcol, ws_vcol + total, ts_val, curbuf->b_p_vts_array, &i, &j); } else { j = total; } @@ -439,10 +446,16 @@ static void shift_block(oparg_T *oap, int amount) // The character's column is in "bd.start_vcol". colnr_T non_white_col = bd.start_vcol; - while (ascii_iswhite(*non_white)) { - incr = lbr_chartabsize_adv(bd.textstart, &non_white, non_white_col); - non_white_col += incr; + chartabsize_T cts; + init_chartabsize_arg(&cts, curwin, curwin->w_cursor.lnum, + 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; + clear_chartabsize_arg(&cts); const colnr_T block_space_width = non_white_col - oap->start_vcol; // We will shift by "total" or "block_space_width", whichever is less. @@ -463,17 +476,19 @@ static void shift_block(oparg_T *oap, int amount) if (bd.startspaces) { verbatim_copy_width -= bd.start_char_vcols; } - while (verbatim_copy_width < destination_col) { - char_u *line = verbatim_copy_end; - - // TODO: is passing verbatim_copy_end for start of the line OK? - incr = lbr_chartabsize(line, verbatim_copy_end, verbatim_copy_width); - if (verbatim_copy_width + incr > destination_col) { + init_chartabsize_arg(&cts, curwin, 0, verbatim_copy_width, + bd.textstart, verbatim_copy_end); + while (cts.cts_vcol < destination_col) { + incr = lbr_chartabsize(&cts); + if (cts.cts_vcol + incr > destination_col) { break; } - verbatim_copy_width += incr; - MB_PTR_ADV(verbatim_copy_end); + cts.cts_vcol += incr; + MB_PTR_ADV(cts.cts_ptr); } + verbatim_copy_width = cts.cts_vcol; + verbatim_copy_end = (char_u *)cts.cts_ptr; + clear_chartabsize_arg(&cts); // If "destination_col" is different from the width of the initial // part of the line that will be copied, it means we encountered a tab @@ -512,7 +527,7 @@ static void shift_block(oparg_T *oap, int amount) /// Caller must prepare for undo. static void block_insert(oparg_T *oap, char_u *s, int b_insert, struct block_def *bdp) { - int p_ts; + 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 @@ -531,18 +546,18 @@ static void block_insert(oparg_T *oap, char_u *s, int b_insert, struct block_def oldp = ml_get(lnum); if (b_insert) { - p_ts = bdp->start_char_vcols; + ts_val = bdp->start_char_vcols; spaces = bdp->startspaces; if (spaces != 0) { - count = p_ts - 1; // we're cutting a TAB + count = ts_val - 1; // we're cutting a TAB } offset = bdp->textcol; } else { // append - p_ts = bdp->end_char_vcols; + ts_val = bdp->end_char_vcols; if (!bdp->is_short) { // spaces = padding after block - spaces = (bdp->endspaces ? p_ts - bdp->endspaces : 0); + spaces = (bdp->endspaces ? ts_val - bdp->endspaces : 0); if (spaces != 0) { - count = p_ts - 1; // we're cutting a TAB + count = ts_val - 1; // we're cutting a TAB } offset = bdp->textcol + bdp->textlen - (spaces != 0); } else { // spaces = padding to block edge @@ -566,7 +581,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 - + (spaces > 0 && !bdp->is_short ? (size_t)p_ts - (size_t)spaces : 0) + + (spaces > 0 && !bdp->is_short ? (size_t)ts_val - (size_t)spaces : 0) + (size_t)count + 1); // copy up to shifted part @@ -585,7 +600,7 @@ static void block_insert(oparg_T *oap, char_u *s, int b_insert, struct block_def if (spaces > 0 && !bdp->is_short) { if (*oldp == TAB) { // insert post-padding - memset(newp + offset + spaces, ' ', (size_t)(p_ts - spaces)); + memset(newp + offset + spaces, ' ', (size_t)(ts_val - spaces)); // We're splitting a TAB, don't copy it. oldp++; // We allowed for that TAB, remember this now @@ -684,7 +699,7 @@ void op_reindent(oparg_T *oap, Indenter how) oap->is_VIsual ? start_lnum + (linenr_T)oap->line_count : last_changed + 1, 0L, true); } else if (oap->is_VIsual) { - redraw_curbuf_later(INVERTED); + redraw_curbuf_later(UPD_INVERTED); } if (oap->line_count > p_report) { @@ -911,7 +926,7 @@ int do_record(int c) if (!ui_has_messages()) { // Enable macro indicator temporarily set_option_value("ch", 1L, NULL, 0); - update_screen(VALID); + update_screen(UPD_VALID); changed_cmdheight = true; } @@ -965,7 +980,7 @@ int do_record(int c) if (changed_cmdheight) { // Restore cmdheight set_option_value("ch", 0L, NULL, 0); - redraw_all_later(CLEAR); + redraw_all_later(UPD_CLEAR); } } return retval; @@ -998,14 +1013,14 @@ 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_u **pp = (char_u **)&(reg->y_array[reg->y_size - 1]); + char **pp = &(reg->y_array[reg->y_size - 1]); char_u *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 = lp; + *pp = (char *)lp; } else { free_register(reg); set_yreg_additional_data(reg, NULL); @@ -1112,7 +1127,7 @@ 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(last_cmdline, + p = vim_strsave_escaped_ext((char_u *)last_cmdline, (char_u *)"\001\002\003\004\005\006\007" "\010\011\012\013\014\015\016\017" "\020\021\022\023\024\025\026\027" @@ -1277,7 +1292,7 @@ int insert_reg(int regname, bool literally_arg) return FAIL; } - char_u *arg; + char *arg; if (regname == '.') { // Insert last inserted text. retval = stuff_inserted(NUL, 1L, true); } else if (get_spec_reg(regname, &arg, &allocated, true)) { @@ -1313,34 +1328,6 @@ int insert_reg(int regname, bool literally_arg) return retval; } -/// Stuff a string into the typeahead buffer, such that edit() will insert it -/// literally ("literally" true) or interpret is as typed characters. -static void stuffescaped(const char *arg, bool literally) -{ - while (*arg != NUL) { - // Stuff a sequence of normal ASCII characters, that's fast. Also - // stuff K_SPECIAL to get the effect of a special key when "literally" - // is true. - const char *const start = arg; - while ((*arg >= ' ' && *arg < DEL) || ((uint8_t)(*arg) == K_SPECIAL - && !literally)) { - arg++; - } - if (arg > start) { - stuffReadbuffLen(start, (arg - start)); - } - - // stuff a single special character - if (*arg != NUL) { - const int c = mb_cptr2char_adv((const char_u **)&arg); - if (literally && ((c < ' ' && c != TAB) || c == DEL)) { - stuffcharReadbuff(Ctrl_V); - } - stuffcharReadbuff(c); - } - } -} - /// If "regname" is a special register, return true and store a pointer to its /// value in "argp". /// @@ -1348,7 +1335,7 @@ static void stuffescaped(const char *arg, bool literally) /// @param errmsg give error message when failing /// /// @return true if "regname" is a special register, -bool get_spec_reg(int regname, char_u **argp, bool *allocated, bool errmsg) +bool get_spec_reg(int regname, char **argp, bool *allocated, bool errmsg) { size_t cnt; @@ -1359,15 +1346,15 @@ bool get_spec_reg(int regname, char_u **argp, bool *allocated, bool errmsg) if (errmsg) { check_fname(); // will give emsg if not set } - *argp = (char_u *)curbuf->b_fname; + *argp = curbuf->b_fname; return true; case '#': // alternate file name - *argp = (char_u *)getaltfname(errmsg); // may give emsg if not set + *argp = getaltfname(errmsg); // may give emsg if not set return true; case '=': // result of expression - *argp = get_expr_line(); + *argp = (char *)get_expr_line(); *allocated = true; return true; @@ -1382,11 +1369,11 @@ bool get_spec_reg(int regname, char_u **argp, bool *allocated, bool errmsg) if (last_search_pat() == NULL && errmsg) { emsg(_(e_noprevre)); } - *argp = last_search_pat(); + *argp = (char *)last_search_pat(); return true; case '.': // last inserted text - *argp = get_last_insert_save(); + *argp = (char *)get_last_insert_save(); *allocated = true; if (*argp == NULL && errmsg) { emsg(_(e_noinstext)); @@ -1398,8 +1385,9 @@ bool get_spec_reg(int regname, char_u **argp, bool *allocated, bool errmsg) if (!errmsg) { return false; } - *argp = file_name_at_cursor(FNAME_MESS | FNAME_HYP | (regname == Ctrl_P ? FNAME_EXP : 0), - 1L, NULL); + *argp + = (char *)file_name_at_cursor(FNAME_MESS | FNAME_HYP | (regname == Ctrl_P ? FNAME_EXP : 0), + 1L, NULL); *allocated = true; return true; @@ -1411,7 +1399,7 @@ bool get_spec_reg(int regname, char_u **argp, bool *allocated, bool errmsg) cnt = find_ident_under_cursor(argp, (regname == Ctrl_W ? (FIND_IDENT|FIND_STRING) : FIND_STRING)); - *argp = cnt ? vim_strnsave(*argp, cnt) : NULL; + *argp = cnt ? xstrnsave(*argp, cnt) : NULL; *allocated = true; return true; @@ -1420,11 +1408,11 @@ bool get_spec_reg(int regname, char_u **argp, bool *allocated, bool errmsg) return false; } - *argp = ml_get_buf(curwin->w_buffer, curwin->w_cursor.lnum, false); + *argp = (char *)ml_get_buf(curwin->w_buffer, curwin->w_cursor.lnum, false); return true; case '_': // black hole: always empty - *argp = (char_u *)""; + *argp = ""; return true; } @@ -1578,6 +1566,7 @@ int op_delete(oparg_T *oap) // Put deleted text into register 1 and shift number registers if the // delete contains a line break, or when using a specific operator (Vi // compatible) + if (oap->motion_type == kMTLineWise || oap->line_count > 1 || oap->use_reg_one) { shift_delete_registers(is_append_register(oap->regname)); reg = &y_regs[1]; @@ -1927,8 +1916,8 @@ static int op_replace(oparg_T *oap, int c) // times. if (utf_char2cells(c) > 1) { if ((numc & 1) && !bd.is_short) { - ++bd.endspaces; - ++n; + bd.endspaces++; + n++; } numc = numc / 2; } @@ -2003,7 +1992,7 @@ static int op_replace(oparg_T *oap, int c) curwin->w_cursor.col = 0; oap->end.col = (colnr_T)STRLEN(ml_get(oap->end.lnum)); if (oap->end.col) { - --oap->end.col; + oap->end.col--; } } else if (!oap->inclusive) { dec(&(oap->end)); @@ -2091,7 +2080,7 @@ void op_tilde(oparg_T *oap) { pos_T pos; struct block_def bd; - int did_change = FALSE; + int did_change = false; if (u_save((linenr_T)(oap->start.lnum - 1), (linenr_T)(oap->end.lnum + 1)) == FAIL) { @@ -2117,7 +2106,7 @@ void op_tilde(oparg_T *oap) pos.col = 0; oap->end.col = (colnr_T)STRLEN(ml_get(oap->end.lnum)); if (oap->end.col) { - --oap->end.col; + oap->end.col--; } } else if (!oap->inclusive) { dec(&(oap->end)); @@ -2144,7 +2133,7 @@ void op_tilde(oparg_T *oap) if (!did_change && oap->is_VIsual) { // No change: need to remove the Visual selection - redraw_curbuf_later(INVERTED); + redraw_curbuf_later(UPD_INVERTED); } if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0) { @@ -2166,7 +2155,7 @@ void op_tilde(oparg_T *oap) /// @param length is rounded up to include the whole last multi-byte character. /// Also works correctly when the number of bytes changes. /// -/// @return TRUE if some character was changed. +/// @return true if some character was changed. static int swapchars(int op_type, pos_T *pos, int length) FUNC_ATTR_NONNULL_ALL { @@ -2263,7 +2252,7 @@ void op_insert(oparg_T *oap, long count1) // vis block is still marked. Get rid of it now. curwin->w_cursor.lnum = oap->start.lnum; - update_screen(INVERTED); + update_screen(UPD_INVERTED); if (oap->motion_type == kMTBlockWise) { // When 'virtualedit' is used, need to insert the extra spaces before @@ -2283,7 +2272,7 @@ void op_insert(oparg_T *oap, long count1) coladvance_force(oap->op_type == OP_APPEND ? oap->end_vcol + 1 : getviscol()); if (oap->op_type == OP_APPEND) { - --curwin->w_cursor.col; + curwin->w_cursor.col--; } curwin->w_ve_flags = old_ve_flags; } @@ -2304,10 +2293,10 @@ void op_insert(oparg_T *oap, long count1) if (oap->motion_type == kMTBlockWise && curwin->w_cursor.coladd == 0) { // Move the cursor to the character right of the block. - curwin->w_set_curswant = TRUE; + curwin->w_set_curswant = true; while (*get_cursor_pos_ptr() != NUL && (curwin->w_cursor.col < bd.textcol + bd.textlen)) { - ++curwin->w_cursor.col; + curwin->w_cursor.col++; } if (bd.is_short && !bd.is_MAX) { // First line was too short, make it longer and adjust the @@ -2413,7 +2402,7 @@ void op_insert(oparg_T *oap, long count1) if (oap->op_type == OP_APPEND) { pre_textlen += bd2.textlen - bd.textlen; if (bd2.endspaces) { - --bd2.textlen; + bd2.textlen--; } } bd.textcol = bd2.textcol; @@ -2464,7 +2453,7 @@ void op_insert(oparg_T *oap, long count1) /// handle a change operation /// -/// @return TRUE if edit() returns because of a CTRL-O command +/// @return true if edit() returns because of a CTRL-O command int op_change(oparg_T *oap) { colnr_T l; @@ -2490,10 +2479,10 @@ int op_change(oparg_T *oap) // save for undo if (curbuf->b_ml.ml_flags & ML_EMPTY) { if (u_save_cursor() == FAIL) { - return FALSE; + return false; } } else if (op_delete(oap) == FAIL) { - return FALSE; + return false; } if ((l > curwin->w_cursor.col) && !LINEEMPTY(curwin->w_cursor.lnum) @@ -2511,7 +2500,7 @@ int op_change(oparg_T *oap) } firstline = ml_get(oap->start.lnum); pre_textlen = (long)STRLEN(firstline); - pre_indent = (long)getwhitecols(firstline); + pre_indent = (long)getwhitecols((char *)firstline); bd.textcol = curwin->w_cursor.col; } @@ -2519,7 +2508,7 @@ int op_change(oparg_T *oap) fix_indent(); } - retval = edit(NUL, FALSE, (linenr_T)1); + retval = edit(NUL, false, (linenr_T)1); /* * In Visual block mode, handle copying the new text to all lines of the @@ -2532,7 +2521,7 @@ int op_change(oparg_T *oap) // the indent, exclude that indent change from the inserted text. firstline = ml_get(oap->start.lnum); if (bd.textcol > (colnr_T)pre_indent) { - long new_indent = (long)getwhitecols(firstline); + long new_indent = (long)getwhitecols((char *)firstline); pre_textlen += new_indent - pre_indent; bd.textcol += (colnr_T)(new_indent - pre_indent); @@ -2961,7 +2950,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) int delcount; int incr = 0; struct block_def bd; - char_u **y_array = NULL; + char **y_array = NULL; linenr_T nr_lines = 0; pos_T new_cursor; int indent; @@ -2970,7 +2959,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) bool first_indent = true; int lendiff = 0; pos_T old_pos; - char_u *insert_string = NULL; + char *insert_string = NULL; bool allocated = false; long cnt; const pos_T orig_start = curbuf->b_op_start; @@ -3093,10 +3082,10 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) * Loop twice: count the number of lines and save them. */ for (;;) { y_size = 0; - ptr = insert_string; + ptr = (char_u *)insert_string; while (ptr != NULL) { if (y_array != NULL) { - y_array[y_size] = ptr; + y_array[y_size] = (char *)ptr; } y_size++; ptr = (char_u *)vim_strchr((char *)ptr, '\n'); @@ -3104,7 +3093,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) if (y_array != NULL) { *ptr = NUL; } - ++ptr; + ptr++; // A trailing '\n' makes the register linewise. if (*ptr == NUL) { y_type = kMTLineWise; @@ -3115,7 +3104,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) if (y_array != NULL) { break; } - y_array = (char_u **)xmalloc(y_size * sizeof(char_u *)); + y_array = xmalloc(y_size * sizeof(char_u *)); } } else { y_size = 1; // use fake one-line yank register @@ -3132,7 +3121,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) y_type = reg->y_type; y_width = reg->y_width; y_size = reg->y_size; - y_array = (char_u **)reg->y_array; + y_array = reg->y_array; } if (curbuf->terminal) { @@ -3308,12 +3297,19 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) // get the old line and advance to the position to insert at oldp = get_cursor_line_ptr(); oldlen = STRLEN(oldp); - for (ptr = oldp; vcol < col && *ptr;) { + chartabsize_T cts; + init_chartabsize_arg(&cts, curwin, curwin->w_cursor.lnum, 0, + oldp, oldp); + + while (cts.cts_vcol < col && *cts.cts_ptr != NUL) { // Count a tab for what it's worth (if list mode not on) - incr = lbr_chartabsize_adv(oldp, &ptr, vcol); - vcol += incr; + incr = lbr_chartabsize_adv(&cts); + cts.cts_vcol += incr; } + vcol = cts.cts_vcol; + ptr = (char_u *)cts.cts_ptr; bd.textcol = (colnr_T)(ptr - oldp); + clear_chartabsize_arg(&cts); shortline = (vcol < col) || (vcol == col && !*ptr); @@ -3322,7 +3318,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) } else if (vcol > col) { bd.endspaces = vcol - col; bd.startspaces = incr - bd.endspaces; - --bd.textcol; + bd.textcol--; delcount = 1; bd.textcol -= utf_head_off(oldp, oldp + bd.textcol); if (oldp[bd.textcol] != TAB) { @@ -3340,9 +3336,14 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) // calculate number of spaces required to fill right side of // block spaces = y_width + 1; + init_chartabsize_arg(&cts, curwin, 0, 0, + (char_u *)y_array[i], (char_u *)y_array[i]); for (int j = 0; j < yanklen; j++) { - spaces -= lbr_chartabsize(NULL, &y_array[i][j], 0); + spaces -= lbr_chartabsize(&cts); + cts.cts_ptr++; + cts.cts_vcol = 0; } + clear_chartabsize_arg(&cts); if (spaces < 0) { spaces = 0; } @@ -3395,7 +3396,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) extmark_splice_cols(curbuf, (int)curwin->w_cursor.lnum - 1, bd.textcol, delcount, (int)totlen + lines_appended, kExtmarkUndo); - ++curwin->w_cursor.lnum; + curwin->w_cursor.lnum++; if (i == 0) { curwin->w_cursor.col += bd.startspaces; } @@ -3442,12 +3443,9 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) } } curbuf->b_op_start = curwin->w_cursor; - } - /* - * Line mode: BACKWARD is the same as FORWARD on the previous line - */ - else if (dir == BACKWARD) { - --lnum; + } else if (dir == BACKWARD) { + // Line mode: BACKWARD is the same as FORWARD on the previous line + lnum--; } new_cursor = curwin->w_cursor; @@ -3580,13 +3578,13 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) for (; i < y_size; i++) { if ((y_type != kMTCharWise || i < y_size - 1)) { - if (ml_append(lnum, (char *)y_array[i], (colnr_T)0, false) == FAIL) { + if (ml_append(lnum, y_array[i], (colnr_T)0, false) == FAIL) { goto error; } new_lnum++; } lnum++; - ++nr_lines; + nr_lines++; if (flags & PUT_FIXINDENT) { old_pos = curwin->w_cursor; curwin->w_cursor.lnum = lnum; @@ -3670,8 +3668,8 @@ error: if (col > 1) { curbuf->b_op_end.col = col - 1; if (len > 0) { - curbuf->b_op_end.col -= utf_head_off(y_array[y_size - 1], - y_array[y_size - 1] + len - 1); + curbuf->b_op_end.col -= utf_head_off((char_u *)y_array[y_size - 1], + (char_u *)y_array[y_size - 1] + len - 1); } } else { curbuf->b_op_end.col = 0; @@ -3702,7 +3700,7 @@ error: // put cursor on first non-blank in first inserted line curwin->w_cursor.col = 0; if (dir == FORWARD) { - ++curwin->w_cursor.lnum; + curwin->w_cursor.lnum++; } beginline(BL_WHITE | BL_FIX); } else { // put cursor on first inserted character @@ -3712,7 +3710,7 @@ error: } msgmore(nr_lines); - curwin->w_set_curswant = TRUE; + curwin->w_set_curswant = true; end: if (cmdmod.cmod_flags & CMOD_LOCKMARKS) { @@ -3726,7 +3724,7 @@ end: xfree(y_array); } - VIsual_active = FALSE; + VIsual_active = false; // If the cursor is past the end of the line put it at the end. adjust_cursor_eol(); @@ -3755,7 +3753,7 @@ void adjust_cursor_eol(void) } } -/// @return TRUE if lines starting with '#' should be left aligned. +/// @return true if lines starting with '#' should be left aligned. int preprocs_left(void) { return ((curbuf->b_p_si && !curbuf->b_p_cin) @@ -3841,7 +3839,7 @@ void ex_display(exarg_T *eap) bool do_show = false; for (size_t j = 0; !do_show && j < yb->y_size; j++) { - do_show = !message_filtered((char_u *)yb->y_array[j]); + do_show = !message_filtered(yb->y_array[j]); } if (do_show || yb->y_size == 0) { @@ -3862,7 +3860,7 @@ void ex_display(exarg_T *eap) 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(p, clen); + msg_outtrans_len((char *)p, clen); p += clen - 1; } } @@ -3878,7 +3876,7 @@ 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(p)) { + && !message_filtered((char *)p)) { msg_puts("\n c \". "); dis_msg(p, true); } @@ -3887,13 +3885,13 @@ void ex_display(exarg_T *eap) if (last_cmdline != NULL && (arg == NULL || vim_strchr((char *)arg, ':') != NULL) && !got_int && !message_filtered(last_cmdline)) { msg_puts("\n c \": "); - dis_msg(last_cmdline, false); + dis_msg((char_u *)last_cmdline, false); } // display current file name if (curbuf->b_fname != NULL && (arg == NULL || vim_strchr((char *)arg, '%') != NULL) && !got_int - && !message_filtered((char_u *)curbuf->b_fname)) { + && !message_filtered(curbuf->b_fname)) { msg_puts("\n c \"% "); dis_msg((char_u *)curbuf->b_fname, false); } @@ -3903,7 +3901,7 @@ void ex_display(exarg_T *eap) char *fname; linenr_T dummy; - if (buflist_name_nr(0, &fname, &dummy) != FAIL && !message_filtered((char_u *)fname)) { + if (buflist_name_nr(0, &fname, &dummy) != FAIL && !message_filtered(fname)) { msg_puts("\n c \"# "); dis_msg((char_u *)fname, false); } @@ -3912,14 +3910,14 @@ 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(last_search_pat())) { + && !message_filtered((char *)last_search_pat())) { msg_puts("\n c \"/ "); 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(expr_line)) { + && !got_int && !message_filtered((char *)expr_line)) { msg_puts("\n c \"= "); dis_msg(expr_line, false); } @@ -3940,10 +3938,10 @@ static void dis_msg(const char_u *p, bool skip_esc) && !(*p == ESC && skip_esc && *(p + 1) == NUL) && (n -= ptr2cells((char *)p)) >= 0) { if ((l = utfc_ptr2len((char *)p)) > 1) { - msg_outtrans_len(p, l); + msg_outtrans_len((char *)p, l); p += l; } else { - msg_outtrans_len(p++, 1); + msg_outtrans_len((char *)p++, 1); } } os_breakcheck(); @@ -3962,9 +3960,9 @@ static void dis_msg(const char_u *p, bool skip_esc) /// comment. char_u *skip_comment(char_u *line, bool process, bool include_space, bool *is_comment) { - char_u *comment_flags = NULL; + char *comment_flags = NULL; int lead_len; - int leader_offset = get_last_leader_offset((char *)line, (char **)&comment_flags); + int leader_offset = get_last_leader_offset((char *)line, &comment_flags); *is_comment = false; if (leader_offset != -1) { @@ -3986,7 +3984,7 @@ char_u *skip_comment(char_u *line, bool process, bool include_space, bool *is_co return line; } - lead_len = get_leader_len((char *)line, (char **)&comment_flags, false, include_space); + lead_len = get_leader_len((char *)line, &comment_flags, false, include_space); if (lead_len == 0) { return line; @@ -4001,7 +3999,7 @@ char_u *skip_comment(char_u *line, bool process, bool include_space, bool *is_co || *comment_flags == ':') { break; } - ++comment_flags; + comment_flags++; } // If we found a colon, it means that we are not processing a line @@ -4015,8 +4013,8 @@ char_u *skip_comment(char_u *line, bool process, bool include_space, bool *is_co } /// @param count number of lines (minimal 2) to join at cursor position. -/// @param save_undo when TRUE, save lines for undo first. -/// @param use_formatoptions set to FALSE when e.g. processing backspace and comment +/// @param save_undo when true, save lines for undo first. +/// @param use_formatoptions set to false when e.g. processing backspace and comment /// leaders should not be removed. /// @param setmark when true, sets the '[ and '] mark, else, the caller is expected /// to set those marks. @@ -4037,7 +4035,7 @@ int do_join(size_t count, int insert_space, int save_undo, int use_formatoptions colnr_T col = 0; int ret = OK; int *comments = NULL; - int remove_comments = (use_formatoptions == TRUE) + int remove_comments = (use_formatoptions == true) && has_format_option(FO_REMOVE_COMS); bool prev_was_comment = false; assert(count >= 1); @@ -4092,11 +4090,11 @@ int do_join(size_t count, int insert_space, int save_undo, int use_formatoptions if (endcurr1 == ' ') { endcurr1 = endcurr2; } else { - ++spaces[t]; + spaces[t]++; } // Extra space when 'joinspaces' set and line ends in '.', '?', or '!'. if (p_js && (endcurr1 == '.' || endcurr1 == '?' || endcurr1 == '!')) { - ++spaces[t]; + spaces[t]++; } } } @@ -4212,7 +4210,7 @@ int do_join(size_t count, int insert_space, int save_undo, int use_formatoptions check_cursor_col(); curwin->w_cursor.coladd = 0; - curwin->w_set_curswant = TRUE; + curwin->w_set_curswant = true; theend: xfree(spaces); @@ -4222,532 +4220,6 @@ theend: return ret; } -/// @return TRUE if the two comment leaders given are the same. -/// -/// @param lnum The first line. White-space is ignored. -/// -/// @note the whole of 'leader1' must match 'leader2_len' characters from 'leader2'. -static int same_leader(linenr_T lnum, int leader1_len, char_u *leader1_flags, int leader2_len, - char_u *leader2_flags) -{ - int idx1 = 0, idx2 = 0; - char_u *p; - char_u *line1; - char_u *line2; - - if (leader1_len == 0) { - return leader2_len == 0; - } - - /* - * If first leader has 'f' flag, the lines can be joined only if the - * second line does not have a leader. - * If first leader has 'e' flag, the lines can never be joined. - * If first leader has 's' flag, the lines can only be joined if there is - * some text after it and the second line has the 'm' flag. - */ - if (leader1_flags != NULL) { - for (p = leader1_flags; *p && *p != ':'; ++p) { - if (*p == COM_FIRST) { - return leader2_len == 0; - } - if (*p == COM_END) { - return FALSE; - } - if (*p == COM_START) { - if (*(ml_get(lnum) + leader1_len) == NUL) { - return FALSE; - } - if (leader2_flags == NULL || leader2_len == 0) { - return FALSE; - } - for (p = leader2_flags; *p && *p != ':'; ++p) { - if (*p == COM_MIDDLE) { - return TRUE; - } - } - return FALSE; - } - } - } - - /* - * Get current line and next line, compare the leaders. - * The first line has to be saved, only one line can be locked at a time. - */ - line1 = vim_strsave(ml_get(lnum)); - for (idx1 = 0; ascii_iswhite(line1[idx1]); idx1++) {} - line2 = ml_get(lnum + 1); - for (idx2 = 0; idx2 < leader2_len; ++idx2) { - if (!ascii_iswhite(line2[idx2])) { - if (line1[idx1++] != line2[idx2]) { - break; - } - } else { - while (ascii_iswhite(line1[idx1])) { - ++idx1; - } - } - } - xfree(line1); - - return idx2 == leader2_len && idx1 == leader1_len; -} - -/// Implementation of the format operator 'gq'. -/// -/// @param keep_cursor keep cursor on same text char -static void op_format(oparg_T *oap, int keep_cursor) -{ - linenr_T old_line_count = curbuf->b_ml.ml_line_count; - - // Place the cursor where the "gq" or "gw" command was given, so that "u" - // can put it back there. - curwin->w_cursor = oap->cursor_start; - - if (u_save((linenr_T)(oap->start.lnum - 1), - (linenr_T)(oap->end.lnum + 1)) == FAIL) { - return; - } - curwin->w_cursor = oap->start; - - if (oap->is_VIsual) { - // When there is no change: need to remove the Visual selection - redraw_curbuf_later(INVERTED); - } - - if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0) { - // Set '[ mark at the start of the formatted area - curbuf->b_op_start = oap->start; - } - - // For "gw" remember the cursor position and put it back below (adjusted - // for joined and split lines). - if (keep_cursor) { - saved_cursor = oap->cursor_start; - } - - format_lines((linenr_T)oap->line_count, keep_cursor); - - /* - * Leave the cursor at the first non-blank of the last formatted line. - * If the cursor was moved one line back (e.g. with "Q}") go to the next - * line, so "." will do the next lines. - */ - if (oap->end_adjusted && curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) { - ++curwin->w_cursor.lnum; - } - beginline(BL_WHITE | BL_FIX); - old_line_count = curbuf->b_ml.ml_line_count - old_line_count; - msgmore(old_line_count); - - if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0) { - // put '] mark on the end of the formatted area - curbuf->b_op_end = curwin->w_cursor; - } - - if (keep_cursor) { - curwin->w_cursor = saved_cursor; - saved_cursor.lnum = 0; - - // formatting may have made the cursor position invalid - check_cursor(); - } - - if (oap->is_VIsual) { - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->w_old_cursor_lnum != 0) { - // When lines have been inserted or deleted, adjust the end of - // the Visual area to be redrawn. - if (wp->w_old_cursor_lnum > wp->w_old_visual_lnum) { - wp->w_old_cursor_lnum += old_line_count; - } else { - wp->w_old_visual_lnum += old_line_count; - } - } - } - } -} - -/// Implementation of the format operator 'gq' for when using 'formatexpr'. -static void op_formatexpr(oparg_T *oap) -{ - if (oap->is_VIsual) { - // When there is no change: need to remove the Visual selection - redraw_curbuf_later(INVERTED); - } - - if (fex_format(oap->start.lnum, oap->line_count, NUL) != 0) { - // As documented: when 'formatexpr' returns non-zero fall back to - // internal formatting. - op_format(oap, false); - } -} - -/// @param c character to be inserted -int fex_format(linenr_T lnum, long count, int c) -{ - int use_sandbox = was_set_insecurely(curwin, "formatexpr", OPT_LOCAL); - int r; - char_u *fex; - - /* - * Set v:lnum to the first line number and v:count to the number of lines. - * Set v:char to the character to be inserted (can be NUL). - */ - set_vim_var_nr(VV_LNUM, (varnumber_T)lnum); - set_vim_var_nr(VV_COUNT, (varnumber_T)count); - set_vim_var_char(c); - - // Make a copy, the option could be changed while calling it. - fex = vim_strsave(curbuf->b_p_fex); - // Evaluate the function. - if (use_sandbox) { - sandbox++; - } - r = (int)eval_to_number((char *)fex); - if (use_sandbox) { - sandbox--; - } - - set_vim_var_string(VV_CHAR, NULL, -1); - xfree(fex); - - return r; -} - -/// @param line_count number of lines to format, starting at the cursor position. -/// when negative, format until the end of the paragraph. -/// -/// Lines after the cursor line are saved for undo, caller must have saved the -/// first line. -/// -/// @param avoid_fex don't use 'formatexpr' -void format_lines(linenr_T line_count, int avoid_fex) -{ - bool is_not_par; // current line not part of parag. - bool next_is_not_par; // next line not part of paragraph - bool is_end_par; // at end of paragraph - bool prev_is_end_par = false; // prev. line not part of parag. - 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 - bool advance = true; - int second_indent = -1; // indent for second line (comment aware) - bool first_par_line = true; - int smd_save; - long count; - bool need_set_indent = true; // set indent of next paragraph - linenr_T first_line = curwin->w_cursor.lnum; - bool force_format = false; - const int old_State = State; - - // length of a line to force formatting: 3 * 'tw' - const int max_len = comp_textwidth(true) * 3; - - // check for 'q', '2' and '1' in 'formatoptions' - const bool do_comments = has_format_option(FO_Q_COMS); // format comments - int do_comments_list = 0; // format comments with 'n' or '2' - const bool do_second_indent = has_format_option(FO_Q_SECOND); - const bool do_number_indent = has_format_option(FO_Q_NUMBER); - const bool do_trail_white = has_format_option(FO_WHITE_PAR); - - // Get info about the previous and current line. - if (curwin->w_cursor.lnum > 1) { - is_not_par = fmt_check_par(curwin->w_cursor.lnum - 1, - &leader_len, &leader_flags, do_comments); - } else { - is_not_par = true; - } - next_is_not_par = fmt_check_par(curwin->w_cursor.lnum, - &next_leader_len, &next_leader_flags, do_comments - ); - is_end_par = (is_not_par || next_is_not_par); - if (!is_end_par && do_trail_white) { - is_end_par = !ends_in_white(curwin->w_cursor.lnum - 1); - } - - curwin->w_cursor.lnum--; - for (count = line_count; count != 0 && !got_int; --count) { - /* - * Advance to next paragraph. - */ - if (advance) { - curwin->w_cursor.lnum++; - prev_is_end_par = is_end_par; - is_not_par = next_is_not_par; - leader_len = next_leader_len; - leader_flags = next_leader_flags; - } - - /* - * The last line to be formatted. - */ - if (count == 1 || curwin->w_cursor.lnum == curbuf->b_ml.ml_line_count) { - next_is_not_par = true; - next_leader_len = 0; - next_leader_flags = NULL; - } else { - next_is_not_par = fmt_check_par(curwin->w_cursor.lnum + 1, - &next_leader_len, &next_leader_flags, do_comments - ); - if (do_number_indent) { - next_is_start_par = - (get_number_indent(curwin->w_cursor.lnum + 1) > 0); - } - } - advance = true; - is_end_par = (is_not_par || next_is_not_par || next_is_start_par); - if (!is_end_par && do_trail_white) { - is_end_par = !ends_in_white(curwin->w_cursor.lnum); - } - - /* - * Skip lines that are not in a paragraph. - */ - if (is_not_par) { - if (line_count < 0) { - break; - } - } else { - /* - * For the first line of a paragraph, check indent of second line. - * Don't do this for comments and empty lines. - */ - if (first_par_line - && (do_second_indent || do_number_indent) - && prev_is_end_par - && curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) { - if (do_second_indent && !LINEEMPTY(curwin->w_cursor.lnum + 1)) { - if (leader_len == 0 && next_leader_len == 0) { - // no comment found - second_indent = - get_indent_lnum(curwin->w_cursor.lnum + 1); - } else { - second_indent = next_leader_len; - do_comments_list = 1; - } - } else if (do_number_indent) { - if (leader_len == 0 && next_leader_len == 0) { - // no comment found - second_indent = - get_number_indent(curwin->w_cursor.lnum); - } else { - // get_number_indent() is now "comment aware"... - second_indent = - get_number_indent(curwin->w_cursor.lnum); - do_comments_list = 1; - } - } - } - - /* - * 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, leader_flags, - next_leader_len, - 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 - || check_linecomment(get_cursor_line_ptr()) == MAXCOL) { - is_end_par = true; - } - } - - /* - * If we have got to the end of a paragraph, or the line is - * getting long, format it. - */ - if (is_end_par || force_format) { - if (need_set_indent) { - int indent = 0; // amount of indent needed - - // Replace indent in first line of a paragraph with minimal - // number of tabs and spaces, according to current options. - // For the very first formatted line keep the current - // indent. - if (curwin->w_cursor.lnum == first_line) { - indent = get_indent(); - } else if (curbuf->b_p_lisp) { - indent = get_lisp_indent(); - } else { - if (cindent_on()) { - indent = *curbuf->b_p_inde != NUL ? get_expr_indent() : get_c_indent(); - } else { - indent = get_indent(); - } - } - (void)set_indent(indent, SIN_CHANGED); - } - - // put cursor on last non-space - State = MODE_NORMAL; // don't go past end-of-line - coladvance(MAXCOL); - while (curwin->w_cursor.col && ascii_isspace(gchar_cursor())) { - dec_cursor(); - } - - // do the formatting, without 'showmode' - State = MODE_INSERT; // for open_line() - smd_save = p_smd; - p_smd = FALSE; - insertchar(NUL, INSCHAR_FORMAT - + (do_comments ? INSCHAR_DO_COM : 0) - + (do_comments && do_comments_list - ? INSCHAR_COM_LIST : 0) - + (avoid_fex ? INSCHAR_NO_FEX : 0), second_indent); - State = old_State; - p_smd = smd_save; - second_indent = -1; - // at end of par.: need to set indent of next par. - need_set_indent = is_end_par; - if (is_end_par) { - // When called with a negative line count, break at the - // end of the paragraph. - if (line_count < 0) { - break; - } - first_par_line = true; - } - force_format = false; - } - - /* - * When still in same paragraph, join the lines together. But - * first delete the leader from the second line. - */ - if (!is_end_par) { - advance = false; - curwin->w_cursor.lnum++; - curwin->w_cursor.col = 0; - if (line_count < 0 && u_save_cursor() == FAIL) { - break; - } - if (next_leader_len > 0) { - (void)del_bytes(next_leader_len, false, false); - mark_col_adjust(curwin->w_cursor.lnum, (colnr_T)0, 0L, - (long)-next_leader_len, 0); - } else if (second_indent > 0) { // the "leader" for FO_Q_SECOND - int indent = (int)getwhitecols_curline(); - - if (indent > 0) { - (void)del_bytes(indent, false, false); - mark_col_adjust(curwin->w_cursor.lnum, - (colnr_T)0, 0L, (long)-indent, 0); - } - } - curwin->w_cursor.lnum--; - if (do_join(2, TRUE, FALSE, FALSE, false) == FAIL) { - beep_flush(); - break; - } - first_par_line = false; - // If the line is getting long, format it next time - if (STRLEN(get_cursor_line_ptr()) > (size_t)max_len) { - force_format = true; - } else { - force_format = false; - } - } - } - line_breakcheck(); - } -} - -/// @return TRUE if line "lnum" ends in a white character. -static int ends_in_white(linenr_T lnum) -{ - char_u *s = ml_get(lnum); - size_t l; - - if (*s == NUL) { - return FALSE; - } - l = STRLEN(s) - 1; - return ascii_iswhite(s[l]); -} - -/// Blank lines, and lines containing only the comment leader, are left -/// untouched by the formatting. The function returns TRUE in this -/// case. It also returns TRUE when a line starts with the end of a comment -/// ('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, int do_comments) -{ - char_u *flags = NULL; // init for GCC - char_u *ptr; - - ptr = ml_get(lnum); - if (do_comments) { - *leader_len = get_leader_len((char *)ptr, (char **)leader_flags, false, true); - } else { - *leader_len = 0; - } - - if (*leader_len > 0) { - /* - * Search for 'e' flag in comment leader flags. - */ - flags = *leader_flags; - while (*flags && *flags != ':' && *flags != COM_END) { - ++flags; - } - } - - return *skipwhite((char *)ptr + *leader_len) == NUL - || (*leader_len > 0 && *flags == COM_END) - || startPS(lnum, NUL, FALSE); -} - -/// Used for auto-formatting. -/// -/// @return TRUE when a paragraph starts in line "lnum". -/// FALSE when the previous line is in the same paragraph. -int paragraph_start(linenr_T lnum) -{ - char_u *p; - int leader_len = 0; // leader len of current line - char_u *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 - - if (lnum <= 1) { - return TRUE; // start of the file - } - p = ml_get(lnum - 1); - if (*p == NUL) { - return TRUE; // after empty line - } - const bool do_comments = has_format_option(FO_Q_COMS); // format comments - if (fmt_check_par(lnum - 1, &leader_len, &leader_flags, do_comments)) { - return true; // after non-paragraph line - } - - if (fmt_check_par(lnum, &next_leader_len, &next_leader_flags, do_comments)) { - return true; // "lnum" is not a paragraph line - } - - if (has_format_option(FO_WHITE_PAR) && !ends_in_white(lnum - 1)) { - return TRUE; // missing trailing space in previous line. - } - 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, leader_flags, - next_leader_len, next_leader_flags)) { - return TRUE; // change of comment leader. - } - return FALSE; -} - /// prepare a few things for block mode yank/delete/tilde /// /// for delete: @@ -4784,22 +4256,28 @@ static void block_prep(oparg_T *oap, struct block_def *bdp, linenr_T lnum, bool bdp->start_char_vcols = 0; line = ml_get(lnum); - pstart = line; prev_pstart = line; - while (bdp->start_vcol < oap->start_vcol && *pstart) { + + chartabsize_T cts; + init_chartabsize_arg(&cts, curwin, lnum, bdp->start_vcol, line, line); + while (cts.cts_vcol < oap->start_vcol && *cts.cts_ptr != NUL) { // Count a tab for what it's worth (if list mode not on) - incr = lbr_chartabsize(line, pstart, bdp->start_vcol); - bdp->start_vcol += incr; - if (ascii_iswhite(*pstart)) { + incr = lbr_chartabsize(&cts); + cts.cts_vcol += incr; + if (ascii_iswhite(*cts.cts_ptr)) { bdp->pre_whitesp += incr; bdp->pre_whitesp_c++; } else { bdp->pre_whitesp = 0; bdp->pre_whitesp_c = 0; } - prev_pstart = pstart; - MB_PTR_ADV(pstart); + prev_pstart = (char_u *)cts.cts_ptr; + MB_PTR_ADV(cts.cts_ptr); } + bdp->start_vcol = cts.cts_vcol; + pstart = (char_u *)cts.cts_ptr; + clear_chartabsize_arg(&cts); + bdp->start_char_vcols = incr; if (bdp->start_vcol < oap->start_vcol) { // line too short bdp->end_vcol = bdp->start_vcol; @@ -4835,13 +4313,19 @@ static void block_prep(oparg_T *oap, struct block_def *bdp, linenr_T lnum, bool } } } else { + init_chartabsize_arg(&cts, curwin, lnum, bdp->end_vcol, + line, pend); prev_pend = pend; - while (bdp->end_vcol <= oap->end_vcol && *pend != NUL) { + while (cts.cts_vcol <= oap->end_vcol && *cts.cts_ptr != NUL) { // Count a tab for what it's worth (if list mode not on) - prev_pend = pend; - incr = lbr_chartabsize_adv(line, &pend, bdp->end_vcol); - bdp->end_vcol += incr; + prev_pend = (char_u *)cts.cts_ptr; + incr = lbr_chartabsize_adv(&cts); + cts.cts_vcol += incr; } + bdp->end_vcol = cts.cts_vcol; + pend = (char_u *)cts.cts_ptr; + clear_chartabsize_arg(&cts); + if (bdp->end_vcol <= oap->end_vcol && (!is_del || oap->op_type == OP_APPEND @@ -4966,7 +4450,7 @@ void op_addsub(oparg_T *oap, linenr_T Prenum1, bool g_cmd) if (!change_cnt && oap->is_VIsual) { // No change: need to remove the Visual selection - redraw_curbuf_later(INVERTED); + redraw_curbuf_later(UPD_INVERTED); } // Set '[ mark if something changed. Keep the last end @@ -5014,12 +4498,12 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1) pos_T endpos; colnr_T save_coladd = 0; - const bool do_hex = vim_strchr((char *)curbuf->b_p_nf, 'x') != NULL; // "heX" - const bool do_oct = vim_strchr((char *)curbuf->b_p_nf, 'o') != NULL; // "Octal" - const bool do_bin = vim_strchr((char *)curbuf->b_p_nf, 'b') != NULL; // "Bin" - const bool do_alpha = vim_strchr((char *)curbuf->b_p_nf, 'p') != NULL; // "alPha" + const bool do_hex = vim_strchr(curbuf->b_p_nf, 'x') != NULL; // "heX" + const bool do_oct = vim_strchr(curbuf->b_p_nf, 'o') != NULL; // "Octal" + const bool do_bin = vim_strchr(curbuf->b_p_nf, 'b') != NULL; // "Bin" + const bool do_alpha = vim_strchr(curbuf->b_p_nf, 'p') != NULL; // "alPha" // "Unsigned" - const bool do_unsigned = vim_strchr((char *)curbuf->b_p_nf, 'u') != NULL; + const bool do_unsigned = vim_strchr(curbuf->b_p_nf, 'u') != NULL; if (virtual_active()) { save_coladd = pos->coladd; @@ -5318,7 +4802,7 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1) } *ptr = NUL; STRCAT(buf1, buf2); - ins_str(buf1); // insert the new number + ins_str((char *)buf1); // insert the new number endpos = curwin->w_cursor; if (curwin->w_cursor.col) { curwin->w_cursor.col--; @@ -5457,16 +4941,16 @@ void *get_reg_contents(int regname, int flags) return NULL; } - char_u *retval; + char *retval; bool allocated; if (get_spec_reg(regname, &retval, &allocated, false)) { if (retval == NULL) { return NULL; } if (allocated) { - return get_reg_wrap_one_line(retval, flags); + return get_reg_wrap_one_line((char_u *)retval, flags); } - return get_reg_wrap_one_line(vim_strsave(retval), flags); + return get_reg_wrap_one_line(vim_strsave((char_u *)retval), flags); } yankreg_T *reg = get_yank_register(regname, YREG_PASTE); @@ -5557,11 +5041,11 @@ void write_reg_contents(int name, const char_u *str, ssize_t len, int must_appen write_reg_contents_ex(name, str, len, must_append, kMTUnknown, 0L); } -void write_reg_contents_lst(int name, char_u **strings, bool must_append, MotionType yank_type, +void write_reg_contents_lst(int name, char **strings, bool must_append, MotionType yank_type, colnr_T block_len) { if (name == '/' || name == '=') { - char_u *s = strings[0]; + char_u *s = (char_u *)strings[0]; if (strings[0] == NULL) { s = (char_u *)""; } else if (strings[1] != NULL) { @@ -5583,7 +5067,7 @@ void write_reg_contents_lst(int name, char_u **strings, bool must_append, Motion return; } - str_to_reg(reg, yank_type, (char_u *)strings, STRLEN((char_u *)strings), + str_to_reg(reg, yank_type, (char *)strings, STRLEN((char_u *)strings), block_len, true); finish_write_reg(name, reg, old_y_previous); } @@ -5615,7 +5099,7 @@ void write_reg_contents_ex(int name, const char_u *str, ssize_t len, bool must_a // Special case: '/' search pattern if (name == '/') { - set_last_search_pat(str, RE_SEARCH, TRUE, TRUE); + set_last_search_pat(str, RE_SEARCH, true, true); return; } @@ -5671,7 +5155,7 @@ void write_reg_contents_ex(int name, const char_u *str, ssize_t len, bool must_a if (!(reg = init_write_reg(name, &old_y_previous, must_append))) { return; } - str_to_reg(reg, yank_type, str, (size_t)len, block_len, false); + str_to_reg(reg, yank_type, (char *)str, (size_t)len, block_len, false); finish_write_reg(name, reg, old_y_previous); } @@ -5685,7 +5169,7 @@ void write_reg_contents_ex(int name, const char_u *str, ssize_t len, bool must_a /// @param len length of the string (Ignored when str_list=true.) /// @param blocklen width of visual block, or -1 for "I don't know." /// @param str_list True if str is `char_u **`. -static void str_to_reg(yankreg_T *y_ptr, MotionType yank_type, const char_u *str, size_t len, +static void str_to_reg(yankreg_T *y_ptr, MotionType yank_type, const char *str, size_t len, colnr_T blocklen, bool str_list) FUNC_ATTR_NONNULL_ALL { @@ -5705,18 +5189,18 @@ static void str_to_reg(yankreg_T *y_ptr, MotionType yank_type, const char_u *str // Count the number of lines within the string if (str_list) { - for (char_u **ss = (char_u **)str; *ss != NULL; ++ss) { + for (char_u **ss = (char_u **)str; *ss != NULL; ss++) { newlines++; } } else { newlines = memcnt(str, '\n', len); if (yank_type == kMTCharWise || len == 0 || str[len - 1] != '\n') { extraline = 1; - ++newlines; // count extra newline at the end + newlines++; // count extra newline at the end } if (y_ptr->y_size > 0 && y_ptr->y_type == kMTCharWise) { append = true; - --newlines; // uncount newline when appending first line + newlines--; // uncount newline when appending first line } } @@ -5727,9 +5211,8 @@ static void str_to_reg(yankreg_T *y_ptr, MotionType yank_type, const char_u *str } // Grow the register array to hold the pointers to the new lines. - char_u **pp = xrealloc(y_ptr->y_array, - (y_ptr->y_size + newlines) * sizeof(char_u *)); - y_ptr->y_array = (char **)pp; + char **pp = xrealloc(y_ptr->y_array, (y_ptr->y_size + newlines) * sizeof(char_u *)); + y_ptr->y_array = pp; size_t lnum = y_ptr->y_size; // The current line number. @@ -5738,7 +5221,7 @@ static void str_to_reg(yankreg_T *y_ptr, MotionType yank_type, const char_u *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) { + for (char_u **ss = (char_u **)str; *ss != NULL; ss++, lnum++) { size_t ss_len = STRLEN(*ss); pp[lnum] = xmemdupz(*ss, ss_len); if (ss_len > maxlen) { @@ -5747,7 +5230,7 @@ static void str_to_reg(yankreg_T *y_ptr, MotionType yank_type, const char_u *str } } else { size_t line_len; - for (const char_u *start = str, *end = str + len; + for (const char_u *start = (char_u *)str, *end = (char_u *)str + len; start < end + extraline; start += line_len + 1, lnum++) { assert(end - start >= 0); @@ -5770,7 +5253,7 @@ static void str_to_reg(yankreg_T *y_ptr, MotionType yank_type, const char_u *str xfree(pp[lnum]); append = false; // only first line is appended } - pp[lnum] = (char_u *)s; + pp[lnum] = s; // Convert NULs to '\n' to prevent truncation. memchrsub(pp[lnum], NUL, '\n', s_len); @@ -5789,7 +5272,7 @@ static void str_to_reg(yankreg_T *y_ptr, MotionType yank_type, const char_u *str void clear_oparg(oparg_T *oap) { - memset(oap, 0, sizeof(oparg_T)); + CLEAR_POINTER(oap); } /// Count the number of bytes, characters and "words" in a line. @@ -5888,12 +5371,12 @@ void cursor_pos_info(dict_T *dict) max_pos = VIsual; } if (*p_sel == 'e' && max_pos.col > 0) { - --max_pos.col; + max_pos.col--; } if (l_VIsual_mode == Ctrl_V) { - char_u *const saved_sbr = p_sbr; - char_u *const saved_w_sbr = curwin->w_p_sbr; + char *const saved_sbr = p_sbr; + char *const saved_w_sbr = curwin->w_p_sbr; // Make 'sbr' empty for a moment to get the correct size. p_sbr = empty_option; @@ -5901,8 +5384,7 @@ void cursor_pos_info(dict_T *dict) oparg.is_VIsual = true; oparg.motion_type = kMTBlockWise; oparg.op_type = OP_NOP; - getvcols(curwin, &min_pos, &max_pos, - &oparg.start_vcol, &oparg.end_vcol); + getvcols(curwin, &min_pos, &max_pos, &oparg.start_vcol, &oparg.end_vcol); p_sbr = saved_sbr; curwin->w_p_sbr = saved_w_sbr; if (curwin->w_curswant == MAXCOL) { @@ -5918,7 +5400,7 @@ void cursor_pos_info(dict_T *dict) line_count_selected = max_pos.lnum - min_pos.lnum + 1; } - for (lnum = 1; lnum <= curbuf->b_ml.ml_line_count; ++lnum) { + for (lnum = 1; lnum <= curbuf->b_ml.ml_line_count; lnum++) { // Check for a CTRL-C every 100000 characters. if (byte_count > last_check) { os_breakcheck(); @@ -6065,14 +5547,14 @@ void cursor_pos_info(dict_T *dict) } if (dict == NULL) { // Don't shorten this message, the user asked for it. - p = p_shm; - p_shm = (char_u *)""; + p = (char_u *)p_shm; + p_shm = ""; if (p_ch < 1) { msg_start(); msg_scroll = true; } msg((char *)IObuff); - p_shm = p; + p_shm = (char *)p; } } @@ -6140,6 +5622,23 @@ static void op_colon(oparg_T *oap) // do_cmdline() does the rest } +/// callback function for 'operatorfunc' +static Callback opfunc_cb; + +/// Process the 'operatorfunc' option value. +/// @return OK or FAIL +int set_operatorfunc_option(void) +{ + return option_set_callback_func((char_u *)p_opfunc, &opfunc_cb); +} + +#if defined(EXITFREE) +void free_operatorfunc_option(void) +{ + callback_free(&opfunc_cb); +} +#endif + /// Handle the "g@" operator: call 'operatorfunc'. static void op_function(const oparg_T *oap) FUNC_ATTR_NONNULL_ALL @@ -6177,7 +5676,10 @@ static void op_function(const oparg_T *oap) // Reset finish_op so that mode() returns the right value. finish_op = false; - (void)call_func_retnr((char *)p_opfunc, 1, argv); + typval_T rettv; + if (callback_call(&opfunc_cb, 1, argv, &rettv)) { + tv_clear(&rettv); + } virtual_op = save_virtual_op; finish_op = save_finish_op; @@ -6338,7 +5840,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) // If 'cpoptions' does not contain 'r', insert the search // pattern to really repeat the same command. if (vim_strchr(p_cpo, CPO_REDO) == NULL) { - AppendToRedobuffLit((char *)cap->searchbuf, -1); + AppendToRedobuffLit(cap->searchbuf, -1); } AppendToRedobuff(NL_STR); } else if (cap->cmdchar == ':' || cap->cmdchar == K_COMMAND) { @@ -6348,7 +5850,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) if (repeat_cmdline == NULL) { ResetRedobuff(); } else { - AppendToRedobuffLit((char *)repeat_cmdline, -1); + AppendToRedobuffLit(repeat_cmdline, -1); AppendToRedobuff(NL_STR); XFREE_CLEAR(repeat_cmdline); } @@ -6581,7 +6083,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) && oap->motion_force == NUL) { // Make sure redrawing is correct. curwin->w_p_lbr = lbr_saved; - redraw_curbuf_later(INVERTED); + redraw_curbuf_later(UPD_INVERTED); } } } @@ -6613,7 +6115,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) if (oap->is_VIsual && (oap->empty || !MODIFIABLE(curbuf) || oap->op_type == OP_FOLD)) { curwin->w_p_lbr = lbr_saved; - redraw_curbuf_later(INVERTED); + redraw_curbuf_later(UPD_INVERTED); } // If the end of an operator is in column one while oap->motion_type diff --git a/src/nvim/option.c b/src/nvim/option.c index 4958c10220..9bc5409ff9 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -27,18 +27,18 @@ #include <stdlib.h> #include <string.h> +#include "nvim/arglist.h" #include "nvim/ascii.h" #include "nvim/buffer.h" +#include "nvim/change.h" #include "nvim/charset.h" -#include "nvim/cursor.h" #include "nvim/cursor_shape.h" +#include "nvim/decoration_provider.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/vars.h" -#include "nvim/ex_cmds2.h" #include "nvim/ex_docmd.h" #include "nvim/ex_getln.h" #include "nvim/ex_session.h" @@ -49,9 +49,10 @@ #include "nvim/hardcopy.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/macros.h" #include "nvim/mapping.h" #include "nvim/mbyte.h" @@ -62,16 +63,19 @@ #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/os.h" #include "nvim/os_unix.h" #include "nvim/path.h" -#include "nvim/popupmnu.h" +#include "nvim/popupmenu.h" #include "nvim/regexp.h" -#include "nvim/runtime.h" #include "nvim/screen.h" +#include "nvim/search.h" #include "nvim/spell.h" #include "nvim/spellfile.h" +#include "nvim/spellsuggest.h" #include "nvim/strings.h" #include "nvim/syntax.h" #include "nvim/ui.h" @@ -82,11 +86,25 @@ #ifdef WIN32 # include "nvim/os/pty_conpty_win.h" #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" -#include "nvim/quickfix.h" + +static char e_unknown_option[] + = N_("E518: Unknown option"); +static char e_not_allowed_in_modeline[] + = N_("E520: Not allowed in a modeline"); +static char e_not_allowed_in_modeline_when_modelineexpr_is_off[] + = N_("E992: Not allowed in a modeline when 'modelineexpr' is off"); +static char e_key_code_not_set[] + = N_("E846: Key code not set"); +static char e_number_required_after_equal[] + = N_("E521: Number required after ="); +static char e_preview_window_already_exists[] + = N_("E590: A preview window already exists"); /* * The options that are local to a window or buffer have "indir" set to one of @@ -119,74 +137,6 @@ typedef enum { static char *p_term = NULL; static char *p_ttytype = NULL; -/* - * These are the global values for options which are also local to a buffer. - * Only to be used in option.c! - */ -static int p_ai; -static int p_bin; -static int p_bomb; -static char_u *p_bh; -static char_u *p_bt; -static int p_bl; -static long p_channel; -static int p_ci; -static int p_cin; -static char_u *p_cink; -static char_u *p_cino; -static char_u *p_cinw; -static char_u *p_cinsd; -static char_u *p_com; -static char_u *p_cms; -static char_u *p_cpt; -static char_u *p_cfu; -static char_u *p_ofu; -static char_u *p_tfu; -static char_u *p_umf; -static int p_eol; -static int p_fixeol; -static int p_et; -static char_u *p_fenc; -static char_u *p_ff; -static char_u *p_fo; -static char_u *p_flp; -static char_u *p_ft; -static long p_iminsert; -static long p_imsearch; -static char_u *p_inex; -static char_u *p_inde; -static char_u *p_indk; -static char_u *p_fex; -static int p_inf; -static char_u *p_isk; -static int p_lisp; -static int p_ml; -static int p_ma; -static int p_mod; -static char_u *p_mps; -static char_u *p_nf; -static int p_pi; -static char_u *p_qe; -static int p_ro; -static int p_si; -static long p_sts; -static char_u *p_sua; -static long p_sw; -static int p_swf; -static long p_smc; -static char_u *p_syn; -static char_u *p_spc; -static char_u *p_spf; -static char_u *p_spl; -static char_u *p_spo; -static long p_ts; -static long p_tw; -static int p_udf; -static long p_wm; -static char_u *p_vsts; -static char_u *p_vts; -static char_u *p_keymap; - // Saved values for when 'bin' is set. static int p_et_nobin; static int p_ml_nobin; @@ -199,7 +149,7 @@ static int p_et_nopaste; static long p_sts_nopaste; static long p_tw_nopaste; static long p_wm_nopaste; -static char_u *p_vsts_nopaste; +static char *p_vsts_nopaste; typedef struct vimoption { char *fullname; // full option name @@ -215,64 +165,6 @@ typedef struct vimoption { } vimoption_T; /* - * Flags - */ -#define P_BOOL 0x01U // the option is boolean -#define P_NUM 0x02U // the option is numeric -#define P_STRING 0x04U // the option is a string -#define P_ALLOCED 0x08U // the string option is in allocated memory, - // must use free_string_option() when - // assigning new value. Not set if default is - // the same. -#define P_EXPAND 0x10U // environment expansion. NOTE: P_EXPAND can - // never be used for local or hidden options -#define P_NODEFAULT 0x40U // don't set to default value -#define P_DEF_ALLOCED 0x80U // default value is in allocated memory, must - // use free() when assigning new value -#define P_WAS_SET 0x100U // option has been set/reset -#define P_NO_MKRC 0x200U // don't include in :mkvimrc output - -// when option changed, what to display: -#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 -#define P_RALL 0x6000U ///< redraw all windows -#define P_RCLR 0x7000U ///< clear and redraw all - -#define P_COMMA 0x8000U ///< comma separated list -#define P_ONECOMMA 0x18000U ///< P_COMMA and cannot have two consecutive - ///< commas -#define P_NODUP 0x20000U ///< don't allow duplicate strings -#define P_FLAGLIST 0x40000U ///< list of single-char flags - -#define P_SECURE 0x80000U ///< cannot change in modeline or secure mode -#define P_GETTEXT 0x100000U ///< expand default value with _() -#define P_NOGLOB 0x200000U ///< do not use local value for global vimrc -#define P_NFNAME 0x400000U ///< only normal file name chars allowed -#define P_INSECURE 0x800000U ///< option was set from a modeline -#define P_PRI_MKRC 0x1000000U ///< priority for :mkvimrc (setting option - ///< has side effects) -#define P_NO_ML 0x2000000U ///< not allowed in modeline -#define P_CURSWANT 0x4000000U ///< update curswant required; not needed - ///< when there is a redraw flag -#define P_NO_DEF_EXP 0x8000000U ///< Do not expand default value. - -#define P_RWINONLY 0x10000000U ///< only redraw current window -#define P_NDNAME 0x20000000U ///< only normal dir name chars allowed -#define P_UI_OPTION 0x40000000U ///< send option to remote ui -#define P_MLE 0x80000000U ///< under control of 'modelineexpr' - -#define HIGHLIGHT_INIT \ - "8:SpecialKey,~:EndOfBuffer,z:TermCursor,Z:TermCursorNC,@:NonText,d:Directory,e:ErrorMsg," \ - "i:IncSearch,l:Search,y:CurSearch,m:MoreMsg,M:ModeMsg,n:LineNr,a:LineNrAbove,b:LineNrBelow," \ - "N:CursorLineNr,G:CursorLineSign,O:CursorLineFold" \ - "r:Question,s:StatusLine,S:StatusLineNC,c:VertSplit,t:Title,v:Visual,V:VisualNOS,w:WarningMsg," \ - "W:WildMenu,f:Folded,F:FoldColumn,A:DiffAdd,C:DiffChange,D:DiffDelete,T:DiffText,>:SignColumn," \ - "-:Conceal,B:SpellBad,P:SpellCap,R:SpellRare,L:SpellLocal,+:Pmenu,=:PmenuSel,x:PmenuSbar," \ - "X:PmenuThumb,*:TabLine,#:TabLineSel,_:TabLineFill,!:CursorColumn,.:CursorLine,o:ColorColumn," \ - "q:QuickFixLine,0:Whitespace,I:NormalNC" - -/* * options[] is initialized here. * The order of the options MUST be alphabetic for ":set all" and findoption(). * All option names MUST start with a lowercase letter (for findoption()). @@ -287,59 +179,6 @@ typedef struct vimoption { #define OPTION_COUNT ARRAY_SIZE(options) -static char *(p_ambw_values[]) = { "single", "double", NULL }; -static char *(p_bg_values[]) = { "light", "dark", NULL }; -static char *(p_nf_values[]) = { "bin", "octal", "hex", "alpha", - "unsigned", NULL }; -static char *(p_ff_values[]) = { FF_UNIX, FF_DOS, FF_MAC, 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 }; -static char *(p_slm_values[]) = { "mouse", "key", "cmd", NULL }; -static char *(p_km_values[]) = { "startsel", "stopsel", NULL }; -static char *(p_scbopt_values[]) = { "ver", "hor", "jump", NULL }; -static char *(p_debug_values[]) = { "msg", "throw", "beep", NULL }; -static char *(p_ead_values[]) = { "both", "ver", "hor", NULL }; -static char *(p_buftype_values[]) = { "nofile", "nowrite", "quickfix", - "help", "acwrite", "terminal", - "prompt", NULL }; - -static char *(p_bufhidden_values[]) = { "hide", "unload", "delete", - "wipe", NULL }; -static char *(p_bs_values[]) = { "indent", "eol", "start", "nostop", NULL }; -static char *(p_fdm_values[]) = { "manual", "expr", "marker", "indent", - "syntax", "diff", NULL }; -static char *(p_fcl_values[]) = { "all", NULL }; -static char *(p_cot_values[]) = { "menu", "menuone", "longest", "preview", - "noinsert", "noselect", NULL }; -#ifdef BACKSLASH_IN_FILENAME -static char *(p_csl_values[]) = { "slash", "backslash", NULL }; -#endif -static char *(p_icm_values[]) = { "nosplit", "split", NULL }; -static char *(p_scl_values[]) = { "yes", "no", "auto", "auto:1", "auto:2", - "auto:3", "auto:4", "auto:5", "auto:6", "auto:7", "auto:8", - "auto:9", - "yes:1", "yes:2", "yes:3", "yes:4", "yes:5", "yes:6", - "yes:7", "yes:8", - "yes:9", "number", NULL }; -static char *(p_fdc_values[]) = { "auto", "auto:1", "auto:2", - "auto:3", "auto:4", "auto:5", "auto:6", "auto:7", "auto:8", - "auto:9", - "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", NULL }; - -/// All possible flags for 'shm'. -static char_u 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, -}; - -static char e_unclosed_expression_sequence[] = N_("E540: Unclosed expression sequence"); -static char e_unbalanced_groups[] = N_("E542: unbalanced groups"); - #ifdef INCLUDE_GENERATED_DECLARATIONS # include "option.c.generated.h" #endif @@ -580,7 +419,7 @@ void set_init_1(bool clean_arg) // NOTE: mlterm's author is being asked to 'set' a variable // instead of an environment variable due to inheritance. if (os_env_exists("MLTERM")) { - set_option_value("tbidi", 1L, NULL, 0); + set_option_value_give_err("tbidi", 1L, NULL, 0); } didset_options2(); @@ -600,7 +439,7 @@ void set_init_1(bool clean_arg) #ifdef HAVE_WORKING_LIBINTL // GNU gettext 0.10.37 supports this feature: set the codeset used for // translated messages independently from the current locale. - (void)bind_textdomain_codeset(PROJECT_NAME, (char *)p_enc); + (void)bind_textdomain_codeset(PROJECT_NAME, p_enc); #endif // Set the default for 'helplang'. @@ -616,7 +455,7 @@ static void set_option_default(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 = get_varp_scope(&(options[opt_idx]), both ? OPT_LOCAL : opt_flags); + varp = (char_u *)get_varp_scope(&(options[opt_idx]), both ? OPT_LOCAL : opt_flags); uint32_t flags = options[opt_idx].flags; if (varp != NULL) { // skip hidden option, nothing to do for it if (flags & P_STRING) { @@ -627,7 +466,7 @@ static void set_option_default(int opt_idx, int opt_flags) (char *)options[opt_idx].def_val, opt_flags, 0); } else { if ((opt_flags & OPT_FREE) && (flags & P_ALLOCED)) { - free_string_option(*(char_u **)(varp)); + free_string_option(*(char **)(varp)); } *(char_u **)varp = options[opt_idx].def_val; options[opt_idx].flags &= ~P_ALLOCED; @@ -766,16 +605,17 @@ void free_all_options(void) if (options[i].indir == PV_NONE) { // global option: free value and default value. if ((options[i].flags & P_ALLOCED) && options[i].var != NULL) { - free_string_option(*(char_u **)options[i].var); + free_string_option(*(char **)options[i].var); } if (options[i].flags & P_DEF_ALLOCED) { - free_string_option(options[i].def_val); + free_string_option((char *)options[i].def_val); } } else if (options[i].var != VAR_WIN && (options[i].flags & P_STRING)) { // buffer-local option: free global value - clear_string_option((char_u **)options[i].var); + clear_string_option((char **)options[i].var); } } + free_operatorfunc_option(); } #endif @@ -835,12 +675,12 @@ void set_init_3(void) if (FNAMECMP(p, "csh") == 0 || FNAMECMP(p, "tcsh") == 0) { if (do_sp) { - p_sp = (char_u *)"|& tee"; - options[idx_sp].def_val = p_sp; + p_sp = "|& tee"; + options[idx_sp].def_val = (char_u *)p_sp; } if (do_srr) { - p_srr = (char_u *)">&"; - options[idx_srr].def_val = p_srr; + p_srr = ">&"; + options[idx_srr].def_val = (char_u *)p_srr; } } else if (FNAMECMP(p, "sh") == 0 || FNAMECMP(p, "ksh") == 0 @@ -854,12 +694,12 @@ void set_init_3(void) || FNAMECMP(p, "dash") == 0) { // Always use POSIX shell style redirection if we reach this if (do_sp) { - p_sp = (char_u *)"2>&1| tee"; - options[idx_sp].def_val = p_sp; + p_sp = "2>&1| tee"; + options[idx_sp].def_val = (char_u *)p_sp; } if (do_srr) { - p_srr = (char_u *)">%s 2>&1"; - options[idx_srr].def_val = p_srr; + p_srr = ">%s 2>&1"; + options[idx_srr].def_val = (char_u *)p_srr; } } xfree(p); @@ -892,7 +732,7 @@ void set_helplang_default(const char *lang) int idx = findoption("hlg"); if (idx >= 0 && !(options[idx].flags & P_WAS_SET)) { if (options[idx].flags & P_ALLOCED) { - free_string_option(p_hlg); + free_string_option((char *)p_hlg); } p_hlg = (char_u *)xmemdupz(lang, lang_len); // zh_CN becomes "cn", zh_TW becomes "tw". @@ -970,7 +810,7 @@ int do_set(char *arg, int opt_flags) int opt_idx; char *errmsg; char errbuf[80]; - char_u *startarg; + 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 @@ -979,7 +819,7 @@ int do_set(char *arg, int opt_flags) varnumber_T value; int key; uint32_t flags; // flags for current option - char_u *varp = NULL; // pointer to variable for current option + char *varp = NULL; // pointer to variable for current option int did_show = false; // already showed one value int adding; // "opt+=arg" int prepending; // "opt^=arg" @@ -993,7 +833,7 @@ int do_set(char *arg, int opt_flags) while (*arg != NUL) { // loop to process all options errmsg = NULL; - startarg = (char_u *)arg; // remember for error message + startarg = arg; // remember for error message if (STRNCMP(arg, "all", 3) == 0 && !isalpha(arg[3]) && !(opt_flags & OPT_MODELINE)) { @@ -1009,7 +849,7 @@ int do_set(char *arg, int opt_flags) didset_options(); didset_options2(); ui_refresh_options(); - redraw_all_later(CLEAR); + redraw_all_later(UPD_CLEAR); } else { showoptions(1, opt_flags); did_show = true; @@ -1090,7 +930,7 @@ int do_set(char *arg, int opt_flags) nextchar = (uint8_t)arg[len]; if (opt_idx == -1 && key == 0) { // found a mismatch: skip - errmsg = N_("E518: Unknown option"); + errmsg = e_unknown_option; goto skip; } @@ -1101,7 +941,7 @@ int do_set(char *arg, int opt_flags) if (vim_strchr("=:!&<", nextchar) == NULL && (!(options[opt_idx].flags & P_BOOL) || nextchar == '?')) { - errmsg = _(e_unsupportedoption); + errmsg = e_unsupportedoption; } goto skip; } @@ -1128,11 +968,11 @@ int do_set(char *arg, int opt_flags) // Disallow changing some options from modelines. if (opt_flags & OPT_MODELINE) { if (flags & (P_SECURE | P_NO_ML)) { - errmsg = N_("E520: Not allowed in a modeline"); + errmsg = e_not_allowed_in_modeline; goto skip; } if ((flags & P_MLE) && !p_mle) { - errmsg = N_("E992: Not allowed in a modeline when 'modelineexpr' is off"); + errmsg = e_not_allowed_in_modeline_when_modelineexpr_is_off; goto skip; } // In diff mode some options are overruled. This avoids that @@ -1189,7 +1029,7 @@ int do_set(char *arg, int opt_flags) showoneopt(&options[opt_idx], opt_flags); if (p_verbose > 0) { // Mention where the option was last set. - if (varp == options[opt_idx].var) { + if (varp == (char *)options[opt_idx].var) { option_last_set_msg(options[opt_idx].last_set); } else if ((int)options[opt_idx].indir & PV_WIN) { option_last_set_msg(curwin->w_p_script_ctx[ @@ -1200,7 +1040,7 @@ int do_set(char *arg, int opt_flags) } } } else { - errmsg = N_("E846: Key code not set"); + errmsg = e_key_code_not_set; goto skip; } if (nextchar != '?' @@ -1251,8 +1091,7 @@ int do_set(char *arg, int opt_flags) } } - errmsg = set_bool_option(opt_idx, varp, (int)value, - opt_flags); + errmsg = set_bool_option(opt_idx, (char_u *)varp, (int)value, opt_flags); } else { // Numeric or string. if (vim_strchr("=:&<", nextchar) == NULL || prefix != 1) { @@ -1294,11 +1133,11 @@ int do_set(char *arg, int opt_flags) // Allow negative, octal and hex numbers. vim_str2nr((char_u *)arg, NULL, &i, STR2NR_ALL, &value, NULL, 0, true); if (i == 0 || (arg[i] != NUL && !ascii_iswhite(arg[i]))) { - errmsg = N_("E521: Number required after ="); + errmsg = e_number_required_after_equal; goto skip; } } else { - errmsg = N_("E521: Number required after ="); + errmsg = e_number_required_after_equal; goto skip; } @@ -1311,7 +1150,7 @@ int do_set(char *arg, int opt_flags) if (removing) { value = *(long *)varp - value; } - errmsg = set_num_option(opt_idx, varp, (long)value, + errmsg = set_num_option(opt_idx, (char_u *)varp, (long)value, errbuf, sizeof(errbuf), opt_flags); } else if (opt_idx >= 0) { // String. @@ -1334,7 +1173,7 @@ int do_set(char *arg, int opt_flags) // reset, use the global value here. if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0 && ((int)options[opt_idx].indir & PV_BOTH)) { - varp = options[opt_idx].var; + varp = (char *)options[opt_idx].var; } // The old value is kept until we are sure that the @@ -1348,7 +1187,7 @@ int do_set(char *arg, int opt_flags) // A global-local string option might have an empty // option as value to indicate that the global // value should be used. - if (((int)options[opt_idx].indir & PV_BOTH) && origval_l == empty_option) { + if (((int)options[opt_idx].indir & PV_BOTH) && origval_l == (char_u *)empty_option) { origval_l = origval_g; } } @@ -1368,7 +1207,7 @@ int do_set(char *arg, int opt_flags) // required when an environment variable was set // later if (newval == NULL) { - newval = empty_option; + newval = (char_u *)empty_option; } else if (!(options[opt_idx].flags & P_NO_DEF_EXP)) { s = option_expand(opt_idx, newval); if (s == NULL) { @@ -1383,27 +1222,20 @@ int do_set(char *arg, int opt_flags) } else { arg++; // jump to after the '=' or ':' - /* - * Set 'keywordprg' to ":help" if an empty - * value was passed to :set by the user. - * Misuse errbuf[] for the resulting string. - */ - if (varp == (char_u *)&p_kp - && (*arg == NUL || *arg == ' ')) { + // Set 'keywordprg' to ":help" if an empty + // value was passed to :set by the user. + // Misuse errbuf[] for the resulting string. + if (varp == (char *)&p_kp && (*arg == NUL || *arg == ' ')) { STRCPY(errbuf, ":help"); save_arg = (char_u *)arg; arg = errbuf; - } - /* - * Convert 'backspace' number to string, for - * adding, prepending and removing string. - */ - else if (varp == (char_u *)&p_bs - && ascii_isdigit(**(char_u **)varp)) { + } else if (varp == (char *)&p_bs && ascii_isdigit(**(char_u **)varp)) { + // Convert 'backspace' number to string, for + // adding, prepending and removing string. i = getdigits_int((char **)varp, true, 0); switch (i) { case 0: - *(char_u **)varp = empty_option; + *(char **)varp = empty_option; break; case 1: *(char_u **)varp = vim_strsave((char_u *)"indent,eol"); @@ -1426,14 +1258,10 @@ int do_set(char *arg, int opt_flags) origval_g = *(char_u **)varp; } oldval = *(char_u **)varp; - } - /* - * Convert 'whichwrap' number to string, for - * backwards compatibility with Vim 3.0. - * Misuse errbuf[] for the resulting string. - */ - else if (varp == (char_u *)&p_ww - && ascii_isdigit(*arg)) { + } else if (varp == (char *)&p_ww && ascii_isdigit(*arg)) { + // Convert 'whichwrap' number to string, for + // backwards compatibility with Vim 3.0. + // Misuse errbuf[] for the resulting string. *errbuf = NUL; i = getdigits_int(&arg, true, 0); if (i & 1) { @@ -1453,14 +1281,11 @@ int do_set(char *arg, int opt_flags) } save_arg = (char_u *)arg; arg = errbuf; - } - /* - * Remove '>' before 'dir' and 'bdir', for - * backwards compatibility with version 3.0 - */ - else if (*arg == '>' - && (varp == (char_u *)&p_dir - || varp == (char_u *)&p_bdir)) { + } else if (*arg == '>' + && (varp == (char *)&p_dir + || varp == (char *)&p_bdir)) { + // Remove '>' before 'dir' and 'bdir', for + // backwards compatibility with version 3.0 arg++; } @@ -1658,7 +1483,7 @@ int do_set(char *arg, int opt_flags) // 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_u **)varp, oldval, + errmsg = did_set_string_option(opt_idx, (char **)varp, (char *)oldval, errbuf, sizeof(errbuf), opt_flags, &value_checked); @@ -1718,17 +1543,17 @@ skip: if (errmsg != NULL) { STRLCPY(IObuff, _(errmsg), IOSIZE); i = (int)STRLEN(IObuff) + 2; - if (i + ((char_u *)arg - startarg) < IOSIZE) { + if (i + (arg - startarg) < IOSIZE) { // append the argument with the error STRCAT(IObuff, ": "); - assert((char_u *)arg >= startarg); - memmove(IObuff + i, startarg, (size_t)((char_u *)arg - startarg)); - IObuff[i + ((char_u *)arg - startarg)] = NUL; + assert(arg >= startarg); + memmove(IObuff + i, startarg, (size_t)(arg - startarg)); + IObuff[i + (arg - startarg)] = NUL; } // make sure all characters are printable trans_characters((char *)IObuff, IOSIZE); - no_wait_return++; // wait_return done later + no_wait_return++; // wait_return() done later emsg((char *)IObuff); // show error highlighted no_wait_return--; @@ -1758,7 +1583,7 @@ theend: /// @param opt_flags possibly with OPT_MODELINE /// @param new_value value was replaced completely /// @param value_checked value was checked to be safe, no need to set P_INSECURE -static void did_set_option(int opt_idx, int opt_flags, int new_value, int value_checked) +void did_set_option(int opt_idx, int opt_flags, int new_value, int value_checked) { options[opt_idx].flags |= P_WAS_SET; @@ -1775,19 +1600,9 @@ static void did_set_option(int opt_idx, int opt_flags, int new_value, int value_ } } -static char *illegal_char(char *errbuf, size_t errbuflen, int c) -{ - if (errbuf == NULL) { - return ""; - } - vim_snprintf(errbuf, errbuflen, _("E539: Illegal character <%s>"), - (char *)transchar(c)); - return errbuf; -} - /// Convert a key name or string into a key value. /// Used for 'wildchar' and 'cedit' options. -static int string_to_key(char_u *arg) +int string_to_key(char_u *arg) { if (*arg == '<') { return find_key_option(arg + 1, true); @@ -1798,29 +1613,11 @@ static int string_to_key(char_u *arg) return *arg; } -/// Check value of 'cedit' and set cedit_key. -/// Returns NULL if value is OK, error message otherwise. -static char *check_cedit(void) -{ - int n; - - if (*p_cedit == NUL) { - cedit_key = -1; - } else { - n = string_to_key(p_cedit); - if (vim_isprintc(n)) { - return e_invarg; - } - cedit_key = n; - } - return NULL; -} - // When changing 'title', 'titlestring', 'icon' or 'iconstring', call // maketitle() to create and display it. // When switching the title or icon off, call ui_set_{icon,title}(NULL) to get // the old value back. -static void did_set_title(void) +void did_set_title(void) { if (starting != NO_SCREEN) { maketitle(); @@ -1902,16 +1699,14 @@ int get_shada_parameter(int type) /// Return NULL if the parameter is not specified in the string. char_u *find_shada_parameter(int type) { - char_u *p; - - for (p = p_shada; *p; p++) { + for (char *p = p_shada; *p; p++) { if (*p == type) { - return p + 1; + return (char_u *)p + 1; } if (*p == 'n') { // 'n' is always the last one break; } - p = (char_u *)vim_strchr((char *)p, ','); // skip until next ',' + p = vim_strchr(p, ','); // skip until next ',' if (p == NULL) { // hit the end without finding parameter break; } @@ -1946,35 +1741,15 @@ static char_u *option_expand(int opt_idx, char_u *val) * names. * For 'spellsuggest' expand after "file:". */ - expand_env_esc(val, NameBuff, MAXPATHL, + expand_env_esc(val, (char_u *)NameBuff, MAXPATHL, (char_u **)options[opt_idx].var == &p_tags, false, - (char_u **)options[opt_idx].var == &p_sps ? (char_u *)"file:" : + (char_u **)options[opt_idx].var == (char_u **)&p_sps ? (char_u *)"file:" : NULL); if (STRCMP(NameBuff, val) == 0) { // they are the same return NULL; } - return NameBuff; -} - -/// After setting various option values: recompute variables that depend on -/// option values. -static void didset_string_options(void) -{ - (void)opt_strings_flags(p_cmp, p_cmp_values, &cmp_flags, true); - (void)opt_strings_flags(p_bkc, p_bkc_values, &bkc_flags, true); - (void)opt_strings_flags(p_bo, p_bo_values, &bo_flags, true); - (void)opt_strings_flags(p_ssop, p_ssop_values, &ssop_flags, true); - (void)opt_strings_flags(p_vop, p_ssop_values, &vop_flags, true); - (void)opt_strings_flags(p_fdo, p_fdo_values, &fdo_flags, true); - (void)opt_strings_flags(p_dy, p_dy_values, &dy_flags, true); - (void)opt_strings_flags(p_rdb, p_rdb_values, &rdb_flags, true); - (void)opt_strings_flags(p_tc, p_tc_values, &tc_flags, false); - (void)opt_strings_flags(p_tpf, p_tpf_values, &tpf_flags, true); - (void)opt_strings_flags(p_ve, p_ve_values, &ve_flags, true); - (void)opt_strings_flags(p_swb, p_swb_values, &swb_flags, true); - (void)opt_strings_flags(p_wop, p_wop_values, &wop_flags, true); - (void)opt_strings_flags(p_jop, p_jop_values, &jop_flags, true); + return (char_u *)NameBuff; } /// After setting various option values: recompute variables that depend on @@ -1994,7 +1769,7 @@ static void didset_options(void) (void)check_cedit(); // initialize the table for 'breakat'. fill_breakat_flags(); - didset_window_options(curwin); + didset_window_options(curwin, true); } // More side effects of setting options. @@ -2003,9 +1778,6 @@ static void didset_options2(void) // Initialize the highlight_attr[] table. highlight_changed(); - // Parse default for 'clipboard'. - (void)opt_strings_flags(p_cb, p_cb_values, &cb_flags, true); - // Parse default for 'fillchars'. (void)set_chars_option(curwin, &curwin->w_p_fcs, true); @@ -2027,96 +1799,11 @@ void check_options(void) for (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_u **)get_varp(&(options[opt_idx]))); + check_string_option((char **)get_varp(&(options[opt_idx]))); } } } -/// Check string options in a buffer for NULL value. -void check_buf_options(buf_T *buf) -{ - check_string_option(&buf->b_p_bh); - check_string_option(&buf->b_p_bt); - check_string_option(&buf->b_p_fenc); - check_string_option(&buf->b_p_ff); - check_string_option(&buf->b_p_def); - check_string_option(&buf->b_p_inc); - check_string_option(&buf->b_p_inex); - check_string_option(&buf->b_p_inde); - check_string_option(&buf->b_p_indk); - check_string_option(&buf->b_p_fp); - check_string_option(&buf->b_p_fex); - check_string_option(&buf->b_p_kp); - check_string_option(&buf->b_p_mps); - check_string_option(&buf->b_p_fo); - check_string_option(&buf->b_p_flp); - check_string_option(&buf->b_p_isk); - check_string_option(&buf->b_p_com); - check_string_option(&buf->b_p_cms); - check_string_option(&buf->b_p_nf); - check_string_option(&buf->b_p_qe); - check_string_option(&buf->b_p_syn); - check_string_option(&buf->b_s.b_syn_isk); - check_string_option(&buf->b_s.b_p_spc); - check_string_option(&buf->b_s.b_p_spf); - check_string_option(&buf->b_s.b_p_spl); - check_string_option(&buf->b_s.b_p_spo); - check_string_option(&buf->b_p_sua); - check_string_option(&buf->b_p_cink); - check_string_option(&buf->b_p_cino); - parse_cino(buf); - check_string_option(&buf->b_p_ft); - check_string_option(&buf->b_p_cinw); - check_string_option(&buf->b_p_cinsd); - check_string_option(&buf->b_p_cpt); - check_string_option(&buf->b_p_cfu); - check_string_option(&buf->b_p_ofu); - check_string_option(&buf->b_p_keymap); - check_string_option(&buf->b_p_gp); - check_string_option(&buf->b_p_mp); - check_string_option(&buf->b_p_efm); - check_string_option(&buf->b_p_ep); - check_string_option(&buf->b_p_path); - check_string_option(&buf->b_p_tags); - check_string_option(&buf->b_p_tfu); - check_string_option(&buf->b_p_tc); - check_string_option(&buf->b_p_dict); - check_string_option(&buf->b_p_tsr); - check_string_option(&buf->b_p_tsrfu); - check_string_option(&buf->b_p_lw); - check_string_option(&buf->b_p_bkc); - check_string_option(&buf->b_p_menc); - check_string_option(&buf->b_p_vsts); - check_string_option(&buf->b_p_vts); -} - -/// Free the string allocated for an option. -/// Checks for the string being empty_option. This may happen if we're out of -/// memory, vim_strsave() returned NULL, which was replaced by empty_option by -/// check_options(). -/// Does NOT check for P_ALLOCED flag! -void free_string_option(char_u *p) -{ - if (p != empty_option) { - xfree(p); - } -} - -void clear_string_option(char_u **pp) -{ - if (*pp != empty_option) { - xfree(*pp); - } - *pp = empty_option; -} - -static void check_string_option(char_u **pp) -{ - if (*pp == NULL) { - *pp = empty_option; - } -} - /// Return true when option "opt" was set from a modeline or in secure mode. /// Return false when it wasn't. /// Return -1 for an unknown option. @@ -2163,172 +1850,18 @@ static uint32_t *insecure_flag(win_T *const wp, int opt_idx, int opt_flags) } /// Redraw the window title and/or tab page text later. -static void redraw_titles(void) +void redraw_titles(void) { need_maketitle = true; redraw_tabline = true; } -static int shada_idx = -1; - -/// Set a string option to a new value (without checking the effect). -/// The string is copied into allocated memory. -/// if ("opt_idx" == -1) "name" is used, otherwise "opt_idx" is used. -/// When "set_sid" is zero set the scriptID to current_sctx.sc_sid. When -/// "set_sid" is SID_NONE don't set the scriptID. Otherwise set the scriptID to -/// "set_sid". -/// -/// @param opt_flags OPT_FREE, OPT_LOCAL and/or OPT_GLOBAL -void set_string_option_direct(const char *name, int opt_idx, const char *val, int opt_flags, - int set_sid) -{ - char *s; - char **varp; - int both = (opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0; - int idx = opt_idx; - - if (idx == -1) { // Use name. - idx = findoption(name); - if (idx < 0) { // Not found (should not happen). - internal_error("set_string_option_direct()"); - siemsg(_("For option %s"), name); - return; - } - } - - if (options[idx].var == NULL) { // can't set hidden option - return; - } - - assert((void *)options[idx].var != (void *)&p_shada); - - s = xstrdup(val); - { - varp = (char **)get_varp_scope(&(options[idx]), both ? OPT_LOCAL : opt_flags); - if ((opt_flags & OPT_FREE) && (options[idx].flags & P_ALLOCED)) { - free_string_option((char_u *)(*varp)); - } - *varp = s; - - // For buffer/window local option may also set the global value. - if (both) { - set_string_option_global(idx, (char_u **)varp); - } - - options[idx].flags |= P_ALLOCED; - - /* When setting both values of a global option with a local value, - * make the local value empty, so that the global value is used. */ - if (((int)options[idx].indir & PV_BOTH) && both) { - free_string_option((char_u *)(*varp)); - *varp = (char *)empty_option; - } - if (set_sid != SID_NONE) { - sctx_T script_ctx; - - if (set_sid == 0) { - script_ctx = current_sctx; - } else { - script_ctx.sc_sid = set_sid; - script_ctx.sc_seq = 0; - script_ctx.sc_lnum = 0; - } - set_option_sctx_idx(idx, opt_flags, script_ctx); - } - } -} - -/// Set global value for string option when it's a local option. -/// -/// @param opt_idx option index -/// @param varp pointer to option variable -static void set_string_option_global(int opt_idx, char_u **varp) -{ - char_u **p, *s; - - // the global value is always allocated - if (options[opt_idx].var == VAR_WIN) { - p = (char_u **)GLOBAL_WO(varp); - } else { - p = (char_u **)options[opt_idx].var; - } - if (options[opt_idx].indir != PV_NONE && p != varp) { - s = vim_strsave(*varp); - free_string_option(*p); - *p = s; - } -} - -/// Set a string option to a new value, handling the effects -/// -/// @param[in] opt_idx Option to set. -/// @param[in] value New value. -/// @param[in] opt_flags Option flags: expected to contain #OPT_LOCAL and/or -/// #OPT_GLOBAL. -/// -/// @return NULL on success, error message on error. -static char *set_string_option(const int opt_idx, const char *const value, const int opt_flags) - FUNC_ATTR_NONNULL_ARG(2) FUNC_ATTR_WARN_UNUSED_RESULT -{ - if (options[opt_idx].var == NULL) { // don't set hidden option - return NULL; - } - - char *const s = xstrdup(value); - char **const varp = (char **)get_varp_scope(&(options[opt_idx]), - ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0 - ? (((int)options[opt_idx].indir & PV_BOTH) - ? OPT_GLOBAL : OPT_LOCAL) - : opt_flags)); - char *const oldval = *varp; - char *oldval_l = NULL; - char *oldval_g = NULL; - - if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) { - oldval_l = *(char **)get_varp_scope(&(options[opt_idx]), OPT_LOCAL); - oldval_g = *(char **)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL); - } - - *varp = s; - - char *const saved_oldval = xstrdup(oldval); - char *const saved_oldval_l = (oldval_l != NULL) ? xstrdup(oldval_l) : 0; - char *const saved_oldval_g = (oldval_g != NULL) ? xstrdup(oldval_g) : 0; - char *const saved_newval = xstrdup(s); - - int value_checked = false; - char *const r = did_set_string_option(opt_idx, (char_u **)varp, (char_u *)oldval, - NULL, 0, - opt_flags, &value_checked); - if (r == NULL) { - did_set_option(opt_idx, opt_flags, true, value_checked); - } - - // call autocommand after handling side effects - if (r == NULL) { - if (!starting) { - trigger_optionsset_string(opt_idx, opt_flags, saved_oldval, saved_oldval_l, saved_oldval_g, - saved_newval); - } - if (options[opt_idx].flags & P_UI_OPTION) { - ui_call_option_set(cstr_as_string(options[opt_idx].fullname), - STRING_OBJ(cstr_as_string(saved_newval))); - } - } - xfree(saved_oldval); - xfree(saved_oldval_l); - xfree(saved_oldval_g); - xfree(saved_newval); - - return r; -} - /// Return true if "val" is a valid name: only consists of alphanumeric ASCII /// characters or characters in "allowed". -static bool valid_name(const char_u *val, const char *allowed) +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 = val; *s != NUL; s++) { + for (const char_u *s = (char_u *)val; *s != NUL; s++) { if (!ASCII_ISALNUM(*s) && vim_strchr(allowed, *s) == NULL) { return false; @@ -2337,1632 +1870,36 @@ static bool valid_name(const char_u *val, const char *allowed) return true; } -/// Return true if "val" is a valid 'filetype' name. -/// Also used for 'syntax' and 'keymap'. -static bool valid_filetype(const char_u *val) - FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT -{ - return valid_name(val, ".-_"); -} - -/// Return true if "val" is a valid 'spelllang' value. -bool valid_spelllang(const char_u *val) - FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT -{ - return valid_name(val, ".-_,@"); -} - -/// Return true if "val" is a valid 'spellfile' value. -static bool valid_spellfile(const char_u *val) - FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT -{ - for (const char_u *s = val; *s != NUL; s++) { - if (!vim_isfilec(*s) && *s != ',' && *s != ' ') { - return false; - } - } - return true; -} - -/// Handle setting 'mousescroll'. -/// @return error message, NULL if it's OK. -static char *check_mousescroll(char *string) -{ - long vertical = -1; - long horizontal = -1; - - for (;;) { - char *end = vim_strchr(string, ','); - size_t length = end ? (size_t)(end - string) : STRLEN(string); - - // Both "ver:" and "hor:" are 4 bytes long. - // They should be followed by at least one digit. - if (length <= 4) { - return e_invarg; - } - - long *direction; - - if (memcmp(string, "ver:", 4) == 0) { - direction = &vertical; - } else if (memcmp(string, "hor:", 4) == 0) { - direction = &horizontal; - } else { - return e_invarg; - } - - // If the direction has already been set, this is a duplicate. - if (*direction != -1) { - return e_invarg; - } - - // Verify that only digits follow the colon. - for (size_t i = 4; i < length; i++) { - if (!ascii_isdigit(string[i])) { - return N_("E548: digit expected"); - } - } - - string += 4; - *direction = getdigits_int(&string, false, -1); - - // Num options are generally kept within the signed int range. - // We know this number won't be negative because we've already checked for - // a minus sign. We'll allow 0 as a means of disabling mouse scrolling. - if (*direction == -1) { - return e_invarg; - } - - if (!end) { - break; - } - - string = end + 1; - } - - // If a direction wasn't set, fallback to the default value. - p_mousescroll_vert = (vertical == -1) ? MOUSESCROLL_VERT_DFLT : vertical; - p_mousescroll_hor = (horizontal == -1) ? MOUSESCROLL_HOR_DFLT : horizontal; - - return NULL; -} - -/// Handle string options that need some action to perform when changed. -/// The new value must be allocated. -/// Returns NULL for success, or an error message for an error. -/// -/// @param opt_idx index in options[] table -/// @param varp pointer to the option variable -/// @param oldval previous value of the option -/// @param errbuf buffer for errors, or NULL -/// @param errbuflen length of errors buffer -/// @param opt_flags OPT_LOCAL and/or OPT_GLOBAL -/// @param value_checked value was checked to be safe, no need to set P_INSECURE -static char *did_set_string_option(int opt_idx, char_u **varp, char_u *oldval, char *errbuf, - size_t errbuflen, int opt_flags, int *value_checked) -{ - char *errmsg = NULL; - char_u *s, *p; - int did_chartab = false; - char_u **gvarp; - bool free_oldval = (options[opt_idx].flags & P_ALLOCED); - bool value_changed = false; - - /* Get the global option to compare with, otherwise we would have to check - * two values for all local options. */ - gvarp = (char_u **)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL); - - // Disallow changing some options from secure mode - if ((secure || sandbox != 0) - && (options[opt_idx].flags & P_SECURE)) { - errmsg = e_secure; - } else if (((options[opt_idx].flags & P_NFNAME) - && strpbrk((char *)(*varp), - (secure ? "/\\*?[|;&<>\r\n" : "/\\*?[<>\r\n")) != NULL) - || ((options[opt_idx].flags & P_NDNAME) - && strpbrk((char *)(*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. - errmsg = e_invarg; - } else if (gvarp == &p_bkc) { // 'backupcopy' - char_u *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; - } - } - } 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 = N_("E589: 'backupext' and 'patchmode' are equal"); - } - } else if (varp == &curwin->w_p_briopt) { // 'breakindentopt' - if (briopt_check(curwin) == FAIL) { - errmsg = e_invarg; - } - } else if (varp == &p_isi - || varp == &(curbuf->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 - } - } else if (varp == &p_hf) { // 'helpfile' - // May compute new values for $VIM and $VIMRUNTIME - if (didset_vim) { - os_setenv("VIM", "", 1); - didset_vim = false; - } - if (didset_vimruntime) { - os_setenv("VIMRUNTIME", "", 1); - didset_vimruntime = false; - } - } 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 == &p_hlg) { // 'helplang' - // Check for "", "ab", "ab,cd", etc. - for (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; - } - } - } else if (varp == &p_hl) { - // 'highlight' - if (STRCMP(*varp, HIGHLIGHT_INIT) != 0) { - errmsg = e_unsupportedoption; - } - } else if (varp == &p_jop) { // 'jumpoptions' - if (opt_strings_flags(p_jop, p_jop_values, &jop_flags, true) != OK) { - errmsg = e_invarg; - } - } else if (gvarp == &p_nf) { // 'nrformats' - if (check_opt_strings(*varp, p_nf_values, true) != OK) { - errmsg = e_invarg; - } - } 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; - } - } else if (varp == &p_vop) { // 'viewoptions' - if (opt_strings_flags(p_vop, p_ssop_values, &vop_flags, true) != OK) { - errmsg = e_invarg; - } - } else if (varp == &p_rdb) { // 'redrawdebug' - if (opt_strings_flags(p_rdb, p_rdb_values, &rdb_flags, true) != OK) { - errmsg = e_invarg; - } - } else if (varp == (char_u **)&p_sbo) { // 'scrollopt' - if (check_opt_strings((char_u *)p_sbo, p_scbopt_values, true) != OK) { - errmsg = e_invarg; - } - } 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 { - FOR_ALL_TAB_WINDOWS(tp, wp) { - if (set_chars_option(wp, &wp->w_p_lcs, true) != NULL) { - errmsg = _("E834: Conflicts with value of 'listchars'"); - goto ambw_end; - } - if (set_chars_option(wp, &wp->w_p_fcs, true) != NULL) { - errmsg = _("E835: Conflicts with value of 'fillchars'"); - goto ambw_end; - } - } -ambw_end: - {} // clint prefers {} over ; as an empty statement - } - } 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 = vim_strsave((char_u *)(dark ? "dark" : "light")); - check_string_option(&p_bg); - init_highlight(false, false); - } - } else { - errmsg = e_invarg; - } - } else if (varp == &p_wim) { // 'wildmode' - if (check_opt_wim() == FAIL) { - errmsg = e_invarg; - } - // 'wildoptions' - } else if (varp == &p_wop) { - if (opt_strings_flags(p_wop, p_wop_values, &wop_flags, true) != OK) { - errmsg = e_invarg; - } - } else if (varp == &p_wak) { // 'winaltkeys' - if (*p_wak == NUL - || check_opt_strings(p_wak, p_wak_values, false) != OK) { - errmsg = e_invarg; - } - } else if (varp == &p_ei) { // 'eventignore' - if (check_ei() == FAIL) { - errmsg = e_invarg; - } - // 'encoding', 'fileencoding' and 'makeencoding' - } else if (varp == &p_enc || gvarp == &p_fenc || gvarp == &p_menc) { - if (gvarp == &p_fenc) { - if (!MODIFIABLE(curbuf) && opt_flags != OPT_GLOBAL) { - errmsg = e_modifiable; - } else if (vim_strchr((char *)(*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(); - } - } 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(NOT_VALID); - } - } - } else if (varp == (char_u **)&p_ffs) { // 'fileformats' - if (check_opt_strings((char_u *)p_ffs, p_ff_values, true) != OK) { - errmsg = e_invarg; - } - } else if (gvarp == &p_mps) { // 'matchpairs' - for (p = *varp; *p != NUL; p++) { - int x2 = -1; - int x3 = -1; - - p += utfc_ptr2len((char *)p); - if (*p != NUL) { - x2 = *p++; - } - if (*p != NUL) { - x3 = utf_ptr2char((char *)p); - p += utfc_ptr2len((char *)p); - } - if (x2 != ':' || x3 == -1 || (*p != NUL && *p != ',')) { - errmsg = e_invarg; - break; - } - if (*p == NUL) { - break; - } - } - } 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); - } - } else if (varp == &p_lcs) { // global 'listchars' - errmsg = set_chars_option(curwin, varp, false); - if (errmsg == NULL) { - // The current window is set to use the global 'listchars' value. - // So clear the window-local value. - if (!(opt_flags & OPT_GLOBAL)) { - clear_string_option(&curwin->w_p_lcs); - } - FOR_ALL_TAB_WINDOWS(tp, wp) { - // If no error was returned above, we don't expect an error - // here, so ignore the return value. - (void)set_chars_option(wp, &wp->w_p_lcs, true); - } - redraw_all_later(NOT_VALID); - } - } else if (varp == &curwin->w_p_lcs) { // local 'listchars' - errmsg = set_chars_option(curwin, varp, true); - } else if (varp == &p_fcs) { // global 'fillchars' - errmsg = set_chars_option(curwin, varp, false); - if (errmsg == NULL) { - // The current window is set to use the global 'fillchars' value. - // So clear the window-local value. - if (!(opt_flags & OPT_GLOBAL)) { - clear_string_option(&curwin->w_p_fcs); - } - FOR_ALL_TAB_WINDOWS(tp, wp) { - // If no error was returned above, we don't expect an error - // here, so ignore the return value. - (void)set_chars_option(wp, &wp->w_p_fcs, true); - } - redraw_all_later(NOT_VALID); - } - } else if (varp == &curwin->w_p_fcs) { // local 'fillchars' - errmsg = set_chars_option(curwin, 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; - } - // 'shada' - } else if (varp == &p_shada) { - // TODO(ZyX-I): Remove this code in the future, alongside with &viminfo - // option. - opt_idx = ((options[opt_idx].fullname[0] == 'v') - ? (shada_idx == -1 - ? ((shada_idx = findoption("shada"))) - : shada_idx) - : 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 = (options[opt_idx].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"); - } - } else if (gvarp == &p_sbr) { // 'showbreak' - for (s = *varp; *s;) { - if (ptr2cells((char *)s) != 1) { - errmsg = N_("E595: 'showbreak' contains unprintable or wide character"); - } - MB_PTR_ADV(s); - } - } 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((char *)(*varp), '%') && check_stl_option((char *)(*varp)) == NULL) { - stl_syntax |= flagval; - } else { - stl_syntax &= ~flagval; - } - did_set_title(); - } else if (varp == &p_sel) { // 'selection' - if (*p_sel == NUL - || check_opt_strings(p_sel, p_sel_values, false) != OK) { - errmsg = e_invarg; - } - } else if (varp == &p_slm) { // 'selectmode' - if (check_opt_strings(p_slm, p_slm_values, true) != OK) { - errmsg = e_invarg; - } - } 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((char *)p_km, 'o') != NULL); - km_startsel = (vim_strchr((char *)p_km, 'a') != NULL); - } - } else if (varp == &p_mousem) { // 'mousemodel' - if (check_opt_strings(p_mousem, p_mousem_values, false) != OK) { - errmsg = e_invarg; - } - } else if (varp == &p_mousescroll) { // 'mousescroll' - errmsg = check_mousescroll((char *)p_mousescroll); - } else if (varp == &p_swb) { // 'switchbuf' - if (opt_strings_flags(p_swb, p_swb_values, &swb_flags, true) != OK) { - errmsg = e_invarg; - } - } else if (varp == &p_debug) { // 'debug' - if (check_opt_strings(p_debug, p_debug_values, true) != OK) { - errmsg = e_invarg; - } - } 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(); - } - } else if (varp == &p_ead) { // 'eadirection' - if (check_opt_strings(p_ead, p_ead_values, false) != OK) { - errmsg = e_invarg; - } - } 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 (**varp != NUL && STRCMP("camel", *varp) != 0) { - errmsg = e_invarg; - } - } else if (varp == &p_sps) { // 'spellsuggest' - if (spell_check_sps() != OK) { - errmsg = e_invarg; - } - } else if (varp == &p_msm) { // 'mkspellmem' - if (spell_check_msm() != OK) { - errmsg = e_invarg; - } - } 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, VALID); - } - curbuf->b_help = (curbuf->b_p_bt[0] == 'h'); - redraw_titles(); - } - } else if (gvarp == &p_stl || gvarp == (char_u **)&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((char **)&s, true, 0); - if (wid && *s == '(' && (errmsg = check_stl_option((char *)p_ruf)) == NULL) { - ru_wid = wid; - } else { - errmsg = check_stl_option((char *)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((char *)s); - } - if (varp == &p_ruf && errmsg == NULL) { - comp_col(); - } - // add / remove window bars for 'winbar' - if (gvarp == (char_u **)&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; - } - } - } - } else if (varp == &p_cot) { // 'completeopt' - if (check_opt_strings(p_cot, p_cot_values, true) != OK) { - errmsg = e_invarg; - } else { - completeopt_was_set(); - } -#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) { - 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) { - // '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((char *)p_pt, - STRLEN(p_pt), - (char **)&p, REPTERM_FROM_PART | REPTERM_DO_LT, NULL, - CPO_TO_CPO_FLAGS); - if (p != NULL) { - free_string_option(p_pt); - p_pt = p; - } - } - } 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; - } - } else if (varp == &p_bo) { - if (opt_strings_flags(p_bo, p_bo_values, &bo_flags, true) != OK) { - errmsg = e_invarg; - } - } 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; - } - } else if (varp == &p_cmp) { // 'casemap' - if (opt_strings_flags(p_cmp, p_cmp_values, &cmp_flags, true) != OK) { - errmsg = e_invarg; - } - } 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 = (char_u *)vim_strchr((char *)(*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); - } - } else if (gvarp == &p_cms) { // 'commentstring' - if (**varp != NUL && strstr((char *)(*varp), "%s") == NULL) { - errmsg = N_("E537: 'commentstring' must be empty or contain %s"); - } - } else if (varp == &p_fdo) { // 'foldopen' - if (opt_strings_flags(p_fdo, p_fdo_values, &fdo_flags, true) != OK) { - errmsg = e_invarg; - } - } 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); - } - } else if (gvarp == &p_ve) { // 'virtualedit' - char_u *ve = p_ve; - unsigned int *flags = &ve_flags; - - if (opt_flags & OPT_LOCAL) { - ve = curwin->w_p_ve; - flags = &curwin->w_ve_flags; - } - - if ((opt_flags & OPT_LOCAL) && *ve == NUL) { - // make the local value empty: use the global value - *flags = 0; - } else { - if (opt_strings_flags(ve, p_ve_values, flags, true) != OK) { - errmsg = e_invarg; - } else if (STRCMP(p_ve, oldval) != 0) { - // Recompute cursor position in case the new 've' setting - // changes something. - validate_virtcol(); - coladvance(curwin->w_virtcol); - } - } - } else if (varp == &p_csqf) { - if (p_csqf != NULL) { - 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; - } - } - } - } else if (gvarp == &p_cino) { // 'cinoptions' - // TODO(vim): recognize errors - parse_cino(curbuf); - // inccommand - } else if (varp == &p_icm) { - 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; - } - } 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_u *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_u *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; - } - } - } - } else if (varp == &p_qftf) { - if (!qf_process_qftf_option()) { - errmsg = e_invarg; - } - } else { - // Options that are a list of flags. - p = NULL; - if (varp == &p_ww) { // 'whichwrap' - p = (char_u *)WW_ALL; - } - if (varp == &p_shm) { // 'shortmess' - p = (char_u *)SHM_ALL; - } else if (varp == (char_u **)&(p_cpo)) { // 'cpoptions' - p = (char_u *)CPO_VI; - } else if (varp == &(curbuf->b_p_fo)) { // 'formatoptions' - p = (char_u *)FO_ALL; - } else if (varp == &curwin->w_p_cocu) { // 'concealcursor' - p = (char_u *)COCU_ALL; - } else if (varp == &p_mouse) { // 'mouse' - p = (char_u *)MOUSE_ALL; - } - if (p != NULL) { - for (s = *varp; *s; s++) { - if (vim_strchr((char *)p, *s) == NULL) { - errmsg = illegal_char(errbuf, errbuflen, *s); - break; - } - } - } - } - - /* - * If error detected, restore the previous value. - */ - if (errmsg != NULL) { - free_string_option(*varp); - *varp = oldval; - /* - * When resetting some values, need to act on it. - */ - if (did_chartab) { - (void)init_chartab(); - } - } else { - // Remember where the option was set. - set_option_sctx_idx(opt_idx, opt_flags, current_sctx); - // Free string options that are in allocated memory. - // Use "free_oldval", because recursiveness may change the flags under - // our fingers (esp. init_highlight()). - if (free_oldval) { - free_string_option(oldval); - } - options[opt_idx].flags |= P_ALLOCED; - - if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0 - && ((int)options[opt_idx].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(&(options[opt_idx]), OPT_LOCAL); - free_string_option(*(char_u **)p); - *(char_u **)p = empty_option; - } else if (!(opt_flags & OPT_LOCAL) && opt_flags != OPT_GLOBAL) { - // May set global value for local option. - set_string_option_global(opt_idx, varp); - } - - /* - * 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, (char *)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, (char *)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_u fname[200]; - char_u *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((char *)fname, sizeof(fname), "spell/%.*s.vim", - (int)(p - q), q); - source_runtime((char *)fname, DIP_ALL); - } - } - } - - if (varp == &p_mouse) { - setmouse(); // in case 'mouse' changed - } - - if (curwin->w_curswant != MAXCOL - && (options[opt_idx].flags & (P_CURSWANT | P_RALL)) != 0) { - curwin->w_set_curswant = true; - } - - check_redraw(options[opt_idx].flags); - - return errmsg; -} - -/// Simple int comparison function for use with qsort() -static int int_cmp(const void *a, const void *b) -{ - return *(const int *)a - *(const int *)b; -} - -/// Handle setting 'signcolumn' for value 'val' -/// -/// @return OK when the value is valid, FAIL otherwise -int check_signcolumn(char_u *val) -{ - if (*val == NUL) { - return FAIL; - } - // check for basic match - if (check_opt_strings(val, p_scl_values, false) == OK) { - return OK; - } - - // check for 'auto:<NUMBER>-<NUMBER>' - if (STRLEN(val) == 8 - && !STRNCMP(val, "auto:", 5) - && ascii_isdigit(val[5]) - && val[6] == '-' - && ascii_isdigit(val[7])) { - int min = val[5] - '0'; - int max = val[7] - '0'; - if (min < 1 || max < 2 || min > 8 || max > 9 || min >= max) { - return FAIL; - } - return OK; - } - - return FAIL; -} - -/// Handle setting 'colorcolumn' or 'textwidth' in window "wp". -/// -/// @return error message, NULL if it's OK. -char *check_colorcolumn(win_T *wp) -{ - char_u *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;) { - if (*s == '-' || *s == '+') { - // -N and +N: add to 'textwidth' - col = (*s == '-') ? -1 : 1; - s++; - if (!ascii_isdigit(*s)) { - return e_invarg; - } - col = col * getdigits_int((char **)&s, true, 0); - if (wp->w_buffer->b_p_tw == 0) { - goto skip; // 'textwidth' not set, skip this item - } - assert((col >= 0 - && wp->w_buffer->b_p_tw <= INT_MAX - col - && wp->w_buffer->b_p_tw + col >= INT_MIN) - || (col < 0 - && wp->w_buffer->b_p_tw >= INT_MIN - col - && wp->w_buffer->b_p_tw + col <= INT_MAX)); - col += (int)wp->w_buffer->b_p_tw; - if (col < 0) { - goto skip; - } - } else if (ascii_isdigit(*s)) { - col = getdigits_int((char **)&s, true, 0); - } else { - return e_invarg; - } - color_cols[count++] = col - 1; // 1-based to 0-based -skip: - if (*s == NUL) { - break; - } - if (*s != ',') { - return e_invarg; - } - if (*++s == NUL) { - return e_invarg; // illegal trailing comma as in "set cc=80," - } - } - - xfree(wp->w_p_cc_cols); - if (count == 0) { - wp->w_p_cc_cols = NULL; - } else { - wp->w_p_cc_cols = xmalloc(sizeof(int) * (count + 1)); - /* sort the columns for faster usage on screen redraw inside - * win_line() */ - qsort(color_cols, count, sizeof(int), int_cmp); - - for (unsigned int i = 0; i < count; i++) { - // skip duplicates - if (j == 0 || wp->w_p_cc_cols[j - 1] != color_cols[i]) { - wp->w_p_cc_cols[j++] = color_cols[i]; - } - } - wp->w_p_cc_cols[j] = -1; // end marker - } - - return NULL; // no error -} - void check_blending(win_T *wp) { wp->w_grid_alloc.blending = wp->w_p_winbl > 0 || (wp->w_floating && wp->w_float_config.shadow); } -/// 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(char_u **p) -{ - char_u *s = *p; - - if (s[0] == '\\' && (s[1] == 'x' || s[1] == 'u' || s[1] == 'U')) { - int64_t num = 0; - int bytes; - int n; - for (bytes = s[1] == 'x' ? 1 : s[1] == 'u' ? 2 : 4; bytes > 0; bytes--) { - *p += 2; - n = hexhex2nr(*p); - if (n < 0) { - return 0; - } - num = num * 256 + n; - } - *p += 2; - return (int)num; - } - - // TODO(bfredl): use schar_T representation and utfc_ptr2len - int clen = utf_ptr2len((char *)s); - int c = mb_cptr2char_adv((const char_u **)p); - if (clen == 1 && c > 127) { // Invalid UTF-8 byte - return 0; - } - return c; -} - -/// Handle setting 'listchars' or 'fillchars'. -/// Assume monocell characters -/// -/// @param varp either &curwin->w_p_lcs or &curwin->w_p_fcs -/// @return error message, NULL if it's OK. -static char *set_chars_option(win_T *wp, char_u **varp, bool set) -{ - int round, i, len, len2, entries; - char_u *p, *s; - int c1; - int c2 = 0; - int c3 = 0; - char_u *last_multispace = NULL; // Last occurrence of "multispace:" - char_u *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 - - struct chars_tab { - int *cp; ///< char value - char *name; ///< char id - int def; ///< default value - }; - struct chars_tab *tab; - - struct chars_tab fcs_tab[] = { - { &wp->w_p_fcs_chars.stl, "stl", ' ' }, - { &wp->w_p_fcs_chars.stlnc, "stlnc", ' ' }, - { &wp->w_p_fcs_chars.wbr, "wbr", ' ' }, - { &wp->w_p_fcs_chars.horiz, "horiz", 9472 }, // ─ - { &wp->w_p_fcs_chars.horizup, "horizup", 9524 }, // ┴ - { &wp->w_p_fcs_chars.horizdown, "horizdown", 9516 }, // ┬ - { &wp->w_p_fcs_chars.vert, "vert", 9474 }, // │ - { &wp->w_p_fcs_chars.vertleft, "vertleft", 9508 }, // ┤ - { &wp->w_p_fcs_chars.vertright, "vertright", 9500 }, // ├ - { &wp->w_p_fcs_chars.verthoriz, "verthoriz", 9532 }, // ┼ - { &wp->w_p_fcs_chars.fold, "fold", 183 }, // · - { &wp->w_p_fcs_chars.foldopen, "foldopen", '-' }, - { &wp->w_p_fcs_chars.foldclosed, "foldclose", '+' }, - { &wp->w_p_fcs_chars.foldsep, "foldsep", 9474 }, // │ - { &wp->w_p_fcs_chars.diff, "diff", '-' }, - { &wp->w_p_fcs_chars.msgsep, "msgsep", ' ' }, - { &wp->w_p_fcs_chars.eob, "eob", '~' }, - }; - struct chars_tab lcs_tab[] = { - { &wp->w_p_lcs_chars.eol, "eol", NUL }, - { &wp->w_p_lcs_chars.ext, "extends", NUL }, - { &wp->w_p_lcs_chars.nbsp, "nbsp", NUL }, - { &wp->w_p_lcs_chars.prec, "precedes", NUL }, - { &wp->w_p_lcs_chars.space, "space", NUL }, - { &wp->w_p_lcs_chars.tab2, "tab", NUL }, - { &wp->w_p_lcs_chars.lead, "lead", NUL }, - { &wp->w_p_lcs_chars.trail, "trail", NUL }, - { &wp->w_p_lcs_chars.conceal, "conceal", NUL }, - }; - - if (varp == &p_lcs || varp == &wp->w_p_lcs) { - tab = lcs_tab; - entries = ARRAY_SIZE(lcs_tab); - if (varp == &wp->w_p_lcs && wp->w_p_lcs[0] == NUL) { - varp = &p_lcs; - } - } else { - tab = fcs_tab; - entries = ARRAY_SIZE(fcs_tab); - if (varp == &wp->w_p_fcs && wp->w_p_fcs[0] == NUL) { - varp = &p_fcs; - } - if (*p_ambw == 'd') { - // XXX: If ambiwidth=double then some characters take 2 columns, - // which is forbidden (TUI limitation?). Set old defaults. - fcs_tab[3].def = '-'; - fcs_tab[4].def = '-'; - fcs_tab[5].def = '-'; - fcs_tab[6].def = '|'; - fcs_tab[7].def = '|'; - fcs_tab[8].def = '|'; - fcs_tab[9].def = '+'; - fcs_tab[10].def = '-'; - fcs_tab[13].def = '|'; - } - } - - // first round: check for valid value, second round: assign values - for (round = 0; round <= (set ? 1 : 0); round++) { - if (round > 0) { - // After checking that the value is valid: set defaults - for (i = 0; i < entries; i++) { - if (tab[i].cp != NULL) { - *(tab[i].cp) = tab[i].def; - } - } - if (varp == &p_lcs || varp == &wp->w_p_lcs) { - wp->w_p_lcs_chars.tab1 = NUL; - wp->w_p_lcs_chars.tab3 = NUL; - - xfree(wp->w_p_lcs_chars.multispace); - if (multispace_len > 0) { - wp->w_p_lcs_chars.multispace = xmalloc(((size_t)multispace_len + 1) * sizeof(int)); - wp->w_p_lcs_chars.multispace[multispace_len] = NUL; - } else { - wp->w_p_lcs_chars.multispace = NULL; - } - - xfree(wp->w_p_lcs_chars.leadmultispace); - if (lead_multispace_len > 0) { - wp->w_p_lcs_chars.leadmultispace - = xmalloc(((size_t)lead_multispace_len + 1) * sizeof(int)); - wp->w_p_lcs_chars.leadmultispace[lead_multispace_len] = NUL; - } else { - wp->w_p_lcs_chars.leadmultispace = NULL; - } - } - } - p = *varp; - while (*p) { - for (i = 0; i < entries; i++) { - len = (int)STRLEN(tab[i].name); - if (STRNCMP(p, tab[i].name, len) == 0 - && p[len] == ':' - && p[len + 1] != NUL) { - c2 = c3 = 0; - s = p + len + 1; - c1 = get_encoded_char_adv(&s); - if (c1 == 0 || char2cells(c1) > 1) { - return e_invarg; - } - if (tab[i].cp == &wp->w_p_lcs_chars.tab2) { - if (*s == NUL) { - return e_invarg; - } - c2 = get_encoded_char_adv(&s); - if (c2 == 0 || char2cells(c2) > 1) { - return e_invarg; - } - if (!(*s == ',' || *s == NUL)) { - c3 = get_encoded_char_adv(&s); - if (c3 == 0 || char2cells(c3) > 1) { - return e_invarg; - } - } - } - if (*s == ',' || *s == NUL) { - if (round > 0) { - if (tab[i].cp == &wp->w_p_lcs_chars.tab2) { - wp->w_p_lcs_chars.tab1 = c1; - wp->w_p_lcs_chars.tab2 = c2; - wp->w_p_lcs_chars.tab3 = c3; - } else if (tab[i].cp != NULL) { - *(tab[i].cp) = c1; - } - } - p = s; - break; - } - } - } - - if (i == entries) { - len = (int)STRLEN("multispace"); - len2 = (int)STRLEN("leadmultispace"); - if ((varp == &p_lcs || varp == &wp->w_p_lcs) - && STRNCMP(p, "multispace", len) == 0 - && p[len] == ':' - && p[len + 1] != NUL) { - s = p + len + 1; - if (round == 0) { - // Get length of lcs-multispace string in the first round - last_multispace = p; - multispace_len = 0; - while (*s != NUL && *s != ',') { - c1 = get_encoded_char_adv(&s); - if (c1 == 0 || char2cells(c1) > 1) { - return e_invarg; - } - multispace_len++; - } - if (multispace_len == 0) { - // lcs-multispace cannot be an empty string - return e_invarg; - } - p = s; - } else { - int multispace_pos = 0; - while (*s != NUL && *s != ',') { - c1 = get_encoded_char_adv(&s); - if (p == last_multispace) { - wp->w_p_lcs_chars.multispace[multispace_pos++] = c1; - } - } - p = s; - } - } else if ((varp == &p_lcs || varp == &wp->w_p_lcs) - && STRNCMP(p, "leadmultispace", len2) == 0 - && p[len2] == ':' - && p[len2 + 1] != NUL) { - s = p + len2 + 1; - if (round == 0) { - // get length of lcs-leadmultispace string in first round - last_lmultispace = p; - lead_multispace_len = 0; - while (*s != NUL && *s != ',') { - c1 = get_encoded_char_adv(&s); - if (c1 == 0 || char2cells(c1) > 1) { - return e_invarg; - } - lead_multispace_len++; - } - if (lead_multispace_len == 0) { - // lcs-leadmultispace cannot be an empty string - return e_invarg; - } - p = s; - } else { - int multispace_pos = 0; - while (*s != NUL && *s != ',') { - c1 = get_encoded_char_adv(&s); - if (p == last_lmultispace) { - wp->w_p_lcs_chars.leadmultispace[multispace_pos++] = c1; - } - } - p = s; - } - } else { - return e_invarg; - } - } - if (*p == ',') { - p++; - } - } - } - - return NULL; // no error -} - -/// Check validity of options with the 'statusline' format. -/// Return an untranslated error message or NULL. -char *check_stl_option(char *s) -{ - int groupdepth = 0; - static char errbuf[80]; - - while (*s) { - // Check for valid keys after % sequences - while (*s && *s != '%') { - s++; - } - if (!*s) { - break; - } - s++; - if (*s == '%' || *s == STL_TRUNCMARK || *s == STL_SEPARATE) { - s++; - continue; - } - if (*s == ')') { - s++; - if (--groupdepth < 0) { - break; - } - continue; - } - if (*s == '-') { - s++; - } - while (ascii_isdigit(*s)) { - s++; - } - if (*s == STL_USER_HL) { - continue; - } - if (*s == '.') { - s++; - while (*s && ascii_isdigit(*s)) { - s++; - } - } - if (*s == '(') { - groupdepth++; - continue; - } - if (vim_strchr(STL_ALL, *s) == NULL) { - return illegal_char(errbuf, sizeof(errbuf), *s); - } - if (*s == '{') { - bool reevaluate = (*++s == '%'); - - if (reevaluate && *++s == '}') { - // "}" is not allowed immediately after "%{%" - return illegal_char(errbuf, sizeof(errbuf), '}'); - } - while ((*s != '}' || (reevaluate && s[-1] != '%')) && *s) { - s++; - } - if (*s != '}') { - return e_unclosed_expression_sequence; - } - } - } - if (groupdepth != 0) { - return e_unbalanced_groups; - } - return NULL; -} - -static char *did_set_spell_option(bool is_spellfile) +/// Handle setting `winhighlight' in window "wp" +bool parse_winhl_opt(win_T *wp) { - char *errmsg = NULL; + const char *p = (const char *)wp->w_p_winhl; - if (is_spellfile) { - int l = (int)STRLEN(curwin->w_s->b_p_spf); - if (l > 0 - && (l < 4 || STRCMP(curwin->w_s->b_p_spf + l - 4, ".add") != 0)) { - errmsg = e_invarg; + if (!*p) { + if (wp->w_ns_hl_winhl && wp->w_ns_hl == wp->w_ns_hl_winhl) { + wp->w_ns_hl = 0; + wp->w_hl_needs_update = true; } - } - 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; - } - } + return true; } - return errmsg; -} - -/// Set curbuf->b_cap_prog to the regexp program for 'spellcapcheck'. -/// Return error message when failed, NULL when OK. -static char *compile_cap_prog(synblock_T *synblock) - FUNC_ATTR_NONNULL_ALL -{ - regprog_T *rp = synblock->b_cap_prog; - char_u *re; - - if (synblock->b_p_spc == NULL || *synblock->b_p_spc == NUL) { - synblock->b_cap_prog = NULL; + if (wp->w_ns_hl_winhl == 0) { + wp->w_ns_hl_winhl = (int)nvim_create_namespace(NULL_STRING); } else { - // Prepend a ^ so that we only match at one column - re = concat_str((char_u *)"^", synblock->b_p_spc); - synblock->b_cap_prog = vim_regcomp((char *)re, RE_MAGIC); - xfree(re); - if (synblock->b_cap_prog == NULL) { - synblock->b_cap_prog = rp; // restore the previous program - return e_invarg; - } + // namespace already exist. invalidate existing items + DecorProvider *dp = get_decor_provider(wp->w_ns_hl_winhl, true); + dp->hl_valid++; } + wp->w_ns_hl = wp->w_ns_hl_winhl; + int ns_hl = wp->w_ns_hl; - vim_regfree(rp); - return NULL; -} - -/// Handle setting `winhighlight' in window "wp" -static bool parse_winhl_opt(win_T *wp) -{ - int w_hl_id_normal = 0; - int w_hl_ids[HLF_COUNT] = { 0 }; - int hlf; - - const char *p = (const char *)wp->w_p_winhl; while (*p) { char *colon = strchr(p, ':'); if (!colon) { @@ -3973,34 +1910,22 @@ static bool parse_winhl_opt(win_T *wp) char *commap = xstrchrnul(hi, ','); size_t len = (size_t)(commap - hi); int hl_id = len ? syn_check_group(hi, len) : -1; + int hl_id_link = nlen ? syn_check_group(p, nlen) : 0; - if (strncmp("Normal", p, nlen) == 0) { - w_hl_id_normal = hl_id; - } else { - for (hlf = 0; hlf < HLF_COUNT; hlf++) { - if (strlen(hlf_names[hlf]) == nlen - && strncmp(hlf_names[hlf], p, nlen) == 0) { - w_hl_ids[hlf] = hl_id; - break; - } - } - if (hlf == HLF_COUNT) { - return false; - } - } + HlAttrs attrs = HLATTRS_INIT; + attrs.rgb_ae_attr |= HL_GLOBAL; + ns_hl_def(ns_hl, hl_id_link, attrs, hl_id, NULL); p = *commap ? commap + 1 : ""; } - wp->w_hl_id_normal = w_hl_id_normal; - memcpy(wp->w_hl_ids, w_hl_ids, sizeof(w_hl_ids)); wp->w_hl_needs_update = true; return true; } -// Set the script_ctx for an option, taking care of setting the buffer- or -// window-local value. -static void set_option_sctx_idx(int opt_idx, int opt_flags, sctx_T script_ctx) +/// Set the script_ctx for an option, taking care of setting the buffer- or +/// window-local value. +void set_option_sctx_idx(int opt_idx, int opt_flags, sctx_T script_ctx) { int both = (opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0; int indir = (int)options[opt_idx].indir; @@ -4009,7 +1934,7 @@ static void set_option_sctx_idx(int opt_idx, int opt_flags, sctx_T script_ctx) .script_ctx = { script_ctx.sc_sid, script_ctx.sc_seq, - script_ctx.sc_lnum + sourcing_lnum + script_ctx.sc_lnum + SOURCING_LNUM }, current_channel_id }; @@ -4041,6 +1966,7 @@ static char *set_bool_option(const int opt_idx, char_u *const varp, const int va { int old_value = *(int *)varp; int old_global_value = 0; + char *errmsg = NULL; // Disallow changing some options from secure mode if ((secure || sandbox != 0) @@ -4145,7 +2071,7 @@ static char *set_bool_option(const int opt_idx, char_u *const varp, const int va paste_option_changed(); } else if ((int *)varp == &p_ic && p_hls) { // when 'ignorecase' is set or reset and 'hlsearch' is set, redraw - redraw_all_later(SOME_VALID); + redraw_all_later(UPD_SOME_VALID); } else if ((int *)varp == &p_hls) { // when 'hlsearch' is set or reset: reset no_hlsearch set_no_hlsearch(false); @@ -4162,7 +2088,7 @@ static char *set_bool_option(const int opt_idx, char_u *const varp, const int va FOR_ALL_WINDOWS_IN_TAB(win, curtab) { if (win->w_p_pvw && win != curwin) { curwin->w_p_pvw = false; - return N_("E590: A preview window already exists"); + return e_preview_window_already_exists; } } } @@ -4221,10 +2147,7 @@ static char *set_bool_option(const int opt_idx, char_u *const varp, const int va } } else if ((int *)varp == &curwin->w_p_spell) { // 'spell' if (curwin->w_p_spell) { - char *errmsg = did_set_spelllang(curwin); - if (errmsg != NULL) { - emsg(_(errmsg)); - } + errmsg = did_set_spelllang(curwin); } } @@ -4243,7 +2166,7 @@ static char *set_bool_option(const int opt_idx, char_u *const varp, const int va // Enable Arabic shaping (major part of what Arabic requires) if (!p_arshape) { p_arshape = true; - redraw_all_later(NOT_VALID); + redraw_all_later(UPD_NOT_VALID); } } @@ -4261,7 +2184,7 @@ static char *set_bool_option(const int opt_idx, char_u *const varp, const int va p_deco = true; // Force-set the necessary keymap for arabic. - set_option_value("keymap", 0L, "arabic", OPT_LOCAL); + errmsg = set_option_value("keymap", 0L, "arabic", OPT_LOCAL); } else { /* * 'arabic' is reset, handle various sub-settings. @@ -4341,7 +2264,7 @@ static char *set_bool_option(const int opt_idx, char_u *const varp, const int va } check_redraw(options[opt_idx].flags); - return NULL; + return errmsg; } /// Set the value of a number option, taking care of side effects @@ -4785,51 +2708,8 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, char *errbuf, return errmsg; } -/// Trigger the OptionSet autocommand. -/// "opt_idx" is the index of the option being set. -/// "opt_flags" can be OPT_LOCAL etc. -/// "oldval" the old value -/// "oldval_l" the old local value (only non-NULL if global and local value are set) -/// "oldval_g" the old global value (only non-NULL if global and local value are set) -/// "newval" the new value -static void trigger_optionsset_string(int opt_idx, int opt_flags, char *oldval, char *oldval_l, - char *oldval_g, char *newval) -{ - // Don't do this recursively. - if (oldval != NULL - && newval != NULL - && *get_vim_var_str(VV_OPTION_TYPE) == NUL) { - 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, options[opt_idx].fullname, NULL, false, NULL); - reset_v_option_vars(); - } -} - /// Called after an option changed: check if something needs to be redrawn. -static void check_redraw(uint32_t flags) +void check_redraw(uint32_t flags) { // Careful: P_RCLR and P_RALL are a combination of other P_ flags bool doclear = (flags & P_RCLR) == P_RCLR; @@ -4843,15 +2723,15 @@ static void check_redraw(uint32_t flags) changed_window_setting(); } if (flags & P_RBUF) { - redraw_curbuf_later(NOT_VALID); + redraw_curbuf_later(UPD_NOT_VALID); } if (flags & P_RWINONLY) { - redraw_later(curwin, NOT_VALID); + redraw_later(curwin, UPD_NOT_VALID); } if (doclear) { - redraw_all_later(CLEAR); + redraw_all_later(UPD_CLEAR); } else if (all) { - redraw_all_later(NOT_VALID); + redraw_all_later(UPD_NOT_VALID); } } @@ -5001,7 +2881,7 @@ bool set_tty_option(const char *name, char *value) void set_tty_background(const char *value) { - if (option_was_set("bg") || strequal((char *)p_bg, value)) { + if (option_was_set("bg") || strequal(p_bg, value)) { // background is already set... ignore return; } @@ -5011,7 +2891,7 @@ void set_tty_background(const char *value) ? "autocmd VimEnter * ++once ++nested set bg=light" : "autocmd VimEnter * ++once ++nested set bg=dark"); } else { - set_option_value("bg", 0L, value, 0); + set_option_value_give_err("bg", 0L, value, 0); reset_option_was_set("bg"); } } @@ -5021,7 +2901,7 @@ void set_tty_background(const char *value) /// @param[in] arg Option name. /// /// @return Option index or -1 if option was not found. -static int findoption(const char *const arg) +int findoption(const char *const arg) FUNC_ATTR_NONNULL_ALL { return findoption_len(arg, strlen(arg)); @@ -5050,14 +2930,14 @@ getoption_T get_option_value(const char *name, long *numval, char **stringval, i return gov_unknown; } - char_u *varp = get_varp_scope(&(options[opt_idx]), opt_flags); + char_u *varp = (char_u *)get_varp_scope(&(options[opt_idx]), opt_flags); if (options[opt_idx].flags & P_STRING) { if (varp == NULL) { // hidden option return gov_hidden_string; } if (stringval != NULL) { - if ((char_u **)varp == &p_pt) { // 'pastetoggle' + if ((char **)varp == &p_pt) { // 'pastetoggle' *stringval = str2special_save(*(char **)(varp), false, false); } else { *stringval = xstrdup(*(char **)(varp)); @@ -5176,7 +3056,7 @@ int get_option_value_strict(char *name, int64_t *numval, char **stringval, int o // only getting a pointer, no need to use aucmd_prepbuf() curbuf = (buf_T *)from; curwin->w_buffer = curbuf; - varp = get_varp_scope(p, OPT_LOCAL); + varp = (char_u *)get_varp_scope(p, OPT_LOCAL); curbuf = save_curbuf; curwin->w_buffer = curbuf; } @@ -5184,7 +3064,7 @@ int get_option_value_strict(char *name, int64_t *numval, char **stringval, int o win_T *save_curwin = curwin; curwin = (win_T *)from; curbuf = curwin->w_buffer; - varp = get_varp_scope(p, OPT_LOCAL); + varp = (char_u *)get_varp_scope(p, OPT_LOCAL); curwin = save_curwin; curbuf = curwin->w_buffer; } @@ -5207,6 +3087,49 @@ int get_option_value_strict(char *name, int64_t *numval, char **stringval, int o return rv; } +/// Return the flags for the option at 'opt_idx'. +uint32_t get_option_flags(int opt_idx) +{ + return options[opt_idx].flags; +} + +/// Set a flag for the option at 'opt_idx'. +void set_option_flag(int opt_idx, uint32_t flag) +{ + options[opt_idx].flags |= flag; +} + +/// Clear a flag for the option at 'opt_idx'. +void clear_option_flag(int opt_idx, uint32_t flag) +{ + options[opt_idx].flags &= ~flag; +} + +/// Returns true if the option at 'opt_idx' is a global option +bool is_global_option(int opt_idx) +{ + return options[opt_idx].indir == PV_NONE; +} + +/// Returns true if the option at 'opt_idx' is a global option which also has a +/// local value. +int is_global_local_option(int opt_idx) +{ + return options[opt_idx].indir & PV_BOTH; +} + +/// Returns true if the option at 'opt_idx' is a window-local option +bool is_window_local_option(int opt_idx) +{ + return options[opt_idx].var == VAR_WIN; +} + +/// Returns true if the option at 'opt_idx' is a hidden option +bool is_hidden_option(int opt_idx) +{ + return options[opt_idx].var == NULL; +} + /// Set the value of an option /// /// @param[in] name Option name. @@ -5217,7 +3140,7 @@ int get_option_value_strict(char *name, int64_t *numval, char **stringval, int o /// is cleared (the exact semantics of this depend /// on the option). /// -/// @return NULL on success, error message on error. +/// @return NULL on success, an untranslated error message on error. char *set_option_value(const char *const name, const long number, const char *const string, const int opt_flags) FUNC_ATTR_NONNULL_ARG(1) @@ -5247,7 +3170,7 @@ char *set_option_value(const char *const name, const long number, const char *co return set_string_option(opt_idx, s, opt_flags); } - varp = get_varp_scope(&(options[opt_idx]), 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; @@ -5287,6 +3210,18 @@ char *set_option_value(const char *const name, const long number, const char *co return NULL; } +/// Call set_option_value() and when an error is returned report it. +/// +/// @param opt_flags OPT_LOCAL or 0 (both) +void set_option_value_give_err(const char *name, long number, const char *string, int opt_flags) +{ + char *errmsg = set_option_value(name, number, string, opt_flags); + + if (errmsg != NULL) { + emsg(_(errmsg)); + } +} + /// Return true if "name" is a string option. /// Returns false if option "name" does not exist. bool is_string_option(const char *name) @@ -5364,14 +3299,14 @@ static void showoptions(int all, int opt_flags) item_count = 0; for (p = &options[0]; p->fullname != NULL; p++) { // apply :filter /pat/ - if (message_filtered((char_u *)p->fullname)) { + if (message_filtered(p->fullname)) { continue; } varp = NULL; if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) != 0) { if (p->indir != PV_NONE) { - varp = get_varp_scope(p, opt_flags); + varp = (char_u *)get_varp_scope(p, opt_flags); } } else { varp = get_varp(p); @@ -5475,13 +3410,12 @@ void ui_refresh_options(void) /// @param opt_flags OPT_LOCAL or OPT_GLOBAL static void showoneopt(vimoption_T *p, int opt_flags) { - char_u *varp; int save_silent = silent_mode; silent_mode = false; info_message = true; // use mch_msg(), not mch_errmsg() - varp = get_varp_scope(p, opt_flags); + char_u *varp = (char_u *)get_varp_scope(p, opt_flags); // for 'modified' we also need to check if 'ff' or 'fenc' changed. if ((p->flags & P_BOOL) && ((int *)varp == &curbuf->b_changed @@ -5527,7 +3461,7 @@ static void showoneopt(vimoption_T *p, int opt_flags) int makeset(FILE *fd, int opt_flags, int local_only) { vimoption_T *p; - char_u *varp; // currently used value + char *varp; // currently used value char_u *varp_fresh; // local value char_u *varp_local = NULL; // fresh value char *cmd; @@ -5564,7 +3498,7 @@ int makeset(FILE *fd, int opt_flags, int local_only) continue; } // Global values are only written when not at the default value. - if ((opt_flags & OPT_GLOBAL) && optval_default(p, varp)) { + if ((opt_flags & OPT_GLOBAL) && optval_default(p, (char_u *)varp)) { continue; } @@ -5583,11 +3517,11 @@ 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 = get_varp_scope(p, OPT_GLOBAL); + varp_fresh = (char_u *)get_varp_scope(p, OPT_GLOBAL); if (!optval_default(p, varp_fresh)) { round = 1; - varp_local = varp; - varp = varp_fresh; + varp_local = (char_u *)varp; + varp = (char *)varp_fresh; } } } @@ -5595,7 +3529,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 = varp_local, round++) { + for (; round <= 2; varp = (char *)varp_local, round++) { if (round == 1 || (opt_flags & OPT_GLOBAL)) { cmd = "set"; } else { @@ -5623,8 +3557,7 @@ int makeset(FILE *fd, int opt_flags, int local_only) } do_endif = true; } - if (put_setstring(fd, cmd, p->fullname, (char_u **)varp, - p->flags) == FAIL) { + if (put_setstring(fd, cmd, p->fullname, (char **)varp, p->flags) == FAIL) { return FAIL; } if (do_endif) { @@ -5645,29 +3578,25 @@ int makeset(FILE *fd, int opt_flags, int local_only) int makefoldset(FILE *fd) { if (put_setstring(fd, "setlocal", "fdm", &curwin->w_p_fdm, 0) == FAIL - || put_setstring(fd, "setlocal", "fde", &curwin->w_p_fde, 0) - == FAIL - || put_setstring(fd, "setlocal", "fmr", &curwin->w_p_fmr, 0) - == FAIL - || put_setstring(fd, "setlocal", "fdi", &curwin->w_p_fdi, 0) - == FAIL + || put_setstring(fd, "setlocal", "fde", &curwin->w_p_fde, 0) == FAIL + || put_setstring(fd, "setlocal", "fmr", &curwin->w_p_fmr, 0) == FAIL + || put_setstring(fd, "setlocal", "fdi", &curwin->w_p_fdi, 0) == FAIL || put_setnum(fd, "setlocal", "fdl", &curwin->w_p_fdl) == FAIL || put_setnum(fd, "setlocal", "fml", &curwin->w_p_fml) == FAIL || put_setnum(fd, "setlocal", "fdn", &curwin->w_p_fdn) == FAIL - || put_setbool(fd, "setlocal", "fen", - curwin->w_p_fen) == FAIL) { + || put_setbool(fd, "setlocal", "fen", curwin->w_p_fen) == FAIL) { return FAIL; } return OK; } -static int put_setstring(FILE *fd, char *cmd, char *name, char_u **valuep, uint64_t flags) +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_u *p; + char *p; if (fprintf(fd, "%s %s=", cmd, name) < 0) { return FAIL; @@ -5677,7 +3606,7 @@ static int put_setstring(FILE *fd, char *cmd, char *name, char_u **valuep, uint6 // options some characters have to be escaped with // CTRL-V or backslash if (valuep == &p_pt) { - s = *valuep; + s = (char_u *)(*valuep); while (*s != NUL) { if (put_escstr(fd, (char_u *)str2special((const char **)&s, false, false), 2) @@ -5690,27 +3619,27 @@ static int put_setstring(FILE *fd, char *cmd, char *name, char_u **valuep, uint6 // replace home directory in the whole option value into "buf" buf = xmalloc(size); - home_replace(NULL, (char *)(*valuep), (char *)buf, size, false); + home_replace(NULL, *valuep, (char *)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 // can be expanded when read back. if (size >= MAXPATHL && (flags & P_COMMA) != 0 - && vim_strchr((char *)(*valuep), ',') != NULL) { + && vim_strchr(*valuep, ',') != NULL) { part = xmalloc(size); // write line break to clear the option, e.g. ':set rtp=' if (put_eol(fd) == FAIL) { goto fail; } - p = buf; + p = (char *)buf; while (*p != NUL) { // for each comma separated option part, append value to // the option, :set rtp+=value if (fprintf(fd, "%s %s+=", cmd, name) < 0) { goto fail; } - (void)copy_option_part((char **)&p, (char *)part, size, ","); + (void)copy_option_part(&p, (char *)part, size, ","); if (put_escstr(fd, part, 2) == FAIL || put_eol(fd) == FAIL) { goto fail; } @@ -5724,7 +3653,7 @@ static int put_setstring(FILE *fd, char *cmd, char *name, char_u **valuep, uint6 return FAIL; } xfree(buf); - } else if (put_escstr(fd, *valuep, 2) == FAIL) { + } else if (put_escstr(fd, (char_u *)(*valuep), 2) == FAIL) { return FAIL; } } @@ -5771,47 +3700,6 @@ static int put_setbool(FILE *fd, char *cmd, char *name, int value) return OK; } -/// Compute columns for ruler and shown command. 'sc_col' is also used to -/// decide what the maximum length of a message on the status line can be. -/// If there is a status line for the last window, 'sc_col' is independent -/// of 'ru_col'. - -#define COL_RULER 17 // columns needed by standard ruler - -void comp_col(void) -{ - int last_has_status = (p_ls > 1 || (p_ls == 1 && !ONE_WINDOW)); - - sc_col = 0; - ru_col = 0; - if (p_ru) { - ru_col = (ru_wid ? ru_wid : COL_RULER) + 1; - // no last status line, adjust sc_col - if (!last_has_status) { - sc_col = ru_col; - } - } - if (p_sc) { - sc_col += SHOWCMD_COLS; - if (!p_ru || last_has_status) { // no need for separating space - sc_col++; - } - } - assert(sc_col >= 0 - && INT_MIN + sc_col <= Columns); - sc_col = Columns - sc_col; - assert(ru_col >= 0 - && INT_MIN + ru_col <= Columns); - ru_col = Columns - ru_col; - if (sc_col <= 0) { // screen too narrow, will become a mess - sc_col = 1; - } - if (ru_col <= 0) { - ru_col = 1; - } - set_vim_var_nr(VV_ECHOSPACE, sc_col - 1); -} - // Unset local option value, similar to ":set opt<". void unset_global_local_option(char *name, void *from) { @@ -5891,7 +3779,7 @@ void unset_global_local_option(char *name, void *from) clear_string_option(&((win_T *)from)->w_p_stl); break; case PV_WBR: - clear_string_option((char_u **)&((win_T *)from)->w_p_wbr); + clear_string_option(&((win_T *)from)->w_p_wbr); break; case PV_UL: buf->b_p_ul = NO_LOCAL_UNDOLEVEL; @@ -5905,12 +3793,12 @@ void unset_global_local_option(char *name, void *from) case PV_LCS: clear_string_option(&((win_T *)from)->w_p_lcs); set_chars_option((win_T *)from, &((win_T *)from)->w_p_lcs, true); - redraw_later((win_T *)from, NOT_VALID); + redraw_later((win_T *)from, UPD_NOT_VALID); break; case PV_FCS: clear_string_option(&((win_T *)from)->w_p_fcs); set_chars_option((win_T *)from, &((win_T *)from)->w_p_fcs, true); - redraw_later((win_T *)from, NOT_VALID); + redraw_later((win_T *)from, UPD_NOT_VALID); break; case PV_VE: clear_string_option(&((win_T *)from)->w_p_ve); @@ -5920,76 +3808,83 @@ void unset_global_local_option(char *name, void *from) } /// Get pointer to option variable, depending on local or global scope. -static char_u *get_varp_scope(vimoption_T *p, int opt_flags) +static char *get_varp_scope(vimoption_T *p, int opt_flags) { if ((opt_flags & OPT_GLOBAL) && p->indir != PV_NONE) { if (p->var == VAR_WIN) { - return (char_u *)GLOBAL_WO(get_varp(p)); + return GLOBAL_WO(get_varp(p)); } - return p->var; + return (char *)p->var; } if ((opt_flags & OPT_LOCAL) && ((int)p->indir & PV_BOTH)) { switch ((int)p->indir) { case PV_FP: - return (char_u *)&(curbuf->b_p_fp); + return (char *)&(curbuf->b_p_fp); case PV_EFM: - return (char_u *)&(curbuf->b_p_efm); + return (char *)&(curbuf->b_p_efm); case PV_GP: - return (char_u *)&(curbuf->b_p_gp); + return (char *)&(curbuf->b_p_gp); case PV_MP: - return (char_u *)&(curbuf->b_p_mp); + return (char *)&(curbuf->b_p_mp); case PV_EP: - return (char_u *)&(curbuf->b_p_ep); + return (char *)&(curbuf->b_p_ep); case PV_KP: - return (char_u *)&(curbuf->b_p_kp); + return (char *)&(curbuf->b_p_kp); case PV_PATH: - return (char_u *)&(curbuf->b_p_path); + return (char *)&(curbuf->b_p_path); case PV_AR: - return (char_u *)&(curbuf->b_p_ar); + return (char *)&(curbuf->b_p_ar); case PV_TAGS: - return (char_u *)&(curbuf->b_p_tags); + return (char *)&(curbuf->b_p_tags); case PV_TC: - return (char_u *)&(curbuf->b_p_tc); + return (char *)&(curbuf->b_p_tc); case PV_SISO: - return (char_u *)&(curwin->w_p_siso); + return (char *)&(curwin->w_p_siso); case PV_SO: - return (char_u *)&(curwin->w_p_so); + return (char *)&(curwin->w_p_so); case PV_DEF: - return (char_u *)&(curbuf->b_p_def); + return (char *)&(curbuf->b_p_def); case PV_INC: - return (char_u *)&(curbuf->b_p_inc); + return (char *)&(curbuf->b_p_inc); case PV_DICT: - return (char_u *)&(curbuf->b_p_dict); + return (char *)&(curbuf->b_p_dict); case PV_TSR: - return (char_u *)&(curbuf->b_p_tsr); + return (char *)&(curbuf->b_p_tsr); case PV_TSRFU: - return (char_u *)&(curbuf->b_p_tsrfu); + return (char *)&(curbuf->b_p_tsrfu); case PV_TFU: - return (char_u *)&(curbuf->b_p_tfu); + return (char *)&(curbuf->b_p_tfu); case PV_SBR: - return (char_u *)&(curwin->w_p_sbr); + return (char *)&(curwin->w_p_sbr); case PV_STL: - return (char_u *)&(curwin->w_p_stl); + return (char *)&(curwin->w_p_stl); case PV_WBR: - return (char_u *)&(curwin->w_p_wbr); + return (char *)&(curwin->w_p_wbr); case PV_UL: - return (char_u *)&(curbuf->b_p_ul); + return (char *)&(curbuf->b_p_ul); case PV_LW: - return (char_u *)&(curbuf->b_p_lw); + return (char *)&(curbuf->b_p_lw); case PV_BKC: - return (char_u *)&(curbuf->b_p_bkc); + return (char *)&(curbuf->b_p_bkc); case PV_MENC: - return (char_u *)&(curbuf->b_p_menc); + return (char *)&(curbuf->b_p_menc); case PV_FCS: - return (char_u *)&(curwin->w_p_fcs); + return (char *)&(curwin->w_p_fcs); case PV_LCS: - return (char_u *)&(curwin->w_p_lcs); + return (char *)&(curwin->w_p_lcs); case PV_VE: - return (char_u *)&(curwin->w_p_ve); + return (char *)&(curwin->w_p_ve); } return NULL; // "cannot happen" } - return get_varp(p); + return (char *)get_varp(p); +} + +/// Get pointer to option variable at 'opt_idx', depending on local or global +/// scope. +char *get_option_varp_scope(int opt_idx, int opt_flags) +{ + return get_varp_scope(&(options[opt_idx]), opt_flags); } /// Get pointer to option variable. @@ -6303,13 +4198,25 @@ static char_u *get_varp(vimoption_T *p) return (char_u *)&(curbuf->b_p_wm); } +/// Return a pointer to the variable for option at 'opt_idx' +char_u *get_option_var(int opt_idx) +{ + return options[opt_idx].var; +} + +/// Return the full name of the option at 'opt_idx' +char *get_option_fullname(int opt_idx) +{ + return options[opt_idx].fullname; +} + /// Get the value of 'equalprg', either the buffer-local one or the global one. char_u *get_equalprg(void) { if (*curbuf->b_p_ep == NUL) { return p_ep; } - return curbuf->b_p_ep; + return (char_u *)curbuf->b_p_ep; } /// Copy options from one window to another. @@ -6318,7 +4225,15 @@ void win_copy_options(win_T *wp_from, win_T *wp_to) { copy_winopt(&wp_from->w_onebuf_opt, &wp_to->w_onebuf_opt); copy_winopt(&wp_from->w_allbuf_opt, &wp_to->w_allbuf_opt); - didset_window_options(wp_to); + didset_window_options(wp_to, true); +} + +static char *copy_option_val(const char *val) +{ + if (val == empty_option) { + return empty_option; // no need to allocate memory + } + return xstrdup(val); } /// Copy the options from one winopt_T to another. @@ -6329,21 +4244,23 @@ void copy_winopt(winopt_T *from, winopt_T *to) { to->wo_arab = from->wo_arab; to->wo_list = from->wo_list; + to->wo_lcs = copy_option_val(from->wo_lcs); + to->wo_fcs = copy_option_val(from->wo_fcs); to->wo_nu = from->wo_nu; to->wo_rnu = from->wo_rnu; - to->wo_ve = vim_strsave(from->wo_ve); + to->wo_ve = copy_option_val(from->wo_ve); to->wo_ve_flags = from->wo_ve_flags; to->wo_nuw = from->wo_nuw; to->wo_rl = from->wo_rl; - to->wo_rlc = vim_strsave(from->wo_rlc); - to->wo_sbr = vim_strsave(from->wo_sbr); - to->wo_stl = vim_strsave(from->wo_stl); - to->wo_wbr = xstrdup(from->wo_wbr); + to->wo_rlc = copy_option_val(from->wo_rlc); + to->wo_sbr = copy_option_val(from->wo_sbr); + to->wo_stl = copy_option_val(from->wo_stl); + to->wo_wbr = copy_option_val(from->wo_wbr); to->wo_wrap = from->wo_wrap; to->wo_wrap_save = from->wo_wrap_save; to->wo_lbr = from->wo_lbr; to->wo_bri = from->wo_bri; - to->wo_briopt = vim_strsave(from->wo_briopt); + to->wo_briopt = copy_option_val(from->wo_briopt); to->wo_scb = from->wo_scb; to->wo_scb_save = from->wo_scb_save; to->wo_crb = from->wo_crb; @@ -6351,32 +4268,28 @@ void copy_winopt(winopt_T *from, winopt_T *to) to->wo_spell = from->wo_spell; to->wo_cuc = from->wo_cuc; to->wo_cul = from->wo_cul; - to->wo_culopt = vim_strsave(from->wo_culopt); - to->wo_cc = vim_strsave(from->wo_cc); + to->wo_culopt = copy_option_val(from->wo_culopt); + to->wo_cc = copy_option_val(from->wo_cc); to->wo_diff = from->wo_diff; to->wo_diff_saved = from->wo_diff_saved; - to->wo_cocu = vim_strsave(from->wo_cocu); + to->wo_cocu = copy_option_val(from->wo_cocu); to->wo_cole = from->wo_cole; - to->wo_fdc = vim_strsave(from->wo_fdc); - to->wo_fdc_save = from->wo_diff_saved - ? vim_strsave(from->wo_fdc_save) : empty_option; + to->wo_fdc = copy_option_val(from->wo_fdc); + to->wo_fdc_save = from->wo_diff_saved ? xstrdup(from->wo_fdc_save) : empty_option; to->wo_fen = from->wo_fen; to->wo_fen_save = from->wo_fen_save; - to->wo_fdi = vim_strsave(from->wo_fdi); + to->wo_fdi = copy_option_val(from->wo_fdi); to->wo_fml = from->wo_fml; to->wo_fdl = from->wo_fdl; to->wo_fdl_save = from->wo_fdl_save; - to->wo_fdm = vim_strsave(from->wo_fdm); - to->wo_fdm_save = from->wo_diff_saved - ? vim_strsave(from->wo_fdm_save) : empty_option; + to->wo_fdm = copy_option_val(from->wo_fdm); + to->wo_fdm_save = from->wo_diff_saved ? xstrdup(from->wo_fdm_save) : empty_option; to->wo_fdn = from->wo_fdn; - to->wo_fde = vim_strsave(from->wo_fde); - to->wo_fdt = vim_strsave(from->wo_fdt); - to->wo_fmr = vim_strsave(from->wo_fmr); - to->wo_scl = vim_strsave(from->wo_scl); - to->wo_winhl = vim_strsave(from->wo_winhl); - to->wo_fcs = vim_strsave(from->wo_fcs); - to->wo_lcs = vim_strsave(from->wo_lcs); + to->wo_fde = copy_option_val(from->wo_fde); + to->wo_fdt = copy_option_val(from->wo_fdt); + to->wo_fmr = copy_option_val(from->wo_fmr); + to->wo_scl = copy_option_val(from->wo_scl); + to->wo_winhl = copy_option_val(from->wo_winhl); to->wo_winbl = from->wo_winbl; // Copy the script context so that we know were the value was last set. @@ -6411,10 +4324,10 @@ static void check_winopt(winopt_T *wop) check_string_option(&wop->wo_cocu); check_string_option(&wop->wo_briopt); check_string_option(&wop->wo_winhl); - check_string_option(&wop->wo_fcs); check_string_option(&wop->wo_lcs); + check_string_option(&wop->wo_fcs); check_string_option(&wop->wo_ve); - check_string_option((char_u **)&wop->wo_wbr); + check_string_option(&wop->wo_wbr); } /// Free the allocated memory inside a winopt_T. @@ -6437,13 +4350,13 @@ void clear_winopt(winopt_T *wop) clear_string_option(&wop->wo_cocu); clear_string_option(&wop->wo_briopt); clear_string_option(&wop->wo_winhl); - clear_string_option(&wop->wo_fcs); clear_string_option(&wop->wo_lcs); + clear_string_option(&wop->wo_fcs); clear_string_option(&wop->wo_ve); - clear_string_option((char_u **)&wop->wo_wbr); + clear_string_option(&wop->wo_wbr); } -void didset_window_options(win_T *wp) +void didset_window_options(win_T *wp, bool valid_cursor) { check_colorcolumn(wp); briopt_check(wp); @@ -6452,7 +4365,7 @@ void didset_window_options(win_T *wp) set_chars_option(wp, &wp->w_p_lcs, true); parse_winhl_opt(wp); // sets w_hl_needs_update also for w_p_winbl check_blending(wp); - set_winbar_win(wp, false); + set_winbar_win(wp, false, valid_cursor); wp->w_grid_alloc.blending = wp->w_p_winbl > 0; } @@ -6515,14 +4428,14 @@ void buf_copy_options(buf_T *buf, int flags) } if (should_copy || (flags & BCO_ALWAYS)) { - memset(buf->b_p_script_ctx, 0, sizeof(buf->b_p_script_ctx)); + CLEAR_FIELD(buf->b_p_script_ctx); init_buf_opt_idx(); // Don't copy the options specific to a help buffer when // BCO_NOHELP is given or the options were initialized already // (jumping back to a help file with CTRL-T or CTRL-O) dont_do_help = ((flags & BCO_NOHELP) && buf->b_help) || buf->b_p_initialized; if (dont_do_help) { // don't free b_p_isk - save_p_isk = buf->b_p_isk; + save_p_isk = (char_u *)buf->b_p_isk; buf->b_p_isk = NULL; } // Always free the allocated strings. If not already initialized, @@ -6530,19 +4443,19 @@ void buf_copy_options(buf_T *buf, int flags) if (!buf->b_p_initialized) { free_buf_options(buf, true); buf->b_p_ro = false; // don't copy readonly - buf->b_p_fenc = vim_strsave(p_fenc); + buf->b_p_fenc = xstrdup(p_fenc); switch (*p_ffs) { case 'm': - buf->b_p_ff = vim_strsave((char_u *)FF_MAC); + buf->b_p_ff = xstrdup(FF_MAC); break; case 'd': - buf->b_p_ff = vim_strsave((char_u *)FF_DOS); + buf->b_p_ff = xstrdup(FF_DOS); break; case 'u': - buf->b_p_ff = vim_strsave((char_u *)FF_UNIX); + buf->b_p_ff = xstrdup(FF_UNIX); break; default: - buf->b_p_ff = vim_strsave(p_ff); + buf->b_p_ff = xstrdup(p_ff); break; } buf->b_p_bh = empty_option; @@ -6587,44 +4500,42 @@ void buf_copy_options(buf_T *buf, int flags) buf->b_p_swf = p_swf; COPY_OPT_SCTX(buf, BV_SWF); } - buf->b_p_cpt = vim_strsave(p_cpt); + buf->b_p_cpt = xstrdup(p_cpt); COPY_OPT_SCTX(buf, BV_CPT); #ifdef BACKSLASH_IN_FILENAME buf->b_p_csl = vim_strsave(p_csl); COPY_OPT_SCTX(buf, BV_CSL); #endif - buf->b_p_cfu = vim_strsave(p_cfu); + buf->b_p_cfu = xstrdup(p_cfu); COPY_OPT_SCTX(buf, BV_CFU); - buf->b_p_ofu = vim_strsave(p_ofu); + buf->b_p_ofu = xstrdup(p_ofu); COPY_OPT_SCTX(buf, BV_OFU); - buf->b_p_tfu = vim_strsave(p_tfu); + buf->b_p_tfu = xstrdup(p_tfu); COPY_OPT_SCTX(buf, BV_TFU); - buf->b_p_umf = vim_strsave(p_umf); + buf->b_p_umf = xstrdup(p_umf); COPY_OPT_SCTX(buf, BV_UMF); buf->b_p_sts = p_sts; COPY_OPT_SCTX(buf, BV_STS); buf->b_p_sts_nopaste = p_sts_nopaste; - buf->b_p_vsts = vim_strsave(p_vsts); + buf->b_p_vsts = xstrdup(p_vsts); COPY_OPT_SCTX(buf, BV_VSTS); if (p_vsts && p_vsts != empty_option) { (void)tabstop_set(p_vsts, &buf->b_p_vsts_array); } else { buf->b_p_vsts_array = NULL; } - buf->b_p_vsts_nopaste = p_vsts_nopaste - ? vim_strsave(p_vsts_nopaste) - : NULL; - buf->b_p_com = vim_strsave(p_com); + buf->b_p_vsts_nopaste = p_vsts_nopaste ? xstrdup(p_vsts_nopaste) : NULL; + buf->b_p_com = xstrdup(p_com); COPY_OPT_SCTX(buf, BV_COM); - buf->b_p_cms = vim_strsave(p_cms); + buf->b_p_cms = xstrdup(p_cms); COPY_OPT_SCTX(buf, BV_CMS); - buf->b_p_fo = vim_strsave(p_fo); + buf->b_p_fo = xstrdup(p_fo); COPY_OPT_SCTX(buf, BV_FO); - buf->b_p_flp = vim_strsave(p_flp); + buf->b_p_flp = xstrdup(p_flp); COPY_OPT_SCTX(buf, BV_FLP); - buf->b_p_nf = vim_strsave(p_nf); + buf->b_p_nf = xstrdup(p_nf); COPY_OPT_SCTX(buf, BV_NF); - buf->b_p_mps = vim_strsave(p_mps); + buf->b_p_mps = xstrdup(p_mps); COPY_OPT_SCTX(buf, BV_MPS); buf->b_p_si = p_si; COPY_OPT_SCTX(buf, BV_SI); @@ -6634,18 +4545,18 @@ void buf_copy_options(buf_T *buf, int flags) COPY_OPT_SCTX(buf, BV_CI); buf->b_p_cin = p_cin; COPY_OPT_SCTX(buf, BV_CIN); - buf->b_p_cink = vim_strsave(p_cink); + buf->b_p_cink = xstrdup(p_cink); COPY_OPT_SCTX(buf, BV_CINK); - buf->b_p_cino = vim_strsave(p_cino); + buf->b_p_cino = xstrdup(p_cino); COPY_OPT_SCTX(buf, BV_CINO); - buf->b_p_cinsd = vim_strsave(p_cinsd); + buf->b_p_cinsd = xstrdup(p_cinsd); COPY_OPT_SCTX(buf, BV_CINSD); // Don't copy 'filetype', it must be detected buf->b_p_ft = empty_option; buf->b_p_pi = p_pi; COPY_OPT_SCTX(buf, BV_PI); - buf->b_p_cinw = vim_strsave(p_cinw); + buf->b_p_cinw = xstrdup(p_cinw); COPY_OPT_SCTX(buf, BV_CINW); buf->b_p_lisp = p_lisp; COPY_OPT_SCTX(buf, BV_LISP); @@ -6654,25 +4565,25 @@ void buf_copy_options(buf_T *buf, int flags) buf->b_p_smc = p_smc; COPY_OPT_SCTX(buf, BV_SMC); buf->b_s.b_syn_isk = empty_option; - buf->b_s.b_p_spc = vim_strsave(p_spc); + buf->b_s.b_p_spc = xstrdup(p_spc); COPY_OPT_SCTX(buf, BV_SPC); (void)compile_cap_prog(&buf->b_s); - buf->b_s.b_p_spf = vim_strsave(p_spf); + buf->b_s.b_p_spf = xstrdup(p_spf); COPY_OPT_SCTX(buf, BV_SPF); - buf->b_s.b_p_spl = vim_strsave(p_spl); + buf->b_s.b_p_spl = xstrdup(p_spl); COPY_OPT_SCTX(buf, BV_SPL); - buf->b_s.b_p_spo = vim_strsave(p_spo); + buf->b_s.b_p_spo = xstrdup(p_spo); COPY_OPT_SCTX(buf, BV_SPO); - buf->b_p_inde = vim_strsave(p_inde); + buf->b_p_inde = xstrdup(p_inde); COPY_OPT_SCTX(buf, BV_INDE); - buf->b_p_indk = vim_strsave(p_indk); + buf->b_p_indk = xstrdup(p_indk); COPY_OPT_SCTX(buf, BV_INDK); buf->b_p_fp = empty_option; - buf->b_p_fex = vim_strsave(p_fex); + buf->b_p_fex = xstrdup(p_fex); COPY_OPT_SCTX(buf, BV_FEX); - buf->b_p_sua = vim_strsave(p_sua); + buf->b_p_sua = xstrdup(p_sua); COPY_OPT_SCTX(buf, BV_SUA); - buf->b_p_keymap = vim_strsave(p_keymap); + buf->b_p_keymap = xstrdup(p_keymap); COPY_OPT_SCTX(buf, BV_KMAP); buf->b_kmap_state |= KEYMAP_INIT; // This isn't really an option, but copying the langmap and IME @@ -6699,12 +4610,12 @@ void buf_copy_options(buf_T *buf, int flags) buf->b_tc_flags = 0; buf->b_p_def = empty_option; buf->b_p_inc = empty_option; - buf->b_p_inex = vim_strsave(p_inex); + buf->b_p_inex = xstrdup(p_inex); COPY_OPT_SCTX(buf, BV_INEX); buf->b_p_dict = empty_option; buf->b_p_tsr = empty_option; buf->b_p_tsrfu = empty_option; - buf->b_p_qe = vim_strsave(p_qe); + buf->b_p_qe = xstrdup(p_qe); COPY_OPT_SCTX(buf, BV_QE); buf->b_p_udf = p_udf; COPY_OPT_SCTX(buf, BV_UDF); @@ -6718,19 +4629,19 @@ void buf_copy_options(buf_T *buf, int flags) * or to a help buffer. */ if (dont_do_help) { - buf->b_p_isk = save_p_isk; + buf->b_p_isk = (char *)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 { buf->b_p_vts_array = NULL; } } else { - buf->b_p_isk = vim_strsave(p_isk); + buf->b_p_isk = xstrdup(p_isk); COPY_OPT_SCTX(buf, BV_ISK); did_isk = true; buf->b_p_ts = p_ts; COPY_OPT_SCTX(buf, BV_TS); - buf->b_p_vts = vim_strsave(p_vts); + buf->b_p_vts = xstrdup(p_vts); COPY_OPT_SCTX(buf, BV_VTS); if (p_vts && p_vts != empty_option && !buf->b_p_vts_array) { (void)tabstop_set(p_vts, &buf->b_p_vts_array); @@ -7051,7 +4962,7 @@ 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 = NameBuff; + var = (char_u *)NameBuff; } else { var = (char_u *)""; } @@ -7084,9 +4995,7 @@ void ExpandOldSetting(int *num_file, char ***file) /// @param opt_flags OPT_GLOBAL and/or OPT_LOCAL static void option_value2string(vimoption_T *opp, int opt_flags) { - char_u *varp; - - varp = get_varp_scope(opp, opt_flags); + char_u *varp = (char_u *)get_varp_scope(opp, opt_flags); if (opp->flags & P_NUM) { long wc = 0; @@ -7108,7 +5017,7 @@ static void option_value2string(vimoption_T *opp, int opt_flags) } else if (opp->flags & P_EXPAND) { home_replace(NULL, (char *)varp, (char *)NameBuff, MAXPATHL, false); // Translate 'pastetoggle' into special key names. - } else if ((char_u **)opp->var == &p_pt) { + } else if ((char **)opp->var == &p_pt) { str2specialbuf((const char *)p_pt, (char *)NameBuff, MAXPATHL); } else { STRLCPY(NameBuff, varp, MAXPATHL); @@ -7130,25 +5039,14 @@ static int wc_use_keyname(char_u *varp, long *wcp) return false; } -/// Return true if format option 'x' is in effect. -/// Take care of no formatting when 'paste' is set. -bool has_format_option(int x) - FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT -{ - if (p_paste) { - return false; - } - return vim_strchr((char *)curbuf->b_p_fo, x) != NULL; -} - /// @returns true if "x" is present in 'shortmess' option, or /// 'shortmess' contains 'a' and "x" is present in SHM_ALL_ABBREVIATIONS. bool shortmess(int x) { return (p_shm != NULL - && (vim_strchr((char *)p_shm, x) != NULL - || (vim_strchr((char *)p_shm, 'a') != NULL - && vim_strchr((char *)SHM_ALL_ABBREVIATIONS, x) != NULL))); + && (vim_strchr(p_shm, x) != NULL + || (vim_strchr(p_shm, 'a') != NULL + && vim_strchr(SHM_ALL_ABBREVIATIONS, x) != NULL))); } /// paste_option_changed() - Called after p_paste was set or reset. @@ -7178,7 +5076,7 @@ static void paste_option_changed(void) xfree(buf->b_p_vsts_nopaste); } buf->b_p_vsts_nopaste = buf->b_p_vsts && buf->b_p_vsts != empty_option - ? vim_strsave(buf->b_p_vsts) + ? xstrdup(buf->b_p_vsts) : NULL; } @@ -7197,9 +5095,7 @@ static void paste_option_changed(void) if (p_vsts_nopaste) { xfree(p_vsts_nopaste); } - p_vsts_nopaste = p_vsts && p_vsts != empty_option - ? vim_strsave(p_vsts) - : NULL; + p_vsts_nopaste = p_vsts && p_vsts != empty_option ? xstrdup(p_vsts) : NULL; } // Always set the option values, also when 'paste' is set when it is @@ -7249,9 +5145,7 @@ static void paste_option_changed(void) if (buf->b_p_vsts) { free_string_option(buf->b_p_vsts); } - buf->b_p_vsts = buf->b_p_vsts_nopaste - ? vim_strsave(buf->b_p_vsts_nopaste) - : empty_option; + buf->b_p_vsts = buf->b_p_vsts_nopaste ? xstrdup(buf->b_p_vsts_nopaste) : empty_option; xfree(buf->b_p_vsts_array); if (buf->b_p_vsts && buf->b_p_vsts != empty_option) { (void)tabstop_set(buf->b_p_vsts, &buf->b_p_vsts_array); @@ -7278,7 +5172,7 @@ static void paste_option_changed(void) if (p_vsts) { free_string_option(p_vsts); } - p_vsts = p_vsts_nopaste ? vim_strsave(p_vsts_nopaste) : empty_option; + p_vsts = p_vsts_nopaste ? xstrdup(p_vsts_nopaste) : empty_option; } old_p_paste = p_paste; @@ -7336,7 +5230,7 @@ void reset_option_was_set(const char *name) } /// fill_breakat_flags() -- called when 'breakat' changes value. -static void fill_breakat_flags(void) +void fill_breakat_flags(void) { char_u *p; int i; @@ -7346,16 +5240,16 @@ static void fill_breakat_flags(void) } if (p_breakat != NULL) { - for (p = p_breakat; *p; p++) { + for (p = (char_u *)p_breakat; *p; p++) { breakat_flags[*p] = true; } } } /// fill_culopt_flags() -- called when 'culopt' changes value -static int fill_culopt_flags(char_u *val, win_T *wp) +int fill_culopt_flags(char *val, win_T *wp) { - char_u *p; + char *p; char_u culopt_flags_new = 0; if (val == NULL) { @@ -7395,101 +5289,41 @@ static int fill_culopt_flags(char_u *val, win_T *wp) return OK; } -/// Check an option that can be a range of string values. -/// -/// @param list when true: accept a list of values -/// -/// @return OK for correct value, FAIL otherwise. Empty is always OK. -static int check_opt_strings(char_u *val, char **values, int list) -{ - return opt_strings_flags(val, values, NULL, list); -} - -/// Handle an option that can be a range of string values. -/// Set a flag in "*flagp" for each string present. -/// -/// @param val new value -/// @param values array of valid string values -/// @param list when true: accept a list of values -/// -/// @return OK for correct value, FAIL otherwise. Empty is always OK. -static int opt_strings_flags(char_u *val, char **values, unsigned *flagp, bool list) -{ - unsigned int new_flags = 0; - - while (*val) { - for (unsigned int i = 0;; i++) { - if (values[i] == NULL) { // val not found in values[] - return FAIL; - } - - size_t len = STRLEN(values[i]); - if (STRNCMP(values[i], val, len) == 0 - && ((list && val[len] == ',') || val[len] == NUL)) { - val += len + (val[len] == ','); - assert(i < sizeof(1U) * 8); - new_flags |= (1U << i); - break; // check next item in val list - } - } - } - if (flagp != NULL) { - *flagp = new_flags; - } - - return OK; -} - -/// Read the 'wildmode' option, fill wim_flags[]. -static int check_opt_wim(void) +/// 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 +int option_set_callback_func(char_u *optval, Callback *optcb) { - char_u new_wim_flags[4]; - char_u *p; - int i; - int idx = 0; - - for (i = 0; i < 4; i++) { - new_wim_flags[i] = 0; + if (optval == NULL || *optval == NUL) { + callback_free(optcb); + return OK; } - for (p = p_wim; *p; p++) { - for (i = 0; ASCII_ISALPHA(p[i]); i++) {} - if (p[i] != NUL && p[i] != ',' && p[i] != ':') { - return FAIL; - } - if (i == 7 && STRNCMP(p, "longest", 7) == 0) { - new_wim_flags[idx] |= WIM_LONGEST; - } else if (i == 4 && STRNCMP(p, "full", 4) == 0) { - new_wim_flags[idx] |= WIM_FULL; - } else if (i == 4 && STRNCMP(p, "list", 4) == 0) { - new_wim_flags[idx] |= WIM_LIST; - } else if (i == 8 && STRNCMP(p, "lastused", 8) == 0) { - new_wim_flags[idx] |= WIM_BUFLASTUSED; - } else { + typval_T *tv; + if (*optval == '{' + || (STRNCMP(optval, "function(", 9) == 0) + || (STRNCMP(optval, "funcref(", 8) == 0)) { + // Lambda expression or a funcref + tv = eval_expr((char *)optval); + if (tv == NULL) { return FAIL; } - p += i; - if (*p == NUL) { - break; - } - if (*p == ',') { - if (idx == 3) { - return FAIL; - } - idx++; - } + } else { + // treat everything else as a function name string + tv = xcalloc(1, sizeof(*tv)); + tv->v_type = VAR_STRING; + tv->vval.v_string = (char *)vim_strsave(optval); } - // fill remaining entries with last flag - while (idx < 3) { - new_wim_flags[idx + 1] = new_wim_flags[idx]; - idx++; + Callback cb; + if (!callback_from_typval(&cb, tv)) { + tv_free(tv); + return FAIL; } - // only when there are no errors, wim_flags[] is changed - for (i = 0; i < 4; i++) { - wim_flags[i] = new_wim_flags[i]; - } + callback_free(optcb); + *optcb = cb; + tv_free(tv); return OK; } @@ -7510,412 +5344,7 @@ bool can_bs(int what) case '0': return false; } - return vim_strchr((char *)p_bs, what) != NULL; -} - -/// Save the current values of 'fileformat' and 'fileencoding', so that we know -/// the file must be considered changed when the value is different. -void save_file_ff(buf_T *buf) -{ - buf->b_start_ffc = *buf->b_p_ff; - buf->b_start_eol = buf->b_p_eol; - buf->b_start_bomb = buf->b_p_bomb; - - // Only use free/alloc when necessary, they take time. - if (buf->b_start_fenc == NULL - || STRCMP(buf->b_start_fenc, buf->b_p_fenc) != 0) { - xfree(buf->b_start_fenc); - buf->b_start_fenc = (char *)vim_strsave(buf->b_p_fenc); - } -} - -/// Return true if 'fileformat' and/or 'fileencoding' has a different value -/// from when editing started (save_file_ff() called). -/// Also when 'endofline' was changed and 'binary' is set, or when 'bomb' was -/// changed and 'binary' is not set. -/// Also when 'endofline' was changed and 'fixeol' is not set. -/// When "ignore_empty" is true don't consider a new, empty buffer to be -/// changed. -bool file_ff_differs(buf_T *buf, bool ignore_empty) - FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT -{ - // In a buffer that was never loaded the options are not valid. - if (buf->b_flags & BF_NEVERLOADED) { - return false; - } - if (ignore_empty - && (buf->b_flags & BF_NEW) - && buf->b_ml.ml_line_count == 1 - && *ml_get_buf(buf, (linenr_T)1, false) == NUL) { - return false; - } - 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) { - return true; - } - if (!buf->b_p_bin && buf->b_start_bomb != buf->b_p_bomb) { - return true; - } - if (buf->b_start_fenc == NULL) { - return *buf->b_p_fenc != NUL; - } - return STRCMP(buf->b_start_fenc, buf->b_p_fenc) != 0; -} - -/// return OK if "p" is a valid fileformat name, FAIL otherwise. -int check_ff_value(char_u *p) -{ - return check_opt_strings(p, p_ff_values, false); -} - -// Set the integer values corresponding to the string setting of 'vartabstop'. -// "array" will be set, caller must free it if needed. -// Return false for an error. -bool tabstop_set(char_u *var, long **array) -{ - long valcount = 1; - int t; - char_u *cp; - - if (var[0] == NUL || (var[0] == '0' && var[1] == NUL)) { - *array = NULL; - return true; - } - - for (cp = var; *cp != NUL; cp++) { - if (cp == var || cp[-1] == ',') { - char_u *end; - - if (strtol((char *)cp, (char **)&end, 10) <= 0) { - if (cp != end) { - emsg(_(e_positive)); - } else { - semsg(_(e_invarg2), cp); - } - return false; - } - } - - if (ascii_isdigit(*cp)) { - continue; - } - if (cp[0] == ',' && cp > var && cp[-1] != ',' && cp[1] != NUL) { - valcount++; - continue; - } - semsg(_(e_invarg2), var); - return false; - } - - *array = (long *)xmalloc((unsigned)(valcount + 1) * sizeof(long)); - (*array)[0] = valcount; - - t = 1; - for (cp = var; *cp != NUL;) { - int n = atoi((char *)cp); - - // Catch negative values, overflow and ridiculous big values. - if (n <= 0 || n > TABSTOP_MAX) { - semsg(_(e_invarg2), cp); - XFREE_CLEAR(*array); - return false; - } - (*array)[t++] = n; - while (*cp != NUL && *cp != ',') { - cp++; - } - if (*cp != NUL) { - cp++; - } - } - - return true; -} - -// 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) -{ - long ts = ts_arg == 0 ? 8 : ts_arg; - colnr_T tabcol = 0; - int t; - long padding = 0; - - if (vts == NULL || vts[0] == 0) { - return (int)(ts - (col % ts)); - } - - const long tabcount = vts[0]; - - for (t = 1; t <= tabcount; t++) { - tabcol += (colnr_T)vts[t]; - if (tabcol > col) { - padding = tabcol - col; - break; - } - } - if (t > tabcount) { - padding = vts[tabcount] - ((col - tabcol) % vts[tabcount]); - } - - return (int)padding; -} - -// Find the size of the tab that covers a particular column. -int tabstop_at(colnr_T col, long ts, long *vts) -{ - colnr_T tabcol = 0; - int t; - long tab_size = 0; - - if (vts == NULL || vts[0] == 0) { - return (int)ts; - } - - const long tabcount = vts[0]; - for (t = 1; t <= tabcount; t++) { - tabcol += (colnr_T)vts[t]; - if (tabcol > col) { - tab_size = vts[t]; - break; - } - } - if (t > tabcount) { - tab_size = vts[tabcount]; - } - - return (int)tab_size; -} - -// Find the column on which a tab starts. -colnr_T tabstop_start(colnr_T col, long ts, long *vts) -{ - colnr_T tabcol = 0; - int t; - - if (vts == NULL || vts[0] == 0) { - return (int)((col / ts) * ts); - } - - const long tabcount = vts[0]; - for (t = 1; t <= tabcount; t++) { - tabcol += (colnr_T)vts[t]; - if (tabcol > col) { - return (int)(tabcol - vts[t]); - } - } - - const int excess = (int)(tabcol % vts[tabcount]); - return (int)(excess + ((col - excess) / vts[tabcount]) * vts[tabcount]); -} - -// 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, - int *nspcs) -{ - int spaces = end_col - start_col; - colnr_T tabcol = 0; - long padding = 0; - int t; - long ts = ts_arg == 0 ? curbuf->b_p_ts : ts_arg; - - if (vts == NULL || vts[0] == 0) { - int tabs = 0; - - const int initspc = (int)(ts - (start_col % ts)); - if (spaces >= initspc) { - spaces -= initspc; - tabs++; - } - tabs += (int)(spaces / ts); - spaces -= (int)((spaces / ts) * ts); - - *ntabs = tabs; - *nspcs = spaces; - return; - } - - // Find the padding needed to reach the next tabstop. - const long tabcount = vts[0]; - for (t = 1; t <= tabcount; t++) { - tabcol += (colnr_T)vts[t]; - if (tabcol > start_col) { - padding = tabcol - start_col; - break; - } - } - if (t > tabcount) { - padding = vts[tabcount] - ((start_col - tabcol) % vts[tabcount]); - } - - // If the space needed is less than the padding no tabs can be used. - if (spaces < padding) { - *ntabs = 0; - *nspcs = spaces; - return; - } - - *ntabs = 1; - spaces -= (int)padding; - - // At least one tab has been used. See if any more will fit. - while (spaces != 0 && ++t <= tabcount) { - padding = vts[t]; - if (spaces < padding) { - *nspcs = spaces; - return; - } - *ntabs += 1; - spaces -= (int)padding; - } - - *ntabs += spaces / (int)vts[tabcount]; - *nspcs = spaces % (int)vts[tabcount]; -} - -// See if two tabstop arrays contain the same values. -bool tabstop_eq(long *ts1, long *ts2) -{ - int t; - - if ((ts1 == 0 && ts2) || (ts1 && ts2 == 0)) { - return false; - } - if (ts1 == ts2) { - return true; - } - if (ts1[0] != ts2[0]) { - return false; - } - - for (t = 1; t <= ts1[0]; t++) { - if (ts1[t] != ts2[t]) { - return false; - } - } - - return true; -} - -// Copy a tabstop array, allocating space for the new array. -int *tabstop_copy(long *oldts) -{ - long *newts; - int t; - - if (oldts == 0) { - return 0; - } - - newts = xmalloc((unsigned)(oldts[0] + 1) * sizeof(long)); - for (t = 0; t <= oldts[0]; t++) { - newts[t] = oldts[t]; - } - - return (int *)newts; -} - -// Return a count of the number of tabstops. -int tabstop_count(long *ts) -{ - return ts != NULL ? (int)ts[0] : 0; -} - -// Return the first tabstop, or 8 if there are no tabstops defined. -int tabstop_first(long *ts) -{ - return ts != NULL ? (int)ts[1] : 8; -} - -/// Return the effective shiftwidth value for current buffer, using the -/// 'tabstop' value when 'shiftwidth' is zero. -int get_sw_value(buf_T *buf) -{ - long result = get_sw_value_col(buf, 0); - assert(result >= 0 && result <= INT_MAX); - return (int)result; -} - -// Idem, using the first non-black in the current line. -long get_sw_value_indent(buf_T *buf) -{ - pos_T pos = curwin->w_cursor; - - pos.col = (colnr_T)getwhitecols_curline(); - return get_sw_value_pos(buf, &pos); -} - -// Idem, using "pos". -long get_sw_value_pos(buf_T *buf, pos_T *pos) -{ - pos_T save_cursor = curwin->w_cursor; - long sw_value; - - curwin->w_cursor = *pos; - sw_value = get_sw_value_col(buf, get_nolist_virtcol()); - curwin->w_cursor = save_cursor; - return sw_value; -} - -// Idem, using virtual column "col". -long get_sw_value_col(buf_T *buf, colnr_T col) -{ - return buf->b_p_sw ? buf->b_p_sw - : tabstop_at(col, buf->b_p_ts, buf->b_p_vts_array); -} - -/// Return the effective softtabstop value for the current buffer, -/// using the shiftwidth value when 'softtabstop' is negative. -int get_sts_value(void) -{ - long result = curbuf->b_p_sts < 0 ? get_sw_value(curbuf) : curbuf->b_p_sts; - assert(result >= 0 && result <= INT_MAX); - return (int)result; -} - -/// This is called when 'breakindentopt' is changed and when a window is -/// initialized -static bool briopt_check(win_T *wp) -{ - int bri_shift = 0; - int bri_min = 20; - bool bri_sbr = false; - int bri_list = 0; - - char *p = (char *)wp->w_p_briopt; - while (*p != NUL) { - 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])) { - p += 4; - bri_min = getdigits_int(&p, true, 0); - } else if (STRNCMP(p, "sbr", 3) == 0) { - p += 3; - bri_sbr = true; - } else if (STRNCMP(p, "list:", 5) == 0) { - p += 5; - bri_list = (int)getdigits(&p, false, 0); - } - if (*p != ',' && *p != NUL) { - return false; - } - if (*p == ',') { - p++; - } - } - - wp->w_briopt_shift = bri_shift; - wp->w_briopt_min = bri_min; - wp->w_briopt_sbr = bri_sbr; - wp->w_briopt_list = bri_list; - - return true; + return vim_strchr(p_bs, what) != NULL; } /// Get the local or global value of 'backupcopy'. @@ -7940,19 +5369,19 @@ char_u *get_showbreak_value(win_T *const win) FUNC_ATTR_WARN_UNUSED_RESULT { if (win->w_p_sbr == NULL || *win->w_p_sbr == NUL) { - return p_sbr; + return (char_u *)p_sbr; } if (STRCMP(win->w_p_sbr, "NONE") == 0) { - return empty_option; + return (char_u *)empty_option; } - return win->w_p_sbr; + return (char_u *)win->w_p_sbr; } /// Return the current end-of-line type: EOL_DOS, EOL_UNIX or EOL_MAC. int get_fileformat(const buf_T *buf) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { - int c = *buf->b_p_ff; + int c = (unsigned char)(*buf->b_p_ff); if (buf->b_p_bin || c == 'u') { return EOL_UNIX; @@ -7979,7 +5408,7 @@ int get_fileformat_force(const buf_T *buf, const exarg_T *eap) ? (eap->force_bin == FORCE_BIN) : buf->b_p_bin) { return EOL_UNIX; } - c = *buf->b_p_ff; + c = (unsigned char)(*buf->b_p_ff); } if (c == 'u') { return EOL_UNIX; @@ -8036,7 +5465,7 @@ void set_fileformat(int eol_style, int opt_flags) } /// Skip to next part of an option argument: skip space and comma -char_u *skip_to_option_part(const char_u *p) +char *skip_to_option_part(const char *p) { if (*p == ',') { p++; @@ -8044,7 +5473,7 @@ char_u *skip_to_option_part(const char_u *p) while (*p == ' ') { p++; } - return (char_u *)p; + return (char *)p; } /// Isolate one part of a string option separated by `sep_chars`. @@ -8079,7 +5508,7 @@ size_t copy_option_part(char **option, char *buf, size_t maxlen, char *sep_chars if (*p != NUL && *p != ',') { // skip non-standard separator p++; } - p = (char *)skip_to_option_part((char_u *)p); // p points to next file name + p = skip_to_option_part(p); // p points to next file name *option = p; return len; diff --git a/src/nvim/option.h b/src/nvim/option.h index a5a57cc66d..c65d2ee182 100644 --- a/src/nvim/option.h +++ b/src/nvim/option.h @@ -19,23 +19,6 @@ typedef enum { #define BCO_ALWAYS 2 // always copy the options #define BCO_NOHELP 4 // don't touch the help related options -/// Flags for option-setting functions -/// -/// When OPT_GLOBAL and OPT_LOCAL are both missing, set both local and global -/// values, get local value. -typedef enum { - OPT_FREE = 0x01, ///< Free old value if it was allocated. - OPT_GLOBAL = 0x02, ///< Use global value. - OPT_LOCAL = 0x04, ///< Use local value. - OPT_MODELINE = 0x08, ///< Option in modeline. - OPT_WINONLY = 0x10, ///< Only set window-local options. - OPT_NOWIN = 0x20, ///< Don’t set window-local options. - OPT_ONECOLUMN = 0x40, ///< list options one per line - OPT_NO_REDRAW = 0x80, ///< ignore redraw flags on option - OPT_SKIPRTP = 0x100, ///< "skiprtp" in 'sessionoptions' - OPT_CLEAR = 0x200, ///< Clear local value of an option. -} OptionFlags; - #ifdef INCLUDE_GENERATED_DECLARATIONS # include "option.h.generated.h" #endif diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index 92ab7489bb..78cb324fe4 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -7,6 +7,69 @@ // option_defs.h: definition of global variables for settable options +// Flags +#define P_BOOL 0x01U ///< the option is boolean +#define P_NUM 0x02U ///< the option is numeric +#define P_STRING 0x04U ///< the option is a string +#define P_ALLOCED 0x08U ///< the string option is in allocated memory, + ///< must use free_string_option() when + ///< assigning new value. Not set if default is + ///< the same. +#define P_EXPAND 0x10U ///< environment expansion. NOTE: P_EXPAND can + ///< never be used for local or hidden options +#define P_NODEFAULT 0x40U ///< don't set to default value +#define P_DEF_ALLOCED 0x80U ///< default value is in allocated memory, must + ///< use free() when assigning new value +#define P_WAS_SET 0x100U ///< option has been set/reset +#define P_NO_MKRC 0x200U ///< don't include in :mkvimrc output + +// when option changed, what to display: +#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 +#define P_RALL 0x6000U ///< redraw all windows +#define P_RCLR 0x7000U ///< clear and redraw all + +#define P_COMMA 0x8000U ///< comma separated list +#define P_ONECOMMA 0x18000U ///< P_COMMA and cannot have two consecutive + ///< commas +#define P_NODUP 0x20000U ///< don't allow duplicate strings +#define P_FLAGLIST 0x40000U ///< list of single-char flags + +#define P_SECURE 0x80000U ///< cannot change in modeline or secure mode +#define P_GETTEXT 0x100000U ///< expand default value with _() +#define P_NOGLOB 0x200000U ///< do not use local value for global vimrc +#define P_NFNAME 0x400000U ///< only normal file name chars allowed +#define P_INSECURE 0x800000U ///< option was set from a modeline +#define P_PRI_MKRC 0x1000000U ///< priority for :mkvimrc (setting option + ///< has side effects) +#define P_NO_ML 0x2000000U ///< not allowed in modeline +#define P_CURSWANT 0x4000000U ///< update curswant required; not needed + ///< when there is a redraw flag +#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_NO_DEF_EXP 0x40000000U ///< Do not expand default value. +#define P_UI_OPTION 0x80000000U ///< send option to remote ui + +/// Flags for option-setting functions +/// +/// When OPT_GLOBAL and OPT_LOCAL are both missing, set both local and global +/// values, get local value. +typedef enum { + OPT_FREE = 0x01, ///< Free old value if it was allocated. + OPT_GLOBAL = 0x02, ///< Use global value. + OPT_LOCAL = 0x04, ///< Use local value. + OPT_MODELINE = 0x08, ///< Option in modeline. + OPT_WINONLY = 0x10, ///< Only set window-local options. + OPT_NOWIN = 0x20, ///< Don’t set window-local options. + OPT_ONECOLUMN = 0x40, ///< list options one per line + OPT_NO_REDRAW = 0x80, ///< ignore redraw flags on option + OPT_SKIPRTP = 0x100, ///< "skiprtp" in 'sessionoptions' + OPT_CLEAR = 0x200, ///< Clear local value of an option. +} OptionFlags; + // Return value from get_option_value_strict #define SOPT_BOOL 0x01 // Boolean option #define SOPT_NUM 0x02 // Number option @@ -21,6 +84,16 @@ #define SREQ_WIN 1 // Request window-local option value #define SREQ_BUF 2 // Request buffer-local option value +#define HIGHLIGHT_INIT \ + "8:SpecialKey,~:EndOfBuffer,z:TermCursor,Z:TermCursorNC,@:NonText,d:Directory,e:ErrorMsg," \ + "i:IncSearch,l:Search,y:CurSearch,m:MoreMsg,M:ModeMsg,n:LineNr,a:LineNrAbove,b:LineNrBelow," \ + "N:CursorLineNr,G:CursorLineSign,O:CursorLineFold" \ + "r:Question,s:StatusLine,S:StatusLineNC,c:VertSplit,t:Title,v:Visual,V:VisualNOS,w:WarningMsg," \ + "W:WildMenu,f:Folded,F:FoldColumn,A:DiffAdd,C:DiffChange,D:DiffDelete,T:DiffText,>:SignColumn," \ + "-:Conceal,B:SpellBad,P:SpellCap,R:SpellRare,L:SpellLocal,+:Pmenu,=:PmenuSel,x:PmenuSbar," \ + "X:PmenuThumb,*:TabLine,#:TabLineSel,_:TabLineFill,!:CursorColumn,.:CursorLine,o:ColorColumn," \ + "q:QuickFixLine,0:Whitespace,I:NormalNC" + // Default values for 'errorformat'. // The "%f|%l| %m" one is used for when the contents of the quickfix window is // written to a file. @@ -185,7 +258,7 @@ enum { SHM_SEARCHCOUNT = 'S', ///< Search sats: '[1/10]' }; /// Represented by 'a' flag. -#define SHM_ALL_ABBREVIATIONS ((char_u[]) { \ +#define SHM_ALL_ABBREVIATIONS ((char[]) { \ SHM_RO, SHM_MOD, SHM_FILE, SHM_LAST, SHM_TEXT, SHM_LINES, SHM_NEW, SHM_WRI, \ 0, \ }) @@ -311,37 +384,39 @@ enum { */ EXTERN long p_aleph; // 'aleph' -EXTERN int p_acd; // 'autochdir' -EXTERN char_u *p_ambw; // 'ambiwidth' +EXTERN char *p_ambw; ///< 'ambiwidth' +EXTERN int p_acd; ///< 'autochdir' +EXTERN int p_ai; ///< 'autoindent' +EXTERN int p_bin; ///< 'binary' +EXTERN int p_bomb; ///< 'bomb' +EXTERN int p_bl; ///< 'buflisted' +EXTERN int p_cin; ///< 'cindent' +EXTERN long p_channel; ///< 'channel' +EXTERN char *p_cink; ///< 'cinkeys' +EXTERN char *p_cinsd; ///< 'cinscopedecls' +EXTERN char *p_cinw; ///< 'cinwords' +EXTERN char *p_cfu; ///< 'completefunc' +EXTERN char *p_ofu; ///< 'omnifunc' +EXTERN char *p_tsrfu; ///< 'thesaurusfunc' +EXTERN int p_ci; ///< 'copyindent' EXTERN int p_ar; // 'autoread' EXTERN int p_aw; // 'autowrite' EXTERN int p_awa; // 'autowriteall' -EXTERN char_u *p_bs; // 'backspace' -EXTERN char_u *p_bg; // 'background' +EXTERN char *p_bs; // 'backspace' +EXTERN char *p_bg; // 'background' EXTERN int p_bk; // 'backup' -EXTERN char_u *p_bkc; // 'backupcopy' +EXTERN char *p_bkc; // 'backupcopy' EXTERN unsigned int bkc_flags; ///< flags from 'backupcopy' -#ifdef IN_OPTION_C -static char *(p_bkc_values[]) = -{ "yes", "auto", "no", "breaksymlink", "breakhardlink", NULL }; -#endif #define BKC_YES 0x001 #define BKC_AUTO 0x002 #define BKC_NO 0x004 #define BKC_BREAKSYMLINK 0x008 #define BKC_BREAKHARDLINK 0x010 -EXTERN char_u *p_bdir; // 'backupdir' -EXTERN char_u *p_bex; // 'backupext' -EXTERN char_u *p_bo; // 'belloff' +EXTERN char *p_bdir; // 'backupdir' +EXTERN char *p_bex; // 'backupext' +EXTERN char *p_bo; // 'belloff' EXTERN char breakat_flags[256]; // which characters are in 'breakat' EXTERN unsigned bo_flags; -#ifdef IN_OPTION_C -static char *(p_bo_values[]) = { "all", "backspace", "cursor", "complete", - "copy", "ctrlg", "error", "esc", "ex", - "hangul", "lang", "mess", "showmatch", - "operator", "register", "shell", "spell", - "wildmode", NULL }; -#endif // values for the 'belloff' option #define BO_ALL 0x0001 @@ -365,90 +440,88 @@ static char *(p_bo_values[]) = { "all", "backspace", "cursor", "complete", #define BO_WILD 0x40000 EXTERN char_u *p_bsk; // 'backupskip' -EXTERN char_u *p_breakat; // 'breakat' -EXTERN char_u *p_cmp; // 'casemap' +EXTERN char *p_breakat; // 'breakat' +EXTERN char *p_bh; ///< 'bufhidden' +EXTERN char *p_bt; ///< 'buftype' +EXTERN char *p_cmp; // 'casemap' EXTERN unsigned cmp_flags; -#ifdef IN_OPTION_C -static char *(p_cmp_values[]) = { "internal", "keepascii", NULL }; -#endif #define CMP_INTERNAL 0x001 #define CMP_KEEPASCII 0x002 -EXTERN char_u *p_enc; // 'encoding' -EXTERN int p_deco; // 'delcombine' -EXTERN char_u *p_ccv; // 'charconvert' -EXTERN char_u *p_cedit; // 'cedit' -EXTERN char_u *p_cb; // 'clipboard' +EXTERN char *p_enc; // 'encoding' +EXTERN int p_deco; // 'delcombine' +EXTERN char *p_ccv; // 'charconvert' +EXTERN char *p_cino; ///< 'cinoptions' +EXTERN char *p_cedit; // 'cedit' +EXTERN char *p_cb; // 'clipboard' EXTERN unsigned cb_flags; -#ifdef IN_OPTION_C -static char *(p_cb_values[]) = { "unnamed", "unnamedplus", NULL }; -#endif #define CB_UNNAMED 0x001 #define CB_UNNAMEDPLUS 0x002 #define CB_UNNAMEDMASK (CB_UNNAMED | CB_UNNAMEDPLUS) EXTERN long p_cwh; // 'cmdwinheight' EXTERN long p_ch; // 'cmdheight' +EXTERN char *p_cms; ///< 'commentstring' +EXTERN char *p_cpt; ///< 'complete' EXTERN long p_columns; // 'columns' EXTERN int p_confirm; // 'confirm' -EXTERN char_u *p_cot; // 'completeopt' +EXTERN char *p_cot; // 'completeopt' #ifdef BACKSLASH_IN_FILENAME EXTERN char_u *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_u *p_csprg; // 'cscopeprg' +EXTERN char *p_csprg; // 'cscopeprg' EXTERN int p_csre; // 'cscoperelative' -EXTERN char_u *p_csqf; // 'cscopequickfix' +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_u *p_debug; // 'debug' -EXTERN char_u *p_def; // 'define' -EXTERN char_u *p_inc; -EXTERN char_u *p_dip; // 'diffopt' -EXTERN char_u *p_dex; // 'diffexpr' -EXTERN char_u *p_dict; // 'dictionary' +EXTERN char *p_debug; // 'debug' +EXTERN char *p_def; // 'define' +EXTERN char *p_inc; +EXTERN char *p_dip; // 'diffopt' +EXTERN char *p_dex; // 'diffexpr' +EXTERN char *p_dict; // 'dictionary' EXTERN int p_dg; // 'digraph' -EXTERN char_u *p_dir; // 'directory' -EXTERN char_u *p_dy; // 'display' +EXTERN char *p_dir; // 'directory' +EXTERN char *p_dy; // 'display' EXTERN unsigned dy_flags; -#ifdef IN_OPTION_C -static char *(p_dy_values[]) = { "lastline", "truncate", "uhex", "msgsep", - NULL }; -#endif #define DY_LASTLINE 0x001 #define DY_TRUNCATE 0x002 #define DY_UHEX 0x004 // code should use msg_use_msgsep() to check if msgsep is active #define DY_MSGSEP 0x008 EXTERN int p_ed; // 'edcompatible' +EXTERN char *p_ead; // 'eadirection' EXTERN int p_emoji; // 'emoji' -EXTERN char_u *p_ead; // 'eadirection' EXTERN int p_ea; // 'equalalways' -EXTERN char_u *p_ep; // 'equalprg' +EXTERN char_u *p_ep; // 'equalprg' EXTERN int p_eb; // 'errorbells' -EXTERN char_u *p_ef; // 'errorfile' -EXTERN char *p_efm; // 'errorformat' -EXTERN char *p_gefm; // 'grepformat' -EXTERN char_u *p_gp; // 'grepprg' -EXTERN char_u *p_ei; // 'eventignore' +EXTERN char_u *p_ef; // 'errorfile' +EXTERN char *p_efm; // 'errorformat' +EXTERN char *p_gefm; // 'grepformat' +EXTERN char_u *p_gp; // 'grepprg' +EXTERN int p_eol; ///< 'endofline' +EXTERN char *p_ei; // 'eventignore' +EXTERN int p_et; ///< 'expandtab' EXTERN int p_exrc; // 'exrc' -EXTERN char_u *p_fencs; // 'fileencodings' -EXTERN char *p_ffs; // 'fileformats' +EXTERN char *p_fenc; ///< 'fileencoding' +EXTERN char *p_fencs; // 'fileencodings' +EXTERN char *p_ff; ///< 'fileformat' +EXTERN char *p_ffs; // 'fileformats' EXTERN int p_fic; // 'fileignorecase' -EXTERN char_u *p_fcl; // 'foldclose' +EXTERN char *p_ft; ///< 'filetype' +EXTERN char *p_fcs; ///< 'fillchar' +EXTERN int p_fixeol; ///< 'fixendofline' +EXTERN char *p_fcl; // 'foldclose' EXTERN long p_fdls; // 'foldlevelstart' -EXTERN char_u *p_fdo; // 'foldopen' +EXTERN char *p_fdo; // 'foldopen' EXTERN unsigned fdo_flags; -#ifdef IN_OPTION_C -static char *(p_fdo_values[]) = { "all", "block", "hor", "mark", "percent", - "quickfix", "search", "tag", "insert", - "undo", "jump", NULL }; -#endif #define FDO_ALL 0x001 #define FDO_BLOCK 0x002 #define FDO_HOR 0x004 @@ -460,66 +533,76 @@ static char *(p_fdo_values[]) = { "all", "block", "hor", "mark", "percent", #define FDO_INSERT 0x100 #define FDO_UNDO 0x200 #define FDO_JUMP 0x400 -EXTERN char_u *p_fp; // 'formatprg' +EXTERN char *p_fex; ///< 'formatexpr' +EXTERN char *p_flp; ///< 'formatlistpat' +EXTERN char *p_fo; ///< 'formatoptions' +EXTERN char_u *p_fp; // 'formatprg' EXTERN int p_fs; // 'fsync' EXTERN int p_gd; // 'gdefault' -EXTERN char_u *p_pdev; // 'printdevice' -EXTERN char_u *p_penc; // 'printencoding' -EXTERN char_u *p_pexpr; // 'printexpr' -EXTERN char_u *p_pmfn; // 'printmbfont' -EXTERN char_u *p_pmcs; // 'printmbcharset' -EXTERN char_u *p_pfn; // 'printfont' -EXTERN char_u *p_popt; // 'printoptions' -EXTERN char_u *p_header; // 'printheader' -EXTERN char_u *p_guicursor; // 'guicursor' -EXTERN char_u *p_guifont; // 'guifont' -EXTERN char_u *p_guifontwide; // 'guifontwide' -EXTERN char_u *p_hf; // 'helpfile' +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_hf; // 'helpfile' EXTERN long p_hh; // 'helpheight' -EXTERN char_u *p_hlg; // 'helplang' +EXTERN char_u *p_hlg; // 'helplang' EXTERN int p_hid; // 'hidden' -EXTERN char_u *p_hl; // 'highlight' +EXTERN char *p_hl; // 'highlight' EXTERN int p_hls; // 'hlsearch' EXTERN long p_hi; // 'history' EXTERN int p_hkmap; // 'hkmap' EXTERN int p_hkmapp; // 'hkmapp' EXTERN int p_arshape; // 'arabicshape' EXTERN int p_icon; // 'icon' -EXTERN char_u *p_iconstring; // 'iconstring' +EXTERN char *p_iconstring; // 'iconstring' EXTERN int p_ic; // 'ignorecase' +EXTERN long p_iminsert; ///< 'iminsert' +EXTERN long p_imsearch; ///< 'imsearch' +EXTERN int p_inf; ///< 'infercase' +EXTERN char *p_inex; ///< 'includeexpr' EXTERN int p_is; // 'incsearch' -EXTERN char_u *p_icm; // 'inccommand' -EXTERN char_u *p_isf; // 'isfname' -EXTERN char_u *p_isi; // 'isident' -EXTERN char_u *p_isp; // 'isprint' +EXTERN char *p_inde; ///< 'indentexpr' +EXTERN char *p_indk; ///< 'indentkeys' +EXTERN char *p_icm; // 'inccommand' +EXTERN char *p_isf; // 'isfname' +EXTERN char *p_isi; // 'isident' +EXTERN char *p_isk; ///< 'iskeyword' +EXTERN char *p_isp; // 'isprint' EXTERN int p_js; // 'joinspaces' -EXTERN char_u *p_jop; // 'jumpooptions' +EXTERN char *p_jop; // 'jumpooptions' EXTERN unsigned jop_flags; -#ifdef IN_OPTION_C -static char *(p_jop_values[]) = { "stack", "view", NULL }; -#endif #define JOP_STACK 0x01 #define JOP_VIEW 0x02 -EXTERN char_u *p_kp; // 'keywordprg' -EXTERN char_u *p_km; // 'keymodel' -EXTERN char_u *p_langmap; // 'langmap' +EXTERN char *p_keymap; ///< 'keymap' +EXTERN char_u *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 long p_lines; // 'lines' -EXTERN long p_linespace; // 'linespace' -EXTERN char_u *p_lispwords; // 'lispwords' +EXTERN char_u *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 long p_ls; // 'laststatus' EXTERN long p_stal; // 'showtabline' -EXTERN char_u *p_lcs; // 'listchars' +EXTERN char *p_lcs; // 'listchars' EXTERN int p_lz; // 'lazyredraw' EXTERN int p_lpl; // 'loadplugins' EXTERN int p_magic; // 'magic' -EXTERN char_u *p_menc; // 'makeencoding' -EXTERN char *p_mef; // 'makeef' -EXTERN char_u *p_mp; // 'makeprg' -EXTERN char_u *p_cc; // 'colorcolumn' +EXTERN char *p_menc; // 'makeencoding' +EXTERN char *p_mef; // 'makeef' +EXTERN char_u *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' @@ -527,72 +610,62 @@ EXTERN long p_mfd; // 'maxfuncdepth' EXTERN long p_mmd; // 'maxmapdepth' EXTERN long p_mmp; // 'maxmempattern' EXTERN long p_mis; // 'menuitems' -EXTERN char_u *p_msm; // 'mkspellmem' -EXTERN int p_mle; // 'modelineexpr' +EXTERN char *p_msm; // 'mkspellmem' +EXTERN int p_ml; ///< 'modeline' +EXTERN int p_mle; // 'modelineexpr' EXTERN long p_mls; // 'modelines' -EXTERN char_u *p_mouse; // 'mouse' -EXTERN char_u *p_mousem; // 'mousemodel' -EXTERN int p_mousef; // 'mousefocus' -EXTERN char_u *p_mousescroll; // 'mousescroll' +EXTERN int p_ma; ///< 'modifiable' +EXTERN int p_mod; ///< 'modified' +EXTERN char *p_mouse; // 'mouse' +EXTERN char *p_mousem; // 'mousemodel' +EXTERN int p_mousef; // 'mousefocus' +EXTERN char *p_mousescroll; // 'mousescroll' EXTERN long p_mousescroll_vert INIT(= MOUSESCROLL_VERT_DFLT); EXTERN long p_mousescroll_hor INIT(= MOUSESCROLL_HOR_DFLT); EXTERN long p_mouset; // 'mousetime' EXTERN int p_more; // 'more' -EXTERN char_u *p_opfunc; // 'operatorfunc' -EXTERN char_u *p_para; // 'paragraphs' +EXTERN char *p_nf; ///< 'nrformats' +EXTERN char *p_opfunc; // 'operatorfunc' +EXTERN char_u *p_para; // 'paragraphs' EXTERN int p_paste; // 'paste' -EXTERN char_u *p_pt; // 'pastetoggle' -EXTERN char_u *p_pex; // 'patchexpr' -EXTERN char_u *p_pm; // 'patchmode' -EXTERN char_u *p_path; // 'path' -EXTERN char_u *p_cdpath; // 'cdpath' +EXTERN char *p_pt; // 'pastetoggle' +EXTERN char_u *p_pex; // 'patchexpr' +EXTERN char *p_pm; // 'patchmode' +EXTERN char_u *p_path; // 'path' +EXTERN char_u *p_cdpath; // 'cdpath' +EXTERN int p_pi; ///< 'preserveindent' EXTERN long p_pyx; // 'pyxversion' -EXTERN char_u *p_rdb; // 'redrawdebug' +EXTERN char *p_qe; ///< 'quoteescape' +EXTERN int p_ro; ///< 'readonly' +EXTERN char *p_rdb; // 'redrawdebug' EXTERN unsigned rdb_flags; -#ifdef IN_OPTION_C -static char *(p_rdb_values[]) = { - "compositor", - "nothrottle", - "invalid", - "nodelta", - NULL -}; -#endif #define RDB_COMPOSITOR 0x001 #define RDB_NOTHROTTLE 0x002 #define RDB_INVALID 0x004 #define RDB_NODELTA 0x008 -EXTERN long p_rdt; // 'redrawtime' -EXTERN long p_re; // 'regexpengine' -EXTERN long p_report; // 'report' -EXTERN long p_pvh; // 'previewheight' -EXTERN int p_ari; // 'allowrevins' -EXTERN int p_ri; // 'revins' -EXTERN int p_ru; // 'ruler' -EXTERN char_u *p_ruf; // 'rulerformat' -EXTERN char_u *p_pp; // 'packpath' -EXTERN char_u *p_qftf; // 'quickfixtextfunc' -EXTERN char_u *p_rtp; // 'runtimepath' -EXTERN long p_scbk; // 'scrollback' -EXTERN long p_sj; // 'scrolljump' -EXTERN long p_so; // 'scrolloff' -EXTERN char *p_sbo; // 'scrollopt' -EXTERN char_u *p_sections; // 'sections' -EXTERN int p_secure; // 'secure' -EXTERN char_u *p_sel; // 'selection' -EXTERN char_u *p_slm; // 'selectmode' -EXTERN char_u *p_ssop; // 'sessionoptions' +EXTERN long p_rdt; // 'redrawtime' +EXTERN long p_re; // 'regexpengine' +EXTERN long p_report; // 'report' +EXTERN long p_pvh; // 'previewheight' +EXTERN int p_ari; // 'allowrevins' +EXTERN int p_ri; // 'revins' +EXTERN int p_ru; // 'ruler' +EXTERN char *p_ruf; // 'rulerformat' +EXTERN char *p_pp; // 'packpath' +EXTERN char *p_qftf; // 'quickfixtextfunc' +EXTERN char *p_rtp; // 'runtimepath' +EXTERN long p_scbk; // 'scrollback' +EXTERN long p_sj; // 'scrolljump' +EXTERN long p_so; // 'scrolloff' +EXTERN char *p_sbo; // 'scrollopt' +EXTERN char *p_sections; // 'sections' +EXTERN int p_secure; // 'secure' +EXTERN char *p_sel; // 'selection' +EXTERN char *p_slm; // 'selectmode' +EXTERN char *p_ssop; // 'sessionoptions' EXTERN unsigned ssop_flags; -#ifdef IN_OPTION_C -// Also used for 'viewoptions'! Keep in sync with SSOP_ flags. -static char *(p_ssop_values[]) = { - "buffers", "winpos", "resize", "winsize", - "localoptions", "options", "help", "blank", "globals", "slash", "unix", - "sesdir", "curdir", "folds", "cursor", "tabpages", "terminal", "skiprtp", - NULL -}; -#endif + #define SSOP_BUFFERS 0x001 #define SSOP_WINPOS 0x002 #define SSOP_RESIZE 0x004 @@ -612,22 +685,23 @@ static char *(p_ssop_values[]) = { #define SSOP_TERMINAL 0x10000 #define SSOP_SKIP_RTP 0x20000 -EXTERN char_u *p_sh; // 'shell' -EXTERN char_u *p_shcf; // 'shellcmdflag' -EXTERN char_u *p_sp; // 'shellpipe' -EXTERN char_u *p_shq; // 'shellquote' -EXTERN char_u *p_sxq; // 'shellxquote' -EXTERN char_u *p_sxe; // 'shellxescape' -EXTERN char_u *p_srr; // 'shellredir' +EXTERN char_u *p_sh; // 'shell' +EXTERN char_u *p_shcf; // 'shellcmdflag' +EXTERN char *p_sp; // 'shellpipe' +EXTERN char_u *p_shq; // 'shellquote' +EXTERN char_u *p_sxq; // 'shellxquote' +EXTERN char_u *p_sxe; // 'shellxescape' +EXTERN char *p_srr; // 'shellredir' EXTERN int p_stmp; // 'shelltemp' #ifdef BACKSLASH_IN_FILENAME EXTERN int p_ssl; // 'shellslash' #endif -EXTERN char_u *p_stl; // 'statusline' -EXTERN char *p_wbr; // 'winbar' +EXTERN char *p_stl; // 'statusline' +EXTERN char *p_wbr; // 'winbar' EXTERN int p_sr; // 'shiftround' -EXTERN char_u *p_shm; // 'shortmess' -EXTERN char_u *p_sbr; // 'showbreak' +EXTERN long p_sw; ///< 'shiftwidth' +EXTERN char *p_shm; // 'shortmess' +EXTERN char *p_sbr; // 'showbreak' EXTERN int p_sc; // 'showcmd' EXTERN int p_sft; // 'showfulltag' EXTERN int p_sm; // 'showmatch' @@ -635,16 +709,17 @@ EXTERN int p_smd; // 'showmode' EXTERN long p_ss; // 'sidescroll' EXTERN long p_siso; // 'sidescrolloff' EXTERN int p_scs; // 'smartcase' +EXTERN int p_si; ///< 'smartindent' EXTERN int p_sta; // 'smarttab' +EXTERN long p_sts; ///< 'softtabstop' EXTERN int p_sb; // 'splitbelow' +EXTERN char *p_sua; ///< 'suffixesadd' +EXTERN int p_swf; ///< 'swapfile' +EXTERN long p_smc; ///< 'synmaxcol' EXTERN long p_tpm; // 'tabpagemax' -EXTERN char_u *p_tal; // 'tabline' -EXTERN char_u *p_tpf; // 'termpastefilter' +EXTERN char *p_tal; // 'tabline' +EXTERN char *p_tpf; // 'termpastefilter' EXTERN unsigned int tpf_flags; ///< flags from 'termpastefilter' -#ifdef IN_OPTION_C -static char *(p_tpf_values[]) = -{ "BS", "HT", "FF", "ESC", "DEL", "C0", "C1", NULL }; -#endif #define TPF_BS 0x001 #define TPF_HT 0x002 #define TPF_FF 0x004 @@ -652,29 +727,30 @@ static char *(p_tpf_values[]) = #define TPF_DEL 0x010 #define TPF_C0 0x020 #define TPF_C1 0x040 -EXTERN char_u *p_sps; // 'spellsuggest' +EXTERN char *p_tfu; ///< 'tagfunc' +EXTERN char *p_umf; ///< 'usermarkfunc' +EXTERN char *p_spc; ///< 'spellcapcheck' +EXTERN char *p_spf; ///< 'spellfile' +EXTERN char *p_spl; ///< 'spelllang' +EXTERN char *p_spo; // 'spelloptions' +EXTERN char *p_sps; // 'spellsuggest' EXTERN int p_spr; // 'splitright' EXTERN int p_sol; // 'startofline' -EXTERN char_u *p_su; // 'suffixes' -EXTERN char_u *p_swb; // 'switchbuf' +EXTERN char_u *p_su; // 'suffixes' +EXTERN char *p_swb; // 'switchbuf' EXTERN unsigned swb_flags; -#ifdef IN_OPTION_C -static char *(p_swb_values[]) = -{ "useopen", "usetab", "split", "newtab", "vsplit", "uselast", NULL }; -#endif +// Keep in sync with p_swb_values in optionstr.c #define SWB_USEOPEN 0x001 #define SWB_USETAB 0x002 #define SWB_SPLIT 0x004 #define SWB_NEWTAB 0x008 #define SWB_VSPLIT 0x010 #define SWB_USELAST 0x020 +EXTERN char *p_syn; ///< 'syntax' +EXTERN long p_ts; ///< 'tabstop' EXTERN int p_tbs; ///< 'tagbsearch' -EXTERN char_u *p_tc; ///< 'tagcase' +EXTERN char *p_tc; ///< 'tagcase' EXTERN unsigned tc_flags; ///< flags from 'tagcase' -#ifdef IN_OPTION_C -static char *(p_tc_values[]) = -{ "followic", "ignore", "match", "followscs", "smart", NULL }; -#endif #define TC_FOLLOWIC 0x01 #define TC_IGNORE 0x02 #define TC_MATCH 0x04 @@ -685,35 +761,34 @@ EXTERN int p_tr; ///< 'tagrelative' EXTERN char_u *p_tags; ///< 'tags' EXTERN int p_tgst; ///< 'tagstack' EXTERN int p_tbidi; ///< 'termbidi' +EXTERN long p_tw; ///< 'textwidth' EXTERN int p_to; ///< 'tildeop' 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_u *p_titlestring; ///< 'titlestring' +EXTERN char *p_titlestring; ///< 'titlestring' EXTERN char_u *p_tsr; ///< 'thesaurus' -EXTERN char_u *p_tsrfu; ///< 'thesaurusfunc' EXTERN int p_tgc; ///< 'termguicolors' EXTERN int p_ttimeout; ///< 'ttimeout' EXTERN long p_ttm; ///< 'ttimeoutlen' EXTERN char_u *p_udir; ///< 'undodir' +EXTERN int p_udf; ///< 'undofile' EXTERN long p_ul; ///< 'undolevels' EXTERN long p_ur; ///< 'undoreload' EXTERN long p_uc; ///< 'updatecount' EXTERN long p_ut; ///< 'updatetime' -EXTERN char_u *p_fcs; ///< 'fillchar' -EXTERN char_u *p_shada; ///< 'shada' +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_u *p_vop; ///< 'viewoptions' +EXTERN char *p_vop; ///< 'viewoptions' EXTERN unsigned vop_flags; ///< uses SSOP_ flags EXTERN int p_vb; ///< 'visualbell' -EXTERN char_u *p_ve; ///< 'virtualedit' +EXTERN char *p_ve; ///< 'virtualedit' EXTERN unsigned ve_flags; -#ifdef IN_OPTION_C -static char *(p_ve_values[]) = { "block", "insert", "all", "onemore", "none", "NONE", NULL }; -#endif #define VE_BLOCK 5U // includes "all" #define VE_INSERT 6U // includes "all" #define VE_ALL 4U @@ -724,29 +799,27 @@ EXTERN long p_verbose; // 'verbose' #ifdef IN_OPTION_C char_u *p_vfile = (char_u *)""; // used before options are initialized #else -extern char_u *p_vfile; // 'verbosefile' +extern char *p_vfile; // 'verbosefile' #endif EXTERN int p_warn; // 'warn' -EXTERN char_u *p_wop; // 'wildoptions' +EXTERN char *p_wop; // 'wildoptions' EXTERN unsigned wop_flags; -#ifdef IN_OPTION_C -static char *(p_wop_values[]) = { "tagfile", "pum", NULL }; -#endif #define WOP_TAGFILE 0x01 #define WOP_PUM 0x02 EXTERN long p_window; // 'window' -EXTERN char_u *p_wak; // 'winaltkeys' -EXTERN char_u *p_wig; // 'wildignore' -EXTERN char_u *p_ww; // 'whichwrap' +EXTERN char *p_wak; // 'winaltkeys' +EXTERN char *p_wig; // 'wildignore' +EXTERN char *p_ww; // 'whichwrap' EXTERN long p_wc; // 'wildchar' EXTERN long p_wcm; // 'wildcharm' EXTERN int p_wic; // 'wildignorecase' -EXTERN char_u *p_wim; // 'wildmode' +EXTERN char *p_wim; // 'wildmode' EXTERN int p_wmnu; // 'wildmenu' EXTERN long p_wh; // 'winheight' EXTERN long p_wmh; // 'winminheight' EXTERN long p_wmw; // 'winminwidth' EXTERN long p_wiw; // 'winwidth' +EXTERN long p_wm; ///< 'wrapmargin' EXTERN int p_ws; // 'wrapscan' EXTERN int p_write; // 'write' EXTERN int p_wa; // 'writeany' @@ -901,8 +974,8 @@ enum { WV_WRAP, WV_SCL, WV_WINHL, - WV_FCS, WV_LCS, + WV_FCS, WV_WINBL, WV_WBR, WV_COUNT, // must be the last one diff --git a/src/nvim/optionstr.c b/src/nvim/optionstr.c new file mode 100644 index 0000000000..1bdfcbecb9 --- /dev/null +++ b/src/nvim/optionstr.c @@ -0,0 +1,1663 @@ +// 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 <stdlib.h> +#include <string.h> + +#include "nvim/api/private/helpers.h" +#include "nvim/ascii.h" +#include "nvim/autocmd.h" +#include "nvim/charset.h" +#include "nvim/cursor.h" +#include "nvim/cursor_shape.h" +#include "nvim/diff.h" +#include "nvim/digraph.h" +#include "nvim/drawscreen.h" +#include "nvim/eval.h" +#include "nvim/eval/vars.h" +#include "nvim/ex_getln.h" +#include "nvim/hardcopy.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/mapping.h" +#include "nvim/memline.h" +#include "nvim/mouse.h" +#include "nvim/move.h" +#include "nvim/ops.h" +#include "nvim/option.h" +#include "nvim/optionstr.h" +#include "nvim/quickfix.h" +#include "nvim/runtime.h" +#include "nvim/spell.h" +#include "nvim/spellfile.h" +#include "nvim/spellsuggest.h" +#include "nvim/strings.h" +#include "nvim/ui.h" +#include "nvim/vim.h" +#include "nvim/window.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "optionstr.c.generated.h" +#endif + +static char e_unclosed_expression_sequence[] + = N_("E540: Unclosed expression sequence"); +static char e_unbalanced_groups[] + = N_("E542: unbalanced groups"); +static char e_backupext_and_patchmode_are_equal[] + = N_("E589: 'backupext' and 'patchmode' are equal"); +static char e_showbreak_contains_unprintable_or_wide_character[] + = N_("E595: 'showbreak' contains unprintable or wide character"); + +static char *(p_ambw_values[]) = { "single", "double", NULL }; +static char *(p_bg_values[]) = { "light", "dark", NULL }; +static char *(p_bkc_values[]) = { "yes", "auto", "no", "breaksymlink", "breakhardlink", NULL }; +static char *(p_bo_values[]) = { "all", "backspace", "cursor", "complete", "copy", "ctrlg", "error", + "esc", "ex", "hangul", "lang", "mess", "showmatch", "operator", + "register", "shell", "spell", "wildmode", NULL }; +static char *(p_nf_values[]) = { "bin", "octal", "hex", "alpha", "unsigned", NULL }; +static char *(p_ff_values[]) = { FF_UNIX, FF_DOS, FF_MAC, NULL }; +static char *(p_cmp_values[]) = { "internal", "keepascii", NULL }; +static char *(p_dy_values[]) = { "lastline", "truncate", "uhex", "msgsep", NULL }; +static char *(p_fdo_values[]) = { "all", "block", "hor", "mark", "percent", "quickfix", "search", + "tag", "insert", "undo", "jump", NULL }; +/// Also used for 'viewoptions'! Keep in sync with SSOP_ flags. +static char *(p_ssop_values[]) = { "buffers", "winpos", "resize", "winsize", "localoptions", + "options", "help", "blank", "globals", "slash", "unix", "sesdir", + "curdir", "folds", "cursor", "tabpages", "terminal", "skiprtp", + NULL }; +// Keep in sync with SWB_ flags in option_defs.h +static char *(p_swb_values[]) = { "useopen", "usetab", "split", "newtab", "vsplit", "uselast", + 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_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 }; +static char *(p_slm_values[]) = { "mouse", "key", "cmd", NULL }; +static char *(p_km_values[]) = { "startsel", "stopsel", NULL }; +static char *(p_scbopt_values[]) = { "ver", "hor", "jump", NULL }; +static char *(p_debug_values[]) = { "msg", "throw", "beep", NULL }; +static char *(p_ead_values[]) = { "both", "ver", "hor", NULL }; +static char *(p_buftype_values[]) = { "nofile", "nowrite", "quickfix", "help", "acwrite", + "terminal", "prompt", NULL }; +static char *(p_bufhidden_values[]) = { "hide", "unload", "delete", "wipe", NULL }; +static char *(p_bs_values[]) = { "indent", "eol", "start", "nostop", NULL }; +static char *(p_fdm_values[]) = { "manual", "expr", "marker", "indent", + "syntax", "diff", NULL }; +static char *(p_fcl_values[]) = { "all", NULL }; +static char *(p_cot_values[]) = { "menu", "menuone", "longest", "preview", "noinsert", "noselect", + NULL }; +#ifdef BACKSLASH_IN_FILENAME +static char *(p_csl_values[]) = { "slash", "backslash", NULL }; +#endif + +static char *(p_scl_values[]) = { "yes", "no", "auto", "auto:1", "auto:2", "auto:3", "auto:4", + "auto:5", "auto:6", "auto:7", "auto:8", "auto:9", "yes:1", + "yes:2", "yes:3", "yes:4", "yes:5", "yes:6", "yes:7", "yes:8", + "yes:9", "number", NULL }; +static char *(p_fdc_values[]) = { "auto", "auto:1", "auto:2", "auto:3", "auto:4", "auto:5", + "auto:6", "auto:7", "auto:8", "auto:9", "0", "1", "2", "3", "4", + "5", "6", "7", "8", "9", NULL }; +static char *(p_cb_values[]) = { "unnamed", "unnamedplus", NULL }; +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 }; + +/// 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, }; + +/// After setting various option values: recompute variables that depend on +/// option values. +void didset_string_options(void) +{ + (void)opt_strings_flags(p_cmp, p_cmp_values, &cmp_flags, true); + (void)opt_strings_flags(p_bkc, p_bkc_values, &bkc_flags, true); + (void)opt_strings_flags(p_bo, p_bo_values, &bo_flags, true); + (void)opt_strings_flags(p_ssop, p_ssop_values, &ssop_flags, true); + (void)opt_strings_flags(p_vop, p_ssop_values, &vop_flags, true); + (void)opt_strings_flags(p_fdo, p_fdo_values, &fdo_flags, true); + (void)opt_strings_flags(p_dy, p_dy_values, &dy_flags, true); + (void)opt_strings_flags(p_rdb, p_rdb_values, &rdb_flags, true); + (void)opt_strings_flags(p_tc, p_tc_values, &tc_flags, false); + (void)opt_strings_flags(p_tpf, p_tpf_values, &tpf_flags, true); + (void)opt_strings_flags(p_ve, p_ve_values, &ve_flags, true); + (void)opt_strings_flags(p_swb, p_swb_values, &swb_flags, true); + (void)opt_strings_flags(p_wop, p_wop_values, &wop_flags, true); + (void)opt_strings_flags(p_jop, p_jop_values, &jop_flags, true); + (void)opt_strings_flags(p_cb, p_cb_values, &cb_flags, true); +} + +/// Trigger the OptionSet autocommand. +/// "opt_idx" is the index of the option being set. +/// "opt_flags" can be OPT_LOCAL etc. +/// "oldval" the old value +/// "oldval_l" the old local value (only non-NULL if global and local value are set) +/// "oldval_g" the old global value (only non-NULL if global and local value are set) +/// "newval" the new value +void trigger_optionsset_string(int opt_idx, int opt_flags, char *oldval, char *oldval_l, + char *oldval_g, char *newval) +{ + // Don't do this recursively. + if (oldval != NULL + && newval != NULL + && *get_vim_var_str(VV_OPTION_TYPE) == NUL) { + 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_fullname(opt_idx), NULL, false, NULL); + reset_v_option_vars(); + } +} + +static char *illegal_char(char *errbuf, size_t errbuflen, int c) +{ + if (errbuf == NULL) { + return ""; + } + vim_snprintf(errbuf, errbuflen, _("E539: Illegal character <%s>"), + (char *)transchar(c)); + return errbuf; +} + +/// Check string options in a buffer for NULL value. +void check_buf_options(buf_T *buf) +{ + check_string_option(&buf->b_p_bh); + check_string_option(&buf->b_p_bt); + check_string_option(&buf->b_p_fenc); + check_string_option(&buf->b_p_ff); + check_string_option(&buf->b_p_def); + check_string_option(&buf->b_p_inc); + check_string_option(&buf->b_p_inex); + check_string_option(&buf->b_p_inde); + check_string_option(&buf->b_p_indk); + check_string_option(&buf->b_p_fp); + check_string_option(&buf->b_p_fex); + check_string_option(&buf->b_p_kp); + check_string_option(&buf->b_p_mps); + check_string_option(&buf->b_p_fo); + check_string_option(&buf->b_p_flp); + check_string_option(&buf->b_p_isk); + check_string_option(&buf->b_p_com); + check_string_option(&buf->b_p_cms); + check_string_option(&buf->b_p_nf); + check_string_option(&buf->b_p_qe); + check_string_option(&buf->b_p_syn); + check_string_option(&buf->b_s.b_syn_isk); + check_string_option(&buf->b_s.b_p_spc); + check_string_option(&buf->b_s.b_p_spf); + check_string_option(&buf->b_s.b_p_spl); + check_string_option(&buf->b_s.b_p_spo); + check_string_option(&buf->b_p_sua); + check_string_option(&buf->b_p_cink); + check_string_option(&buf->b_p_cino); + parse_cino(buf); + check_string_option(&buf->b_p_ft); + check_string_option(&buf->b_p_cinw); + check_string_option(&buf->b_p_cinsd); + check_string_option(&buf->b_p_cpt); + check_string_option(&buf->b_p_cfu); + check_string_option(&buf->b_p_ofu); + check_string_option(&buf->b_p_keymap); + check_string_option(&buf->b_p_gp); + check_string_option(&buf->b_p_mp); + check_string_option(&buf->b_p_efm); + check_string_option(&buf->b_p_ep); + check_string_option(&buf->b_p_path); + check_string_option(&buf->b_p_tags); + check_string_option(&buf->b_p_tfu); + check_string_option(&buf->b_p_tc); + check_string_option(&buf->b_p_dict); + check_string_option(&buf->b_p_tsr); + check_string_option(&buf->b_p_tsrfu); + check_string_option(&buf->b_p_lw); + check_string_option(&buf->b_p_bkc); + check_string_option(&buf->b_p_menc); + check_string_option(&buf->b_p_vsts); + check_string_option(&buf->b_p_vts); +} + +/// Free the string allocated for an option. +/// Checks for the string being empty_option. This may happen if we're out of +/// memory, vim_strsave() returned NULL, which was replaced by empty_option by +/// check_options(). +/// Does NOT check for P_ALLOCED flag! +void free_string_option(char *p) +{ + if (p != empty_option) { + xfree(p); + } +} + +void clear_string_option(char **pp) +{ + if (*pp != empty_option) { + xfree(*pp); + } + *pp = empty_option; +} + +void check_string_option(char **pp) +{ + if (*pp == NULL) { + *pp = empty_option; + } +} + +/// Set global value for string option when it's a local option. +/// +/// @param opt_idx option index +/// @param varp pointer to option variable +static void set_string_option_global(int opt_idx, char **varp) +{ + char **p; + + // the global value is always allocated + if (is_window_local_option(opt_idx)) { + p = (char **)GLOBAL_WO(varp); + } else { + p = (char **)get_option_var(opt_idx); + } + if (!is_global_option(opt_idx) && p != varp) { + char *s = xstrdup(*varp); + free_string_option(*p); + *p = s; + } +} + +/// Set a string option to a new value (without checking the effect). +/// The string is copied into allocated memory. +/// if ("opt_idx" == -1) "name" is used, otherwise "opt_idx" is used. +/// When "set_sid" is zero set the scriptID to current_sctx.sc_sid. When +/// "set_sid" is SID_NONE don't set the scriptID. Otherwise set the scriptID to +/// "set_sid". +/// +/// @param opt_flags OPT_FREE, OPT_LOCAL and/or OPT_GLOBAL +void set_string_option_direct(const char *name, int opt_idx, const char *val, int opt_flags, + int set_sid) +{ + char *s; + char **varp; + int both = (opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0; + int idx = opt_idx; + + if (idx == -1) { // Use name. + idx = findoption(name); + if (idx < 0) { // Not found (should not happen). + internal_error("set_string_option_direct()"); + siemsg(_("For option %s"), name); + return; + } + } + + if (is_hidden_option(idx)) { // can't set hidden option + return; + } + + assert((void *)get_option_var(idx) != (void *)&p_shada); + + s = xstrdup(val); + { + varp = (char **)get_option_varp_scope(idx, both ? OPT_LOCAL : opt_flags); + if ((opt_flags & OPT_FREE) && (get_option_flags(idx) & P_ALLOCED)) { + free_string_option(*varp); + } + *varp = s; + + // For buffer/window local option may also set the global value. + if (both) { + set_string_option_global(idx, varp); + } + + set_option_flag(idx, P_ALLOCED); + + // When setting both values of a global option with a local value, + // make the local value empty, so that the global value is used. + if (is_global_local_option(idx) && both) { + free_string_option(*varp); + *varp = empty_option; + } + if (set_sid != SID_NONE) { + sctx_T script_ctx; + + if (set_sid == 0) { + script_ctx = current_sctx; + } else { + script_ctx.sc_sid = set_sid; + script_ctx.sc_seq = 0; + script_ctx.sc_lnum = 0; + } + set_option_sctx_idx(idx, opt_flags, script_ctx); + } + } +} + +/// Like set_string_option_direct(), but for a window-local option in "wp". +/// Blocks autocommands to avoid the old curwin becoming invalid. +void set_string_option_direct_in_win(win_T *wp, const char *name, int opt_idx, const char *val, + int opt_flags, int set_sid) +{ + win_T *save_curwin = curwin; + + block_autocmds(); + curwin = wp; + curbuf = curwin->w_buffer; + set_string_option_direct(name, opt_idx, val, opt_flags, set_sid); + curwin = save_curwin; + curbuf = curwin->w_buffer; + unblock_autocmds(); +} + +/// Set a string option to a new value, handling the effects +/// +/// @param[in] opt_idx Option to set. +/// @param[in] value New value. +/// @param[in] opt_flags Option flags: expected to contain #OPT_LOCAL and/or +/// #OPT_GLOBAL. +/// +/// @return NULL on success, an untranslated error message on error. +char *set_string_option(const int opt_idx, const char *const value, const int opt_flags) + FUNC_ATTR_NONNULL_ARG(2) FUNC_ATTR_WARN_UNUSED_RESULT +{ + if (is_hidden_option(opt_idx)) { // don't set hidden option + return NULL; + } + + char *const s = xstrdup(value); + char **const varp + = (char **)get_option_varp_scope(opt_idx, + (opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0 + ? (is_global_local_option(opt_idx) + ? OPT_GLOBAL : OPT_LOCAL) + : opt_flags); + char *const oldval = *varp; + char *oldval_l = NULL; + char *oldval_g = NULL; + + if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) { + oldval_l = *(char **)get_option_varp_scope(opt_idx, OPT_LOCAL); + oldval_g = *(char **)get_option_varp_scope(opt_idx, OPT_GLOBAL); + } + + *varp = s; + + char *const saved_oldval = xstrdup(oldval); + char *const saved_oldval_l = (oldval_l != NULL) ? xstrdup(oldval_l) : 0; + char *const saved_oldval_g = (oldval_g != NULL) ? xstrdup(oldval_g) : 0; + char *const saved_newval = xstrdup(s); + + int value_checked = false; + char *const errmsg = did_set_string_option(opt_idx, varp, oldval, + NULL, 0, + opt_flags, &value_checked); + if (errmsg == NULL) { + did_set_option(opt_idx, opt_flags, true, value_checked); + } + + // 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); + } + if (get_option_flags(opt_idx) & P_UI_OPTION) { + ui_call_option_set(cstr_as_string(get_option_fullname(opt_idx)), + STRING_OBJ(cstr_as_string(saved_newval))); + } + } + xfree(saved_oldval); + xfree(saved_oldval_l); + xfree(saved_oldval_g); + xfree(saved_newval); + + return errmsg; +} + +/// Return true if "val" is a valid 'filetype' name. +/// Also used for 'syntax' and 'keymap'. +static bool valid_filetype(const char *val) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ + return valid_name(val, ".-_"); +} + +/// Handle setting 'mousescroll'. +/// @return error message, NULL if it's OK. +static char *check_mousescroll(char *string) +{ + long vertical = -1; + long horizontal = -1; + + for (;;) { + char *end = vim_strchr(string, ','); + size_t length = end ? (size_t)(end - string) : STRLEN(string); + + // Both "ver:" and "hor:" are 4 bytes long. + // They should be followed by at least one digit. + if (length <= 4) { + return e_invarg; + } + + long *direction; + + if (memcmp(string, "ver:", 4) == 0) { + direction = &vertical; + } else if (memcmp(string, "hor:", 4) == 0) { + direction = &horizontal; + } else { + return e_invarg; + } + + // If the direction has already been set, this is a duplicate. + if (*direction != -1) { + return e_invarg; + } + + // Verify that only digits follow the colon. + for (size_t i = 4; i < length; i++) { + if (!ascii_isdigit(string[i])) { + return N_("E548: digit expected"); + } + } + + string += 4; + *direction = getdigits_int(&string, false, -1); + + // Num options are generally kept within the signed int range. + // We know this number won't be negative because we've already checked for + // a minus sign. We'll allow 0 as a means of disabling mouse scrolling. + if (*direction == -1) { + return e_invarg; + } + + if (!end) { + break; + } + + string = end + 1; + } + + // If a direction wasn't set, fallback to the default value. + p_mousescroll_vert = (vertical == -1) ? MOUSESCROLL_VERT_DFLT : vertical; + p_mousescroll_hor = (horizontal == -1) ? MOUSESCROLL_HOR_DFLT : horizontal; + + return NULL; +} + +/// Handle setting 'signcolumn' for value 'val' +/// +/// @return OK when the value is valid, FAIL otherwise +static int check_signcolumn(char *val) +{ + if (*val == NUL) { + return FAIL; + } + // check for basic match + if (check_opt_strings(val, p_scl_values, false) == OK) { + return OK; + } + + // check for 'auto:<NUMBER>-<NUMBER>' + if (STRLEN(val) == 8 + && !STRNCMP(val, "auto:", 5) + && ascii_isdigit(val[5]) + && val[6] == '-' + && ascii_isdigit(val[7])) { + int min = val[5] - '0'; + int max = val[7] - '0'; + if (min < 1 || max < 2 || min > 8 || max > 9 || min >= max) { + return FAIL; + } + return OK; + } + + return FAIL; +} + +/// Check validity of options with the 'statusline' format. +/// Return an untranslated error message or NULL. +char *check_stl_option(char *s) +{ + int groupdepth = 0; + static char errbuf[80]; + + while (*s) { + // Check for valid keys after % sequences + while (*s && *s != '%') { + s++; + } + if (!*s) { + break; + } + s++; + if (*s == '%' || *s == STL_TRUNCMARK || *s == STL_SEPARATE) { + s++; + continue; + } + if (*s == ')') { + s++; + if (--groupdepth < 0) { + break; + } + continue; + } + if (*s == '-') { + s++; + } + while (ascii_isdigit(*s)) { + s++; + } + if (*s == STL_USER_HL) { + continue; + } + if (*s == '.') { + s++; + while (*s && ascii_isdigit(*s)) { + s++; + } + } + if (*s == '(') { + groupdepth++; + continue; + } + if (vim_strchr(STL_ALL, *s) == NULL) { + return illegal_char(errbuf, sizeof(errbuf), *s); + } + if (*s == '{') { + bool reevaluate = (*++s == '%'); + + if (reevaluate && *++s == '}') { + // "}" is not allowed immediately after "%{%" + return illegal_char(errbuf, sizeof(errbuf), '}'); + } + while ((*s != '}' || (reevaluate && s[-1] != '%')) && *s) { + s++; + } + if (*s != '}') { + return e_unclosed_expression_sequence; + } + } + } + if (groupdepth != 0) { + return e_unbalanced_groups; + } + return NULL; +} + +static int shada_idx = -1; + +/// Handle string options that need some action to perform when changed. +/// The new value must be allocated. +/// +/// @param opt_idx index in options[] table +/// @param varp pointer to the option variable +/// @param oldval previous value of the option +/// @param errbuf buffer for errors, or NULL +/// @param errbuflen length of errors buffer +/// @param opt_flags OPT_LOCAL and/or OPT_GLOBAL +/// @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) +{ + char *errmsg = NULL; + char *s, *p; + int did_chartab = false; + bool free_oldval = (get_option_flags(opt_idx) & P_ALLOCED); + bool value_changed = false; + + // Get the global option to compare with, otherwise we would have to check + // two values for all local options. + char **gvarp = (char **)get_option_varp_scope(opt_idx, OPT_GLOBAL); + + // Disallow changing some options from secure mode + if ((secure || sandbox != 0) + && (get_option_flags(opt_idx) & P_SECURE)) { + errmsg = e_secure; + } else if (((get_option_flags(opt_idx) & P_NFNAME) + && strpbrk(*varp, (secure ? "/\\*?[|;&<>\r\n" : "/\\*?[<>\r\n")) != NULL) + || ((get_option_flags(opt_idx) & 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. + 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; + } + } + } 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; + } + } else if (varp == &p_isi + || varp == &(curbuf->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 + } + } 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"); + } + } 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 == &p_hl) { // 'highlight' + if (STRCMP(*varp, HIGHLIGHT_INIT) != 0) { + errmsg = e_unsupportedoption; + } + } else if (varp == &p_jop) { // 'jumpoptions' + if (opt_strings_flags(p_jop, p_jop_values, &jop_flags, true) != OK) { + errmsg = e_invarg; + } + } else if (gvarp == &p_nf) { // 'nrformats' + if (check_opt_strings(*varp, p_nf_values, true) != OK) { + errmsg = e_invarg; + } + } 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; + } + } else if (varp == &p_vop) { // 'viewoptions' + if (opt_strings_flags(p_vop, p_ssop_values, &vop_flags, true) != OK) { + errmsg = e_invarg; + } + } else if (varp == &p_rdb) { // 'redrawdebug' + if (opt_strings_flags(p_rdb, p_rdb_values, &rdb_flags, true) != OK) { + errmsg = e_invarg; + } + } else if (varp == &p_sbo) { // 'scrollopt' + if (check_opt_strings(p_sbo, p_scbopt_values, true) != OK) { + errmsg = e_invarg; + } + } 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(); + } + } 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; + } + } else if (varp == &p_wim) { // 'wildmode' + if (check_opt_wim() == FAIL) { + errmsg = e_invarg; + } + } else if (varp == &p_wop) { // 'wildoptions' + if (opt_strings_flags(p_wop, p_wop_values, &wop_flags, true) != OK) { + errmsg = e_invarg; + } + } else if (varp == &p_wak) { // 'winaltkeys' + if (*p_wak == NUL + || check_opt_strings(p_wak, p_wak_values, false) != OK) { + errmsg = e_invarg; + } + } else if (varp == &p_ei) { // 'eventignore' + if (check_ei() == FAIL) { + errmsg = e_invarg; + } + } 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(); + } + } 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); + } + } + } else if (varp == &p_ffs) { // 'fileformats' + if (check_opt_strings(p_ffs, p_ff_values, true) != OK) { + errmsg = e_invarg; + } + } 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; + } + } + } 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); + } + } 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); + } 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; + } + } else if (varp == &p_shada) { // 'shada' + // TODO(ZyX-I): Remove this code in the future, alongside with &viminfo + // option. + opt_idx = ((get_option_fullname(opt_idx)[0] == 'v') + ? (shada_idx == -1 ? ((shada_idx = findoption("shada"))) : shada_idx) + : 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 = (get_option_flags(opt_idx) & 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"); + } + } 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); + } + } 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(); + } else if (varp == &p_sel) { // 'selection' + if (*p_sel == NUL + || check_opt_strings(p_sel, p_sel_values, false) != OK) { + errmsg = e_invarg; + } + } else if (varp == &p_slm) { // 'selectmode' + if (check_opt_strings(p_slm, p_slm_values, true) != OK) { + errmsg = e_invarg; + } + } 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); + } + } else if (varp == &p_mousem) { // 'mousemodel' + if (check_opt_strings(p_mousem, p_mousem_values, false) != OK) { + errmsg = e_invarg; + } + } 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; + } + } else if (varp == &p_debug) { // 'debug' + if (check_opt_strings(p_debug, p_debug_values, true) != OK) { + errmsg = e_invarg; + } + } 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(); + } + } else if (varp == &p_ead) { // 'eadirection' + if (check_opt_strings(p_ead, p_ead_values, false) != OK) { + errmsg = e_invarg; + } + } 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 (**varp != NUL && STRCMP("camel", *varp) != 0) { + errmsg = e_invarg; + } + } else if (varp == &p_sps) { // 'spellsuggest' + if (spell_check_sps() != OK) { + errmsg = e_invarg; + } + } else if (varp == &p_msm) { // 'mkspellmem' + if (spell_check_msm() != OK) { + errmsg = e_invarg; + } + } 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(); + } + } 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; + } + } + } + } else if (varp == &p_cot) { // 'completeopt' + if (check_opt_strings(p_cot, p_cot_values, true) != OK) { + errmsg = e_invarg; + } else { + completeopt_was_set(); + } +#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) { + 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) { + // '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; + } + } + } 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; + } + } else if (varp == &p_bo) { + if (opt_strings_flags(p_bo, p_bo_values, &bo_flags, true) != OK) { + errmsg = e_invarg; + } + } 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; + } + } else if (varp == &p_cmp) { // 'casemap' + if (opt_strings_flags(p_cmp, p_cmp_values, &cmp_flags, true) != OK) { + errmsg = e_invarg; + } + } 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); + } + } else if (gvarp == &p_cms) { // 'commentstring' + if (**varp != NUL && strstr(*varp, "%s") == NULL) { + errmsg = N_("E537: 'commentstring' must be empty or contain %s"); + } + } else if (varp == &p_fdo) { // 'foldopen' + if (opt_strings_flags(p_fdo, p_fdo_values, &fdo_flags, true) != OK) { + errmsg = e_invarg; + } + } 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); + } + } 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; + } + } + } + } else if (gvarp == &p_cino) { // 'cinoptions' + // TODO(vim): recognize errors + parse_cino(curbuf); + } 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; + } + } 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; + } + } + } + } else if (varp == &p_opfunc) { // 'operatorfunc' + if (set_operatorfunc_option() == FAIL) { + errmsg = e_invarg; + } + } 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; + } + } + } + } + + // If error detected, restore the previous value. + if (errmsg != NULL) { + free_string_option(*varp); + *varp = oldval; + // When resetting some values, need to act on it. + if (did_chartab) { + (void)init_chartab(); + } + } else { + // Remember where the option was set. + set_option_sctx_idx(opt_idx, opt_flags, current_sctx); + // Free string options that are in allocated memory. + // Use "free_oldval", because recursiveness may change the flags under + // our fingers (esp. init_highlight()). + if (free_oldval) { + free_string_option(oldval); + } + set_option_flag(opt_idx, P_ALLOCED); + + if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0 + && is_global_local_option(opt_idx)) { + // global option with local value set to use global value; free + // the local value and make it empty + p = get_option_varp_scope(opt_idx, OPT_LOCAL); + free_string_option(*(char **)p); + *(char **)p = empty_option; + } else if (!(opt_flags & OPT_LOCAL) && opt_flags != OPT_GLOBAL) { + // May set global value for local option. + set_string_option_global(opt_idx, varp); + } + + // 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 == &p_mouse) { + setmouse(); // in case 'mouse' changed + } + + if (curwin->w_curswant != MAXCOL + && (get_option_flags(opt_idx) & (P_CURSWANT | P_RALL)) != 0) { + curwin->w_set_curswant = true; + } + + check_redraw(get_option_flags(opt_idx)); + + return errmsg; +} + +/// Check an option that can be a range of string values. +/// +/// @param list when true: accept a list of values +/// +/// @return OK for correct value, FAIL otherwise. Empty is always OK. +static int check_opt_strings(char *val, char **values, int list) +{ + return opt_strings_flags(val, values, NULL, list); +} + +/// Handle an option that can be a range of string values. +/// Set a flag in "*flagp" for each string present. +/// +/// @param val new value +/// @param values array of valid string values +/// @param list when true: accept a list of values +/// +/// @return OK for correct value, FAIL otherwise. Empty is always OK. +static int opt_strings_flags(char *val, char **values, unsigned *flagp, bool list) +{ + unsigned int new_flags = 0; + + while (*val) { + for (unsigned int i = 0;; i++) { + if (values[i] == NULL) { // val not found in values[] + return FAIL; + } + + size_t len = STRLEN(values[i]); + if (STRNCMP(values[i], val, len) == 0 + && ((list && val[len] == ',') || val[len] == NUL)) { + val += len + (val[len] == ','); + assert(i < sizeof(1U) * 8); + new_flags |= (1U << i); + break; // check next item in val list + } + } + } + if (flagp != NULL) { + *flagp = new_flags; + } + + return OK; +} + +/// @return OK if "p" is a valid fileformat name, FAIL otherwise. +int check_ff_value(char *p) +{ + return check_opt_strings(p, p_ff_values, false); +} diff --git a/src/nvim/optionstr.h b/src/nvim/optionstr.h new file mode 100644 index 0000000000..ac8d90e10e --- /dev/null +++ b/src/nvim/optionstr.h @@ -0,0 +1,10 @@ +#ifndef NVIM_OPTIONSTR_H +#define NVIM_OPTIONSTR_H + +#include "nvim/buffer_defs.h" // for buf_T, win_T +#include "nvim/option_defs.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "optionstr.h.generated.h" +#endif +#endif // NVIM_OPTIONSTR_H diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c index eaa56ffe63..c940c86675 100644 --- a/src/nvim/os/env.c +++ b/src/nvim/os/env.c @@ -8,9 +8,8 @@ #include "nvim/ascii.h" #include "nvim/charset.h" +#include "nvim/cmdexpand.h" #include "nvim/eval.h" -#include "nvim/ex_getln.h" -#include "nvim/fileio.h" #include "nvim/macros.h" #include "nvim/map.h" #include "nvim/memory.h" @@ -554,9 +553,9 @@ char_u *expand_env_save_opt(char_u *src, bool one) /// @param src Input string e.g. "$HOME/vim.hlp" /// @param dst[out] Where to put the result /// @param dstlen Maximum length of the result -void expand_env(char_u *src, char_u *dst, int dstlen) +void expand_env(char *src, char *dst, int dstlen) { - expand_env_esc(src, dst, dstlen, false, false, NULL); + expand_env_esc((char_u *)src, (char_u *)dst, dstlen, false, false, NULL); } /// Expand environment variable with path name and escaping. @@ -580,18 +579,18 @@ void expand_env_esc(char_u *restrict srcp, char_u *restrict dst, int dstlen, boo int prefix_len = (prefix == NULL) ? 0 : (int)STRLEN(prefix); - char_u *src = (char_u *)skipwhite((char *)srcp); + char *src = skipwhite((char *)srcp); dstlen--; // leave one char space for "\," while (*src && dstlen > 0) { // Skip over `=expr`. if (src[0] == '`' && src[1] == '=') { - var = src; + var = (char_u *)src; src += 2; - (void)skip_expr((char **)&src); + (void)skip_expr(&src); if (*src == '`') { src++; } - size_t len = (size_t)(src - var); + size_t len = (size_t)(src - (char *)var); if (len > (size_t)dstlen) { len = (size_t)dstlen; } @@ -608,7 +607,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 = src + 1; + tail = (char_u *)src + 1; var = dst; int c = dstlen - 1; @@ -646,11 +645,11 @@ void expand_env_esc(char_u *restrict srcp, char_u *restrict dst, int dstlen, boo || vim_ispathsep(src[1]) || vim_strchr(" ,\t\n", src[1]) != NULL) { var = (char_u *)homedir; - tail = src + 1; + tail = (char_u *)src + 1; } else { // user directory #if defined(UNIX) // Copy ~user to dst[], so we can put a NUL after it. - tail = src; + tail = (char_u *)src; var = dst; int c = dstlen - 1; while (c-- > 0 @@ -723,7 +722,7 @@ void expand_env_esc(char_u *restrict srcp, char_u *restrict dst, int dstlen, boo tail++; } dst += c; - src = tail; + src = (char *)tail; copy_char = false; } if (mustfree) { @@ -737,17 +736,17 @@ 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++ = *src++; + *dst++ = (char_u)(*src++); dstlen--; } else if ((src[0] == ' ' || src[0] == ',') && !one) { at_start = true; } if (dstlen > 0) { - *dst++ = *src++; + *dst++ = (char_u)(*src++); dstlen--; if (prefix != NULL - && src - prefix_len >= srcp + && src - prefix_len >= (char *)srcp && STRNCMP(src - prefix_len, prefix, prefix_len) == 0) { at_start = true; } @@ -766,12 +765,12 @@ static char *vim_version_dir(const char *vimdir) return NULL; } char *p = concat_fnames(vimdir, VIM_VERSION_NODOT, true); - if (os_isdir((char_u *)p)) { + if (os_isdir(p)) { return p; } xfree(p); p = concat_fnames(vimdir, RUNTIME_DIRNAME, true); - if (os_isdir((char_u *)p)) { + if (os_isdir(p)) { return p; } xfree(p); @@ -938,8 +937,8 @@ char *vim_getenv(const char *name) // - the directory name from 'helpfile' (unless it contains '$') // - the executable name from argv[0] if (vim_path == NULL) { - if (p_hf != NULL && vim_strchr((char *)p_hf, '$') == NULL) { - vim_path = (char *)p_hf; + if (p_hf != NULL && vim_strchr(p_hf, '$') == NULL) { + vim_path = p_hf; } char exe_name[MAXPATHL]; @@ -958,7 +957,7 @@ char *vim_getenv(const char *name) char *vim_path_end = path_tail(vim_path); // remove "doc/" from 'helpfile', if present - if (vim_path == (char *)p_hf) { + if (vim_path == p_hf) { vim_path_end = remove_tail(vim_path, vim_path_end, "doc"); } @@ -977,7 +976,7 @@ char *vim_getenv(const char *name) assert(vim_path_end >= vim_path); vim_path = xstrndup(vim_path, (size_t)(vim_path_end - vim_path)); - if (!os_isdir((char_u *)vim_path)) { + if (!os_isdir(vim_path)) { xfree(vim_path); vim_path = NULL; } @@ -1069,9 +1068,8 @@ size_t home_replace(const buf_T *const buf, const char *src, char *const dst, si must_free = true; size_t usedlen = 0; size_t flen = strlen(homedir_env_mod); - char_u *fbuf = NULL; - (void)modify_fname(":p", false, &usedlen, - &homedir_env_mod, (char **)&fbuf, &flen); + char *fbuf = NULL; + (void)modify_fname(":p", false, &usedlen, &homedir_env_mod, &fbuf, &flen); flen = strlen(homedir_env_mod); assert(homedir_env_mod != homedir_env); if (vim_ispathsep(homedir_env_mod[flen - 1])) { @@ -1158,15 +1156,12 @@ char *home_replace_save(buf_T *buf, const char *src) /// Function given to ExpandGeneric() to obtain an environment variable name. char *get_env_name(expand_T *xp, int idx) { -#define ENVNAMELEN 100 - // this static buffer is needed to avoid a memory leak in ExpandGeneric - static char_u name[ENVNAMELEN]; assert(idx >= 0); char *envname = os_getenvname_at_index((size_t)idx); if (envname) { - STRLCPY(name, envname, ENVNAMELEN); + STRLCPY(xp->xp_buf, envname, EXPAND_BUF_LEN); xfree(envname); - return (char *)name; + return xp->xp_buf; } return NULL; } @@ -1231,3 +1226,29 @@ bool os_shell_is_cmdexe(const char *sh) } return striequal("cmd.exe", path_tail(sh)); } + +/// Removes environment variable "name" and take care of side effects. +void vim_unsetenv_ext(const char *var) +{ + os_unsetenv(var); + + // "homedir" is not cleared, keep using the old value until $HOME is set. + if (STRICMP(var, "VIM") == 0) { + didset_vim = false; + } else if (STRICMP(var, "VIMRUNTIME") == 0) { + didset_vimruntime = false; + } +} + +/// Set environment variable "name" and take care of side effects. +void vim_setenv_ext(const char *name, const char *val) +{ + os_setenv(name, val, 1); + if (STRICMP(name, "HOME") == 0) { + init_homedir(); + } else if (didset_vim && STRICMP(name, "VIM") == 0) { + didset_vim = false; + } else if (didset_vimruntime && STRICMP(name, "VIMRUNTIME") == 0) { + didset_vimruntime = false; + } +} diff --git a/src/nvim/os/fs.c b/src/nvim/os/fs.c index 901a1bc5a6..c0d5616666 100644 --- a/src/nvim/os/fs.c +++ b/src/nvim/os/fs.c @@ -129,10 +129,10 @@ bool os_isrealdir(const char *name) /// Check if the given path exists and is a directory. /// /// @return `true` if `name` is a directory. -bool os_isdir(const char_u *name) +bool os_isdir(const char *name) FUNC_ATTR_NONNULL_ALL { - int32_t mode = os_getperm((const char *)name); + int32_t mode = os_getperm(name); if (mode < 0) { return false; } @@ -144,25 +144,6 @@ bool os_isdir(const char_u *name) return true; } -/// Check if the given path is a directory and is executable. -/// Gives the same results as `os_isdir()` on Windows. -/// -/// @return `true` if `name` is a directory and executable. -bool os_isdir_executable(const char *name) - FUNC_ATTR_NONNULL_ALL -{ - int32_t mode = os_getperm(name); - if (mode < 0) { - return false; - } - -#ifdef WIN32 - return (S_ISDIR(mode)); -#else - return (S_ISDIR(mode) && (S_IXUSR & mode)); -#endif -} - /// Check what `name` is: /// @return NODE_NORMAL: file or directory (or doesn't exist) /// NODE_WRITABLE: writable device, socket, fifo, etc. @@ -339,7 +320,7 @@ static bool is_executable_ext(const char *name, char **abspath) const char *ext_end = ext; size_t ext_len = - copy_option_part((char_u **)&ext_end, (char_u *)buf_end, + copy_option_part(&ext_end, (char_u *)buf_end, sizeof(os_buf) - (size_t)(buf_end - os_buf), ENV_SEPSTR); if (ext_len != 0) { bool in_pathext = nameext_len == ext_len @@ -884,7 +865,7 @@ int os_file_is_writable(const char *name) int r; RUN_UV_FS_FUNC(r, uv_fs_access, name, W_OK, NULL); if (r == 0) { - return os_isdir((char_u *)name) ? 2 : 1; + return os_isdir(name) ? 2 : 1; } return 0; } @@ -930,11 +911,11 @@ int os_mkdir_recurse(const char *const dir, int32_t mode, char **const failed_di // We're done when it's "/" or "c:/". const size_t dirlen = strlen(dir); char *const curdir = xmemdupz(dir, dirlen); - char *const past_head = (char *)get_past_head((char_u *)curdir); + char *const past_head = get_past_head(curdir); char *e = curdir + dirlen; const char *const real_end = e; const char past_head_save = *past_head; - while (!os_isdir((char_u *)curdir)) { + while (!os_isdir(curdir)) { e = path_tail_with_sep(curdir); if (e <= past_head) { *past_head = NUL; @@ -1051,7 +1032,7 @@ int os_remove(const char *path) bool os_fileinfo(const char *path, FileInfo *file_info) FUNC_ATTR_NONNULL_ARG(2) { - memset(file_info, 0, sizeof(*file_info)); + CLEAR_POINTER(file_info); return os_stat(path, &(file_info->stat)) == kLibuvSuccess; } @@ -1063,7 +1044,7 @@ bool os_fileinfo(const char *path, FileInfo *file_info) bool os_fileinfo_link(const char *path, FileInfo *file_info) FUNC_ATTR_NONNULL_ARG(2) { - memset(file_info, 0, sizeof(*file_info)); + CLEAR_POINTER(file_info); if (path == NULL) { return false; } @@ -1087,7 +1068,7 @@ bool os_fileinfo_fd(int file_descriptor, FileInfo *file_info) FUNC_ATTR_NONNULL_ALL { uv_fs_t request; - memset(file_info, 0, sizeof(*file_info)); + CLEAR_POINTER(file_info); fs_loop_lock(); bool ok = uv_fs_fstat(&fs_loop, &request, diff --git a/src/nvim/os/input.c b/src/nvim/os/input.c index c47a891c18..bfe6d59dc6 100644 --- a/src/nvim/os/input.c +++ b/src/nvim/os/input.c @@ -8,10 +8,9 @@ #include "nvim/api/private/defs.h" #include "nvim/ascii.h" +#include "nvim/autocmd.h" #include "nvim/event/loop.h" #include "nvim/event/rstream.h" -#include "nvim/ex_cmds2.h" -#include "nvim/fileio.h" #include "nvim/getchar.h" #include "nvim/keycodes.h" #include "nvim/main.h" @@ -19,6 +18,7 @@ #include "nvim/memory.h" #include "nvim/msgpack_rpc/channel.h" #include "nvim/os/input.h" +#include "nvim/profile.h" #include "nvim/screen.h" #include "nvim/state.h" #include "nvim/ui.h" diff --git a/src/nvim/os/pty_conpty_win.h b/src/nvim/os/pty_conpty_win.h index c243db4fa5..15e7c3da0c 100644 --- a/src/nvim/os/pty_conpty_win.h +++ b/src/nvim/os/pty_conpty_win.h @@ -1,6 +1,9 @@ #ifndef NVIM_OS_PTY_CONPTY_WIN_H #define NVIM_OS_PTY_CONPTY_WIN_H +#include "nvim/lib/kvec.h" +#include "nvim/os/input.h" + #ifndef HPCON # define HPCON VOID * #endif diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c index 8f2018c1f4..461a79c37b 100644 --- a/src/nvim/os/shell.c +++ b/src/nvim/os/shell.c @@ -25,6 +25,7 @@ #include "nvim/os/shell.h" #include "nvim/os/signal.h" #include "nvim/path.h" +#include "nvim/profile.h" #include "nvim/screen.h" #include "nvim/strings.h" #include "nvim/tag.h" @@ -54,7 +55,7 @@ static void save_patterns(int num_pat, char **pat, int *num_file, char ***file) char_u *s = vim_strsave((char_u *)pat[i]); // Be compatible with expand_filename(): halve the number of // backslashes. - backslash_halve(s); + backslash_halve((char *)s); (*file)[i] = (char *)s; } *num_file = num_pat; @@ -73,7 +74,7 @@ static bool have_wildcard(int num, char **file) static bool have_dollars(int num, char **file) { for (int i = 0; i < num; i++) { - if (vim_strchr((char *)file[i], '$') != NULL) { + if (vim_strchr(file[i], '$') != NULL) { return true; } } @@ -248,10 +249,16 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in } STRCAT(command, ">"); } else { - if (flags & EW_NOTFOUND) { - STRCPY(command, "set nonomatch; "); - } else { - STRCPY(command, "unset nonomatch; "); + STRCPY(command, ""); + if (shell_style == STYLE_GLOB) { + // Assume the nonomatch option is valid only for csh like shells, + // otherwise, this may set the positional parameters for the shell, + // e.g. "$*". + if (flags & EW_NOTFOUND) { + STRCAT(command, "set nonomatch; "); + } else { + STRCAT(command, "unset nonomatch; "); + } } if (shell_style == STYLE_GLOB) { STRCAT(command, "glob >"); @@ -501,7 +508,7 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in } // check if this entry should be included - dir = (os_isdir((char_u *)(*file)[i])); + dir = (os_isdir((*file)[i])); if ((dir && !(flags & EW_DIR)) || (!dir && !(flags & EW_FILE))) { continue; } @@ -904,7 +911,7 @@ static int do_os_system(char **argv, const char *input, size_t len, char **outpu out_data_ring(NULL, SIZE_MAX); } if (forward_output) { - // caller should decide if wait_return is invoked + // caller should decide if wait_return() is invoked no_wait_return++; msg_end(); no_wait_return--; diff --git a/src/nvim/os/signal.c b/src/nvim/os/signal.c index 581f025a0f..e592570966 100644 --- a/src/nvim/os/signal.c +++ b/src/nvim/os/signal.c @@ -9,10 +9,10 @@ #endif #include "nvim/ascii.h" +#include "nvim/autocmd.h" #include "nvim/eval.h" #include "nvim/event/loop.h" #include "nvim/event/signal.h" -#include "nvim/fileio.h" #include "nvim/globals.h" #include "nvim/log.h" #include "nvim/main.h" diff --git a/src/nvim/path.c b/src/nvim/path.c index a0b09bcec2..a5cec6772f 100644 --- a/src/nvim/path.c +++ b/src/nvim/path.c @@ -8,9 +8,9 @@ #include "nvim/ascii.h" #include "nvim/charset.h" +#include "nvim/cmdexpand.h" #include "nvim/eval.h" #include "nvim/ex_docmd.h" -#include "nvim/ex_getln.h" #include "nvim/file_search.h" #include "nvim/fileio.h" #include "nvim/garray.h" @@ -33,7 +33,7 @@ #include "nvim/vim.h" #include "nvim/window.h" -#define URL_SLASH 1 // path_is_url() has found "://" +#define URL_SLASH 1 // path_is_url() has found ":/" #define URL_BACKSLASH 2 // path_is_url() has found ":\\" #ifdef gen_expand_wildcards @@ -62,7 +62,7 @@ FileComparison path_full_compare(char *const s1, char *const s2, const bool chec FileID file_id_1, file_id_2; if (expandenv) { - expand_env((char_u *)s1, (char_u *)exp1, MAXPATHL); + expand_env(s1, exp1, MAXPATHL); } else { STRLCPY(exp1, s1, MAXPATHL); } @@ -104,7 +104,7 @@ char *path_tail(const char *fname) return ""; } - const char *tail = (char *)get_past_head((char_u *)fname); + const char *tail = get_past_head(fname); const char *p = tail; // Find last part of path. while (*p != NUL) { @@ -130,7 +130,7 @@ char *path_tail_with_sep(char *fname) assert(fname != NULL); // Don't remove the '/' from "c:/file". - char *past_head = (char *)get_past_head((char_u *)fname); + char *past_head = get_past_head(fname); char *tail = path_tail(fname); while (tail > past_head && after_pathsep(fname, tail)) { tail--; @@ -150,7 +150,7 @@ char *path_tail_with_sep(char *fname) const char_u *invocation_path_tail(const char_u *invocation, size_t *len) FUNC_ATTR_NONNULL_RET FUNC_ATTR_NONNULL_ARG(1) { - const char_u *tail = get_past_head((char_u *)invocation); + const char_u *tail = (char_u *)get_past_head((char *)invocation); const char_u *p = tail; while (*p != NUL && *p != ' ') { bool was_sep = vim_ispathsep_nocolon(*p); @@ -215,28 +215,26 @@ bool is_path_head(const char_u *path) /// Get a pointer to one character past the head of a path name. /// Unix: after "/"; Win: after "c:\" /// If there is no head, path is returned. -char_u *get_past_head(const char_u *path) +char *get_past_head(const char *path) { - const char_u *retval = path; + const char *retval = path; #ifdef WIN32 // May skip "c:" - if (is_path_head(path)) { + if (is_path_head((char_u *)path)) { retval = path + 2; } #endif while (vim_ispathsep(*retval)) { - ++retval; + retval++; } - return (char_u *)retval; + return (char *)retval; } -/* - * Return TRUE if 'c' is a path separator. - * Note that for MS-Windows this includes the colon. - */ +/// Return true if 'c' is a path separator. +/// Note that for MS-Windows this includes the colon. int vim_ispathsep(int c) { #ifdef UNIX @@ -262,9 +260,7 @@ int vim_ispathsep_nocolon(int c) ; } -/* - * return TRUE if 'c' is a path list separator. - */ +/// return true if 'c' is a path list separator. int vim_ispathlistsep(int c) { #ifdef UNIX @@ -314,16 +310,14 @@ void shorten_dir_len(char_u *str, int trim_len) /// Shorten the path of a file from "~/foo/../.bar/fname" to "~/f/../.b/fname" /// It's done in-place. -void shorten_dir(char_u *str) +void shorten_dir(char *str) { - shorten_dir_len(str, 1); + shorten_dir_len((char_u *)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!. - */ +/// 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) { char *p = path_tail_with_sep((char *)fname); @@ -332,7 +326,7 @@ bool dir_of_file_exists(char_u *fname) } char c = *p; *p = NUL; - bool retval = os_isdir(fname); + bool retval = os_isdir((char *)fname); *p = c; return retval; } @@ -376,16 +370,16 @@ int path_fnamencmp(const char *const fname1, const char *const fname2, size_t le const char *p1 = fname1; const char *p2 = fname2; while (len > 0) { - c1 = utf_ptr2char((const char_u *)p1); - c2 = utf_ptr2char((const char_u *)p2); + c1 = utf_ptr2char(p1); + c2 = utf_ptr2char(p2); if ((c1 == NUL || c2 == NUL || (!((c1 == '/' || c1 == '\\') && (c2 == '\\' || c2 == '/')))) && (p_fic ? (c1 != c2 && CH_FOLD(c1) != CH_FOLD(c2)) : c1 != c2)) { break; } - len -= (size_t)utfc_ptr2len((const char_u *)p1); - p1 += utfc_ptr2len((const char_u *)p1); - p2 += utfc_ptr2len((const char_u *)p2); + len -= (size_t)utfc_ptr2len(p1); + p1 += utfc_ptr2len(p1); + p2 += utfc_ptr2len(p2); } return p_fic ? CH_FOLD(c1) - CH_FOLD(c2) : c1 - c2; #else @@ -637,7 +631,7 @@ static size_t do_path_expand(garray_T *gap, const char_u *path, size_t wildoff, 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 >= path + wildoff && rem_backslash(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) { @@ -663,16 +657,16 @@ static size_t do_path_expand(garray_T *gap, const char_u *path, size_t wildoff, // Now we have one wildcard component between "s" and "e". /* Remove backslashes between "wildoff" and the start of the wildcard * component. */ - for (p = buf + wildoff; p < s; ++p) { - if (rem_backslash(p)) { + for (p = buf + wildoff; p < s; p++) { + if (rem_backslash((char *)p)) { STRMOVE(p, p + 1); - --e; - --s; + e--; + s--; } } // Check for "**" between "s" and "e". - for (p = s; p < e; ++p) { + for (p = s; p < e; p++) { if (p[0] == '*' && p[1] == '*') { starstar = true; } @@ -695,11 +689,11 @@ static size_t do_path_expand(garray_T *gap, const char_u *path, size_t wildoff, regmatch.rm_ic = true; // Always ignore case on Windows. #endif if (flags & (EW_NOERROR | EW_NOTWILD)) { - ++emsg_silent; + emsg_silent++; } regmatch.regprog = vim_regcomp(pat, RE_MAGIC); if (flags & (EW_NOERROR | EW_NOTWILD)) { - --emsg_silent; + emsg_silent--; } xfree(pat); @@ -725,7 +719,7 @@ static size_t do_path_expand(garray_T *gap, const char_u *path, size_t wildoff, // Find all matching entries. char_u *name; scandir_next_with_dots(NULL); // initialize - while ((name = (char_u *)scandir_next_with_dots(&dir)) != NULL) { + while (!got_int && (name = (char_u *)scandir_next_with_dots(&dir)) != NULL) { if ((name[0] != '.' || starts_with_dot || ((flags & EW_DODOT) @@ -742,9 +736,9 @@ static size_t do_path_expand(garray_T *gap, const char_u *path, size_t wildoff, * find matches. */ STRCPY(buf + len, "/**"); STRCPY(buf + len + 3, path_end); - ++stardepth; + stardepth++; (void)do_path_expand(gap, buf, len + 1, flags, true); - --stardepth; + stardepth--; } STRCPY(buf + len, path_end); @@ -758,7 +752,7 @@ static size_t do_path_expand(garray_T *gap, const char_u *path, size_t wildoff, // no more wildcards, check if there is a match // remove backslashes for the remaining components only if (*path_end != NUL) { - backslash_halve(buf + len + 1); + backslash_halve((char *)buf + len + 1); } // add existing file or symbolic link if ((flags & EW_ALLLINKS) ? os_fileinfo_link((char *)buf, &file_info) @@ -774,8 +768,10 @@ static size_t do_path_expand(garray_T *gap, const char_u *path, size_t wildoff, xfree(buf); vim_regfree(regmatch.regprog); + // When interrupted the matches probably won't be used and sorting can be + // slow, thus skip it. size_t matches = (size_t)(gap->ga_len - start_len); - if (matches > 0) { + if (matches > 0 && !got_int) { qsort(((char_u **)gap->ga_data) + start_len, matches, sizeof(char_u *), pstrcmp); } @@ -804,10 +800,8 @@ static int find_previous_pathsep(char_u *path, char_u **psep) return FAIL; } -/* - * Returns TRUE if "maybe_unique" is unique wrt other_paths in "gap". - * "maybe_unique" is the end portion of "((char_u **)gap->ga_data)[i]". - */ +/// Returns true if "maybe_unique" is unique wrt other_paths in "gap". +/// "maybe_unique" is the end portion of "((char_u **)gap->ga_data)[i]". static bool is_unique(char_u *maybe_unique, garray_T *gap, int i) { char_u **other_paths = (char_u **)gap->ga_data; @@ -841,7 +835,7 @@ static bool is_unique(char_u *maybe_unique, garray_T *gap, int i) */ static void expand_path_option(char_u *curdir, garray_T *gap) { - char_u *path_option = *curbuf->b_p_path == NUL ? p_path : curbuf->b_p_path; + char_u *path_option = *curbuf->b_p_path == NUL ? p_path : (char_u *)curbuf->b_p_path; char_u *buf = xmalloc(MAXPATHL); while (*path_option != NUL) { @@ -1141,16 +1135,14 @@ 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(paths, pattern, gap, glob_flags); + globpath((char *)paths, (char *)pattern, gap, glob_flags); xfree(paths); return gap->ga_len; } -/* - * Return TRUE if "p" contains what looks like an environment variable. - * Allowing for escaping. - */ +/// Return true if "p" contains what looks like an environment variable. +/// Allowing for escaping. static bool has_env_var(char_u *p) { for (; *p; MB_PTR_ADV(p)) { @@ -1165,7 +1157,7 @@ static bool has_env_var(char_u *p) #ifdef SPECIAL_WILDCHAR -// Return TRUE if "p" contains a special wildcard character, one that Vim +// 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) { @@ -1254,7 +1246,7 @@ int gen_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, i */ ga_init(&ga, (int)sizeof(char_u *), 30); - for (int i = 0; i < num_pat; ++i) { + for (int i = 0; i < num_pat && !got_int; i++) { add_pat = -1; p = (char_u *)pat[i]; @@ -1320,7 +1312,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 = backslash_halve_save(p); + char_u *t = (char_u *)backslash_halve_save((char *)p); /* When EW_NOTFOUND is used, always add files and dirs. Makes * "vim c:/" work. */ @@ -1363,9 +1355,7 @@ void FreeWild(int count, char **files) xfree(files); } -/* - * Return TRUE if we can expand this backtick thing here. - */ +/// Return true if we can expand this backtick thing here. static int vim_backtick(char_u *p) { return *p == '`' && *(p + 1) != NUL && *(p + STRLEN(p) - 1) == '`'; @@ -1401,7 +1391,7 @@ static int expand_backtick(garray_T *gap, char_u *pat, int flags) cmd = skipwhite(cmd); // skip over white space p = cmd; while (*p != NUL && *p != '\r' && *p != '\n') { // skip over entry - ++p; + p++; } // add an entry if it is not empty if (p > cmd) { @@ -1409,11 +1399,11 @@ static int expand_backtick(garray_T *gap, char_u *pat, int flags) *p = NUL; addfile(gap, (char_u *)cmd, flags); *p = i; - ++cnt; + cnt++; } cmd = p; while (*cmd != NUL && (*cmd == '\r' || *cmd == '\n')) { - ++cmd; + cmd++; } } @@ -1482,7 +1472,7 @@ void addfile(garray_T *gap, char_u *f, int flags) } #endif - isdir = os_isdir(f); + isdir = os_isdir((char *)f); if ((isdir && !(flags & EW_DIR)) || (!isdir && !(flags & EW_FILE))) { return; } @@ -1656,12 +1646,12 @@ void simplify_filename(char_u *filename) *p = NUL; } else { if (p > start && tail[-1] == '.') { - --p; + p--; } STRMOVE(p, tail); // strip previous component } - --components; + components--; } } else if (p == start && !relative) { // leading "/.." or "/../" STRMOVE(p, tail); // strip ".." or "../" @@ -1682,7 +1672,7 @@ void simplify_filename(char_u *filename) static char *eval_includeexpr(const char *const ptr, const size_t len) { set_vim_var_string(VV_FNAME, ptr, (ptrdiff_t)len); - char *res = eval_to_string_safe((char *)curbuf->b_p_inex, NULL, + char *res = eval_to_string_safe(curbuf->b_p_inex, NULL, was_set_insecurely(curwin, "includeexpr", OPT_LOCAL)); set_vim_var_string(VV_FNAME, NULL, 0); return res; @@ -1724,7 +1714,7 @@ char_u *find_file_name_in_path(char_u *ptr, size_t len, int options, long count, ptr = tofree; len = STRLEN(ptr); file_name = find_file_in_path(ptr, len, options & ~FNAME_MESS, - TRUE, rel_fname); + true, rel_fname); } } if (file_name == NULL && (options & FNAME_MESS)) { @@ -1738,7 +1728,7 @@ char_u *find_file_name_in_path(char_u *ptr, size_t len, int options, long count, * appears several times in the path. */ while (file_name != NULL && --count > 0) { xfree(file_name); - file_name = find_file_in_path(ptr, len, options, FALSE, rel_fname); + file_name = find_file_in_path(ptr, len, options, false, rel_fname); } } else { file_name = vim_strnsave(ptr, len); @@ -1749,12 +1739,26 @@ char_u *find_file_name_in_path(char_u *ptr, size_t len, int options, long count, return file_name; } -// Check if the "://" of a URL is at the pointer, return URL_SLASH. +/// Checks for a Windows drive letter ("C:/") at the start of the path. +/// +/// @see https://url.spec.whatwg.org/#start-with-a-windows-drive-letter +bool path_has_drive_letter(const char *p) + FUNC_ATTR_NONNULL_ALL +{ + return strlen(p) >= 2 + && ASCII_ISALPHA(p[0]) + && (p[1] == ':' || p[1] == '|') + && (strlen(p) == 2 || ((p[2] == '/') | (p[2] == '\\') | (p[2] == '?') | (p[2] == '#'))); +} + +// Check if the ":/" of a URL is at the pointer, return URL_SLASH. // Also check for ":\\", which MS Internet Explorer accepts, return // URL_BACKSLASH. int path_is_url(const char *p) { - if (strncmp(p, "://", 3) == 0) { + // In the spec ':' is enough to recognize a scheme + // https://url.spec.whatwg.org/#scheme-state + if (strncmp(p, ":/", 2) == 0) { return URL_SLASH; } else if (strncmp(p, ":\\\\", 3) == 0) { return URL_BACKSLASH; @@ -1779,6 +1783,10 @@ int path_with_url(const char *fname) return 0; } + if (path_has_drive_letter(fname)) { + return 0; + } + // check body: alpha or dash for (p = fname + 1; (isalpha(*p) || (*p == '-')); p++) {} @@ -1787,7 +1795,7 @@ int path_with_url(const char *fname) return 0; } - // "://" or ":\\" must follow + // ":/" or ":\\" must follow return path_is_url(p); } @@ -1800,9 +1808,7 @@ bool path_with_extension(const char *path, const char *extension) return strcmp(last_dot + 1, extension) == 0; } -/* - * Return TRUE if "name" is a full (absolute) path name or URL. - */ +/// Return true if "name" is a full (absolute) path name or URL. bool vim_isAbsName(char_u *name) { return path_with_url((char *)name) != 0 || path_is_absolute(name); @@ -1839,7 +1845,7 @@ int vim_FullName(const char *fname, char *buf, size_t len, bool force) return OK; } - int rv = path_to_absolute((char_u *)fname, (char_u *)buf, len, force); + int rv = path_to_absolute(fname, buf, len, force); if (rv == FAIL) { xstrlcpy(buf, fname, len); // something failed; use the filename } @@ -1898,7 +1904,7 @@ void path_fix_case(char *name) } // Open the directory where the file is located. - char *slash = (char *)STRRCHR(name, '/'); + char *slash = strrchr(name, '/'); char *tail; Directory dir; bool ok; @@ -1939,21 +1945,17 @@ void path_fix_case(char *name) os_closedir(&dir); } -/* - * Return TRUE if "p" points to just after a path separator. - * Takes care of multi-byte characters. - * "b" must point to the start of the file name - */ +/// Return true if "p" points to just after a path separator. +/// Takes care of multi-byte characters. +/// "b" must point to the start of the file name int after_pathsep(const char *b, const char *p) { return p > b && vim_ispathsep(p[-1]) && utf_head_off((char_u *)b, (char_u *)p - 1) == 0; } -/* - * 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. - */ +/// 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) { char ffname[MAXPATHL]; @@ -1965,7 +1967,7 @@ bool same_directory(char_u *f1, char_u *f2) return false; } - (void)vim_FullName((char *)f1, (char *)ffname, MAXPATHL, FALSE); + (void)vim_FullName((char *)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 @@ -2133,10 +2135,11 @@ int expand_wildcards_eval(char_u **pat, int *num_file, char ***file, int flags) if (*exp_pat == '%' || *exp_pat == '#' || *exp_pat == '<') { emsg_off++; - eval_pat = eval_vars((char_u *)exp_pat, (char_u *)exp_pat, &usedlen, NULL, &ignored_msg, NULL); + eval_pat = eval_vars((char_u *)exp_pat, (char_u *)exp_pat, &usedlen, NULL, &ignored_msg, NULL, + true); emsg_off--; if (eval_pat != NULL) { - exp_pat = (char *)concat_str(eval_pat, (char_u *)exp_pat + usedlen); + exp_pat = concat_str((char *)eval_pat, exp_pat + usedlen); } } @@ -2192,7 +2195,7 @@ int expand_wildcards(int num_pat, char **pat, int *num_files, char ***files, int ffname = (char_u *)FullName_save((*files)[i], false); assert((*files)[i] != NULL); assert(ffname != NULL); - if (match_file_list(p_wig, (char_u *)(*files)[i], ffname)) { + if (match_file_list((char_u *)p_wig, (char_u *)(*files)[i], ffname)) { // remove this matching file from the list xfree((*files)[i]); for (j = i; j + 1 < *num_files; j++) { @@ -2205,17 +2208,14 @@ int expand_wildcards(int num_pat, char **pat, int *num_files, char ***files, int } } - // // Move the names where 'suffixes' match to the end. - // + // Skip when interrupted, the result probably won't be used. assert(*num_files == 0 || *files != NULL); - if (*num_files > 1) { + if (*num_files > 1 && !got_int) { non_suf_match = 0; for (i = 0; i < *num_files; i++) { if (!match_suffix((char_u *)(*files)[i])) { - // // Move the name without matching suffix to the front of the list. - // p = (char_u *)(*files)[i]; for (j = i; j > non_suf_match; j--) { (*files)[j] = (*files)[j - 1]; @@ -2234,9 +2234,7 @@ int expand_wildcards(int num_pat, char **pat, int *num_files, char ***files, int return retval; } -/* - * Return TRUE if "fname" matches with an entry in 'suffixes'. - */ +/// Return true if "fname" matches with an entry in 'suffixes'. int match_suffix(char_u *fname) { #define MAXSUFLEN 30 // maximum length of a file suffix @@ -2355,20 +2353,20 @@ int append_path(char *path, const char *to_append, size_t max_len) /// @param force also expand when "fname" is already absolute. /// /// @return FAIL for failure, OK for success. -static int path_to_absolute(const char_u *fname, char_u *buf, size_t len, int force) +static int path_to_absolute(const char *fname, char *buf, size_t len, int force) { - char_u *p; + char *p; *buf = NUL; char *relative_directory = xmalloc(len); char *end_of_path = (char *)fname; // expand it if forced or not an absolute path - if (force || !path_is_absolute(fname)) { - p = STRRCHR(fname, '/'); + if (force || !path_is_absolute((char_u *)fname)) { + p = strrchr(fname, '/'); #ifdef WIN32 if (p == NULL) { - p = STRRCHR(fname, '\\'); + p = strrchr(fname, '\\'); } #endif if (p != NULL) { @@ -2382,24 +2380,24 @@ static int path_to_absolute(const char_u *fname, char_u *buf, size_t len, int fo memcpy(relative_directory, fname, (size_t)(p - fname)); relative_directory[p - fname] = NUL; } - end_of_path = (char *)(p + 1); + end_of_path = p + 1; } else { relative_directory[0] = NUL; end_of_path = (char *)fname; } - if (FAIL == path_full_dir_name(relative_directory, (char *)buf, len)) { + if (FAIL == path_full_dir_name(relative_directory, buf, len)) { xfree(relative_directory); return FAIL; } } xfree(relative_directory); - return append_path((char *)buf, end_of_path, len); + return append_path(buf, end_of_path, len); } /// Check if file `fname` is a full (absolute) path. /// -/// @return `TRUE` if "fname" is absolute. +/// @return `true` if "fname" is absolute. int path_is_absolute(const char_u *fname) { #ifdef WIN32 diff --git a/src/nvim/plines.c b/src/nvim/plines.c index 70bdbd8b1d..cc730ba307 100644 --- a/src/nvim/plines.c +++ b/src/nvim/plines.c @@ -98,15 +98,15 @@ int plines_win_nofill(win_T *wp, linenr_T lnum, bool winheight) /// "wp". Does not care about folding, 'wrap' or 'diff'. int plines_win_nofold(win_T *wp, linenr_T lnum) { - char_u *s; + char *s; unsigned int col; int width; - s = ml_get_buf(wp->w_buffer, lnum, false); + s = (char *)ml_get_buf(wp->w_buffer, lnum, false); if (*s == NUL) { // empty line return 1; } - col = win_linetabsize(wp, s, MAXCOL); + col = win_linetabsize(wp, lnum, (char_u *)s, MAXCOL); // If list mode is on, then the '$' at the end of the line may take up one // extra column. @@ -145,23 +145,27 @@ int plines_win_col(win_T *wp, linenr_T lnum, long column) } char_u *line = ml_get_buf(wp->w_buffer, lnum, false); - char_u *s = line; colnr_T col = 0; - while (*s != NUL && --column >= 0) { - col += win_lbr_chartabsize(wp, line, s, col, NULL); - MB_PTR_ADV(s); + chartabsize_T cts; + + 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); } - // If *s is a TAB, and the TAB is not displayed as ^I, and we're not in - // MODE_INSERT state, then col must be adjusted so that it represents the + // If *cts.cts_ptr is a TAB, and the TAB is not displayed as ^I, and we're not + // in MODE_INSERT state, then col must be adjusted so that it represents the // last screen position of the TAB. This only fixes an error when the TAB // wraps from one screen line to the next (when 'columns' is not a multiple // of 'ts') -- webb. - if (*s == TAB && (State & MODE_NORMAL) + col = cts.cts_vcol; + if (*cts.cts_ptr == TAB && (State & MODE_NORMAL) && (!wp->w_p_list || wp->w_p_lcs_chars.tab1)) { - col += win_lbr_chartabsize(wp, line, s, col, NULL) - 1; + col += win_lbr_chartabsize(&cts, NULL) - 1; } + clear_chartabsize_arg(&cts); // Add column offset for 'number', 'relativenumber', 'foldcolumn', etc. int width = wp->w_width_inner - win_col_off(wp); @@ -223,13 +227,13 @@ int plines_m_win(win_T *wp, linenr_T first, linenr_T last) /// @param col /// /// @return Number of characters. -int win_chartabsize(win_T *wp, char_u *p, colnr_T col) +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((char *)p); + return ptr2cells(p); } } @@ -241,24 +245,24 @@ int win_chartabsize(win_T *wp, char_u *p, colnr_T col) /// @return Number of characters the string will take on the screen. int linetabsize(char_u *s) { - return linetabsize_col(0, s); + return linetabsize_col(0, (char *)s); } -/// Like linetabsize(), but starting at column "startcol". +/// Like linetabsize(), but "s" starts at column "startcol". /// /// @param startcol /// @param s /// /// @return Number of characters the string will take on the screen. -int linetabsize_col(int startcol, char_u *s) +int linetabsize_col(int startcol, char *s) { - colnr_T col = startcol; - char_u *line = s; // pointer to start of line, for breakindent - - while (*s != NUL) { - col += lbr_chartabsize_adv(line, &s, col); + chartabsize_T cts; + init_chartabsize_arg(&cts, curwin, 0, startcol, (char_u *)s, (char_u *)s); + while (*cts.cts_ptr != NUL) { + cts.cts_vcol += lbr_chartabsize_adv(&cts); } - return (int)col; + clear_chartabsize_arg(&cts); + return cts.cts_vcol; } /// Like linetabsize(), but for a given window instead of the current one. @@ -268,19 +272,39 @@ int linetabsize_col(int startcol, char_u *s) /// @param len /// /// @return Number of characters the string will take on the screen. -unsigned int win_linetabsize(win_T *wp, char_u *line, colnr_T len) +unsigned int win_linetabsize(win_T *wp, linenr_T lnum, char_u *line, colnr_T len) { - colnr_T col = 0; - - for (char_u *s = line; - *s != NUL && (len == MAXCOL || s < line + len); - MB_PTR_ADV(s)) { - col += win_lbr_chartabsize(wp, line, s, col, NULL); + chartabsize_T cts; + init_chartabsize_arg(&cts, wp, lnum, 0, line, line); + for (; *cts.cts_ptr != NUL && (len == MAXCOL || cts.cts_ptr < (char *)line + len); + MB_PTR_ADV(cts.cts_ptr)) { + cts.cts_vcol += win_lbr_chartabsize(&cts, NULL); } + clear_chartabsize_arg(&cts); + return (unsigned int)cts.cts_vcol; +} - return (unsigned int)col; +/// Prepare the structure passed to chartabsize functions. +/// +/// "line" is the start of the line, "ptr" is the first relevant character. +/// When "lnum" is zero do not use text properties that insert text. +void init_chartabsize_arg(chartabsize_T *cts, win_T *wp, linenr_T lnum, colnr_T col, char_u *line, + char_u *ptr) +{ + cts->cts_win = wp; + cts->cts_lnum = lnum; + cts->cts_vcol = col; + cts->cts_line = (char *)line; + cts->cts_ptr = (char *)ptr; + cts->cts_cur_text_width = 0; + // TODO(bfredl): actually lookup inline virtual text here + cts->cts_has_virt_text = false; } +/// Free any allocated item in "cts". +void clear_chartabsize_arg(chartabsize_T *cts) +{} + /// like win_chartabsize(), but also check for line breaks on the screen /// /// @param line @@ -288,16 +312,16 @@ unsigned int win_linetabsize(win_T *wp, char_u *line, colnr_T len) /// @param col /// /// @return The number of characters taken up on the screen. -int lbr_chartabsize(char_u *line, unsigned char *s, colnr_T col) +int lbr_chartabsize(chartabsize_T *cts) { if (!curwin->w_p_lbr && *get_showbreak_value(curwin) == NUL - && !curwin->w_p_bri) { + && !curwin->w_p_bri && !cts->cts_has_virt_text) { if (curwin->w_p_wrap) { - return win_nolbr_chartabsize(curwin, s, col, NULL); + return win_nolbr_chartabsize(cts, NULL); } - return win_chartabsize(curwin, s, col); + return win_chartabsize(curwin, cts->cts_ptr, cts->cts_vcol); } - return win_lbr_chartabsize(curwin, line == NULL ? s: line, s, col, NULL); + return win_lbr_chartabsize(cts, NULL); } /// Call lbr_chartabsize() and advance the pointer. @@ -307,12 +331,12 @@ int lbr_chartabsize(char_u *line, unsigned char *s, colnr_T col) /// @param col /// /// @return The number of characters take up on the screen. -int lbr_chartabsize_adv(char_u *line, char_u **s, colnr_T col) +int lbr_chartabsize_adv(chartabsize_T *cts) { int retval; - retval = lbr_chartabsize(line, *s, col); - MB_PTR_ADV(*s); + retval = lbr_chartabsize(cts); + MB_PTR_ADV(cts->cts_ptr); return retval; } @@ -322,17 +346,19 @@ int lbr_chartabsize_adv(char_u *line, char_u **s, colnr_T col) /// string at start of line. Warning: *headp is only set if it's a non-zero /// value, init to 0 before calling. /// -/// @param wp -/// @param line -/// @param s -/// @param col +/// @param cts /// @param headp /// /// @return The number of characters taken up on the screen. -int win_lbr_chartabsize(win_T *wp, char_u *line, char_u *s, colnr_T col, int *headp) +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; + colnr_T vcol = cts->cts_vcol; + colnr_T col2; - colnr_T col_adj = 0; // col + screen size of tab + colnr_T col_adj = 0; // vcol + screen size of tab colnr_T colmax; int added; int mb_added = 0; @@ -340,16 +366,23 @@ int win_lbr_chartabsize(win_T *wp, char_u *line, char_u *s, colnr_T col, int *he char_u *ps; int n; + cts->cts_cur_text_width = 0; + // No 'linebreak', 'showbreak' and 'breakindent': return quickly. - if (!wp->w_p_lbr && !wp->w_p_bri && *get_showbreak_value(wp) == NUL) { + if (!wp->w_p_lbr && !wp->w_p_bri && *get_showbreak_value(wp) == NUL + && !cts->cts_has_virt_text) { if (wp->w_p_wrap) { - return win_nolbr_chartabsize(wp, s, col, headp); + return win_nolbr_chartabsize(cts, headp); } - return win_chartabsize(wp, s, col); + return win_chartabsize(wp, (char *)s, vcol); + } + + // First get normal size, without 'linebreak' or virtual text + int size = win_chartabsize(wp, (char *)s, vcol); + if (cts->cts_has_virt_text) { + // TODO(bfredl): inline virtual text } - // First get normal size, without 'linebreak' - int size = win_chartabsize(wp, s, col); int c = *s; if (*s == TAB) { col_adj = size - 1; @@ -365,15 +398,15 @@ int win_lbr_chartabsize(win_T *wp, char_u *line, char_u *s, colnr_T col, int *he // Count all characters from first non-blank after a blank up to next // non-blank after a blank. numberextra = win_col_off(wp); - col2 = col; + col2 = vcol; colmax = (colnr_T)(wp->w_width_inner - numberextra - col_adj); - if (col >= colmax) { + if (vcol >= colmax) { colmax += col_adj; n = colmax + win_col_off2(wp); if (n > 0) { - colmax += (((col - colmax) / n) + 1) * n - col_adj; + colmax += (((vcol - colmax) / n) + 1) * n - col_adj; } } @@ -383,21 +416,21 @@ int win_lbr_chartabsize(win_T *wp, char_u *line, char_u *s, colnr_T col, int *he c = *s; if (!(c != NUL - && (vim_isbreak(c) || col2 == col || !vim_isbreak((int)(*ps))))) { + && (vim_isbreak(c) || col2 == vcol || !vim_isbreak((int)(*ps))))) { break; } - col2 += win_chartabsize(wp, s, col2); + col2 += win_chartabsize(wp, (char *)s, col2); if (col2 >= colmax) { // doesn't fit - size = colmax - col + col_adj; + size = colmax - vcol + col_adj; break; } } } else if ((size == 2) && (MB_BYTE2LEN(*s) > 1) && wp->w_p_wrap - && in_win_border(wp, col)) { + && in_win_border(wp, vcol)) { // Count the ">" in the last column. size++; mb_added = 1; @@ -409,40 +442,40 @@ int win_lbr_chartabsize(win_T *wp, char_u *line, char_u *s, colnr_T col, int *he added = 0; char *const sbr = (char *)get_showbreak_value(wp); - if ((*sbr != NUL || wp->w_p_bri) && wp->w_p_wrap && col != 0) { + if ((*sbr != NUL || wp->w_p_bri) && wp->w_p_wrap && vcol != 0) { colnr_T sbrlen = 0; int numberwidth = win_col_off(wp); numberextra = numberwidth; - col += numberextra + mb_added; + vcol += numberextra + mb_added; - if (col >= (colnr_T)wp->w_width_inner) { - col -= wp->w_width_inner; + if (vcol >= (colnr_T)wp->w_width_inner) { + vcol -= wp->w_width_inner; numberextra = wp->w_width_inner - (numberextra - win_col_off2(wp)); - if (col >= numberextra && numberextra > 0) { - col %= numberextra; + if (vcol >= numberextra && numberextra > 0) { + vcol %= numberextra; } if (*sbr != NUL) { sbrlen = (colnr_T)mb_charlen((char_u *)sbr); - if (col >= sbrlen) { - col -= sbrlen; + if (vcol >= sbrlen) { + vcol -= sbrlen; } } - if (col >= numberextra && numberextra > 0) { - col %= numberextra; - } else if (col > 0 && numberextra > 0) { - col += numberwidth - win_col_off2(wp); + if (vcol >= numberextra && numberextra > 0) { + vcol %= numberextra; + } else if (vcol > 0 && numberextra > 0) { + vcol += numberwidth - win_col_off2(wp); } numberwidth -= win_col_off2(wp); } - if (col == 0 || (col + size + sbrlen > (colnr_T)wp->w_width_inner)) { + if (vcol == 0 || (vcol + size + sbrlen > (colnr_T)wp->w_width_inner)) { if (*sbr != NUL) { if (size + sbrlen + numberwidth > (colnr_T)wp->w_width_inner) { // Calculate effective window width. int width = (colnr_T)wp->w_width_inner - sbrlen - numberwidth; - int prev_width = col ? ((colnr_T)wp->w_width_inner - (sbrlen + col)) + int prev_width = vcol ? ((colnr_T)wp->w_width_inner - (sbrlen + vcol)) : 0; if (width <= 0) { @@ -459,11 +492,11 @@ int win_lbr_chartabsize(win_T *wp, char_u *line, char_u *s, colnr_T col, int *he } if (wp->w_p_bri) { - added += get_breakindent_win(wp, line); + added += get_breakindent_win(wp, (char_u *)line); } size += added; - if (col != 0) { + if (vcol != 0) { added = 0; } } @@ -485,8 +518,11 @@ int win_lbr_chartabsize(win_T *wp, char_u *line, char_u *s, colnr_T col, int *he /// @param headp /// /// @return The number of characters take up on the screen. -static int win_nolbr_chartabsize(win_T *wp, char_u *s, colnr_T col, int *headp) +static int win_nolbr_chartabsize(chartabsize_T *cts, int *headp) { + win_T *wp = cts->cts_win; + char *s = cts->cts_ptr; + colnr_T col = cts->cts_vcol; int n; if ((*s == TAB) && (!wp->w_p_list || wp->w_p_lcs_chars.tab1)) { @@ -494,11 +530,11 @@ static int win_nolbr_chartabsize(win_T *wp, char_u *s, colnr_T col, int *headp) wp->w_buffer->b_p_ts, wp->w_buffer->b_p_vts_array); } - n = ptr2cells((char *)s); + n = ptr2cells(s); // Add one cell for a double-width character in the last column of the // window, displayed with a ">". - if ((n == 2) && (MB_BYTE2LEN(*s) > 1) && in_win_border(wp, col)) { + if ((n == 2) && (MB_BYTE2LEN((uint8_t)(*s)) > 1) && in_win_border(wp, col)) { if (headp != NULL) { *headp = 1; } diff --git a/src/nvim/plines.h b/src/nvim/plines.h index 32778b69f1..7b228f3e91 100644 --- a/src/nvim/plines.h +++ b/src/nvim/plines.h @@ -3,6 +3,20 @@ #include "nvim/vim.h" +// Argument for lbr_chartabsize(). +typedef struct { + win_T *cts_win; + linenr_T cts_lnum; // zero when not using text properties + char *cts_line; // start of the line + char *cts_ptr; // current position in line + + bool cts_has_virt_text; // true if if a property inserts text + int cts_cur_text_width; // width of current inserted text + // TODO(bfredl): iterator in to the marktree for scanning virt text + + int cts_vcol; // virtual column at current position +} chartabsize_T; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "plines.h.generated.h" #endif diff --git a/src/nvim/po/af.po b/src/nvim/po/af.po index 82345f8a46..d64789660e 100644 --- a/src/nvim/po/af.po +++ b/src/nvim/po/af.po @@ -5320,8 +5320,8 @@ msgstr "E424: Te veel verskillende uitlig-eienskappe in gebruik" msgid "E669: Unprintable character in group name" msgstr "E669: Onvertoonbare karakter in groepnaam" -msgid "W18: Invalid character in group name" -msgstr "W18: Ongeldige karakter groepnaam" +msgid "E5248: Invalid character in group name" +msgstr "E5248: Ongeldige karakter groepnaam" #~ msgid "E849: Too many highlight and syntax groups" #~ msgstr "" diff --git a/src/nvim/po/ca.po b/src/nvim/po/ca.po index 6c4d6ddd22..5869e6567c 100644 --- a/src/nvim/po/ca.po +++ b/src/nvim/po/ca.po @@ -5983,9 +5983,9 @@ msgstr "E424: Hi ha massa atributs de ressalt diferents en s" msgid "E669: Unprintable character in group name" msgstr "E669: Carcter no imprimible en el nom del grup" -#: ../syntax.c:7434 -msgid "W18: Invalid character in group name" -msgstr "W18: Hi ha un carcter no vlid en el nom del grup" +#: ../highlight_group.c:1756 +msgid "E5248: Invalid character in group name" +msgstr "E5248: Hi ha un carcter no vlid en el nom del grup" #: ../syntax.c:7448 msgid "E849: Too many highlight and syntax groups" diff --git a/src/nvim/po/cs.cp1250.po b/src/nvim/po/cs.cp1250.po index 859039eb87..bce5c0fa76 100644 --- a/src/nvim/po/cs.cp1250.po +++ b/src/nvim/po/cs.cp1250.po @@ -6072,10 +6072,10 @@ msgstr "" msgid "E669: Unprintable character in group name" msgstr "" -#: ../syntax.c:7434 +#: ../highlight_group.c:1756 #, fuzzy -msgid "W18: Invalid character in group name" -msgstr "E182: Chybn jmno pkazu" +msgid "E5248: Invalid character in group name" +msgstr "E5248: Chybn jmno pkazu" #: ../syntax.c:7448 msgid "E849: Too many highlight and syntax groups" diff --git a/src/nvim/po/cs.po b/src/nvim/po/cs.po index 4d9ad58836..f4eab3f0d4 100644 --- a/src/nvim/po/cs.po +++ b/src/nvim/po/cs.po @@ -6072,10 +6072,10 @@ msgstr "" msgid "E669: Unprintable character in group name" msgstr "" -#: ../syntax.c:7434 +#: ../highlight_group.c:1756 #, fuzzy -msgid "W18: Invalid character in group name" -msgstr "E182: Chybn jmno pkazu" +msgid "E5248: Invalid character in group name" +msgstr "E5248: Chybn jmno pkazu" #: ../syntax.c:7448 msgid "E849: Too many highlight and syntax groups" diff --git a/src/nvim/po/da.po b/src/nvim/po/da.po index dfcc052288..1c3284f867 100644 --- a/src/nvim/po/da.po +++ b/src/nvim/po/da.po @@ -5599,8 +5599,8 @@ msgstr "E424: For mange forskellige fremhævningsattributter i brug" msgid "E669: Unprintable character in group name" msgstr "E669: Tegn som ikke kan udskrives i gruppenavn" -msgid "W18: Invalid character in group name" -msgstr "W18: Ugyldige tegn i gruppenavn" +msgid "E5248: Invalid character in group name" +msgstr "E5248: Ugyldige tegn i gruppenavn" msgid "E849: Too many highlight and syntax groups" msgstr "E849: For mange fremhævnings- og syntaksgrupper" diff --git a/src/nvim/po/de.po b/src/nvim/po/de.po index 2dde77e9f7..ce3fd77ede 100644 --- a/src/nvim/po/de.po +++ b/src/nvim/po/de.po @@ -5408,8 +5408,8 @@ msgid "E669: Unprintable character in group name" msgstr "E669: Nicht druckbare Zeichen im Namen der Gruppe" #: ../syntax.c:7304 -msgid "W18: Invalid character in group name" -msgstr "W18: Ungltiges Zeichen im Namen der Gruppe" +msgid "E5248: Invalid character in group name" +msgstr "E5248: Ungltiges Zeichen im Namen der Gruppe" #: ../syntax.c:7318 msgid "E849: Too many highlight and syntax groups" diff --git a/src/nvim/po/en_GB.po b/src/nvim/po/en_GB.po index 66cdba6f92..81ee9ed6a0 100644 --- a/src/nvim/po/en_GB.po +++ b/src/nvim/po/en_GB.po @@ -5730,8 +5730,8 @@ msgstr "" msgid "E669: Unprintable character in group name" msgstr "" -#: ../syntax.c:7434 -msgid "W18: Invalid character in group name" +#: ../highlight_group.c:1756 +msgid "E5248: Invalid character in group name" msgstr "" #: ../syntax.c:7448 diff --git a/src/nvim/po/eo.po b/src/nvim/po/eo.po index 1c503d0a84..263fb61b18 100644 --- a/src/nvim/po/eo.po +++ b/src/nvim/po/eo.po @@ -5378,8 +5378,8 @@ msgstr "E424: Tro da malsamaj atributoj de emfazo uzataj" msgid "E669: Unprintable character in group name" msgstr "E669: Nepresebla signo en nomo de grupo" -msgid "W18: Invalid character in group name" -msgstr "W18: Nevalida signo en nomo de grupo" +msgid "E5248: Invalid character in group name" +msgstr "E5248: Nevalida signo en nomo de grupo" msgid "E849: Too many highlight and syntax groups" msgstr "E849: Tro da emfazaj kaj sintaksaj grupoj" diff --git a/src/nvim/po/es.po b/src/nvim/po/es.po index adea651b7c..8a44f6a534 100644 --- a/src/nvim/po/es.po +++ b/src/nvim/po/es.po @@ -6053,9 +6053,9 @@ msgstr "E669: Carácter no imprimible en el nombre del grupo" # This is an error, but since there previously was no check only # * give a warning. -#: ../syntax.c:7434 -msgid "W18: Invalid character in group name" -msgstr "W18: Hay un carácter no válido en el nombre del grupo" +#: ../highlight_group.c:1756 +msgid "E5248: Invalid character in group name" +msgstr "E5248: Hay un carácter no válido en el nombre del grupo" #: ../syntax.c:7448 msgid "E849: Too many highlight and syntax groups" diff --git a/src/nvim/po/fi.po b/src/nvim/po/fi.po index f10d4ce977..1c0da244ba 100644 --- a/src/nvim/po/fi.po +++ b/src/nvim/po/fi.po @@ -5369,8 +5369,8 @@ msgstr "E424: Liikaa eri korostusattribuutteja" msgid "E669: Unprintable character in group name" msgstr "E669: Tulostuskelvoton merkki ryhmän nimessä" -msgid "W18: Invalid character in group name" -msgstr "W18: Virheellinen merkki ryhmän nimessä" +msgid "E5248: Invalid character in group name" +msgstr "E5248: Virheellinen merkki ryhmän nimessä" msgid "E849: Too many highlight and syntax groups" msgstr "E849: Liikaa korostuksia ja syntaksiryhmiä" diff --git a/src/nvim/po/fr.po b/src/nvim/po/fr.po index 614ba013e6..be2141cd6d 100644 --- a/src/nvim/po/fr.po +++ b/src/nvim/po/fr.po @@ -2040,8 +2040,8 @@ msgstr "" msgid "E669: Unprintable character in group name" msgstr "E669: Caractre non imprimable dans un nom de groupe" -msgid "W18: Invalid character in group name" -msgstr "W18: Caractre invalide dans un nom de groupe" +msgid "E5248: Invalid character in group name" +msgstr "E5248: Caractre invalide dans un nom de groupe" msgid "E849: Too many highlight and syntax groups" msgstr "E849: Trop de groupes de surbrillance et de syntaxe" diff --git a/src/nvim/po/ga.po b/src/nvim/po/ga.po index 1c25ee481c..346f0faa84 100644 --- a/src/nvim/po/ga.po +++ b/src/nvim/po/ga.po @@ -5668,8 +5668,8 @@ msgstr "E424: An iomarca trithe aibhsithe in sid" msgid "E669: Unprintable character in group name" msgstr "E669: Carachtar neamhghrafach in ainm grpa" -msgid "W18: Invalid character in group name" -msgstr "W18: Carachtar neamhbhail in ainm grpa" +msgid "E5248: Invalid character in group name" +msgstr "E5248: Carachtar neamhbhail in ainm grpa" msgid "E849: Too many highlight and syntax groups" msgstr "E849: An iomarca grpa aibhsithe agus comhrire" diff --git a/src/nvim/po/it.po b/src/nvim/po/it.po index 313280c807..152ed2cbe3 100644 --- a/src/nvim/po/it.po +++ b/src/nvim/po/it.po @@ -6046,9 +6046,9 @@ msgstr "E424: Troppi gruppi evidenziazione differenti in uso" msgid "E669: Unprintable character in group name" msgstr "E669: Carattere non stampabile in un nome di gruppo" -#: ../syntax.c:7434 -msgid "W18: Invalid character in group name" -msgstr "W18: Carattere non ammesso in un nome di gruppo" +#: ../highlight_group.c:1756 +msgid "E5248: Invalid character in group name" +msgstr "E5248: Carattere non ammesso in un nome di gruppo" #: ../syntax.c:7448 msgid "E849: Too many highlight and syntax groups" diff --git a/src/nvim/po/ko.UTF-8.po b/src/nvim/po/ko.UTF-8.po index b99c22caeb..09be710374 100644 --- a/src/nvim/po/ko.UTF-8.po +++ b/src/nvim/po/ko.UTF-8.po @@ -5913,9 +5913,9 @@ msgstr "E424: 너무 많은 다른 하이라이트 속성이 사용되고 있습 msgid "E669: Unprintable character in group name" msgstr "E669: 그룹 이름에 출력할 수 없는 문자가 있습니다" -#: ../syntax.c:7434 -msgid "W18: Invalid character in group name" -msgstr "W18: 그룹 이름에 이상한 문자" +#: ../highlight_group.c:1756 +msgid "E5248: Invalid character in group name" +msgstr "E5248: 그룹 이름에 이상한 문자" #: ../syntax.c:7448 msgid "E849: Too many highlight and syntax groups" diff --git a/src/nvim/po/nb.po b/src/nvim/po/nb.po index 2285d755cf..9bc730ae71 100644 --- a/src/nvim/po/nb.po +++ b/src/nvim/po/nb.po @@ -5930,9 +5930,9 @@ msgstr "E424: For mange forskjellige uthevingsattributter i bruk" msgid "E669: Unprintable character in group name" msgstr "E669: Ikke-skrivbart tegn i gruppenavn" -#: ../syntax.c:7434 -msgid "W18: Invalid character in group name" -msgstr "W18: Ugyldig tegn i gruppenavn" +#: ../highlight_group.c:1756 +msgid "E5248: Invalid character in group name" +msgstr "E5248: Ugyldig tegn i gruppenavn" #: ../syntax.c:7448 msgid "E849: Too many highlight and syntax groups" diff --git a/src/nvim/po/nl.po b/src/nvim/po/nl.po index 00d113c83c..4d2e55adc6 100644 --- a/src/nvim/po/nl.po +++ b/src/nvim/po/nl.po @@ -5913,8 +5913,8 @@ msgstr "" msgid "E669: Unprintable character in group name" msgstr "" -#: ../syntax.c:7434 -msgid "W18: Invalid character in group name" +#: ../highlight_group.c:1756 +msgid "E5248: Invalid character in group name" msgstr "" #: ../syntax.c:7448 diff --git a/src/nvim/po/no.po b/src/nvim/po/no.po index 2285d755cf..9bc730ae71 100644 --- a/src/nvim/po/no.po +++ b/src/nvim/po/no.po @@ -5930,9 +5930,9 @@ msgstr "E424: For mange forskjellige uthevingsattributter i bruk" msgid "E669: Unprintable character in group name" msgstr "E669: Ikke-skrivbart tegn i gruppenavn" -#: ../syntax.c:7434 -msgid "W18: Invalid character in group name" -msgstr "W18: Ugyldig tegn i gruppenavn" +#: ../highlight_group.c:1756 +msgid "E5248: Invalid character in group name" +msgstr "E5248: Ugyldig tegn i gruppenavn" #: ../syntax.c:7448 msgid "E849: Too many highlight and syntax groups" diff --git a/src/nvim/po/pl.UTF-8.po b/src/nvim/po/pl.UTF-8.po index 5f1779d1bd..7e978113f6 100644 --- a/src/nvim/po/pl.UTF-8.po +++ b/src/nvim/po/pl.UTF-8.po @@ -5908,9 +5908,9 @@ msgstr "E424: Zbyt wiele różnych atrybutów podkreślania w użyciu" msgid "E669: Unprintable character in group name" msgstr "E669: Niedrukowalny znak w nazwie grupy" -#: ../syntax.c:7434 -msgid "W18: Invalid character in group name" -msgstr "W18: nieprawidłowy znak w nazwie grupy" +#: ../highlight_group.c:1756 +msgid "E5248: Invalid character in group name" +msgstr "E5248: nieprawidłowy znak w nazwie grupy" #: ../syntax.c:7448 msgid "E849: Too many highlight and syntax groups" diff --git a/src/nvim/po/pt_BR.po b/src/nvim/po/pt_BR.po index 533d916de1..bfd64b4d28 100644 --- a/src/nvim/po/pt_BR.po +++ b/src/nvim/po/pt_BR.po @@ -6070,8 +6070,8 @@ msgid "E669: Unprintable character in group name" msgstr "E669: Caractere no-imprimvel no nome do grupo" #: ../syntax.c:7320 -msgid "W18: Invalid character in group name" -msgstr "W18: Caractere invlido no nome do grupo" +msgid "E5248: Invalid character in group name" +msgstr "E5248: Caractere invlido no nome do grupo" #: ../syntax.c:7334 msgid "E849: Too many highlight and syntax groups" diff --git a/src/nvim/po/ru.po b/src/nvim/po/ru.po index 7566036d3e..da356770d7 100644 --- a/src/nvim/po/ru.po +++ b/src/nvim/po/ru.po @@ -5977,9 +5977,9 @@ msgstr "E424: Используется слишком много разных а msgid "E669: Unprintable character in group name" msgstr "E669: Непечатный символ в имени группы" -#: ../syntax.c:7434 -msgid "W18: Invalid character in group name" -msgstr "W18: Недопустимый символ в имени группы" +#: ../highlight_group.c:1756 +msgid "E5248: Invalid character in group name" +msgstr "E5248: Недопустимый символ в имени группы" #: ../syntax.c:7448 msgid "E849: Too many highlight and syntax groups" diff --git a/src/nvim/po/sk.cp1250.po b/src/nvim/po/sk.cp1250.po index ff95c68a12..cb8b8c6abb 100644 --- a/src/nvim/po/sk.cp1250.po +++ b/src/nvim/po/sk.cp1250.po @@ -5940,9 +5940,9 @@ msgstr "E424: Pouvanch prli vea odlinch zvrazovacch vlastnost" msgid "E669: Unprintable character in group name" msgstr "E669: Netlaiteln znak v mene skupiny" -#: ../syntax.c:7434 -msgid "W18: Invalid character in group name" -msgstr "W18: Chybn znak v mene skupiny" +#: ../highlight_group.c:1756 +msgid "E5248: Invalid character in group name" +msgstr "E5248: Chybn znak v mene skupiny" #: ../syntax.c:7448 msgid "E849: Too many highlight and syntax groups" diff --git a/src/nvim/po/sk.po b/src/nvim/po/sk.po index d35622726f..4f9e1fe185 100644 --- a/src/nvim/po/sk.po +++ b/src/nvim/po/sk.po @@ -5940,9 +5940,9 @@ msgstr "E424: Pouvanch prli vea odlinch zvrazovacch vlastnost" msgid "E669: Unprintable character in group name" msgstr "E669: Netlaiteln znak v mene skupiny" -#: ../syntax.c:7434 -msgid "W18: Invalid character in group name" -msgstr "W18: Chybn znak v mene skupiny" +#: ../highlight_group.c:1756 +msgid "E5248: Invalid character in group name" +msgstr "E5248: Chybn znak v mene skupiny" #: ../syntax.c:7448 msgid "E849: Too many highlight and syntax groups" diff --git a/src/nvim/po/sr.po b/src/nvim/po/sr.po index 3c45e1bf80..cbdf736d0f 100644 --- a/src/nvim/po/sr.po +++ b/src/nvim/po/sr.po @@ -6050,8 +6050,8 @@ msgstr "E424: У употреби је превише различитих ат msgid "E669: Unprintable character in group name" msgstr "E669: У имену групе је карактер који не може да се штампа" -msgid "W18: Invalid character in group name" -msgstr "W18: Неважећи карактер у имену групе" +msgid "E5248: Invalid character in group name" +msgstr "E5248: Неважећи карактер у имену групе" msgid "E849: Too many highlight and syntax groups" msgstr "E849: Превише синтаксних и група истицања" diff --git a/src/nvim/po/sv.po b/src/nvim/po/sv.po index d50c9d695d..406900f7b2 100644 --- a/src/nvim/po/sv.po +++ b/src/nvim/po/sv.po @@ -2056,8 +2056,8 @@ msgid "E669: Unprintable character in group name" msgstr "E669: Outskrivbart tecken i gruppnamn" #: ../syntax.c:7292 -msgid "W18: Invalid character in group name" -msgstr "W18: Ogiltigt tecken i gruppnamn" +msgid "E5248: Invalid character in group name" +msgstr "E5248: Ogiltigt tecken i gruppnamn" #: ../syntax.c:7306 msgid "E849: Too many highlight and syntax groups" diff --git a/src/nvim/po/tr.po b/src/nvim/po/tr.po index fae2fd4967..20b667d198 100644 --- a/src/nvim/po/tr.po +++ b/src/nvim/po/tr.po @@ -3054,8 +3054,8 @@ msgstr "E423: İzin verilmeyen argüman: %s" msgid "E669: Unprintable character in group name" msgstr "E669: Grup adında yazdırılamayan karakter" -msgid "W18: Invalid character in group name" -msgstr "W18: Grup adında geçersiz karakter" +msgid "E5248: Invalid character in group name" +msgstr "E5248: Grup adında geçersiz karakter" msgid "E849: Too many highlight and syntax groups" msgstr "E849: Çok fazla vurgulama ve sözdizim grupları" diff --git a/src/nvim/po/uk.po b/src/nvim/po/uk.po index da87d50683..427abd9b77 100644 --- a/src/nvim/po/uk.po +++ b/src/nvim/po/uk.po @@ -3058,8 +3058,8 @@ msgstr "E423: Неправильний аргумент: %s" msgid "E669: Unprintable character in group name" msgstr "E669: Недруковний символ у назві групи" -msgid "W18: Invalid character in group name" -msgstr "W18: Некоректний символ у назві групи" +msgid "E5248: Invalid character in group name" +msgstr "E5248: Некоректний символ у назві групи" msgid "E849: Too many highlight and syntax groups" msgstr "E849: Забагато груп підсвічування і синтаксису" diff --git a/src/nvim/po/vi.po b/src/nvim/po/vi.po index c693f910d8..ad59718a30 100644 --- a/src/nvim/po/vi.po +++ b/src/nvim/po/vi.po @@ -5975,9 +5975,9 @@ msgstr "E424: Sử dụng quá nhiều thuộc tính chiếu sáng cú pháp" msgid "E669: Unprintable character in group name" msgstr "E669: Ký tự không thể tin ra trong tên nhóm" -#: ../syntax.c:7434 -msgid "W18: Invalid character in group name" -msgstr "W18: Ký tự không cho phép trong tên nhóm" +#: ../highlight_group.c:1756 +msgid "E5248: Invalid character in group name" +msgstr "E5248: Ký tự không cho phép trong tên nhóm" #: ../syntax.c:7448 msgid "E849: Too many highlight and syntax groups" diff --git a/src/nvim/po/zh_CN.UTF-8.po b/src/nvim/po/zh_CN.UTF-8.po index 70c1389d7f..afa2f29029 100644 --- a/src/nvim/po/zh_CN.UTF-8.po +++ b/src/nvim/po/zh_CN.UTF-8.po @@ -734,9 +734,9 @@ msgid "E120: Using <SID> not in a script context: %s" msgstr "E120: <SID> 不能在 script 上下文外使用: %s" #: ../eval.c:7391 -#, fuzzy, c-format +#, c-format msgid "E725: Calling dict function without Dictionary: %s" -msgstr "E720: Dictionary 中缺少冒号: %s" +msgstr "E725: 调用字典函数但是没有字典:%s" #: ../eval.c:7453 #, fuzzy @@ -766,19 +766,16 @@ msgid "E737: Key already exists: %s" msgstr "E737: 键已存在: %s" #: ../eval.c:8692 -#, fuzzy msgid "extend() argument" -msgstr "--cmd 参数" +msgstr "extend() 参数" #: ../eval.c:8915 -#, fuzzy msgid "map() argument" -msgstr "-c 参数" +msgstr "map() 参数" #: ../eval.c:8916 -#, fuzzy msgid "filter() argument" -msgstr "-c 参数" +msgstr "filter() 参数" #: ../eval.c:9229 #, c-format @@ -849,9 +846,8 @@ msgid "E702: Sort compare function failed" msgstr "E702: Sort 比较函数失败" #: ../eval.c:13806 -#, fuzzy msgid "E882: Uniq compare function failed" -msgstr "E702: Sort 比较函数失败" +msgstr "E882: Uniq 比较函数失败" #: ../eval.c:14085 msgid "(Invalid)" @@ -864,31 +860,31 @@ msgstr "E677: 写临时文件出错" #: ../eval.c:16159 #, fuzzy msgid "E805: Using a Float as a Number" -msgstr "E745: 将 List 作数字使用" +msgstr "E805: 将浮点数当做数字使用" #: ../eval.c:16162 msgid "E703: Using a Funcref as a Number" -msgstr "E703: 将 Funcref 作数字使用" +msgstr "E703: 将函数当做数字使用" #: ../eval.c:16170 msgid "E745: Using a List as a Number" -msgstr "E745: 将 List 作数字使用" +msgstr "E745: 将列表当做数字使用" #: ../eval.c:16173 msgid "E728: Using a Dictionary as a Number" -msgstr "E728: 将 Dictionary 作数字使用" +msgstr "E728: 将字典当做数字使用" #: ../eval.c:16259 msgid "E729: using Funcref as a String" -msgstr "E729: 将 Funcref 作 String 使用" +msgstr "E729: 将函数当做字符串使用" #: ../eval.c:16262 msgid "E730: using List as a String" -msgstr "E730: 将 List 作 String 使用" +msgstr "E730: 将列表当做字符串使用" #: ../eval.c:16265 msgid "E731: using Dictionary as a String" -msgstr "E731: 将 Dictionary 作 String 使用" +msgstr "E731: 将字典当做字符串使用" #: ../eval.c:16619 #, c-format @@ -3053,11 +3049,11 @@ msgstr "E673: 不兼容的多字节编码和字符集。" #: ../hardcopy.c:2238 msgid "E674: printmbcharset cannot be empty with multi-byte encoding." -msgstr "E674: printmbcharset 在多字节编码下不能为空。" +msgstr "E674: printmbcharset 在多字节编码下不能为空" #: ../hardcopy.c:2254 msgid "E675: No default font specified for multi-byte printing." -msgstr "E675: 没有指定多字节打印的默认字体。" +msgstr "E675: 没有指定多字节打印的默认字体" #: ../hardcopy.c:2426 msgid "E324: Can't open PostScript output file" @@ -4204,9 +4200,8 @@ msgstr "E329: 没有菜单 \"%s\"" #. Only a mnemonic or accelerator is not valid. #: ../menu.c:329 -#, fuzzy msgid "E792: Empty menu name" -msgstr "E749: 空的缓冲区" +msgstr "E792: 空的菜单名称" #: ../menu.c:340 msgid "E330: Menu path must not lead to a sub-menu" @@ -4329,9 +4324,8 @@ msgid "E766: Insufficient arguments for printf()" msgstr "E766: printf() 的参数不足" #: ../message.c:3119 -#, fuzzy msgid "E807: Expected Float argument for printf()" -msgstr "E766: printf() 的参数不足" +msgstr "E807: 期盼浮点数作为printf()参数" #: ../message.c:3873 msgid "E767: Too many arguments to printf()" @@ -5675,9 +5669,9 @@ msgid "E781: .sug file doesn't match .spl file: %s" msgstr "E781: .sug 文件不能匹配 .spl 文件: %s" #: ../spell.c:9305 -#, fuzzy, c-format +#, c-format msgid "E782: error while reading .sug file: %s" -msgstr "E47: 读取错误文件失败" +msgstr "E782: 当读取.sug 文件时错误" #. This should have been checked when generating the .spl #. file. @@ -5867,6 +5861,7 @@ msgstr "E410: 不正确的 :syntax 子命令: %s" msgid "" " TOTAL COUNT MATCH SLOWEST AVERAGE NAME PATTERN" msgstr "" +" 总 计 计 数 匹 配 最 慢 的 平 均 名 字 模 式" #: ../syntax.c:6146 msgid "E679: recursive loop loading syncolor.vim" @@ -5942,9 +5937,9 @@ msgstr "E424: 使用了太多不同的高亮度属性" msgid "E669: Unprintable character in group name" msgstr "E669: 组名中存在不可显示字符" -#: ../syntax.c:7434 -msgid "W18: Invalid character in group name" -msgstr "W18: 组名中含有无效字符" +#: ../highlight_group.c:1756 +msgid "E5248: Invalid character in group name" +msgstr "E5248: 组名中含有无效字符" #: ../syntax.c:7448 msgid "E849: Too many highlight and syntax groups" @@ -6103,14 +6098,13 @@ msgstr "Vim: 读错误,退出中...\n" #. This happens when the FileChangedRO autocommand changes the #. * file in a way it becomes shorter. #: ../undo.c:379 -#, fuzzy msgid "E881: Line count changed unexpectedly" -msgstr "E787: 意外地改变了缓冲区" +msgstr "E881: 行数意外地改变了" #: ../undo.c:627 -#, fuzzy, c-format +#, c-format msgid "E828: Cannot open undo file for writing: %s" -msgstr "E212: 无法打开并写入文件" +msgstr "E828: 无法打开撤销文件去写入" #: ../undo.c:717 #, c-format diff --git a/src/nvim/po/zh_TW.UTF-8.po b/src/nvim/po/zh_TW.UTF-8.po index e2fb2d39d4..e95b1e2cad 100644 --- a/src/nvim/po/zh_TW.UTF-8.po +++ b/src/nvim/po/zh_TW.UTF-8.po @@ -59,19 +59,19 @@ msgstr "無法傳送回應訊息" #: ../api/private/helpers.c:204 msgid "internal error: unknown option type" -msgstr "" +msgstr "內部錯誤: 未知的選項類型" #: ../buffer.c:92 msgid "[Location List]" -msgstr "" +msgstr "[Location 列表]" #: ../buffer.c:93 msgid "[Quickfix List]" -msgstr "" +msgstr "[Quickfix 列表]" #: ../buffer.c:94 msgid "E855: Autocommands caused command to abort" -msgstr "" +msgstr "E855: 自動命令導致命令被停止" #: ../buffer.c:135 msgid "E82: Cannot allocate any buffer, exiting..." @@ -349,7 +349,7 @@ msgstr "E103: 緩衝區 \"%s\" 不是在 diff 模式" #: ../diff.c:2193 msgid "E787: Buffer changed unexpectedly" -msgstr "" +msgstr "E787: 意外地改變了緩衝區" #: ../digraph.c:1598 msgid "E104: Escape not allowed in digraph" @@ -365,7 +365,7 @@ msgstr "E105: 使用 :loadkeymap " #: ../digraph.c:1821 msgid "E791: Empty keymap entry" -msgstr "" +msgstr "E791: 空的鍵位映射項" #: ../edit.c:82 msgid " Keyword completion (^N^P)" @@ -434,11 +434,11 @@ msgstr "已到段落結尾" #: ../edit.c:101 msgid "E839: Completion function changed window" -msgstr "" +msgstr "E839: 補全函式更改了窗口" #: ../edit.c:102 msgid "E840: Completion function deleted text" -msgstr "" +msgstr "E840: 補全函式刪除了文本" #: ../edit.c:1847 msgid "'dictionary' option is empty" @@ -556,7 +556,7 @@ msgstr "E118: 函式 %s 的引數過多" #: ../eval.c:148 #, c-format msgid "E716: Key not present in Dictionary: %s" -msgstr "" +msgstr "E716: 鍵在字典中不存在: %s" #: ../eval.c:150 #, c-format @@ -581,7 +581,7 @@ msgstr "E360: 不能用 -f 選項執行 shell" #: ../eval.c:154 #, c-format msgid "E734: Wrong variable type for %s=" -msgstr "" +msgstr "E734: 錯誤的變數類型: %s=" #: ../eval.c:155 #, fuzzy, c-format @@ -595,19 +595,19 @@ msgstr "E461: 不合法的變數名稱: %s" #: ../eval.c:157 msgid "E806: using Float as a String" -msgstr "" +msgstr "E806: 使用浮點數作為字串" #: ../eval.c:1830 msgid "E687: Less targets than List items" -msgstr "" +msgstr "E687: 目標比列表項數少" #: ../eval.c:1834 msgid "E688: More targets than List items" -msgstr "" +msgstr "E688: 目標比列表項數多" #: ../eval.c:1906 msgid "Double ; in list of variables" -msgstr "" +msgstr "變數列表出現兩個 ;" #: ../eval.c:2078 #, fuzzy, c-format @@ -616,23 +616,23 @@ msgstr "E138: 無法寫入 viminfo 檔案 %s !" #: ../eval.c:2391 msgid "E689: Can only index a List or Dictionary" -msgstr "" +msgstr "E689: 只能索引一個列表或者字典" #: ../eval.c:2396 msgid "E708: [:] must come last" -msgstr "" +msgstr "E708: [:] 必須在最後" #: ../eval.c:2439 msgid "E709: [:] requires a List value" -msgstr "" +msgstr "E709: [:] 需要一個列表值" #: ../eval.c:2674 msgid "E710: List value has more items than target" -msgstr "" +msgstr "E710: 列表值的項比目標多" #: ../eval.c:2678 msgid "E711: List value has not enough items" -msgstr "" +msgstr "E711: 列表值沒有足夠多的項" #: ../eval.c:2867 #, fuzzy @@ -651,7 +651,7 @@ msgstr "E108: 無此變數: \"%s\"" #: ../eval.c:3333 msgid "E743: variable nested too deep for (un)lock" -msgstr "" +msgstr "E743: (un)lock 的變數嵌套過深" #: ../eval.c:3630 msgid "E109: Missing ':' after '?'" @@ -659,7 +659,7 @@ msgstr "E109: '?' 後缺少 ':'" #: ../eval.c:3893 msgid "E691: Can only compare List with List" -msgstr "" +msgstr "E691: 只能比較列表和列表" #: ../eval.c:3895 #, fuzzy @@ -668,7 +668,7 @@ msgstr "E449: 收到不正確的運算式" #: ../eval.c:3915 msgid "E735: Can only compare Dictionary with Dictionary" -msgstr "" +msgstr "E735: 只能比較字典和字典" #: ../eval.c:3917 #, fuzzy @@ -677,7 +677,7 @@ msgstr "E116: 函式 %s 的引數不正確" #: ../eval.c:3932 msgid "E693: Can only compare Funcref with Funcref" -msgstr "" +msgstr "E693: 只能比較Funcref 和 Funcref" #: ../eval.c:3934 #, fuzzy @@ -736,7 +736,7 @@ msgstr "E242: 找不到顏色: %s" #: ../eval.c:6499 #, c-format msgid "E721: Duplicate key in Dictionary: \"%s\"" -msgstr "" +msgstr "E721: Dictionary 中出現重複的鍵: \"%s\"" #: ../eval.c:6517 #, fuzzy, c-format @@ -781,7 +781,7 @@ msgstr "E120: <SID> 不能在 script 本文外使用: %s" #: ../eval.c:7391 #, c-format msgid "E725: Calling dict function without Dictionary: %s" -msgstr "" +msgstr "E725: 調用字典函式但是沒有字典: %s" #: ../eval.c:7453 #, fuzzy @@ -814,16 +814,15 @@ msgstr "E227: %s 的 mapping 已經存在" #: ../eval.c:8692 msgid "extend() argument" -msgstr "" +msgstr "extend() 參數" #: ../eval.c:8915 -#, fuzzy msgid "map() argument" -msgstr "vim [參數] " +msgstr "map() 參數" #: ../eval.c:8916 msgid "filter() argument" -msgstr "" +msgstr "filter() 參數" #: ../eval.c:9229 #, c-format @@ -857,19 +856,19 @@ msgstr "E596: 不正確的字型" #: ../eval.c:11980 msgid "E726: Stride is zero" -msgstr "" +msgstr "E726: 步長為零" #: ../eval.c:11982 msgid "E727: Start past end" -msgstr "" +msgstr "E727: 起始值在終止值後" #: ../eval.c:12024 ../eval.c:15297 msgid "<empty>" -msgstr "" +msgstr "<空>" #: ../eval.c:12282 msgid "remove() argument" -msgstr "" +msgstr "remove() 參數" #: ../eval.c:12466 msgid "E655: Too many symbolic links (cycle?)" @@ -877,11 +876,11 @@ msgstr "E655: 太多層的符號鏈結(symlink) (循環?)" #: ../eval.c:12593 msgid "reverse() argument" -msgstr "" +msgstr "reverse() 參數" #: ../eval.c:13721 msgid "sort() argument" -msgstr "" +msgstr "sort() 參數" #: ../eval.c:13721 #, fuzzy @@ -895,7 +894,7 @@ msgstr "E237: 無法選擇此印表機" #: ../eval.c:13806 msgid "E882: Uniq compare function failed" -msgstr "" +msgstr "E882: Uniq 比較函式失敗" #: ../eval.c:14085 msgid "(Invalid)" @@ -908,32 +907,31 @@ msgstr "E208: 寫入檔案 \"%s\" 錯誤" #: ../eval.c:16159 msgid "E805: Using a Float as a Number" -msgstr "" +msgstr "E805: 將浮點數當做數字使用" #: ../eval.c:16162 msgid "E703: Using a Funcref as a Number" -msgstr "" +msgstr "E703: 將函式當做數字使用" #: ../eval.c:16170 msgid "E745: Using a List as a Number" -msgstr "" +msgstr "E745: 將列表當做數字使用" #: ../eval.c:16173 msgid "E728: Using a Dictionary as a Number" -msgstr "" +msgstr "E728: 將字典當做數字使用" #: ../eval.c:16259 msgid "E729: using Funcref as a String" -msgstr "" +msgstr "E729: 將函式當做字串使用" #: ../eval.c:16262 -#, fuzzy msgid "E730: using List as a String" -msgstr "E374: 格式化字串裡少了 ]" +msgstr "E730: 將列表當做字串使用" #: ../eval.c:16265 msgid "E731: using Dictionary as a String" -msgstr "" +msgstr "E731: 將字典當做字串使用" #: ../eval.c:16619 #, fuzzy, c-format @@ -953,12 +951,12 @@ msgstr "E128: 函式名稱第一個字母必須大寫: %s" #: ../eval.c:16732 #, c-format msgid "E705: Variable name conflicts with existing function: %s" -msgstr "" +msgstr "E705: 變數名與已有函式名衝突: %s" #: ../eval.c:16763 #, c-format msgid "E741: Value is locked: %s" -msgstr "" +msgstr "E741: 值已鎖定: %s" #: ../eval.c:16764 ../eval.c:16769 ../message.c:1839 msgid "Unknown" @@ -971,7 +969,7 @@ msgstr "E284: 不能設定 IC 數值" #: ../eval.c:16838 msgid "E698: variable nested too deep for making a copy" -msgstr "" +msgstr "E698: 變數嵌套過深無法複製" #: ../eval.c:17249 #, c-format @@ -1216,7 +1214,7 @@ msgstr "要覆寫已存在的檔案 \"%.*s\"?" #: ../ex_cmds.c:2317 #, c-format msgid "Swap file \"%s\" exists, overwrite anyway?" -msgstr "" +msgstr "交換文件 \"%s\" 已存在,確實需要覆蓋嗎?" #: ../ex_cmds.c:2326 #, fuzzy, c-format @@ -1456,7 +1454,7 @@ msgstr "%3d %s %s 第 %<PRId64> 行 " #: ../ex_cmds2.c:942 msgid "E750: First use \":profile start {fname}\"" -msgstr "" +msgstr "E750: 請先使用 :profile start <fname>" #: ../ex_cmds2.c:1269 #, fuzzy, c-format @@ -1555,7 +1553,7 @@ msgstr "vim [參數] " #: ../ex_cmds2.c:2771 msgid "environment variable" -msgstr "" +msgstr "環境變數" #: ../ex_cmds2.c:2773 #, fuzzy @@ -2047,7 +2045,7 @@ msgstr "E199: 已刪除掉作用中的視窗或暫存區" #: ../file_search.c:203 msgid "E854: path too long for completion" -msgstr "" +msgstr "E854: 補全用的路徑太長了" #: ../file_search.c:446 #, c-format @@ -2099,11 +2097,11 @@ msgstr "[未命名]" #: ../fileio.c:511 msgid "[New DIRECTORY]" -msgstr "" +msgstr "[新目錄]" #: ../fileio.c:529 ../fileio.c:532 msgid "[File too big]" -msgstr "" +msgstr "[文件太大]" #: ../fileio.c:534 msgid "[Permission Denied]" @@ -2265,7 +2263,7 @@ msgstr "E513: 無法寫入 -- 轉換失敗" msgid "" "E513: write error, conversion failed in line %<PRId64> (make 'fenc' empty to " "override)" -msgstr "" +msgstr "E513: 寫入錯誤,轉換失敗 (請將 'fenc' 置空以強制執行)" #: ../fileio.c:3448 msgid "E514: write error (file system full?)" @@ -2720,7 +2718,7 @@ msgstr "E49: 錯誤的捲動大小" #: ../globals.h:1021 msgid "E901: Job table is full" -msgstr "" +msgstr "E901: 任務表已經滿" #: ../globals.h:1024 #, c-format @@ -2877,7 +2875,7 @@ msgstr "E42: 沒有錯誤" #: ../globals.h:1067 msgid "E776: No location list" -msgstr "" +msgstr "E776: 沒有位置列表" #: ../globals.h:1068 msgid "E43: Damaged match string" @@ -2992,7 +2990,7 @@ msgstr "E473: 內部錯誤" #: ../globals.h:1104 msgid "E363: pattern uses more memory than 'maxmempattern'" -msgstr "" +msgstr "E363: 表達式的內存超出 'maxmempattern'" #: ../globals.h:1105 #, fuzzy @@ -3097,15 +3095,15 @@ msgstr "E621: \"%s\" 資源檔版本錯誤" #: ../hardcopy.c:2225 msgid "E673: Incompatible multi-byte encoding and character set." -msgstr "" +msgstr "E673: 不兼容的多字節編碼和字元集" #: ../hardcopy.c:2238 msgid "E674: printmbcharset cannot be empty with multi-byte encoding." -msgstr "" +msgstr "E674: printmbcharset 在多字節編碼下不能為空" #: ../hardcopy.c:2254 msgid "E675: No default font specified for multi-byte printing." -msgstr "" +msgstr "E675: 沒有指定多字節打印的默認字型" #: ../hardcopy.c:2426 msgid "E324: Can't open PostScript output file" @@ -3267,6 +3265,7 @@ msgstr "%-5s: %-30s (用法: %s)" #: ../if_cscope.c:1155 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" @@ -3276,6 +3275,16 @@ msgid "" " s: Find this C symbol\n" " t: Find this text string\n" 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" @@ -3509,7 +3518,7 @@ msgstr "-N\t\t\t'nocompatible' 不完全與傳統 Vi 相容,可使用 Vim 加 #: ../main.c:2215 msgid "-V[N][fname]\t\tBe verbose [level N] [log messages to fname]" -msgstr "" +msgstr "-V[N][fname]\t\t詳細 [level N] [log messages to fname]" #: ../main.c:2216 msgid "-D\t\t\tDebugging mode" @@ -3602,7 +3611,7 @@ msgstr "-W <scriptout>\t對檔案 <scriptout> 寫入所有輸入的命令" #: ../main.c:2240 msgid "--startuptime <file>\tWrite startup timing messages to <file>" -msgstr "" +msgstr "--startuptime <file>\t將啟動時間寫入到文件 <file>" #: ../main.c:2242 msgid "-i <viminfo>\t\tUse <viminfo> instead of .viminfo" @@ -3794,7 +3803,7 @@ msgstr "" #: ../memline.c:945 msgid " has been damaged (page size is smaller than minimum value).\n" -msgstr "" +msgstr "已损坏(頁面大小小於最小值)。\n" #: ../memline.c:974 #, c-format @@ -4082,7 +4091,7 @@ msgstr "E317: 指標區塊 id 錯 2" #: ../memline.c:3070 #, c-format msgid "E773: Symlink loop for \"%s\"" -msgstr "" +msgstr "E773: \"%s\" 符號鏈接出現循環" #: ../memline.c:3221 msgid "E325: ATTENTION" @@ -4231,7 +4240,7 @@ msgstr "E329: 沒有那樣的選單" #. Only a mnemonic or accelerator is not valid. #: ../menu.c:329 msgid "E792: Empty menu name" -msgstr "" +msgstr "E792: 空的菜單名稱" #: ../menu.c:340 msgid "E330: Menu path must not lead to a sub-menu" @@ -4312,7 +4321,7 @@ msgstr "-- 尚有 --" #: ../message.c:2398 msgid " SPACE/d/j: screen/page/line down, b/u/k: up, q: quit " -msgstr "" +msgstr " 空格/d/j: 屏幕/頁/行 下翻,b/u/k: 上翻,q: 退出 " #: ../message.c:3021 ../message.c:3031 msgid "Question" @@ -4357,7 +4366,7 @@ msgstr "E116: 函式 %s 的引數不正確" #: ../message.c:3119 msgid "E807: Expected Float argument for printf()" -msgstr "" +msgstr "E807: 期盼浮點數作為printf()參數" #: ../message.c:3873 #, fuzzy @@ -4370,11 +4379,11 @@ msgstr "W10: 注意: 你正在修改一個唯讀檔" #: ../misc1.c:2537 msgid "Type number and <Enter> or click with mouse (empty cancels): " -msgstr "" +msgstr "請輸入數字並<Enter>或點擊鼠標(空白取消): " #: ../misc1.c:2539 msgid "Type number and <Enter> (empty cancels): " -msgstr "" +msgstr "請選擇數字並(<Enter> 取消): " #: ../misc1.c:2585 msgid "1 more line" @@ -4400,7 +4409,7 @@ msgstr " (已中斷)" #: ../misc1.c:2635 msgid "Beep!" -msgstr "" +msgstr "Beep!" #: ../misc2.c:738 #, c-format @@ -4617,7 +4626,7 @@ msgstr "E520: 不能在 Modeline 裡出現" #: ../option.c:2815 msgid "E846: Key code not set" -msgstr "" +msgstr "E846: 未設置鍵位代碼" #: ../option.c:2924 msgid "E521: Number required after =" @@ -4642,11 +4651,11 @@ msgstr "E589: 'backupext' 跟 'patchmode' 是一樣的" #: ../option.c:3964 msgid "E834: Conflicts with value of 'listchars'" -msgstr "" +msgstr "E834: 與'listchars'中的值發生衝突" #: ../option.c:3966 msgid "E835: Conflicts with value of 'fillchars'" -msgstr "" +msgstr "E835: 與'fillchars'中的值發生衝突" #: ../option.c:4163 msgid "E524: Missing colon" @@ -4884,7 +4893,7 @@ msgstr "E382: 無法寫入,'buftype' 選項已設定" #: ../quickfix.c:2812 msgid "E683: File name missing or invalid pattern" -msgstr "" +msgstr "E683: 缺少文件名或模式無效" #: ../quickfix.c:2911 #, fuzzy, c-format @@ -5022,25 +5031,26 @@ msgid "" "E864: \\%#= can only be followed by 0, 1, or 2. The automatic engine will be " "used " msgstr "" +"E864: \\%#= 後面只能是0,1,或者2。自動引擎將會被使用" #: ../regexp_nfa.c:239 msgid "E865: (NFA) Regexp end encountered prematurely" -msgstr "" +msgstr "E865: (NFA) 過早的遇到了正則表達式的結尾" #: ../regexp_nfa.c:240 #, c-format msgid "E866: (NFA regexp) Misplaced %c" -msgstr "" +msgstr "E866: (NFA regexp) %c 放錯了位置" #: ../regexp_nfa.c:242 #, c-format msgid "E877: (NFA regexp) Invalid character class: %<PRId64>" -msgstr "" +msgstr "E877: (NFA regexp) 不可用的字元類: %<PRId64>" #: ../regexp_nfa.c:1261 #, c-format msgid "E867: (NFA) Unknown operator '\\z%c'" -msgstr "" +msgstr "E867: (NFA) 未知的操作符 '\\z%c'" #: ../regexp_nfa.c:1387 #, c-format @@ -5050,21 +5060,21 @@ msgstr "" #: ../regexp_nfa.c:1802 #, c-format msgid "E869: (NFA) Unknown operator '\\@%c'" -msgstr "" +msgstr "E869: (NFA) 未知的操作符 '\\%%%c'" #: ../regexp_nfa.c:1831 msgid "E870: (NFA regexp) Error reading repetition limits" -msgstr "" +msgstr "E870: (NFA regexp) 读取重复限制时出错" #. 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 "" +msgstr "E871: (NFA regexp) 不能多个跟多个!" #. Too many `(' #: ../regexp_nfa.c:2037 msgid "E872: (NFA regexp) Too many '('" -msgstr "" +msgstr "E872: (NFA regexp) 太多 '('" #: ../regexp_nfa.c:2042 #, fuzzy @@ -5073,31 +5083,32 @@ msgstr "E50: 太多 \\z(" #: ../regexp_nfa.c:2066 msgid "E873: (NFA regexp) proper termination error" -msgstr "" +msgstr "E873: (NFA regexp) 未適當終止" #: ../regexp_nfa.c:2599 msgid "E874: (NFA) Could not pop the stack !" -msgstr "" +msgstr "E874: (NFA) 無法出棧!" #: ../regexp_nfa.c:3298 msgid "" "E875: (NFA regexp) (While converting from postfix to NFA), too many states " "left on stack" -msgstr "" +msgstr "E875: (NFA regexp) (從後綴轉到 NFA 时),棧上遺留了太多狀態" #: ../regexp_nfa.c:3302 msgid "E876: (NFA regexp) Not enough space to store the whole NFA " -msgstr "" +msgstr "E876: (NFA regexp) 沒有足夠的空間存儲NFA " #: ../regexp_nfa.c:4571 ../regexp_nfa.c:4869 msgid "" "Could not open temporary log file for writing, displaying on stderr ... " msgstr "" +"無法打開臨時日志文件進行寫入,顯示在stderr中..." #: ../regexp_nfa.c:4840 #, c-format msgid "(NFA) COULD NOT OPEN %s !" -msgstr "" +msgstr "(NFA) 不能打开 %s !" #: ../regexp_nfa.c:6049 #, fuzzy @@ -5270,17 +5281,17 @@ msgstr "E297: 暫存檔寫入錯誤" #: ../spell.c:952 msgid "E758: Truncated spell file" -msgstr "" +msgstr "E758: 已截斷的拼寫文件" #: ../spell.c:953 #, c-format msgid "Trailing text in %s line %d: %s" -msgstr "" +msgstr "%s 第 %d 行,多餘的後續文本: %s" #: ../spell.c:954 #, c-format msgid "Affix name too long in %s line %d: %s" -msgstr "" +msgstr "%s 第 %d 行,附加項名字太長: %s" #: ../spell.c:955 #, fuzzy @@ -5289,20 +5300,20 @@ msgstr "E431: Tag 檔 \"%s\" 格式錯誤" #: ../spell.c:957 msgid "E762: Character in FOL, LOW or UPP is out of range" -msgstr "" +msgstr "E762: FOL、LOW 或 UPP 中字元超出範圍" #: ../spell.c:958 msgid "Compressing word tree..." -msgstr "" +msgstr "壓縮單詞樹……" #: ../spell.c:1951 msgid "E756: Spell checking is not enabled" -msgstr "" +msgstr "E756: 拼寫檢查未啟用" #: ../spell.c:2249 #, c-format msgid "Warning: Cannot find word list \"%s.%s.spl\" or \"%s.ascii.spl\"" -msgstr "" +msgstr "警告: 找不到單詞列表 \"%s.%s.spl\" or \"%s.ascii.spl\"" #: ../spell.c:2473 #, fuzzy, c-format @@ -5316,11 +5327,11 @@ msgstr "E307: %s 看起來不像是 Vim 暫存檔" #: ../spell.c:2501 msgid "E771: Old spell file, needs to be updated" -msgstr "" +msgstr "E771: 舊的拼寫文件,需要更新" #: ../spell.c:2504 msgid "E772: Spell file is for newer version of Vim" -msgstr "" +msgstr "E772: 為更高版本的 Vim 所使用的拼寫文件" #: ../spell.c:2602 #, fuzzy @@ -5340,66 +5351,68 @@ msgstr "搜尋 tag 檔案 \"%s\"" #: ../spell.c:4589 ../spell.c:5635 ../spell.c:6140 #, c-format msgid "Conversion failure for word in %s line %d: %s" -msgstr "" +msgstr "單詞 %s 轉換失敗,第 %d 行: %s" #: ../spell.c:4630 ../spell.c:6170 #, c-format msgid "Conversion in %s not supported: from %s to %s" -msgstr "" +msgstr "不支持 %s 中的轉換: 从 %s 到 %s" #: ../spell.c:4642 #, c-format msgid "Invalid value for FLAG in %s line %d: %s" -msgstr "" +msgstr "%s 第 %d 行,FLAG 的值无效: %s" #: ../spell.c:4655 #, c-format msgid "FLAG after using flags in %s line %d: %s" -msgstr "" +msgstr "%s 第 %d 行,在使用標志後出現 FLAG: %s" #: ../spell.c:4723 #, c-format msgid "" "Defining COMPOUNDFORBIDFLAG after PFX item may give wrong results in %s line " "%d" -msgstr "" +msgstr "在 PFX 項之後定義 COMPOUNDFORBIDFLAG (%s 第%d行)可能會給出的錯誤結果" +"%d" #: ../spell.c:4731 #, c-format msgid "" "Defining COMPOUNDPERMITFLAG after PFX item may give wrong results in %s line " "%d" -msgstr "" +msgstr "在 PFX 項之後定義 COMPOUNDFORBIDFLAG (%s 第%d行)可能會給出的錯誤結果" +"%d" #: ../spell.c:4747 #, c-format msgid "Wrong COMPOUNDRULES value in %s line %d: %s" -msgstr "" +msgstr "%s 第 %d 行,错误的 COMPOUNDMIN 值: %s" #: ../spell.c:4771 #, c-format msgid "Wrong COMPOUNDWORDMAX value in %s line %d: %s" -msgstr "" +msgstr "%s 第 %d 行,错误的 COMPOUNDWORDMAX 值: %s" #: ../spell.c:4777 #, c-format msgid "Wrong COMPOUNDMIN value in %s line %d: %s" -msgstr "" +msgstr "%s 第 %d 行,错误的 COMPOUNDMIN 值: %s" #: ../spell.c:4783 #, c-format msgid "Wrong COMPOUNDSYLMAX value in %s line %d: %s" -msgstr "" +msgstr "%s 第 %d 行,错误的 COMPOUNDSYLMAX 值: %s" #: ../spell.c:4795 #, c-format msgid "Wrong CHECKCOMPOUNDPATTERN value in %s line %d: %s" -msgstr "" +msgstr "%s 第 %d 行,错误的 CHECKCOMPOUNDPATTERN 值: %s" #: ../spell.c:4847 #, c-format msgid "Different combining flag in continued affix block in %s line %d: %s" -msgstr "" +msgstr "%s 第 %d 行,在連續的附加塊種出現不同的組合標誌: %s" #: ../spell.c:4850 #, fuzzy, c-format @@ -5412,45 +5425,47 @@ msgid "" "Affix also used for BAD/RARE/KEEPCASE/NEEDAFFIX/NEEDCOMPOUND/NOSUGGEST in %s " "line %d: %s" msgstr "" +"%s 第 %d 行,附加項被 BAD/RARE/KEEPCASE/NEEDAFFIX/NEEDCOMPOUND/NOSUGGEST 使" +"用: %s" #: ../spell.c:4893 #, c-format msgid "Expected Y or N in %s line %d: %s" -msgstr "" +msgstr "%s 第 %d 行,此處需要 Y 或 N: %s" #: ../spell.c:4968 #, c-format msgid "Broken condition in %s line %d: %s" -msgstr "" +msgstr "%s 第 %d 行,錯誤的條件: %s" #: ../spell.c:5091 #, c-format msgid "Expected REP(SAL) count in %s line %d" -msgstr "" +msgstr "%s 第 %d 行,此處需要 REP(SAL) 計數" #: ../spell.c:5120 #, c-format msgid "Expected MAP count in %s line %d" -msgstr "" +msgstr "%s 第 %d 行,此處需要 MAP 計數" #: ../spell.c:5132 #, c-format msgid "Duplicate character in MAP in %s line %d" -msgstr "" +msgstr "%s 第 %d 行,MAP 中存在重複的字元" #: ../spell.c:5176 #, c-format msgid "Unrecognized or duplicate item in %s line %d: %s" -msgstr "" +msgstr "%s 第 %d 行,無法識別或重複的項: %s" #: ../spell.c:5197 #, c-format msgid "Missing FOL/LOW/UPP line in %s" -msgstr "" +msgstr "%s 中缺少 FOL/LOW/UPP 行" #: ../spell.c:5220 msgid "COMPOUNDSYLMAX used without SYLLABLE" -msgstr "" +msgstr "在没有 SYLLABLE 的情況下使用了 COMPOUNDSYLMAX" #: ../spell.c:5236 #, fuzzy @@ -5464,32 +5479,32 @@ msgstr "太多編輯參數" #: ../spell.c:5240 msgid "Too many postponed prefixes and/or compound flags" -msgstr "" +msgstr "太多延遲前綴和/或組合標誌" #: ../spell.c:5250 #, c-format msgid "Missing SOFO%s line in %s" -msgstr "" +msgstr "%s 中缺少 SOFO%s 行" #: ../spell.c:5253 #, c-format msgid "Both SAL and SOFO lines in %s" -msgstr "" +msgstr "%s 同時出現 SAL 和 SOFO 行" #: ../spell.c:5331 #, c-format msgid "Flag is not a number in %s line %d: %s" -msgstr "" +msgstr "%s 第 %d 行,標誌不是數字: %s" #: ../spell.c:5334 #, c-format msgid "Illegal flag in %s line %d: %s" -msgstr "" +msgstr "%s 第 %d 行,無效的標誌: %s" #: ../spell.c:5493 ../spell.c:5501 #, c-format msgid "%s value differs from what is used in another .aff file" -msgstr "" +msgstr "%s 的值與另一個 .aff 文件中使用的值不相同" #: ../spell.c:5602 #, fuzzy, c-format @@ -5499,12 +5514,12 @@ msgstr "掃瞄字典: %s" #: ../spell.c:5611 #, c-format msgid "E760: No word count in %s" -msgstr "" +msgstr "E760: %s 中没有單詞計數" #: ../spell.c:5669 #, c-format msgid "line %6d, word %6d - %s" -msgstr "" +msgstr "第 %6d 行,第 %6d 个單詞 - %s" #: ../spell.c:5691 #, fuzzy, c-format @@ -5514,17 +5529,17 @@ msgstr "每一行都找不到: %s" #: ../spell.c:5694 #, c-format msgid "First duplicate word in %s line %d: %s" -msgstr "" +msgstr "%s 第 %d 行,首次出現重複的單詞: %s" #: ../spell.c:5746 #, c-format msgid "%d duplicate word(s) in %s" -msgstr "" +msgstr "存在 %d 个重複的單詞,在 %s 中" #: ../spell.c:5748 #, c-format msgid "Ignored %d word(s) with non-ASCII characters in %s" -msgstr "" +msgstr "忽略了含有非 ASCII 字元的 %d 个單詞,在 %s 中" #: ../spell.c:6115 #, fuzzy, c-format @@ -5534,42 +5549,42 @@ msgstr "從標準輸入讀取..." #: ../spell.c:6155 #, c-format msgid "Duplicate /encoding= line ignored in %s line %d: %s" -msgstr "" +msgstr "%s 第 %ld 行,重复的 /encoding= 行已被忽略: %s" #: ../spell.c:6159 #, c-format msgid "/encoding= line after word ignored in %s line %d: %s" -msgstr "" +msgstr "%s 第 %d 行,单词后的 /encoding= 行已被忽略: %s" #: ../spell.c:6180 #, c-format msgid "Duplicate /regions= line ignored in %s line %d: %s" -msgstr "" +msgstr "%s 第 %d 行,重复的 /regions= 行已被忽略: %s" #: ../spell.c:6185 #, c-format msgid "Too many regions in %s line %d: %s" -msgstr "" +msgstr "%s 第 %d 行,太多區域: %s" #: ../spell.c:6198 #, c-format msgid "/ line ignored in %s line %d: %s" -msgstr "" +msgstr "%s 第 %d 行,/ 行已被忽略: %s" #: ../spell.c:6224 #, fuzzy, c-format msgid "Invalid region nr in %s line %d: %s" -msgstr "E573: 不正確的伺服器 id : %s" +msgstr "%s 第 %d 行,無效的區域號: %s" #: ../spell.c:6230 #, c-format msgid "Unrecognized flags in %s line %d: %s" -msgstr "" +msgstr "%s 第 %d 行,不可識別的標誌: %s" #: ../spell.c:6257 #, c-format msgid "Ignored %d words with non-ASCII characters" -msgstr "" +msgstr "忽略了含有非 ASCII 字元的 %d 个單詞" #: ../spell.c:6656 #, c-format @@ -5578,23 +5593,23 @@ msgstr "" #: ../spell.c:7340 msgid "Reading back spell file..." -msgstr "" +msgstr "读取拼寫文件……" #. Go through the trie of good words, soundfold each word and add it to #. the soundfold trie. #: ../spell.c:7357 msgid "Performing soundfolding..." -msgstr "" +msgstr "正在 soundfolding……" #: ../spell.c:7368 #, c-format msgid "Number of words after soundfolding: %<PRId64>" -msgstr "" +msgstr "soundfolding 后的單詞数: %<PRId64>" #: ../spell.c:7476 #, c-format msgid "Total number of words: %d" -msgstr "" +msgstr "單詞总数: %d" #: ../spell.c:7655 #, fuzzy, c-format @@ -5604,11 +5619,11 @@ msgstr "寫入 viminfo 檔案 \"%s\" 中" #: ../spell.c:7707 ../spell.c:7927 #, c-format msgid "Estimated runtime memory use: %d bytes" -msgstr "" +msgstr "估計運行時的內存用量: %d 位元" #: ../spell.c:7820 msgid "E751: Output file name must not have region name" -msgstr "" +msgstr "E751: 輸出文件不能含有區域名" #: ../spell.c:7822 #, fuzzy @@ -5622,7 +5637,7 @@ msgstr "E15: 不正確的運算式: %s" #: ../spell.c:7907 msgid "Warning: both compounding and NOBREAK specified" -msgstr "" +msgstr "警告: 同時指定了 compounding 和 NOBREAK" #: ../spell.c:7920 #, fuzzy, c-format @@ -5631,30 +5646,30 @@ msgstr "寫入 viminfo 檔案 \"%s\" 中" #: ../spell.c:7925 msgid "Done!" -msgstr "" +msgstr "完成!" #: ../spell.c:8034 #, c-format msgid "E765: 'spellfile' does not have %<PRId64> entries" -msgstr "" +msgstr "E765: 'spellfile' 没有 %<PRId64> 項" #: ../spell.c:8074 #, c-format msgid "Word '%.*s' removed from %s" -msgstr "" +msgstr "从 %s 中删除了單詞" #: ../spell.c:8117 #, c-format msgid "Word '%.*s' added to %s" -msgstr "" +msgstr "向 %s 中添加了單詞" #: ../spell.c:8381 msgid "E763: Word characters differ between spell files" -msgstr "" +msgstr "E763: 拼寫文件之間的字元不相同" #: ../spell.c:8684 msgid "Sorry, no suggestions" -msgstr "" +msgstr "抱歉,没有建议" #: ../spell.c:8687 #, fuzzy, c-format @@ -5671,7 +5686,7 @@ msgstr "將變動存儲至 \"%.*s\"?" #: ../spell.c:8737 #, c-format msgid " < \"%.*s\"" -msgstr "" +msgstr " < \"%.*s\"" #: ../spell.c:8882 #, fuzzy @@ -5691,28 +5706,28 @@ msgstr "E307: %s 看起來不像是 Vim 暫存檔" #: ../spell.c:9282 #, c-format msgid "E779: Old .sug file, needs to be updated: %s" -msgstr "" +msgstr "E779: 舊的.sug 文件,需要更新: %s" #: ../spell.c:9286 #, c-format msgid "E780: .sug file is for newer version of Vim: %s" -msgstr "" +msgstr "E780: .sug 文件適用於較新的 Vim 版本: %s" #: ../spell.c:9295 #, c-format msgid "E781: .sug file doesn't match .spl file: %s" -msgstr "" +msgstr "E781: .sug 文件不能匹配 .spl 文件: %s" #: ../spell.c:9305 #, fuzzy, c-format msgid "E782: error while reading .sug file: %s" -msgstr "E47: 讀取錯誤檔案失敗" +msgstr "E782: 當讀取.sug 文件時錯誤" #. This should have been checked when generating the .spl #. file. #: ../spell.c:11575 msgid "E783: duplicate char in MAP entry" -msgstr "" +msgstr "E783: MAP 條目中有重複的字元" #: ../syntax.c:266 msgid "No Syntax items defined for this buffer" @@ -5894,10 +5909,11 @@ msgstr "E410: 不正確的 :syntax 子命令: %s" msgid "" " TOTAL COUNT MATCH SLOWEST AVERAGE NAME PATTERN" msgstr "" +" 總 計 計 數 匹 配 最 慢 的 平 均 名 字 模 式" #: ../syntax.c:6146 msgid "E679: recursive loop loading syncolor.vim" -msgstr "" +msgstr "E679: 加載 syncolor.vim 时出現嵌套循環" #: ../syntax.c:6256 #, c-format @@ -5969,13 +5985,13 @@ msgstr "E424: 使用了過多相異的高亮度屬性" msgid "E669: Unprintable character in group name" msgstr "E669: 群組名稱中有無法列印的字元" -#: ../syntax.c:7434 -msgid "W18: Invalid character in group name" -msgstr "W18: 群組名稱中有不正確的字元" +#: ../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 "" +msgstr "E849: 高亮和語法組過多" #: ../tag.c:104 msgid "E555: at bottom of tag stack" @@ -6083,7 +6099,7 @@ msgstr "E435: 找不到 tag, 用猜的!" #: ../tag.c:2797 #, c-format msgid "Duplicate field name: %s" -msgstr "" +msgstr "重複的字段名: %s" #: ../term.c:1442 msgid "' not known. Available builtin terminals are:" @@ -6131,35 +6147,35 @@ msgstr "Vim: 讀取輸入錯誤,離開中...\n" #. * file in a way it becomes shorter. #: ../undo.c:379 msgid "E881: Line count changed unexpectedly" -msgstr "" +msgstr "E881: 行數意外地改變了" #: ../undo.c:627 -#, fuzzy, c-format +#, c-format msgid "E828: Cannot open undo file for writing: %s" -msgstr "E212: 無法以寫入模式開啟" +msgstr "E828: 無法打開撤銷文件去寫入" #: ../undo.c:717 #, c-format msgid "E825: Corrupted undo file (%s): %s" -msgstr "" +msgstr "E825: 已損壞的撤銷文件 (%s): %s" #: ../undo.c:1039 msgid "Cannot write undo file in any directory in 'undodir'" -msgstr "" +msgstr "不能寫入撤銷文件到 'undodir' 中的任何文件夾" #: ../undo.c:1074 #, c-format msgid "Will not overwrite with undo file, cannot read: %s" -msgstr "" +msgstr "不能寫入撤銷文件,不可讀取: %s" #: ../undo.c:1092 #, c-format msgid "Will not overwrite, this is not an undo file: %s" -msgstr "" +msgstr "不會覆蓋,這不是撤銷文件: %s" #: ../undo.c:1108 msgid "Skipping undo file write, nothing to undo" -msgstr "" +msgstr "跳過撤消文件寫入,沒有可撤消的內容" #: ../undo.c:1121 #, fuzzy, c-format @@ -6174,7 +6190,7 @@ msgstr "E297: 暫存檔寫入錯誤" #: ../undo.c:1280 #, c-format msgid "Not reading undo file, owner differs: %s" -msgstr "" +msgstr "不能讀取撤銷文件,擁有者不同: %s" #: ../undo.c:1292 #, fuzzy, c-format @@ -6198,7 +6214,7 @@ msgstr "E484: 無法開啟檔案 %s" #: ../undo.c:1328 msgid "File contents changed, cannot use undo info" -msgstr "" +msgstr "文件內容已經改變,不能使用撤銷信息" #: ../undo.c:1497 #, fuzzy, c-format @@ -6207,11 +6223,11 @@ msgstr "結束執行 %s" #: ../undo.c:1586 ../undo.c:1812 msgid "Already at oldest change" -msgstr "" +msgstr "已經在最早的改變" #: ../undo.c:1597 ../undo.c:1814 msgid "Already at newest change" -msgstr "" +msgstr "已經在最新的改變" #: ../undo.c:1806 #, fuzzy, c-format @@ -6259,11 +6275,11 @@ msgstr "%<PRId64> 行 %s 過 %d 次" #: ../undo.c:2228 msgid "before" -msgstr "" +msgstr "之前" #: ../undo.c:2228 msgid "after" -msgstr "" +msgstr "之後" #: ../undo.c:2325 #, fuzzy @@ -6272,7 +6288,7 @@ msgstr "沒有這個 mapping 對應" #: ../undo.c:2330 msgid "number changes when saved" -msgstr "" +msgstr " 編號 改變 時間 保存" #: ../undo.c:2360 #, fuzzy, c-format diff --git a/src/nvim/popupmnu.c b/src/nvim/popupmenu.c index 746429e5d5..0d9080ceb7 100644 --- a/src/nvim/popupmnu.c +++ b/src/nvim/popupmenu.c @@ -1,7 +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 -/// @file popupmnu.c +/// @file popupmenu.c /// /// Popup menu (PUM) @@ -13,17 +13,19 @@ #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/ex_cmds.h" +#include "nvim/grid.h" +#include "nvim/highlight.h" #include "nvim/insexpand.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/menu.h" #include "nvim/move.h" #include "nvim/option.h" -#include "nvim/popupmnu.h" -#include "nvim/screen.h" +#include "nvim/popupmenu.h" #include "nvim/search.h" #include "nvim/strings.h" #include "nvim/ui.h" @@ -55,7 +57,7 @@ static bool pum_external = false; static bool pum_invalid = false; // the screen was just cleared #ifdef INCLUDE_GENERATED_DECLARATIONS -# include "popupmnu.c.generated.h" +# include "popupmenu.c.generated.h" #endif #define PUM_DEF_HEIGHT 10 @@ -154,7 +156,6 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i if (pum_external) { if (array_changed) { Arena arena = ARENA_EMPTY; - arena_start(&arena, &ui_ext_fixblk); Array arr = arena_array(&arena, (size_t)size); for (int i = 0; i < size; i++) { Array item = arena_array(&arena, 4); @@ -166,7 +167,7 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i } ui_call_popupmenu_show(arr, selected, pum_win_row, cursor_col, pum_anchor_grid); - arena_mem_free(arena_finish(&arena), &ui_ext_fixblk); + arena_mem_free(arena_finish(&arena)); } else { ui_call_popupmenu_select(selected); return; @@ -463,7 +464,7 @@ void pum_redraw(void) / (pum_size - pum_height); } - for (i = 0; i < pum_height; ++i) { + for (i = 0; i < pum_height; i++) { idx = i + pum_first; attr = (idx == pum_selected) ? attr_select : attr_norm; @@ -483,7 +484,7 @@ void pum_redraw(void) grid_col = col_off; totwidth = 0; - for (round = 1; round <= 3; ++round) { + for (round = 1; round <= 3; round++) { width = 0; s = NULL; @@ -523,7 +524,7 @@ void pum_redraw(void) } if (pum_rl) { - char *rt = (char *)reverse_text(st); + char *rt = reverse_text((char *)st); char *rt_start = rt; int size = vim_strsize(rt); @@ -541,14 +542,13 @@ void pum_redraw(void) size++; } } - grid_puts_len(&pum_grid, (char_u *)rt, (int)STRLEN(rt), row, - grid_col - size + 1, attr); + grid_puts_len(&pum_grid, rt, (int)STRLEN(rt), row, grid_col - size + 1, attr); xfree(rt_start); xfree(st); grid_col -= width; } else { // use grid_puts_len() to truncate the text - grid_puts(&pum_grid, st, row, grid_col, attr); + grid_puts(&pum_grid, (char *)st, row, grid_col, attr); xfree(st); grid_col += width; } @@ -559,11 +559,11 @@ void pum_redraw(void) // Display two spaces for a Tab. if (pum_rl) { - grid_puts_len(&pum_grid, (char_u *)" ", 2, row, grid_col - 1, + grid_puts_len(&pum_grid, " ", 2, row, grid_col - 1, attr); grid_col -= 2; } else { - grid_puts_len(&pum_grid, (char_u *)" ", 2, row, grid_col, attr); + grid_puts_len(&pum_grid, " ", 2, row, grid_col, attr); grid_col += 2; } totwidth += 2; @@ -639,11 +639,11 @@ void pum_redraw(void) /// @param n /// @param repeat /// -/// @returns TRUE when the window was resized and the location of the popup +/// @returns true when the window was resized and the location of the popup /// menu must be recomputed. -static int pum_set_selected(int n, int repeat) +static bool pum_set_selected(int n, int repeat) { - int resized = FALSE; + int resized = false; int context = pum_height / 2; pum_selected = n; @@ -702,7 +702,7 @@ static int pum_set_selected(int n, int repeat) if ((pum_array[pum_selected].pum_info != NULL) && (Rows > 10) && (repeat <= 1) - && (vim_strchr((char *)p_cot, 'p') != NULL)) { + && (vim_strchr(p_cot, 'p') != NULL)) { win_T *curwin_save = curwin; tabpage_T *curtab_save = curtab; int res = OK; @@ -742,11 +742,11 @@ static int pum_set_selected(int n, int repeat) if (res == OK) { // Edit a new, empty buffer. Set options for a "wipeout" // buffer. - set_option_value("swf", 0L, NULL, OPT_LOCAL); - set_option_value("bl", 0L, NULL, OPT_LOCAL); - set_option_value("bt", 0L, "nofile", OPT_LOCAL); - set_option_value("bh", 0L, "wipe", OPT_LOCAL); - set_option_value("diff", 0L, NULL, OPT_LOCAL); + set_option_value_give_err("swf", 0L, NULL, OPT_LOCAL); + set_option_value_give_err("bl", 0L, NULL, OPT_LOCAL); + set_option_value_give_err("bt", 0L, "nofile", OPT_LOCAL); + set_option_value_give_err("bh", 0L, "wipe", OPT_LOCAL); + set_option_value_give_err("diff", 0L, NULL, OPT_LOCAL); } } @@ -776,12 +776,12 @@ static int pum_set_selected(int n, int repeat) if (curwin->w_height < lnum) { win_setheight((int)lnum); - resized = TRUE; + resized = true; } } curbuf->b_changed = false; - curbuf->b_p_ma = FALSE; + curbuf->b_p_ma = false; curwin->w_cursor.lnum = 1; curwin->w_cursor.col = 0; @@ -795,12 +795,12 @@ static int pum_set_selected(int n, int repeat) // window is not resized, skip the preview window's // status line redrawing. if (ins_compl_active() && !resized) { - curwin->w_redr_status = FALSE; + curwin->w_redr_status = false; } // Return cursor to where we were validate_cursor(); - redraw_later(curwin, SOME_VALID); + redraw_later(curwin, UPD_SOME_VALID); // When the preview window was resized we need to // update the view on the buffer. Only go back to @@ -904,7 +904,7 @@ void pum_recompose(void) /// Gets the height of the menu. /// /// @return the height of the popup menu, the number of entries visible. -/// Only valid when pum_visible() returns TRUE! +/// Only valid when pum_visible() returns true! int pum_get_height(void) { if (pum_external) { @@ -999,7 +999,7 @@ static void pum_execute_menu(vimmenu_T *menu, int mode) for (vimmenu_T *mp = menu->children; mp != NULL; mp = mp->next) { if ((mp->modes & mp->enabled & mode) && idx++ == pum_selected) { - memset(&ea, 0, sizeof(ea)); + CLEAR_FIELD(ea); execute_menu(&ea, mp, -1); break; } diff --git a/src/nvim/popupmnu.h b/src/nvim/popupmenu.h index 7d3f4c6f51..851ad31486 100644 --- a/src/nvim/popupmnu.h +++ b/src/nvim/popupmenu.h @@ -1,5 +1,5 @@ -#ifndef NVIM_POPUPMNU_H -#define NVIM_POPUPMNU_H +#ifndef NVIM_POPUPMENU_H +#define NVIM_POPUPMENU_H #include "nvim/grid_defs.h" #include "nvim/macros.h" @@ -17,6 +17,6 @@ typedef struct { EXTERN ScreenGrid pum_grid INIT(= SCREEN_GRID_INIT); #ifdef INCLUDE_GENERATED_DECLARATIONS -# include "popupmnu.h.generated.h" +# include "popupmenu.h.generated.h" #endif -#endif // NVIM_POPUPMNU_H +#endif // NVIM_POPUPMENU_H diff --git a/src/nvim/profile.c b/src/nvim/profile.c index fe7bd2e912..cded231c85 100644 --- a/src/nvim/profile.c +++ b/src/nvim/profile.c @@ -6,16 +6,32 @@ #include <stdio.h> #include "nvim/assert.h" +#include "nvim/charset.h" +#include "nvim/debugger.h" +#include "nvim/eval.h" +#include "nvim/eval/userfunc.h" +#include "nvim/fileio.h" #include "nvim/func_attr.h" #include "nvim/globals.h" // for the global `time_fd` (startuptime) -#include "nvim/os/os_defs.h" +#include "nvim/os/os.h" #include "nvim/os/time.h" #include "nvim/profile.h" +#include "nvim/runtime.h" +#include "nvim/vim.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "profile.c.generated.h" #endif +/// Struct used in sn_prl_ga for every line of a script. +typedef struct sn_prl_S { + int snp_count; ///< nr of times line was executed + proftime_T sn_prl_total; ///< time spent in a line + children + proftime_T sn_prl_self; ///< time spent in a line itself +} sn_prl_T; + +#define PRL_ITEM(si, idx) (((sn_prl_T *)(si)->sn_prl_ga.ga_data)[(idx)]) + static proftime_T prof_wait_time; /// Gets the current time. @@ -195,6 +211,652 @@ int profile_cmp(proftime_T tm1, proftime_T tm2) FUNC_ATTR_CONST return profile_signed(tm2 - tm1) < 0 ? -1 : 1; } +static char *profile_fname = NULL; + +/// Reset all profiling information. +void profile_reset(void) +{ + // Reset sourced files. + for (int id = 1; id <= script_items.ga_len; id++) { + scriptitem_T *si = &SCRIPT_ITEM(id); + if (si->sn_prof_on) { + si->sn_prof_on = false; + si->sn_pr_force = false; + si->sn_pr_child = profile_zero(); + si->sn_pr_nest = 0; + si->sn_pr_count = 0; + si->sn_pr_total = profile_zero(); + si->sn_pr_self = profile_zero(); + si->sn_pr_start = profile_zero(); + si->sn_pr_children = profile_zero(); + ga_clear(&si->sn_prl_ga); + si->sn_prl_start = profile_zero(); + si->sn_prl_children = profile_zero(); + si->sn_prl_wait = profile_zero(); + si->sn_prl_idx = -1; + si->sn_prl_execed = 0; + } + } + + // Reset functions. + hashtab_T *const functbl = func_tbl_get(); + size_t todo = functbl->ht_used; + hashitem_T *hi = functbl->ht_array; + + for (; todo > (size_t)0; hi++) { + if (!HASHITEM_EMPTY(hi)) { + todo--; + ufunc_T *uf = HI2UF(hi); + if (uf->uf_prof_initialized) { + uf->uf_profiling = 0; + uf->uf_tm_count = 0; + uf->uf_tm_total = profile_zero(); + uf->uf_tm_self = profile_zero(); + uf->uf_tm_children = profile_zero(); + + for (int i = 0; i < uf->uf_lines.ga_len; i++) { + uf->uf_tml_count[i] = 0; + uf->uf_tml_total[i] = uf->uf_tml_self[i] = 0; + } + + uf->uf_tml_start = profile_zero(); + uf->uf_tml_children = profile_zero(); + uf->uf_tml_wait = profile_zero(); + uf->uf_tml_idx = -1; + uf->uf_tml_execed = 0; + } + } + } + + XFREE_CLEAR(profile_fname); +} + +/// ":profile cmd args" +void ex_profile(exarg_T *eap) +{ + static proftime_T pause_time; + + char *e; + int len; + + e = skiptowhite(eap->arg); + len = (int)(e - eap->arg); + e = skipwhite(e); + + 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); + do_profiling = PROF_YES; + profile_set_wait(profile_zero()); + set_vim_var_nr(VV_PROFILING, 1L); + } else if (do_profiling == PROF_NONE) { + emsg(_("E750: First use \":profile start {fname}\"")); + } else if (STRCMP(eap->arg, "stop") == 0) { + profile_dump(); + do_profiling = PROF_NONE; + set_vim_var_nr(VV_PROFILING, 0L); + profile_reset(); + } else if (STRCMP(eap->arg, "pause") == 0) { + if (do_profiling == PROF_YES) { + pause_time = profile_start(); + } + do_profiling = PROF_PAUSED; + } else if (STRCMP(eap->arg, "continue") == 0) { + if (do_profiling == PROF_PAUSED) { + pause_time = profile_end(pause_time); + profile_set_wait(profile_add(profile_get_wait(), pause_time)); + } + do_profiling = PROF_YES; + } else if (STRCMP(eap->arg, "dump") == 0) { + profile_dump(); + } else { + // The rest is similar to ":breakadd". + ex_breakadd(eap); + } +} + +/// Command line expansion for :profile. +static enum { + PEXP_SUBCMD, ///< expand :profile sub-commands + PEXP_FUNC, ///< expand :profile func {funcname} +} pexpand_what; + +static char *pexpand_cmds[] = { + "continue", + "dump", + "file", + "func", + "pause", + "start", + "stop", + NULL +}; + +/// Function given to ExpandGeneric() to obtain the profile command +/// specific expansion. +char *get_profile_name(expand_T *xp, int idx) + FUNC_ATTR_PURE +{ + switch (pexpand_what) { + case PEXP_SUBCMD: + return pexpand_cmds[idx]; + // case PEXP_FUNC: TODO + default: + return NULL; + } +} + +/// Handle command line completion for :profile command. +void set_context_in_profile_cmd(expand_T *xp, const char *arg) +{ + // Default: expand subcommands. + xp->xp_context = EXPAND_PROFILE; + pexpand_what = PEXP_SUBCMD; + xp->xp_pattern = (char *)arg; + + char_u *const end_subcmd = (char_u *)skiptowhite(arg); + if (*end_subcmd == NUL) { + return; + } + + if ((const char *)end_subcmd - arg == 5 && strncmp(arg, "start", 5) == 0) { + xp->xp_context = EXPAND_FILES; + xp->xp_pattern = skipwhite((char *)end_subcmd); + return; + } + + // TODO(tarruda): expand function names after "func" + xp->xp_context = EXPAND_NOTHING; +} + +static proftime_T inchar_time; + +/// Called when starting to wait for the user to type a character. +void prof_inchar_enter(void) +{ + inchar_time = profile_start(); +} + +/// Called when finished waiting for the user to type a character. +void prof_inchar_exit(void) +{ + inchar_time = profile_end(inchar_time); + profile_set_wait(profile_add(profile_get_wait(), inchar_time)); +} + +/// @return true when a function defined in the current script should be +/// profiled. +bool prof_def_func(void) + FUNC_ATTR_PURE +{ + if (current_sctx.sc_sid > 0) { + return SCRIPT_ITEM(current_sctx.sc_sid).sn_pr_force; + } + return false; +} + +/// 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, + bool prefer_self) +{ + if (count > 0) { + fprintf(fd, "%5d ", count); + if (prefer_self && profile_equal(*total, *self)) { + fprintf(fd, " "); + } else { + fprintf(fd, "%s ", profile_msg(*total)); + } + if (!prefer_self && profile_equal(*total, *self)) { + fprintf(fd, " "); + } else { + fprintf(fd, "%s ", profile_msg(*self)); + } + } else { + fprintf(fd, " "); + } +} + +/// @param prefer_self when equal print only self time +static void prof_sort_list(FILE *fd, ufunc_T **sorttab, int st_len, char *title, bool prefer_self) +{ + int i; + ufunc_T *fp; + + fprintf(fd, "FUNCTIONS SORTED ON %s TIME\n", title); + fprintf(fd, "count total (s) self (s) function\n"); + for (i = 0; i < 20 && i < st_len; i++) { + 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) { + fprintf(fd, " <SNR>%s()\n", fp->uf_name + 3); + } else { + fprintf(fd, " %s()\n", fp->uf_name); + } + } + fprintf(fd, "\n"); +} + +/// Compare function for total time sorting. +static int prof_total_cmp(const void *s1, const void *s2) +{ + ufunc_T *p1 = *(ufunc_T **)s1; + ufunc_T *p2 = *(ufunc_T **)s2; + return profile_cmp(p1->uf_tm_total, p2->uf_tm_total); +} + +/// Compare function for self time sorting. +static int prof_self_cmp(const void *s1, const void *s2) +{ + ufunc_T *p1 = *(ufunc_T **)s1; + ufunc_T *p2 = *(ufunc_T **)s2; + return profile_cmp(p1->uf_tm_self, p2->uf_tm_self); +} + +/// Start profiling function "fp". +void func_do_profile(ufunc_T *fp) +{ + int len = fp->uf_lines.ga_len; + + if (!fp->uf_prof_initialized) { + if (len == 0) { + len = 1; // avoid getting error for allocating zero bytes + } + fp->uf_tm_count = 0; + fp->uf_tm_self = profile_zero(); + fp->uf_tm_total = profile_zero(); + + if (fp->uf_tml_count == NULL) { + fp->uf_tml_count = xcalloc((size_t)len, sizeof(int)); + } + + if (fp->uf_tml_total == NULL) { + fp->uf_tml_total = xcalloc((size_t)len, sizeof(proftime_T)); + } + + if (fp->uf_tml_self == NULL) { + fp->uf_tml_self = xcalloc((size_t)len, sizeof(proftime_T)); + } + + fp->uf_tml_idx = -1; + fp->uf_prof_initialized = true; + } + + fp->uf_profiling = true; +} + +/// Prepare profiling for entering a child or something else that is not +/// counted for the script/function itself. +/// Should always be called in pair with prof_child_exit(). +/// +/// @param tm place to store waittime +void prof_child_enter(proftime_T *tm) +{ + funccall_T *fc = get_current_funccal(); + + if (fc != NULL && fc->func->uf_profiling) { + fc->prof_child = profile_start(); + } + + script_prof_save(tm); +} + +/// Take care of time spent in a child. +/// Should always be called after prof_child_enter(). +/// +/// @param tm where waittime was stored +void prof_child_exit(proftime_T *tm) +{ + funccall_T *fc = get_current_funccal(); + + if (fc != NULL && fc->func->uf_profiling) { + fc->prof_child = profile_end(fc->prof_child); + // don't count waiting time + fc->prof_child = profile_sub_wait(*tm, fc->prof_child); + fc->func->uf_tm_children = + profile_add(fc->func->uf_tm_children, fc->prof_child); + fc->func->uf_tml_children = + profile_add(fc->func->uf_tml_children, fc->prof_child); + } + script_prof_restore(tm); +} + +/// Called when starting to read a function line. +/// "sourcing_lnum" must be correct! +/// When skipping lines it may not actually be executed, but we won't find out +/// until later and we need to store the time now. +void func_line_start(void *cookie) +{ + funccall_T *fcp = (funccall_T *)cookie; + ufunc_T *fp = fcp->func; + + if (fp->uf_profiling && SOURCING_LNUM >= 1 && SOURCING_LNUM <= fp->uf_lines.ga_len) { + fp->uf_tml_idx = SOURCING_LNUM - 1; + // Skip continuation lines. + while (fp->uf_tml_idx > 0 && FUNCLINE(fp, fp->uf_tml_idx) == NULL) { + fp->uf_tml_idx--; + } + fp->uf_tml_execed = false; + fp->uf_tml_start = profile_start(); + fp->uf_tml_children = profile_zero(); + fp->uf_tml_wait = profile_get_wait(); + } +} + +/// Called when actually executing a function line. +void func_line_exec(void *cookie) +{ + funccall_T *fcp = (funccall_T *)cookie; + ufunc_T *fp = fcp->func; + + if (fp->uf_profiling && fp->uf_tml_idx >= 0) { + fp->uf_tml_execed = true; + } +} + +/// Called when done with a function line. +void func_line_end(void *cookie) +{ + funccall_T *fcp = (funccall_T *)cookie; + ufunc_T *fp = fcp->func; + + if (fp->uf_profiling && fp->uf_tml_idx >= 0) { + if (fp->uf_tml_execed) { + fp->uf_tml_count[fp->uf_tml_idx]++; + fp->uf_tml_start = profile_end(fp->uf_tml_start); + fp->uf_tml_start = profile_sub_wait(fp->uf_tml_wait, fp->uf_tml_start); + fp->uf_tml_total[fp->uf_tml_idx] = + profile_add(fp->uf_tml_total[fp->uf_tml_idx], fp->uf_tml_start); + fp->uf_tml_self[fp->uf_tml_idx] = + profile_self(fp->uf_tml_self[fp->uf_tml_idx], fp->uf_tml_start, + fp->uf_tml_children); + } + fp->uf_tml_idx = -1; + } +} + +/// Dump the profiling results for all functions in file "fd". +static void func_dump_profile(FILE *fd) +{ + hashtab_T *const functbl = func_tbl_get(); + hashitem_T *hi; + int todo; + ufunc_T *fp; + ufunc_T **sorttab; + int st_len = 0; + + todo = (int)functbl->ht_used; + if (todo == 0) { + return; // nothing to dump + } + + sorttab = xmalloc(sizeof(ufunc_T *) * (size_t)todo); + + for (hi = functbl->ht_array; todo > 0; hi++) { + if (!HASHITEM_EMPTY(hi)) { + todo--; + fp = HI2UF(hi); + if (fp->uf_prof_initialized) { + sorttab[st_len++] = fp; + + if (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); + } + if (fp->uf_script_ctx.sc_sid != 0) { + bool should_free; + const LastSet last_set = (LastSet){ + .script_ctx = fp->uf_script_ctx, + .channel_id = 0, + }; + char *p = get_scriptname(last_set, &should_free); + fprintf(fd, " Defined: %s:%" PRIdLINENR "\n", + p, fp->uf_script_ctx.sc_lnum); + if (should_free) { + xfree(p); + } + } + if (fp->uf_tm_count == 1) { + fprintf(fd, "Called 1 time\n"); + } else { + fprintf(fd, "Called %d times\n", fp->uf_tm_count); + } + fprintf(fd, "Total time: %s\n", profile_msg(fp->uf_tm_total)); + fprintf(fd, " Self time: %s\n", profile_msg(fp->uf_tm_self)); + fprintf(fd, "\n"); + fprintf(fd, "count total (s) self (s)\n"); + + for (int i = 0; i < fp->uf_lines.ga_len; i++) { + if (FUNCLINE(fp, i) == NULL) { + continue; + } + prof_func_line(fd, fp->uf_tml_count[i], + &fp->uf_tml_total[i], &fp->uf_tml_self[i], true); + fprintf(fd, "%s\n", FUNCLINE(fp, i)); + } + fprintf(fd, "\n"); + } + } + } + + if (st_len > 0) { + qsort((void *)sorttab, (size_t)st_len, sizeof(ufunc_T *), + prof_total_cmp); + prof_sort_list(fd, sorttab, st_len, "TOTAL", false); + qsort((void *)sorttab, (size_t)st_len, sizeof(ufunc_T *), + prof_self_cmp); + prof_sort_list(fd, sorttab, st_len, "SELF", true); + } + + xfree(sorttab); +} + +/// Start profiling a script. +void profile_init(scriptitem_T *si) +{ + si->sn_pr_count = 0; + si->sn_pr_total = profile_zero(); + si->sn_pr_self = profile_zero(); + + ga_init(&si->sn_prl_ga, sizeof(sn_prl_T), 100); + si->sn_prl_idx = -1; + si->sn_prof_on = true; + si->sn_pr_nest = 0; +} + +/// Save time when starting to invoke another script or function. +/// +/// @param tm place to store wait time +void script_prof_save(proftime_T *tm) +{ + scriptitem_T *si; + + 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_start(); + } + } + *tm = profile_get_wait(); +} + +/// Count time spent in children after invoking another script or function. +void script_prof_restore(proftime_T *tm) +{ + scriptitem_T *si; + + 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); + } + } +} + +/// Dump the profiling results for all scripts in file "fd". +static void script_dump_profile(FILE *fd) +{ + scriptitem_T *si; + FILE *sfd; + sn_prl_T *pp; + + for (int id = 1; id <= script_items.ga_len; id++) { + si = &SCRIPT_ITEM(id); + if (si->sn_prof_on) { + fprintf(fd, "SCRIPT %s\n", si->sn_name); + if (si->sn_pr_count == 1) { + fprintf(fd, "Sourced 1 time\n"); + } else { + fprintf(fd, "Sourced %d times\n", si->sn_pr_count); + } + fprintf(fd, "Total time: %s\n", profile_msg(si->sn_pr_total)); + fprintf(fd, " Self time: %s\n", profile_msg(si->sn_pr_self)); + fprintf(fd, "\n"); + fprintf(fd, "count total (s) self (s)\n"); + + sfd = os_fopen(si->sn_name, "r"); + if (sfd == NULL) { + fprintf(fd, "Cannot open file!\n"); + } else { + // Keep going till the end of file, so that trailing + // continuation lines are listed. + for (int i = 0;; i++) { + if (vim_fgets(IObuff, IOSIZE, sfd)) { + break; + } + // When a line has been truncated, append NL, taking care + // of multi-byte characters . + if (IObuff[IOSIZE - 2] != NUL && IObuff[IOSIZE - 2] != NL) { + int n = IOSIZE - 2; + + // Move to the first byte of this char. + // utf_head_off() doesn't work, because it checks + // for a truncated character. + while (n > 0 && (IObuff[n] & 0xc0) == 0x80) { + n--; + } + + IObuff[n] = NL; + IObuff[n + 1] = NUL; + } + if (i < si->sn_prl_ga.ga_len + && (pp = &PRL_ITEM(si, i))->snp_count > 0) { + fprintf(fd, "%5d ", pp->snp_count); + if (profile_equal(pp->sn_prl_total, pp->sn_prl_self)) { + fprintf(fd, " "); + } else { + fprintf(fd, "%s ", profile_msg(pp->sn_prl_total)); + } + fprintf(fd, "%s ", profile_msg(pp->sn_prl_self)); + } else { + fprintf(fd, " "); + } + fprintf(fd, "%s", IObuff); + } + fclose(sfd); + } + fprintf(fd, "\n"); + } + } +} + +/// Dump the profiling info. +void profile_dump(void) +{ + FILE *fd; + + 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); + } + } +} + +/// Called when starting to read a script line. +/// "sourcing_lnum" must be correct! +/// When skipping lines it may not actually be executed, but we won't find out +/// until later and we need to store the time now. +void script_line_start(void) +{ + scriptitem_T *si; + sn_prl_T *pp; + + if (current_sctx.sc_sid <= 0 || current_sctx.sc_sid > script_items.ga_len) { + return; + } + si = &SCRIPT_ITEM(current_sctx.sc_sid); + if (si->sn_prof_on && SOURCING_LNUM >= 1) { + // Grow the array before starting the timer, so that the time spent + // here isn't counted. + (void)ga_grow(&si->sn_prl_ga, SOURCING_LNUM - si->sn_prl_ga.ga_len); + si->sn_prl_idx = SOURCING_LNUM - 1; + while (si->sn_prl_ga.ga_len <= si->sn_prl_idx + && si->sn_prl_ga.ga_len < si->sn_prl_ga.ga_maxlen) { + // Zero counters for a line that was not used before. + pp = &PRL_ITEM(si, si->sn_prl_ga.ga_len); + pp->snp_count = 0; + pp->sn_prl_total = profile_zero(); + pp->sn_prl_self = profile_zero(); + si->sn_prl_ga.ga_len++; + } + si->sn_prl_execed = false; + si->sn_prl_start = profile_start(); + si->sn_prl_children = profile_zero(); + si->sn_prl_wait = profile_get_wait(); + } +} + +/// Called when actually executing a function line. +void script_line_exec(void) +{ + scriptitem_T *si; + + if (current_sctx.sc_sid <= 0 || current_sctx.sc_sid > script_items.ga_len) { + return; + } + si = &SCRIPT_ITEM(current_sctx.sc_sid); + if (si->sn_prof_on && si->sn_prl_idx >= 0) { + si->sn_prl_execed = true; + } +} + +/// Called when done with a function line. +void script_line_end(void) +{ + scriptitem_T *si; + sn_prl_T *pp; + + if (current_sctx.sc_sid <= 0 || current_sctx.sc_sid > script_items.ga_len) { + return; + } + si = &SCRIPT_ITEM(current_sctx.sc_sid); + if (si->sn_prof_on && si->sn_prl_idx >= 0 + && si->sn_prl_idx < si->sn_prl_ga.ga_len) { + if (si->sn_prl_execed) { + pp = &PRL_ITEM(si, si->sn_prl_idx); + pp->snp_count++; + si->sn_prl_start = profile_end(si->sn_prl_start); + si->sn_prl_start = profile_sub_wait(si->sn_prl_wait, si->sn_prl_start); + pp->sn_prl_total = profile_add(pp->sn_prl_total, si->sn_prl_start); + pp->sn_prl_self = profile_self(pp->sn_prl_self, si->sn_prl_start, + si->sn_prl_children); + } + si->sn_prl_idx = -1; + } +} + /// globals for use in the startuptime related functionality (time_*). static proftime_T g_start_time; static proftime_T g_prev_time; diff --git a/src/nvim/profile.h b/src/nvim/profile.h index 17c35c5eb7..547d11185f 100644 --- a/src/nvim/profile.h +++ b/src/nvim/profile.h @@ -4,7 +4,8 @@ #include <stdint.h> #include <time.h> -typedef uint64_t proftime_T; +#include "nvim/ex_cmds_defs.h" +#include "nvim/runtime.h" #define TIME_MSG(s) do { \ if (time_fd != NULL) time_msg(s, NULL); \ diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 99129bd15e..f9c4892b91 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -9,10 +9,12 @@ #include <string.h> #include "nvim/api/private/helpers.h" +#include "nvim/arglist.h" #include "nvim/ascii.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/ex_cmds.h" @@ -22,6 +24,7 @@ #include "nvim/ex_getln.h" #include "nvim/fileio.h" #include "nvim/fold.h" +#include "nvim/help.h" #include "nvim/highlight_group.h" #include "nvim/mark.h" #include "nvim/mbyte.h" @@ -31,13 +34,13 @@ #include "nvim/move.h" #include "nvim/normal.h" #include "nvim/option.h" +#include "nvim/optionstr.h" #include "nvim/os/input.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/screen.h" #include "nvim/search.h" #include "nvim/strings.h" #include "nvim/ui.h" @@ -64,9 +67,9 @@ struct qfline_S { char *qf_module; ///< module name for this error char *qf_pattern; ///< search pattern for the error char *qf_text; ///< description of the error - char qf_viscol; ///< set to TRUE if qf_col and qf_end_col is + char qf_viscol; ///< set to true if qf_col and qf_end_col is // screen column - char qf_cleared; ///< set to TRUE if line has been deleted + char qf_cleared; ///< set to true if line has been deleted char qf_type; ///< type of the error (mostly 'E'); 1 for :helpgrep char qf_valid; ///< valid error message detected }; @@ -97,7 +100,7 @@ typedef struct qf_list_S { qfline_T *qf_ptr; ///< pointer to the current error int qf_count; ///< number of errors (0 means empty list) int qf_index; ///< current index in the error list - int qf_nonevalid; ///< TRUE if not a single valid entry found + int qf_nonevalid; ///< true if not a single valid entry found char *qf_title; ///< title derived from the command that created ///< the error list or set by setqflist typval_T *qf_ctx; ///< context set by setqflist/setloclist @@ -604,7 +607,7 @@ static efm_T *parse_efm_option(char *efm) goto parse_efm_error; } // Advance to next part - efm = (char *)skip_to_option_part((char_u *)efm + len); // skip comma and spaces + efm = skip_to_option_part(efm + len); // skip comma and spaces } if (fmt_first == NULL) { // nothing found @@ -796,7 +799,7 @@ retry: // Convert a line if it contains a non-ASCII character if (state->vc.vc_type != CONV_NONE && has_non_ascii((char_u *)state->linebuf)) { - char *line = (char *)string_convert(&state->vc, (char_u *)state->linebuf, &state->linelen); + char *line = string_convert(&state->vc, state->linebuf, &state->linelen); if (line != NULL) { if (state->linelen < IOSIZE) { STRLCPY(state->linebuf, line, state->linelen + 1); @@ -997,7 +1000,7 @@ static int qf_setup_state(qfstate_T *pstate, char *restrict enc, const char *res { pstate->vc.vc_type = CONV_NONE; if (enc != NULL && *enc != NUL) { - convert_setup(&pstate->vc, (char_u *)enc, p_enc); + convert_setup(&pstate->vc, enc, p_enc); } if (efile != NULL @@ -1088,7 +1091,7 @@ static int qf_init_ext(qf_info_T *qi, int qf_idx, const char *restrict efile, bu // Use the local value of 'errorformat' if it's set. if (errorformat == p_efm && tv == NULL && buf && *buf->b_p_efm != NUL) { - efm = (char *)buf->b_p_efm; + efm = buf->b_p_efm; } else { efm = errorformat; } @@ -1224,7 +1227,7 @@ static void qf_new_list(qf_info_T *qi, const char *qf_title) qi->qf_curlist = qi->qf_listcount++; } qf_list_T *qfl = qf_get_curlist(qi); - memset(qfl, 0, sizeof(qf_list_T)); + CLEAR_POINTER(qfl); qf_store_title(qfl, qf_title); qfl->qfl_type = qi->qfl_type; qfl->qf_id = ++last_qf_id; @@ -1241,7 +1244,7 @@ static int qf_parse_fmt_f(regmatch_T *rmp, int midx, qffields_T *fields, int pre // Expand ~/file and $HOME/file to full path. char c = (char)(*rmp->endp[midx]); *rmp->endp[midx] = NUL; - expand_env(rmp->startp[midx], (char_u *)fields->namebuf, CMDBUFFSIZE); + expand_env((char *)rmp->startp[midx], fields->namebuf, CMDBUFFSIZE); *rmp->endp[midx] = (char_u)c; // For separate filename patterns (%O, %P and %Q), the specified file @@ -1888,7 +1891,7 @@ static qf_info_T *ll_get_or_alloc_list(win_T *wp) /// Get the quickfix/location list stack to use for the specified Ex command. /// For a location list command, returns the stack for the current window. If /// the location list is not found, then returns NULL and prints an error -/// message if 'print_emsg' is TRUE. +/// message if 'print_emsg' is true. static qf_info_T *qf_cmd_get_stack(exarg_T *eap, int print_emsg) { qf_info_T *qi = &ql_info; @@ -2126,7 +2129,7 @@ static char *qf_push_dir(char *dirbuf, struct dir_stack_T **stackptr, bool is_fi while (ds_new) { xfree((*stackptr)->dirname); (*stackptr)->dirname = concat_fnames(ds_new->dirname, dirbuf, true); - if (os_isdir((char_u *)(*stackptr)->dirname)) { + if (os_isdir((*stackptr)->dirname)) { break; } @@ -2508,7 +2511,7 @@ static win_T *qf_find_win_with_normal_buf(void) } // Go to a window in any tabpage containing the specified file. Returns true -// if successfully jumped to the window. Otherwise returns FALSE. +// if successfully jumped to the window. Otherwise returns false. static bool qf_goto_tabwin_with_file(int fnum) { FOR_ALL_TAB_WINDOWS(tp, wp) { @@ -2920,7 +2923,7 @@ void qf_jump(qf_info_T *qi, int dir, int errornr, int forceit) // If 'newwin' is true, then open the file in a new window. static void qf_jump_newwin(qf_info_T *qi, int dir, int errornr, int forceit, bool newwin) { - char *old_swb = (char *)p_swb; + char *old_swb = p_swb; unsigned old_swb_flags = swb_flags; const bool old_KeyTyped = KeyTyped; // getting file may reset it @@ -2929,7 +2932,7 @@ static void qf_jump_newwin(qf_info_T *qi, int dir, int errornr, int forceit, boo } if (qf_stack_empty(qi) || qf_list_empty(qf_get_curlist(qi))) { - emsg(_(e_quickfix)); + emsg(_(e_no_errors)); return; } @@ -2991,10 +2994,10 @@ theend: qfl->qf_ptr = qf_ptr; qfl->qf_index = qf_index; } - if (p_swb != (char_u *)old_swb && p_swb == empty_option) { + if (p_swb != old_swb && p_swb == empty_option) { // Restore old 'switchbuf' value, but not when an autocommand or // modeline has changed the value. - p_swb = (char_u *)old_swb; + p_swb = old_swb; swb_flags = old_swb_flags; } decr_quickfix_busy(); @@ -3035,23 +3038,23 @@ static void qf_list_entry(qfline_T *qfp, int qf_idx, bool cursel) // text of the entry. bool filter_entry = true; if (qfp->qf_module != NULL && *qfp->qf_module != NUL) { - filter_entry &= message_filtered((char_u *)qfp->qf_module); + filter_entry &= message_filtered(qfp->qf_module); } if (filter_entry && fname != NULL) { - filter_entry &= message_filtered((char_u *)fname); + filter_entry &= message_filtered(fname); } if (filter_entry && qfp->qf_pattern != NULL) { - filter_entry &= message_filtered((char_u *)qfp->qf_pattern); + filter_entry &= message_filtered(qfp->qf_pattern); } if (filter_entry) { - filter_entry &= message_filtered((char_u *)qfp->qf_text); + filter_entry &= message_filtered(qfp->qf_text); } if (filter_entry) { return; } msg_putchar('\n'); - msg_outtrans_attr(IObuff, cursel ? HL_ATTR(HLF_QFL) : qfFileAttr); + msg_outtrans_attr((char *)IObuff, cursel ? HL_ATTR(HLF_QFL) : qfFileAttr); if (qfp->qf_lnum != 0) { msg_puts_attr(":", qfSepAttr); @@ -3108,7 +3111,7 @@ void qf_list(exarg_T *eap) } if (qf_stack_empty(qi) || qf_list_empty(qf_get_curlist(qi))) { - emsg(_(e_quickfix)); + emsg(_(e_no_errors)); return; } @@ -3119,7 +3122,7 @@ void qf_list(exarg_T *eap) } int idx1 = 1; int idx2 = -1; - if (!get_list_range((char_u **)&arg, &idx1, &idx2) || *arg != NUL) { + if (!get_list_range(&arg, &idx1, &idx2) || *arg != NUL) { semsg(_(e_trailing_arg), arg); return; } @@ -3177,7 +3180,7 @@ static void qf_fmt_text(const char *restrict text, char *restrict buf, int bufsi int i; const char *p = (char *)text; - for (i = 0; *p != NUL && i < bufsize - 1; ++i) { + for (i = 0; *p != NUL && i < bufsize - 1; i++) { if (*p == '\n') { buf[i] = ' '; while (*++p != NUL) { @@ -3260,13 +3263,13 @@ void qf_age(exarg_T *eap) emsg(_("E380: At bottom of quickfix stack")); break; } - --qi->qf_curlist; + qi->qf_curlist--; } else { if (qi->qf_curlist >= qi->qf_listcount - 1) { emsg(_("E381: At top of quickfix stack")); break; } - ++qi->qf_curlist; + qi->qf_curlist++; } } qf_msg(qi, qi->qf_curlist, ""); @@ -3390,7 +3393,7 @@ bool qf_mark_adjust(win_T *wp, linenr_T line1, linenr_T line2, linenr_T amount, found_one = true; if (qfp->qf_lnum >= line1 && qfp->qf_lnum <= line2) { if (amount == MAXLNUM) { - qfp->qf_cleared = TRUE; + qfp->qf_cleared = true; } else { qfp->qf_lnum += amount; } @@ -3464,7 +3467,7 @@ void qf_view_result(bool split) qi = GET_LOC_LIST(curwin); } if (qf_list_empty(qf_get_curlist(qi))) { - emsg(_(e_quickfix)); + emsg(_(e_no_errors)); return; } @@ -3555,12 +3558,12 @@ static int qf_goto_cwindow(const qf_info_T *qi, bool resize, int sz, bool vertsp static void qf_set_cwindow_options(void) { // switch off 'swapfile' - set_option_value("swf", 0L, NULL, OPT_LOCAL); - set_option_value("bt", 0L, "quickfix", OPT_LOCAL); - set_option_value("bh", 0L, "hide", OPT_LOCAL); + set_option_value_give_err("swf", 0L, NULL, OPT_LOCAL); + set_option_value_give_err("bt", 0L, "quickfix", OPT_LOCAL); + set_option_value_give_err("bh", 0L, "hide", OPT_LOCAL); RESET_BINDING(curwin); curwin->w_p_diff = false; - set_option_value("fdm", 0L, "manual", OPT_LOCAL); + set_option_value_give_err("fdm", 0L, "manual", OPT_LOCAL); } // Open a new quickfix or location list window, load the quickfix buffer and @@ -3707,7 +3710,7 @@ static void qf_win_goto(win_T *win, linenr_T lnum) curwin->w_cursor.coladd = 0; curwin->w_curswant = 0; update_topline(curwin); // scroll to show the line - redraw_later(curwin, VALID); + redraw_later(curwin, UPD_VALID); curwin->w_redr_status = true; // update ruler curwin = old_curwin; curbuf = curwin->w_buffer; @@ -3744,7 +3747,7 @@ linenr_T qf_current_entry(win_T *wp) } /// Update the cursor position in the quickfix window to the current error. -/// Return TRUE if there is a quickfix window. +/// Return true if there is a quickfix window. /// /// @param old_qf_index previous qf_index or zero static bool qf_win_pos_update(qf_info_T *qi, int old_qf_index) @@ -3827,38 +3830,11 @@ static buf_T *qf_find_buf(qf_info_T *qi) return NULL; } -// Process the 'quickfixtextfunc' option value. -bool qf_process_qftf_option(void) +/// Process the 'quickfixtextfunc' option value. +/// @return OK or FAIL +int qf_process_qftf_option(void) { - if (p_qftf == NULL || *p_qftf == NUL) { - callback_free(&qftf_cb); - return true; - } - - typval_T *tv; - if (*p_qftf == '{') { - // Lambda expression - tv = eval_expr((char *)p_qftf); - if (tv == NULL) { - return false; - } - } else { - // treat everything else as a function name string - tv = xcalloc(1, sizeof(*tv)); - tv->v_type = VAR_STRING; - tv->vval.v_string = (char *)vim_strsave(p_qftf); - } - - Callback cb; - if (!callback_from_typval(&cb, tv)) { - tv_free(tv); - return false; - } - - callback_free(&qftf_cb); - qftf_cb = cb; - tv_free(tv); - return true; + return option_set_callback_func((char_u *)p_qftf, &qftf_cb); } /// Update the w:quickfix_title variable in the quickfix/location list window in @@ -3922,7 +3898,7 @@ static void qf_update_buffer(qf_info_T *qi, qfline_T *old_last) // 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, NOT_VALID); + redraw_buf_later(buf, UPD_NOT_VALID); } } } @@ -4132,7 +4108,7 @@ static void qf_fill_buffer(qf_list_T *qfl, buf_T *buf, qfline_T *old_last, int q // resembles reading a file into a buffer, it's more logical when using // autocommands. curbuf->b_ro_locked++; - set_option_value("ft", 0L, "qf", OPT_LOCAL); + set_option_value_give_err("ft", 0L, "qf", OPT_LOCAL); curbuf->b_p_ma = false; keep_filetype = true; // don't detect 'filetype' @@ -4142,7 +4118,7 @@ static void qf_fill_buffer(qf_list_T *qfl, buf_T *buf, qfline_T *old_last, int q curbuf->b_ro_locked--; // make sure it will be redrawn - redraw_curbuf_later(NOT_VALID); + redraw_curbuf_later(UPD_NOT_VALID); } // Restore KeyTyped, setting 'filetype' may reset it. @@ -4199,7 +4175,7 @@ static void qf_jump_first(qf_info_T *qi, unsigned save_qfid, int forceit) } } -// Return TRUE when using ":vimgrep" for ":grep". +// Return true when using ":vimgrep" for ":grep". int grep_internal(cmdidx_T cmdidx) { return (cmdidx == CMD_grep @@ -4207,7 +4183,7 @@ int grep_internal(cmdidx_T cmdidx) || cmdidx == CMD_grepadd || cmdidx == CMD_lgrepadd) && STRCMP("internal", - *curbuf->b_p_gp == NUL ? p_gp : curbuf->b_p_gp) == 0; + *curbuf->b_p_gp == NUL ? p_gp : (char_u *)curbuf->b_p_gp) == 0; } // Return the make/grep autocmd name. @@ -4246,7 +4222,7 @@ static char *make_get_fullcmd(const char *makecmd, const char *fname) // If 'shellpipe' empty: don't redirect to 'errorfile'. if (*p_sp != NUL) { - append_redir(cmd, len, (char *)p_sp, (char *)fname); + append_redir(cmd, len, p_sp, (char *)fname); } // Display the fully formed command. Output a newline if there's something @@ -4265,7 +4241,7 @@ static char *make_get_fullcmd(const char *makecmd, const char *fname) // Used for ":make", ":lmake", ":grep", ":lgrep", ":grepadd", and ":lgrepadd" void ex_make(exarg_T *eap) { - char *enc = (*curbuf->b_p_menc != NUL) ? (char *)curbuf->b_p_menc : (char *)p_menc; + char *enc = (*curbuf->b_p_menc != NUL) ? curbuf->b_p_menc : p_menc; // Redirect ":grep" to ":vimgrep" if 'grepprg' is "internal". if (grep_internal(eap->cmdidx)) { @@ -4352,7 +4328,7 @@ static char *get_mef_name(void) char *p; - for (p = p_mef; *p; ++p) { + for (p = p_mef; *p; p++) { if (p[0] == '#' && p[1] == '#') { break; } @@ -4856,7 +4832,7 @@ static void qf_get_nth_below_entry(qfline_T *entry_arg, linenr_T n, bool linewis } /// Get the nth quickfix entry above the specified entry. Searches backwards in -/// the list. If linewise is TRUE, then treat multiple entries on a single line +/// the list. If linewise is true, then treat multiple entries on a single line /// as one. static void qf_get_nth_above_entry(qfline_T *entry, linenr_T n, bool linewise, int *errornr) FUNC_ATTR_NONNULL_ALL @@ -4922,7 +4898,7 @@ void ex_cbelow(exarg_T *eap) || eap->cmdidx == CMD_cafter) ? BUF_HAS_QF_ENTRY : BUF_HAS_LL_ENTRY; if (!(curbuf->b_has_qf_entry & buf_has_flag)) { - emsg(_(e_quickfix)); + emsg(_(e_no_errors)); return; } @@ -4934,7 +4910,7 @@ void ex_cbelow(exarg_T *eap) qf_list_T *qfl = qf_get_curlist(qi); // check if the list has valid errors if (!qf_list_has_valid_entries(qfl)) { - emsg(_(e_quickfix)); + emsg(_(e_no_errors)); return; } @@ -5005,7 +4981,7 @@ void ex_cfile(exarg_T *eap) set_string_option_direct("ef", -1, eap->arg, OPT_FREE, 0); } - char *enc = (*curbuf->b_p_menc != NUL) ? (char *)curbuf->b_p_menc : (char *)p_menc; + char *enc = (*curbuf->b_p_menc != NUL) ? curbuf->b_p_menc : p_menc; if (is_loclist_cmd(eap->cmdidx)) { wp = curwin; @@ -5164,6 +5140,7 @@ static bool vgr_match_buflines(qf_list_T *qfl, char *fname, buf_T *buf, char *sp FUNC_ATTR_NONNULL_ARG(1, 3, 4, 5, 6) { bool found_match = false; + const size_t pat_len = STRLEN(spat); for (linenr_T lnum = 1; lnum <= buf->b_ml.ml_line_count && *tomatch > 0; lnum++) { colnr_T col = 0; @@ -5205,7 +5182,6 @@ static bool vgr_match_buflines(qf_list_T *qfl, char *fname, buf_T *buf, char *sp } } } else { - const size_t pat_len = STRLEN(spat); char *const str = (char *)ml_get_buf(buf, lnum, false); int score; uint32_t matches[MAX_FUZZY_MATCHES]; @@ -5299,7 +5275,7 @@ static bool existing_swapfile(const buf_T *buf) /// :{count}vimgrep /{pattern}/[g][j] {file} ... static int vgr_process_args(exarg_T *eap, vgr_args_T *args) { - memset(args, 0, sizeof(*args)); + CLEAR_POINTER(args); args->regmatch.regprog = NULL; args->qf_title = xstrdup(qf_cmdtitle(*eap->cmdlinep)); @@ -5329,7 +5305,7 @@ 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((char_u *)p, &args->fcount, &args->fnames, true) == FAIL) { + if (get_arglist_exp(p, &args->fcount, &args->fnames, true) == FAIL) { return FAIL; } if (args->fcount == 0) { @@ -5453,7 +5429,7 @@ static int vgr_process_files(win_T *wp, qf_info_T *qi, vgr_args_T *cmd_args, boo // options! aco_save_T aco; aucmd_prepbuf(&aco, buf); - apply_autocmds(EVENT_FILETYPE, (char *)buf->b_p_ft, buf->b_fname, true, buf); + apply_autocmds(EVENT_FILETYPE, buf->b_p_ft, buf->b_fname, true, buf); do_modelines(OPT_NOWIN); aucmd_restbuf(&aco); } @@ -5633,7 +5609,7 @@ static buf_T *load_dummy_buffer(char *fname, char *dirname_start, char *resultin if (readfile_result == OK && !got_int && !(curbuf->b_flags & BF_NEW)) { - failed = FALSE; + failed = false; if (curbuf != newbuf) { // Bloody autocommands changed the buffer! Can happen when // using netrw and editing a remote file. Use the current @@ -5701,7 +5677,7 @@ static void wipe_dummy_buffer(buf_T *buf, char *dirname_start) cleanup_T cs; // Reset the error/interrupt/exception state here so that aborting() - // returns FALSE when wiping out the buffer. Otherwise it doesn't + // returns false when wiping out the buffer. Otherwise it doesn't // work when got_int is set. enter_cleanup(&cs); @@ -5779,7 +5755,7 @@ static int get_qfline_items(qfline_T *qfp, list_T *list) /// If qf_idx is -1, use the current list. Otherwise, use the specified list. /// If eidx is not 0, then return only the specified entry. Otherwise return /// all the entries. -int get_errorlist(qf_info_T *qi_arg, win_T *wp, int qf_idx, int eidx, list_T *list) +static int get_errorlist(qf_info_T *qi_arg, win_T *wp, int qf_idx, int eidx, list_T *list) { qf_info_T *qi = qi_arg; @@ -6149,7 +6125,7 @@ static int qf_getprop_qftf(qf_list_T *qfl, dict_T *retdict) /// Return quickfix/location list details (title) as a dictionary. /// 'what' contains the details to return. If 'list_idx' is -1, /// then current list is used. Otherwise the specified list is used. -int qf_get_properties(win_T *wp, dict_T *what, dict_T *retdict) +static int qf_get_properties(win_T *wp, dict_T *what, dict_T *retdict) { qf_info_T *qi = &ql_info; dictitem_T *di = NULL; @@ -7057,7 +7033,7 @@ static void hgr_search_in_rtp(qf_list_T *qfl, regmatch_T *p_regmatch, const char FUNC_ATTR_NONNULL_ARG(1, 2) { // Go through all directories in 'runtimepath' - char *p = (char *)p_rtp; + char *p = p_rtp; while (*p != NUL && !got_int) { copy_option_part(&p, (char *)NameBuff, MAXPATHL, ","); @@ -7086,9 +7062,10 @@ 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; - p_cpo = (char *)empty_option; + p_cpo = empty_option; bool new_qi = false; if (is_loclist_cmd(eap->cmdidx)) { @@ -7116,14 +7093,24 @@ void ex_helpgrep(exarg_T *eap) qfl->qf_ptr = qfl->qf_start; qfl->qf_index = 1; qf_list_changed(qfl); - qf_update_buffer(qi, NULL); + updated = true; } - if ((char_u *)p_cpo == empty_option) { + if (p_cpo == empty_option) { p_cpo = save_cpo; } else { - // Darn, some plugin changed the value. - free_string_option((char_u *)save_cpo); + // Darn, some plugin changed the value. If it's still empty it was + // changed and restored, need to restore in the complicated way. + if (*p_cpo == NUL) { + set_option_value_give_err("cpo", 0L, save_cpo, 0); + } + free_string_option(save_cpo); + } + + if (updated) { + // This may open a window and source scripts, do this after 'cpo' was + // restored. + qf_update_buffer(qi, NULL); } if (au_name != NULL) { @@ -7157,3 +7144,137 @@ void ex_helpgrep(exarg_T *eap) } } } + +static void get_qf_loc_list(int is_qf, win_T *wp, typval_T *what_arg, typval_T *rettv) +{ + if (what_arg->v_type == VAR_UNKNOWN) { + tv_list_alloc_ret(rettv, kListLenMayKnow); + if (is_qf || wp != NULL) { + (void)get_errorlist(NULL, wp, -1, 0, rettv->vval.v_list); + } + } else { + tv_dict_alloc_ret(rettv); + if (is_qf || wp != NULL) { + if (what_arg->v_type == VAR_DICT) { + dict_T *d = what_arg->vval.v_dict; + + if (d != NULL) { + qf_get_properties(wp, d, rettv->vval.v_dict); + } + } else { + emsg(_(e_dictreq)); + } + } + } +} + +/// "getloclist()" function +void f_getloclist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + win_T *wp = find_win_by_nr_or_id(&argvars[0]); + get_qf_loc_list(false, wp, &argvars[1], rettv); +} + +/// "getqflist()" functions +void f_getqflist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + get_qf_loc_list(true, NULL, &argvars[0], rettv); +} + +/// Create quickfix/location list from VimL values +/// +/// Used by `setqflist()` and `setloclist()` functions. Accepts invalid +/// args argument in which case errors out, including VAR_UNKNOWN parameters. +/// +/// @param[in,out] wp Window to create location list for. May be NULL in +/// which case quickfix list will be created. +/// @param[in] args [list, action, what] +/// @param[in] args[0] Quickfix list contents. +/// @param[in] args[1] Optional. Action to perform: +/// append to an existing list, replace its content, +/// or create a new one. +/// @param[in] args[2] Optional. Quickfix list properties or title. +/// Defaults to caller function name. +/// @param[out] rettv Return value: 0 in case of success, -1 otherwise. +static void set_qf_ll_list(win_T *wp, typval_T *args, typval_T *rettv) + FUNC_ATTR_NONNULL_ARG(2, 3) +{ + static char *e_invact = N_("E927: Invalid action: '%s'"); + const char *title = NULL; + char action = ' '; + static int recursive = 0; + rettv->vval.v_number = -1; + dict_T *what = NULL; + + typval_T *list_arg = &args[0]; + if (list_arg->v_type != VAR_LIST) { + emsg(_(e_listreq)); + return; + } else if (recursive != 0) { + emsg(_(e_au_recursive)); + return; + } + + typval_T *action_arg = &args[1]; + if (action_arg->v_type == VAR_UNKNOWN) { + // Option argument was not given. + goto skip_args; + } else if (action_arg->v_type != VAR_STRING) { + emsg(_(e_stringreq)); + return; + } + const char *const act = tv_get_string_chk(action_arg); + if ((*act == 'a' || *act == 'r' || *act == ' ' || *act == 'f') + && act[1] == NUL) { + action = *act; + } else { + semsg(_(e_invact), act); + return; + } + + typval_T *const what_arg = &args[2]; + if (what_arg->v_type == VAR_UNKNOWN) { + // Option argument was not given. + goto skip_args; + } else if (what_arg->v_type == VAR_STRING) { + title = tv_get_string_chk(what_arg); + if (!title) { + // Type error. Error already printed by tv_get_string_chk(). + return; + } + } else if (what_arg->v_type == VAR_DICT && what_arg->vval.v_dict != NULL) { + what = what_arg->vval.v_dict; + } else { + emsg(_(e_dictreq)); + return; + } + +skip_args: + if (!title) { + title = (wp ? ":setloclist()" : ":setqflist()"); + } + + recursive++; + list_T *const l = list_arg->vval.v_list; + if (set_errorlist(wp, l, action, (char *)title, what) == OK) { + rettv->vval.v_number = 0; + } + recursive--; +} + +/// "setloclist()" function +void f_setloclist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + rettv->vval.v_number = -1; + + win_T *win = find_win_by_nr_or_id(&argvars[0]); + if (win != NULL) { + set_qf_ll_list(win, &argvars[1], rettv); + } +} + +/// "setqflist()" function +void f_setqflist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + set_qf_ll_list(NULL, argvars, rettv); +} diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c index fbbf904f8b..a52343e28b 100644 --- a/src/nvim/regexp.c +++ b/src/nvim/regexp.c @@ -20,7 +20,6 @@ #include "nvim/charset.h" #include "nvim/eval.h" #include "nvim/eval/userfunc.h" -#include "nvim/ex_cmds2.h" #include "nvim/garray.h" #include "nvim/mark.h" #include "nvim/memline.h" @@ -28,6 +27,7 @@ #include "nvim/message.h" #include "nvim/os/input.h" #include "nvim/plines.h" +#include "nvim/profile.h" #include "nvim/regexp.h" #include "nvim/strings.h" #include "nvim/vim.h" @@ -481,19 +481,17 @@ static char_u *skip_anyof(char *p) return (char_u *)p; } -/* - * Skip past regular expression. - * Stop at end of "startp" or where "dirc" is found ('/', '?', etc). - * Take care of characters with a backslash in front of it. - * Skip strings inside [ and ]. - * 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_u *skip_regexp(char_u *startp, int dirc, int magic, char_u **newp) +/// Skip past regular expression. +/// Stop at end of "startp" or where "dirc" is found ('/', '?', etc). +/// Take care of characters with a backslash in front of it. +/// Skip strings inside [ and ]. +/// 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) { int mymagic; - char_u *p = startp; + char *p = startp; if (magic) { mymagic = MAGIC_ON; @@ -508,7 +506,7 @@ char_u *skip_regexp(char_u *startp, int dirc, int magic, char_u **newp) } if ((p[0] == '[' && mymagic >= MAGIC_ON) || (p[0] == '\\' && p[1] == '[' && mymagic <= MAGIC_OFF)) { - p = skip_anyof((char *)p + 1); + p = (char *)skip_anyof(p + 1); if (p[0] == NUL) { break; } @@ -516,7 +514,7 @@ char_u *skip_regexp(char_u *startp, int dirc, int magic, char_u **newp) if (dirc == '?' && newp != NULL && p[1] == '?') { // change "\?" to "?", make a copy first. if (*newp == NULL) { - *newp = vim_strsave(startp); + *newp = xstrdup(startp); p = *newp + (p - startp); } STRMOVE(p, p + 1); @@ -823,7 +821,7 @@ static int64_t gethexchrs(int maxinputlen) } nr <<= 4; nr |= hex2nr(c); - ++regparse; + regparse++; } if (i == 0) { @@ -880,7 +878,7 @@ static int64_t getoctchrs(void) } nr <<= 3; nr |= hex2nr(c); - ++regparse; + regparse++; } if (i == 0) { @@ -1167,7 +1165,7 @@ static bool reg_match_visual(void) rex.line = reg_getline(rex.lnum); rex.input = rex.line + col; - unsigned int cols_u = win_linetabsize(wp, rex.line, col); + unsigned int cols_u = win_linetabsize(wp, rex.reg_firstlnum + rex.lnum, rex.line, col); assert(cols_u <= MAXCOL); colnr_T cols = (colnr_T)cols_u; if (cols < start || cols > end - (*p_sel == 'e')) { @@ -2097,8 +2095,8 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, int des dst++; } - ++s; - --len; + s++; + len--; } } } diff --git a/src/nvim/regexp_bt.c b/src/nvim/regexp_bt.c index fff5ae9b7f..88f0d781af 100644 --- a/src/nvim/regexp_bt.c +++ b/src/nvim/regexp_bt.c @@ -466,7 +466,7 @@ static void regcomp_start(char_u *expr, int re_flags) // num_complex_braces = 0; regnpar = 1; - memset(had_endbrace, 0, sizeof(had_endbrace)); + CLEAR_FIELD(had_endbrace); regnzpar = 1; re_has_z = 0; regsize = 0L; @@ -2189,7 +2189,7 @@ collection: while (*regparse != NUL && *regparse != ']') { if (*regparse == '-') { - ++regparse; + regparse++; // The '-' is not used for a range at the end and // after or before a '\n'. if (*regparse == ']' || *regparse == NUL @@ -2619,7 +2619,7 @@ static char_u *regpiece(int *flagp) regoptail(ret, regnode(BACK)); regoptail(ret, ret); reginsert_limits(BRACE_LIMITS, minval, maxval, ret); - ++num_complex_braces; + num_complex_braces++; } if (minval > 0 && maxval > 0) { *flagp = (HASWIDTH | (flags & (HASNL | HASLOOKBH))); @@ -2792,7 +2792,7 @@ static char_u *reg(int paren, int *flagp) EMSG2_RET_NULL(_("E51: Too many %s("), reg_magic == MAGIC_ALL); } parno = regnpar; - ++regnpar; + regnpar++; ret = regnode(MOPEN + parno); } else if (paren == REG_NPAREN) { // Make a NOPEN node. @@ -3181,7 +3181,7 @@ static int regrepeat(char_u *p, long maxcount) } else { break; } - ++count; + count++; } break; @@ -3299,7 +3299,7 @@ do_class: } else { break; } - ++count; + count++; } break; @@ -3415,7 +3415,7 @@ do_class: break; } scan += len; - ++count; + count++; } } } @@ -3453,7 +3453,7 @@ do_class: } scan++; } - ++count; + count++; } break; @@ -3764,6 +3764,7 @@ static bool regmatch(char_u *scan, proftime_T *tm, int *timed_out) case RE_VCOL: if (!re_num_cmp(win_linetabsize(rex.reg_win == NULL ? curwin : rex.reg_win, + rex.reg_firstlnum + rex.lnum, rex.line, (colnr_T)(rex.input - rex.line)) + 1, scan)) { @@ -4369,7 +4370,7 @@ static bool regmatch(char_u *scan, proftime_T *tm, int *timed_out) case BRACE_COMPLEX + 8: case BRACE_COMPLEX + 9: no = op - BRACE_COMPLEX; - ++brace_count[no]; + brace_count[no]++; // If not matched enough times yet, try one more if (brace_count[no] <= (brace_min[no] <= brace_max[no] diff --git a/src/nvim/regexp_defs.h b/src/nvim/regexp_defs.h index 09f244c2f6..b313dfe877 100644 --- a/src/nvim/regexp_defs.h +++ b/src/nvim/regexp_defs.h @@ -15,7 +15,6 @@ #include <stdbool.h> #include "nvim/pos.h" -#include "nvim/profile.h" #include "nvim/types.h" /* diff --git a/src/nvim/regexp_nfa.c b/src/nvim/regexp_nfa.c index 1a5c250664..d4db710d93 100644 --- a/src/nvim/regexp_nfa.c +++ b/src/nvim/regexp_nfa.c @@ -5055,7 +5055,7 @@ skip_add: // avoid compiler warnings save_ptr = NULL; - memset(&save_multipos, 0, sizeof(save_multipos)); + CLEAR_FIELD(save_multipos); // Set the position (with "off" added) in the subexpression. Save // and restore it when it was in use. Otherwise fill any gap. @@ -5180,7 +5180,7 @@ skip_add: save_ptr = sub->list.line[subidx].end; sub->list.line[subidx].end = rex.input + off; // avoid compiler warnings - memset(&save_multipos, 0, sizeof(save_multipos)); + CLEAR_FIELD(save_multipos); } subs = addstate(l, state->out, subs, pim, off_arg); @@ -5488,7 +5488,7 @@ static void nfa_save_listids(nfa_regprog_T *prog, int *list) for (i = prog->nstate; --i >= 0;) { list[i] = p->lastlist[1]; p->lastlist[1] = 0; - ++p; + p++; } } @@ -5503,7 +5503,7 @@ static void nfa_restore_listids(nfa_regprog_T *prog, int *list) p = &prog->state[0]; for (i = prog->nstate; --i >= 0;) { p->lastlist[1] = list[i]; - ++p; + p++; } } @@ -6910,7 +6910,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.line, col); + uintmax_t lts = win_linetabsize(wp, rex.reg_firstlnum + rex.lnum, rex.line, col); assert(t->state->val >= 0); result = nfa_re_num_cmp((uintmax_t)t->state->val, op, lts + 1); } diff --git a/src/nvim/runtime.c b/src/nvim/runtime.c index 5d28d624fe..e85167f8fd 100644 --- a/src/nvim/runtime.c +++ b/src/nvim/runtime.c @@ -7,20 +7,176 @@ #include "nvim/api/private/helpers.h" #include "nvim/ascii.h" +#include "nvim/autocmd.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_docmd.h" +#include "nvim/ex_eval.h" #include "nvim/lua/executor.h" +#include "nvim/memline.h" #include "nvim/option.h" +#include "nvim/os/input.h" #include "nvim/os/os.h" +#include "nvim/profile.h" #include "nvim/runtime.h" #include "nvim/vim.h" +/// Structure used to store info for each sourced file. +/// It is shared between do_source() and getsourceline(). +/// This is required, because it needs to be handed to do_cmdline() and +/// sourcing can be done recursively. +struct source_cookie { + FILE *fp; ///< opened file for sourcing + char *nextline; ///< if not NULL: line that was read ahead + linenr_T sourcing_lnum; ///< line number of the source file + int finished; ///< ":finish" used +#if defined(USE_CRNL) + int fileformat; ///< EOL_UNKNOWN, EOL_UNIX or EOL_DOS + bool error; ///< true if LF found after CR-LF +#endif + linenr_T breakpoint; ///< next line with breakpoint or zero + char *fname; ///< name of sourced file + int dbg_tick; ///< debug_tick when breakpoint was set + int level; ///< top nesting level of sourced file + vimconv_T conv; ///< type of conversion +}; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "runtime.c.generated.h" #endif +garray_T exestack = { 0, 0, sizeof(estack_T), 50, NULL }; +garray_T script_items = { 0, 0, sizeof(scriptitem_T), 4, NULL }; + +/// Initialize the execution stack. +void estack_init(void) +{ + ga_grow(&exestack, 10); + estack_T *entry = ((estack_T *)exestack.ga_data) + exestack.ga_len; + entry->es_type = ETYPE_TOP; + entry->es_name = NULL; + entry->es_lnum = 0; + entry->es_info.ufunc = NULL; + exestack.ga_len++; +} + +/// Add an item to the execution stack. +/// @return the new entry +estack_T *estack_push(etype_T type, char *name, linenr_T lnum) +{ + ga_grow(&exestack, 1); + estack_T *entry = ((estack_T *)exestack.ga_data) + exestack.ga_len; + entry->es_type = type; + entry->es_name = name; + entry->es_lnum = lnum; + entry->es_info.ufunc = NULL; + exestack.ga_len++; + return entry; +} + +/// Add a user function to the execution stack. +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), + lnum); + if (entry != NULL) { + entry->es_info.ufunc = ufunc; + } +} + +/// Take an item off of the execution stack. +void estack_pop(void) +{ + if (exestack.ga_len > 1) { + exestack.ga_len--; + } +} + +/// Get the current value for <sfile> in allocated memory. +/// @param which ESTACK_SFILE for <sfile>, ESTACK_STACK for <stack> or +/// ESTACK_SCRIPT for <script>. +char *estack_sfile(estack_arg_T which) +{ + const estack_T *entry = ((estack_T *)exestack.ga_data) + exestack.ga_len - 1; + if (which == ESTACK_SFILE && entry->es_type != ETYPE_UFUNC) { + if (entry->es_name == NULL) { + return NULL; + } + return xstrdup(entry->es_name); + } + + // If evaluated in a function or autocommand, return the path of the script + // where it is defined, at script level the current script path is returned + // instead. + if (which == ESTACK_SCRIPT) { + // Walk the stack backwards, starting from the current frame. + for (int idx = exestack.ga_len - 1; idx >= 0; idx--, entry--) { + if (entry->es_type == ETYPE_UFUNC || entry->es_type == ETYPE_AUCMD) { + const sctx_T *const def_ctx = (entry->es_type == ETYPE_UFUNC + ? &entry->es_info.ufunc->uf_script_ctx + : &entry->es_info.aucmd->script_ctx); + return def_ctx->sc_sid > 0 + ? xstrdup((SCRIPT_ITEM(def_ctx->sc_sid).sn_name)) + : NULL; + } else if (entry->es_type == ETYPE_SCRIPT) { + return xstrdup(entry->es_name); + } + } + return NULL; + } + + // Give information about each stack entry up to the root. + // For a function we compose the call stack, as it was done in the past: + // "function One[123]..Two[456]..Three" + garray_T ga; + ga_init(&ga, sizeof(char), 100); + etype_T last_type = ETYPE_SCRIPT; + for (int idx = 0; idx < exestack.ga_len; idx++) { + entry = ((estack_T *)exestack.ga_data) + idx; + if (entry->es_name != NULL) { + size_t len = strlen(entry->es_name) + 15; + char *type_name = ""; + if (entry->es_type != last_type) { + switch (entry->es_type) { + case ETYPE_SCRIPT: + type_name = "script "; break; + case ETYPE_UFUNC: + type_name = "function "; break; + default: + type_name = ""; break; + } + last_type = entry->es_type; + } + len += strlen(type_name); + ga_grow(&ga, (int)len); + linenr_T lnum = idx == exestack.ga_len - 1 + ? which == ESTACK_STACK ? SOURCING_LNUM : 0 + : entry->es_lnum; + char *dots = idx == exestack.ga_len - 1 ? "" : ".."; + if (lnum == 0) { + // For the bottom entry of <sfile>: do not add the line number, + // it is used in <slnum>. Also leave it out when the number is + // not set. + vim_snprintf((char *)ga.ga_data + ga.ga_len, len, "%s%s%s", + type_name, entry->es_name, dots); + } else { + vim_snprintf((char *)ga.ga_data + ga.ga_len, len, "%s%s[%" PRIdLINENR "]%s", + type_name, entry->es_name, lnum, dots); + } + ga.ga_len += (int)strlen((char *)ga.ga_data + ga.ga_len); + } + } + + return (char *)ga.ga_data; +} + static bool runtime_search_path_valid = false; static int *runtime_search_path_ref = NULL; static RuntimeSearchPath runtime_search_path; @@ -36,7 +192,7 @@ void runtime_init(void) void ex_runtime(exarg_T *eap) { char *arg = eap->arg; - char *p = (char *)skiptowhite((char_u *)arg); + char *p = skiptowhite(arg); ptrdiff_t len = p - arg; int flags = eap->forceit ? DIP_ALL : 0; @@ -70,9 +226,9 @@ static void source_callback(char *fname, void *cookie) /// When "flags" has DIP_ERR: give an error message if there is no match. /// /// return FAIL when no file could be sourced, OK otherwise. -int do_in_path(char_u *path, char *name, int flags, DoInRuntimepathCB callback, void *cookie) +int do_in_path(char *path, char *name, int flags, DoInRuntimepathCB callback, void *cookie) { - char_u *tail; + char *tail; int num_files; char **files; int i; @@ -80,20 +236,20 @@ int do_in_path(char_u *path, char *name, int flags, DoInRuntimepathCB callback, // Make a copy of 'runtimepath'. Invoking the callback may change the // value. - char_u *rtp_copy = vim_strsave(path); + char *rtp_copy = xstrdup(path); char *buf = xmallocz(MAXPATHL); { if (p_verbose > 10 && name != NULL) { verbose_enter(); - smsg(_("Searching for \"%s\" in \"%s\""), name, (char *)path); + smsg(_("Searching for \"%s\" in \"%s\""), name, path); verbose_leave(); } // Loop over all entries in 'runtimepath'. - char_u *rtp = rtp_copy; + char *rtp = rtp_copy; while (*rtp != NUL && ((flags & DIP_ALL) || !did_one)) { // Copy the path from 'runtimepath' to buf[]. - copy_option_part((char **)&rtp, buf, MAXPATHL, ","); + copy_option_part(&rtp, buf, MAXPATHL, ","); size_t buflen = STRLEN(buf); // Skip after or non-after directories. @@ -111,15 +267,14 @@ int do_in_path(char_u *path, char *name, int flags, DoInRuntimepathCB callback, did_one = true; } else if (buflen + STRLEN(name) + 2 < MAXPATHL) { add_pathsep(buf); - tail = (char_u *)buf + STRLEN(buf); + tail = buf + STRLEN(buf); // Loop over all patterns in "name" - char_u *np = (char_u *)name; + char *np = name; while (*np != NUL && ((flags & DIP_ALL) || !did_one)) { // Append the pattern from "name" to buf[]. - assert(MAXPATHL >= (tail - (char_u *)buf)); - copy_option_part((char **)&np, (char *)tail, (size_t)(MAXPATHL - (tail - (char_u *)buf)), - "\t "); + assert(MAXPATHL >= (tail - buf)); + copy_option_part(&np, tail, (size_t)(MAXPATHL - (tail - buf)), "\t "); if (p_verbose > 10) { verbose_enter(); @@ -208,19 +363,19 @@ void runtime_search_path_unref(RuntimeSearchPath path, int *ref) /// When "flags" has DIP_ERR: give an error message if there is no match. /// /// return FAIL when no file could be sourced, OK otherwise. -int do_in_cached_path(char_u *name, int flags, DoInRuntimepathCB callback, void *cookie) +int do_in_cached_path(char *name, int flags, DoInRuntimepathCB callback, void *cookie) { - char_u *tail; + char *tail; int num_files; char **files; int i; bool did_one = false; - char_u buf[MAXPATHL]; + char buf[MAXPATHL]; if (p_verbose > 10 && name != NULL) { verbose_enter(); - smsg(_("Searching for \"%s\" in runtime path"), (char *)name); + smsg(_("Searching for \"%s\" in runtime path"), name); verbose_leave(); } @@ -244,15 +399,15 @@ int do_in_cached_path(char_u *name, int flags, DoInRuntimepathCB callback, void (*callback)(item.path, cookie); } else if (buflen + STRLEN(name) + 2 < MAXPATHL) { STRCPY(buf, item.path); - add_pathsep((char *)buf); + add_pathsep(buf); tail = buf + STRLEN(buf); // Loop over all patterns in "name" - char_u *np = name; + char *np = name; while (*np != NUL && ((flags & DIP_ALL) || !did_one)) { // Append the pattern from "name" to buf[]. assert(MAXPATHL >= (tail - buf)); - copy_option_part((char **)&np, (char *)tail, (size_t)(MAXPATHL - (tail - buf)), "\t "); + copy_option_part(&np, tail, (size_t)(MAXPATHL - (tail - buf)), "\t "); if (p_verbose > 10) { verbose_enter(); @@ -264,7 +419,7 @@ int do_in_cached_path(char_u *name, int flags, DoInRuntimepathCB callback, void | (flags & DIP_DIRFILE) ? (EW_DIR|EW_FILE) : 0; // Expand wildcards, invoke the callback for each match. - char *(pat[]) = { (char *)buf }; + char *(pat[]) = { buf }; if (gen_expand_wildcards(1, pat, &num_files, &files, ew_flags) == OK) { for (i = 0; i < num_files; i++) { (*callback)(files[i], cookie); @@ -344,7 +499,7 @@ ArrayOf(String) runtime_get_named_common(bool lua, Array pat, bool all, if (lua) { if (item->has_lua == kNone) { size_t size = (size_t)snprintf(buf, buf_len, "%s/lua/", item->path); - item->has_lua = (size < buf_len && os_isdir((char_u *)buf)); + item->has_lua = (size < buf_len && os_isdir(buf)); } if (item->has_lua == kFalse) { continue; @@ -379,13 +534,13 @@ done: /// If "name" is NULL calls callback for each entry in "path". Cookie is /// passed by reference in this case, setting it to NULL indicates that callback /// has done its job. -int do_in_path_and_pp(char_u *path, char_u *name, int flags, DoInRuntimepathCB callback, - void *cookie) +int do_in_path_and_pp(char *path, char *name, int flags, DoInRuntimepathCB callback, void *cookie) { int done = FAIL; if ((flags & DIP_NORTP) == 0) { - done |= do_in_path(path, (char *)((name && !*name) ? NULL : name), flags, callback, cookie); + done |= do_in_path(path, (name && !*name) ? NULL : name, flags, callback, + cookie); } if ((done == FAIL || (flags & DIP_ALL)) && (flags & DIP_START)) { @@ -470,7 +625,7 @@ static void expand_rtp_entry(RuntimeSearchPath *search_path, Map(String, handle_ } static void expand_pack_entry(RuntimeSearchPath *search_path, Map(String, handle_T) *rtp_used, - CharVec *after_path, char_u *pack_entry, size_t pack_entry_len) + CharVec *after_path, char *pack_entry, size_t pack_entry_len) { static char buf[MAXPATHL]; char *(start_pat[]) = { "/pack/*/start/*", "/start/*" }; // NOLINT @@ -509,10 +664,10 @@ RuntimeSearchPath runtime_search_path_build(void) RuntimeSearchPath search_path = KV_INITIAL_VALUE; CharVec after_path = KV_INITIAL_VALUE; - static char_u buf[MAXPATHL]; - for (char *entry = (char *)p_pp; *entry != NUL;) { + static char buf[MAXPATHL]; + for (char *entry = p_pp; *entry != NUL;) { char *cur_entry = entry; - copy_option_part(&entry, (char *)buf, MAXPATHL, ","); + copy_option_part(&entry, buf, MAXPATHL, ","); String the_entry = { .data = cur_entry, .size = STRLEN(buf) }; @@ -521,20 +676,20 @@ RuntimeSearchPath runtime_search_path_build(void) } char *rtp_entry; - for (rtp_entry = (char *)p_rtp; *rtp_entry != NUL;) { + for (rtp_entry = p_rtp; *rtp_entry != NUL;) { char *cur_entry = rtp_entry; - copy_option_part(&rtp_entry, (char *)buf, MAXPATHL, ","); + copy_option_part(&rtp_entry, buf, MAXPATHL, ","); size_t buflen = STRLEN(buf); - if (path_is_after((char *)buf, buflen)) { + if (path_is_after(buf, buflen)) { rtp_entry = cur_entry; break; } // fact: &rtp entries can contain wild chars - expand_rtp_entry(&search_path, &rtp_used, (char *)buf, false); + expand_rtp_entry(&search_path, &rtp_used, buf, false); - handle_T *h = map_ref(String, handle_T)(&pack_used, cstr_as_string((char *)buf), false); + handle_T *h = map_ref(String, handle_T)(&pack_used, cstr_as_string(buf), false); if (h) { (*h)++; expand_pack_entry(&search_path, &rtp_used, &after_path, buf, buflen); @@ -545,7 +700,7 @@ RuntimeSearchPath runtime_search_path_build(void) String item = kv_A(pack_entries, i); handle_T h = map_get(String, handle_T)(&pack_used, item); if (h == 0) { - expand_pack_entry(&search_path, &rtp_used, &after_path, (char_u *)item.data, item.size); + expand_pack_entry(&search_path, &rtp_used, &after_path, item.data, item.size); } } @@ -557,8 +712,8 @@ RuntimeSearchPath runtime_search_path_build(void) // "after" dirs in rtp for (; *rtp_entry != NUL;) { - copy_option_part(&rtp_entry, (char *)buf, MAXPATHL, ","); - expand_rtp_entry(&search_path, &rtp_used, (char *)buf, path_is_after((char *)buf, STRLEN(buf))); + copy_option_part(&rtp_entry, buf, MAXPATHL, ","); + expand_rtp_entry(&search_path, &rtp_used, buf, path_is_after(buf, STRLEN(buf))); } // strings are not owned @@ -612,13 +767,13 @@ int do_in_runtimepath(char *name, int flags, DoInRuntimepathCB callback, void *c { int success = FAIL; if (!(flags & DIP_NORTP)) { - success |= do_in_cached_path((name && !*name) ? NULL : (char_u *)name, flags, callback, cookie); + success |= do_in_cached_path((name && !*name) ? NULL : name, flags, callback, cookie); flags = (flags & ~DIP_START) | DIP_NORTP; } // TODO(bfredl): we could integrate disabled OPT dirs into the cached path // which would effectivize ":packadd myoptpack" as well if ((flags & (DIP_START|DIP_OPT)) && (success == FAIL || (flags & DIP_ALL))) { - success |= do_in_path_and_pp(p_rtp, (char_u *)name, flags, callback, cookie); + success |= do_in_path_and_pp(p_rtp, name, flags, callback, cookie); } return success; } @@ -634,7 +789,7 @@ int source_runtime(char *name, int flags) } /// Just like source_runtime(), but use "path" instead of 'runtimepath'. -int source_in_path(char_u *path, char_u *name, int flags) +int source_in_path(char *path, char *name, int flags) { return do_in_path_and_pp(path, name, flags, source_callback, NULL); } @@ -658,17 +813,23 @@ static void source_all_matches(char *pat) /// /// @param fname the package path /// @param is_pack whether the added dir is a "pack/*/start/*/" style package -static int add_pack_dir_to_rtp(char_u *fname, bool is_pack) +static int add_pack_dir_to_rtp(char *fname, bool is_pack) { - char_u *p4, *p3, *p2, *p1, *p; - char_u *buf = NULL; + char *p; + char *buf = NULL; char *afterdir = NULL; int retval = FAIL; - p4 = p3 = p2 = p1 = get_past_head(fname); + char *p1 = get_past_head(fname); + char *p2 = p1; + char *p3 = p1; + char *p4 = p1; for (p = p1; *p; MB_PTR_ADV(p)) { if (vim_ispathsep_nocolon(*p)) { - p4 = p3; p3 = p2; p2 = p1; p1 = p; + p4 = p3; + p3 = p2; + p2 = p1; + p1 = p; } } @@ -678,9 +839,9 @@ static int add_pack_dir_to_rtp(char_u *fname, bool is_pack) // // find the part up to "pack" in 'runtimepath' p4++; // append pathsep in order to expand symlink - char_u c = *p4; + char c = *p4; *p4 = NUL; - char *const ffname = fix_fname((char *)fname); + char *const ffname = fix_fname(fname); *p4 = c; if (ffname == NULL) { @@ -699,10 +860,10 @@ static int add_pack_dir_to_rtp(char_u *fname, bool is_pack) for (const char *entry = (const char *)p_rtp; *entry != NUL;) { const char *cur_entry = entry; - copy_option_part((char **)&entry, (char *)buf, MAXPATHL, ","); + copy_option_part((char **)&entry, buf, MAXPATHL, ","); if (insp == NULL) { - add_pathsep((char *)buf); - char *const rtp_ffname = fix_fname((char *)buf); + add_pathsep(buf); + char *const rtp_ffname = fix_fname(buf); if (rtp_ffname == NULL) { goto theend; } @@ -714,7 +875,7 @@ static int add_pack_dir_to_rtp(char_u *fname, bool is_pack) } } - if ((p = (char_u *)strstr((char *)buf, "after")) != NULL + if ((p = strstr(buf, "after")) != NULL && p > buf && vim_ispathsep(p[-1]) && (vim_ispathsep(p[5]) || p[5] == NUL || p[5] == ',')) { @@ -734,9 +895,9 @@ static int add_pack_dir_to_rtp(char_u *fname, bool is_pack) } // check if rtp/pack/name/start/name/after exists - afterdir = concat_fnames((char *)fname, "after", true); + afterdir = concat_fnames(fname, "after", true); size_t afterlen = 0; - if (is_pack ? pack_has_entries((char_u *)afterdir) : os_isdir((char_u *)afterdir)) { + if (is_pack ? pack_has_entries(afterdir) : os_isdir(afterdir)) { afterlen = strlen(afterdir) + 1; // add one for comma } @@ -789,7 +950,7 @@ static int add_pack_dir_to_rtp(char_u *fname, bool is_pack) xstrlcat(new_rtp, afterdir, new_rtp_capacity); } - set_option_value("rtp", 0L, new_rtp, 0); + set_option_value_give_err("rtp", 0L, new_rtp, 0); xfree(new_rtp); retval = OK; @@ -803,29 +964,29 @@ theend: /// Load scripts in "plugin" directory of the package. /// For opt packages, also load scripts in "ftdetect" (start packages already /// load these from filetype.vim) -static int load_pack_plugin(bool opt, char_u *fname) +static int load_pack_plugin(bool opt, char *fname) { static const char *ftpat = "%s/ftdetect/*.vim"; // NOLINT - char *const ffname = fix_fname((char *)fname); + char *const ffname = fix_fname(fname); size_t len = strlen(ffname) + STRLEN(ftpat); - char_u *pat = xmallocz(len); + char *pat = xmallocz(len); - vim_snprintf((char *)pat, len, "%s/plugin/**/*.vim", ffname); // NOLINT - source_all_matches((char *)pat); - vim_snprintf((char *)pat, len, "%s/plugin/**/*.lua", ffname); // NOLINT - source_all_matches((char *)pat); + vim_snprintf(pat, len, "%s/plugin/**/*.vim", ffname); // NOLINT + source_all_matches(pat); + vim_snprintf(pat, len, "%s/plugin/**/*.lua", ffname); // NOLINT + source_all_matches(pat); - char_u *cmd = vim_strsave((char_u *)"g:did_load_filetypes"); + char *cmd = xstrdup("g:did_load_filetypes"); // If runtime/filetype.vim wasn't loaded yet, the scripts will be // found when it loads. - if (opt && eval_to_number((char *)cmd) > 0) { + if (opt && eval_to_number(cmd) > 0) { do_cmdline_cmd("augroup filetypedetect"); - vim_snprintf((char *)pat, len, ftpat, ffname); - source_all_matches((char *)pat); + vim_snprintf(pat, len, ftpat, ffname); + source_all_matches(pat); vim_snprintf((char *)pat, len, "%s/ftdetect/*.lua", ffname); // NOLINT - source_all_matches((char *)pat); + source_all_matches(pat); do_cmdline_cmd("augroup END"); } xfree(cmd); @@ -840,7 +1001,7 @@ static int APP_ADD_DIR; static int APP_LOAD; static int APP_BOTH; -static void add_pack_plugin(bool opt, char_u *fname, void *cookie) +static void add_pack_plugin(bool opt, char *fname, void *cookie) { if (cookie != &APP_LOAD) { char *buf = xmalloc(MAXPATHL); @@ -849,7 +1010,7 @@ static void add_pack_plugin(bool opt, char_u *fname, void *cookie) const char *p = (const char *)p_rtp; while (*p != NUL) { copy_option_part((char **)&p, buf, MAXPATHL, ","); - if (path_fnamecmp(buf, (char *)fname) == 0) { + if (path_fnamecmp(buf, fname) == 0) { found = true; break; } @@ -870,12 +1031,12 @@ static void add_pack_plugin(bool opt, char_u *fname, void *cookie) static void add_start_pack_plugin(char *fname, void *cookie) { - add_pack_plugin(false, (char_u *)fname, cookie); + add_pack_plugin(false, fname, cookie); } static void add_opt_pack_plugin(char *fname, void *cookie) { - add_pack_plugin(true, (char_u *)fname, cookie); + add_pack_plugin(true, fname, cookie); } /// Add all packages in the "start" directory to 'runtimepath'. @@ -884,11 +1045,11 @@ void add_pack_start_dirs(void) do_in_path(p_pp, NULL, DIP_ALL + DIP_DIR, add_pack_start_dir, NULL); } -static bool pack_has_entries(char_u *buf) +static bool pack_has_entries(char *buf) { int num_files; char **files; - char *(pat[]) = { (char *)buf }; + char *(pat[]) = { buf }; if (gen_expand_wildcards(1, pat, &num_files, &files, EW_DIR) == OK) { FreeWild(num_files, files); } @@ -897,7 +1058,7 @@ static bool pack_has_entries(char_u *buf) static void add_pack_start_dir(char *fname, void *cookie) { - static char_u buf[MAXPATHL]; + static char buf[MAXPATHL]; char *(start_pat[]) = { "/start/*", "/pack/*/start/*" }; // NOLINT for (int i = 0; i < 2; i++) { if (STRLEN(fname) + STRLEN(start_pat[i]) + 1 > MAXPATHL) { @@ -938,12 +1099,12 @@ void ex_packloadall(exarg_T *eap) void load_plugins(void) { if (p_lpl) { - char_u *rtp_copy = p_rtp; - char_u *const plugin_pattern_vim = (char_u *)"plugin/**/*.vim"; // NOLINT - char_u *const plugin_pattern_lua = (char_u *)"plugin/**/*.lua"; // NOLINT + char *rtp_copy = p_rtp; + char *const plugin_pattern_vim = "plugin/**/*.vim"; // NOLINT + char *const plugin_pattern_lua = "plugin/**/*.lua"; // NOLINT if (!did_source_packages) { - rtp_copy = vim_strsave(p_rtp); + rtp_copy = xstrdup(p_rtp); add_pack_start_dirs(); } @@ -960,8 +1121,8 @@ void load_plugins(void) } TIME_MSG("loading packages"); - source_runtime((char *)plugin_pattern_vim, DIP_ALL | DIP_AFTER); - source_runtime((char *)plugin_pattern_lua, DIP_ALL | DIP_AFTER); + source_runtime(plugin_pattern_vim, DIP_ALL | DIP_AFTER); + source_runtime(plugin_pattern_lua, DIP_ALL | DIP_AFTER); TIME_MSG("loading after plugins"); } } @@ -984,13 +1145,168 @@ void ex_packadd(exarg_T *eap) vim_snprintf(pat, len, plugpat, round == 1 ? "start" : "opt", eap->arg); // The first round don't give a "not found" error, in the second round // only when nothing was found in the first round. - res = do_in_path(p_pp, pat, DIP_ALL + DIP_DIR + (round == 2 && res == FAIL ? DIP_ERR : 0), - round == 1 ? add_start_pack_plugin : add_opt_pack_plugin, - eap->forceit ? &APP_ADD_DIR : &APP_BOTH); + res = + do_in_path(p_pp, pat, DIP_ALL + DIP_DIR + (round == 2 && res == FAIL ? DIP_ERR : 0), + round == 1 ? add_start_pack_plugin : add_opt_pack_plugin, + eap->forceit ? &APP_ADD_DIR : &APP_BOTH); xfree(pat); } } +/// 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 +/// "dirnames" is an array with one or more directory names. +int ExpandRTDir(char *pat, int flags, int *num_file, char ***file, char *dirnames[]) +{ + *num_file = 0; + *file = NULL; + size_t pat_len = STRLEN(pat); + + garray_T ga; + ga_init(&ga, (int)sizeof(char *), 10); + + // TODO(bfredl): this is bullshit, exandpath should not reinvent path logic. + for (int i = 0; dirnames[i] != NULL; i++) { + size_t size = STRLEN(dirnames[i]) + pat_len + 7; + char *s = xmalloc(size); + snprintf(s, size, "%s/%s*.vim", 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; + char *s = xmalloc(size); + snprintf(s, size, "pack/*/start/*/%s/%s*.vim", 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; + char *s = xmalloc(size); + snprintf(s, size, "start/*/%s/%s*.vim", 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; + char *s = xmalloc(size); + snprintf(s, size, "pack/*/opt/*/%s/%s*.vim", 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; + char *s = xmalloc(size); + snprintf(s, size, "opt/*/%s/%s*.vim", 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); + } + } + + for (int i = 0; i < ga.ga_len; i++) { + char *match = ((char **)ga.ga_data)[i]; + 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))) { + e -= 4; + for (s = e; s > match; MB_PTR_BACK(match, s)) { + if (vim_ispathsep(*s)) { + break; + } + } + s++; + *e = NUL; + assert((e - s) + 1 >= 0); + memmove(match, s, (size_t)(e - s) + 1); + } + } + + if (GA_EMPTY(&ga)) { + return FAIL; + } + + // Sort and remove duplicates which can happen when specifying multiple + // directories in dirnames. + ga_remove_duplicate_strings(&ga); + + *file = ga.ga_data; + *num_file = ga.ga_len; + return OK; +} + +/// Expand loadplugin names: +/// 'packpath'/pack/ * /opt/{pat} +int ExpandPackAddDir(char *pat, int *num_file, char ***file) +{ + garray_T ga; + + *num_file = 0; + *file = NULL; + size_t pat_len = STRLEN(pat); + ga_init(&ga, (int)sizeof(char *), 10); + + size_t buflen = pat_len + 26; + char *s = xmalloc(buflen); + snprintf(s, buflen, "pack/*/opt/%s*", pat); // NOLINT + globpath(p_pp, s, &ga, 0); + snprintf(s, buflen, "opt/%s*", pat); // NOLINT + globpath(p_pp, s, &ga, 0); + xfree(s); + + for (int i = 0; i < ga.ga_len; i++) { + char *match = ((char **)ga.ga_data)[i]; + s = path_tail(match); + memmove(match, s, STRLEN(s) + 1); + } + + if (GA_EMPTY(&ga)) { + return FAIL; + } + + // Sort and remove duplicates which can happen when specifying multiple + // directories in dirnames. + ga_remove_duplicate_strings(&ga); + + *file = ga.ga_data; + *num_file = ga.ga_len; + return OK; +} + /// Append string with escaped commas static char *strcpy_comma_escaped(char *dest, const char *src, const size_t len) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT @@ -1163,7 +1479,7 @@ char *get_lib_dir(void) // TODO(bfredl): too fragile? Ideally default_lib_dir would be made empty // in an appimage build if (strlen(default_lib_dir) != 0 - && os_isdir((const char_u *)default_lib_dir)) { + && os_isdir(default_lib_dir)) { return xstrdup(default_lib_dir); } @@ -1282,3 +1598,900 @@ freeall: return rtp; } #undef NVIM_SIZE + +static void cmd_source(char *fname, exarg_T *eap) +{ + if (eap != NULL && *fname == NUL) { + cmd_source_buffer(eap); + } else if (eap != NULL && eap->forceit) { + // ":source!": read Normal mode commands + // Need to execute the commands directly. This is required at least + // for: + // - ":g" command busy + // - after ":argdo", ":windo" or ":bufdo" + // - another command follows + // - inside a loop + openscript(fname, global_busy || listcmd_busy || eap->nextcmd != NULL + || eap->cstack->cs_idx >= 0); + + // ":source" read ex commands + } else if (do_source(fname, false, DOSO_NONE) == FAIL) { + semsg(_(e_notopen), fname); + } +} + +/// ":source [{fname}]" +void ex_source(exarg_T *eap) +{ + cmd_source(eap->arg, eap); +} + +/// ":options" +void ex_options(exarg_T *eap) +{ + char buf[500]; + bool multi_mods = 0; + + buf[0] = NUL; + (void)add_win_cmd_modifers(buf, &cmdmod, &multi_mods); + + os_setenv("OPTWIN_CMD", buf, 1); + cmd_source(SYS_OPTWIN_FILE, NULL); +} + +/// ":source" and associated commands. +/// +/// @return address holding the next breakpoint line for a source cookie +linenr_T *source_breakpoint(void *cookie) +{ + return &((struct source_cookie *)cookie)->breakpoint; +} + +/// @return the address holding the debug tick for a source cookie. +int *source_dbg_tick(void *cookie) +{ + return &((struct source_cookie *)cookie)->dbg_tick; +} + +/// @return the nesting level for a source cookie. +int source_level(void *cookie) + FUNC_ATTR_PURE +{ + return ((struct source_cookie *)cookie)->level; +} + +/// Special function to open a file without handle inheritance. +/// If possible the handle is closed on exec(). +static FILE *fopen_noinh_readbin(char *filename) +{ +#ifdef WIN32 + int fd_tmp = os_open(filename, O_RDONLY | O_BINARY | O_NOINHERIT, 0); +#else + int fd_tmp = os_open(filename, O_RDONLY, 0); +#endif + + if (fd_tmp < 0) { + return NULL; + } + + (void)os_set_cloexec(fd_tmp); + + return fdopen(fd_tmp, READBIN); +} + +/// Concatenate VimL line if it starts with a line continuation into a growarray +/// (excluding the continuation chars and leading whitespace) +/// +/// @note Growsize of the growarray may be changed to speed up concatenations! +/// +/// @param ga the growarray to append to +/// @param init_growsize the starting growsize value of the growarray +/// @param p pointer to the beginning of the line to consider +/// @param len the length of this line +/// +/// @return true if this line did begin with a continuation (the next line +/// should also be considered, if it exists); false otherwise +static bool concat_continued_line(garray_T *const ga, const int init_growsize, const char *const p, + size_t len) + FUNC_ATTR_NONNULL_ALL +{ + 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) { + return true; + } else if (len == 0 || line[0] != '\\') { + return false; + } + if (ga->ga_len > init_growsize) { + ga_set_growsize(ga, MIN(ga->ga_len, 8000)); + } + ga_concat_len(ga, line + 1, len - 1); + return true; +} + +typedef struct { + linenr_T curr_lnum; + const linenr_T final_lnum; +} GetBufferLineCookie; + +typedef struct { + char *buf; + size_t offset; +} GetStrLineCookie; + +/// Get one full line from a sourced string (in-memory, no file). +/// Called by do_cmdline() when it's called from do_source_str(). +/// +/// @return pointer to allocated line, or NULL for end-of-file or +/// some error. +static char *get_str_line(int c, void *cookie, int indent, bool do_concat) +{ + GetStrLineCookie *p = cookie; + if (STRLEN(p->buf) <= p->offset) { + return NULL; + } + const char *line = p->buf + p->offset; + const char *eol = skip_to_newline(line); + garray_T ga; + ga_init(&ga, sizeof(char), 400); + ga_concat_len(&ga, line, (size_t)(eol - line)); + if (do_concat && vim_strchr(p_cpo, CPO_CONCAT) == NULL) { + while (eol[0] != NUL) { + line = eol + 1; + const char *const next_eol = skip_to_newline(line); + if (!concat_continued_line(&ga, 400, line, (size_t)(next_eol - line))) { + break; + } + eol = (char *)next_eol; + } + } + ga_append(&ga, NUL); + p->offset = (size_t)(eol - p->buf) + 1; + return ga.ga_data; +} + +/// Create a new script item and allocate script-local vars. @see new_script_vars +/// +/// @param name File name of the script. NULL for anonymous :source. +/// @param[out] sid_out SID of the new item. +/// +/// @return pointer to the created script item. +scriptitem_T *new_script_item(char *const name, scid_T *const sid_out) +{ + static scid_T last_current_SID = 0; + const scid_T sid = ++last_current_SID; + if (sid_out != NULL) { + *sid_out = sid; + } + ga_grow(&script_items, sid - script_items.ga_len); + while (script_items.ga_len < sid) { + script_items.ga_len++; + SCRIPT_ITEM(script_items.ga_len).sn_name = NULL; + SCRIPT_ITEM(script_items.ga_len).sn_prof_on = false; + } + SCRIPT_ITEM(sid).sn_name = name; + new_script_vars(sid); // Allocate the local script variables to use for this script. + return &SCRIPT_ITEM(sid); +} + +static int source_using_linegetter(void *cookie, LineGetter fgetline, const char *traceback_name) +{ + char *save_sourcing_name = SOURCING_NAME; + linenr_T save_sourcing_lnum = SOURCING_LNUM; + char sourcing_name_buf[256]; + char *sname; + if (save_sourcing_name == NULL) { + sname = (char *)traceback_name; + } else { + snprintf((char *)sourcing_name_buf, sizeof(sourcing_name_buf), + "%s called at %s:%" PRIdLINENR, traceback_name, save_sourcing_name, + save_sourcing_lnum); + sname = sourcing_name_buf; + } + estack_push(ETYPE_SCRIPT, sname, 0); + + const sctx_T save_current_sctx = current_sctx; + if (current_sctx.sc_sid != SID_LUA) { + current_sctx.sc_sid = SID_STR; + } + current_sctx.sc_seq = 0; + current_sctx.sc_lnum = save_sourcing_lnum; + funccal_entry_T entry; + save_funccal(&entry); + int retval = do_cmdline(NULL, fgetline, cookie, + DOCMD_VERBOSE | DOCMD_NOWAIT | DOCMD_REPEAT); + estack_pop(); + current_sctx = save_current_sctx; + restore_funccal(); + return retval; +} + +static void cmd_source_buffer(const exarg_T *const eap) + FUNC_ATTR_NONNULL_ALL +{ + if (curbuf == NULL) { + return; + } + garray_T ga; + ga_init(&ga, sizeof(char), 400); + const linenr_T final_lnum = eap->line2; + // Copy the contents to be executed. + for (linenr_T curr_lnum = eap->line1; curr_lnum <= final_lnum; curr_lnum++) { + // Adjust growsize to current length to speed up concatenating many lines. + if (ga.ga_len > 400) { + ga_set_growsize(&ga, MIN(ga.ga_len, 8000)); + } + ga_concat(&ga, (char *)ml_get(curr_lnum)); + ga_append(&ga, NL); + } + ((char *)ga.ga_data)[ga.ga_len - 1] = NUL; + const GetStrLineCookie cookie = { + .buf = ga.ga_data, + .offset = 0, + }; + if (curbuf->b_fname + && path_with_extension((const char *)curbuf->b_fname, "lua")) { + nlua_source_using_linegetter(get_str_line, (void *)&cookie, + ":source (no file)"); + } else { + source_using_linegetter((void *)&cookie, get_str_line, + ":source (no file)"); + } + ga_clear(&ga); +} + +/// Executes lines in `src` as Ex commands. +/// +/// @see do_source() +int do_source_str(const char *cmd, const char *traceback_name) +{ + GetStrLineCookie cookie = { + .buf = (char *)cmd, + .offset = 0, + }; + return source_using_linegetter((void *)&cookie, get_str_line, traceback_name); +} + +/// When fname is a 'lua' file nlua_exec_file() is invoked to source it. +/// Otherwise reads the file `fname` and executes its lines as Ex commands. +/// +/// This function may be called recursively! +/// +/// @see do_source_str +/// +/// @param fname +/// @param check_other check for .vimrc and _vimrc +/// @param is_vimrc DOSO_ value +/// +/// @return FAIL if file could not be opened, OK otherwise +int do_source(char *fname, int check_other, int is_vimrc) +{ + struct source_cookie cookie; + char *p; + char *fname_exp; + uint8_t *firstline = NULL; + int retval = FAIL; + int save_debug_break_level = debug_break_level; + scriptitem_T *si = NULL; + proftime_T wait_start; + bool trigger_source_post = false; + + p = expand_env_save(fname); + if (p == NULL) { + return retval; + } + fname_exp = fix_fname(p); + xfree(p); + if (fname_exp == NULL) { + return retval; + } + if (os_isdir(fname_exp)) { + smsg(_("Cannot source a directory: \"%s\""), fname); + goto theend; + } + + // Apply SourceCmd autocommands, they should get the file and source it. + if (has_autocmd(EVENT_SOURCECMD, fname_exp, NULL) + && apply_autocmds(EVENT_SOURCECMD, fname_exp, fname_exp, + false, curbuf)) { + retval = aborting() ? FAIL : OK; + if (retval == OK) { + // Apply SourcePost autocommands. + apply_autocmds(EVENT_SOURCEPOST, fname_exp, fname_exp, false, curbuf); + } + goto theend; + } + + // Apply SourcePre autocommands, they may get the file. + apply_autocmds(EVENT_SOURCEPRE, fname_exp, fname_exp, false, curbuf); + + cookie.fp = fopen_noinh_readbin(fname_exp); + if (cookie.fp == NULL && check_other) { + // Try again, replacing file name ".vimrc" by "_vimrc" or vice versa, + // and ".exrc" by "_exrc" or vice versa. + p = path_tail(fname_exp); + if ((*p == '.' || *p == '_') + && (STRICMP(p + 1, "nvimrc") == 0 || STRICMP(p + 1, "exrc") == 0)) { + *p = (*p == '_') ? '.' : '_'; + cookie.fp = fopen_noinh_readbin(fname_exp); + } + } + + if (cookie.fp == NULL) { + if (p_verbose > 1) { + verbose_enter(); + if (SOURCING_NAME == NULL) { + smsg(_("could not source \"%s\""), fname); + } else { + smsg(_("line %" PRId64 ": could not source \"%s\""), + (int64_t)SOURCING_LNUM, fname); + } + verbose_leave(); + } + goto theend; + } + + // The file exists. + // - In verbose mode, give a message. + // - For a vimrc file, may want to call vimrc_found(). + if (p_verbose > 1) { + verbose_enter(); + if (SOURCING_NAME == NULL) { + smsg(_("sourcing \"%s\""), fname); + } else { + smsg(_("line %" PRId64 ": sourcing \"%s\""), (int64_t)SOURCING_LNUM, fname); + } + verbose_leave(); + } + if (is_vimrc == DOSO_VIMRC) { + vimrc_found(fname_exp, "MYVIMRC"); + } + +#ifdef USE_CRNL + // If no automatic file format: Set default to CR-NL. + if (*p_ffs == NUL) { + cookie.fileformat = EOL_DOS; + } else { + cookie.fileformat = EOL_UNKNOWN; + } + cookie.error = false; +#endif + + cookie.nextline = NULL; + cookie.sourcing_lnum = 0; + cookie.finished = false; + + // Check if this script has a breakpoint. + cookie.breakpoint = dbg_find_breakpoint(true, fname_exp, (linenr_T)0); + cookie.fname = fname_exp; + cookie.dbg_tick = debug_tick; + + cookie.level = ex_nesting_level; + + // start measuring script load time if --startuptime was passed and + // time_fd was successfully opened afterwards. + proftime_T rel_time; + proftime_T start_time; + FILE * const l_time_fd = time_fd; + if (l_time_fd != NULL) { + time_push(&rel_time, &start_time); + } + + const int l_do_profiling = do_profiling; + if (l_do_profiling == PROF_YES) { + prof_child_enter(&wait_start); // entering a child now + } + + // Don't use local function variables, if called from a function. + // Also starts profiling timer for nested script. + funccal_entry_T funccalp_entry; + save_funccal(&funccalp_entry); + + const sctx_T save_current_sctx = current_sctx; + si = get_current_script_id(&fname_exp, ¤t_sctx); + + // Keep the sourcing name/lnum, for recursive calls. + estack_push(ETYPE_SCRIPT, si->sn_name, 0); + + if (l_do_profiling == PROF_YES) { + bool forceit = false; + + // Check if we do profiling for this script. + if (!si->sn_prof_on && has_profiling(true, si->sn_name, &forceit)) { + profile_init(si); + si->sn_pr_force = forceit; + } + if (si->sn_prof_on) { + si->sn_pr_count++; + si->sn_pr_start = profile_start(); + si->sn_pr_children = profile_zero(); + } + } + + cookie.conv.vc_type = CONV_NONE; // no conversion + + if (path_with_extension((const char *)fname_exp, "lua")) { + const sctx_T current_sctx_backup = current_sctx; + current_sctx.sc_sid = SID_LUA; + current_sctx.sc_lnum = 0; + // Source the file as lua + nlua_exec_file((const char *)fname_exp); + current_sctx = current_sctx_backup; + } 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 + && 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); + p = string_convert(&cookie.conv, (char *)firstline + 3, NULL); + if (p == NULL) { + p = xstrdup((char *)firstline + 3); + } + xfree(firstline); + firstline = (uint8_t *)p; + } + // Call do_cmdline, which will call getsourceline() to get the lines. + do_cmdline((char *)firstline, getsourceline, (void *)&cookie, + DOCMD_VERBOSE|DOCMD_NOWAIT|DOCMD_REPEAT); + } + retval = OK; + + if (l_do_profiling == PROF_YES) { + // Get "si" again, "script_items" may have been reallocated. + si = &SCRIPT_ITEM(current_sctx.sc_sid); + if (si->sn_prof_on) { + si->sn_pr_start = profile_end(si->sn_pr_start); + si->sn_pr_start = profile_sub_wait(wait_start, si->sn_pr_start); + si->sn_pr_total = profile_add(si->sn_pr_total, si->sn_pr_start); + si->sn_pr_self = profile_self(si->sn_pr_self, si->sn_pr_start, + si->sn_pr_children); + } + } + + if (got_int) { + emsg(_(e_interr)); + } + estack_pop(); + if (p_verbose > 1) { + verbose_enter(); + smsg(_("finished sourcing %s"), fname); + if (SOURCING_NAME != NULL) { + smsg(_("continuing in %s"), SOURCING_NAME); + } + verbose_leave(); + } + + if (l_time_fd != NULL) { + vim_snprintf((char *)IObuff, IOSIZE, "sourcing %s", fname); + time_msg((char *)IObuff, &start_time); + time_pop(rel_time); + } + + if (!got_int) { + trigger_source_post = true; + } + + // After a "finish" in debug mode, need to break at first command of next + // sourced file. + if (save_debug_break_level > ex_nesting_level + && debug_break_level == ex_nesting_level) { + debug_break_level++; + } + + current_sctx = save_current_sctx; + restore_funccal(); + if (l_do_profiling == PROF_YES) { + prof_child_exit(&wait_start); // leaving a child now + } + fclose(cookie.fp); + xfree(cookie.nextline); + xfree(firstline); + convert_setup(&cookie.conv, NULL, NULL); + + if (trigger_source_post) { + apply_autocmds(EVENT_SOURCEPOST, fname_exp, fname_exp, false, curbuf); + } + +theend: + xfree(fname_exp); + return retval; +} + +/// Check if fname was sourced before to finds its SID. +/// If it's new, generate a new SID. +/// +/// @param[in,out] fnamep pointer to file path of script +/// @param[out] ret_sctx sctx of this script +scriptitem_T *get_current_script_id(char **fnamep, sctx_T *ret_sctx) +{ + static int last_current_SID_seq = 0; + + sctx_T script_sctx = { .sc_seq = ++last_current_SID_seq, + .sc_lnum = 0, + .sc_sid = 0 }; + scriptitem_T *si = NULL; + + assert(script_items.ga_len >= 0); + for (script_sctx.sc_sid = script_items.ga_len; script_sctx.sc_sid > 0; script_sctx.sc_sid--) { + // We used to check inode here, but that doesn't work: + // - If a script is edited and written, it may get a different + // inode number, even though to the user it is the same script. + // - If a script is deleted and another script is written, with a + // different name, the inode may be re-used. + si = &SCRIPT_ITEM(script_sctx.sc_sid); + if (si->sn_name != NULL && FNAMECMP(si->sn_name, *fnamep) == 0) { + // Found it! + break; + } + } + if (script_sctx.sc_sid == 0) { + si = new_script_item(*fnamep, &script_sctx.sc_sid); + *fnamep = xstrdup(si->sn_name); + } + if (ret_sctx != NULL) { + *ret_sctx = script_sctx; + } + + return si; +} + +/// ":scriptnames" +void ex_scriptnames(exarg_T *eap) +{ + if (eap->addr_count > 0) { + // :script {scriptId}: edit the script + if (eap->line2 < 1 || eap->line2 > script_items.ga_len) { + emsg(_(e_invarg)); + } else { + eap->arg = SCRIPT_ITEM(eap->line2).sn_name; + do_exedit(eap, NULL); + } + return; + } + + 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)) { + msg_putchar('\n'); + msg_outtrans((char *)IObuff); + line_breakcheck(); + } + } + } +} + +#if defined(BACKSLASH_IN_FILENAME) +/// Fix slashes in the list of script names for 'shellslash'. +void scriptnames_slash_adjust(void) +{ + for (int i = 1; i <= script_items.ga_len; i++) { + if (SCRIPT_ITEM(i).sn_name != NULL) { + slash_adjust(SCRIPT_ITEM(i).sn_name); + } + } +} + +#endif + +/// Get a pointer to a script name. Used for ":verbose set". +/// Message appended to "Last set from " +char *get_scriptname(LastSet last_set, bool *should_free) +{ + *should_free = false; + + switch (last_set.script_ctx.sc_sid) { + case SID_MODELINE: + return _("modeline"); + case SID_CMDARG: + return _("--cmd argument"); + case SID_CARG: + return _("-c argument"); + case SID_ENV: + return _("environment variable"); + case SID_ERROR: + return _("error handler"); + case SID_WINLAYOUT: + return _("changed window size"); + 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; + 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)"), + last_set.script_ctx.sc_sid); + return (char *)IObuff; + } + + *should_free = true; + return home_replace_save(NULL, sname); + } + } +} + +#if defined(EXITFREE) +void free_scriptnames(void) +{ + profile_reset(); + +# define FREE_SCRIPTNAME(item) xfree((item)->sn_name) + GA_DEEP_CLEAR(&script_items, scriptitem_T, FREE_SCRIPTNAME); +} +#endif + +linenr_T get_sourced_lnum(LineGetter fgetline, void *cookie) + FUNC_ATTR_PURE +{ + return fgetline == getsourceline + ? ((struct source_cookie *)cookie)->sourcing_lnum + : SOURCING_LNUM; +} + +/// Get one full line from a sourced file. +/// Called by do_cmdline() when it's called from do_source(). +/// +/// @return pointer to the line in allocated memory, or NULL for end-of-file or +/// some error. +char *getsourceline(int c, void *cookie, int indent, bool do_concat) +{ + struct source_cookie *sp = (struct source_cookie *)cookie; + char *line; + char *p; + + // If breakpoints have been added/deleted need to check for it. + if (sp->dbg_tick < debug_tick) { + sp->breakpoint = dbg_find_breakpoint(true, sp->fname, SOURCING_LNUM); + sp->dbg_tick = debug_tick; + } + if (do_profiling == PROF_YES) { + script_line_end(); + } + // Set the current sourcing line number. + SOURCING_LNUM = sp->sourcing_lnum + 1; + // Get current line. If there is a read-ahead line, use it, otherwise get + // one now. + if (sp->finished) { + line = NULL; + } else if (sp->nextline == NULL) { + line = get_one_sourceline(sp); + } else { + line = sp->nextline; + sp->nextline = NULL; + sp->sourcing_lnum++; + } + if (line != NULL && do_profiling == PROF_YES) { + script_line_start(); + } + + // Only concatenate lines starting with a \ when 'cpoptions' doesn't + // contain the 'C' flag. + if (line != NULL && do_concat && (vim_strchr(p_cpo, CPO_CONCAT) == NULL)) { + // compensate for the one line read-ahead + sp->sourcing_lnum--; + + // Get the next line and concatenate it when it starts with a + // backslash. We always need to read the next line, keep it in + // sp->nextline. + // Also check for a comment in between continuation lines: "\ . + sp->nextline = get_one_sourceline(sp); + if (sp->nextline != NULL + && (*(p = skipwhite(sp->nextline)) == '\\' + || (p[0] == '"' && p[1] == '\\' && p[2] == ' '))) { + garray_T ga; + + ga_init(&ga, (int)sizeof(char), 400); + ga_concat(&ga, line); + while (sp->nextline != NULL + && concat_continued_line(&ga, 400, sp->nextline, STRLEN(sp->nextline))) { + xfree(sp->nextline); + sp->nextline = get_one_sourceline(sp); + } + ga_append(&ga, NUL); + xfree(line); + line = ga.ga_data; + } + } + + if (line != NULL && sp->conv.vc_type != CONV_NONE) { + char *s; + + // Convert the encoding of the script line. + s = string_convert(&sp->conv, line, NULL); + if (s != NULL) { + xfree(line); + line = s; + } + } + + // Did we encounter a breakpoint? + if (sp->breakpoint != 0 && sp->breakpoint <= SOURCING_LNUM) { + dbg_breakpoint(sp->fname, SOURCING_LNUM); + // Find next breakpoint. + sp->breakpoint = dbg_find_breakpoint(true, sp->fname, SOURCING_LNUM); + sp->dbg_tick = debug_tick; + } + + return line; +} + +static char *get_one_sourceline(struct source_cookie *sp) +{ + garray_T ga; + int len; + int c; + char *buf; +#ifdef USE_CRNL + int has_cr; // CR-LF found +#endif + bool have_read = false; + + // use a growarray to store the sourced line + ga_init(&ga, 1, 250); + + // Loop until there is a finished line (or end-of-file). + sp->sourcing_lnum++; + for (;;) { + // make room to read at least 120 (more) characters + ga_grow(&ga, 120); + buf = ga.ga_data; + +retry: + errno = 0; + if (fgets(buf + ga.ga_len, ga.ga_maxlen - ga.ga_len, + sp->fp) == NULL) { + if (errno == EINTR) { + goto retry; + } + + break; + } + len = ga.ga_len + (int)STRLEN(buf + ga.ga_len); +#ifdef USE_CRNL + // Ignore a trailing CTRL-Z, when in Dos mode. Only recognize the + // CTRL-Z by its own, or after a NL. + if ((len == 1 || (len >= 2 && buf[len - 2] == '\n')) + && sp->fileformat == EOL_DOS + && buf[len - 1] == Ctrl_Z) { + buf[len - 1] = NUL; + break; + } +#endif + + have_read = true; + ga.ga_len = len; + + // If the line was longer than the buffer, read more. + if (ga.ga_maxlen - ga.ga_len == 1 && buf[len - 1] != '\n') { + continue; + } + + if (len >= 1 && buf[len - 1] == '\n') { // remove trailing NL +#ifdef USE_CRNL + has_cr = (len >= 2 && buf[len - 2] == '\r'); + if (sp->fileformat == EOL_UNKNOWN) { + if (has_cr) { + sp->fileformat = EOL_DOS; + } else { + sp->fileformat = EOL_UNIX; + } + } + + if (sp->fileformat == EOL_DOS) { + if (has_cr) { // replace trailing CR + buf[len - 2] = '\n'; + len--; + ga.ga_len--; + } else { // lines like ":map xx yy^M" will have failed + if (!sp->error) { + msg_source(HL_ATTR(HLF_W)); + emsg(_("W15: Warning: Wrong line separator, ^M may be missing")); + } + sp->error = true; + sp->fileformat = EOL_UNIX; + } + } +#endif + // The '\n' is escaped if there is an odd number of ^V's just + // before it, first set "c" just before the 'V's and then check + // len&c parities (is faster than ((len-c)%2 == 0)) -- Acevedo + for (c = len - 2; c >= 0 && buf[c] == Ctrl_V; c--) {} + if ((len & 1) != (c & 1)) { // escaped NL, read more + sp->sourcing_lnum++; + continue; + } + + buf[len - 1] = NUL; // remove the NL + } + + // Check for ^C here now and then, so recursive :so can be broken. + line_breakcheck(); + break; + } + + if (have_read) { + return ga.ga_data; + } + + xfree(ga.ga_data); + return NULL; +} + +/// ":scriptencoding": Set encoding conversion for a sourced script. +/// Without the multi-byte feature it's simply ignored. +void ex_scriptencoding(exarg_T *eap) +{ + struct source_cookie *sp; + char *name; + + if (!getline_equal(eap->getline, eap->cookie, getsourceline)) { + emsg(_("E167: :scriptencoding used outside of a sourced file")); + return; + } + + if (*eap->arg != NUL) { + name = enc_canonize(eap->arg); + } else { + name = eap->arg; + } + + // Setup for conversion from the specified encoding to 'encoding'. + sp = (struct source_cookie *)getline_cookie(eap->getline, eap->cookie); + convert_setup(&sp->conv, name, p_enc); + + if (name != eap->arg) { + xfree(name); + } +} + +/// ":finish": Mark a sourced file as finished. +void ex_finish(exarg_T *eap) +{ + if (getline_equal(eap->getline, eap->cookie, getsourceline)) { + do_finish(eap, false); + } else { + emsg(_("E168: :finish used outside of a sourced file")); + } +} + +/// Mark a sourced file as finished. Possibly makes the ":finish" pending. +/// Also called for a pending finish at the ":endtry" or after returning from +/// an extra do_cmdline(). "reanimate" is used in the latter case. +void do_finish(exarg_T *eap, int reanimate) +{ + int idx; + + if (reanimate) { + ((struct source_cookie *)getline_cookie(eap->getline, + eap->cookie))->finished = false; + } + + // Cleanup (and deactivate) conditionals, but stop when a try conditional + // not in its finally clause (which then is to be executed next) is found. + // In this case, make the ":finish" pending for execution at the ":endtry". + // Otherwise, finish normally. + idx = cleanup_conditionals(eap->cstack, 0, true); + if (idx >= 0) { + eap->cstack->cs_pending[idx] = CSTP_FINISH; + report_make_pending(CSTP_FINISH, NULL); + } else { + ((struct source_cookie *)getline_cookie(eap->getline, + eap->cookie))->finished = true; + } +} + +/// @return true when a sourced file had the ":finish" command: Don't give error +/// message for missing ":endif". +/// false when not sourcing a file. +bool source_finished(LineGetter fgetline, void *cookie) +{ + return getline_equal(fgetline, cookie, getsourceline) + && ((struct source_cookie *)getline_cookie(fgetline, cookie))->finished; +} diff --git a/src/nvim/runtime.h b/src/nvim/runtime.h index d83ec00185..053c71212e 100644 --- a/src/nvim/runtime.h +++ b/src/nvim/runtime.h @@ -3,7 +3,77 @@ #include <stdbool.h> -#include "nvim/ex_docmd.h" +#include "nvim/autocmd.h" +#include "nvim/eval/typval.h" +#include "nvim/ex_cmds_defs.h" +#include "nvim/ex_eval_defs.h" + +typedef enum { + ETYPE_TOP, ///< toplevel + ETYPE_SCRIPT, ///< sourcing script, use es_info.sctx + ETYPE_UFUNC, ///< user function, use es_info.ufunc + ETYPE_AUCMD, ///< autocomand, use es_info.aucmd + ETYPE_MODELINE, ///< modeline, use es_info.sctx + ETYPE_EXCEPT, ///< exception, use es_info.exception + ETYPE_ARGS, ///< command line argument + ETYPE_ENV, ///< environment variable + ETYPE_INTERNAL, ///< internal operation + ETYPE_SPELL, ///< loading spell file +} etype_T; + +/// Entry in the execution stack "exestack". +typedef struct { + linenr_T es_lnum; ///< replaces "sourcing_lnum" + char *es_name; ///< replaces "sourcing_name" + etype_T es_type; + union { + sctx_T *sctx; ///< script and modeline info + ufunc_T *ufunc; ///< function info + AutoPatCmd *aucmd; ///< autocommand info + except_T *except; ///< exception info + } es_info; +} estack_T; + +/// Stack of execution contexts. Each entry is an estack_T. +/// Current context is at ga_len - 1. +extern garray_T exestack; +/// name of error message source +#define SOURCING_NAME (((estack_T *)exestack.ga_data)[exestack.ga_len - 1].es_name) +/// line number in the message source or zero +#define SOURCING_LNUM (((estack_T *)exestack.ga_data)[exestack.ga_len - 1].es_lnum) + +/// Argument for estack_sfile(). +typedef enum { + ESTACK_NONE, + ESTACK_SFILE, + ESTACK_STACK, + ESTACK_SCRIPT, +} estack_arg_T; + +typedef struct scriptitem_S { + char *sn_name; + bool sn_prof_on; ///< true when script is/was profiled + bool sn_pr_force; ///< forceit: profile functions in this script + proftime_T sn_pr_child; ///< time set when going into first child + int sn_pr_nest; ///< nesting for sn_pr_child + // profiling the script as a whole + int sn_pr_count; ///< nr of times sourced + proftime_T sn_pr_total; ///< time spent in script + children + proftime_T sn_pr_self; ///< time spent in script itself + proftime_T sn_pr_start; ///< time at script start + proftime_T sn_pr_children; ///< time in children after script start + // profiling the script per line + garray_T sn_prl_ga; ///< things stored for every line + proftime_T sn_prl_start; ///< start time for current line + proftime_T sn_prl_children; ///< time spent in children for this line + proftime_T sn_prl_wait; ///< wait start time for current line + linenr_T sn_prl_idx; ///< index of line being timed; -1 if none + int sn_prl_execed; ///< line being timed was executed +} scriptitem_T; + +/// 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]) typedef void (*DoInRuntimepathCB)(char *, void *); diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 0d3b936c48..d268dde845 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -1,629 +1,55 @@ // 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 -// screen.c: code for displaying on the screen -// +// screen.c: Lower level code for displaying on the screen. +// grid.c contains some other lower-level code. + // Output to the screen (console, terminal emulator or GUI window) is minimized // by remembering what is already on the screen, and only updating the parts // that changed. -// -// The grid_*() functions write to the screen and handle updating grid->lines[]. -// -// update_screen() is the function that updates all windows and status lines. -// It is called from the main loop when must_redraw is non-zero. It may be -// called from other places when an immediate screen update is needed. -// -// The part of the buffer that is displayed in a window is set with: -// - w_topline (first buffer line in window) -// - w_topfill (filler lines above the first line) -// - w_leftcol (leftmost window cell in window), -// - w_skipcol (skipped window cells of first line) -// -// Commands that only move the cursor around in a window, do not need to take -// action to update the display. The main loop will check if w_topline is -// valid and update it (scroll the window) when needed. -// -// Commands that scroll a window change w_topline and must call -// check_cursor() to move the cursor into the visible part of the window, and -// call redraw_later(wp, VALID) to have the window displayed by update_screen() -// later. -// -// Commands that change text in the buffer must call changed_bytes() or -// changed_lines() to mark the area that changed and will require updating -// later. The main loop will call update_screen(), which will update each -// window that shows the changed buffer. This assumes text above the change -// can remain displayed as it is. Text after the change may need updating for -// scrolling, folding and syntax highlighting. -// -// Commands that change how a window is displayed (e.g., setting 'list') or -// invalidate the contents of a window in another way (e.g., change fold -// settings), must call redraw_later(wp, NOT_VALID) to have the whole window -// redisplayed by update_screen() later. -// -// Commands that change how a buffer is displayed (e.g., setting 'tabstop') -// must call redraw_curbuf_later(NOT_VALID) to have all the windows for the -// buffer redisplayed by update_screen() later. -// -// Commands that change highlighting and possibly cause a scroll too must call -// redraw_later(wp, SOME_VALID) to update the whole window but still use -// scrolling to avoid redrawing everything. But the length of displayed lines -// must not change, use NOT_VALID then. -// -// Commands that move the window position must call redraw_later(wp, NOT_VALID). -// TODO(neovim): should minimize redrawing by scrolling when possible. -// -// Commands that change everything (e.g., resizing the screen) must call -// redraw_all_later(NOT_VALID) or redraw_all_later(CLEAR). -// -// Things that are handled indirectly: -// - When messages scroll the screen up, msg_scrolled will be set and -// update_screen() called to redraw. -/// #include <assert.h> #include <inttypes.h> #include <stdbool.h> #include <string.h> -#include "nvim/api/extmark.h" -#include "nvim/api/private/helpers.h" -#include "nvim/api/ui.h" -#include "nvim/api/vim.h" -#include "nvim/arabic.h" -#include "nvim/ascii.h" #include "nvim/buffer.h" #include "nvim/charset.h" +#include "nvim/cmdexpand.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/edit.h" +#include "nvim/drawscreen.h" #include "nvim/eval.h" -#include "nvim/ex_cmds.h" -#include "nvim/ex_cmds2.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/grid_defs.h" +#include "nvim/grid.h" #include "nvim/highlight.h" #include "nvim/highlight_group.h" -#include "nvim/indent.h" -#include "nvim/insexpand.h" -#include "nvim/lib/kvec.h" -#include "nvim/log.h" -#include "nvim/lua/executor.h" -#include "nvim/main.h" -#include "nvim/mark.h" -#include "nvim/match.h" -#include "nvim/mbyte.h" -#include "nvim/memline.h" -#include "nvim/memory.h" #include "nvim/menu.h" -#include "nvim/message.h" #include "nvim/move.h" -#include "nvim/normal.h" #include "nvim/option.h" -#include "nvim/os/time.h" -#include "nvim/os_unix.h" -#include "nvim/path.h" -#include "nvim/plines.h" -#include "nvim/popupmnu.h" -#include "nvim/quickfix.h" +#include "nvim/optionstr.h" +#include "nvim/profile.h" #include "nvim/regexp.h" #include "nvim/screen.h" #include "nvim/search.h" -#include "nvim/sign.h" -#include "nvim/spell.h" #include "nvim/state.h" -#include "nvim/strings.h" -#include "nvim/syntax.h" -#include "nvim/terminal.h" -#include "nvim/ui.h" +#include "nvim/statusline.h" #include "nvim/ui_compositor.h" #include "nvim/undo.h" -#include "nvim/version.h" -#include "nvim/vim.h" #include "nvim/window.h" -#define MB_FILLER_CHAR '<' /* character used when a double-width character - * doesn't fit. */ - -static match_T search_hl; // used for 'hlsearch' highlight matching - -// for line_putchar. Contains the state that needs to be remembered from -// putting one character to the next. -typedef struct { - const char *p; - int prev_c; // previous Arabic character - int prev_c1; // first composing char for prev_c -} LineState; -#define LINE_STATE(p) { p, 0, 0 } - -/// Whether to call "ui_call_grid_resize" in win_grid_alloc -static bool send_grid_resize = false; - -static bool conceal_cursor_used = false; - -static bool redraw_popupmenu = false; -static bool msg_grid_invalid = false; - -static bool resizing = false; - -typedef struct { - NS ns_id; - uint64_t mark_id; - int win_row; - int win_col; -} WinExtmark; -static kvec_t(WinExtmark) win_extmark_arr INIT(= KV_INITIAL_VALUE); - #ifdef INCLUDE_GENERATED_DECLARATIONS # include "screen.c.generated.h" #endif -static char *provider_err = NULL; - -/// Redraw a window later, with update_screen(type). -/// -/// Set must_redraw only if not already set to a higher value. -/// e.g. if must_redraw is CLEAR, type NOT_VALID will do nothing. -void redraw_later(win_T *wp, int type) - FUNC_ATTR_NONNULL_ALL -{ - if (!exiting && wp->w_redr_type < type) { - wp->w_redr_type = type; - if (type >= NOT_VALID) { - wp->w_lines_valid = 0; - } - if (must_redraw < type) { // must_redraw is the maximum of all windows - must_redraw = type; - } - } -} - -/* - * Mark all windows to be redrawn later. - */ -void redraw_all_later(int type) -{ - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - redraw_later(wp, type); - } - // This may be needed when switching tabs. - if (must_redraw < type) { - must_redraw = type; - } -} - -void screen_invalidate_highlights(void) -{ - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - redraw_later(wp, NOT_VALID); - wp->w_grid_alloc.valid = false; - } -} - -/* - * Mark all windows that are editing the current buffer to be updated later. - */ -void redraw_curbuf_later(int type) -{ - redraw_buf_later(curbuf, type); -} - -void redraw_buf_later(buf_T *buf, int type) -{ - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->w_buffer == buf) { - redraw_later(wp, type); - } - } -} - -void redraw_buf_line_later(buf_T *buf, linenr_T line) -{ - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->w_buffer == buf - && line >= wp->w_topline && line < wp->w_botline) { - redrawWinline(wp, line); - } - } -} - -void redraw_buf_range_later(buf_T *buf, linenr_T firstline, linenr_T lastline) -{ - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->w_buffer == buf - && lastline >= wp->w_topline && firstline < wp->w_botline) { - if (wp->w_redraw_top == 0 || wp->w_redraw_top > firstline) { - wp->w_redraw_top = firstline; - } - if (wp->w_redraw_bot == 0 || wp->w_redraw_bot < lastline) { - wp->w_redraw_bot = lastline; - } - redraw_later(wp, VALID); - } - } -} - -/* - * Changed something in the current window, at buffer line "lnum", that - * requires that line and possibly other lines to be redrawn. - * Used when entering/leaving Insert mode with the cursor on a folded line. - * Used to remove the "$" from a change command. - * Note that when also inserting/deleting lines w_redraw_top and w_redraw_bot - * may become invalid and the whole window will have to be redrawn. - */ -void redrawWinline(win_T *wp, linenr_T lnum) - FUNC_ATTR_NONNULL_ALL -{ - if (lnum >= wp->w_topline - && lnum < wp->w_botline) { - if (wp->w_redraw_top == 0 || wp->w_redraw_top > lnum) { - wp->w_redraw_top = lnum; - } - if (wp->w_redraw_bot == 0 || wp->w_redraw_bot < lnum) { - wp->w_redraw_bot = lnum; - } - redraw_later(wp, VALID); - } -} - -/// called when the status bars for the buffer 'buf' need to be updated -void redraw_buf_status_later(buf_T *buf) -{ - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->w_buffer == buf && (wp->w_status_height || (wp == curwin && global_stl_height()) - || wp->w_winbar_height)) { - wp->w_redr_status = true; - if (must_redraw < VALID) { - must_redraw = VALID; - } - } - } -} - -void redraw_win_signcol(win_T *wp) -{ - // If we can compute a change in the automatic sizing of the sign column - // under 'signcolumn=auto:X' and signs currently placed in the buffer, better - // figuring it out here so we can redraw the entire screen for it. - int scwidth = wp->w_scwidth; - wp->w_scwidth = win_signcol_count(wp); - if (wp->w_scwidth != scwidth) { - changed_line_abv_curs_win(wp); - } -} - -/// Update all windows that are editing the current buffer. -void update_curbuf(int type) -{ - redraw_curbuf_later(type); - update_screen(type); -} - -/// Redraw the parts of the screen that is marked for redraw. -/// -/// Most code shouldn't call this directly, rather use redraw_later() and -/// and redraw_all_later() to mark parts of the screen as needing a redraw. -/// -/// @param type set to a NOT_VALID to force redraw of entire screen -int update_screen(int type) -{ - static bool did_intro = false; - bool is_stl_global = global_stl_height() > 0; - - // Don't do anything if the screen structures are (not yet) valid. - // A VimResized autocmd can invoke redrawing in the middle of a resize, - // which would bypass the checks in screen_resize for popupmenu etc. - if (!default_grid.chars || resizing) { - return FAIL; - } - - // May have postponed updating diffs. - if (need_diff_redraw) { - diff_redraw(true); - } - - if (must_redraw) { - if (type < must_redraw) { // use maximal type - type = must_redraw; - } - - // must_redraw is reset here, so that when we run into some weird - // reason to redraw while busy redrawing (e.g., asynchronous - // scrolling), or update_topline() in win_update() will cause a - // scroll, or a decoration provider requires a redraw, the screen - // will be redrawn later or in win_update(). - must_redraw = 0; - } +static char e_conflicts_with_value_of_listchars[] = N_("E834: Conflicts with value of 'listchars'"); +static char e_conflicts_with_value_of_fillchars[] = N_("E835: Conflicts with value of 'fillchars'"); - // Need to update w_lines[]. - if (curwin->w_lines_valid == 0 && type < NOT_VALID) { - type = NOT_VALID; - } - - /* Postpone the redrawing when it's not needed and when being called - * recursively. */ - if (!redrawing() || updating_screen) { - must_redraw = type; - if (type > INVERTED_ALL) { - curwin->w_lines_valid = 0; // don't use w_lines[].wl_size now - } - return FAIL; - } - updating_screen = 1; - - display_tick++; // let syntax code know we're in a next round of - // display updating - - // Tricky: vim code can reset msg_scrolled behind our back, so need - // separate bookkeeping for now. - if (msg_did_scroll) { - msg_did_scroll = false; - msg_scrolled_at_flush = 0; - } - - if (type >= CLEAR || !default_grid.valid) { - ui_comp_set_screen_valid(false); - } - - // if the screen was scrolled up when displaying a message, scroll it down - if (msg_scrolled || msg_grid_invalid) { - clear_cmdline = true; - int valid = MAX(Rows - msg_scrollsize(), 0); - if (msg_grid.chars) { - // non-displayed part of msg_grid is considered invalid. - for (int i = 0; i < MIN(msg_scrollsize(), msg_grid.rows); i++) { - grid_clear_line(&msg_grid, msg_grid.line_offset[i], - msg_grid.cols, false); - } - } - if (msg_use_msgsep()) { - msg_grid.throttled = false; - // CLEAR is already handled - if (type == NOT_VALID && !ui_has(kUIMultigrid) && msg_scrolled) { - ui_comp_set_screen_valid(false); - for (int i = valid; i < Rows - p_ch; i++) { - grid_clear_line(&default_grid, default_grid.line_offset[i], - Columns, false); - } - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->w_floating) { - continue; - } - if (W_ENDROW(wp) > valid) { - wp->w_redr_type = MAX(wp->w_redr_type, NOT_VALID); - } - if (!is_stl_global && W_ENDROW(wp) + wp->w_status_height > valid) { - wp->w_redr_status = true; - } - } - if (is_stl_global && Rows - p_ch - 1 > valid) { - curwin->w_redr_status = true; - } - } - msg_grid_set_pos(Rows - (int)p_ch, false); - msg_grid_invalid = false; - } else if (msg_scrolled > Rows - 5) { // clearing is faster - type = CLEAR; - } else if (type != CLEAR) { - check_for_delay(false); - grid_ins_lines(&default_grid, 0, msg_scrolled, Rows, 0, Columns); - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->w_floating) { - continue; - } - if (wp->w_winrow < msg_scrolled) { - if (W_ENDROW(wp) > msg_scrolled - && wp->w_redr_type < REDRAW_TOP - && wp->w_lines_valid > 0 - && wp->w_topline == wp->w_lines[0].wl_lnum) { - wp->w_upd_rows = msg_scrolled - wp->w_winrow; - wp->w_redr_type = REDRAW_TOP; - } else { - wp->w_redr_type = NOT_VALID; - if (wp->w_winrow + wp->w_winbar_height <= msg_scrolled) { - wp->w_redr_status = true; - } - } - } - } - if (is_stl_global && Rows - p_ch - 1 <= msg_scrolled) { - curwin->w_redr_status = true; - } - redraw_cmdline = true; - redraw_tabline = true; - } - msg_scrolled = 0; - msg_scrolled_at_flush = 0; - need_wait_return = false; - } - - win_ui_flush(); - msg_ext_check_clear(); - - // reset cmdline_row now (may have been changed temporarily) - compute_cmdrow(); - - bool hl_changed = false; - // Check for changed highlighting - if (need_highlight_changed) { - highlight_changed(); - hl_changed = true; - } - - if (type == CLEAR) { // first clear screen - screenclear(); // will reset clear_cmdline - cmdline_screen_cleared(); // clear external cmdline state - type = NOT_VALID; - // must_redraw may be set indirectly, avoid another redraw later - must_redraw = 0; - } else if (!default_grid.valid) { - grid_invalidate(&default_grid); - default_grid.valid = true; - } - - // After disabling msgsep the grid might not have been deallocated yet, - // hence we also need to check msg_grid.chars - if (type == NOT_VALID && (msg_use_grid() || msg_grid.chars)) { - grid_fill(&default_grid, Rows - (int)p_ch, Rows, 0, Columns, ' ', ' ', 0); - } - - ui_comp_set_screen_valid(true); - - DecorProviders providers; - decor_providers_start(&providers, type, &provider_err); - - // "start" callback could have changed highlights for global elements - if (win_check_ns_hl(NULL)) { - redraw_cmdline = true; - redraw_tabline = true; - } - - if (clear_cmdline) { // going to clear cmdline (done below) - check_for_delay(false); - } - - /* Force redraw when width of 'number' or 'relativenumber' column - * changes. */ - if (curwin->w_redr_type < NOT_VALID - && curwin->w_nrwidth != ((curwin->w_p_nu || curwin->w_p_rnu) - ? number_width(curwin) : 0)) { - curwin->w_redr_type = NOT_VALID; - } - - /* - * Only start redrawing if there is really something to do. - */ - if (type == INVERTED) { - update_curswant(); - } - if (curwin->w_redr_type < type - && !((type == VALID - && curwin->w_lines[0].wl_valid - && curwin->w_topfill == curwin->w_old_topfill - && curwin->w_botfill == curwin->w_old_botfill - && curwin->w_topline == curwin->w_lines[0].wl_lnum) - || (type == INVERTED - && VIsual_active - && curwin->w_old_cursor_lnum == curwin->w_cursor.lnum - && curwin->w_old_visual_mode == VIsual_mode - && (curwin->w_valid & VALID_VIRTCOL) - && curwin->w_old_curswant == curwin->w_curswant) - )) { - curwin->w_redr_type = type; - } - - // Redraw the tab pages line if needed. - if (redraw_tabline || type >= NOT_VALID) { - update_window_hl(curwin, type >= NOT_VALID); - FOR_ALL_TABS(tp) { - if (tp != curtab) { - update_window_hl(tp->tp_curwin, type >= NOT_VALID); - } - } - draw_tabline(); - } - - /* - * Correct stored syntax highlighting info for changes in each displayed - * buffer. Each buffer must only be done once. - */ - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - update_window_hl(wp, type >= NOT_VALID || hl_changed); - - buf_T *buf = wp->w_buffer; - if (buf->b_mod_set) { - if (buf->b_mod_tick_syn < display_tick - && syntax_present(wp)) { - syn_stack_apply_changes(buf); - buf->b_mod_tick_syn = display_tick; - } - - if (buf->b_mod_tick_decor < display_tick) { - decor_providers_invoke_buf(buf, &providers, &provider_err); - buf->b_mod_tick_decor = display_tick; - } - } - } - - /* - * Go from top to bottom through the windows, redrawing the ones that need - * it. - */ - bool did_one = false; - search_hl.rm.regprog = NULL; - - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->w_redr_type == CLEAR && wp->w_floating && wp->w_grid_alloc.chars) { - grid_invalidate(&wp->w_grid_alloc); - wp->w_redr_type = NOT_VALID; - } - - // reallocate grid if needed. - win_grid_alloc(wp); - - if (wp->w_redr_border || wp->w_redr_type >= NOT_VALID) { - win_redr_border(wp); - } - - if (wp->w_redr_type != 0) { - if (!did_one) { - did_one = true; - start_search_hl(); - } - win_update(wp, &providers); - } - - // redraw status line and window bar after the window to minimize cursor movement - if (wp->w_redr_status) { - win_redr_winbar(wp); - win_redr_status(wp); - } - } - - end_search_hl(); - - // May need to redraw the popup menu. - if (pum_drawn() && must_redraw_pum) { - pum_redraw(); - } - - /* Reset b_mod_set flags. Going through all windows is probably faster - * than going through all buffers (there could be many buffers). */ - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - wp->w_buffer->b_mod_set = false; - } - - updating_screen = 0; - - /* Clear or redraw the command line. Done last, because scrolling may - * mess up the command line. */ - if (clear_cmdline || redraw_cmdline) { - showmode(); - } - - // May put up an introductory message when not editing a file - if (!did_intro) { - maybe_intro_message(); - } - did_intro = true; - - decor_providers_invoke_end(&providers, &provider_err); - kvi_destroy(providers); - - // either cmdline is cleared, not drawn or mode is last drawn - cmdline_was_last_drawn = false; - return OK; -} - -// Return true if the cursor line in window "wp" may be concealed, according -// to the 'concealcursor' option. +/// Return true if the cursor line in window "wp" may be concealed, according +/// to the 'concealcursor' option. bool conceal_cursor_line(const win_T *wp) FUNC_ATTR_NONNULL_ALL { @@ -643,21 +69,7 @@ bool conceal_cursor_line(const win_T *wp) } else { return false; } - return vim_strchr((char *)wp->w_p_cocu, c) != NULL; -} - -// Check if the cursor line needs to be redrawn because of 'concealcursor'. -// -// When cursor is moved at the same time, both lines will be redrawn regardless. -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); - } + return vim_strchr(wp->w_p_cocu, c) != NULL; } /// Whether cursorline is drawn in a special way @@ -669,1081 +81,6 @@ bool win_cursorline_standout(const win_T *wp) return wp->w_p_cul || (wp->w_p_cole > 0 && !conceal_cursor_line(wp)); } -/* - * Update a single window. - * - * This may cause the windows below it also to be redrawn (when clearing the - * screen or scrolling lines). - * - * How the window is redrawn depends on wp->w_redr_type. Each type also - * implies the one below it. - * NOT_VALID redraw the whole window - * SOME_VALID redraw the whole window but do scroll when possible - * REDRAW_TOP redraw the top w_upd_rows window lines, otherwise like VALID - * INVERTED redraw the changed part of the Visual area - * INVERTED_ALL redraw the whole Visual area - * VALID 1. scroll up/down to adjust for a changed w_topline - * 2. update lines at the top when scrolled down - * 3. redraw changed text: - * - if wp->w_buffer->b_mod_set set, update lines between - * b_mod_top and b_mod_bot. - * - if wp->w_redraw_top non-zero, redraw lines between - * wp->w_redraw_top and wp->w_redr_bot. - * - continue redrawing when syntax status is invalid. - * 4. if scrolled up, update lines at the bottom. - * This results in three areas that may need updating: - * top: from first row to top_end (when scrolled down) - * mid: from mid_start to mid_end (update inversion or changed text) - * bot: from bot_start to last row (when scrolled up) - */ -static void win_update(win_T *wp, 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 - updating. 999 when no mid area updating. */ - int mid_end = 0; /* Below last row of the mid area that needs - updating. 0 when no mid area updating. */ - int bot_start = 999; /* first row of the bot area that needs - updating. 999 when no bot area updating */ - bool scrolled_down = false; // true when scrolled down when w_topline got smaller a bit - bool top_to_mod = false; // redraw above mod_top - - 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; - 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; - - if (type >= NOT_VALID) { - wp->w_redr_status = true; - wp->w_lines_valid = 0; - } - - // Window is zero-height: Only need to draw the separator - if (wp->w_grid.rows == 0) { - // draw the horizontal separator below this window - draw_hsep_win(wp); - draw_sep_connectors_win(wp); - wp->w_redr_type = 0; - return; - } - - // Window is zero-width: Only need to draw the separator. - if (wp->w_grid.cols == 0) { - // draw the vertical separator right of this window - draw_vsep_win(wp); - draw_sep_connectors_win(wp); - wp->w_redr_type = 0; - return; - } - - redraw_win_signcol(wp); - - init_search_hl(wp, &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) { - type = NOT_VALID; - wp->w_nrwidth = i; - - if (buf->terminal) { - terminal_check_size(buf->terminal); - } - } else if (buf->b_mod_set - && buf->b_mod_xlines != 0 - && wp->w_redraw_top != 0) { - // When there are both inserted/deleted lines and specific lines to be - // redrawn, w_redraw_top and w_redraw_bot may be invalid, just redraw - // everything (only happens when redrawing is off for while). - type = NOT_VALID; - } else { - /* - * Set mod_top to the first line that needs displaying because of - * changes. Set mod_bot to the first line after the changes. - */ - mod_top = wp->w_redraw_top; - if (wp->w_redraw_bot != 0) { - mod_bot = wp->w_redraw_bot + 1; - } else { - mod_bot = 0; - } - if (buf->b_mod_set) { - if (mod_top == 0 || mod_top > buf->b_mod_top) { - mod_top = buf->b_mod_top; - /* Need to redraw lines above the change that may be included - * in a pattern match. */ - if (syntax_present(wp)) { - mod_top -= buf->b_s.b_syn_sync_linebreaks; - if (mod_top < 1) { - mod_top = 1; - } - } - } - if (mod_bot == 0 || mod_bot < buf->b_mod_bot) { - mod_bot = buf->b_mod_bot; - } - - // When 'hlsearch' is on and using a multi-line search pattern, a - // change in one line may make the Search highlighting in a - // previous line invalid. Simple solution: redraw all visible - // lines above the change. - // Same for a match pattern. - if (search_hl.rm.regprog != NULL - && re_multiline(search_hl.rm.regprog)) { - top_to_mod = true; - } else { - const matchitem_T *cur = wp->w_match_head; - while (cur != NULL) { - if (cur->match.regprog != NULL - && re_multiline(cur->match.regprog)) { - top_to_mod = true; - break; - } - cur = cur->next; - } - } - } - if (mod_top != 0 && hasAnyFolding(wp)) { - linenr_T lnumt, lnumb; - - /* - * A change in a line can cause lines above it to become folded or - * unfolded. Find the top most buffer line that may be affected. - * If the line was previously folded and displayed, get the first - * line of that fold. If the line is folded now, get the first - * folded line. Use the minimum of these two. - */ - - /* Find last valid w_lines[] entry above mod_top. Set lnumt to - * the line below it. If there is no valid entry, use w_topline. - * Find the first valid w_lines[] entry below mod_bot. Set lnumb - * 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) { - if (wp->w_lines[i].wl_valid) { - if (wp->w_lines[i].wl_lastlnum < mod_top) { - lnumt = wp->w_lines[i].wl_lastlnum + 1; - } - if (lnumb == MAXLNUM && wp->w_lines[i].wl_lnum >= mod_bot) { - lnumb = wp->w_lines[i].wl_lnum; - // When there is a fold column it might need updating - // in the next line ("J" just above an open fold). - if (compute_foldcolumn(wp, 0) > 0) { - lnumb++; - } - } - } - } - - (void)hasFoldingWin(wp, mod_top, &mod_top, NULL, true, NULL); - if (mod_top > lnumt) { - mod_top = lnumt; - } - - // Now do the same for the bottom line (one above mod_bot). - mod_bot--; - (void)hasFoldingWin(wp, mod_bot, NULL, &mod_bot, true, NULL); - mod_bot++; - if (mod_bot < lnumb) { - mod_bot = lnumb; - } - } - - /* When a change starts above w_topline and the end is below - * w_topline, start redrawing at w_topline. - * If the end of the change is above w_topline: do like no change was - * made, but redraw the first line to find changes in syntax. */ - if (mod_top != 0 && mod_top < wp->w_topline) { - if (mod_bot > wp->w_topline) { - mod_top = wp->w_topline; - } else if (syntax_present(wp)) { - top_end = 1; - } - } - - /* When line numbers are displayed need to redraw all lines below - * inserted/deleted lines. */ - if (mod_top != 0 && buf->b_mod_xlines != 0 && wp->w_p_nu) { - mod_bot = MAXLNUM; - } - } - wp->w_redraw_top = 0; // reset for next time - wp->w_redraw_bot = 0; - - /* - * When only displaying the lines at the top, set top_end. Used when - * window has scrolled down for msg_scrolled. - */ - if (type == REDRAW_TOP) { - j = 0; - for (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; - break; - } - } - if (top_end == 0) { - // not found (cannot happen?): redraw everything - type = NOT_VALID; - } else { - // top area defined, the rest is VALID - type = VALID; - } - } - - /* - * If there are no changes on the screen that require a complete redraw, - * handle three cases: - * 1: we are off the top of the screen by a few lines: scroll down - * 2: wp->w_topline is below wp->w_lines[0].wl_lnum: may scroll up - * 3: wp->w_topline is wp->w_lines[0].wl_lnum: find first entry in - * w_lines[] that needs updating. - */ - if ((type == VALID || type == SOME_VALID - || type == INVERTED || type == INVERTED_ALL) - && !wp->w_botfill && !wp->w_old_botfill) { - if (mod_top != 0 - && wp->w_topline == mod_top - && (!wp->w_lines[0].wl_valid - || wp->w_topline == wp->w_lines[0].wl_lnum)) { - // w_topline is the first changed line and window is not scrolled, - // the scrolling from changed lines will be done further down. - } else if (wp->w_lines[0].wl_valid - && (wp->w_topline < wp->w_lines[0].wl_lnum - || (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. - */ - if (hasAnyFolding(wp)) { - linenr_T ln; - - /* count the number of lines we are off, counting a sequence - * of folded lines as one */ - j = 0; - for (ln = wp->w_topline; ln < wp->w_lines[0].wl_lnum; ln++) { - j++; - if (j >= wp->w_grid.rows - 2) { - break; - } - (void)hasFoldingWin(wp, ln, NULL, &ln, true, NULL); - } - } else { - 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); - // 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; - } - if (i != 0 && i < wp->w_grid.rows - 2) { // less than a screen off - // Try to insert the correct number of lines. - // If not the last window, delete the lines at the bottom. - // win_ins_lines may fail when the terminal can't do it. - win_scroll_lines(wp, 0, i); - if (wp->w_lines_valid != 0) { - // Need to update rows that are new, stop at the - // first one that scrolled down. - top_end = i; - scrolled_down = true; - - // Move the entries that were scrolled, disable - // the entries for the lines to be redrawn. - if ((wp->w_lines_valid += (linenr_T)j) > wp->w_grid.rows) { - wp->w_lines_valid = wp->w_grid.rows; - } - for (idx = wp->w_lines_valid; idx - j >= 0; idx--) { - wp->w_lines[idx] = wp->w_lines[idx - j]; - } - while (idx >= 0) { - wp->w_lines[idx--].wl_valid = false; - } - } - } else { - mid_start = 0; // redraw all lines - } - } else { - mid_start = 0; // redraw all lines - } - } else { - /* - * New topline is at or below old topline: May scroll up. - * When topline didn't change, find first entry in w_lines[] that - * 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++) { - if (wp->w_lines[i].wl_valid - && wp->w_lines[i].wl_lnum == wp->w_topline) { - j = i; - break; - } - row += wp->w_lines[i].wl_size; - } - if (j == -1) { - /* if wp->w_topline is not in wp->w_lines[].wl_lnum redraw all - * lines */ - mid_start = 0; - } else { - /* - * Try to delete the correct number of lines. - * wp->w_topline is at wp->w_lines[i].wl_lnum. - */ - /* If the topline didn't change, delete old filler lines, - * otherwise delete filler lines of the new topline... */ - if (wp->w_lines[0].wl_lnum == wp->w_topline) { - row += wp->w_old_topfill; - } else { - row += win_get_fill(wp, wp->w_topline); - } - // ... but don't delete new filler lines. - row -= wp->w_topfill; - if (row > 0) { - win_scroll_lines(wp, 0, -row); - bot_start = wp->w_grid.rows - row; - } - if ((row == 0 || bot_start < 999) && wp->w_lines_valid != 0) { - /* - * Skip the lines (below the deleted lines) that are still - * valid and don't need redrawing. Copy their info - * upwards, to compensate for the deleted lines. Set - * bot_start to the first row that needs redrawing. - */ - bot_start = 0; - idx = 0; - for (;;) { - wp->w_lines[idx] = wp->w_lines[j]; - /* stop at line that didn't fit, unless it is still - * valid (no lines deleted) */ - if (row > 0 && bot_start + row - + (int)wp->w_lines[j].wl_size > wp->w_grid.rows) { - wp->w_lines_valid = idx + 1; - break; - } - bot_start += wp->w_lines[idx++].wl_size; - - // stop at the last valid entry in w_lines[].wl_size - if (++j >= wp->w_lines_valid) { - wp->w_lines_valid = idx; - break; - } - } - - // Correct the first entry for filler lines at the top - // when it won't get updated below. - if (win_may_fill(wp) && bot_start > 0) { - wp->w_lines[0].wl_size = (uint16_t)(plines_win_nofill(wp, wp->w_topline, true) - + wp->w_topfill); - } - } - } - } - - // When starting redraw in the first line, redraw all lines. - if (mid_start == 0) { - mid_end = wp->w_grid.rows; - } - } else { - // Not VALID or INVERTED: redraw all lines. - mid_start = 0; - mid_end = wp->w_grid.rows; - } - - if (type == SOME_VALID) { - // SOME_VALID: redraw all lines. - mid_start = 0; - mid_end = wp->w_grid.rows; - type = NOT_VALID; - } - - // check if we are updating or removing the inverted part - if ((VIsual_active && buf == curwin->w_buffer) - || (wp->w_old_cursor_lnum != 0 && type != NOT_VALID)) { - linenr_T from, to; - - if (VIsual_active) { - if (VIsual_mode != wp->w_old_visual_mode || type == INVERTED_ALL) { - // If the type of Visual selection changed, redraw the whole - // selection. Also when the ownership of the X selection is - // gained or lost. - if (curwin->w_cursor.lnum < VIsual.lnum) { - from = curwin->w_cursor.lnum; - to = VIsual.lnum; - } else { - from = VIsual.lnum; - to = curwin->w_cursor.lnum; - } - // redraw more when the cursor moved as well - if (wp->w_old_cursor_lnum < from) { - from = wp->w_old_cursor_lnum; - } - if (wp->w_old_cursor_lnum > to) { - to = wp->w_old_cursor_lnum; - } - if (wp->w_old_visual_lnum < from) { - from = wp->w_old_visual_lnum; - } - if (wp->w_old_visual_lnum > to) { - to = wp->w_old_visual_lnum; - } - } else { - /* - * Find the line numbers that need to be updated: The lines - * between the old cursor position and the current cursor - * position. Also check if the Visual position changed. - */ - if (curwin->w_cursor.lnum < wp->w_old_cursor_lnum) { - from = curwin->w_cursor.lnum; - to = wp->w_old_cursor_lnum; - } else { - from = wp->w_old_cursor_lnum; - to = curwin->w_cursor.lnum; - if (from == 0) { // Visual mode just started - from = to; - } - } - - if (VIsual.lnum != wp->w_old_visual_lnum - || VIsual.col != wp->w_old_visual_col) { - if (wp->w_old_visual_lnum < from - && wp->w_old_visual_lnum != 0) { - from = wp->w_old_visual_lnum; - } - if (wp->w_old_visual_lnum > to) { - to = wp->w_old_visual_lnum; - } - if (VIsual.lnum < from) { - from = VIsual.lnum; - } - if (VIsual.lnum > to) { - to = VIsual.lnum; - } - } - } - - /* - * If in block mode and changed column or curwin->w_curswant: - * update all lines. - * First compute the actual start and end column. - */ - if (VIsual_mode == Ctrl_V) { - colnr_T fromc, toc; - unsigned int save_ve_flags = curwin->w_ve_flags; - - if (curwin->w_p_lbr) { - curwin->w_ve_flags = VE_ALL; - } - - getvcols(wp, &VIsual, &curwin->w_cursor, &fromc, &toc); - toc++; - curwin->w_ve_flags = save_ve_flags; - // Highlight to the end of the line, unless 'virtualedit' has - // "block". - if (curwin->w_curswant == MAXCOL) { - if (get_ve_flags() & VE_BLOCK) { - pos_T pos; - int cursor_above = curwin->w_cursor.lnum < VIsual.lnum; - - // Need to find the longest line. - toc = 0; - pos.coladd = 0; - for (pos.lnum = curwin->w_cursor.lnum; - cursor_above ? pos.lnum <= VIsual.lnum : pos.lnum >= VIsual.lnum; - pos.lnum += cursor_above ? 1 : -1) { - colnr_T t; - - pos.col = (colnr_T)STRLEN(ml_get_buf(wp->w_buffer, pos.lnum, false)); - getvvcol(wp, &pos, NULL, NULL, &t); - if (toc < t) { - toc = t; - } - } - toc++; - } else { - toc = MAXCOL; - } - } - - if (fromc != wp->w_old_cursor_fcol - || toc != wp->w_old_cursor_lcol) { - if (from > VIsual.lnum) { - from = VIsual.lnum; - } - if (to < VIsual.lnum) { - to = VIsual.lnum; - } - } - wp->w_old_cursor_fcol = fromc; - wp->w_old_cursor_lcol = toc; - } - } else { - // Use the line numbers of the old Visual area. - if (wp->w_old_cursor_lnum < wp->w_old_visual_lnum) { - from = wp->w_old_cursor_lnum; - to = wp->w_old_visual_lnum; - } else { - from = wp->w_old_visual_lnum; - to = wp->w_old_cursor_lnum; - } - } - - /* - * There is no need to update lines above the top of the window. - */ - if (from < wp->w_topline) { - from = wp->w_topline; - } - - /* - * If we know the value of w_botline, use it to restrict the update to - * the lines that are visible in the window. - */ - if (wp->w_valid & VALID_BOTLINE) { - if (from >= wp->w_botline) { - from = wp->w_botline - 1; - } - if (to >= wp->w_botline) { - to = wp->w_botline - 1; - } - } - - /* - * Find the minimal part to be updated. - * Watch out for scrolling that made entries in w_lines[] invalid. - * E.g., CTRL-U makes the first half of w_lines[] invalid and sets - * top_end; need to redraw from top_end to the "to" line. - * A middle mouse click with a Visual selection may change the text - * 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; - if (scrolled_down) { - mid_start = top_end; - } else { - mid_start = 0; - } - while (lnum < from && idx < wp->w_lines_valid) { // find start - if (wp->w_lines[idx].wl_valid) { - mid_start += wp->w_lines[idx].wl_size; - } else if (!scrolled_down) { - srow += wp->w_lines[idx].wl_size; - } - ++idx; - if (idx < wp->w_lines_valid && wp->w_lines[idx].wl_valid) { - lnum = wp->w_lines[idx].wl_lnum; - } else { - ++lnum; - } - } - srow += mid_start; - mid_end = wp->w_grid.rows; - for (; idx < wp->w_lines_valid; idx++) { // find end - if (wp->w_lines[idx].wl_valid - && wp->w_lines[idx].wl_lnum >= to + 1) { - // Only update until first row of this line - mid_end = srow; - break; - } - srow += wp->w_lines[idx].wl_size; - } - } - } - - if (VIsual_active && buf == curwin->w_buffer) { - wp->w_old_visual_mode = (char)VIsual_mode; - wp->w_old_cursor_lnum = curwin->w_cursor.lnum; - wp->w_old_visual_lnum = VIsual.lnum; - wp->w_old_visual_col = VIsual.col; - wp->w_old_curswant = curwin->w_curswant; - } else { - wp->w_old_visual_mode = 0; - wp->w_old_cursor_lnum = 0; - wp->w_old_visual_lnum = 0; - 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); - - 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) */ - if (row == wp->w_grid.rows) { - didline = true; - break; - } - - // stop updating when hit the end of the file - if (lnum > buf->b_ml.ml_line_count) { - eof = true; - break; - } - - /* Remember the starting row of the line that is going to be dealt - * with. It is used further down when the line doesn't fit. */ - srow = row; - - // Update a line when it is in an area that needs updating, when it - // has changes or w_lines[idx] is invalid. - // "bot_start" may be halfway a wrapped line after using - // win_scroll_lines(), check if the current line includes it. - // When syntax folding is being used, the saved syntax states will - // already have been updated, we can't see where the syntax state is - // the same again, just update until the end of the window. - if (row < top_end - || (row >= mid_start && row < mid_end) - || top_to_mod - || idx >= wp->w_lines_valid - || (row + wp->w_lines[idx].wl_size > bot_start) - || (mod_top != 0 - && (lnum == mod_top - || (lnum >= mod_top - && (lnum < mod_bot - || did_update == DID_FOLD - || (did_update == DID_LINE - && syntax_present(wp) - && ((foldmethodIsSyntax(wp) - && hasAnyFolding(wp)) - || syntax_check_changed(lnum))) - // match in fixed position might need redraw - // if lines were inserted or deleted - || (wp->w_match_head != NULL - && buf->b_mod_xlines != 0))))) - || (cursorline_standout && lnum == wp->w_cursor.lnum) - || lnum == wp->w_last_cursorline) { - if (lnum == mod_top) { - top_to_mod = false; - } - - /* - * When at start of changed lines: May scroll following lines - * up or down to minimize redrawing. - * Don't do this when the change continues until the end. - * Don't scroll when dollar_vcol >= 0, keep the "$". - * Don't scroll when redrawing the top, scrolled already above. - */ - if (lnum == mod_top - && mod_bot != MAXLNUM - && !(dollar_vcol >= 0 && mod_bot == mod_top + 1) - && row >= top_end) { - int old_rows = 0; - int new_rows = 0; - int xtra_rows; - linenr_T l; - - /* Count the old number of window rows, using w_lines[], which - * should still contain the sizes for the lines as they are - * currently displayed. */ - for (i = idx; i < wp->w_lines_valid; ++i) { - /* Only valid lines have a meaningful wl_lnum. Invalid - * lines are part of the changed area. */ - if (wp->w_lines[i].wl_valid - && wp->w_lines[i].wl_lnum == mod_bot) { - break; - } - old_rows += wp->w_lines[i].wl_size; - if (wp->w_lines[i].wl_valid - && wp->w_lines[i].wl_lastlnum + 1 == mod_bot) { - /* Must have found the last valid entry above mod_bot. - * Add following invalid entries. */ - ++i; - while (i < wp->w_lines_valid - && !wp->w_lines[i].wl_valid) { - old_rows += wp->w_lines[i++].wl_size; - } - break; - } - } - - if (i >= wp->w_lines_valid) { - /* We can't find a valid line below the changed lines, - * need to redraw until the end of the window. - * Inserting/deleting lines has no use. */ - bot_start = 0; - } else { - /* Able to count old number of rows: Count new window - * rows, and may insert/delete lines */ - j = idx; - for (l = lnum; l < mod_bot; l++) { - if (hasFoldingWin(wp, l, NULL, &l, true, NULL)) { - new_rows++; - } else if (l == wp->w_topline) { - new_rows += plines_win_nofill(wp, l, true) + wp->w_topfill; - } else { - new_rows += plines_win(wp, l, true); - } - j++; - if (new_rows > wp->w_grid.rows - row - 2) { - // it's getting too much, must redraw the rest - new_rows = 9999; - break; - } - } - xtra_rows = new_rows - old_rows; - if (xtra_rows < 0) { - /* May scroll text up. If there is not enough - * remaining text or scrolling fails, must redraw the - * rest. If scrolling works, must redraw the text - * below the scrolled text. */ - if (row - xtra_rows >= wp->w_grid.rows - 2) { - mod_bot = MAXLNUM; - } else { - win_scroll_lines(wp, row, xtra_rows); - bot_start = wp->w_grid.rows + xtra_rows; - } - } else if (xtra_rows > 0) { - /* May scroll text down. If there is not enough - * remaining text of scrolling fails, must redraw the - * rest. */ - if (row + xtra_rows >= wp->w_grid.rows - 2) { - mod_bot = MAXLNUM; - } else { - win_scroll_lines(wp, row + old_rows, xtra_rows); - if (top_end > row + old_rows) { - // Scrolled the part at the top that requires - // updating down. - top_end += xtra_rows; - } - } - } - - /* When not updating the rest, may need to move w_lines[] - * entries. */ - if (mod_bot != MAXLNUM && i != j) { - if (j < i) { - int x = row + new_rows; - - // move entries in w_lines[] upwards - for (;;) { - // stop at last valid entry in w_lines[] - if (i >= wp->w_lines_valid) { - wp->w_lines_valid = (int)j; - break; - } - wp->w_lines[j] = wp->w_lines[i]; - // stop at a line that won't fit - if (x + (int)wp->w_lines[j].wl_size - > wp->w_grid.rows) { - wp->w_lines_valid = (int)j + 1; - break; - } - x += wp->w_lines[j++].wl_size; - ++i; - } - if (bot_start > x) { - bot_start = x; - } - } else { // j > i - // move entries in w_lines[] downwards - j -= i; - wp->w_lines_valid += (linenr_T)j; - if (wp->w_lines_valid > wp->w_grid.rows) { - wp->w_lines_valid = wp->w_grid.rows; - } - for (i = wp->w_lines_valid; i - j >= idx; i--) { - wp->w_lines[i] = wp->w_lines[i - j]; - } - - /* The w_lines[] entries for inserted lines are - * now invalid, but wl_size may be used above. - * Reset to zero. */ - while (i >= idx) { - wp->w_lines[i].wl_size = 0; - wp->w_lines[i--].wl_valid = FALSE; - } - } - } - } - } - - /* - * When lines are folded, display one line for all of them. - * Otherwise, display normally (can be several display lines when - * 'wrap' is on). - */ - foldinfo_T foldinfo = fold_info(wp, lnum); - - if (foldinfo.fi_lines == 0 - && idx < wp->w_lines_valid - && wp->w_lines[idx].wl_valid - && wp->w_lines[idx].wl_lnum == lnum - && lnum > wp->w_topline - && !(dy_flags & (DY_LASTLINE | DY_TRUNCATE)) - && srow + wp->w_lines[idx].wl_size > wp->w_grid.rows - && win_get_fill(wp, lnum) == 0) { - // This line is not going to fit. Don't draw anything here, - // will draw "@ " lines below. - row = wp->w_grid.rows + 1; - } else { - prepare_search_hl(wp, &search_hl, lnum); - // Let the syntax stuff know we skipped a few lines. - if (syntax_last_parsed != 0 && syntax_last_parsed + 1 < lnum - && syntax_present(wp)) { - syntax_end_parsing(syntax_last_parsed + 1); - } - - // Display one line - row = win_line(wp, lnum, srow, - foldinfo.fi_lines ? srow : wp->w_grid.rows, - mod_top == 0, false, foldinfo, &line_providers); - - if (foldinfo.fi_lines == 0) { - wp->w_lines[idx].wl_folded = false; - wp->w_lines[idx].wl_lastlnum = lnum; - did_update = DID_LINE; - syntax_last_parsed = lnum; - } else { - foldinfo.fi_lines--; - wp->w_lines[idx].wl_folded = true; - wp->w_lines[idx].wl_lastlnum = lnum + foldinfo.fi_lines; - did_update = DID_FOLD; - } - } - - wp->w_lines[idx].wl_lnum = lnum; - wp->w_lines[idx].wl_valid = true; - - if (row > wp->w_grid.rows) { // past end of grid - // we may need the size of that too long line later on - if (dollar_vcol == -1) { - wp->w_lines[idx].wl_size = (uint16_t)plines_win(wp, lnum, true); - } - idx++; - break; - } - if (dollar_vcol == -1) { - wp->w_lines[idx].wl_size = (uint16_t)(row - srow); - } - idx++; - lnum += foldinfo.fi_lines + 1; - } else { - if (wp->w_p_rnu && wp->w_last_cursor_lnum_rnu != wp->w_cursor.lnum) { - // 'relativenumber' set and cursor moved vertically: The - // text doesn't need to be drawn, but the number column does. - foldinfo_T info = fold_info(wp, lnum); - (void)win_line(wp, lnum, srow, wp->w_grid.rows, true, true, - info, &line_providers); - } - - // This line does not need to be drawn, advance to the next one. - row += wp->w_lines[idx++].wl_size; - if (row > wp->w_grid.rows) { // past end of screen - break; - } - lnum = wp->w_lines[idx - 1].wl_lastlnum + 1; - did_update = DID_NONE; - } - - if (lnum > buf->b_ml.ml_line_count) { - eof = true; - break; - } - } - /* - * End of loop over all window lines. - */ - - // Now that the window has been redrawn with the old and new cursor line, - // update w_last_cursorline. - wp->w_last_cursorline = cursorline_standout ? wp->w_cursor.lnum : 0; - - wp->w_last_cursor_lnum_rnu = wp->w_p_rnu ? wp->w_cursor.lnum : 0; - - if (idx > wp->w_lines_valid) { - wp->w_lines_valid = idx; - } - - /* - * Let the syntax stuff know we stop parsing here. - */ - if (syntax_last_parsed != 0 && syntax_present(wp)) { - syntax_end_parsing(syntax_last_parsed + 1); - } - - /* - * 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; - wp->w_filler_rows = 0; - if (!eof && !didline) { - int at_attr = hl_combine_attr(wp->w_hl_attr_normal, - win_hl_attr(wp, HLF_AT)); - if (lnum == wp->w_topline) { - /* - * Single line that does not fit! - * Don't overwrite it, it can be edited. - */ - wp->w_botline = lnum + 1; - } else if (win_get_fill(wp, lnum) >= wp->w_grid.rows - srow) { - // Window ends in filler lines. - wp->w_botline = lnum; - wp->w_filler_rows = wp->w_grid.rows - srow; - } else if (dy_flags & DY_TRUNCATE) { // 'display' has "truncate" - int scr_row = wp->w_grid.rows - 1; - - // Last line isn't finished: Display "@@@" in the last screen line. - grid_puts_len(&wp->w_grid, (char_u *)"@@", MIN(wp->w_grid.cols, 2), scr_row, 0, at_attr); - - grid_fill(&wp->w_grid, scr_row, scr_row + 1, 2, wp->w_grid.cols, - '@', ' ', at_attr); - set_empty_rows(wp, srow); - wp->w_botline = lnum; - } else if (dy_flags & DY_LASTLINE) { // 'display' has "lastline" - int start_col = wp->w_grid.cols - 3; - - // Last line isn't finished: Display "@@@" at the end. - grid_fill(&wp->w_grid, wp->w_grid.rows - 1, wp->w_grid.rows, - MAX(start_col, 0), wp->w_grid.cols, '@', '@', at_attr); - set_empty_rows(wp, srow); - wp->w_botline = lnum; - } else { - win_draw_end(wp, '@', ' ', true, srow, wp->w_grid.rows, HLF_AT); - wp->w_botline = lnum; - } - } 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); - 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; - row = win_line(wp, wp->w_botline, row, wp->w_grid.rows, - false, false, info, &line_providers); - } - } else if (dollar_vcol == -1) { - wp->w_botline = lnum; - } - - // make sure the rest of the screen is blank - // write the 'eob' character to rows that aren't part of the file. - win_draw_end(wp, wp->w_p_fcs_chars.eob, ' ', false, row, wp->w_grid.rows, - HLF_EOB); - } - - kvi_destroy(line_providers); - - if (wp->w_redr_type >= REDRAW_TOP) { - draw_vsep_win(wp); - draw_hsep_win(wp); - draw_sep_connectors_win(wp); - } - syn_set_timeout(NULL); - - // Reset the type of redrawing required, the window has been updated. - wp->w_redr_type = 0; - wp->w_old_topfill = wp->w_topfill; - wp->w_old_botfill = wp->w_botfill; - - // Send win_extmarks if needed - for (size_t n = 0; n < kv_size(win_extmark_arr); n++) { - ui_call_win_extmark(wp->w_grid_alloc.handle, wp->handle, - kv_A(win_extmark_arr, n).ns_id, (Integer)kv_A(win_extmark_arr, n).mark_id, - kv_A(win_extmark_arr, n).win_row, kv_A(win_extmark_arr, n).win_col); - } - - if (dollar_vcol == -1) { - /* - * There is a trick with w_botline. If we invalidate it on each - * change that might modify it, this will cause a lot of expensive - * calls to plines_win() in update_topline() each time. Therefore the - * value of w_botline is often approximated, and this value is used to - * compute the value of w_topline. If the value of w_botline was - * wrong, check that the value of w_topline is correct (cursor is on - * the visible part of the text). If it's not, we need to redraw - * again. Mostly this just means scrolling up a few lines, so it - * doesn't look too bad. Only do this for the current window (where - * changes are relevant). - */ - wp->w_valid |= VALID_BOTLINE; - wp->w_viewport_invalid = true; - if (wp == curwin && wp->w_botline != old_botline && !recursive) { - recursive = true; - curwin->w_valid &= ~VALID_TOPLINE; - 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; - curbuf->b_mod_set = false; - win_update(curwin, providers); - must_redraw = 0; - curbuf->b_mod_set = i; - } - recursive = false; - } - } - - // restore got_int, unless CTRL-C was hit while redrawing - if (!got_int) { - got_int = save_got_int; - } -} - /// Returns width of the signcolumn that should be used for the whole window /// /// @param wp window we want signcolumn width from @@ -1783,7 +120,7 @@ static int win_fill_end(win_T *wp, int c1, int c2, int off, int width, int row, /// Clear lines near the end of the window and mark the unused lines with "c1". /// Use "c2" as filler character. /// When "draw_margin" is true, then draw the sign/fold/number columns. -static void win_draw_end(win_T *wp, int c1, int c2, bool draw_margin, int row, int endrow, hlf_T hl) +void win_draw_end(win_T *wp, int c1, int c2, bool draw_margin, int row, int endrow, hlf_T hl) { assert(hl >= 0 && hl < HLF_COUNT); int n = 0; @@ -1808,7 +145,7 @@ static void win_draw_end(win_T *wp, int c1, int c2, bool draw_margin, int row, i } } - int attr = hl_combine_attr(wp->w_hl_attr_normal, win_hl_attr(wp, (int)hl)); + int attr = hl_combine_attr(win_bg_attr(wp), win_hl_attr(wp, (int)hl)); if (wp->w_p_rl) { grid_fill(&wp->w_grid, row, endrow, wp->w_wincol, W_ENDCOL(wp) - 1 - n, @@ -1822,20 +159,9 @@ static void win_draw_end(win_T *wp, int c1, int c2, bool draw_margin, int row, i set_empty_rows(wp, row); } -/// Advance **color_cols -/// -/// @return true when there are columns to draw. -static bool advance_color_col(int vcol, int **color_cols) -{ - while (**color_cols >= 0 && vcol > **color_cols) { - ++*color_cols; - } - return **color_cols >= 0; -} - -// Compute the width of the foldcolumn. Based on 'foldcolumn' and how much -// space is available for window "wp", minus "col". -static int compute_foldcolumn(win_T *wp, int col) +/// Compute the width of the foldcolumn. Based on 'foldcolumn' and how much +/// space is available for window "wp", minus "col". +int compute_foldcolumn(win_T *wp, int col) { int fdc = win_fdccol_count(wp); int wmw = wp == curwin && p_wmw == 0 ? 1 : (int)p_wmw; @@ -1847,63 +173,6 @@ static int compute_foldcolumn(win_T *wp, int col) return fdc; } -/// Put a single char from an UTF-8 buffer into a line buffer. -/// -/// 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); - int u8c, u8cc[MAX_MCO]; - if (cells > maxcells) { - return -1; - } - 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)); - 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; - - // 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); - s->prev_c1 = u8cc[0]; - } else { - pc = utfc_ptr2char(p + c_len, pcc); - nc = s->prev_c; - pc1 = pcc[0]; - } - s->prev_c = u8c; - - u8c = arabic_shape(u8c, &firstbyte, &u8cc[0], pc, pc1, nc); - } else { - s->prev_c = u8c; - } - schar_from_cc(dest[0], u8c, u8cc); - } - if (cells > 1) { - dest[1][0] = 0; - } -done: - s->p += c_len; - return cells; -} - /// Fills the foldcolumn at "p" for window "wp". /// Only to be called when 'foldcolumn' > 0. /// @@ -1913,7 +182,7 @@ done: /// /// Assume monocell characters /// @return number of chars added to \param p -static size_t fill_foldcolumn(char_u *p, win_T *wp, foldinfo_T foldinfo, linenr_T lnum) +size_t fill_foldcolumn(char_u *p, win_T *wp, foldinfo_T foldinfo, linenr_T lnum) { int i = 0; int level; @@ -1968,2751 +237,22 @@ static size_t fill_foldcolumn(char_u *p, win_T *wp, foldinfo_T foldinfo, linenr_ return MAX(char_counter + (size_t)(fdc - i), (size_t)fdc); } -static inline void provider_err_virt_text(linenr_T lnum, char *err) -{ - Decoration err_decor = DECORATION_INIT; - int hl_err = syn_check_group(S_LEN("ErrorMsg")); - kv_push(err_decor.virt_text, - ((VirtTextChunk){ .text = provider_err, - .hl_id = hl_err })); - err_decor.virt_text_width = (int)mb_string2cells(err); - decor_add_ephemeral(lnum - 1, 0, lnum - 1, 0, &err_decor, 0, 0); -} - -static inline void get_line_number_str(win_T *wp, linenr_T lnum, char_u *buf, size_t buf_len) -{ - long num; - char *fmt = "%*ld "; - - if (wp->w_p_nu && !wp->w_p_rnu) { - // 'number' + 'norelativenumber' - num = (long)lnum; - } else { - // 'relativenumber', don't use negative numbers - num = labs((long)get_cursor_rel_lnum(wp, lnum)); - if (num == 0 && wp->w_p_nu && wp->w_p_rnu) { - // 'number' + 'relativenumber' - num = lnum; - fmt = "%-*ld "; - } - } - - snprintf((char *)buf, buf_len, fmt, number_width(wp), num); -} - -/// Display line "lnum" of window 'wp' on the screen. -/// wp->w_virtcol needs to be valid. -/// -/// @param lnum line to display -/// @param startrow first row relative to window grid -/// @param endrow last grid row to be redrawn -/// @param nochange not updating for changed text -/// @param number_only only update the number column -/// @param foldinfo fold info for this line -/// @param[in, out] providers decoration providers active this line -/// items will be disables if they cause errors -/// or explicitly return `false`. -/// -/// @return the number of last row the line occupies. -static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, - bool number_only, foldinfo_T foldinfo, DecorProviders *providers) -{ - int c = 0; // init for GCC - 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" - 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 - // 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 - 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 - 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; - int saved_c_extra = 0; - int saved_c_final = 0; - int saved_char_attr = 0; - - int n_attr = 0; // chars with special attr - int saved_attr2 = 0; // char_attr saved for n_attr - int n_attr3 = 0; // chars with overruling special attr - int saved_attr3 = 0; // char_attr saved for n_attr3 - - int n_skip = 0; // nr of chars to skip for 'nowrap' - - int fromcol = -10; // start of inverting - int tocol = MAXCOL; // end of inverting - int fromcol_prev = -2; // start of inverting after cursor - bool noinvcur = false; // don't invert the cursor - bool lnum_in_visual_area = false; - pos_T pos; - long v; - - 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 area_attr = 0; // attributes desired by highlighting - int search_attr = 0; // attributes desired by 'hlsearch' - int vcol_save_attr = 0; // saved attr for 'cursorcolumn' - int syntax_attr = 0; // attributes desired by syntax - int has_syntax = FALSE; // this buffer has syntax highl. - int save_did_emsg; - int eol_hl_off = 0; // 1 if highlighted char after EOL - bool draw_color_col = false; // highlight colorcolumn - 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 - int nextlinecol = 0; // column where nextline[] starts - int nextline_idx = 0; /* index in nextline[] where next line - starts */ - int spell_attr = 0; // attributes desired by spelling - int word_end = 0; // last byte with same spell_attr - static linenr_T checked_lnum = 0; // line number for "checked_col" - static int checked_col = 0; /* column in "checked_lnum" up to which - * there are no spell errors */ - static int cap_col = -1; // column to check for Cap word - static linenr_T capcol_lnum = 0; // line number where "cap_col" - int cur_checked_col = 0; // checked column for current line - int extra_check = 0; // has syntax or linebreak - int multi_attr = 0; // attributes desired by multibyte - int mb_l = 1; // multi-byte byte length - int mb_c = 0; // decoded multi-byte character - bool mb_utf8 = false; // screen char is UTF-8 char - int u8cc[MAX_MCO]; // composing UTF-8 chars - int filler_lines; // nr of filler lines to be drawn - int filler_todo; // nr of filler lines still to do + 1 - hlf_T diff_hlf = (hlf_T)0; // type of diff highlighting - int change_start = MAXCOL; // first col of changed area - int change_end = -1; // last col of changed area - colnr_T trailcol = MAXCOL; // start of trailing spaces - colnr_T leadcol = 0; // start of leading spaces - bool in_multispace = false; // in multiple consecutive spaces - int multispace_pos = 0; // position in lcs-multispace string - bool need_showbreak = false; // overlong line, skip first x chars - sign_attrs_T sattrs[SIGN_SHOW_MAX]; // attributes for signs - int num_signs; // number of signs for line - int line_attr = 0; // attribute for the whole line - int line_attr_save; - int line_attr_lowprio = 0; // low-priority attribute for the line - int line_attr_lowprio_save; - int prev_c = 0; // previous Arabic character - int prev_c1 = 0; // first composing char for prev_c - - bool search_attr_from_match = false; // if search_attr is from :match - 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 - - bool area_active = false; - - int cul_attr = 0; // set when 'cursorline' active - // 'cursorlineopt' has "screenline" and cursor is in this line - bool cul_screenline = false; - // margin columns for the screen line, needed for when 'cursorlineopt' - // contains "screenline" - 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 - - int syntax_flags = 0; - int syntax_seqnr = 0; - int prev_syntax_id = 0; - int conceal_attr = win_hl_attr(wp, HLF_CONCEAL); - bool is_concealing = false; - int boguscols = 0; ///< nonexistent columns added to - ///< 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 \ - { \ - n_extra += vcol_off; \ - vcol -= vcol_off; \ - vcol_off = 0; \ - col -= boguscols; \ - old_boguscols = boguscols; \ - boguscols = 0; \ - } - - if (startrow > endrow) { // past the end already! - return startrow; - } - - row = startrow; - - buf_T *buf = wp->w_buffer; - bool end_fill = (lnum == buf->b_ml.ml_line_count + 1); - - if (!number_only) { - // To speed up the loop below, set extra_check when there is linebreak, - // trailing white space and/or syntax processing to be done. - extra_check = wp->w_p_lbr; - if (syntax_present(wp) && !wp->w_s->b_syn_error && !wp->w_s->b_syn_slow - && !has_fold && !end_fill) { - // Prepare for syntax highlighting in this line. When there is an - // error, stop syntax highlighting. - save_did_emsg = did_emsg; - did_emsg = false; - syntax_start(wp, lnum); - if (did_emsg) { - wp->w_s->b_syn_error = true; - } else { - did_emsg = save_did_emsg; - if (!wp->w_s->b_syn_slow) { - has_syntax = true; - extra_check = true; - } - } - } - - has_decor = decor_redraw_line(buf, lnum - 1, &decor_state); - - providers_invoke_line(wp, providers, lnum - 1, &has_decor, &provider_err); - - if (provider_err) { - provider_err_virt_text(lnum, provider_err); - has_decor = true; - provider_err = NULL; - } - - if (has_decor) { - extra_check = true; - } - - // Check for columns to display for 'colorcolumn'. - color_cols = wp->w_buffer->terminal ? NULL : wp->w_p_cc_cols; - if (color_cols != NULL) { - draw_color_col = advance_color_col((int)VCOL_HLC, &color_cols); - } - - if (wp->w_p_spell - && !has_fold - && !end_fill - && *wp->w_s->b_p_spl != NUL - && !GA_EMPTY(&wp->w_s->b_langp) - && *(char **)(wp->w_s->b_langp.ga_data) != NULL) { - // Prepare for spell checking. - has_spell = true; - extra_check = true; - - // Get the start of the next line, so that words that wrap to the next - // line are found too: "et<line-break>al.". - // Trick: skip a few chars for C/shell/Vim comments - nextline[SPWORDLEN] = NUL; - if (lnum < wp->w_buffer->b_ml.ml_line_count) { - line = ml_get_buf(wp->w_buffer, lnum + 1, false); - spell_cat_line(nextline + SPWORDLEN, line, SPWORDLEN); - } - - // When a word wrapped from the previous line the start of the current - // line is valid. - if (lnum == checked_lnum) { - cur_checked_col = checked_col; - } - checked_lnum = 0; - - // When there was a sentence end in the previous line may require a - // word starting with capital in this line. In line 1 always check - // the first word. - if (lnum != capcol_lnum) { - cap_col = -1; - } - if (lnum == 1) { - cap_col = 0; - } - capcol_lnum = 0; - } - - // handle Visual active in this window - if (VIsual_active && wp->w_buffer == curwin->w_buffer) { - pos_T *top, *bot; - - if (ltoreq(curwin->w_cursor, VIsual)) { - // Visual is after curwin->w_cursor - top = &curwin->w_cursor; - bot = &VIsual; - } else { - // Visual is before curwin->w_cursor - top = &VIsual; - bot = &curwin->w_cursor; - } - lnum_in_visual_area = (lnum >= top->lnum && lnum <= bot->lnum); - if (VIsual_mode == Ctrl_V) { - // block mode - if (lnum_in_visual_area) { - fromcol = wp->w_old_cursor_fcol; - tocol = wp->w_old_cursor_lcol; - } - } else { - // non-block mode - if (lnum > top->lnum && lnum <= bot->lnum) { - fromcol = 0; - } else if (lnum == top->lnum) { - if (VIsual_mode == 'V') { // linewise - fromcol = 0; - } else { - getvvcol(wp, top, (colnr_T *)&fromcol, NULL, NULL); - if (gchar_pos(top) == NUL) { - tocol = fromcol + 1; - } - } - } - if (VIsual_mode != 'V' && lnum == bot->lnum) { - if (*p_sel == 'e' && bot->col == 0 - && bot->coladd == 0) { - fromcol = -10; - tocol = MAXCOL; - } else if (bot->col == MAXCOL) { - tocol = MAXCOL; - } else { - pos = *bot; - if (*p_sel == 'e') { - getvvcol(wp, &pos, (colnr_T *)&tocol, NULL, NULL); - } else { - getvvcol(wp, &pos, NULL, NULL, (colnr_T *)&tocol); - tocol++; - } - } - } - } - - // Check if the char under the cursor should be inverted (highlighted). - if (!highlight_match && lnum == curwin->w_cursor.lnum && wp == curwin - && cursor_is_block_during_visual(*p_sel == 'e')) { - noinvcur = true; - } - - // if inverting in this line set area_highlighting - if (fromcol >= 0) { - area_highlighting = true; - attr = win_hl_attr(wp, HLF_V); - } - // handle 'incsearch' and ":s///c" highlighting - } else if (highlight_match - && wp == curwin - && !has_fold - && lnum >= curwin->w_cursor.lnum - && lnum <= curwin->w_cursor.lnum + search_match_lines) { - if (lnum == curwin->w_cursor.lnum) { - getvcol(curwin, &(curwin->w_cursor), - (colnr_T *)&fromcol, NULL, NULL); - } else { - fromcol = 0; - } - if (lnum == curwin->w_cursor.lnum + search_match_lines) { - pos.lnum = lnum; - pos.col = search_match_endcol; - getvcol(curwin, &pos, (colnr_T *)&tocol, NULL, NULL); - } - // do at least one character; happens when past end of line - if (fromcol == tocol && search_match_endcol) { - tocol = fromcol + 1; - } - area_highlighting = true; - attr = win_hl_attr(wp, HLF_I); - } - } - - filler_lines = diff_check(wp, lnum); - if (filler_lines < 0) { - if (filler_lines == -1) { - if (diff_find_change(wp, lnum, &change_start, &change_end)) { - diff_hlf = HLF_ADD; // added line - } else if (change_start == 0) { - diff_hlf = HLF_TXD; // changed text - } else { - diff_hlf = HLF_CHD; // changed line - } - } else { - diff_hlf = HLF_ADD; // added line - } - filler_lines = 0; - area_highlighting = true; - } - VirtLines virt_lines = KV_INITIAL_VALUE; - int n_virt_lines = decor_virt_lines(wp, lnum, &virt_lines); - filler_lines += n_virt_lines; - if (lnum == wp->w_topline) { - filler_lines = wp->w_topfill; - n_virt_lines = MIN(n_virt_lines, filler_lines); - } - filler_todo = filler_lines; - - // Cursor line highlighting for 'cursorline' in the current window. - if (lnum == wp->w_cursor.lnum) { - // Do not show the cursor line in the text when Visual mode is active, - // because it's not clear what is selected then. - if (wp->w_p_cul && !(wp == curwin && VIsual_active) - && wp->w_p_culopt_flags != CULOPT_NBR) { - cul_screenline = (wp->w_p_wrap - && (wp->w_p_culopt_flags & CULOPT_SCRLINE)); - if (!cul_screenline) { - cul_attr = win_hl_attr(wp, HLF_CUL); - HlAttrs ae = syn_attr2entry(cul_attr); - // We make a compromise here (#7383): - // * low-priority CursorLine if fg is not set - // * high-priority ("same as Vim" priority) CursorLine if fg is set - if (ae.rgb_fg_color == -1 && ae.cterm_fg_color == 0) { - line_attr_lowprio = cul_attr; - } else { - if (!(State & MODE_INSERT) && bt_quickfix(wp->w_buffer) - && qf_current_entry(wp) == lnum) { - line_attr = hl_combine_attr(cul_attr, line_attr); - } else { - line_attr = cul_attr; - } - } - } else { - margin_columns_win(wp, &left_curline_col, &right_curline_col); - } - area_highlighting = true; - } - } - - memset(sattrs, 0, sizeof(sattrs)); - num_signs = buf_get_signattrs(wp->w_buffer, lnum, sattrs); - decor_redraw_signs(buf, lnum - 1, &num_signs, sattrs); - - // If this line has a sign with line highlighting set line_attr. - // TODO(bfredl, vigoux): this should not take priority over decoration! - sign_attrs_T *sattr = sign_get_attr(SIGN_LINEHL, sattrs, 0, 1); - if (sattr != NULL) { - line_attr = sattr->sat_linehl; - } - - // Highlight the current line in the quickfix window. - if (bt_quickfix(wp->w_buffer) && qf_current_entry(wp) == lnum) { - line_attr = win_hl_attr(wp, HLF_QFL); - } - - if (line_attr_lowprio || line_attr) { - area_highlighting = true; - } - - if (cul_screenline) { - line_attr_save = line_attr; - line_attr_lowprio_save = line_attr_lowprio; - } - - line = end_fill ? (char_u *)"" : 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(line); - } - - /* To be able to spell-check over line boundaries copy the end of the - * current line into nextline[]. Above the start of the next line was - * copied to nextline[SPWORDLEN]. */ - if (nextline[SPWORDLEN] == NUL) { - // No next line or it is empty. - nextlinecol = MAXCOL; - nextline_idx = 0; - } else { - v = (long)STRLEN(line); - if (v < SPWORDLEN) { - /* Short line, use it completely and append the start of the - * next line. */ - nextlinecol = 0; - memmove(nextline, line, (size_t)v); - STRMOVE(nextline + v, nextline + SPWORDLEN); - nextline_idx = (int)v + 1; - } else { - // Long line, use only the last SPWORDLEN bytes. - nextlinecol = (int)v - SPWORDLEN; - memmove(nextline, line + nextlinecol, SPWORDLEN); // -V512 - nextline_idx = SPWORDLEN + 1; - } - } - } - - if (wp->w_p_list && !has_fold && !end_fill) { - if (wp->w_p_lcs_chars.space - || wp->w_p_lcs_chars.multispace != NULL - || wp->w_p_lcs_chars.leadmultispace != NULL - || wp->w_p_lcs_chars.trail - || wp->w_p_lcs_chars.lead - || wp->w_p_lcs_chars.nbsp) { - extra_check = true; - } - // find start of trailing whitespace - if (wp->w_p_lcs_chars.trail) { - trailcol = (colnr_T)STRLEN(ptr); - while (trailcol > (colnr_T)0 && ascii_iswhite(ptr[trailcol - 1])) { - trailcol--; - } - trailcol += (colnr_T)(ptr - line); - } - // find end of leading whitespace - if (wp->w_p_lcs_chars.lead || wp->w_p_lcs_chars.leadmultispace != NULL) { - leadcol = 0; - while (ascii_iswhite(ptr[leadcol])) { - leadcol++; - } - if (ptr[leadcol] == NUL) { - // in a line full of spaces all of them are treated as trailing - leadcol = (colnr_T)0; - } else { - // keep track of the first column not filled with spaces - leadcol += (colnr_T)(ptr - line) + 1; - } - } - } - - /* - * 'nowrap' or 'wrap' and a single line that doesn't fit: Advance to the - * first character to be displayed. - */ - if (wp->w_p_wrap) { - v = wp->w_skipcol; - } else { - v = wp->w_leftcol; - } - if (v > 0 && !number_only) { - char_u *prev_ptr = ptr; - while (vcol < v && *ptr != NUL) { - c = win_lbr_chartabsize(wp, line, ptr, (colnr_T)vcol, NULL); - vcol += c; - prev_ptr = ptr; - MB_PTR_ADV(ptr); - } - - // When: - // - 'cuc' is set, or - // - 'colorcolumn' is set, or - // - 'virtualedit' is set, or - // - the visual mode is active, - // the end of the line may be before the start of the displayed part. - if (vcol < v && (wp->w_p_cuc - || draw_color_col - || virtual_active() - || (VIsual_active && wp->w_buffer == curwin->w_buffer))) { - vcol = v; - } - - /* Handle a character that's not completely on the screen: Put ptr at - * that character but skip the first few screen characters. */ - if (vcol > v) { - vcol -= c; - 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) >= c || *ptr == TAB) { - n_skip = (int)(v - vcol); - } - } - - /* - * Adjust for when the inverted text is before the screen, - * and when the start of the inverted text is before the screen. - */ - if (tocol <= vcol) { - fromcol = 0; - } else if (fromcol >= 0 && fromcol < vcol) { - fromcol = (int)vcol; - } - - // When w_skipcol is non-zero, first line needs 'showbreak' - if (wp->w_p_wrap) { - need_showbreak = true; - } - // When spell checking a word we need to figure out the start of the - // word and if it's badly spelled or not. - if (has_spell) { - size_t len; - colnr_T linecol = (colnr_T)(ptr - line); - hlf_T spell_hlf = HLF_COUNT; - - pos = wp->w_cursor; - wp->w_cursor.lnum = lnum; - wp->w_cursor.col = linecol; - len = spell_move_to(wp, FORWARD, true, true, &spell_hlf); - - // spell_move_to() may call ml_get() and make "line" invalid - line = ml_get_buf(wp->w_buffer, lnum, false); - ptr = line + linecol; - - if (len == 0 || (int)wp->w_cursor.col > ptr - line) { - /* no bad word found at line start, don't check until end of a - * word */ - spell_hlf = HLF_COUNT; - word_end = (int)(spell_to_word_end(ptr, wp) - line + 1); - } else { - // bad word found, use attributes until end of word - assert(len <= INT_MAX); - word_end = wp->w_cursor.col + (int)len + 1; - - // Turn index into actual attributes. - if (spell_hlf != HLF_COUNT) { - spell_attr = highlight_attr[spell_hlf]; - } - } - wp->w_cursor = pos; - - // Need to restart syntax highlighting for this line. - if (has_syntax) { - syntax_start(wp, lnum); - } - } - } - - /* - * Correct highlighting for cursor that can't be disabled. - * Avoids having to check this for each character. - */ - if (fromcol >= 0) { - if (noinvcur) { - if ((colnr_T)fromcol == wp->w_virtcol) { - /* highlighting starts at cursor, let it start just after the - * cursor */ - fromcol_prev = fromcol; - fromcol = -1; - } else if ((colnr_T)fromcol < wp->w_virtcol) { - // restart highlighting after the cursor - fromcol_prev = wp->w_virtcol; - } - } - if (fromcol >= tocol) { - fromcol = -1; - } - } - - if (!number_only && !has_fold && !end_fill) { - v = ptr - line; - area_highlighting |= prepare_search_hl_line(wp, lnum, (colnr_T)v, - &line, &search_hl, &search_attr, - &search_attr_from_match); - ptr = line + v; // "line" may have been updated - } - - int off = 0; // Offset relative start of line - int col = 0; // Visual column on screen. - if (wp->w_p_rl) { - // Rightleft window: process the text in the normal direction, but put - // it in linebuf_char[off] from right to left. Start at the - // rightmost column of the window. - col = grid->cols - 1; - off += col; - } - - // won't highlight after TERM_ATTRS_MAX columns - int term_attrs[TERM_ATTRS_MAX] = { 0 }; - if (wp->w_buffer->terminal) { - terminal_get_line_attributes(wp->w_buffer->terminal, wp, lnum, term_attrs); - extra_check = true; - } - - int sign_idx = 0; - // Repeat for the whole displayed line. - for (;;) { - int has_match_conc = 0; ///< match wants to conceal - int decor_conceal = 0; - - bool did_decrement_ptr = false; - - // Skip this quickly when working on the text. - if (draw_state != WL_LINE) { - if (cul_screenline) { - cul_attr = 0; - line_attr = line_attr_save; - line_attr_lowprio = line_attr_lowprio_save; - } - - if (draw_state == WL_CMDLINE - 1 && n_extra == 0) { - draw_state = WL_CMDLINE; - if (cmdwin_type != 0 && wp == curwin) { - // Draw the cmdline character. - n_extra = 1; - c_extra = cmdwin_type; - c_final = NUL; - char_attr = win_hl_attr(wp, HLF_AT); - } - } - - 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 - // already be in use. - xfree(p_extra_free); - p_extra_free = xmalloc(MAX_MCO * (size_t)fdc + 1); - n_extra = (int)fill_foldcolumn(p_extra_free, wp, foldinfo, lnum); - p_extra_free[n_extra] = NUL; - p_extra = p_extra_free; - c_extra = NUL; - c_final = NUL; - if (use_cursor_line_sign(wp, lnum)) { - char_attr = win_hl_attr(wp, HLF_CLF); - } else { - char_attr = win_hl_attr(wp, HLF_FC); - } - } - } - - // sign column, this is hit until sign_idx reaches count - if (draw_state == WL_SIGN - 1 && n_extra == 0) { - draw_state = WL_SIGN; - /* Show the sign column when there are any signs in this - * buffer or when using Netbeans. */ - if (wp->w_scwidth > 0) { - get_sign_display_info(false, wp, lnum, sattrs, row, - startrow, filler_lines, filler_todo, - &c_extra, &c_final, extra, sizeof(extra), - &p_extra, &n_extra, &char_attr, sign_idx); - sign_idx++; - if (sign_idx < wp->w_scwidth) { - draw_state = WL_SIGN - 1; - } else { - sign_idx = 0; - } - } - } - - if (draw_state == WL_NR - 1 && n_extra == 0) { - draw_state = WL_NR; - /* Display the absolute or relative line number. After the - * first fill with blanks when the 'n' flag isn't in 'cpo' */ - if ((wp->w_p_nu || wp->w_p_rnu) - && (row == startrow + filler_lines - || vim_strchr(p_cpo, CPO_NUMCOL) == NULL)) { - // If 'signcolumn' is set to 'number' and a sign is present - // in 'lnum', then display the sign instead of the line - // number. - if (*wp->w_p_scl == 'n' && *(wp->w_p_scl + 1) == 'u' - && num_signs > 0 && sign_get_attr(SIGN_TEXT, sattrs, 0, 1)) { - get_sign_display_info(true, wp, lnum, sattrs, row, - startrow, filler_lines, filler_todo, - &c_extra, &c_final, extra, sizeof(extra), - &p_extra, &n_extra, &char_attr, sign_idx); - } 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)); - if (wp->w_skipcol > 0) { - for (p_extra = extra; *p_extra == ' '; p_extra++) { - *p_extra = '-'; - } - } - 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 = skiptowhite(p2) - 1; - for (char_u *p1 = (char_u *)skipwhite((char *)extra); p1 < p2; p1++, p2--) { - const char_u t = *p1; - *p1 = *p2; - *p2 = t; - } - } - p_extra = extra; - c_extra = NUL; - } else { - c_extra = ' '; - } - c_final = NUL; - n_extra = number_width(wp) + 1; - char_attr = get_line_number_attr(wp, lnum, row, startrow, filler_lines, sattrs); - } - } - } - - if (draw_state == WL_NR && n_extra == 0) { - win_col_offset = off; - } - - if (wp->w_briopt_sbr && draw_state == WL_BRI - 1 - && n_extra == 0 && *get_showbreak_value(wp) != NUL) { - // draw indent after showbreak value - draw_state = WL_BRI; - } else if (wp->w_briopt_sbr && draw_state == WL_SBR && n_extra == 0) { - // after the showbreak, draw the breakindent - draw_state = WL_BRI - 1; - } - - // draw 'breakindent': indent wrapped text accordingly - if (draw_state == WL_BRI - 1 && n_extra == 0) { - draw_state = WL_BRI; - // if need_showbreak is set, breakindent also applies - if (wp->w_p_bri && (row != startrow || need_showbreak) - && filler_lines == 0) { - char_attr = 0; - - if (diff_hlf != (hlf_T)0) { - char_attr = win_hl_attr(wp, (int)diff_hlf); - } - p_extra = NULL; - c_extra = ' '; - c_final = NUL; - n_extra = - 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) { - n_extra = 0; - } - } - if (wp->w_skipcol > 0 && wp->w_p_wrap && wp->w_briopt_sbr) { - need_showbreak = false; - } - // Correct end of highlighted area for 'breakindent', - // required wen 'linebreak' is also set. - if (tocol == vcol) { - tocol += n_extra; - } - } - } - - if (draw_state == WL_SBR - 1 && n_extra == 0) { - draw_state = WL_SBR; - if (filler_todo > filler_lines - n_virt_lines) { - // TODO(bfredl): check this doesn't inhibit TUI-style - // clear-to-end-of-line. - c_extra = ' '; - c_final = NUL; - if (wp->w_p_rl) { - n_extra = col + 1; - } else { - n_extra = grid->cols - col; - } - char_attr = 0; - } else if (filler_todo > 0) { - // draw "deleted" diff line(s) - if (char2cells(wp->w_p_fcs_chars.diff) > 1) { - c_extra = '-'; - c_final = NUL; - } else { - c_extra = wp->w_p_fcs_chars.diff; - c_final = NUL; - } - if (wp->w_p_rl) { - n_extra = col + 1; - } else { - n_extra = grid->cols - col; - } - char_attr = win_hl_attr(wp, HLF_DED); - } - char_u *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); - char_attr = win_hl_attr(wp, HLF_AT); - if (wp->w_skipcol == 0 || !wp->w_p_wrap) { - need_showbreak = false; - } - vcol_sbr = vcol + mb_charlen(sbr); - // Correct end of highlighted area for 'showbreak', - // required when 'linebreak' is also set. - if (tocol == vcol) { - tocol += n_extra; - } - // Combine 'showbreak' with 'cursorline', prioritizing 'showbreak'. - if (cul_attr) { - char_attr = hl_combine_attr(cul_attr, char_attr); - } - } - } - - if (draw_state == WL_LINE - 1 && n_extra == 0) { - sign_idx = 0; - draw_state = WL_LINE; - - if (has_decor && row == startrow + filler_lines) { - // hide virt_text on text hidden by 'nowrap' - decor_redraw_col(wp->w_buffer, (int)vcol, off, true, &decor_state); - } - - if (saved_n_extra) { - // Continue item from end of wrapped line. - n_extra = saved_n_extra; - c_extra = saved_c_extra; - c_final = saved_c_final; - p_extra = saved_p_extra; - char_attr = saved_char_attr; - } else { - char_attr = 0; - } - } - } - - if (cul_screenline && draw_state == WL_LINE - && vcol >= left_curline_col - && vcol < right_curline_col) { - cul_attr = win_hl_attr(wp, HLF_CUL); - HlAttrs ae = syn_attr2entry(cul_attr); - if (ae.rgb_fg_color == -1 && ae.cterm_fg_color == 0) { - line_attr_lowprio = cul_attr; - } else { - if (!(State & MODE_INSERT) && bt_quickfix(wp->w_buffer) - && qf_current_entry(wp) == lnum) { - line_attr = hl_combine_attr(cul_attr, line_attr); - } else { - line_attr = cul_attr; - } - } - } - - // When still displaying '$' of change command, stop at cursor - if (((dollar_vcol >= 0 - && wp == curwin - && lnum == wp->w_cursor.lnum - && vcol >= (long)wp->w_virtcol) - || (number_only && draw_state > WL_NR)) - && 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, - wp->w_hl_attr_normal, false); - // Pretend we have finished updating the window. Except when - // 'cursorcolumn' is set. - if (wp->w_p_cuc) { - row = wp->w_cline_row + wp->w_cline_height; - } else { - row = grid->rows; - } - break; - } - - if (draw_state == WL_LINE - && has_fold - && col == win_col_offset - && n_extra == 0 - && row == startrow) { - char_attr = win_hl_attr(wp, HLF_FL); - - linenr_T lnume = lnum + foldinfo.fi_lines - 1; - memset(buf_fold, ' ', FOLD_TEXT_LEN); - 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); - p_extra_free = p_extra; - } - c_extra = NUL; - c_final = NUL; - p_extra[n_extra] = NUL; - } - - if (draw_state == WL_LINE - && has_fold - && col < grid->cols - && n_extra == 0 - && row == startrow) { - // fill rest of line with 'fold' - c_extra = wp->w_p_fcs_chars.fold; - c_final = NUL; - - n_extra = wp->w_p_rl ? (col + 1) : (grid->cols - col); - } - - if (draw_state == WL_LINE - && has_fold - && col >= grid->cols - && n_extra != 0 - && row == startrow) { - // Truncate the folding. - n_extra = 0; - } - - if (draw_state == WL_LINE && (area_highlighting || has_spell)) { - // handle Visual or match highlighting in this line - if (vcol == fromcol - || (vcol + 1 == fromcol && n_extra == 0 - && utf_ptr2cells((char *)ptr) > 1) - || ((int)vcol_prev == fromcol_prev - && vcol_prev < vcol // not at margin - && vcol < tocol)) { - area_attr = attr; // start highlighting - if (area_highlighting) { - area_active = true; - } - } else if (area_attr != 0 && (vcol == tocol - || (noinvcur - && (colnr_T)vcol == wp->w_virtcol))) { - area_attr = 0; // stop highlighting - area_active = false; - } - - if (!n_extra) { - // Check for start/end of 'hlsearch' and other matches. - // After end, check for start/end of next match. - // When another match, have to check for start again. - v = (ptr - line); - search_attr = update_search_hl(wp, lnum, (colnr_T)v, &line, &search_hl, &has_match_conc, - &match_conc, lcs_eol_one, &search_attr_from_match); - ptr = line + v; // "line" may have been changed - - // Do not allow a conceal over EOL otherwise EOL will be missed - // and bad things happen. - if (*ptr == NUL) { - has_match_conc = 0; - } - } - - if (diff_hlf != (hlf_T)0) { - if (diff_hlf == HLF_CHD && ptr - line >= change_start - && n_extra == 0) { - diff_hlf = HLF_TXD; // changed text - } - if (diff_hlf == HLF_TXD && ptr - line > change_end - && n_extra == 0) { - diff_hlf = HLF_CHD; // changed line - } - line_attr = win_hl_attr(wp, (int)diff_hlf); - // Overlay CursorLine onto diff-mode highlight. - if (cul_attr) { - line_attr = 0 != line_attr_lowprio // Low-priority CursorLine - ? hl_combine_attr(hl_combine_attr(cul_attr, line_attr), - hl_get_underline()) - : hl_combine_attr(line_attr, cul_attr); - } - } - - // Decide which of the highlight attributes to use. - attr_pri = true; - - if (area_attr != 0) { - char_attr = hl_combine_attr(line_attr, area_attr); - if (!highlight_match) { - // let search highlight show in Visual area if possible - char_attr = hl_combine_attr(search_attr, char_attr); - } - } else if (search_attr != 0) { - char_attr = hl_combine_attr(line_attr, search_attr); - } - // Use line_attr when not in the Visual or 'incsearch' area - // (area_attr may be 0 when "noinvcur" is set). - else if (line_attr != 0 && ((fromcol == -10 && tocol == MAXCOL) - || vcol < fromcol || vcol_prev < fromcol_prev - || vcol >= tocol)) { - char_attr = line_attr; - } else { - attr_pri = false; - if (has_syntax) { - char_attr = syntax_attr; - } else { - char_attr = 0; - } - } - } - - // Get the next character to put on the screen. - // - // The "p_extra" points to the extra stuff that is inserted to - // represent special characters (non-printable stuff) and other - // things. When all characters are the same, c_extra is used. - // If c_final is set, it will compulsorily be used at the end. - // "p_extra" must end in a NUL to avoid utfc_ptr2len() reads past - // "p_extra[n_extra]". - // For the '$' of the 'list' option, n_extra == 1, p_extra == "". - if (n_extra > 0) { - if (c_extra != NUL || (n_extra == 1 && c_final != NUL)) { - c = (n_extra == 1 && c_final != NUL) ? c_final : c_extra; - mb_c = c; // doesn't handle non-utf-8 multi-byte! - if (utf_char2len(c) > 1) { - mb_utf8 = true; - u8cc[0] = 0; - c = 0xc0; - } else { - mb_utf8 = false; - } - } else { - assert(p_extra != NULL); - c = *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_utf8 = false; - if (mb_l > n_extra) { - mb_l = 1; - } else if (mb_l > 1) { - mb_c = utfc_ptr2char(p_extra, u8cc); - mb_utf8 = true; - c = 0xc0; - } - if (mb_l == 0) { // at the NUL at end-of-line - mb_l = 1; - } - - // If a double-width char doesn't fit display a '>' in the last column. - if ((wp->w_p_rl ? (col <= 0) : (col >= grid->cols - 1)) - && utf_char2cells(mb_c) == 2) { - c = '>'; - mb_c = c; - mb_l = 1; - (void)mb_l; - multi_attr = win_hl_attr(wp, HLF_AT); - - if (cul_attr) { - multi_attr = 0 != line_attr_lowprio - ? hl_combine_attr(cul_attr, multi_attr) - : hl_combine_attr(multi_attr, cul_attr); - } - - // put the pointer back to output the double-width - // character at the start of the next line. - n_extra++; - p_extra--; - } else { - n_extra -= mb_l - 1; - p_extra += mb_l - 1; - } - p_extra++; - } - n_extra--; - } else if (foldinfo.fi_lines > 0) { - // skip writing the buffer line itself - c = NUL; - XFREE_CLEAR(p_extra_free); - } else { - int c0; - - XFREE_CLEAR(p_extra_free); - - // Get a character from the line itself. - c0 = c = *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_utf8 = false; - if (mb_l > 1) { - mb_c = utfc_ptr2char(ptr, u8cc); - // Overlong encoded ASCII or ASCII with composing char - // is displayed normally, except a NUL. - if (mb_c < 0x80) { - c0 = c = mb_c; - } - mb_utf8 = true; - - // At start of the line we can have a composing char. - // Draw it as a space with a composing char. - if (utf_iscomposing(mb_c)) { - int i; - - for (i = MAX_MCO - 1; i > 0; i--) { - u8cc[i] = u8cc[i - 1]; - } - u8cc[0] = mb_c; - mb_c = ' '; - } - } - - if ((mb_l == 1 && c >= 0x80) - || (mb_l >= 1 && mb_c == 0) - || (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); - 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); - mb_utf8 = (c >= 0x80); - n_extra = (int)STRLEN(p_extra); - c_extra = NUL; - c_final = NUL; - if (area_attr == 0 && search_attr == 0) { - n_attr = n_extra + 1; - extra_attr = win_hl_attr(wp, HLF_8); - saved_attr2 = char_attr; // save current attr - } - } else if (mb_l == 0) { // at the NUL at end-of-line - mb_l = 1; - } else if (p_arshape && !p_tbidi && ARABIC_CHAR(mb_c)) { - // Do Arabic shaping. - int pc, pc1, nc; - int pcc[MAX_MCO]; - - // The idea of what is the previous and next - // character depends on 'rightleft'. - if (wp->w_p_rl) { - pc = prev_c; - pc1 = prev_c1; - nc = utf_ptr2char((char *)ptr + mb_l); - prev_c1 = u8cc[0]; - } else { - pc = utfc_ptr2char(ptr + mb_l, pcc); - nc = prev_c; - pc1 = pcc[0]; - } - prev_c = mb_c; - - mb_c = arabic_shape(mb_c, &c, &u8cc[0], pc, pc1, nc); - } else { - prev_c = mb_c; - } - // If a double-width char doesn't fit display a '>' in the - // last column; the character is displayed at the start of the - // next line. - if ((wp->w_p_rl ? (col <= 0) : - (col >= grid->cols - 1)) - && utf_char2cells(mb_c) == 2) { - c = '>'; - mb_c = c; - mb_utf8 = false; - mb_l = 1; - multi_attr = win_hl_attr(wp, HLF_AT); - // Put pointer back so that the character will be - // displayed at the start of the next line. - ptr--; - did_decrement_ptr = true; - } else if (*ptr != NUL) { - ptr += mb_l - 1; - } - - // If a double-width char doesn't fit at the left side display a '<' in - // the first column. Don't do this for unprintable characters. - if (n_skip > 0 && mb_l > 1 && n_extra == 0) { - n_extra = 1; - c_extra = MB_FILLER_CHAR; - c_final = NUL; - c = ' '; - if (area_attr == 0 && search_attr == 0) { - n_attr = n_extra + 1; - extra_attr = win_hl_attr(wp, HLF_AT); - saved_attr2 = char_attr; // save current attr - } - mb_c = c; - mb_utf8 = false; - mb_l = 1; - } - ptr++; - - if (extra_check) { - bool can_spell = true; - - /* Get syntax attribute, unless still at the start of the line - * (double-wide char that doesn't fit). */ - v = (ptr - line); - if (has_syntax && v > 0) { - /* Get the syntax attribute for the character. If there - * is an error, disable syntax highlighting. */ - save_did_emsg = did_emsg; - did_emsg = FALSE; - - syntax_attr = get_syntax_attr((colnr_T)v - 1, - has_spell ? &can_spell : NULL, false); - - if (did_emsg) { - wp->w_s->b_syn_error = TRUE; - has_syntax = FALSE; - } else { - did_emsg = save_did_emsg; - } - - if (wp->w_s->b_syn_slow) { - has_syntax = false; - } - - // Need to get the line again, a multi-line regexp may - // have made it invalid. - line = ml_get_buf(wp->w_buffer, lnum, false); - ptr = line + v; - - if (!attr_pri) { - if (cul_attr) { - char_attr = 0 != line_attr_lowprio - ? hl_combine_attr(cul_attr, syntax_attr) - : hl_combine_attr(syntax_attr, cul_attr); - } else { - char_attr = syntax_attr; - } - } else { - char_attr = hl_combine_attr(syntax_attr, char_attr); - } - // no concealing past the end of the line, it interferes - // with line highlighting. - if (c == NUL) { - syntax_flags = 0; - } else { - syntax_flags = get_syntax_info(&syntax_seqnr); - } - } else if (!attr_pri) { - char_attr = 0; - } - - /* Check spelling (unless at the end of the line). - * Only do this when there is no syntax highlighting, the - * @Spell cluster is not used or the current syntax item - * contains the @Spell cluster. */ - v = (ptr - line); - if (has_spell && v >= word_end && v > cur_checked_col) { - spell_attr = 0; - if (!attr_pri) { - char_attr = syntax_attr; - } - if (c != 0 && (!has_syntax || can_spell)) { - char_u *prev_ptr; - char_u *p; - int len; - hlf_T spell_hlf = HLF_COUNT; - prev_ptr = ptr - mb_l; - v -= mb_l - 1; - - /* Use nextline[] if possible, it has the start of the - * next line concatenated. */ - if ((prev_ptr - line) - nextlinecol >= 0) { - p = nextline + ((prev_ptr - line) - nextlinecol); - } else { - p = prev_ptr; - } - cap_col -= (int)(prev_ptr - line); - size_t tmplen = spell_check(wp, p, &spell_hlf, &cap_col, nochange); - assert(tmplen <= INT_MAX); - len = (int)tmplen; - word_end = (int)v + len; - - /* In Insert mode only highlight a word that - * doesn't touch the cursor. */ - if (spell_hlf != HLF_COUNT - && (State & MODE_INSERT) - && wp->w_cursor.lnum == lnum - && wp->w_cursor.col >= - (colnr_T)(prev_ptr - line) - && wp->w_cursor.col < (colnr_T)word_end) { - spell_hlf = HLF_COUNT; - spell_redraw_lnum = lnum; - } - - if (spell_hlf == HLF_COUNT && p != prev_ptr - && (p - nextline) + len > nextline_idx) { - /* Remember that the good word continues at the - * start of the next line. */ - checked_lnum = lnum + 1; - checked_col = (int)((p - nextline) + len - nextline_idx); - } - - // Turn index into actual attributes. - if (spell_hlf != HLF_COUNT) { - spell_attr = highlight_attr[spell_hlf]; - } - - if (cap_col > 0) { - if (p != prev_ptr - && (p - nextline) + cap_col >= nextline_idx) { - /* Remember that the word in the next line - * must start with a capital. */ - capcol_lnum = lnum + 1; - cap_col = (int)((p - nextline) + cap_col - - nextline_idx); - } else { - // Compute the actual column. - cap_col += (int)(prev_ptr - line); - } - } - } - } - if (spell_attr != 0) { - if (!attr_pri) { - char_attr = hl_combine_attr(char_attr, spell_attr); - } else { - char_attr = hl_combine_attr(spell_attr, char_attr); - } - } - - if (wp->w_buffer->terminal) { - char_attr = hl_combine_attr(term_attrs[vcol], char_attr); - } - - if (has_decor && v > 0) { - bool selected = (area_active || (area_highlighting && noinvcur - && (colnr_T)vcol == wp->w_virtcol)); - int extmark_attr = decor_redraw_col(wp->w_buffer, (colnr_T)v - 1, off, - selected, &decor_state); - if (extmark_attr != 0) { - if (!attr_pri) { - char_attr = hl_combine_attr(char_attr, extmark_attr); - } else { - char_attr = hl_combine_attr(extmark_attr, char_attr); - } - } - - decor_conceal = decor_state.conceal; - if (decor_conceal && decor_state.conceal_char) { - decor_conceal = 2; // really?? - } - } - - // Found last space before word: check for line break. - if (wp->w_p_lbr && c0 == c && vim_isbreak(c) - && !vim_isbreak((int)(*ptr))) { - int mb_off = utf_head_off(line, ptr - 1); - char_u *p = ptr - (mb_off + 1); - // TODO: is passing p for start of the line OK? - n_extra = win_lbr_chartabsize(wp, line, p, (colnr_T)vcol, NULL) - 1; - - // We have just drawn the showbreak value, no need to add - // space for it again. - if (vcol == vcol_sbr) { - n_extra -= mb_charlen(get_showbreak_value(wp)); - if (n_extra < 0) { - n_extra = 0; - } - } - - if (c == TAB && n_extra + col > grid->cols) { - n_extra = tabstop_padding((colnr_T)vcol, wp->w_buffer->b_p_ts, - wp->w_buffer->b_p_vts_array) - 1; - } - c_extra = mb_off > 0 ? MB_FILLER_CHAR : ' '; - c_final = NUL; - if (ascii_iswhite(c)) { - if (c == TAB) { - // See "Tab alignment" below. - FIX_FOR_BOGUSCOLS; - } - if (!wp->w_p_list) { - c = ' '; - } - } - } - - in_multispace = c == ' ' && ((ptr > line + 1 && ptr[-2] == ' ') || *ptr == ' '); - if (!in_multispace) { - multispace_pos = 0; - } - - // 'list': Change char 160 to 'nbsp' and space to 'space'. - // But not when the character is followed by a composing - // character (use mb_l to check that). - if (wp->w_p_list - && ((((c == 160 && mb_l == 1) - || (mb_utf8 - && ((mb_c == 160 && mb_l == 2) - || (mb_c == 0x202f && mb_l == 3)))) - && wp->w_p_lcs_chars.nbsp) - || (c == ' ' - && mb_l == 1 - && (wp->w_p_lcs_chars.space - || (in_multispace && wp->w_p_lcs_chars.multispace != NULL)) - && ptr - line >= leadcol - && ptr - line <= trailcol))) { - if (in_multispace && wp->w_p_lcs_chars.multispace != NULL) { - c = wp->w_p_lcs_chars.multispace[multispace_pos++]; - if (wp->w_p_lcs_chars.multispace[multispace_pos] == NUL) { - multispace_pos = 0; - } - } else { - c = (c == ' ') ? wp->w_p_lcs_chars.space : wp->w_p_lcs_chars.nbsp; - } - n_attr = 1; - extra_attr = win_hl_attr(wp, HLF_0); - saved_attr2 = char_attr; // save current attr - mb_c = c; - if (utf_char2len(c) > 1) { - mb_utf8 = true; - u8cc[0] = 0; - c = 0xc0; - } else { - mb_utf8 = false; - } - } - - if (c == ' ' && ((trailcol != MAXCOL && ptr > line + trailcol) - || (leadcol != 0 && ptr < line + leadcol))) { - if (leadcol != 0 && in_multispace && ptr < line + leadcol - && wp->w_p_lcs_chars.leadmultispace != NULL) { - c = wp->w_p_lcs_chars.leadmultispace[multispace_pos++]; - if (wp->w_p_lcs_chars.leadmultispace[multispace_pos] == NUL) { - multispace_pos = 0; - } - } else if (ptr > line + trailcol && wp->w_p_lcs_chars.trail) { - c = wp->w_p_lcs_chars.trail; - } else if (ptr < line + leadcol && wp->w_p_lcs_chars.lead) { - c = wp->w_p_lcs_chars.lead; - } else if (leadcol != 0 && wp->w_p_lcs_chars.space) { - c = wp->w_p_lcs_chars.space; - } - - n_attr = 1; - extra_attr = win_hl_attr(wp, HLF_0); - saved_attr2 = char_attr; // save current attr - mb_c = c; - if (utf_char2len(c) > 1) { - mb_utf8 = true; - u8cc[0] = 0; - c = 0xc0; - } else { - mb_utf8 = false; - } - } - } - - /* - * Handling of non-printable characters. - */ - if (!vim_isprintc(c)) { - // when getting a character from the file, we may have to - // turn it into something else on the way to putting it on the screen. - 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); - - // Only adjust the tab_len, when at the first column after the - // showbreak value was drawn. - if (*sbr != NUL && vcol == vcol_sbr && wp->w_p_wrap) { - vcol_adjusted = vcol - mb_charlen(sbr); - } - // tab amount depends on current column - tab_len = tabstop_padding((colnr_T)vcol_adjusted, - wp->w_buffer->b_p_ts, - wp->w_buffer->b_p_vts_array) - 1; - - if (!wp->w_p_lbr || !wp->w_p_list) { - n_extra = tab_len; - } else { - char_u *p; - int i; - int saved_nextra = n_extra; - - if (vcol_off > 0) { - // there are characters to conceal - tab_len += vcol_off; - } - // boguscols before FIX_FOR_BOGUSCOLS macro from above. - if (wp->w_p_lcs_chars.tab1 && old_boguscols > 0 - && n_extra > tab_len) { - tab_len += n_extra - tab_len; - } - - // If n_extra > 0, it gives the number of chars - // to use for a tab, else we need to calculate the width - // for a tab. - int len = (tab_len * utf_char2len(wp->w_p_lcs_chars.tab2)); - if (wp->w_p_lcs_chars.tab3) { - len += utf_char2len(wp->w_p_lcs_chars.tab3); - } - if (n_extra > 0) { - len += n_extra - tab_len; - } - c = wp->w_p_lcs_chars.tab1; - p = xmalloc((size_t)len + 1); - memset(p, ' ', (size_t)len); - p[len] = NUL; - xfree(p_extra_free); - p_extra_free = p; - for (i = 0; i < tab_len; i++) { - if (*p == NUL) { - tab_len = i; - break; - } - int lcs = wp->w_p_lcs_chars.tab2; - - // if tab3 is given, use it for the last char - if (wp->w_p_lcs_chars.tab3 && i == tab_len - 1) { - lcs = wp->w_p_lcs_chars.tab3; - } - p += utf_char2bytes(lcs, (char *)p); - n_extra += utf_char2len(lcs) - (saved_nextra > 0 ? 1 : 0); - } - p_extra = p_extra_free; - - // n_extra will be increased by FIX_FOX_BOGUSCOLS - // macro below, so need to adjust for that here - if (vcol_off > 0) { - n_extra -= vcol_off; - } - } - - { - int vc_saved = vcol_off; - - // Tab alignment should be identical regardless of - // 'conceallevel' value. So tab compensates of all - // previous concealed characters, and thus resets - // vcol_off and boguscols accumulated so far in the - // line. Note that the tab can be longer than - // 'tabstop' when there are concealed characters. - FIX_FOR_BOGUSCOLS; - - // Make sure, the highlighting for the tab char will be - // correctly set further below (effectively reverts the - // FIX_FOR_BOGSUCOLS macro). - if (n_extra == tab_len + vc_saved && wp->w_p_list - && wp->w_p_lcs_chars.tab1) { - tab_len += vc_saved; - } - } - - mb_utf8 = false; // don't draw as UTF-8 - if (wp->w_p_list) { - c = (n_extra == 0 && wp->w_p_lcs_chars.tab3) - ? wp->w_p_lcs_chars.tab3 - : wp->w_p_lcs_chars.tab1; - if (wp->w_p_lbr) { - c_extra = NUL; // using p_extra from above - } else { - c_extra = wp->w_p_lcs_chars.tab2; - } - c_final = wp->w_p_lcs_chars.tab3; - n_attr = tab_len + 1; - extra_attr = win_hl_attr(wp, HLF_0); - saved_attr2 = char_attr; // save current attr - mb_c = c; - if (utf_char2len(c) > 1) { - mb_utf8 = true; - u8cc[0] = 0; - c = 0xc0; - } - } else { - c_final = NUL; - c_extra = ' '; - c = ' '; - } - } else if (c == NUL - && (wp->w_p_list - || ((fromcol >= 0 || fromcol_prev >= 0) - && tocol > vcol - && VIsual_mode != Ctrl_V - && (wp->w_p_rl ? (col >= 0) : (col < grid->cols)) - && !(noinvcur - && lnum == wp->w_cursor.lnum - && (colnr_T)vcol == wp->w_virtcol))) - && lcs_eol_one > 0) { - // Display a '$' after the line or highlight an extra - // character if the line break is included. - // For a diff line the highlighting continues after the "$". - if (diff_hlf == (hlf_T)0 - && line_attr == 0 - && line_attr_lowprio == 0) { - // In virtualedit, visual selections may extend beyond end of line - if (area_highlighting && virtual_active() - && tocol != MAXCOL && vcol < tocol) { - n_extra = 0; - } else { - p_extra = at_end_str; - n_extra = 1; - c_extra = NUL; - c_final = NUL; - } - } - if (wp->w_p_list && wp->w_p_lcs_chars.eol > 0) { - c = wp->w_p_lcs_chars.eol; - } else { - c = ' '; - } - lcs_eol_one = -1; - ptr--; // put it back at the NUL - extra_attr = win_hl_attr(wp, HLF_AT); - n_attr = 1; - mb_c = c; - if (utf_char2len(c) > 1) { - mb_utf8 = true; - u8cc[0] = 0; - c = 0xc0; - } else { - mb_utf8 = false; // don't draw as UTF-8 - } - } else if (c != NUL) { - p_extra = transchar_buf(wp->w_buffer, c); - if (n_extra == 0) { - n_extra = byte2cells(c) - 1; - } - if ((dy_flags & DY_UHEX) && wp->w_p_rl) { - rl_mirror(p_extra); // reverse "<12>" - } - c_extra = NUL; - c_final = NUL; - if (wp->w_p_lbr) { - char_u *p; - - c = *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) - p[n_extra] = NUL; - xfree(p_extra_free); - p_extra_free = p_extra = p; - } else { - n_extra = byte2cells(c) - 1; - c = *p_extra++; - } - n_attr = n_extra + 1; - extra_attr = win_hl_attr(wp, HLF_8); - saved_attr2 = char_attr; // save current attr - mb_utf8 = false; // don't draw as UTF-8 - } else if (VIsual_active - && (VIsual_mode == Ctrl_V || VIsual_mode == 'v') - && virtual_active() - && tocol != MAXCOL - && vcol < tocol - && (wp->w_p_rl ? (col >= 0) : (col < grid->cols))) { - c = ' '; - ptr--; // put it back at the NUL - } - } - - if (wp->w_p_cole > 0 - && (wp != curwin || lnum != wp->w_cursor.lnum || conceal_cursor_line(wp)) - && ((syntax_flags & HL_CONCEAL) != 0 || has_match_conc > 0 || decor_conceal > 0) - && !(lnum_in_visual_area && vim_strchr((char *)wp->w_p_cocu, 'v') == NULL)) { - char_attr = conceal_attr; - if (((prev_syntax_id != syntax_seqnr && (syntax_flags & HL_CONCEAL) != 0) - || has_match_conc > 1 || decor_conceal > 1) - && (syn_get_sub_char() != NUL - || (has_match_conc && match_conc) - || (decor_conceal && decor_state.conceal_char) - || wp->w_p_cole == 1) - && wp->w_p_cole != 3) { - // First time at this concealed item: display one - // character. - if (has_match_conc && match_conc) { - c = match_conc; - } else if (decor_conceal && decor_state.conceal_char) { - c = decor_state.conceal_char; - if (decor_state.conceal_attr) { - char_attr = decor_state.conceal_attr; - } - } else if (syn_get_sub_char() != NUL) { - c = syn_get_sub_char(); - } else if (wp->w_p_lcs_chars.conceal != NUL) { - c = wp->w_p_lcs_chars.conceal; - } else { - c = ' '; - } - - prev_syntax_id = syntax_seqnr; - - if (n_extra > 0) { - vcol_off += n_extra; - } - vcol += n_extra; - if (wp->w_p_wrap && n_extra > 0) { - if (wp->w_p_rl) { - col -= n_extra; - boguscols -= n_extra; - } else { - boguscols += n_extra; - col += n_extra; - } - } - n_extra = 0; - n_attr = 0; - } else if (n_skip == 0) { - is_concealing = true; - n_skip = 1; - } - mb_c = c; - if (utf_char2len(c) > 1) { - mb_utf8 = true; - u8cc[0] = 0; - c = 0xc0; - } else { - mb_utf8 = false; // don't draw as UTF-8 - } - } else { - prev_syntax_id = 0; - is_concealing = false; - } - - if (n_skip > 0 && did_decrement_ptr) { - // not showing the '>', put pointer back to avoid getting stuck - ptr++; - } - } // end of printing from buffer content - - /* In the cursor line and we may be concealing characters: correct - * the cursor column when we reach its position. */ - if (!did_wcol && draw_state == WL_LINE - && wp == curwin && lnum == wp->w_cursor.lnum - && conceal_cursor_line(wp) - && (int)wp->w_virtcol <= vcol + n_skip) { - if (wp->w_p_rl) { - wp->w_wcol = grid->cols - col + boguscols - 1; - } else { - wp->w_wcol = col - boguscols; - } - wp->w_wrow = row; - did_wcol = true; - wp->w_valid |= VALID_WCOL|VALID_WROW|VALID_VIRTCOL; - } - - // Don't override visual selection highlighting. - if (n_attr > 0 && draw_state == WL_LINE && !search_attr_from_match) { - char_attr = hl_combine_attr(char_attr, extra_attr); - } - - // Handle the case where we are in column 0 but not on the first - // character of the line and the user wants us to show us a - // special character (via 'listchars' option "precedes:<char>". - if (lcs_prec_todo != NUL - && 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 - && c != NUL) { - c = wp->w_p_lcs_chars.prec; - lcs_prec_todo = NUL; - if (utf_char2cells(mb_c) > 1) { - // Double-width character being overwritten by the "precedes" - // character, need to fill up half the character. - c_extra = MB_FILLER_CHAR; - c_final = NUL; - n_extra = 1; - n_attr = 2; - extra_attr = win_hl_attr(wp, HLF_AT); - } - mb_c = c; - if (utf_char2len(c) > 1) { - mb_utf8 = true; - u8cc[0] = 0; - c = 0xc0; - } else { - mb_utf8 = false; // don't draw as UTF-8 - } - saved_attr3 = char_attr; // save current attr - char_attr = win_hl_attr(wp, HLF_AT); // overwriting char_attr - n_attr3 = 1; - } - - // At end of the text line or just after the last character. - if (c == NUL && eol_hl_off == 0) { - // flag to indicate whether prevcol equals startcol of search_hl or - // one of the matches - bool prevcol_hl_flag = get_prevcol_hl_flag(wp, &search_hl, - (long)(ptr - line) - 1); - - // Invert at least one char, used for Visual and empty line or - // highlight match at end of line. If it's beyond the last - // char on the screen, just overwrite that one (tricky!) Not - // needed when a '$' was displayed for 'list'. - if (wp->w_p_lcs_chars.eol == lcs_eol_one - && ((area_attr != 0 && vcol == fromcol - && (VIsual_mode != Ctrl_V - || lnum == VIsual.lnum - || lnum == curwin->w_cursor.lnum)) - // highlight 'hlsearch' match at end of line - || prevcol_hl_flag)) { - int n = 0; - - if (wp->w_p_rl) { - if (col < 0) { - n = 1; - } - } else { - if (col >= grid->cols) { - n = -1; - } - } - if (n != 0) { - // At the window boundary, highlight the last character - // instead (better than nothing). - off += n; - col += n; - } else { - // Add a blank character to highlight. - schar_from_ascii(linebuf_char[off], ' '); - } - 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, &search_hl, (long)(ptr - line), &char_attr); - } - - int eol_attr = char_attr; - if (cul_attr) { - eol_attr = hl_combine_attr(cul_attr, eol_attr); - } - linebuf_attr[off] = eol_attr; - if (wp->w_p_rl) { - --col; - --off; - } else { - ++col; - ++off; - } - ++vcol; - eol_hl_off = 1; - } - // Highlight 'cursorcolumn' & 'colorcolumn' past end of the line. - if (wp->w_p_wrap) { - v = wp->w_skipcol; - } else { - v = wp->w_leftcol; - } - - // check if line ends before left margin - if (vcol < v + col - win_col_off(wp)) { - vcol = v + col - win_col_off(wp); - } - // Get rid of the boguscols now, we want to draw until the right - // edge for 'cursorcolumn'. - col -= boguscols; - // boguscols = 0; // Disabled because value never read after this - - if (draw_color_col) { - draw_color_col = advance_color_col((int)VCOL_HLC, &color_cols); - } - - bool has_virttext = false; - // Make sure alignment is the same regardless - // if listchars=eol:X is used or not. - int eol_skip = (wp->w_p_lcs_chars.eol == lcs_eol_one && eol_hl_off == 0 - ? 1 : 0); - - if (has_decor) { - has_virttext = decor_redraw_eol(wp->w_buffer, &decor_state, &line_attr, - col + eol_skip); - } - - if (((wp->w_p_cuc - && (int)wp->w_virtcol >= VCOL_HLC - eol_hl_off - && (int)wp->w_virtcol < - (long)grid->cols * (row - startrow + 1) + v - && lnum != wp->w_cursor.lnum) - || draw_color_col || line_attr_lowprio || line_attr - || diff_hlf != (hlf_T)0 || has_virttext)) { - int rightmost_vcol = 0; - int i; - - if (wp->w_p_cuc) { - rightmost_vcol = wp->w_virtcol; - } - - if (draw_color_col) { - // determine rightmost colorcolumn to possibly draw - for (i = 0; color_cols[i] >= 0; i++) { - if (rightmost_vcol < color_cols[i]) { - rightmost_vcol = color_cols[i]; - } - } - } - - int cuc_attr = win_hl_attr(wp, HLF_CUC); - int mc_attr = win_hl_attr(wp, HLF_MC); - - int diff_attr = 0; - if (diff_hlf == HLF_TXD) { - diff_hlf = HLF_CHD; - } - if (diff_hlf != 0) { - diff_attr = win_hl_attr(wp, (int)diff_hlf); - } - - int base_attr = hl_combine_attr(line_attr_lowprio, diff_attr); - if (base_attr || line_attr || has_virttext) { - rightmost_vcol = INT_MAX; - } - - int col_stride = wp->w_p_rl ? -1 : 1; - - while (wp->w_p_rl ? col >= 0 : col < grid->cols) { - schar_from_ascii(linebuf_char[off], ' '); - col += col_stride; - if (draw_color_col) { - draw_color_col = advance_color_col((int)VCOL_HLC, &color_cols); - } - - int col_attr = base_attr; - - if (wp->w_p_cuc && VCOL_HLC == (long)wp->w_virtcol) { - col_attr = cuc_attr; - } else if (draw_color_col && VCOL_HLC == *color_cols) { - col_attr = mc_attr; - } - - col_attr = hl_combine_attr(col_attr, line_attr); - - linebuf_attr[off] = col_attr; - off += col_stride; - - if (VCOL_HLC >= rightmost_vcol) { - break; - } - - vcol += 1; - } - } - - // TODO(bfredl): integrate with the common beyond-the-end-loop - if (wp->w_buffer->terminal) { - // terminal buffers may need to highlight beyond the end of the - // logical line - int n = wp->w_p_rl ? -1 : 1; - while (col >= 0 && col < grid->cols) { - schar_from_ascii(linebuf_char[off], ' '); - linebuf_attr[off] = vcol >= TERM_ATTRS_MAX ? 0 : term_attrs[vcol]; - off += n; - vcol += n; - col += n; - } - } - - 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, - wp->w_hl_attr_normal, false); - row++; - - /* - * Update w_cline_height and w_cline_folded if the cursor line was - * updated (saves a call to plines_win() later). - */ - if (wp == curwin && lnum == curwin->w_cursor.lnum) { - curwin->w_cline_row = startrow; - curwin->w_cline_height = row - startrow; - curwin->w_cline_folded = foldinfo.fi_lines > 0; - curwin->w_valid |= (VALID_CHEIGHT|VALID_CROW); - conceal_cursor_used = conceal_cursor_line(curwin); - } - break; - } - - // Show "extends" character from 'listchars' if beyond the line end and - // 'list' is set. - if (wp->w_p_lcs_chars.ext != NUL - && draw_state == WL_LINE - && wp->w_p_list - && !wp->w_p_wrap - && filler_todo <= 0 - && (wp->w_p_rl ? col == 0 : col == grid->cols - 1) - && !has_fold - && (*ptr != NUL - || lcs_eol_one > 0 - || (n_extra && (c_extra != NUL || *p_extra != NUL)))) { - c = wp->w_p_lcs_chars.ext; - char_attr = win_hl_attr(wp, HLF_AT); - mb_c = c; - if (utf_char2len(c) > 1) { - mb_utf8 = true; - u8cc[0] = 0; - c = 0xc0; - } else { - mb_utf8 = false; - } - } - - // advance to the next 'colorcolumn' - if (draw_color_col) { - draw_color_col = advance_color_col((int)VCOL_HLC, &color_cols); - } - - // Highlight the cursor column if 'cursorcolumn' is set. But don't - // highlight the cursor position itself. - // Also highlight the 'colorcolumn' if it is different than - // 'cursorcolumn' - // Also highlight the 'colorcolumn' if 'breakindent' and/or 'showbreak' - // options are set - vcol_save_attr = -1; - if ((draw_state == WL_LINE - || draw_state == WL_BRI - || draw_state == WL_SBR) - && !lnum_in_visual_area - && search_attr == 0 - && area_attr == 0 - && filler_todo <= 0) { - if (wp->w_p_cuc && VCOL_HLC == (long)wp->w_virtcol - && lnum != wp->w_cursor.lnum) { - vcol_save_attr = char_attr; - char_attr = hl_combine_attr(win_hl_attr(wp, HLF_CUC), char_attr); - } else if (draw_color_col && VCOL_HLC == *color_cols) { - vcol_save_attr = char_attr; - char_attr = hl_combine_attr(win_hl_attr(wp, HLF_MC), char_attr); - } - } - - // Apply lowest-priority line attr now, so everything can override it. - if (draw_state == WL_LINE) { - char_attr = hl_combine_attr(line_attr_lowprio, char_attr); - } - - // Store character to be displayed. - // Skip characters that are left of the screen for 'nowrap'. - vcol_prev = vcol; - if (draw_state < WL_LINE || n_skip <= 0) { - // - // Store the character. - // - if (wp->w_p_rl && utf_char2cells(mb_c) > 1) { - // A double-wide character is: put first half in left cell. - off--; - col--; - } - if (mb_utf8) { - schar_from_cc(linebuf_char[off], mb_c, u8cc); - } else { - schar_from_ascii(linebuf_char[off], (char)c); - } - if (multi_attr) { - linebuf_attr[off] = multi_attr; - multi_attr = 0; - } else { - linebuf_attr[off] = char_attr; - } - - if (utf_char2cells(mb_c) > 1) { - // Need to fill two screen columns. - off++; - col++; - // UTF-8: Put a 0 in the second screen char. - linebuf_char[off][0] = 0; - if (draw_state > WL_NR && filler_todo <= 0) { - vcol++; - } - // When "tocol" is halfway through a character, set it to the end of - // the character, otherwise highlighting won't stop. - if (tocol == vcol) { - tocol++; - } - if (wp->w_p_rl) { - // now it's time to backup one cell - --off; - --col; - } - } - if (wp->w_p_rl) { - --off; - --col; - } else { - ++off; - ++col; - } - } else if (wp->w_p_cole > 0 && is_concealing) { - --n_skip; - ++vcol_off; - if (n_extra > 0) { - vcol_off += n_extra; - } - if (wp->w_p_wrap) { - /* - * Special voodoo required if 'wrap' is on. - * - * Advance the column indicator to force the line - * drawing to wrap early. This will make the line - * take up the same screen space when parts are concealed, - * so that cursor line computations aren't messed up. - * - * To avoid the fictitious advance of 'col' causing - * trailing junk to be written out of the screen line - * we are building, 'boguscols' keeps track of the number - * of bad columns we have advanced. - */ - if (n_extra > 0) { - vcol += n_extra; - if (wp->w_p_rl) { - col -= n_extra; - boguscols -= n_extra; - } else { - col += n_extra; - boguscols += n_extra; - } - n_extra = 0; - n_attr = 0; - } - - if (utf_char2cells(mb_c) > 1) { - // Need to fill two screen columns. - if (wp->w_p_rl) { - --boguscols; - --col; - } else { - ++boguscols; - ++col; - } - } - - if (wp->w_p_rl) { - --boguscols; - --col; - } else { - ++boguscols; - ++col; - } - } else { - if (n_extra > 0) { - vcol += n_extra; - n_extra = 0; - n_attr = 0; - } - } - } else { - --n_skip; - } - - /* Only advance the "vcol" when after the 'number' or 'relativenumber' - * column. */ - if (draw_state > WL_NR - && filler_todo <= 0) { - ++vcol; - } - - if (vcol_save_attr >= 0) { - char_attr = vcol_save_attr; - } - - // restore attributes after "predeces" in 'listchars' - if (draw_state > WL_NR && n_attr3 > 0 && --n_attr3 == 0) { - char_attr = saved_attr3; - } - - // restore attributes after last 'listchars' or 'number' char - if (n_attr > 0 && draw_state == WL_LINE && --n_attr == 0) { - char_attr = saved_attr2; - } - - /* - * 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 - && (draw_state != WL_LINE - || *ptr != NUL - || filler_todo > 0 - || (wp->w_p_list && wp->w_p_lcs_chars.eol != NUL - && p_extra != at_end_str) - || (n_extra != 0 - && (c_extra != NUL || *p_extra != NUL)))) { - bool wrap = wp->w_p_wrap // Wrapping enabled. - && filler_todo <= 0 // Not drawing diff filler lines. - && lcs_eol_one != -1 // Haven't printed the lcs_eol character. - && row != endrow - 1 // Not the last line being displayed. - && (grid->cols == Columns // Window spans the width of the screen, - || ui_has(kUIMultigrid)) // or has dedicated grid. - && !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, offset); - } - } else { - draw_virt_text(wp, buf, win_col_offset, &draw_col, grid->cols, row); - } - - grid_put_linebuf(grid, row, 0, draw_col, grid->cols, wp->w_p_rl, - wp, wp->w_hl_attr_normal, wrap); - if (wrap) { - ScreenGrid *current_grid = grid; - int current_row = row, dummy_col = 0; // dummy_col unused - grid_adjust(¤t_grid, ¤t_row, &dummy_col); - - // Force a redraw of the first column of the next line. - current_grid->attrs[current_grid->line_offset[current_row + 1]] = -1; - - // Remember that the line wraps, used for modeless copy. - current_grid->line_wraps[current_row] = true; - } - - boguscols = 0; - row++; - - /* When not wrapping and finished diff lines, or when displayed - * '$' and highlighting until last column, break here. */ - if ((!wp->w_p_wrap - && filler_todo <= 0 - ) || lcs_eol_one == -1) { - break; - } - - // When the window is too narrow draw all "@" lines. - if (draw_state != WL_LINE && filler_todo <= 0) { - win_draw_end(wp, '@', ' ', true, row, wp->w_grid.rows, HLF_AT); - row = endrow; - } - - // When line got too long for screen break here. - if (row == endrow) { - ++row; - break; - } - - col = 0; - off = 0; - if (wp->w_p_rl) { - col = grid->cols - 1; // col is not used if breaking! - off += col; - } - - // reset the drawing state for the start of a wrapped line - draw_state = WL_START; - saved_n_extra = n_extra; - saved_p_extra = p_extra; - saved_c_extra = c_extra; - saved_c_final = c_final; - saved_char_attr = char_attr; - n_extra = 0; - lcs_prec_todo = wp->w_p_lcs_chars.prec; - if (filler_todo <= 0) { - need_showbreak = true; - } - filler_todo--; - // 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)) { - break; - } - } - } // for every character in the line - - // After an empty line check first word for capital. - if (*skipwhite((char *)line) == NUL) { - capcol_lnum = lnum + 1; - cap_col = 0; - } - - kv_destroy(virt_lines); - xfree(p_extra_free); - return row; -} - -void draw_virt_text(win_T *wp, buf_T *buf, int col_off, int *end_col, int max_col, int win_row) -{ - DecorState *state = &decor_state; - int right_pos = max_col; - bool do_eol = state->eol_col > -1; - for (size_t i = 0; i < kv_size(state->active); i++) { - DecorRange *item = &kv_A(state->active, i); - if (!(item->start_row == state->row - && (kv_size(item->decor.virt_text) || item->decor.ui_watched))) { - continue; - } - if (item->win_col == -1) { - if (item->decor.virt_text_pos == kVTRightAlign) { - right_pos -= item->decor.virt_text_width; - item->win_col = right_pos; - } else if (item->decor.virt_text_pos == kVTEndOfLine && do_eol) { - item->win_col = state->eol_col; - } else if (item->decor.virt_text_pos == kVTWinCol) { - item->win_col = MAX(item->decor.col + col_off, 0); - } - } - if (item->win_col < 0) { - continue; - } - int col; - if (item->decor.ui_watched) { - // send mark position to UI - col = item->win_col; - WinExtmark m = { (NS)item->ns_id, item->mark_id, win_row, col }; - kv_push(win_extmark_arr, m); - } - if (kv_size(item->decor.virt_text)) { - col = draw_virt_text_item(buf, item->win_col, item->decor.virt_text, - item->decor.hl_mode, max_col, item->win_col - col_off); - } - item->win_col = -2; // deactivate - if (item->decor.virt_text_pos == kVTEndOfLine && do_eol) { - state->eol_col = col + 1; - } - - *end_col = MAX(*end_col, col); - } -} - -static int draw_virt_text_item(buf_T *buf, int col, VirtText vt, HlMode hl_mode, int max_col, - int vcol) -{ - LineState s = LINE_STATE(""); - int virt_attr = 0; - size_t virt_pos = 0; - - while (col < max_col) { - if (!*s.p) { - if (virt_pos >= kv_size(vt)) { - break; - } - virt_attr = 0; - do { - s.p = kv_A(vt, virt_pos).text; - int hl_id = kv_A(vt, virt_pos).hl_id; - virt_attr = hl_combine_attr(virt_attr, - hl_id > 0 ? syn_id2attr(hl_id) : 0); - virt_pos++; - } while (!s.p && virt_pos < kv_size(vt)); - if (!s.p) { - break; - } - } - if (!*s.p) { - continue; - } - int attr; - bool through = false; - if (hl_mode == kHlModeCombine) { - attr = hl_combine_attr(linebuf_attr[col], virt_attr); - } else if (hl_mode == kHlModeBlend) { - through = (*s.p == ' '); - attr = hl_blend_attrs(linebuf_attr[col], virt_attr, &through); - } else { - attr = virt_attr; - } - schar_T dummy[2]; - int cells = line_putchar(buf, &s, through ? dummy : &linebuf_char[col], - max_col - col, false, vcol); - // if we failed to emit a char, we still need to advance - cells = MAX(cells, 1); - - for (int c = 0; c < cells; c++) { - linebuf_attr[col++] = attr; - } - vcol += cells; - } - return col; -} - -// Return true if CursorLineSign highlight is to be used. -static bool use_cursor_line_sign(win_T *wp, linenr_T lnum) -{ - return wp->w_p_cul - && lnum == wp->w_cursor.lnum - && (wp->w_p_culopt_flags & CULOPT_NBR); -} - -/// Return true if CursorLineNr highlight is to be used for the number column. -/// -/// - 'cursorline' must be set -/// - lnum must be the cursor line -/// - 'cursorlineopt' has "number" -/// - don't highlight filler lines (when in diff mode) -/// - When line is wrapped and 'cursorlineopt' does not have "line", only highlight the line number -/// itself on the first screenline of the wrapped line, otherwise highlight the number column of -/// all screenlines of the wrapped line. -static bool use_cursor_line_nr(win_T *wp, linenr_T lnum, int row, int startrow, int filler_lines) -{ - return wp->w_p_cul - && lnum == wp->w_cursor.lnum - && (wp->w_p_culopt_flags & CULOPT_NBR) - && (row == startrow + filler_lines - || (row > startrow + filler_lines - && (wp->w_p_culopt_flags & CULOPT_LINE))); -} - -static int get_line_number_attr(win_T *wp, linenr_T lnum, int row, int startrow, int filler_lines, - sign_attrs_T *sattrs) -{ - sign_attrs_T *num_sattr = sign_get_attr(SIGN_NUMHL, sattrs, 0, 1); - if (num_sattr != NULL) { - // :sign defined with "numhl" highlight. - return num_sattr->sat_numhl; - } - - if (wp->w_p_rnu) { - if (lnum < wp->w_cursor.lnum) { - // Use LineNrAbove - return win_hl_attr(wp, HLF_LNA); - } - if (lnum > wp->w_cursor.lnum) { - // Use LineNrBelow - return win_hl_attr(wp, HLF_LNB); - } - } - - if (use_cursor_line_nr(wp, lnum, row, startrow, filler_lines)) { - // TODO(vim): Can we use CursorLine instead of CursorLineNr - // when CursorLineNr isn't set? - return win_hl_attr(wp, HLF_CLN); - } - - return win_hl_attr(wp, HLF_N); -} - -// Get information needed to display the sign in line 'lnum' in window 'wp'. -// If 'nrcol' is TRUE, the sign is going to be displayed in the number column. -// Otherwise the sign is going to be displayed in the sign column. -// -// @param count max number of signs -// @param[out] n_extrap number of characters from pp_extra to display -// @param sign_idxp Index of the displayed sign -static void get_sign_display_info(bool nrcol, win_T *wp, linenr_T lnum, sign_attrs_T sattrs[], - int row, int startrow, int filler_lines, int filler_todo, - int *c_extrap, int *c_finalp, char_u *extra, size_t extra_size, - char_u **pp_extra, int *n_extrap, int *char_attrp, int sign_idx) -{ - // Draw cells with the sign value or blank. - *c_extrap = ' '; - *c_finalp = NUL; - if (nrcol) { - *n_extrap = number_width(wp) + 1; - } else { - if (use_cursor_line_sign(wp, lnum)) { - *char_attrp = win_hl_attr(wp, HLF_CLS); - } else { - *char_attrp = win_hl_attr(wp, HLF_SC); - } - *n_extrap = win_signcol_width(wp); - } - - if (row == startrow + filler_lines && filler_todo <= 0) { - sign_attrs_T *sattr = sign_get_attr(SIGN_TEXT, sattrs, sign_idx, wp->w_scwidth); - if (sattr != NULL) { - *pp_extra = sattr->sat_text; - if (*pp_extra != NULL) { - *c_extrap = NUL; - *c_finalp = NUL; - - if (nrcol) { - int n, width = number_width(wp) - 2; - for (n = 0; n < width; n++) { - extra[n] = ' '; - } - extra[n] = NUL; - STRCAT(extra, *pp_extra); - STRCAT(extra, " "); - *pp_extra = extra; - *n_extrap = (int)STRLEN(*pp_extra); - } else { - int symbol_blen = (int)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 = symbol_blen + win_signcol_width(wp) - - (int)mb_string2cells((char *)(*pp_extra)); - - assert(extra_size > (size_t)symbol_blen); - memset(extra, ' ', extra_size); - memcpy(extra, *pp_extra, (size_t)symbol_blen); - - *pp_extra = extra; - (*pp_extra)[*n_extrap] = NUL; - } - } - - if (use_cursor_line_sign(wp, lnum) && sattr->sat_culhl > 0) { - *char_attrp = sattr->sat_culhl; - } else { - *char_attrp = sattr->sat_texthl; - } - } - } -} - -/* - * Mirror text "str" for right-left displaying. - * Only works for single-byte characters (e.g., numbers). - */ +/// Mirror text "str" for right-left displaying. +/// Only works for single-byte characters (e.g., numbers). void rl_mirror(char_u *str) { char_u *p1, *p2; char_u 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; } } -/// Mark all status lines and window bars for redraw; used after first :cd -void status_redraw_all(void) -{ - bool is_stl_global = global_stl_height() != 0; - - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if ((!is_stl_global && wp->w_status_height) || (is_stl_global && wp == curwin) - || wp->w_winbar_height) { - wp->w_redr_status = true; - redraw_later(wp, VALID); - } - } -} - -/// Marks all status lines and window bars of the current buffer for redraw. -void status_redraw_curbuf(void) -{ - status_redraw_buf(curbuf); -} - -/// Marks all status lines and window bars of the given buffer for redraw. -void status_redraw_buf(buf_T *buf) -{ - bool is_stl_global = global_stl_height() != 0; - - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->w_buffer == buf && ((!is_stl_global && wp->w_status_height) - || (is_stl_global && wp == curwin) || wp->w_winbar_height)) { - wp->w_redr_status = true; - redraw_later(wp, VALID); - } - } -} - -/* - * Redraw all status lines that need to be redrawn. - */ -void redraw_statuslines(void) -{ - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->w_redr_status) { - win_redr_winbar(wp); - win_redr_status(wp); - } - } - if (redraw_tabline) { - draw_tabline(); - } -} - -/* - * Redraw all status lines at the bottom of frame "frp". - */ -void win_redraw_last_status(const frame_T *frp) - FUNC_ATTR_NONNULL_ARG(1) -{ - if (frp->fr_layout == FR_LEAF) { - frp->fr_win->w_redr_status = true; - } else if (frp->fr_layout == FR_ROW) { - FOR_ALL_FRAMES(frp, frp->fr_child) { - win_redraw_last_status(frp); - } - } else { - assert(frp->fr_layout == FR_COL); - frp = frp->fr_child; - while (frp->fr_next != NULL) { - frp = frp->fr_next; - } - win_redraw_last_status(frp); - } -} - -/// 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); - } -} - -/// 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); - } -} - -/// Get the separator connector for specified window corner of window "wp" -static int get_corner_sep_connector(win_T *wp, WindowCorner corner) -{ - // It's impossible for windows to be connected neither vertically nor horizontally - // So if they're not vertically connected, assume they're horizontally connected - if (vsep_connected(wp, corner)) { - if (hsep_connected(wp, corner)) { - return wp->w_p_fcs_chars.verthoriz; - } else if (corner == WC_TOP_LEFT || corner == WC_BOTTOM_LEFT) { - return wp->w_p_fcs_chars.vertright; - } else { - return wp->w_p_fcs_chars.vertleft; - } - } else if (corner == WC_TOP_LEFT || corner == WC_TOP_RIGHT) { - return wp->w_p_fcs_chars.horizdown; - } else { - return wp->w_p_fcs_chars.horizup; - } -} - -/// Draw separator connecting characters on the corners of window "wp" -static void draw_sep_connectors_win(win_T *wp) -{ - // Don't draw separator connectors unless global statusline is enabled and the window has - // either a horizontal or vertical separator - if (global_stl_height() == 0 || !(wp->w_hsep_height == 1 || wp->w_vsep_width == 1)) { - return; - } - - int hl = win_hl_attr(wp, HLF_C); - - // Determine which edges of the screen the window is located on so we can avoid drawing separators - // on corners contained in those edges - bool win_at_top; - bool win_at_bottom = wp->w_hsep_height == 0; - bool win_at_left; - bool win_at_right = wp->w_vsep_width == 0; - frame_T *frp; - - for (frp = wp->w_frame; frp->fr_parent != NULL; frp = frp->fr_parent) { - if (frp->fr_parent->fr_layout == FR_COL && frp->fr_prev != NULL) { - break; - } - } - win_at_top = frp->fr_parent == NULL; - for (frp = wp->w_frame; frp->fr_parent != NULL; frp = frp->fr_parent) { - if (frp->fr_parent->fr_layout == FR_ROW && frp->fr_prev != NULL) { - break; - } - } - win_at_left = frp->fr_parent == NULL; - - // Draw the appropriate separator connector in every corner where drawing them is necessary - if (!(win_at_top || win_at_left)) { - grid_putchar(&default_grid, get_corner_sep_connector(wp, WC_TOP_LEFT), - wp->w_winrow - 1, wp->w_wincol - 1, hl); - } - if (!(win_at_top || win_at_right)) { - grid_putchar(&default_grid, get_corner_sep_connector(wp, WC_TOP_RIGHT), - wp->w_winrow - 1, W_ENDCOL(wp), hl); - } - if (!(win_at_bottom || win_at_left)) { - grid_putchar(&default_grid, get_corner_sep_connector(wp, WC_BOTTOM_LEFT), - W_ENDROW(wp), wp->w_wincol - 1, hl); - } - if (!(win_at_bottom || win_at_right)) { - grid_putchar(&default_grid, get_corner_sep_connector(wp, WC_BOTTOM_RIGHT), - W_ENDROW(wp), W_ENDCOL(wp), hl); - } -} - /// Get the length of an item as it will be shown in the status line. -static int status_match_len(expand_T *xp, char_u *s) +static int wildmenu_match_len(expand_T *xp, char_u *s) { int len = 0; @@ -4725,7 +265,7 @@ static int status_match_len(expand_T *xp, char_u *s) } while (*s != NUL) { - s += skip_status_match_char(xp, s); + s += skip_wildmenu_char(xp, s); len += ptr2cells((char *)s); MB_PTR_ADV(s); } @@ -4733,18 +273,20 @@ static int status_match_len(expand_T *xp, char_u *s) return len; } -/* - * Return the number of characters that should be skipped in a status match. - * These are backslashes used for escaping. Do show backslashes in help tags. - */ -static int skip_status_match_char(expand_T *xp, char_u *s) +/// 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(s) && xp->xp_context != EXPAND_HELP) + 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; } @@ -4761,7 +303,7 @@ static int skip_status_match_char(expand_T *xp, char_u *s) /// If inversion is possible we use it. Else '=' characters are used. /// /// @param matches list of matches -void win_redr_status_matches(expand_T *xp, int num_matches, char **matches, int match, int showtail) +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; @@ -4792,7 +334,7 @@ void win_redr_status_matches(expand_T *xp, int num_matches, char **matches, int highlight = false; } // count 1 for the ending ">" - clen = status_match_len(xp, (char_u *)L_MATCH(match)) + 3; + clen = wildmenu_match_len(xp, (char_u *)L_MATCH(match)) + 3; if (match == 0) { first_match = 0; } else if (match < first_match) { @@ -4802,7 +344,7 @@ void win_redr_status_matches(expand_T *xp, int num_matches, char **matches, int } else { // check if match fits on the screen for (i = first_match; i < match; i++) { - clen += status_match_len(xp, (char_u *)L_MATCH(i)) + 2; + clen += wildmenu_match_len(xp, (char_u *)L_MATCH(i)) + 2; } if (first_match > 0) { clen += 2; @@ -4813,7 +355,7 @@ void win_redr_status_matches(expand_T *xp, int num_matches, char **matches, int // if showing the last match, we can add some on the left clen = 2; for (i = match; i < num_matches; i++) { - clen += status_match_len(xp, (char_u *)L_MATCH(i)) + 2; + clen += wildmenu_match_len(xp, (char_u *)L_MATCH(i)) + 2; if ((long)clen >= Columns) { break; } @@ -4825,7 +367,7 @@ void win_redr_status_matches(expand_T *xp, int num_matches, char **matches, int } if (add_left) { while (first_match > 0) { - clen += status_match_len(xp, (char_u *)L_MATCH(first_match - 1)) + 2; + clen += wildmenu_match_len(xp, (char_u *)L_MATCH(first_match - 1)) + 2; if ((long)clen >= Columns) { break; } @@ -4845,7 +387,7 @@ void win_redr_status_matches(expand_T *xp, int num_matches, char **matches, int clen = len; i = first_match; - while (clen + status_match_len(xp, (char_u *)L_MATCH(i)) + 2 < Columns) { + while (clen + wildmenu_match_len(xp, (char_u *)L_MATCH(i)) + 2 < Columns) { if (i == match) { selstart = buf + len; selstart_col = clen; @@ -4861,8 +403,8 @@ void win_redr_status_matches(expand_T *xp, int num_matches, char **matches, int len += l; clen += l; } else { - for (; *s != NUL; ++s) { - s += skip_status_match_char(xp, s); + 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) @@ -4888,7 +430,7 @@ void win_redr_status_matches(expand_T *xp, int num_matches, char **matches, int if (i != num_matches) { *(buf + len++) = '>'; - ++clen; + clen++; } buf[len] = NUL; @@ -4927,10 +469,10 @@ void win_redr_status_matches(expand_T *xp, int num_matches, char **matches, int ScreenGrid *grid = (wild_menu_showing == WM_SCROLLED) ? &msg_grid_adj : &default_grid; - grid_puts(grid, buf, row, 0, attr); + grid_puts(grid, (char *)buf, row, 0, attr); if (selstart != NULL && highlight) { *selend = NUL; - grid_puts(grid, selstart, row, selstart_col, HL_ATTR(HLF_WM)); + grid_puts(grid, (char *)selstart, row, selstart_col, HL_ATTR(HLF_WM)); } grid_fill(grid, row, row + 1, clen, Columns, @@ -4941,190 +483,6 @@ void win_redr_status_matches(expand_T *xp, int num_matches, char **matches, int xfree(buf); } -/// Redraw the status line of window `wp`. -/// -/// If inversion is possible we use it. Else '=' characters are used. -static void win_redr_status(win_T *wp) -{ - int row; - int col; - char_u *p; - int len; - int fillchar; - int attr; - int width; - int this_ru_col; - bool is_stl_global = global_stl_height() > 0; - static int busy = false; - - // May get here recursively when 'statusline' (indirectly) - // invokes ":redrawstatus". Simply ignore the call then. - if (busy - // Also ignore if wildmenu is showing. - || (wild_menu_showing != 0 && !ui_has(kUIWildmenu))) { - return; - } - busy = true; - - wp->w_redr_status = false; - if (wp->w_status_height == 0 && !(is_stl_global && wp == curwin)) { - // no status line, either global statusline is enabled or the window is a last window - redraw_cmdline = true; - } else if (!redrawing()) { - // Don't redraw right now, do it later. Don't update status line when - // popup menu is visible and may be drawn over it - wp->w_redr_status = true; - } else if (*p_stl != NUL || *wp->w_p_stl != NUL) { - // redraw custom status line - redraw_custom_statusline(wp); - } else { - fillchar = fillchar_status(&attr, wp); - width = is_stl_global ? Columns : wp->w_width; - - get_trans_bufname(wp->w_buffer); - p = NameBuff; - len = (int)STRLEN(p); - - if (bt_help(wp->w_buffer) - || wp->w_p_pvw - || bufIsChanged(wp->w_buffer) - || wp->w_buffer->b_p_ro) { - *(p + len++) = ' '; - } - if (bt_help(wp->w_buffer)) { - snprintf((char *)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); - } - if (bufIsChanged(wp->w_buffer)) { - snprintf((char *)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]")); - // 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; - } - if (this_ru_col <= 1) { - p = (char_u *)"<"; // No room for file name! - len = 1; - } else { - int clen = 0, i; - - // Count total number of display cells. - clen = (int)mb_string2cells((char *)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); - } - len = clen; - if (i > 0) { - p = p + i - 1; - *p = '<'; - ++len; - } - } - - row = is_stl_global ? (Rows - (int)p_ch - 1) : W_ENDROW(wp); - col = is_stl_global ? 0 : wp->w_wincol; - grid_puts(&default_grid, p, row, col, attr); - grid_fill(&default_grid, row, row + 1, len + col, - this_ru_col + col, fillchar, fillchar, attr); - - if (get_keymap_str(wp, "<%s>", (char *)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); - } - - /* - * May need to draw the character below the vertical separator. - */ - if (wp->w_vsep_width != 0 && wp->w_status_height != 0 && redrawing()) { - if (stl_connected(wp)) { - fillchar = fillchar_status(&attr, wp); - } else { - fillchar = fillchar_vsep(wp, &attr); - } - grid_putchar(&default_grid, fillchar, W_ENDROW(wp), W_ENDCOL(wp), attr); - } - busy = FALSE; -} - -/* - * Redraw the status line according to 'statusline' and take care of any - * errors encountered. - */ -static 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. */ - if (entered) { - return; - } - 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; -} - -static void win_redr_winbar(win_T *wp) -{ - static bool entered = false; - - // Return when called recursively. This can happen when the winbar contains an expression - // that triggers a redraw. - if (entered) { - return; - } - entered = true; - - 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; -} - /// Only call if (wp->w_vsep_width != 0). /// /// @return true if the status line of window "wp" is connected to the status @@ -5149,77 +507,6 @@ bool stl_connected(win_T *wp) return false; } -/// Check if horizontal separator of window "wp" at specified window corner is connected to the -/// horizontal separator of another window -/// Assumes global statusline is enabled -static bool hsep_connected(win_T *wp, WindowCorner corner) -{ - bool before = (corner == WC_TOP_LEFT || corner == WC_BOTTOM_LEFT); - int sep_row = (corner == WC_TOP_LEFT || corner == WC_TOP_RIGHT) - ? wp->w_winrow - 1 : W_ENDROW(wp); - frame_T *fr = wp->w_frame; - - while (fr->fr_parent != NULL) { - if (fr->fr_parent->fr_layout == FR_ROW && (before ? fr->fr_prev : fr->fr_next) != NULL) { - fr = before ? fr->fr_prev : fr->fr_next; - break; - } - fr = fr->fr_parent; - } - if (fr->fr_parent == NULL) { - return false; - } - while (fr->fr_layout != FR_LEAF) { - fr = fr->fr_child; - if (fr->fr_parent->fr_layout == FR_ROW && before) { - while (fr->fr_next != NULL) { - fr = fr->fr_next; - } - } else { - while (fr->fr_next != NULL && frame2win(fr)->w_winrow + fr->fr_height < sep_row) { - fr = fr->fr_next; - } - } - } - - return (sep_row == fr->fr_win->w_winrow - 1 || sep_row == W_ENDROW(fr->fr_win)); -} - -/// Check if vertical separator of window "wp" at specified window corner is connected to the -/// vertical separator of another window -static bool vsep_connected(win_T *wp, WindowCorner corner) -{ - bool before = (corner == WC_TOP_LEFT || corner == WC_TOP_RIGHT); - int sep_col = (corner == WC_TOP_LEFT || corner == WC_BOTTOM_LEFT) - ? wp->w_wincol - 1 : W_ENDCOL(wp); - frame_T *fr = wp->w_frame; - - while (fr->fr_parent != NULL) { - if (fr->fr_parent->fr_layout == FR_COL && (before ? fr->fr_prev : fr->fr_next) != NULL) { - fr = before ? fr->fr_prev : fr->fr_next; - break; - } - fr = fr->fr_parent; - } - if (fr->fr_parent == NULL) { - return false; - } - while (fr->fr_layout != FR_LEAF) { - fr = fr->fr_child; - if (fr->fr_parent->fr_layout == FR_COL && before) { - while (fr->fr_next != NULL) { - fr = fr->fr_next; - } - } else { - while (fr->fr_next != NULL && frame2win(fr)->w_wincol + fr->fr_width < sep_col) { - fr = fr->fr_next; - } - } - } - - return (sep_col == fr->fr_win->w_wincol - 1 || sep_col == W_ENDCOL(fr->fr_win)); -} - /// Get the value to show for the language mappings, active 'keymap'. /// /// @param fmt format string containing one %s item @@ -5248,7 +535,7 @@ bool get_keymap_str(win_T *wp, char *fmt, char *buf, int len) curwin = old_curwin; if (p == NULL || *p == NUL) { if (wp->w_buffer->b_kmap_state & KEYMAP_LOADED) { - p = (char *)wp->w_buffer->b_p_keymap; + p = wp->w_buffer->b_p_keymap; } else { p = "lang"; } @@ -5261,305 +548,23 @@ bool get_keymap_str(win_T *wp, char *fmt, char *buf, int len) return buf[0] != NUL; } -/// 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_u *stl; - 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; - - 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; - use_sandbox = was_set_insecurely(wp, "tabline", 0); - } else if (draw_winbar) { - stl = (char_u *)((*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 (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; - use_sandbox = was_set_insecurely(wp, "winbar", 0); - - 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)); - } - } 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 (draw_ruler) { - stl = p_ruf; - // advance past any leading group spec - implicit in ru_col - if (*stl == '%') { - if (*++stl == '-') { - stl++; - } - if (atoi((char *)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); - } - - use_sandbox = was_set_insecurely(wp, "rulerformat", 0); - } else { - if (*wp->w_p_stl != NUL) { - stl = wp->w_p_stl; - } else { - stl = p_stl; - } - use_sandbox = was_set_insecurely(wp, "statusline", *wp->w_p_stl == NUL ? 0 : OPT_LOCAL); - } - - 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 = vim_strsave(stl); - width = - build_stl_str_hl(ewp, buf, sizeof(buf), (char *)stl, use_sandbox, - fillchar, maxwidth, &hltab, &tabtab); - xfree(stl); - ewp->w_p_crb = p_crb_save; - - // 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); - - // 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, (char_u *)p, textlen, row, col, curattr); - col += vim_strnsize((char_u *)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 ? (char_u *)"" : (char_u *)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; - - if (click_defs == NULL) { - goto theend; - } - - col = 0; - len = 0; - p = buf; - StlClickDefinition cur_click_def = { - .type = kStlClickDisabled, - }; - for (n = 0; tabtab[n].start != NULL; n++) { - len += vim_strnsize((char_u *)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; - } - } - while (col < maxwidth) { - click_defs[col++] = cur_click_def; - } - -theend: - entered = false; -} - -static void win_redr_border(win_T *wp) -{ - wp->w_redr_border = false; - if (!(wp->w_floating && wp->w_float_config.border)) { - return; - } - - ScreenGrid *grid = &wp->w_grid_alloc; - - schar_T *chars = wp->w_float_config.border_chars; - int *attrs = wp->w_float_config.border_attr; - - int *adj = wp->w_border_adj; - int irow = wp->w_height_inner + wp->w_winbar_height, icol = wp->w_width_inner; - - if (adj[0]) { - grid_puts_line_start(grid, 0); - 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 (adj[1]) { - grid_put_schar(grid, 0, icol + adj[3], chars[2], attrs[2]); - } - grid_puts_line_flush(false); - } - - for (int i = 0; i < irow; i++) { - if (adj[3]) { - grid_puts_line_start(grid, i + adj[0]); - grid_put_schar(grid, i + adj[0], 0, chars[7], attrs[7]); - grid_puts_line_flush(false); - } - if (adj[1]) { - int ic = (i == 0 && !adj[0] && chars[2][0]) ? 2 : 3; - grid_puts_line_start(grid, i + adj[0]); - grid_put_schar(grid, i + adj[0], icol + adj[3], chars[ic], attrs[ic]); - grid_puts_line_flush(false); - } - } - - if (adj[2]) { - grid_puts_line_start(grid, irow + adj[0]); - if (adj[3]) { - grid_put_schar(grid, irow + adj[0], 0, chars[6], attrs[6]); - } - for (int i = 0; i < icol; i++) { - int ic = (i == 0 && !adj[3] && chars[6][0]) ? 6 : 5; - grid_put_schar(grid, irow + adj[0], i + adj[3], chars[ic], attrs[ic]); - } - if (adj[1]) { - grid_put_schar(grid, irow + adj[0], icol + adj[3], chars[4], attrs[4]); - } - grid_puts_line_flush(false); - } -} - -/* - * Prepare for 'hlsearch' highlighting. - */ -static void start_search_hl(void) +/// 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(&search_hl.rm); + last_pat_prog(&screen_search_hl.rm); // Set the time limit to 'redrawtime'. - search_hl.tm = profile_setlimit(p_rdt); + screen_search_hl.tm = profile_setlimit(p_rdt); } } -/* - * Clean up for 'hlsearch' highlighting. - */ -static void end_search_hl(void) +/// Clean up for 'hlsearch' highlighting. +void end_search_hl(void) { - if (search_hl.rm.regprog != NULL) { - vim_regfree(search_hl.rm.regprog); - search_hl.rm.regprog = NULL; + if (screen_search_hl.rm.regprog != NULL) { + vim_regfree(screen_search_hl.rm.regprog); + screen_search_hl.rm.regprog = NULL; } } @@ -5579,105 +584,6 @@ void check_for_delay(bool check_msg_scroll) } } -/// Resize the screen to Rows and Columns. -/// -/// Allocate default_grid.chars[] and other grid arrays. -/// -/// There may be some time between setting Rows and Columns and (re)allocating -/// default_grid arrays. This happens when starting up and when -/// (manually) changing the screen size. Always use default_grid.rows and -/// 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. -void screenalloc(void) -{ - // It's possible that we produce an out-of-memory message below, which - // will cause this function to be called again. To break the loop, just - // return here. - if (resizing) { - return; - } - resizing = true; - - int retry_count = 0; - -retry: - // Allocation of the screen buffers is done only when the size changes and - // when Rows and Columns have been set and we have started doing full - // screen stuff. - if ((default_grid.chars != NULL - && Rows == default_grid.rows - && Columns == default_grid.cols - ) - || Rows == 0 - || Columns == 0 - || (!full_screen && default_grid.chars == NULL)) { - resizing = false; - return; - } - - /* - * Note that the window sizes are updated before reallocating the arrays, - * thus we must not redraw here! - */ - ++RedrawingDisabled; - - // 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; - } - - win_new_screensize(); // fit the windows in the new sized screen - - comp_col(); // recompute columns for shown command and ruler - - // We're changing the size of the screen. - // - Allocate new arrays for default_grid - // - Move lines from the old arrays into the new arrays, clear extra - // lines (unless the screen is going to be cleared). - // - Free the old arrays. - // - // If anything fails, make grid arrays NULL, so we don't do anything! - // Continuing with the old arrays may result in a crash, because the - // 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; - - default_grid.comp_height = Rows; - default_grid.comp_width = Columns; - - default_grid.row_offset = 0; - default_grid.col_offset = 0; - default_grid.handle = DEFAULT_GRID_HANDLE; - - must_redraw = CLEAR; // need to clear the screen later - - RedrawingDisabled--; - - /* - * Do not apply autocommands more than 3 times to avoid an endless loop - * in case applying autocommands always changes Rows or Columns. - */ - if (starting == 0 && ++retry_count <= 3) { - apply_autocmds(EVENT_VIMRESIZED, NULL, NULL, false, curbuf); - // In rare cases, autocommands may have altered Rows or Columns, - // jump back to check if we need to allocate the screen again. - goto retry; - } - - resizing = false; -} - /// Clear status line, window bar or tab page line click definition table /// /// @param[out] tpcd Table to clear. @@ -5694,67 +600,6 @@ void stl_clear_click_defs(StlClickDefinition *const click_defs, const long click } } -void screenclear(void) -{ - check_for_delay(false); - screenalloc(); // allocate screen buffers if size changed - - int i; - - if (starting == NO_SCREEN || default_grid.chars == NULL) { - return; - } - - // blank out the default grid - for (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; - } - - ui_call_grid_clear(1); // clear the display - ui_comp_set_screen_valid(true); - - clear_cmdline = false; - mode_displayed = false; - - redraw_all_later(NOT_VALID); - redraw_cmdline = true; - redraw_tabline = true; - redraw_popupmenu = true; - pum_invalidate(); - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->w_floating) { - wp->w_redr_type = CLEAR; - } - } - if (must_redraw == CLEAR) { - must_redraw = NOT_VALID; // no need to clear again - } - compute_cmdrow(); - msg_row = cmdline_row; // put cursor on last line for messages - msg_col = 0; - msg_scrolled = 0; // can't scroll back - msg_didany = false; - msg_didout = false; - if (HL_ATTR(HLF_MSG) > 0 && msg_use_grid() && msg_grid.chars) { - grid_invalidate(&msg_grid); - msg_grid_validate(); - msg_grid_invalid = false; - clear_cmdline = true; - } -} - -/// Copy part of a grid line for vertically split window. -static void linecopy(ScreenGrid *grid, int to, int from, int col, int width) -{ - unsigned off_to = (unsigned)(grid->line_offset[to] + (size_t)col); - unsigned off_from = (unsigned)(grid->line_offset[from] + (size_t)col); - - memmove(grid->chars + off_to, grid->chars + off_from, (size_t)width * sizeof(schar_T)); - memmove(grid->attrs + off_to, grid->attrs + off_from, (size_t)width * sizeof(sattr_T)); -} - /// Set cursor to its position in the current window. void setcursor(void) { @@ -5784,9 +629,9 @@ void setcursor_mayforce(bool force) } } -/// Scroll 'line_count' lines at 'row' in window 'wp'. +/// Scroll `line_count` lines at 'row' in window 'wp'. /// -/// Positive `line_count' means scrolling down, so that more space is available +/// Positive `line_count` means scrolling down, so that more space is available /// at 'row'. Negative `line_count` implies deleting lines at `row`. void win_scroll_lines(win_T *wp, int row, int line_count) { @@ -5808,126 +653,29 @@ void win_scroll_lines(win_T *wp, int row, int line_count) } } -/* - * The rest of the routines in this file perform screen manipulations. The - * given operation is performed physically on the screen. The corresponding - * change is also made to the internal screen image. In this way, the editor - * anticipates the effect of editing changes on the appearance of the screen. - * That way, when we call screenupdate a complete redraw isn't usually - * necessary. Another advantage is that we can keep adding code to anticipate - * screen changes, and in the meantime, everything still works. - */ - -/// insert lines on the screen and move the existing lines down -/// 'line_count' is the number of lines to be inserted. -/// 'end' is the line after the scrolled part. Normally it is Rows. -/// 'col' is the column from with we start inserting. -// -/// 'row', 'col' and 'end' are relative to the start of the region. -void grid_ins_lines(ScreenGrid *grid, int row, int line_count, int end, int col, int width) -{ - int i; - int j; - unsigned temp; - - int row_off = 0; - grid_adjust(&grid, &row_off, &col); - row += row_off; - end += row_off; - - if (line_count <= 0) { - return; - } - - // Shift line_offset[] line_count down to reflect the inserted lines. - // Clear the inserted lines. - for (i = 0; i < line_count; i++) { - if (width != grid->cols) { - // need to copy part of a line - j = end - 1 - i; - while ((j -= line_count) >= row) { - linecopy(grid, j + line_count, j, col, width); - } - j += line_count; - grid_clear_line(grid, grid->line_offset[j] + (size_t)col, width, false); - grid->line_wraps[j] = false; - } else { - j = end - 1 - i; - temp = (unsigned)grid->line_offset[j]; - while ((j -= line_count) >= row) { - grid->line_offset[j + line_count] = grid->line_offset[j]; - grid->line_wraps[j + line_count] = grid->line_wraps[j]; - } - grid->line_offset[j + line_count] = temp; - grid->line_wraps[j + line_count] = false; - grid_clear_line(grid, temp, grid->cols, false); - } - } - - if (!grid->throttled) { - ui_call_grid_scroll(grid->handle, row, end, col, col + width, -line_count, 0); - } -} - -/// delete lines on the screen and move lines up. -/// 'end' is the line after the scrolled part. Normally it is Rows. -/// When scrolling region used 'off' is the offset from the top for the region. -/// 'row' and 'end' are relative to the start of the region. -void grid_del_lines(ScreenGrid *grid, int row, int line_count, int end, int col, int width) +/// @return true when postponing displaying the mode message: when not redrawing +/// or inside a mapping. +bool skip_showmode(void) { - int j; - int i; - unsigned temp; - - int row_off = 0; - grid_adjust(&grid, &row_off, &col); - row += row_off; - end += row_off; - - if (line_count <= 0) { - return; - } - - // Now shift line_offset[] line_count up to reflect the deleted lines. - // Clear the inserted lines. - for (i = 0; i < line_count; i++) { - if (width != grid->cols) { - // need to copy part of a line - j = row + i; - while ((j += line_count) <= end - 1) { - linecopy(grid, j - line_count, j, col, width); - } - j -= line_count; - grid_clear_line(grid, grid->line_offset[j] + (size_t)col, width, false); - grid->line_wraps[j] = false; - } else { - // whole width, moving the line pointers is faster - j = row + i; - temp = (unsigned)grid->line_offset[j]; - while ((j += line_count) <= end - 1) { - grid->line_offset[j - line_count] = grid->line_offset[j]; - grid->line_wraps[j - line_count] = grid->line_wraps[j]; - } - grid->line_offset[j - line_count] = temp; - grid->line_wraps[j - line_count] = false; - grid_clear_line(grid, temp, grid->cols, false); - } - } - - if (!grid->throttled) { - ui_call_grid_scroll(grid->handle, row, end, col, col + width, line_count, 0); + // Call char_avail() only when we are going to show something, because it + // takes a bit of time. redrawing() may also call char_avail(). + if (global_busy || msg_silent != 0 || !redrawing() || (char_avail() && !KeyTyped)) { + redraw_mode = true; // show mode later + return true; } + return false; } -// Show the current mode and ruler. -// -// If clear_cmdline is true, clear the rest of the cmdline. -// If clear_cmdline is false there may be a message there that needs to be -// cleared only if a mode is shown. -// Return the length of the message (0 if no message). +/// Show the current mode and ruler. +/// +/// If clear_cmdline is true, clear the rest of the cmdline. +/// If clear_cmdline is false there may be a message there that needs to be +/// cleared only if a mode is shown. +/// If redraw_mode is true show or clear the mode. +/// @return the length of the message (0 if no message). int showmode(void) { - int need_clear; + bool need_clear; int length = 0; int do_mode; int attr; @@ -5948,12 +696,8 @@ int showmode(void) || restart_edit != NUL || VIsual_active)); if (do_mode || reg_recording != 0) { - // Don't show mode right now, when not redrawing or inside a mapping. - // Call char_avail() only when we are going to show something, because - // it takes a bit of time. - if (!redrawing() || (char_avail() && !KeyTyped) || msg_silent != 0) { - redraw_cmdline = true; // show mode later - return 0; + if (skip_showmode()) { + return 0; // show mode later } bool nwr_save = need_wait_return; @@ -5990,13 +734,13 @@ int showmode(void) length = (Rows - msg_row) * Columns - 3; } if (edit_submode_extra != NULL) { - length -= vim_strsize((char *)edit_submode_extra); + length -= vim_strsize(edit_submode_extra); } if (length > 0) { if (edit_submode_pre != NULL) { - length -= vim_strsize((char *)edit_submode_pre); + length -= vim_strsize(edit_submode_pre); } - if (length - vim_strsize((char *)edit_submode) > 0) { + if (length - vim_strsize(edit_submode) > 0) { if (edit_submode_pre != NULL) { msg_puts_attr((const char *)edit_submode_pre, attr); } @@ -6077,7 +821,7 @@ int showmode(void) msg_puts_attr(" --", attr); } - need_clear = TRUE; + need_clear = true; } if (reg_recording != 0 && edit_submode == NULL // otherwise it gets too long @@ -6087,7 +831,7 @@ int showmode(void) } mode_displayed = true; - if (need_clear || clear_cmdline) { + if (need_clear || clear_cmdline || redraw_mode) { msg_clr_eos(); } msg_didout = false; // overwrite this message @@ -6099,6 +843,9 @@ int showmode(void) } else if (clear_cmdline && msg_silent == 0) { // Clear the whole command line. Will reset "clear_cmdline". msg_clr_cmdline(); + } else if (redraw_mode) { + msg_pos_mode(); + msg_clr_eos(); } // NB: also handles clearing the showmode if it was empty or disabled @@ -6116,14 +863,13 @@ int showmode(void) win_redr_ruler(last, true); } redraw_cmdline = false; + redraw_mode = false; clear_cmdline = false; return length; } -/* - * Position for a mode message. - */ +/// Position for a mode message. static void msg_pos_mode(void) { msg_col = 0; @@ -6171,9 +917,7 @@ static void recording_mode(int attr) } } -/* - * Draw the tab pages line at the top of the Vim window. - */ +/// Draw the tab pages line at the top of the Vim window. void draw_tabline(void) { int tabcount = 0; @@ -6191,8 +935,7 @@ void draw_tabline(void) int attr_fill = HL_ATTR(HLF_TPF); char_u *p; int room; - int use_sep_chars = (t_colors < 8 - ); + int use_sep_chars = (t_colors < 8); if (default_grid.chars == NULL) { return; @@ -6226,7 +969,7 @@ void draw_tabline(void) did_emsg |= saved_did_emsg; } else { FOR_ALL_TABS(tp) { - ++tabcount; + tabcount++; } if (tabcount > 0) { @@ -6270,7 +1013,7 @@ void draw_tabline(void) modified = false; - for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount) { + for (wincount = 0; wp != NULL; wp = wp->w_next, wincount++) { if (bufIsChanged(wp->w_buffer)) { modified = true; } @@ -6288,7 +1031,7 @@ void draw_tabline(void) col += len; } if (modified) { - grid_puts_len(&default_grid, (char_u *)"+", 1, 0, col++, attr); + grid_puts_len(&default_grid, "+", 1, 0, col++, attr); } grid_putchar(&default_grid, ' ', 0, col++, attr); } @@ -6299,7 +1042,7 @@ void draw_tabline(void) get_trans_bufname(cwp->w_buffer); shorten_dir(NameBuff); len = vim_strsize((char *)NameBuff); - p = NameBuff; + p = (char_u *)NameBuff; while (len > room) { len -= ptr2cells((char *)p); MB_PTR_ADV(p); @@ -6308,7 +1051,7 @@ void draw_tabline(void) len = Columns - col - 1; } - grid_puts_len(&default_grid, p, (int)STRLEN(p), 0, col, attr); + grid_puts_len(&default_grid, (char *)p, (int)STRLEN(p), 0, col, attr); col += len; } grid_putchar(&default_grid, ' ', 0, col++, attr); @@ -6348,10 +1091,9 @@ void draw_tabline(void) redraw_tabline = false; } -void ui_ext_tabline_update(void) +static void ui_ext_tabline_update(void) { Arena arena = ARENA_EMPTY; - arena_start(&arena, &ui_ext_fixblk); size_t n_tabs = 0; FOR_ALL_TABS(tp) { @@ -6392,13 +1134,9 @@ void ui_ext_tabline_update(void) } ui_call_tabline_update(curtab->handle, tabs, curbuf->handle, buffers); - arena_mem_free(arena_finish(&arena), &ui_ext_fixblk); + arena_mem_free(arena_finish(&arena)); } -/* - * Get buffer name for "buf" into NameBuff[]. - * Takes care of special buffer names and translates special characters. - */ void get_trans_bufname(buf_T *buf) { if (buf_spname(buf) != NULL) { @@ -6409,37 +1147,9 @@ void get_trans_bufname(buf_T *buf) trans_characters((char *)NameBuff, MAXPATHL); } -/* - * Get the character to use in a status line. Get its attributes in "*attr". - */ -int fillchar_status(int *attr, win_T *wp) -{ - int fill; - bool is_curwin = (wp == curwin); - if (is_curwin) { - *attr = win_hl_attr(wp, HLF_S); - fill = wp->w_p_fcs_chars.stl; - } else { - *attr = win_hl_attr(wp, HLF_SNC); - fill = wp->w_p_fcs_chars.stlnc; - } - // Use fill when there is highlighting, and highlighting of current - // window differs, or the fillchars differ, or this is not the - // current window - if (*attr != 0 && ((win_hl_attr(wp, HLF_S) != win_hl_attr(wp, HLF_SNC) - || !is_curwin || ONE_WINDOW) - || (wp->w_p_fcs_chars.stl != wp->w_p_fcs_chars.stlnc))) { - return fill; - } - if (is_curwin) { - return '^'; - } - return '='; -} - /// Get the character to use in a separator between vertically split windows. /// Get its attributes in "*attr". -static int fillchar_vsep(win_T *wp, int *attr) +int fillchar_vsep(win_T *wp, int *attr) { *attr = win_hl_attr(wp, HLF_C); return wp->w_p_fcs_chars.vert; @@ -6447,240 +1157,68 @@ static int fillchar_vsep(win_T *wp, int *attr) /// Get the character to use in a separator between horizontally split windows. /// Get its attributes in "*attr". -static int fillchar_hsep(win_T *wp, int *attr) +int fillchar_hsep(win_T *wp, int *attr) { *attr = win_hl_attr(wp, HLF_C); return wp->w_p_fcs_chars.horiz; } -/* - * Return TRUE if redrawing should currently be done. - */ -int redrawing(void) +/// Return true if redrawing should currently be done. +bool redrawing(void) { return !RedrawingDisabled && !(p_lz && char_avail() && !KeyTyped && !do_redraw); } -/* - * Return TRUE if printing messages should currently be done. - */ -int messaging(void) +/// Return true if printing messages should currently be done. +bool messaging(void) { return !(p_lz && char_avail() && !KeyTyped) && ui_has_messages(); } -/// Show current status info in ruler and various other places -/// -/// @param always if false, only show ruler if position has changed. -void showruler(bool always) -{ - if (!always && !redrawing()) { - return; - } - if ((*p_stl != NUL || *curwin->w_p_stl != NUL) - && (curwin->w_status_height || global_stl_height())) { - redraw_custom_statusline(curwin); - } else { - win_redr_ruler(curwin, always); - } - if (*p_wbr != NUL || *curwin->w_p_wbr != NUL) { - win_redr_winbar(curwin); - } - - if (need_maketitle - || (p_icon && (stl_syntax & STL_IN_ICON)) - || (p_title && (stl_syntax & STL_IN_TITLE))) { - maketitle(); - } - // Redraw the tab pages line if needed. - if (redraw_tabline) { - draw_tabline(); - } -} +#define COL_RULER 17 // columns needed by standard ruler -static void win_redr_ruler(win_T *wp, bool always) +/// Compute columns for ruler and shown command. 'sc_col' is also used to +/// decide what the maximum length of a message on the status line can be. +/// If there is a status line for the last window, 'sc_col' is independent +/// of 'ru_col'. +void comp_col(void) { - bool is_stl_global = global_stl_height() > 0; - static bool did_show_ext_ruler = false; - - // If 'ruler' off, don't do anything - if (!p_ru) { - return; - } - - /* - * Check if cursor.lnum is valid, since win_redr_ruler() may be called - * after deleting lines, before cursor.lnum is corrected. - */ - if (wp->w_cursor.lnum > wp->w_buffer->b_ml.ml_line_count) { - return; - } + int last_has_status = (p_ls > 1 || (p_ls == 1 && !ONE_WINDOW)); - // Don't draw the ruler while doing insert-completion, it might overwrite - // the (long) mode message. - if (wp == lastwin && lastwin->w_status_height == 0 && !is_stl_global) { - if (edit_submode != NULL) { - return; + sc_col = 0; + ru_col = 0; + if (p_ru) { + ru_col = (ru_wid ? ru_wid : COL_RULER) + 1; + // no last status line, adjust sc_col + if (!last_has_status) { + sc_col = ru_col; } } - - 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); + if (p_sc) { + sc_col += SHOWCMD_COLS; + if (!p_ru || last_has_status) { // no need for separating space + sc_col++; } - return; } - - // Check if not in Insert mode and the line is empty (will show "0-1"). - int empty_line = false; - if ((State & MODE_INSERT) == 0 && *ml_get_buf(wp->w_buffer, wp->w_cursor.lnum, false) == NUL) { - empty_line = true; + assert(sc_col >= 0 + && INT_MIN + sc_col <= Columns); + sc_col = Columns - sc_col; + assert(ru_col >= 0 + && INT_MIN + ru_col <= Columns); + ru_col = Columns - ru_col; + if (sc_col <= 0) { // screen too narrow, will become a mess + sc_col = 1; } - - /* - * Only draw the ruler when something changed. - */ - validate_virtcol_win(wp); - if (redraw_cmdline - || always - || wp->w_cursor.lnum != wp->w_ru_cursor.lnum - || wp->w_cursor.col != wp->w_ru_cursor.col - || wp->w_virtcol != wp->w_ru_virtcol - || wp->w_cursor.coladd != wp->w_ru_cursor.coladd - || wp->w_topline != wp->w_ru_topline - || wp->w_buffer->b_ml.ml_line_count != wp->w_ru_line_count - || wp->w_topfill != wp->w_ru_topfill - || empty_line != wp->w_ru_empty) { - int width; - int row; - int fillchar; - int attr; - int off; - bool part_of_status = false; - - if (wp->w_status_height) { - row = W_ENDROW(wp); - fillchar = fillchar_status(&attr, wp); - off = wp->w_wincol; - width = wp->w_width; - part_of_status = true; - } else if (is_stl_global) { - row = Rows - (int)p_ch - 1; - fillchar = fillchar_status(&attr, wp); - off = 0; - width = Columns; - part_of_status = true; - } else { - row = Rows - 1; - fillchar = ' '; - attr = HL_ATTR(HLF_MSG); - width = Columns; - off = 0; - } - - if (!part_of_status && !ui_has_messages()) { - return; - } - - // In list mode virtcol needs to be recomputed - colnr_T virtcol = wp->w_virtcol; - if (wp->w_p_list && wp->w_p_lcs_chars.tab1 == NUL) { - wp->w_p_list = false; - getvvcol(wp, &wp->w_cursor, NULL, &virtcol, NULL); - wp->w_p_list = true; - } - -#define RULER_BUF_LEN 70 - char buffer[RULER_BUF_LEN]; - - /* - * Some sprintfs return the length, some return a pointer. - * To avoid portability problems we use strlen() here. - */ - vim_snprintf(buffer, RULER_BUF_LEN, "%" PRId64 ",", - (wp->w_buffer->b_ml.ml_flags & ML_EMPTY) ? (int64_t)0L - : (int64_t)wp->w_cursor.lnum); - size_t len = STRLEN(buffer); - col_print(buffer + len, RULER_BUF_LEN - len, - empty_line ? 0 : (int)wp->w_cursor.col + 1, - (int)virtcol + 1); - - /* - * Add a "50%" if there is room for it. - * On the last line, don't print in the last column (scrolls the - * screen up on some terminals). - */ - int i = (int)STRLEN(buffer); - get_rel_pos(wp, buffer + i + 1, RULER_BUF_LEN - i - 1); - int o = i + vim_strsize(buffer + i + 1); - if (wp->w_status_height == 0 && !is_stl_global) { // can't use last char of screen - o++; - } - int this_ru_col = ru_col - (Columns - width); - if (this_ru_col < 0) { - this_ru_col = 0; - } - // Never use more than half the window/screen width, leave the other half - // for the filename. - if (this_ru_col < (width + 1) / 2) { - this_ru_col = (width + 1) / 2; - } - if (this_ru_col + o < width) { - // Need at least 3 chars left for get_rel_pos() + NUL. - while (this_ru_col + o < width && RULER_BUF_LEN > i + 4) { - i += utf_char2bytes(fillchar, buffer + i); - o++; - } - get_rel_pos(wp, buffer + i, RULER_BUF_LEN - i); - } - - if (ui_has(kUIMessages) && !part_of_status) { - MAXSIZE_TEMP_ARRAY(content, 1); - MAXSIZE_TEMP_ARRAY(chunk, 2); - ADD_C(chunk, INTEGER_OBJ(attr)); - ADD_C(chunk, STRING_OBJ(cstr_as_string((char *)buffer))); - ADD_C(content, ARRAY_OBJ(chunk)); - ui_call_msg_ruler(content); - did_show_ext_ruler = true; - } else { - if (did_show_ext_ruler) { - ui_call_msg_ruler((Array)ARRAY_DICT_INIT); - did_show_ext_ruler = false; - } - // Truncate at window boundary. - o = 0; - for (i = 0; buffer[i] != NUL; i += utfc_ptr2len(buffer + i)) { - o += utf_ptr2cells(buffer + i); - if (this_ru_col + o > width) { - buffer[i] = NUL; - break; - } - } - - ScreenGrid *grid = part_of_status ? &default_grid : &msg_grid_adj; - grid_puts(grid, (char_u *)buffer, row, this_ru_col + off, attr); - grid_fill(grid, row, row + 1, - this_ru_col + off + (int)STRLEN(buffer), off + width, fillchar, - fillchar, attr); - } - - wp->w_ru_cursor = wp->w_cursor; - wp->w_ru_virtcol = wp->w_virtcol; - wp->w_ru_empty = (char)empty_line; - wp->w_ru_topline = wp->w_topline; - wp->w_ru_line_count = wp->w_buffer->b_ml.ml_line_count; - wp->w_ru_topfill = wp->w_topfill; + if (ru_col <= 0) { + ru_col = 1; } + set_vim_var_nr(VV_ECHOSPACE, sc_col - 1); } -/* - * Return the width of the 'number' and 'relativenumber' column. - * Caller may need to check if 'number' or 'relativenumber' is set. - * Otherwise it depends on 'numberwidth' and the line count. - */ +/// Return the width of the 'number' and 'relativenumber' column. +/// Caller may need to check if 'number' or 'relativenumber' is set. +/// Otherwise it depends on 'numberwidth' and the line count. int number_width(win_T *wp) { int n; @@ -6702,7 +1240,7 @@ int number_width(win_T *wp) n = 0; do { lnum /= 10; - ++n; + n++; } while (lnum > 0); // 'numberwidth' gives the minimal width plus one @@ -6721,155 +1259,282 @@ int number_width(win_T *wp) return n; } -/// Used when 'cursorlineopt' contains "screenline": compute the margins between -/// which the highlighting is used. -static void margin_columns_win(win_T *wp, int *left_col, int *right_col) +/// 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) { - // cache previous calculations depending on w_virtcol - static int saved_w_virtcol; - static win_T *prev_wp; - static int prev_left_col; - static int prev_right_col; - static int prev_col_off; - - int cur_col_off = win_col_off(wp); - int width1; - int width2; - - if (saved_w_virtcol == wp->w_virtcol && prev_wp == wp - && prev_col_off == cur_col_off) { - *right_col = prev_right_col; - *left_col = prev_left_col; - return; - } - - width1 = wp->w_width - cur_col_off; - width2 = width1 + win_col_off2(wp); + const char_u *s = *p; - *left_col = 0; - *right_col = width1; - - if (wp->w_virtcol >= (colnr_T)width1) { - *right_col = width1 + ((wp->w_virtcol - width1) / width2 + 1) * width2; - } - if (wp->w_virtcol >= (colnr_T)width1 && width2 > 0) { - *left_col = (wp->w_virtcol - width1) / width2 * width2 + width1; + 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)); + if (n < 0) { + return 0; + } + num = num * 256 + n; + } + *p += 2; + return (int)num; } - // cache values - prev_left_col = *left_col; - prev_right_col = *right_col; - prev_wp = wp; - saved_w_virtcol = wp->w_virtcol; - prev_col_off = cur_col_off; + // TODO(bfredl): use schar_T representation and utfc_ptr2len + int clen = utf_ptr2len((const char *)s); + int c = mb_cptr2char_adv(p); + if (clen == 1 && c > 127) { // Invalid UTF-8 byte + return 0; + } + return c; } -/// Set dimensions of the Nvim application "screen". -void screen_resize(int width, int height) -{ - // Avoid recursiveness, can happen when setting the window size causes - // another window-changed signal. - if (updating_screen || resizing_screen) { - return; - } +/// Handle setting 'listchars' or 'fillchars'. +/// Assume monocell characters +/// +/// @param varp either the global or the window-local value. +/// @param apply if false, do not store the flags, only check for errors. +/// @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:" + 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); + + struct chars_tab { + int *cp; ///< char value + char *name; ///< char id + int def; ///< default value + }; - if (width < 0 || height < 0) { // just checking... - return; - } + // XXX: Characters taking 2 columns is forbidden (TUI limitation?). Set old defaults in this case. + struct chars_tab fcs_tab[] = { + { &wp->w_p_fcs_chars.stl, "stl", ' ' }, + { &wp->w_p_fcs_chars.stlnc, "stlnc", ' ' }, + { &wp->w_p_fcs_chars.wbr, "wbr", ' ' }, + { &wp->w_p_fcs_chars.horiz, "horiz", char2cells(0x2500) == 1 ? 0x2500 : '-' }, // ─ + { &wp->w_p_fcs_chars.horizup, "horizup", char2cells(0x2534) == 1 ? 0x2534 : '-' }, // ┴ + { &wp->w_p_fcs_chars.horizdown, "horizdown", char2cells(0x252c) == 1 ? 0x252c : '-' }, // ┬ + { &wp->w_p_fcs_chars.vert, "vert", char2cells(0x2502) == 1 ? 0x2502 : '|' }, // │ + { &wp->w_p_fcs_chars.vertleft, "vertleft", char2cells(0x2524) == 1 ? 0x2524 : '|' }, // ┤ + { &wp->w_p_fcs_chars.vertright, "vertright", char2cells(0x251c) == 1 ? 0x251c : '|' }, // ├ + { &wp->w_p_fcs_chars.verthoriz, "verthoriz", char2cells(0x253c) == 1 ? 0x253c : '+' }, // ┼ + { &wp->w_p_fcs_chars.fold, "fold", char2cells(0x00b7) == 1 ? 0x00b7 : '-' }, // · + { &wp->w_p_fcs_chars.foldopen, "foldopen", '-' }, + { &wp->w_p_fcs_chars.foldclosed, "foldclose", '+' }, + { &wp->w_p_fcs_chars.foldsep, "foldsep", char2cells(0x2502) == 1 ? 0x2502 : '|' }, // │ + { &wp->w_p_fcs_chars.diff, "diff", '-' }, + { &wp->w_p_fcs_chars.msgsep, "msgsep", ' ' }, + { &wp->w_p_fcs_chars.eob, "eob", '~' }, + }; - if (State == MODE_HITRETURN || State == MODE_SETWSIZE) { - // postpone the resizing - State = MODE_SETWSIZE; - return; - } + struct chars_tab lcs_tab[] = { + { &wp->w_p_lcs_chars.eol, "eol", NUL }, + { &wp->w_p_lcs_chars.ext, "extends", NUL }, + { &wp->w_p_lcs_chars.nbsp, "nbsp", NUL }, + { &wp->w_p_lcs_chars.prec, "precedes", NUL }, + { &wp->w_p_lcs_chars.space, "space", NUL }, + { &wp->w_p_lcs_chars.tab2, "tab", NUL }, + { &wp->w_p_lcs_chars.lead, "lead", NUL }, + { &wp->w_p_lcs_chars.trail, "trail", NUL }, + { &wp->w_p_lcs_chars.conceal, "conceal", NUL }, + }; - /* curwin->w_buffer can be NULL when we are closing a window and the - * buffer has already been closed and removing a scrollbar causes a resize - * event. Don't resize then, it will happen after entering another buffer. - */ - if (curwin->w_buffer == NULL) { - return; + struct chars_tab *tab; + int entries; + const char_u *value = (char_u *)(*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 + } + } 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 + } } - resizing_screen = true; + // first round: check for valid value, second round: assign values + for (int round = 0; round <= (apply ? 1 : 0); round++) { + if (round > 0) { + // After checking that the value is valid: set defaults + for (int i = 0; i < entries; i++) { + if (tab[i].cp != NULL) { + *(tab[i].cp) = tab[i].def; + } + } + if (is_listchars) { + wp->w_p_lcs_chars.tab1 = NUL; + wp->w_p_lcs_chars.tab3 = NUL; - Rows = height; - Columns = width; - check_screensize(); - int max_p_ch = Rows - min_rows() + 1; - if (!ui_has(kUIMessages) && p_ch > 0 && p_ch > max_p_ch) { - p_ch = max_p_ch ? max_p_ch : 1; - } - height = Rows; - width = Columns; - p_lines = Rows; - p_columns = Columns; - ui_call_grid_resize(1, width, height); + xfree(wp->w_p_lcs_chars.multispace); + if (multispace_len > 0) { + wp->w_p_lcs_chars.multispace = xmalloc(((size_t)multispace_len + 1) * sizeof(int)); + wp->w_p_lcs_chars.multispace[multispace_len] = NUL; + } else { + wp->w_p_lcs_chars.multispace = NULL; + } - send_grid_resize = true; + xfree(wp->w_p_lcs_chars.leadmultispace); + if (lead_multispace_len > 0) { + wp->w_p_lcs_chars.leadmultispace + = xmalloc(((size_t)lead_multispace_len + 1) * sizeof(int)); + wp->w_p_lcs_chars.leadmultispace[lead_multispace_len] = NUL; + } else { + wp->w_p_lcs_chars.leadmultispace = NULL; + } + } + } + const char_u *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 + && p[len] == ':' + && p[len + 1] != NUL) { + const char_u *s = p + len + 1; + int c1 = get_encoded_char_adv(&s); + if (c1 == 0 || char2cells(c1) > 1) { + return e_invarg; + } + int c2 = 0, c3 = 0; + if (tab[i].cp == &wp->w_p_lcs_chars.tab2) { + if (*s == NUL) { + return e_invarg; + } + c2 = get_encoded_char_adv(&s); + if (c2 == 0 || char2cells(c2) > 1) { + return e_invarg; + } + if (!(*s == ',' || *s == NUL)) { + c3 = get_encoded_char_adv(&s); + if (c3 == 0 || char2cells(c3) > 1) { + return e_invarg; + } + } + } + if (*s == ',' || *s == NUL) { + if (round > 0) { + if (tab[i].cp == &wp->w_p_lcs_chars.tab2) { + wp->w_p_lcs_chars.tab1 = c1; + wp->w_p_lcs_chars.tab2 = c2; + wp->w_p_lcs_chars.tab3 = c3; + } else if (tab[i].cp != NULL) { + *(tab[i].cp) = c1; + } + } + p = s; + break; + } + } + } - /// The window layout used to be adjusted here, but it now happens in - /// screenalloc() (also invoked from screenclear()). That is because the - /// recursize "resizing_screen" check above may skip this, but not screenalloc(). + if (i == entries) { + const size_t len = STRLEN("multispace"); + const size_t len2 = STRLEN("leadmultispace"); + if (is_listchars + && STRNCMP(p, "multispace", len) == 0 + && p[len] == ':' + && p[len + 1] != NUL) { + const char_u *s = p + len + 1; + if (round == 0) { + // Get length of lcs-multispace string in the first round + last_multispace = p; + multispace_len = 0; + while (*s != NUL && *s != ',') { + int c1 = get_encoded_char_adv(&s); + if (c1 == 0 || char2cells(c1) > 1) { + return e_invarg; + } + multispace_len++; + } + if (multispace_len == 0) { + // lcs-multispace cannot be an empty string + return e_invarg; + } + p = s; + } else { + int multispace_pos = 0; + while (*s != NUL && *s != ',') { + int c1 = get_encoded_char_adv(&s); + if (p == last_multispace) { + wp->w_p_lcs_chars.multispace[multispace_pos++] = c1; + } + } + p = s; + } + } else if (is_listchars + && STRNCMP(p, "leadmultispace", len2) == 0 + && p[len2] == ':' + && p[len2 + 1] != NUL) { + const char_u *s = p + len2 + 1; + if (round == 0) { + // get length of lcs-leadmultispace string in first round + last_lmultispace = p; + lead_multispace_len = 0; + while (*s != NUL && *s != ',') { + int c1 = get_encoded_char_adv(&s); + if (c1 == 0 || char2cells(c1) > 1) { + return e_invarg; + } + lead_multispace_len++; + } + if (lead_multispace_len == 0) { + // lcs-leadmultispace cannot be an empty string + return e_invarg; + } + p = s; + } else { + int multispace_pos = 0; + while (*s != NUL && *s != ',') { + int c1 = get_encoded_char_adv(&s); + if (p == last_lmultispace) { + wp->w_p_lcs_chars.leadmultispace[multispace_pos++] = c1; + } + } + p = s; + } + } else { + return e_invarg; + } + } - if (State != MODE_ASKMORE && State != MODE_EXTERNCMD && State != MODE_CONFIRM) { - screenclear(); + if (*p == ',') { + p++; + } + } } - if (starting != NO_SCREEN) { - maketitle(); - changed_line_abv_curs(); - invalidate_botline(); - - /* - * We only redraw when it's needed: - * - While at the more prompt or executing an external command, don't - * redraw, but position the cursor. - * - While editing the command line, only redraw that. - * - in Ex mode, don't redraw anything. - * - Otherwise, redraw right now, and position the cursor. - * Always need to call update_screen() or screenalloc(), to make - * sure Rows/Columns and the size of the screen is correct! - */ - if (State == MODE_ASKMORE || State == MODE_EXTERNCMD || State == MODE_CONFIRM - || exmode_active) { - screenalloc(); - if (msg_grid.chars) { - msg_grid_validate(); - } - // TODO(bfredl): sometimes messes up the output. Implement clear+redraw - // also for the pager? (or: what if the pager was just a modal window?) - ui_comp_set_screen_valid(true); - repeat_message(); - } else { - if (curwin->w_p_scb) { - do_check_scrollbind(true); - } - if (State & MODE_CMDLINE) { - redraw_popupmenu = false; - update_screen(NOT_VALID); - redrawcmdline(); - if (pum_drawn()) { - cmdline_pum_display(false); - } - } else { - update_topline(curwin); - if (pum_drawn()) { - // TODO(bfredl): ins_compl_show_pum wants to redraw the screen first. - // For now make sure the nested update_screen(0) won't redraw the - // pum at the old position. Try to untangle this later. - redraw_popupmenu = false; - ins_compl_show_pum(); - } - update_screen(NOT_VALID); - if (redrawing()) { - setcursor(); - } - } + return NULL; // no error +} + +/// Check all global and local values of 'listchars' and 'fillchars'. +/// May set different defaults in case character widths change. +/// +/// @return an untranslated error message if any of them is invalid, NULL otherwise. +char *check_chars_options(void) +{ + if (set_chars_option(curwin, &p_lcs, false) != NULL) { + return e_conflicts_with_value_of_listchars; + } + if (set_chars_option(curwin, &p_fcs, false) != NULL) { + return e_conflicts_with_value_of_fillchars; + } + FOR_ALL_TAB_WINDOWS(tp, wp) { + if (set_chars_option(wp, &wp->w_p_lcs, true) != NULL) { + return e_conflicts_with_value_of_listchars; + } + if (set_chars_option(wp, &wp->w_p_fcs, true) != NULL) { + return e_conflicts_with_value_of_fillchars; } - ui_flush(); } - resizing_screen = false; + return NULL; } /// Check if the new Nvim application "screen" dimensions are valid. @@ -6890,13 +1555,3 @@ void check_screensize(void) Columns = 10000; } } - -win_T *get_win_by_grid_handle(handle_T handle) -{ - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->w_grid_alloc.handle == handle) { - return wp; - } - } - return NULL; -} diff --git a/src/nvim/screen.h b/src/nvim/screen.h index 9eda5223f1..ea1c58cd80 100644 --- a/src/nvim/screen.h +++ b/src/nvim/screen.h @@ -4,31 +4,10 @@ #include <stdbool.h> #include "nvim/buffer_defs.h" -#include "nvim/grid.h" -#include "nvim/pos.h" -#include "nvim/types.h" +#include "nvim/fold.h" +#include "nvim/grid_defs.h" -// flags for update_screen() -// The higher the value, the higher the priority -#define VALID 10 // buffer not changed, or changes marked - // with b_mod_* -#define INVERTED 20 // redisplay inverted part that changed -#define INVERTED_ALL 25 // redisplay whole inverted part -#define REDRAW_TOP 30 // display first w_upd_rows screen lines -#define SOME_VALID 35 // like NOT_VALID but may scroll -#define NOT_VALID 40 // buffer needs complete redraw -#define CLEAR 50 // screen messed up, clear it - -/// corner value flags for hsep_connected and vsep_connected -typedef enum { - WC_TOP_LEFT = 0, - WC_TOP_RIGHT, - WC_BOTTOM_LEFT, - WC_BOTTOM_RIGHT, -} WindowCorner; - -// Maximum columns for terminal highlight attributes -#define TERM_ATTRS_MAX 1024 +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); @@ -39,13 +18,6 @@ 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) -// While redrawing the screen this flag is set. It means the screen size -// ('lines' and 'rows') must not be changed. -EXTERN bool updating_screen INIT(= 0); - -// While resizing the screen this flag is set. -EXTERN bool resizing_screen INIT(= 0); - #ifdef INCLUDE_GENERATED_DECLARATIONS # include "screen.h.generated.h" #endif diff --git a/src/nvim/search.c b/src/nvim/search.c index 403e2f3aa4..e995081df8 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -15,12 +15,14 @@ #include "nvim/buffer.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/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" @@ -42,8 +44,8 @@ #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" @@ -168,7 +170,7 @@ int search_regcomp(char_u *pat, int pat_save, int pat_use, int options, regmmatc } if (curwin->w_p_rl && *curwin->w_p_rlc == 's') { - mr_pattern = reverse_text(pat); + mr_pattern = (char_u *)reverse_text((char *)pat); mr_pattern_alloced = true; } else { mr_pattern = pat; @@ -218,7 +220,7 @@ void save_re_pat(int idx, char_u *pat, int magic) last_idx = idx; // If 'hlsearch' set and search pat changed: need redraw. if (p_hls) { - redraw_all_later(SOME_VALID); + redraw_all_later(UPD_SOME_VALID); } set_no_hlsearch(false); } @@ -271,7 +273,7 @@ void free_search_patterns(void) free_spat(&spats[0]); free_spat(&spats[1]); - memset(spats, 0, sizeof(spats)); + CLEAR_FIELD(spats); if (mr_pattern_alloced) { xfree(mr_pattern); @@ -352,10 +354,8 @@ char_u *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. - */ +/// Return true when case should be ignored for search pattern "pat". +/// Uses the 'ignorecase' and 'smartcase' options. int ignorecase(char_u *pat) { return ignorecase_opt(pat, p_ic, p_scs); @@ -421,7 +421,7 @@ int last_csearch_forward(void) int last_csearch_until(void) { - return last_t_cmd == TRUE; + return last_t_cmd == true; } void set_last_csearch(int c, char_u *s, int len) @@ -431,7 +431,7 @@ void set_last_csearch(int c, char_u *s, int len) if (len) { memcpy(lastc_bytes, s, (size_t)len); } else { - memset(lastc_bytes, 0, sizeof(lastc_bytes)); + CLEAR_FIELD(lastc_bytes); } } @@ -478,8 +478,8 @@ void set_last_search_pat(const char_u *s, int idx, int magic, int setlast) spats[idx].no_scs = false; spats[idx].off.dir = '/'; set_vv_searchforward(); - spats[idx].off.line = FALSE; - spats[idx].off.end = FALSE; + spats[idx].off.line = false; + spats[idx].off.end = false; spats[idx].off.off = 0; if (setlast) { last_idx = idx; @@ -496,7 +496,7 @@ void set_last_search_pat(const char_u *s, int idx, int magic, int setlast) } // If 'hlsearch' set and search pat changed: need redraw. if (p_hls && idx == last_idx && !no_hlsearch) { - redraw_all_later(SOME_VALID); + redraw_all_later(UPD_SOME_VALID); } } @@ -511,9 +511,9 @@ void last_pat_prog(regmmatch_T *regmatch) regmatch->regprog = NULL; return; } - ++emsg_off; // So it doesn't beep if bad expr + emsg_off++; // So it doesn't beep if bad expr (void)search_regcomp((char_u *)"", 0, last_idx, SEARCH_KEEP, regmatch); - --emsg_off; + emsg_off--; } /// Lowest level search function. @@ -608,11 +608,11 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir, start_pos = *pos; // remember start pos for detecting no match found = 0; // default: not found - at_first_line = TRUE; // default: start in first line + at_first_line = true; // default: start in first line if (pos->lnum == 0) { // correct lnum for when starting in line 0 pos->lnum = 1; pos->col = 0; - at_first_line = FALSE; // not in first line now + at_first_line = false; // not in first line now } /* @@ -625,14 +625,14 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir, if (dir == BACKWARD && start_pos.col == 0 && (options & SEARCH_START) == 0) { lnum = pos->lnum - 1; - at_first_line = FALSE; + at_first_line = false; } else { lnum = pos->lnum; } - for (loop = 0; loop <= 1; ++loop) { // loop twice if 'wrapscan' set + for (loop = 0; loop <= 1; loop++) { // loop twice if 'wrapscan' set for (; lnum > 0 && lnum <= buf->b_ml.ml_line_count; - lnum += dir, at_first_line = FALSE) { + lnum += dir, at_first_line = false) { // Stop after checking "stop_lnum", if it's set. if (stop_lnum != 0 && (dir == FORWARD ? lnum > stop_lnum : lnum < stop_lnum)) { @@ -899,7 +899,7 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir, break; // if second loop, stop where started } } - at_first_line = FALSE; + at_first_line = false; // vim_regexec_multi() may clear "regprog" if (regmatch.regprog == NULL) { @@ -989,7 +989,7 @@ static int first_submatch(regmmatch_T *rp) { int submatch; - for (submatch = 1;; ++submatch) { + for (submatch = 1;; submatch++) { if (rp->startpos[submatch].lnum >= 0) { break; } @@ -1004,22 +1004,22 @@ static int first_submatch(regmmatch_T *rp) /// Highest level string search function. /// Search for the 'count'th occurrence of pattern 'pat' in direction 'dirc' /// -/// Careful: If spats[0].off.line == TRUE and spats[0].off.off == 0 this +/// Careful: If spats[0].off.line == true and spats[0].off.off == 0 this /// makes the movement linewise without moving the match position. /// /// @param dirc if 0: use previous dir. /// @param pat NULL or empty : use previous string. -/// @param options if TRUE and -/// SEARCH_REV == TRUE : go in reverse of previous dir. -/// SEARCH_ECHO == TRUE : echo the search command and handle options -/// SEARCH_MSG == TRUE : may give error message -/// SEARCH_OPT == TRUE : interpret optional flags -/// SEARCH_HIS == TRUE : put search pattern in history -/// SEARCH_NOOF == TRUE : don't add offset to position -/// SEARCH_MARK == TRUE : set previous context mark -/// SEARCH_KEEP == TRUE : keep previous search pattern -/// SEARCH_START == TRUE : accept match at curpos itself -/// SEARCH_PEEK == TRUE : check for typed char, cancel search +/// @param options if true and +/// SEARCH_REV == true : go in reverse of previous dir. +/// SEARCH_ECHO == true : echo the search command and handle options +/// SEARCH_MSG == true : may give error message +/// SEARCH_OPT == true : interpret optional flags +/// SEARCH_HIS == true : put search pattern in history +/// SEARCH_NOOF == true : don't add offset to position +/// SEARCH_MARK == true : set previous context mark +/// SEARCH_KEEP == true : keep previous search pattern +/// SEARCH_START == true : accept match at curpos itself +/// SEARCH_PEEK == true : check for typed char, cancel search /// @param oap can be NULL /// @param dirc '/' or '?' /// @param search_delim delimiter for search, e.g. '%' in s%regex%replacement @@ -1036,7 +1036,7 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char_u *pat, long count, char_u *p; long c; char_u *dircp; - char_u *strcopy = NULL; + char *strcopy = NULL; char_u *ps; char_u *msgbuf = NULL; size_t len; @@ -1091,7 +1091,7 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char_u *pat, long count, * Turn 'hlsearch' highlighting back on. */ if (no_hlsearch && !(options & SEARCH_KEEP)) { - redraw_all_later(SOME_VALID); + redraw_all_later(UPD_SOME_VALID); set_no_hlsearch(false); } @@ -1123,20 +1123,20 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char_u *pat, long count, * Find end of regular expression. * If there is a matching '/' or '?', toss it. */ - ps = strcopy; - p = skip_regexp(pat, search_delim, p_magic, &strcopy); - if (strcopy != ps) { + ps = (char_u *)strcopy; + p = (char_u *)skip_regexp((char *)pat, search_delim, p_magic, &strcopy); + if (strcopy != (char *)ps) { // made a copy of "pat" to change "\?" to "?" searchcmdlen += (int)(STRLEN(pat) - STRLEN(strcopy)); - pat = strcopy; - searchstr = strcopy; + pat = (char_u *)strcopy; + searchstr = (char_u *)strcopy; } if (*p == search_delim) { dircp = p; // remember where we put the NUL *p++ = NUL; } - spats[0].off.line = FALSE; - spats[0].off.end = FALSE; + spats[0].off.line = false; + spats[0].off.end = false; spats[0].off.off = 0; // Check for a line offset or a character offset. // For get_address (echo off) we don't check for a character @@ -1160,9 +1160,9 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char_u *pat, long count, } else { // single '+' spats[0].off.off = 1; } - ++p; + p++; while (ascii_isdigit(*p)) { // skip number - ++p; + p++; } } @@ -1261,7 +1261,7 @@ 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 = reverse_text(trunc != NULL ? trunc : msgbuf); + char_u *r = (char_u *)reverse_text(trunc != NULL ? (char *)trunc : (char *)msgbuf); xfree(msgbuf); msgbuf = r; // move reversed text to beginning of buffer @@ -1296,7 +1296,7 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char_u *pat, long count, */ if (!spats[0].off.line && spats[0].off.off && pos.col < MAXCOL - 2) { if (spats[0].off.off > 0) { - for (c = spats[0].off.off; c; --c) { + for (c = spats[0].off.off; c; c--) { if (decl(&pos) == -1) { break; } @@ -1306,7 +1306,7 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char_u *pat, long count, pos.col = MAXCOL; } } else { - for (c = spats[0].off.off; c; ++c) { + for (c = spats[0].off.off; c; c++) { if (incl(&pos) == -1) { break; } @@ -1426,14 +1426,14 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char_u *pat, long count, emsg(_("E386: Expected '?' or '/' after ';'")); goto end_do_search; } - ++pat; + pat++; } if (options & SEARCH_MARK) { setpcmark(); } curwin->w_cursor = pos; - curwin->w_set_curswant = TRUE; + curwin->w_set_curswant = true; end_do_search: if ((options & SEARCH_KEEP) || (cmdmod.cmod_flags & CMOD_KEEPPATTERNS)) { @@ -1497,16 +1497,15 @@ int search_for_exact_line(buf_T *buf, pos_T *pos, Direction dir, char_u *pat) // when adding lines the matching line may be empty but it is not // ignored because we are interested in the next line -- Acevedo - if ((compl_cont_status & CONT_ADDING) - && !(compl_cont_status & CONT_SOL)) { + if (compl_status_adding() && !compl_status_sol()) { if (mb_strcmp_ic((bool)p_ic, (const char *)p, (const char *)pat) == 0) { return OK; } } else if (*p != NUL) { // Ignore empty lines. // Expanding lines or words. - assert(compl_length >= 0); - if ((p_ic ? mb_strnicmp(p, pat, (size_t)compl_length) - : STRNCMP(p, pat, compl_length)) == 0) { + assert(ins_compl_len() >= 0); + if ((p_ic ? mb_strnicmp(p, pat, (size_t)ins_compl_len()) + : STRNCMP(p, pat, ins_compl_len())) == 0) { return OK; } } @@ -1518,12 +1517,10 @@ int search_for_exact_line(buf_T *buf, pos_T *pos, Direction dir, char_u *pat) * Character Searches */ -/* - * Search for a character in a line. If "t_cmd" is FALSE, move to the - * position of the character, otherwise move to just before the char. - * Do this "cap->count1" times. - * Return FAIL or OK. - */ +/// Search for a character in a line. If "t_cmd" is false, move to the +/// position of the character, otherwise move to just before the char. +/// Do this "cap->count1" times. +/// Return FAIL or OK. int searchc(cmdarg_T *cap, int t_cmd) FUNC_ATTR_NONNULL_ALL { @@ -1695,34 +1692,34 @@ static bool find_rawstring_end(char_u *linep, pos_T *startpos, pos_T *endpos) static void find_mps_values(int *initc, int *findc, bool *backwards, bool switchit) FUNC_ATTR_NONNULL_ALL { - char_u *ptr = curbuf->b_p_mps; + char *ptr = curbuf->b_p_mps; while (*ptr != NUL) { - if (utf_ptr2char((char *)ptr) == *initc) { + if (utf_ptr2char(ptr) == *initc) { if (switchit) { *findc = *initc; - *initc = utf_ptr2char((char *)ptr + utfc_ptr2len((char *)ptr) + 1); + *initc = utf_ptr2char(ptr + utfc_ptr2len(ptr) + 1); *backwards = true; } else { - *findc = utf_ptr2char((char *)ptr + utfc_ptr2len((char *)ptr) + 1); + *findc = utf_ptr2char(ptr + utfc_ptr2len(ptr) + 1); *backwards = false; } return; } - char_u *prev = ptr; - ptr += utfc_ptr2len((char *)ptr) + 1; - if (utf_ptr2char((char *)ptr) == *initc) { + char *prev = ptr; + ptr += utfc_ptr2len(ptr) + 1; + if (utf_ptr2char(ptr) == *initc) { if (switchit) { *findc = *initc; - *initc = utf_ptr2char((char *)prev); + *initc = utf_ptr2char(prev); *backwards = false; } else { - *findc = utf_ptr2char((char *)prev); + *findc = utf_ptr2char(prev); *backwards = true; } return; } - ptr += utfc_ptr2len((char *)ptr); + ptr += utfc_ptr2len(ptr); if (*ptr == ',') { ptr++; } @@ -1866,7 +1863,7 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) * the line. */ if (linep[pos.col] == NUL && pos.col) { - --pos.col; + pos.col--; } for (;;) { initc = utf_ptr2char((char *)linep + pos.col); @@ -1980,7 +1977,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(linep); + comment_col = check_linecomment((char *)linep); } if (lisp && comment_col != MAXCOL && pos.col > (colnr_T)comment_col) { lispcomm = true; // find match inside this comment @@ -2000,7 +1997,7 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) if (pos.lnum == 1) { // start of file break; } - --pos.lnum; + pos.lnum--; if (maxtravel > 0 && ++traveled > maxtravel) { break; @@ -2013,7 +2010,7 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) // Check if this line contains a single-line comment if (comment_dir || lisp) { - comment_col = check_linecomment(linep); + comment_col = check_linecomment((char *)linep); } // skip comment if (lisp && comment_col != MAXCOL) { @@ -2035,7 +2032,7 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) || lispcomm) { break; } - ++pos.lnum; + pos.lnum++; if (maxtravel && traveled++ > maxtravel) { break; @@ -2046,7 +2043,7 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) do_quotes = -1; line_breakcheck(); if (lisp) { // find comment pos in new line - comment_col = check_linecomment(linep); + comment_col = check_linecomment((char *)linep); } } else { pos.col += utfc_ptr2len((char *)linep + pos.col); @@ -2130,16 +2127,16 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) * Watch out for "\\". */ at_start = do_quotes; - for (ptr = linep; *ptr; ++ptr) { + for (ptr = linep; *ptr; ptr++) { if (ptr == linep + pos.col + backwards) { at_start = (do_quotes & 1); } if (*ptr == '"' && (ptr == linep || ptr[-1] != '\'' || ptr[1] != '\'')) { - ++do_quotes; + do_quotes++; } if (*ptr == '\\' && ptr[1] != NUL) { - ++ptr; + ptr++; } } do_quotes &= 1; // result is 1 with even number of quotes @@ -2210,7 +2207,7 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) if (do_quotes) { int col; - for (col = pos.col - 1; col >= 0; --col) { + for (col = pos.col - 1; col >= 0; col--) { if (linep[col] != '\\') { break; } @@ -2304,15 +2301,15 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) /// Check if line[] contains a / / comment. /// @returns MAXCOL if not, otherwise return the column. -int check_linecomment(const char_u *line) +int check_linecomment(const char *line) { - const char_u *p = line; // scan from start + const char *p = line; // scan from start // skip Lispish one-line comments if (curbuf->b_p_lisp) { if (vim_strchr((char *)p, ';') != NULL) { // there may be comments bool in_str = false; // inside of string - while ((p = (char_u *)strpbrk((char *)p, "\";")) != NULL) { + while ((p = strpbrk((char *)p, "\";")) != NULL) { if (*p == '"') { if (in_str) { if (*(p - 1) != '\\') { // skip escaped quote @@ -2325,7 +2322,7 @@ int check_linecomment(const char_u *line) } } else if (!in_str && ((p - line) < 2 || (*(p - 1) != '\\' && *(p - 2) != '#')) - && !is_pos_in_string(line, (colnr_T)(p - line))) { + && !is_pos_in_string((char_u *)line, (colnr_T)(p - line))) { break; // found! } p++; @@ -2334,15 +2331,15 @@ int check_linecomment(const char_u *line) p = NULL; } } else { - while ((p = (char_u *)vim_strchr((char *)p, '/')) != NULL) { + while ((p = vim_strchr((char *)p, '/')) != NULL) { // Accept a double /, unless it's preceded with * and followed by *, // because * / / * is an end and start of a C comment. Only // accept the position if it is not inside a string. if (p[1] == '/' && (p == line || p[-1] != '*' || p[2] != '*') - && !is_pos_in_string(line, (colnr_T)(p - line))) { + && !is_pos_in_string((char_u *)line, (colnr_T)(p - line))) { break; } - ++p; + p++; } } @@ -2375,7 +2372,7 @@ void showmatch(int c) * Only show match for chars in the 'matchpairs' option. */ // 'matchpairs' is "x:y,x:y" - for (p = curbuf->b_p_mps; *p != NUL; p++) { + for (p = (char_u *)curbuf->b_p_mps; *p != NUL; p++) { if (utf_ptr2char((char *)p) == c && (curwin->w_p_rl ^ p_ri)) { break; } @@ -2412,7 +2409,7 @@ void showmatch(int c) dollar_vcol = -1; } curwin->w_virtcol++; // do display ')' just before "$" - update_screen(VALID); // show the new char first + update_screen(UPD_VALID); // show the new char first save_dollar_vcol = dollar_vcol; save_state = State; @@ -2421,7 +2418,7 @@ void showmatch(int c) curwin->w_cursor = mpos; // move to matching char *so = 0; // don't use 'scrolloff' here *siso = 0; // don't use 'sidescrolloff' here - showruler(false); + show_cursor_info(false); setcursor(); ui_flush(); // Restore dollar_vcol(), because setcursor() may call curs_rows() @@ -2447,1816 +2444,6 @@ void showmatch(int c) } } -// Find the start of the next sentence, searching in the direction specified -// by the "dir" argument. The cursor is positioned on the start of the next -// sentence when found. If the next sentence is found, return OK. Return FAIL -// otherwise. See ":h sentence" for the precise definition of a "sentence" -// text object. -int findsent(Direction dir, long count) -{ - pos_T pos, tpos; - int c; - int (*func)(pos_T *); - bool noskip = false; // do not skip blanks - - pos = curwin->w_cursor; - if (dir == FORWARD) { - func = incl; - } else { - func = decl; - } - - while (count--) { - const pos_T prev_pos = pos; - - // if on an empty line, skip up to a non-empty line - if (gchar_pos(&pos) == NUL) { - do { - if ((*func)(&pos) == -1) { - break; - } - } while (gchar_pos(&pos) == NUL); - if (dir == FORWARD) { - goto found; - } - // if on the start of a paragraph or a section and searching forward, - // go to the next line - } else if (dir == FORWARD && pos.col == 0 - && startPS(pos.lnum, NUL, false)) { - if (pos.lnum == curbuf->b_ml.ml_line_count) { - return FAIL; - } - pos.lnum++; - goto found; - } else if (dir == BACKWARD) { - decl(&pos); - } - - // go back to the previous non-white non-punctuation character - bool found_dot = false; - while (c = gchar_pos(&pos), ascii_iswhite(c) - || vim_strchr(".!?)]\"'", c) != NULL) { - tpos = pos; - if (decl(&tpos) == -1 || (LINEEMPTY(tpos.lnum) && dir == FORWARD)) { - break; - } - if (found_dot) { - break; - } - if (vim_strchr(".!?", c) != NULL) { - found_dot = true; - } - if (vim_strchr(")]\"'", c) != NULL - && vim_strchr(".!?)]\"'", gchar_pos(&tpos)) == NULL) { - break; - } - decl(&pos); - } - - // remember the line where the search started - const int startlnum = pos.lnum; - const bool cpo_J = vim_strchr(p_cpo, CPO_ENDOFSENT) != NULL; - - for (;;) { // find end of sentence - c = gchar_pos(&pos); - if (c == NUL || (pos.col == 0 && startPS(pos.lnum, NUL, FALSE))) { - if (dir == BACKWARD && pos.lnum != startlnum) { - ++pos.lnum; - } - break; - } - if (c == '.' || c == '!' || c == '?') { - tpos = pos; - do { - if ((c = inc(&tpos)) == -1) { - break; - } - } while (vim_strchr(")]\"'", c = gchar_pos(&tpos)) - != NULL); - if (c == -1 || (!cpo_J && (c == ' ' || c == '\t')) || c == NUL - || (cpo_J && (c == ' ' && inc(&tpos) >= 0 - && gchar_pos(&tpos) == ' '))) { - pos = tpos; - if (gchar_pos(&pos) == NUL) { // skip NUL at EOL - inc(&pos); - } - break; - } - } - if ((*func)(&pos) == -1) { - if (count) { - return FAIL; - } - noskip = true; - break; - } - } -found: - // skip white space - while (!noskip && ((c = gchar_pos(&pos)) == ' ' || c == '\t')) { - if (incl(&pos) == -1) { - break; - } - } - - if (equalpos(prev_pos, pos)) { - // didn't actually move, advance one character and try again - if ((*func)(&pos) == -1) { - if (count) { - return FAIL; - } - break; - } - count++; - } - } - - setpcmark(); - curwin->w_cursor = pos; - return OK; -} - -/// Find the next paragraph or section in direction 'dir'. -/// Paragraphs are currently supposed to be separated by empty lines. -/// If 'what' is NUL we go to the next paragraph. -/// If 'what' is '{' or '}' we go to the next section. -/// If 'both' is TRUE also stop at '}'. -/// -/// @param pincl Return: true if last char is to be included -/// -/// @return TRUE if the next paragraph or section was found. -bool findpar(bool *pincl, int dir, long count, int what, int both) -{ - linenr_T curr; - bool did_skip; // true after separating lines have been skipped - bool first; // true on first line - linenr_T fold_first; // first line of a closed fold - linenr_T fold_last; // last line of a closed fold - bool fold_skipped; // true if a closed fold was skipped this - // iteration - - curr = curwin->w_cursor.lnum; - - while (count--) { - did_skip = false; - for (first = true;; first = false) { - if (*ml_get(curr) != NUL) { - did_skip = true; - } - - // skip folded lines - fold_skipped = false; - if (first && hasFolding(curr, &fold_first, &fold_last)) { - curr = ((dir > 0) ? fold_last : fold_first) + dir; - fold_skipped = true; - } - - if (!first && did_skip && startPS(curr, what, both)) { - break; - } - - if (fold_skipped) { - curr -= dir; - } - if ((curr += dir) < 1 || curr > curbuf->b_ml.ml_line_count) { - if (count) { - return false; - } - curr -= dir; - break; - } - } - } - setpcmark(); - if (both && *ml_get(curr) == '}') { // include line with '}' - ++curr; - } - curwin->w_cursor.lnum = curr; - if (curr == curbuf->b_ml.ml_line_count && what != '}') { - char_u *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) { - curwin->w_cursor.col--; - curwin->w_cursor.col -= utf_head_off(line, line + curwin->w_cursor.col); - *pincl = true; - } - } else { - curwin->w_cursor.col = 0; - } - return true; -} - -/* - * check if the string 's' is a nroff macro that is in option 'opt' - */ -static int inmacro(char_u *opt, char_u *s) -{ - char_u *macro; - - for (macro = opt; macro[0]; macro++) { - // Accept two characters in the option being equal to two characters - // in the line. A space in the option matches with a space in the - // line or the line having ended. - if ((macro[0] == s[0] - || (macro[0] == ' ' - && (s[0] == NUL || s[0] == ' '))) - && (macro[1] == s[1] - || ((macro[1] == NUL || macro[1] == ' ') - && (s[0] == NUL || s[1] == NUL || s[1] == ' ')))) { - break; - } - ++macro; - if (macro[0] == NUL) { - break; - } - } - return macro[0] != NUL; -} - -/* - * startPS: return TRUE if line 'lnum' is the start of a section or paragraph. - * If 'para' is '{' or '}' only check for sections. - * If 'both' is TRUE also stop at '}' - */ -int startPS(linenr_T lnum, int para, int both) -{ - char_u *s; - - s = ml_get(lnum); - if (*s == para || *s == '\f' || (both && *s == '}')) { - return true; - } - if (*s == '.' && (inmacro(p_sections, s + 1) - || (!para && inmacro(p_para, s + 1)))) { - return true; - } - return false; -} - -/* - * The following routines do the word searches performed by the 'w', 'W', - * 'b', 'B', 'e', and 'E' commands. - */ - -/* - * To perform these searches, characters are placed into one of three - * classes, and transitions between classes determine word boundaries. - * - * The classes are: - * - * 0 - white space - * 1 - punctuation - * 2 or higher - keyword characters (letters, digits and underscore) - */ - -static int cls_bigword; // TRUE for "W", "B" or "E" - -/* - * cls() - returns the class of character at curwin->w_cursor - * - * If a 'W', 'B', or 'E' motion is being done (cls_bigword == TRUE), chars - * from class 2 and higher are reported as class 1 since only white space - * boundaries are of interest. - */ -static int cls(void) -{ - int c; - - c = gchar_cursor(); - if (c == ' ' || c == '\t' || c == NUL) { - return 0; - } - - c = utf_class(c); - - // If cls_bigword is TRUE, report all non-blanks as class 1. - if (c != 0 && cls_bigword) { - return 1; - } - return c; -} - -/// fwd_word(count, type, eol) - move forward one word -/// -/// @return FAIL if the cursor was already at the end of the file. -/// If eol is TRUE, last word stops at end of line (for operators). -/// -/// @param bigword "W", "E" or "B" -int fwd_word(long count, int bigword, int eol) -{ - int sclass; // starting class - int i; - int last_line; - - curwin->w_cursor.coladd = 0; - cls_bigword = bigword; - while (--count >= 0) { - // When inside a range of folded lines, move to the last char of the - // last line. - if (hasFolding(curwin->w_cursor.lnum, NULL, &curwin->w_cursor.lnum)) { - coladvance(MAXCOL); - } - sclass = cls(); - - /* - * We always move at least one character, unless on the last - * character in the buffer. - */ - last_line = (curwin->w_cursor.lnum == curbuf->b_ml.ml_line_count); - i = inc_cursor(); - if (i == -1 || (i >= 1 && last_line)) { // started at last char in file - return FAIL; - } - if (i >= 1 && eol && count == 0) { // started at last char in line - return OK; - } - - /* - * Go one char past end of current word (if any) - */ - if (sclass != 0) { - while (cls() == sclass) { - i = inc_cursor(); - if (i == -1 || (i >= 1 && eol && count == 0)) { - return OK; - } - } - } - - /* - * go to next non-white - */ - while (cls() == 0) { - /* - * We'll stop if we land on a blank line - */ - if (curwin->w_cursor.col == 0 && *get_cursor_line_ptr() == NUL) { - break; - } - - i = inc_cursor(); - if (i == -1 || (i >= 1 && eol && count == 0)) { - return OK; - } - } - } - return OK; -} - -/* - * bck_word() - move backward 'count' words - * - * If stop is TRUE and we are already on the start of a word, move one less. - * - * Returns FAIL if top of the file was reached. - */ -int bck_word(long count, int bigword, int stop) -{ - int sclass; // starting class - - curwin->w_cursor.coladd = 0; - cls_bigword = bigword; - while (--count >= 0) { - /* When inside a range of folded lines, move to the first char of the - * first line. */ - if (hasFolding(curwin->w_cursor.lnum, &curwin->w_cursor.lnum, NULL)) { - curwin->w_cursor.col = 0; - } - sclass = cls(); - if (dec_cursor() == -1) { // started at start of file - return FAIL; - } - - if (!stop || sclass == cls() || sclass == 0) { - /* - * Skip white space before the word. - * Stop on an empty line. - */ - while (cls() == 0) { - if (curwin->w_cursor.col == 0 - && LINEEMPTY(curwin->w_cursor.lnum)) { - goto finished; - } - if (dec_cursor() == -1) { // hit start of file, stop here - return OK; - } - } - - /* - * Move backward to start of this word. - */ - if (skip_chars(cls(), BACKWARD)) { - return OK; - } - } - - inc_cursor(); // overshot - forward one -finished: - stop = FALSE; - } - return OK; -} - -/* - * end_word() - move to the end of the word - * - * There is an apparent bug in the 'e' motion of the real vi. At least on the - * System V Release 3 version for the 80386. Unlike 'b' and 'w', the 'e' - * motion crosses blank lines. When the real vi crosses a blank line in an - * 'e' motion, the cursor is placed on the FIRST character of the next - * non-blank line. The 'E' command, however, works correctly. Since this - * appears to be a bug, I have not duplicated it here. - * - * Returns FAIL if end of the file was reached. - * - * If stop is TRUE and we are already on the end of a word, move one less. - * If empty is TRUE stop on an empty line. - */ -int end_word(long count, int bigword, int stop, int empty) -{ - int sclass; // starting class - - curwin->w_cursor.coladd = 0; - cls_bigword = bigword; - while (--count >= 0) { - /* When inside a range of folded lines, move to the last char of the - * last line. */ - if (hasFolding(curwin->w_cursor.lnum, NULL, &curwin->w_cursor.lnum)) { - coladvance(MAXCOL); - } - sclass = cls(); - if (inc_cursor() == -1) { - return FAIL; - } - - /* - * If we're in the middle of a word, we just have to move to the end - * of it. - */ - if (cls() == sclass && sclass != 0) { - /* - * Move forward to end of the current word - */ - if (skip_chars(sclass, FORWARD)) { - return FAIL; - } - } else if (!stop || sclass == 0) { - /* - * We were at the end of a word. Go to the end of the next word. - * First skip white space, if 'empty' is TRUE, stop at empty line. - */ - while (cls() == 0) { - if (empty && curwin->w_cursor.col == 0 - && LINEEMPTY(curwin->w_cursor.lnum)) { - goto finished; - } - if (inc_cursor() == -1) { // hit end of file, stop here - return FAIL; - } - } - - /* - * Move forward to the end of this word. - */ - if (skip_chars(cls(), FORWARD)) { - return FAIL; - } - } - dec_cursor(); // overshot - one char backward -finished: - stop = FALSE; // we move only one word less - } - return OK; -} - -/// Move back to the end of the word. -/// -/// @param bigword TRUE for "B" -/// @param eol if true, then stop at end of line. -/// -/// @return FAIL if start of the file was reached. -int bckend_word(long count, int bigword, bool eol) -{ - int sclass; // starting class - int i; - - curwin->w_cursor.coladd = 0; - cls_bigword = bigword; - while (--count >= 0) { - sclass = cls(); - if ((i = dec_cursor()) == -1) { - return FAIL; - } - if (eol && i == 1) { - return OK; - } - - /* - * Move backward to before the start of this word. - */ - if (sclass != 0) { - while (cls() == sclass) { - if ((i = dec_cursor()) == -1 || (eol && i == 1)) { - return OK; - } - } - } - - /* - * Move backward to end of the previous word - */ - while (cls() == 0) { - if (curwin->w_cursor.col == 0 && LINEEMPTY(curwin->w_cursor.lnum)) { - break; - } - if ((i = dec_cursor()) == -1 || (eol && i == 1)) { - return OK; - } - } - } - return OK; -} - -/// Skip a row of characters of the same class. -/// -/// @return true when end-of-file reached, false otherwise. -static bool skip_chars(int cclass, int dir) -{ - while (cls() == cclass) { - if ((dir == FORWARD ? inc_cursor() : dec_cursor()) == -1) { - return true; - } - } - return false; -} - -/* - * Go back to the start of the word or the start of white space - */ -static void back_in_line(void) -{ - int sclass; // starting class - - sclass = cls(); - for (;;) { - if (curwin->w_cursor.col == 0) { // stop at start of line - break; - } - dec_cursor(); - if (cls() != sclass) { // stop at start of word - inc_cursor(); - break; - } - } -} - -static void find_first_blank(pos_T *posp) -{ - int c; - - while (decl(posp) != -1) { - c = gchar_pos(posp); - if (!ascii_iswhite(c)) { - incl(posp); - break; - } - } -} - -/// Skip count/2 sentences and count/2 separating white spaces. -/// -/// @param at_start_sent cursor is at start of sentence -static void findsent_forward(long count, bool at_start_sent) -{ - while (count--) { - findsent(FORWARD, 1L); - if (at_start_sent) { - find_first_blank(&curwin->w_cursor); - } - if (count == 0 || at_start_sent) { - decl(&curwin->w_cursor); - } - at_start_sent = !at_start_sent; - } -} - -/// Find word under cursor, cursor at end. -/// Used while an operator is pending, and in Visual mode. -/// -/// @param include TRUE: include word and white space -/// @param bigword FALSE == word, TRUE == WORD -int current_word(oparg_T *oap, long count, int include, int bigword) -{ - pos_T start_pos; - pos_T pos; - bool inclusive = true; - int include_white = FALSE; - - cls_bigword = bigword; - clearpos(&start_pos); - - // Correct cursor when 'selection' is exclusive - if (VIsual_active && *p_sel == 'e' && lt(VIsual, curwin->w_cursor)) { - dec_cursor(); - } - - /* - * When Visual mode is not active, or when the VIsual area is only one - * character, select the word and/or white space under the cursor. - */ - if (!VIsual_active || equalpos(curwin->w_cursor, VIsual)) { - /* - * Go to start of current word or white space. - */ - back_in_line(); - start_pos = curwin->w_cursor; - - /* - * If the start is on white space, and white space should be included - * (" word"), or start is not on white space, and white space should - * not be included ("word"), find end of word. - */ - if ((cls() == 0) == include) { - if (end_word(1L, bigword, TRUE, TRUE) == FAIL) { - return FAIL; - } - } else { - /* - * If the start is not on white space, and white space should be - * included ("word "), or start is on white space and white - * space should not be included (" "), find start of word. - * If we end up in the first column of the next line (single char - * word) back up to end of the line. - */ - fwd_word(1L, bigword, TRUE); - if (curwin->w_cursor.col == 0) { - decl(&curwin->w_cursor); - } else { - oneleft(); - } - - if (include) { - include_white = TRUE; - } - } - - if (VIsual_active) { - // should do something when inclusive == false ! - VIsual = start_pos; - redraw_curbuf_later(INVERTED); // update the inversion - } else { - oap->start = start_pos; - oap->motion_type = kMTCharWise; - } - --count; - } - - /* - * When count is still > 0, extend with more objects. - */ - while (count > 0) { - inclusive = true; - if (VIsual_active && lt(curwin->w_cursor, VIsual)) { - /* - * In Visual mode, with cursor at start: move cursor back. - */ - if (decl(&curwin->w_cursor) == -1) { - return FAIL; - } - if (include != (cls() != 0)) { - if (bck_word(1L, bigword, TRUE) == FAIL) { - return FAIL; - } - } else { - if (bckend_word(1L, bigword, true) == FAIL) { - return FAIL; - } - (void)incl(&curwin->w_cursor); - } - } else { - /* - * Move cursor forward one word and/or white area. - */ - if (incl(&curwin->w_cursor) == -1) { - return FAIL; - } - if (include != (cls() == 0)) { - if (fwd_word(1L, bigword, TRUE) == FAIL && count > 1) { - return FAIL; - } - /* - * If end is just past a new-line, we don't want to include - * the first character on the line. - * Put cursor on last char of white. - */ - if (oneleft() == FAIL) { - inclusive = false; - } - } else { - if (end_word(1L, bigword, TRUE, TRUE) == FAIL) { - return FAIL; - } - } - } - --count; - } - - if (include_white && (cls() != 0 - || (curwin->w_cursor.col == 0 && !inclusive))) { - /* - * If we don't include white space at the end, move the start - * to include some white space there. This makes "daw" work - * better on the last word in a sentence (and "2daw" on last-but-one - * word). Also when "2daw" deletes "word." at the end of the line - * (cursor is at start of next line). - * But don't delete white space at start of line (indent). - */ - pos = curwin->w_cursor; // save cursor position - curwin->w_cursor = start_pos; - if (oneleft() == OK) { - back_in_line(); - if (cls() == 0 && curwin->w_cursor.col > 0) { - if (VIsual_active) { - VIsual = curwin->w_cursor; - } else { - oap->start = curwin->w_cursor; - } - } - } - curwin->w_cursor = pos; // put cursor back at end - } - - if (VIsual_active) { - if (*p_sel == 'e' && inclusive && ltoreq(VIsual, curwin->w_cursor)) { - inc_cursor(); - } - if (VIsual_mode == 'V') { - VIsual_mode = 'v'; - redraw_cmdline = true; // show mode later - } - } else { - oap->inclusive = inclusive; - } - - return OK; -} - -/* - * Find sentence(s) under the cursor, cursor at end. - * When Visual active, extend it by one or more sentences. - */ -int current_sent(oparg_T *oap, long count, int include) -{ - pos_T start_pos; - pos_T pos; - bool start_blank; - int c; - bool at_start_sent; - long ncount; - - start_pos = curwin->w_cursor; - pos = start_pos; - findsent(FORWARD, 1L); // Find start of next sentence. - - /* - * When the Visual area is bigger than one character: Extend it. - */ - if (VIsual_active && !equalpos(start_pos, VIsual)) { -extend: - if (lt(start_pos, VIsual)) { - /* - * Cursor at start of Visual area. - * Find out where we are: - * - in the white space before a sentence - * - in a sentence or just after it - * - at the start of a sentence - */ - at_start_sent = true; - decl(&pos); - while (lt(pos, curwin->w_cursor)) { - c = gchar_pos(&pos); - if (!ascii_iswhite(c)) { - at_start_sent = false; - break; - } - incl(&pos); - } - if (!at_start_sent) { - findsent(BACKWARD, 1L); - if (equalpos(curwin->w_cursor, start_pos)) { - at_start_sent = true; // exactly at start of sentence - } else { - // inside a sentence, go to its end (start of next) - findsent(FORWARD, 1L); - } - } - if (include) { // "as" gets twice as much as "is" - count *= 2; - } - while (count--) { - if (at_start_sent) { - find_first_blank(&curwin->w_cursor); - } - c = gchar_cursor(); - if (!at_start_sent || (!include && !ascii_iswhite(c))) { - findsent(BACKWARD, 1L); - } - at_start_sent = !at_start_sent; - } - } else { - /* - * Cursor at end of Visual area. - * Find out where we are: - * - just before a sentence - * - just before or in the white space before a sentence - * - in a sentence - */ - incl(&pos); - at_start_sent = true; - if (!equalpos(pos, curwin->w_cursor)) { // not just before a sentence - at_start_sent = false; - while (lt(pos, curwin->w_cursor)) { - c = gchar_pos(&pos); - if (!ascii_iswhite(c)) { - at_start_sent = true; - break; - } - incl(&pos); - } - if (at_start_sent) { // in the sentence - findsent(BACKWARD, 1L); - } else { // in/before white before a sentence - curwin->w_cursor = start_pos; - } - } - - if (include) { // "as" gets twice as much as "is" - count *= 2; - } - findsent_forward(count, at_start_sent); - if (*p_sel == 'e') { - ++curwin->w_cursor.col; - } - } - return OK; - } - - /* - * If the cursor started on a blank, check if it is just before the start - * of the next sentence. - */ - while (c = gchar_pos(&pos), ascii_iswhite(c)) { - incl(&pos); - } - if (equalpos(pos, curwin->w_cursor)) { - start_blank = true; - find_first_blank(&start_pos); // go back to first blank - } else { - start_blank = false; - findsent(BACKWARD, 1L); - start_pos = curwin->w_cursor; - } - if (include) { - ncount = count * 2; - } else { - ncount = count; - if (start_blank) { - --ncount; - } - } - if (ncount > 0) { - findsent_forward(ncount, true); - } else { - decl(&curwin->w_cursor); - } - - if (include) { - /* - * If the blank in front of the sentence is included, exclude the - * blanks at the end of the sentence, go back to the first blank. - * If there are no trailing blanks, try to include leading blanks. - */ - if (start_blank) { - find_first_blank(&curwin->w_cursor); - c = gchar_pos(&curwin->w_cursor); - if (ascii_iswhite(c)) { - decl(&curwin->w_cursor); - } - } else if (c = gchar_cursor(), !ascii_iswhite(c)) { - find_first_blank(&start_pos); - } - } - - if (VIsual_active) { - // Avoid getting stuck with "is" on a single space before a sentence. - if (equalpos(start_pos, curwin->w_cursor)) { - goto extend; - } - if (*p_sel == 'e') { - ++curwin->w_cursor.col; - } - VIsual = start_pos; - VIsual_mode = 'v'; - redraw_cmdline = true; // show mode later - redraw_curbuf_later(INVERTED); // update the inversion - } else { - // include a newline after the sentence, if there is one - if (incl(&curwin->w_cursor) == -1) { - oap->inclusive = true; - } else { - oap->inclusive = false; - } - oap->start = start_pos; - oap->motion_type = kMTCharWise; - } - return OK; -} - -/// Find block under the cursor, cursor at end. -/// "what" and "other" are two matching parenthesis/brace/etc. -/// -/// @param include TRUE == include white space -/// @param what '(', '{', etc. -/// @param other ')', '}', etc. -int current_block(oparg_T *oap, long count, int include, int what, int other) -{ - pos_T old_pos; - pos_T *pos = NULL; - pos_T start_pos; - pos_T *end_pos; - pos_T old_start, old_end; - char *save_cpo; - bool sol = false; // '{' at start of line - - old_pos = curwin->w_cursor; - old_end = curwin->w_cursor; // remember where we started - old_start = old_end; - - /* - * If we start on '(', '{', ')', '}', etc., use the whole block inclusive. - */ - if (!VIsual_active || equalpos(VIsual, curwin->w_cursor)) { - setpcmark(); - if (what == '{') { // ignore indent - while (inindent(1)) { - if (inc_cursor() != 0) { - break; - } - } - } - if (gchar_cursor() == what) { - // cursor on '(' or '{', move cursor just after it - ++curwin->w_cursor.col; - } - } else if (lt(VIsual, curwin->w_cursor)) { - old_start = VIsual; - curwin->w_cursor = VIsual; // cursor at low end of Visual - } else { - old_end = VIsual; - } - - // Search backwards for unclosed '(', '{', etc.. - // Put this position in start_pos. - // Ignore quotes here. Keep the "M" flag in 'cpo', as that is what the - // user wants. - save_cpo = p_cpo; - p_cpo = vim_strchr(p_cpo, CPO_MATCHBSL) != NULL ? "%M" : "%"; - if ((pos = findmatch(NULL, what)) != NULL) { - while (count-- > 0) { - if ((pos = findmatch(NULL, what)) == NULL) { - break; - } - curwin->w_cursor = *pos; - start_pos = *pos; // the findmatch for end_pos will overwrite *pos - } - } else { - while (count-- > 0) { - if ((pos = findmatchlimit(NULL, what, FM_FORWARD, 0)) == NULL) { - break; - } - curwin->w_cursor = *pos; - start_pos = *pos; // the findmatch for end_pos will overwrite *pos - } - } - p_cpo = save_cpo; - - /* - * Search for matching ')', '}', etc. - * Put this position in curwin->w_cursor. - */ - if (pos == NULL || (end_pos = findmatch(NULL, other)) == NULL) { - curwin->w_cursor = old_pos; - return FAIL; - } - curwin->w_cursor = *end_pos; - - // Try to exclude the '(', '{', ')', '}', etc. when "include" is FALSE. - // If the ending '}', ')' or ']' is only preceded by indent, skip that - // indent. But only if the resulting area is not smaller than what we - // started with. - while (!include) { - incl(&start_pos); - sol = (curwin->w_cursor.col == 0); - decl(&curwin->w_cursor); - while (inindent(1)) { - sol = true; - if (decl(&curwin->w_cursor) != 0) { - break; - } - } - - // In Visual mode, when the resulting area is not bigger than what we - // started with, extend it to the next block, and then exclude again. - // Don't try to expand the area if the area is empty. - if (!lt(start_pos, old_start) && !lt(old_end, curwin->w_cursor) - && !equalpos(start_pos, curwin->w_cursor) - && VIsual_active) { - curwin->w_cursor = old_start; - decl(&curwin->w_cursor); - if ((pos = findmatch(NULL, what)) == NULL) { - curwin->w_cursor = old_pos; - return FAIL; - } - start_pos = *pos; - curwin->w_cursor = *pos; - if ((end_pos = findmatch(NULL, other)) == NULL) { - curwin->w_cursor = old_pos; - return FAIL; - } - curwin->w_cursor = *end_pos; - } else { - break; - } - } - - if (VIsual_active) { - if (*p_sel == 'e') { - inc(&curwin->w_cursor); - } - if (sol && gchar_cursor() != NUL) { - inc(&curwin->w_cursor); // include the line break - } - VIsual = start_pos; - VIsual_mode = 'v'; - redraw_curbuf_later(INVERTED); // update the inversion - showmode(); - } else { - oap->start = start_pos; - oap->motion_type = kMTCharWise; - oap->inclusive = false; - if (sol) { - incl(&curwin->w_cursor); - } else if (ltoreq(start_pos, curwin->w_cursor)) { - // Include the character under the cursor. - oap->inclusive = true; - } else { - // End is before the start (no text in between <>, [], etc.): don't - // operate on any text. - curwin->w_cursor = start_pos; - } - } - - return OK; -} - -/// @param end_tag when true, return true if the cursor is on "</aaa>". -/// -/// @return true if the cursor is on a "<aaa>" tag. Ignore "<aaa/>". -static bool in_html_tag(bool end_tag) -{ - char_u *line = get_cursor_line_ptr(); - char_u *p; - int c; - int lc = NUL; - pos_T pos; - - for (p = line + curwin->w_cursor.col; p > line;) { - if (*p == '<') { // find '<' under/before cursor - break; - } - MB_PTR_BACK(line, p); - if (*p == '>') { // find '>' before cursor - break; - } - } - if (*p != '<') { - return false; - } - - pos.lnum = curwin->w_cursor.lnum; - pos.col = (colnr_T)(p - line); - - MB_PTR_ADV(p); - if (end_tag) { - // check that there is a '/' after the '<' - return *p == '/'; - } - - // check that there is no '/' after the '<' - if (*p == '/') { - return false; - } - - // check that the matching '>' is not preceded by '/' - for (;;) { - if (inc(&pos) < 0) { - return false; - } - c = *ml_get_pos(&pos); - if (c == '>') { - break; - } - lc = c; - } - return lc != '/'; -} - -/// Find tag block under the cursor, cursor at end. -/// -/// @param include true == include white space -int current_tagblock(oparg_T *oap, long count_arg, bool include) -{ - long count = count_arg; - pos_T old_pos; - pos_T start_pos; - pos_T end_pos; - pos_T old_start, old_end; - char_u *p; - char_u *cp; - int len; - bool do_include = include; - bool save_p_ws = p_ws; - int retval = FAIL; - int is_inclusive = true; - - p_ws = false; - - old_pos = curwin->w_cursor; - old_end = curwin->w_cursor; // remember where we started - old_start = old_end; - if (!VIsual_active || *p_sel == 'e') { - decl(&old_end); // old_end is inclusive - } - /* - * If we start on "<aaa>" select that block. - */ - if (!VIsual_active || equalpos(VIsual, curwin->w_cursor)) { - setpcmark(); - - // ignore indent - while (inindent(1)) { - if (inc_cursor() != 0) { - break; - } - } - - if (in_html_tag(false)) { - // cursor on start tag, move to its '>' - while (*get_cursor_pos_ptr() != '>') { - if (inc_cursor() < 0) { - break; - } - } - } else if (in_html_tag(true)) { - // cursor on end tag, move to just before it - while (*get_cursor_pos_ptr() != '<') { - if (dec_cursor() < 0) { - break; - } - } - dec_cursor(); - old_end = curwin->w_cursor; - } - } else if (lt(VIsual, curwin->w_cursor)) { - old_start = VIsual; - curwin->w_cursor = VIsual; // cursor at low end of Visual - } else { - old_end = VIsual; - } - -again: - /* - * Search backwards for unclosed "<aaa>". - * Put this position in start_pos. - */ - for (long n = 0; n < count; n++) { - if (do_searchpair("<[^ \t>/!]\\+\\%(\\_s\\_[^>]\\{-}[^/]>\\|$\\|\\_s\\=>\\)", - "", - "</[^>]*>", BACKWARD, NULL, 0, - NULL, (linenr_T)0, 0L) <= 0) { - curwin->w_cursor = old_pos; - goto theend; - } - } - start_pos = curwin->w_cursor; - - /* - * Search for matching "</aaa>". First isolate the "aaa". - */ - inc_cursor(); - p = get_cursor_pos_ptr(); - for (cp = p; - *cp != NUL && *cp != '>' && !ascii_iswhite(*cp); - MB_PTR_ADV(cp)) {} - len = (int)(cp - p); - if (len == 0) { - curwin->w_cursor = old_pos; - goto theend; - } - const size_t spat_len = (size_t)len + 39; - char *const spat = xmalloc(spat_len); - const size_t epat_len = (size_t)len + 9; - char *const epat = xmalloc(epat_len); - snprintf(spat, spat_len, - "<%.*s\\>\\%%(\\_s\\_[^>]\\{-}\\_[^/]>\\|\\_s\\?>\\)\\c", len, p); - snprintf(epat, epat_len, "</%.*s>\\c", len, p); - - const int r = (int)do_searchpair(spat, "", epat, FORWARD, NULL, 0, NULL, (linenr_T)0, 0L); - - xfree(spat); - xfree(epat); - - if (r < 1 || lt(curwin->w_cursor, old_end)) { - // Can't find other end or it's before the previous end. Could be a - // HTML tag that doesn't have a matching end. Search backwards for - // another starting tag. - count = 1; - curwin->w_cursor = start_pos; - goto again; - } - - if (do_include) { - // Include up to the '>'. - while (*get_cursor_pos_ptr() != '>') { - if (inc_cursor() < 0) { - break; - } - } - } else { - char_u *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 - if (*c == '<' && !VIsual_active && curwin->w_cursor.col == 0) { - // do not decrement cursor - is_inclusive = false; - } else if (*c == '<') { - dec_cursor(); - } - } - end_pos = curwin->w_cursor; - - if (!do_include) { - // Exclude the start tag. - curwin->w_cursor = start_pos; - while (inc_cursor() >= 0) { - if (*get_cursor_pos_ptr() == '>') { - inc_cursor(); - start_pos = curwin->w_cursor; - break; - } - } - curwin->w_cursor = end_pos; - - // If we are in Visual mode and now have the same text as before set - // "do_include" and try again. - if (VIsual_active - && equalpos(start_pos, old_start) - && equalpos(end_pos, old_end)) { - do_include = true; - curwin->w_cursor = old_start; - count = count_arg; - goto again; - } - } - - if (VIsual_active) { - // If the end is before the start there is no text between tags, select - // the char under the cursor. - if (lt(end_pos, start_pos)) { - curwin->w_cursor = start_pos; - } else if (*p_sel == 'e') { - inc_cursor(); - } - VIsual = start_pos; - VIsual_mode = 'v'; - redraw_curbuf_later(INVERTED); // update the inversion - showmode(); - } else { - oap->start = start_pos; - oap->motion_type = kMTCharWise; - if (lt(end_pos, start_pos)) { - // End is before the start: there is no text between tags; operate - // on an empty area. - curwin->w_cursor = start_pos; - oap->inclusive = false; - } else { - oap->inclusive = is_inclusive; - } - } - retval = OK; - -theend: - p_ws = save_p_ws; - return retval; -} - -/// @param include TRUE == include white space -/// @param type 'p' for paragraph, 'S' for section -int current_par(oparg_T *oap, long count, int include, int type) -{ - linenr_T start_lnum; - linenr_T end_lnum; - int white_in_front; - int dir; - int start_is_white; - int prev_start_is_white; - int retval = OK; - int do_white = FALSE; - int t; - int i; - - if (type == 'S') { // not implemented yet - return FAIL; - } - - start_lnum = curwin->w_cursor.lnum; - - /* - * When visual area is more than one line: extend it. - */ - if (VIsual_active && start_lnum != VIsual.lnum) { -extend: - if (start_lnum < VIsual.lnum) { - dir = BACKWARD; - } else { - dir = FORWARD; - } - for (i = (int)count; --i >= 0;) { - if (start_lnum == - (dir == BACKWARD ? 1 : curbuf->b_ml.ml_line_count)) { - retval = FAIL; - break; - } - - prev_start_is_white = -1; - for (t = 0; t < 2; ++t) { - start_lnum += dir; - start_is_white = linewhite(start_lnum); - if (prev_start_is_white == start_is_white) { - start_lnum -= dir; - break; - } - for (;;) { - if (start_lnum == (dir == BACKWARD - ? 1 : curbuf->b_ml.ml_line_count)) { - break; - } - if (start_is_white != linewhite(start_lnum + dir) - || (!start_is_white - && startPS(start_lnum + (dir > 0 - ? 1 : 0), 0, 0))) { - break; - } - start_lnum += dir; - } - if (!include) { - break; - } - if (start_lnum == (dir == BACKWARD - ? 1 : curbuf->b_ml.ml_line_count)) { - break; - } - prev_start_is_white = start_is_white; - } - } - curwin->w_cursor.lnum = start_lnum; - curwin->w_cursor.col = 0; - return retval; - } - - /* - * First move back to the start_lnum of the paragraph or white lines - */ - white_in_front = linewhite(start_lnum); - while (start_lnum > 1) { - if (white_in_front) { // stop at first white line - if (!linewhite(start_lnum - 1)) { - break; - } - } else { // stop at first non-white line of start of paragraph - if (linewhite(start_lnum - 1) || startPS(start_lnum, 0, 0)) { - break; - } - } - --start_lnum; - } - - /* - * Move past the end of any white lines. - */ - end_lnum = start_lnum; - while (end_lnum <= curbuf->b_ml.ml_line_count && linewhite(end_lnum)) { - ++end_lnum; - } - - end_lnum--; - i = (int)count; - if (!include && white_in_front) { - --i; - } - while (i--) { - if (end_lnum == curbuf->b_ml.ml_line_count) { - return FAIL; - } - - if (!include) { - do_white = linewhite(end_lnum + 1); - } - - if (include || !do_white) { - ++end_lnum; - /* - * skip to end of paragraph - */ - while (end_lnum < curbuf->b_ml.ml_line_count - && !linewhite(end_lnum + 1) - && !startPS(end_lnum + 1, 0, 0)) { - ++end_lnum; - } - } - - if (i == 0 && white_in_front && include) { - break; - } - - /* - * skip to end of white lines after paragraph - */ - if (include || do_white) { - while (end_lnum < curbuf->b_ml.ml_line_count - && linewhite(end_lnum + 1)) { - ++end_lnum; - } - } - } - - /* - * If there are no empty lines at the end, try to find some empty lines at - * the start (unless that has been done already). - */ - if (!white_in_front && !linewhite(end_lnum) && include) { - while (start_lnum > 1 && linewhite(start_lnum - 1)) { - --start_lnum; - } - } - - if (VIsual_active) { - // Problem: when doing "Vipipip" nothing happens in a single white - // line, we get stuck there. Trap this here. - if (VIsual_mode == 'V' && start_lnum == curwin->w_cursor.lnum) { - goto extend; - } - if (VIsual.lnum != start_lnum) { - VIsual.lnum = start_lnum; - VIsual.col = 0; - } - VIsual_mode = 'V'; - redraw_curbuf_later(INVERTED); // update the inversion - showmode(); - } else { - oap->start.lnum = start_lnum; - oap->start.col = 0; - oap->motion_type = kMTLineWise; - } - curwin->w_cursor.lnum = end_lnum; - curwin->w_cursor.col = 0; - - return OK; -} - -/// Search quote char from string line[col]. -/// Quote character escaped by one of the characters in "escape" is not counted -/// as a quote. -/// -/// @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) -{ - int c; - - for (;;) { - c = line[col]; - if (c == NUL) { - return -1; - } else if (escape != NULL && vim_strchr((char *)escape, c)) { - col++; - if (line[col] == NUL) { - return -1; - } - } else if (c == quotechar) { - break; - } - col += utfc_ptr2len((char *)line + col); - } - return col; -} - -/// Search backwards in "line" from column "col_start" to find "quotechar". -/// Quote character escaped by one of the characters in "escape" is not counted -/// as a quote. -/// -/// @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) -{ - int n; - - while (col_start > 0) { - 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) { - ++n; - } - } - if (n & 1) { - col_start -= n; // uneven number of escape chars, skip it - } else if (line[col_start] == quotechar) { - break; - } - } - return col_start; -} - -/// Find quote under the cursor, cursor at end. -/// -/// @param include true == include quote char -/// @param quotechar Quote character -/// -/// @return true if found, else false. -bool current_quote(oparg_T *oap, long count, bool include, int quotechar) - FUNC_ATTR_NONNULL_ALL -{ - char_u *line = get_cursor_line_ptr(); - int col_end; - int col_start = curwin->w_cursor.col; - bool inclusive = false; - bool vis_empty = true; // Visual selection <= 1 char - bool vis_bef_curs = false; // Visual starts before cursor - bool did_exclusive_adj = false; // adjusted pos for 'selection' - bool inside_quotes = false; // Looks like "i'" done before - bool selected_quote = false; // Has quote inside selection - int i; - bool restore_vis_bef = false; // resotre VIsual on abort - - // When 'selection' is "exclusive" move the cursor to where it would be - // with 'selection' "inclusive", so that the logic is the same for both. - // The cursor then is moved forward after adjusting the area. - if (VIsual_active) { - // this only works within one line - if (VIsual.lnum != curwin->w_cursor.lnum) { - return false; - } - - vis_bef_curs = lt(VIsual, curwin->w_cursor); - vis_empty = equalpos(VIsual, curwin->w_cursor); - if (*p_sel == 'e') { - if (vis_bef_curs) { - dec_cursor(); - did_exclusive_adj = true; - } else if (!vis_empty) { - dec(&VIsual); - did_exclusive_adj = true; - } - vis_empty = equalpos(VIsual, curwin->w_cursor); - if (!vis_bef_curs && !vis_empty) { - // VIsual needs to be start of Visual selection. - pos_T t = curwin->w_cursor; - - curwin->w_cursor = VIsual; - VIsual = t; - vis_bef_curs = true; - restore_vis_bef = true; - } - } - } - - if (!vis_empty) { - // Check if the existing selection exactly spans the text inside - // quotes. - if (vis_bef_curs) { - inside_quotes = VIsual.col > 0 - && line[VIsual.col - 1] == quotechar - && line[curwin->w_cursor.col] != NUL - && 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 - && line[VIsual.col] != NUL - && line[VIsual.col + 1] == quotechar; - i = curwin->w_cursor.col; - col_end = VIsual.col; - } - - // Find out if we have a quote in the selection. - while (i <= col_end) { - // check for going over the end of the line, which can happen if - // the line was changed after the Visual area was selected. - if (line[i] == NUL) { - break; - } - if (line[i++] == quotechar) { - selected_quote = true; - break; - } - } - } - - if (!vis_empty && line[col_start] == quotechar) { - // Already selecting something and on a quote character. Find the - // next quoted string. - if (vis_bef_curs) { - // Assume we are on a closing quote: move to after the next - // opening quote. - col_start = find_next_quote(line, col_start + 1, quotechar, NULL); - if (col_start < 0) { - goto abort_search; - } - 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; - col_start = curwin->w_cursor.col; - } - } else { - col_end = find_prev_quote(line, col_start, quotechar, NULL); - if (line[col_end] != quotechar) { - goto abort_search; - } - col_start = find_prev_quote(line, col_end, quotechar, - curbuf->b_p_qe); - if (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) { - int first_col = col_start; - - if (!vis_empty) { - if (vis_bef_curs) { - first_col = find_next_quote(line, col_start, quotechar, NULL); - } else { - first_col = find_prev_quote(line, col_start, quotechar, NULL); - } - } - // The cursor is on a quote, we don't know if it's the opening or - // closing quote. Search from the start of the line to find out. - // Also do this when there is a Visual area, a' may leave the cursor - // in between two strings. - col_start = 0; - for (;;) { - // Find open quote character. - col_start = find_next_quote(line, col_start, quotechar, NULL); - if (col_start < 0 || col_start > first_col) { - goto abort_search; - } - // Find close quote character. - col_end = find_next_quote(line, col_start + 1, quotechar, - curbuf->b_p_qe); - if (col_end < 0) { - goto abort_search; - } - // If is cursor between start and end quote character, it is - // target text object. - if (col_start <= first_col && first_col <= col_end) { - break; - } - col_start = col_end + 1; - } - } else { - // Search backward for a starting quote. - col_start = find_prev_quote(line, col_start, quotechar, curbuf->b_p_qe); - if (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) { - goto abort_search; - } - } - - // Find close quote character. - col_end = find_next_quote(line, col_start + 1, quotechar, - curbuf->b_p_qe); - if (col_end < 0) { - goto abort_search; - } - } - - // When "include" is true, include spaces after closing quote or before - // the starting quote. - if (include) { - if (ascii_iswhite(line[col_end + 1])) { - while (ascii_iswhite(line[col_end + 1])) { - ++col_end; - } - } else { - while (col_start > 0 && ascii_iswhite(line[col_start - 1])) { - --col_start; - } - } - } - - // Set start position. After vi" another i" must include the ". - // For v2i" include the quotes. - if (!include && count < 2 && (vis_empty || !inside_quotes)) { - col_start++; - } - curwin->w_cursor.col = col_start; - if (VIsual_active) { - // Set the start of the Visual area when the Visual area was empty, we - // were just inside quotes or the Visual area didn't start at a quote - // and didn't include a quote. - if (vis_empty - || (vis_bef_curs - && !selected_quote - && (inside_quotes - || (line[VIsual.col] != quotechar - && (VIsual.col == 0 - || line[VIsual.col - 1] != quotechar))))) { - VIsual = curwin->w_cursor; - redraw_curbuf_later(INVERTED); - } - } else { - oap->start = curwin->w_cursor; - oap->motion_type = kMTCharWise; - } - - // Set end position. - curwin->w_cursor.col = col_end; - if ((include || count > 1 - // After vi" another i" must include the ". - || (!vis_empty && inside_quotes) - ) && inc_cursor() == 2) { - inclusive = true; - } - if (VIsual_active) { - if (vis_empty || vis_bef_curs) { - // decrement cursor when 'selection' is not exclusive - if (*p_sel != 'e') { - dec_cursor(); - } - } else { - // Cursor is at start of Visual area. Set the end of the Visual - // area when it was just inside quotes or it didn't end at a - // quote. - if (inside_quotes - || (!selected_quote - && line[VIsual.col] != quotechar - && (line[VIsual.col] == NUL - || line[VIsual.col + 1] != quotechar))) { - dec_cursor(); - VIsual = curwin->w_cursor; - } - curwin->w_cursor.col = col_start; - } - if (VIsual_mode == 'V') { - VIsual_mode = 'v'; - redraw_cmdline = true; // show mode later - } - } else { - // Set inclusive and other oap's flags. - oap->inclusive = inclusive; - } - - return true; - -abort_search: - if (VIsual_active && *p_sel == 'e') { - if (did_exclusive_adj) { - inc_cursor(); - } - if (restore_vis_bef) { - pos_T t = curwin->w_cursor; - - curwin->w_cursor = VIsual; - VIsual = t; - } - } - return false; -} - /// Find next search match under cursor, cursor at end. /// Used while an operator is pending, and in Visual mode. /// @@ -4389,7 +2576,7 @@ int current_search(long count, bool forward) may_start_select('c'); setmouse(); - redraw_curbuf_later(INVERTED); + redraw_curbuf_later(UPD_INVERTED); showmode(); return OK; @@ -4399,7 +2586,7 @@ int current_search(long count, bool forward) /// If move is true, check from the beginning of the buffer, /// else from position "cur". /// "direction" is FORWARD or BACKWARD. -/// Returns TRUE, FALSE or -1 for failure. +/// Returns true, false or -1 for failure. static int is_zero_width(char_u *pattern, int move, pos_T *cur, Direction direction) { regmmatch_T regmatch; @@ -4456,9 +2643,7 @@ static int is_zero_width(char_u *pattern, int move, pos_T *cur, Direction direct return result; } -/* - * return TRUE if line 'lnum' is empty or has white chars only. - */ +/// return true if line 'lnum' is empty or has white chars only. int linewhite(linenr_T lnum) { char_u *p; @@ -4551,7 +2736,7 @@ static void update_search_stat(int dirc, pos_T *pos, pos_T *cursor_pos, searchst static buf_T *lbuf = NULL; proftime_T start; - memset(stat, 0, sizeof(searchstat_T)); + CLEAR_POINTER(stat); if (dirc == 0 && !recompute && !EMPTY_POS(lastpos)) { stat->cur = cur; @@ -4637,7 +2822,7 @@ static void update_search_stat(int dirc, pos_T *pos, pos_T *cursor_pos, searchst } // "searchcount()" function -void f_searchcount(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_searchcount(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { pos_T pos = curwin->w_cursor; char_u *pattern = NULL; @@ -4869,7 +3054,7 @@ static int fuzzy_match_compute_score(const char_u *const str, const int strSz, if (currIdx > 0) { // Camel case const char_u *p = str; - int neighbor; + int neighbor = ' '; for (uint32_t sidx = 0; sidx < currIdx; sidx++) { neighbor = utf_ptr2char((char *)p); @@ -5287,13 +3472,13 @@ static void do_fuzzymatch(const typval_T *const argvars, typval_T *const rettv, } /// "matchfuzzy()" function -void f_matchfuzzy(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_matchfuzzy(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { do_fuzzymatch(argvars, rettv, false); } /// "matchfuzzypos()" function -void f_matchfuzzypos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_matchfuzzypos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { do_fuzzymatch(argvars, rettv, true); } @@ -5309,7 +3494,7 @@ static char_u *get_line_and_copy(linenr_T lnum, char_u *buf) } /// Find identifiers or defines in included files. -/// If p_ic && (compl_cont_status & CONT_SOL) then ptr must be in lowercase. +/// If p_ic && compl_status_sol() then ptr must be in lowercase. /// /// @param ptr pointer to search pattern /// @param dir direction of expansion @@ -5351,7 +3536,6 @@ void find_pattern_in_path(char_u *ptr, Direction dir, size_t len, bool whole, bo int i; char_u *already = NULL; char_u *startp = NULL; - char_u *inc_opt = NULL; win_T *curwin_save = NULL; const int l_g_do_tagpreview = g_do_tagpreview; @@ -5362,9 +3546,9 @@ void find_pattern_in_path(char_u *ptr, Direction dir, size_t len, bool whole, bo file_line = xmalloc(LSIZE); if (type != CHECK_PATH && type != FIND_DEFINE - // when CONT_SOL is set compare "ptr" with the beginning of the line - // is faster than quote_meta/regcomp/regexec "ptr" -- Acevedo - && !(compl_cont_status & CONT_SOL)) { + // 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); assert(len <= INT_MAX); sprintf((char *)pat, whole ? "\\<%.*s\\>" : "%.*s", (int)len, ptr); @@ -5376,22 +3560,22 @@ void find_pattern_in_path(char_u *ptr, Direction dir, size_t len, bool whole, bo goto fpip_end; } } - inc_opt = (*curbuf->b_p_inc == NUL) ? p_inc : curbuf->b_p_inc; + char *inc_opt = (*curbuf->b_p_inc == NUL) ? p_inc : curbuf->b_p_inc; if (*inc_opt != NUL) { - incl_regmatch.regprog = vim_regcomp((char *)inc_opt, p_magic ? RE_MAGIC : 0); + incl_regmatch.regprog = vim_regcomp(inc_opt, p_magic ? RE_MAGIC : 0); if (incl_regmatch.regprog == NULL) { goto fpip_end; } - incl_regmatch.rm_ic = FALSE; // don't ignore case in incl. pat. + incl_regmatch.rm_ic = false; // don't ignore case in incl. pat. } if (type == FIND_DEFINE && (*curbuf->b_p_def != NUL || *p_def != NUL)) { def_regmatch.regprog = vim_regcomp(*curbuf->b_p_def == NUL - ? (char *)p_def : (char *)curbuf->b_p_def, + ? p_def : curbuf->b_p_def, p_magic ? RE_MAGIC : 0); if (def_regmatch.regprog == NULL) { goto fpip_end; } - def_regmatch.rm_ic = FALSE; // don't ignore case in define pat. + def_regmatch.rm_ic = false; // don't ignore case in define pat. } files = xcalloc((size_t)max_path_depth, sizeof(SearchedFile)); old_files = max_path_depth; @@ -5412,7 +3596,7 @@ void find_pattern_in_path(char_u *ptr, Direction dir, size_t len, bool whole, bo char_u *p_fname = (curr_fname == (char_u *)curbuf->b_fname) ? (char_u *)curbuf->b_ffname : curr_fname; - if (inc_opt != NULL && strstr((char *)inc_opt, "\\zs") != NULL) { + if (inc_opt != NULL && strstr(inc_opt, "\\zs") != NULL) { // Use text from '\zs' to '\ze' (or end) of 'include'. new_fname = find_file_name_in_path(incl_regmatch.startp[0], (size_t)(incl_regmatch.endp[0] @@ -5424,7 +3608,7 @@ void find_pattern_in_path(char_u *ptr, Direction dir, size_t len, bool whole, bo new_fname = file_name_in_line(incl_regmatch.endp[0], 0, FNAME_EXP|FNAME_INCL|FNAME_REL, 1L, p_fname, NULL); } - already_searched = FALSE; + already_searched = false; if (new_fname != NULL) { // Check whether we have already searched in this file for (i = 0;; i++) { @@ -5467,7 +3651,7 @@ void find_pattern_in_path(char_u *ptr, Direction dir, size_t len, bool whole, bo } did_show = true; while (depth_displayed < depth && !got_int) { - ++depth_displayed; + depth_displayed++; for (i = 0; i < depth_displayed; i++) { msg_puts(" "); } @@ -5482,14 +3666,14 @@ 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(new_fname, HL_ATTR(HLF_D)); + msg_outtrans_attr((char *)new_fname, HL_ATTR(HLF_D)); } else { /* * Isolate the file name. * Include the surrounding "" or <> if present. */ if (inc_opt != NULL - && strstr((char *)inc_opt, "\\zs") != NULL) { + && strstr(inc_opt, "\\zs") != NULL) { // pattern contains \zs, use the match p = incl_regmatch.startp[0]; i = (int)(incl_regmatch.endp[0] @@ -5509,16 +3693,16 @@ void find_pattern_in_path(char_u *ptr, Direction dir, size_t len, bool whole, bo // Avoid checking before the start of the line, can // happen if \zs appears in the regexp. if (p[-1] == '"' || p[-1] == '<') { - --p; - ++i; + p--; + i++; } if (p[i] == '"' || p[i] == '>') { - ++i; + i++; } } save_char = p[i]; p[i] = NUL; - msg_outtrans_attr(p, HL_ATTR(HLF_D)); + msg_outtrans_attr((char *)p, HL_ATTR(HLF_D)); p[i] = save_char; } @@ -5544,7 +3728,7 @@ void find_pattern_in_path(char_u *ptr, Direction dir, size_t len, bool whole, bo bigger[i].fp = NULL; bigger[i].name = NULL; bigger[i].lnum = 0; - bigger[i].matched = FALSE; + bigger[i].matched = false; } for (i = old_files; i < max_path_depth; i++) { bigger[i + max_path_depth] = files[i]; @@ -5561,11 +3745,11 @@ void find_pattern_in_path(char_u *ptr, Direction dir, size_t len, bool whole, bo // Something wrong. We will forget one of our already visited files // now. xfree(files[old_files].name); - ++old_files; + old_files++; } files[depth].name = curr_fname = new_fname; files[depth].lnum = 0; - files[depth].matched = FALSE; + files[depth].matched = false; if (action == ACTION_EXPAND) { msg_hist_off = true; // reset in msg_trunc_attr() vim_snprintf((char *)IObuff, IOSIZE, @@ -5604,8 +3788,7 @@ search_line: * define and this line didn't match define_prog above. */ if (def_regmatch.regprog == NULL || define_matched) { - if (define_matched - || (compl_cont_status & CONT_SOL)) { + if (define_matched || compl_status_sol()) { // compare the first "len" chars from "ptr" startp = (char_u *)skipwhite((char *)p); if (p_ic) { @@ -5640,7 +3823,7 @@ search_line: p = (char_u *)skipwhite((char *)line); if (matched || (p[0] == '/' && p[1] == '*') || p[0] == '*') { - for (p = line; *p && p < startp; ++p) { + for (p = line; *p && p < startp; p++) { if (matched && p[0] == '/' && (p[1] == '*' || p[1] == '/')) { @@ -5671,8 +3854,8 @@ search_line: } found = true; aux = p = startp; - if (compl_cont_status & CONT_ADDING) { - p += compl_length; + if (compl_status_adding()) { + p += ins_compl_len(); if (vim_iswordp(p)) { goto exit_matched; } @@ -5681,7 +3864,7 @@ search_line: p = find_word_end(p); i = (int)(p - aux); - if ((compl_cont_status & CONT_ADDING) && i == compl_length) { + if (compl_status_adding() && i == ins_compl_len()) { // IOSIZE > compl_length, so the STRNCPY works STRNCPY(IObuff, aux, i); @@ -5728,7 +3911,7 @@ search_line: IObuff[i] = NUL; aux = IObuff; - if (i == compl_length) { + if (i == ins_compl_len()) { goto exit_matched; } } @@ -5820,14 +4003,14 @@ search_line: } if (action != ACTION_SHOW) { curwin->w_cursor.col = (colnr_T)(startp - line); - curwin->w_set_curswant = TRUE; + curwin->w_set_curswant = true; } if (l_g_do_tagpreview != 0 && curwin != curwin_save && win_valid(curwin_save)) { // Return cursor to where we were validate_cursor(); - redraw_later(curwin, VALID); + redraw_later(curwin, UPD_VALID); win_enter(curwin_save, true); } break; @@ -5838,7 +4021,7 @@ exit_matched: // are not at the end of it already if (def_regmatch.regprog == NULL && action == ACTION_EXPAND - && !(compl_cont_status & CONT_SOL) + && !compl_status_sol() && *startp != NUL && *(p = startp + utfc_ptr2len((char *)startp)) != NUL) { goto search_line; @@ -5860,7 +4043,7 @@ exit_matched: while (depth >= 0 && !already && vim_fgets(line = file_line, LSIZE, files[depth].fp)) { fclose(files[depth].fp); - --old_files; + old_files--; files[old_files].name = files[depth].name; files[old_files].matched = files[depth].matched; depth--; @@ -5948,10 +4131,10 @@ static void show_pat_in_path(char_u *line, int type, bool did_show, int action, if (fp != NULL) { // We used fgets(), so get rid of newline at end if (p >= line && *p == '\n') { - --p; + p--; } if (p >= line && *p == '\r') { - --p; + p--; } *(p + 1) = NUL; } @@ -5963,7 +4146,7 @@ static void show_pat_in_path(char_u *line, int type, bool did_show, int action, msg_puts_attr((const char *)IObuff, HL_ATTR(HLF_N)); msg_puts(" "); } - msg_prt_line(line, FALSE); + msg_prt_line(line, false); ui_flush(); // show one line at a time // Definition continues until line that doesn't end with '\' @@ -5996,7 +4179,7 @@ void get_search_pattern(SearchPattern *const pat) void get_substitute_pattern(SearchPattern *const pat) { memcpy(pat, &(spats[1]), sizeof(spats[1])); - memset(&(pat->off), 0, sizeof(pat->off)); + CLEAR_FIELD(pat->off); } /// Set last search pattern @@ -6012,7 +4195,7 @@ void set_substitute_pattern(const SearchPattern pat) { free_spat(&spats[1]); memcpy(&(spats[1]), &pat, sizeof(spats[1])); - memset(&(spats[1].off), 0, sizeof(spats[1].off)); + CLEAR_FIELD(spats[1].off); } /// Set last used search pattern diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 6e80b550d8..d2f1b39ca4 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -16,11 +16,12 @@ #include "nvim/ascii.h" #include "nvim/buffer.h" #include "nvim/buffer_defs.h" +#include "nvim/cmdhist.h" #include "nvim/eval/decode.h" #include "nvim/eval/encode.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/garray.h" #include "nvim/globals.h" @@ -1476,7 +1477,7 @@ static char *shada_filename(const char *file) // because various expansions must have already be done by the shell. // If shell is not performing them then they should be done in main.c // where arguments are parsed, *not here*. - expand_env((char_u *)file, &(NameBuff[0]), MAXPATHL); + expand_env((char *)file, &(NameBuff[0]), MAXPATHL); file = (const char *)&(NameBuff[0]); } } @@ -2188,7 +2189,7 @@ static inline ShaDaWriteResult shada_read_when_writing(ShaDaReadDef *const sd_re k = kh_put(file_marks, &wms->file_marks, fname, &kh_ret); FileMarks *const filemarks = &kh_val(&wms->file_marks, k); if (kh_ret > 0) { - memset(filemarks, 0, sizeof(*filemarks)); + CLEAR_POINTER(filemarks); } if (entry.timestamp > filemarks->greatest_timestamp) { filemarks->greatest_timestamp = entry.timestamp; @@ -2451,6 +2452,27 @@ static inline void find_removable_bufs(khash_t(bufset) *removable_bufs) } } +/// Translate a history type number to the associated character +static int hist_type2char(const int type) + FUNC_ATTR_CONST +{ + switch (type) { + case HIST_CMD: + return ':'; + case HIST_SEARCH: + return '/'; + case HIST_EXPR: + return '='; + case HIST_INPUT: + return '@'; + case HIST_DEBUG: + return '>'; + default: + abort(); + } + return NUL; +} + /// Write ShaDa file /// /// @param[in] sd_writer Structure containing file writer definition. @@ -2730,7 +2752,7 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, ShaDaReadDef k = kh_put(file_marks, &wms->file_marks, fname, &kh_ret); FileMarks *const filemarks = &kh_val(&wms->file_marks, k); if (kh_ret > 0) { - memset(filemarks, 0, sizeof(*filemarks)); + CLEAR_POINTER(filemarks); } do { fmark_T fm; @@ -3001,7 +3023,7 @@ shada_write_file_nomerge: {} if (tail != fname) { const char tail_save = *tail; *tail = NUL; - if (!os_isdir((char_u *)fname)) { + if (!os_isdir(fname)) { int ret; char *failed_dir; if ((ret = os_mkdir_recurse(fname, 0700, &failed_dir)) != 0) { @@ -3456,7 +3478,7 @@ shada_read_next_item_start: // data union are NULL so they are safe to xfree(). This is needed in case // somebody calls goto shada_read_next_item_error before anything is set in // the switch. - memset(entry, 0, sizeof(*entry)); + CLEAR_POINTER(entry); if (sd_reader->eof) { return kSDReadStatusFinished; } @@ -3974,7 +3996,7 @@ static bool shada_removable(const char *name) bool retval = false; char *new_name = home_replace_save(NULL, (char *)name); - for (p = (char *)p_shada; *p;) { + 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); diff --git a/src/nvim/sign.c b/src/nvim/sign.c index 7b6b55fede..ed7f53d3ba 100644 --- a/src/nvim/sign.c +++ b/src/nvim/sign.c @@ -9,6 +9,7 @@ #include "nvim/buffer.h" #include "nvim/charset.h" #include "nvim/cursor.h" +#include "nvim/drawscreen.h" #include "nvim/edit.h" #include "nvim/eval/funcs.h" #include "nvim/ex_docmd.h" @@ -16,7 +17,6 @@ #include "nvim/highlight_group.h" #include "nvim/move.h" #include "nvim/option.h" -#include "nvim/screen.h" #include "nvim/sign.h" #include "nvim/syntax.h" #include "nvim/vim.h" @@ -203,7 +203,7 @@ static void insert_sign(buf_T *buf, sign_entry_T *prev, sign_entry_T *next, int // When adding first sign need to redraw the windows to create the // column for signs. if (buf->b_signlist == NULL) { - redraw_buf_later(buf, NOT_VALID); + redraw_buf_later(buf, UPD_NOT_VALID); changed_line_abv_curs(); } @@ -442,27 +442,24 @@ static linenr_T buf_change_sign_type(buf_T *buf, int markId, const char_u *group /// @param max_signs the number of signs, with priority for the ones /// with the highest Ids. /// @return Attrs of the matching sign, or NULL -sign_attrs_T *sign_get_attr(SignType type, sign_attrs_T sattrs[], int idx, int max_signs) +SignTextAttrs *sign_get_attr(int idx, SignTextAttrs sattrs[], int max_signs) { - sign_attrs_T *matches[SIGN_SHOW_MAX]; - int nr_matches = 0; + SignTextAttrs *matches[SIGN_SHOW_MAX]; + int sattr_matches = 0; for (int i = 0; i < SIGN_SHOW_MAX; i++) { - if ((type == SIGN_TEXT && sattrs[i].sat_text != NULL) - || (type == SIGN_LINEHL && sattrs[i].sat_linehl != 0) - || (type == SIGN_NUMHL && sattrs[i].sat_numhl != 0)) { - matches[nr_matches] = &sattrs[i]; - nr_matches++; + if (sattrs[i].text != NULL) { + matches[sattr_matches++] = &sattrs[i]; // attr list is sorted with most important (priority, id), thus we // may stop as soon as we have max_signs matches - if (nr_matches >= max_signs) { + if (sattr_matches >= max_signs) { break; } } } - if (nr_matches > idx) { - return matches[nr_matches - idx - 1]; + if (sattr_matches > idx) { + return matches[sattr_matches - idx - 1]; } return NULL; @@ -474,12 +471,12 @@ sign_attrs_T *sign_get_attr(SignType type, sign_attrs_T sattrs[], int idx, int m /// @param lnum Line in which to search /// @param sattrs Output array for attrs /// @return Number of signs of which attrs were found -int buf_get_signattrs(buf_T *buf, linenr_T lnum, sign_attrs_T sattrs[]) +int buf_get_signattrs(buf_T *buf, linenr_T lnum, SignTextAttrs sattrs[], HlPriAttr *num_attrs, + HlPriAttr *line_attrs, HlPriAttr *cul_attrs) { sign_entry_T *sign; - sign_T *sp; - int nr_matches = 0; + int sattr_matches = 0; FOR_ALL_SIGNS_IN_BUF(buf, sign) { if (sign->se_lnum > lnum) { @@ -488,37 +485,39 @@ int buf_get_signattrs(buf_T *buf, linenr_T lnum, sign_attrs_T sattrs[]) break; } - if (sign->se_lnum == lnum) { - sign_attrs_T sattr; - memset(&sattr, 0, sizeof(sattr)); - sattr.sat_typenr = sign->se_typenr; - sp = find_sign_by_typenr(sign->se_typenr); - if (sp != NULL) { - sattr.sat_text = sp->sn_text; - if (sattr.sat_text != NULL && sp->sn_text_hl != 0) { - sattr.sat_texthl = syn_id2attr(sp->sn_text_hl); - } - if (sp->sn_line_hl != 0) { - sattr.sat_linehl = syn_id2attr(sp->sn_line_hl); - } - if (sp->sn_cul_hl != 0) { - sattr.sat_culhl = syn_id2attr(sp->sn_cul_hl); - } - if (sp->sn_num_hl != 0) { - sattr.sat_numhl = syn_id2attr(sp->sn_num_hl); - } - // Store the priority so we can mesh in extmark signs later - sattr.sat_prio = sign->se_priority; - } + if (sign->se_lnum < lnum) { + continue; + } - sattrs[nr_matches] = sattr; - nr_matches++; - if (nr_matches == SIGN_SHOW_MAX) { - break; + sign_T *sp = find_sign_by_typenr(sign->se_typenr); + if (sp == NULL) { + continue; + } + + if (sp->sn_text != NULL && sattr_matches < SIGN_SHOW_MAX) { + sattrs[sattr_matches++] = (SignTextAttrs) { + .text = sp->sn_text, + .hl_attr_id = sp->sn_text_hl == 0 ? 0 : syn_id2attr(sp->sn_text_hl), + .priority = sign->se_priority + }; + } + + struct { HlPriAttr *dest; int hl; } cattrs[] = { + { line_attrs, sp->sn_line_hl }, + { num_attrs, sp->sn_num_hl }, + { cul_attrs, sp->sn_cul_hl }, + { NULL, -1 }, + }; + for (int i = 0; cattrs[i].dest; i++) { + if (cattrs[i].hl != 0 && sign->se_priority >= cattrs[i].dest->priority) { + *cattrs[i].dest = (HlPriAttr) { + .attr_id = syn_id2attr(cattrs[i].hl), + .priority = sign->se_priority + }; } } } - return nr_matches; + return sattr_matches; } /// Delete sign 'id' in group 'group' from buffer 'buf'. @@ -577,7 +576,7 @@ static linenr_T buf_delsign(buf_T *buf, linenr_T atlnum, int id, char_u *group) // When deleting the last sign the cursor position may change, because the // sign columns no longer shows. And the 'signcolumn' may be hidden. if (buf->b_signlist == NULL) { - redraw_buf_later(buf, NOT_VALID); + redraw_buf_later(buf, UPD_NOT_VALID); changed_line_abv_curs(); } @@ -859,7 +858,7 @@ static void sign_define_init_icon(sign_T *sp, char_u *icon) { xfree(sp->sn_icon); sp->sn_icon = vim_strsave(icon); - backslash_halve(sp->sn_icon); + backslash_halve((char *)sp->sn_icon); } /// Initialize the text for a new sign @@ -935,7 +934,7 @@ static int sign_define_by_name(char_u *name, char_u *icon, char_u *linehl, char_ // non-empty sign list. FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { if (wp->w_buffer->b_signlist != NULL) { - redraw_buf_later(wp->w_buffer, NOT_VALID); + redraw_buf_later(wp->w_buffer, UPD_NOT_VALID); } } } @@ -1085,7 +1084,7 @@ static int sign_unplace(int sign_id, char_u *sign_group, buf_T *buf, linenr_T at } if (sign_id == 0) { // Delete all the signs in the specified buffer - redraw_buf_later(buf, NOT_VALID); + redraw_buf_later(buf, UPD_NOT_VALID); buf_delete_signs(buf, (char *)sign_group); } else { linenr_T lnum; @@ -1354,7 +1353,7 @@ static int parse_sign_cmd_args(int cmd, char_u *arg, char_u **sign_name, int *si if (STRNCMP(arg, "line=", 5) == 0) { arg += 5; *lnum = atoi((char *)arg); - arg = skiptowhite(arg); + arg = (char_u *)skiptowhite((char *)arg); lnum_arg = true; } else if (STRNCMP(arg, "*", 1) == 0 && cmd == SIGNCMD_UNPLACE) { if (*signid != -1) { @@ -1362,11 +1361,11 @@ static int parse_sign_cmd_args(int cmd, char_u *arg, char_u **sign_name, int *si return FAIL; } *signid = -2; - arg = skiptowhite(arg + 1); + arg = (char_u *)skiptowhite((char *)arg + 1); } else if (STRNCMP(arg, "name=", 5) == 0) { arg += 5; name = arg; - arg = skiptowhite(arg); + arg = (char_u *)skiptowhite((char *)arg); if (*arg != NUL) { *arg++ = NUL; } @@ -1377,14 +1376,14 @@ static int parse_sign_cmd_args(int cmd, char_u *arg, char_u **sign_name, int *si } else if (STRNCMP(arg, "group=", 6) == 0) { arg += 6; *group = arg; - arg = skiptowhite(arg); + arg = (char_u *)skiptowhite((char *)arg); if (*arg != NUL) { *arg++ = NUL; } } else if (STRNCMP(arg, "priority=", 9) == 0) { arg += 9; *prio = atoi((char *)arg); - arg = skiptowhite(arg); + arg = (char_u *)skiptowhite((char *)arg); } else if (STRNCMP(arg, "file=", 5) == 0) { arg += 5; filename = arg; @@ -1428,7 +1427,7 @@ void ex_sign(exarg_T *eap) sign_T *sp; // Parse the subcommand. - p = skiptowhite(arg); + p = (char_u *)skiptowhite((char *)arg); idx = sign_cmd_idx(arg, p); if (idx == SIGNCMD_LAST) { semsg(_("E160: Unknown sign command: %s"), arg); @@ -1450,7 +1449,7 @@ void ex_sign(exarg_T *eap) // Isolate the sign name. If it's a number skip leading zeroes, // so that "099" and "99" are the same sign. But keep "0". - p = skiptowhite(arg); + p = (char_u *)skiptowhite((char *)arg); if (*p != NUL) { *p++ = NUL; } @@ -1790,7 +1789,7 @@ void set_context_in_sign_cmd(expand_T *xp, char_u *arg) expand_what = EXP_SUBCMD; xp->xp_pattern = (char *)arg; - end_subcmd = skiptowhite(arg); + end_subcmd = (char_u *)skiptowhite((char *)arg); if (*end_subcmd == NUL) { // expand subcmd name // :sign {subcmd}<CTRL-D> @@ -1815,7 +1814,7 @@ void set_context_in_sign_cmd(expand_T *xp, char_u *arg) do { p = (char_u *)skipwhite((char *)p); last = p; - p = skiptowhite(p); + p = (char_u *)skiptowhite((char *)p); } while (*p != NUL); p = (char_u *)vim_strchr((char *)last, '='); @@ -1965,7 +1964,7 @@ static void sign_define_multiple(list_T *l, list_T *retlist) } /// "sign_define()" function -void f_sign_define(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_sign_define(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const char *name; @@ -1996,7 +1995,7 @@ void f_sign_define(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "sign_getdefined()" function -void f_sign_getdefined(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_sign_getdefined(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const char *name = NULL; @@ -2010,7 +2009,7 @@ void f_sign_getdefined(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "sign_getplaced()" function -void f_sign_getplaced(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_sign_getplaced(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { buf_T *buf = NULL; dict_T *dict; @@ -2068,7 +2067,7 @@ void f_sign_getplaced(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "sign_jump()" function -void f_sign_jump(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_sign_jump(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int sign_id; char *sign_group = NULL; @@ -2226,7 +2225,7 @@ cleanup: } /// "sign_place()" function -void f_sign_place(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_sign_place(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { dict_T *dict = NULL; @@ -2244,7 +2243,7 @@ void f_sign_place(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "sign_placelist()" function. Place multiple signs. -void f_sign_placelist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_sign_placelist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int sign_id; @@ -2284,7 +2283,7 @@ static void sign_undefine_multiple(list_T *l, list_T *retlist) } /// "sign_undefine()" function -void f_sign_undefine(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_sign_undefine(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const char *name; @@ -2374,7 +2373,7 @@ cleanup: } /// "sign_unplace()" function -void f_sign_unplace(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_sign_unplace(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { dict_T *dict = NULL; @@ -2397,7 +2396,7 @@ void f_sign_unplace(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "sign_unplacelist()" function -void f_sign_unplacelist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_sign_unplacelist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int retval; diff --git a/src/nvim/sign_defs.h b/src/nvim/sign_defs.h index e4ece71846..a4fb325ec8 100644 --- a/src/nvim/sign_defs.h +++ b/src/nvim/sign_defs.h @@ -33,15 +33,11 @@ struct sign_entry { }; /// Sign attributes. Used by the screen refresh routines. -typedef struct sign_attrs_S { - int sat_typenr; - char_u *sat_text; - int sat_texthl; - int sat_linehl; - int sat_culhl; - int sat_numhl; - int sat_prio; // Used for inserting extmark signs -} sign_attrs_T; +typedef struct { + char_u *text; + int hl_attr_id; + int priority; +} SignTextAttrs; #define SIGN_SHOW_MAX 9 diff --git a/src/nvim/spell.c b/src/nvim/spell.c index ceb35af4b8..1259736e0e 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -56,71 +56,59 @@ // Use DEBUG_TRIEWALK to print the changes made in suggest_trie_walk() for a // specific word. -// Use this to adjust the score after finding suggestions, based on the -// suggested word sounding like the bad word. This is much faster than doing -// it for every possible suggestion. -// Disadvantage: When "the" is typed as "hte" it sounds quite different ("@" -// vs "ht") and goes down in the list. -// Used when 'spellsuggest' is set to "best". -#define RESCORE(word_score, sound_score) ((3 * (word_score) + (sound_score)) / 4) - -// Do the opposite: based on a maximum end score and a known sound score, -// compute the maximum word score that can be used. -#define MAXSCORE(word_score, sound_score) ((4 * (word_score) - (sound_score)) / 3) - -#include <assert.h> -#include <inttypes.h> -#include <limits.h> -#include <stdbool.h> -#include <stdlib.h> -#include <string.h> -#include <wctype.h> - -// for offsetof() -#include <stddef.h> - -#include "nvim/ascii.h" -#include "nvim/buffer.h" -#include "nvim/change.h" -#include "nvim/charset.h" -#include "nvim/cursor.h" -#include "nvim/edit.h" -#include "nvim/eval.h" -#include "nvim/ex_cmds.h" -#include "nvim/ex_cmds2.h" -#include "nvim/ex_docmd.h" -#include "nvim/fileio.h" -#include "nvim/func_attr.h" -#include "nvim/garray.h" -#include "nvim/getchar.h" -#include "nvim/hashtab.h" -#include "nvim/input.h" -#include "nvim/insexpand.h" -#include "nvim/mark.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/input.h" -#include "nvim/os/os.h" -#include "nvim/os_unix.h" -#include "nvim/path.h" -#include "nvim/regexp.h" -#include "nvim/screen.h" -#include "nvim/search.h" -#include "nvim/spell.h" -#include "nvim/spellfile.h" -#include "nvim/strings.h" -#include "nvim/syntax.h" -#include "nvim/ui.h" -#include "nvim/undo.h" - -// only used for su_badflags -#define WF_MIXCAP 0x20 // mix of upper and lower case: macaRONI - -#define WF_CAPMASK (WF_ONECAP | WF_ALLCAP | WF_KEEPCAP | WF_FIXCAP) +#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 "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 // Result values. Lower number is accepted over higher one. #define SP_BANNED (-1) @@ -136,104 +124,6 @@ slang_T *first_lang = NULL; // file used for "zG" and "zW" char_u *int_wordlist = NULL; -typedef struct wordcount_S { - uint16_t wc_count; // nr of times word was seen - char_u wc_word[1]; // word, actually longer -} wordcount_T; - -#define WC_KEY_OFF offsetof(wordcount_T, wc_word) -#define HI2WC(hi) ((wordcount_T *)((hi)->hi_key - WC_KEY_OFF)) -#define MAXWORDCOUNT 0xffff - -// Information used when looking for suggestions. -typedef struct suginfo_S { - garray_T su_ga; // suggestions, contains "suggest_T" - int su_maxcount; // max. number of suggestions displayed - 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 - 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_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 -} suginfo_T; - -// One word suggestion. Used in "si_ga". -typedef struct { - char_u *st_word; // suggested word, allocated string - int st_wordlen; // STRLEN(st_word) - int st_orglen; // length of replaced text - int st_score; // lower is better - int st_altscore; // used when st_score compares equal - bool st_salscore; // st_score is for soundalike - bool st_had_bonus; // bonus already included in score - slang_T *st_slang; // language used for sound folding -} suggest_T; - -#define SUG(ga, i) (((suggest_T *)(ga).ga_data)[i]) - -// True if a word appears in the list of banned words. -#define WAS_BANNED(su, word) (!HASHITEM_EMPTY(hash_find(&(su)->su_banned, word))) - -// Number of suggestions kept when cleaning up. We need to keep more than -// what is displayed, because when rescore_suggestions() is called the score -// may change and wrong suggestions may be removed later. -#define SUG_CLEAN_COUNT(su) ((su)->su_maxcount < \ - 130 ? 150 : (su)->su_maxcount + 20) - -// Threshold for sorting and cleaning up suggestions. Don't want to keep lots -// of suggestions that are not going to be displayed. -#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 - -// 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 - -#define SCORE_BIG (SCORE_INS * 3) // big difference -#define SCORE_MAXMAX 999999 // accept any score -#define 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 -#define SCORE_EDIT_MIN SCORE_SIMILAR - // Structure to store info for word matching. typedef struct matchinf_S { langp_T *mi_lp; // info for language and region @@ -289,38 +179,10 @@ typedef struct syl_item_S { spelltab_T spelltab; int did_set_spelltab; -// structure used to store soundfolded words that add_sound_suggest() has -// handled already. -typedef struct { - int16_t sft_score; // lowest score used - char_u sft_word[1]; // soundfolded word, actually longer -} sftword_T; - -typedef struct { - int badi; - int goodi; - int score; -} limitscore_T; - #ifdef INCLUDE_GENERATED_DECLARATIONS # include "spell.c.generated.h" #endif -// 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 - -// 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 - -// 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 - // mode values for find_word #define FIND_FOLDWORD 0 // find word case-folded #define FIND_KEEPWORD 1 // find keep-case word @@ -331,8 +193,8 @@ typedef struct { char *e_format = N_("E759: Format error in spell file"); // Remember what "z?" replaced. -static char_u *repl_from = NULL; -static char_u *repl_to = NULL; +char_u *repl_from = NULL; +char_u *repl_to = NULL; /// Main spell-checking function. /// "ptr" points to a character that could be the start of a word. @@ -356,9 +218,7 @@ size_t spell_check(win_T *wp, char_u *ptr, hlf_T *attrp, int *capcol, bool docou matchinf_T mi; // Most things are put in "mi" so that it can // be passed to functions quickly. size_t nrlen = 0; // found a number first - int c; size_t wrongcaplen = 0; - int lpi; bool count_word = docount; bool use_camel_case = *wp->w_s->b_p_spo != NUL; bool camel_case = false; @@ -374,7 +234,7 @@ size_t spell_check(win_T *wp, char_u *ptr, hlf_T *attrp, int *capcol, bool docou return 1; } - memset(&mi, 0, sizeof(matchinf_T)); + CLEAR_FIELD(mi); // A number is always OK. Also skip hexadecimal numbers 0xFF99 and // 0X99FF. But always do check spelling to find "3GPP" and "11 @@ -383,7 +243,7 @@ size_t spell_check(win_T *wp, char_u *ptr, hlf_T *attrp, int *capcol, bool docou if (*ptr == '0' && (ptr[1] == 'b' || ptr[1] == 'B')) { mi.mi_end = (char_u *)skipbin((char *)ptr + 2); } else if (*ptr == '0' && (ptr[1] == 'x' || ptr[1] == 'X')) { - mi.mi_end = skiphex(ptr + 2); + mi.mi_end = (char_u *)skiphex((char *)ptr + 2); } else { mi.mi_end = (char_u *)skipdigits((char *)ptr); } @@ -397,7 +257,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) { - c = utf_ptr2char((char *)mi.mi_fend); + int c = utf_ptr2char((char *)mi.mi_fend); this_upper = SPELL_ISUPPER(c); } @@ -405,7 +265,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; - c = utf_ptr2char((char *)mi.mi_fend); + int c = utf_ptr2char((char *)mi.mi_fend); this_upper = SPELL_ISUPPER(c); camel_case = !prev_upper && this_upper; } @@ -414,7 +274,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. - c = utf_ptr2char((char *)ptr); + int c = utf_ptr2char((char *)ptr); if (!SPELL_ISUPPER(c)) { wrongcaplen = (size_t)(mi.mi_fend - ptr); } @@ -455,7 +315,7 @@ size_t spell_check(win_T *wp, char_u *ptr, hlf_T *attrp, int *capcol, bool docou // Loop over the languages specified in 'spelllang'. // We check them all, because a word may be matched longer in another // language. - for (lpi = 0; lpi < wp->w_s->b_langp.ga_len; ++lpi) { + for (int lpi = 0; lpi < wp->w_s->b_langp.ga_len; lpi++) { mi.mi_lp = LANGP_ENTRY(wp->w_s->b_langp, lpi); // If reloading fails the language is still in the list but everything @@ -615,7 +475,6 @@ static void find_word(matchinf_T *mip, int mode) int endlen[MAXWLEN]; // length at possible word endings idx_T endidx[MAXWLEN]; // possible word endings int endidxcnt = 0; - int len; int c; // Repeat advancing in the tree until: @@ -627,7 +486,7 @@ static void find_word(matchinf_T *mip, int mode) flen = fold_more(mip); } - len = byts[arridx++]; + int len = byts[arridx++]; // If the first possible byte is a zero the word could end here. // Remember this index, we first check for the longest word. @@ -639,13 +498,13 @@ static void find_word(matchinf_T *mip, int mode) } endlen[endidxcnt] = wlen; endidx[endidxcnt++] = arridx++; - --len; + len--; // Skip over the zeros, there can be several flag/region // combinations. while (len > 0 && byts[arridx] == 0) { - ++arridx; - --len; + arridx++; + len--; } if (len == 0) { break; // no children, word must end here @@ -683,8 +542,8 @@ static void find_word(matchinf_T *mip, int mode) // Continue at the child (if there is one). arridx = idxs[lo]; - ++wlen; - --flen; + wlen++; + flen--; // One space in the good word may stand for several spaces in the // checked word. @@ -696,8 +555,8 @@ static void find_word(matchinf_T *mip, int mode) if (ptr[wlen] != ' ' && ptr[wlen] != TAB) { break; } - ++wlen; - --flen; + wlen++; + flen--; } } } @@ -708,7 +567,7 @@ static void find_word(matchinf_T *mip, int mode) // Verify that one of the possible endings is valid. Try the longest // first. while (endidxcnt > 0) { - --endidxcnt; + endidxcnt--; arridx = endidx[endidxcnt]; wlen = endlen[endidxcnt]; @@ -744,7 +603,7 @@ static void find_word(matchinf_T *mip, int mode) // prefix ID. // Repeat this if there are more flags/region alternatives until there // is a match. - for (len = byts[arridx - 1]; len > 0 && byts[arridx] == 0; len--, arridx++) { + for (int len = byts[arridx - 1]; len > 0 && byts[arridx] == 0; len--, arridx++) { uint32_t flags = (uint32_t)idxs[arridx]; // For the fold-case tree check that the case of the checked word @@ -763,11 +622,10 @@ static void find_word(matchinf_T *mip, int mode) || !spell_valid_case(mip->mi_capflags, (int)flags)) { continue; } - } - // When mode is FIND_PREFIX the word must support the prefix: - // check the prefix ID and the condition. Do that for the list at - // mip->mi_prefarridx that find_prefix() filled. - else if (mode == FIND_PREFIX && !prefix_found) { + } else if (mode == FIND_PREFIX && !prefix_found) { + // When mode is FIND_PREFIX the word must support the prefix: + // check the prefix ID and the condition. Do that for the list at + // mip->mi_prefarridx that find_prefix() filled. c = valid_word_prefix(mip->mi_prefcnt, mip->mi_prefarridx, (int)flags, mip->mi_word + mip->mi_cprefixlen, slang, @@ -899,9 +757,8 @@ static void find_word(matchinf_T *mip, int mode) // COMPOUNDRULE, discard the compounded word. continue; } - } - // Check NEEDCOMPOUND: can't use word without compounding. - else if (flags & WF_NEEDCOMP) { + } else if (flags & WF_NEEDCOMP) { + // skip if word is only valid in a compound continue; } @@ -939,14 +796,14 @@ static void find_word(matchinf_T *mip, int mode) #if 0 c = mip->mi_compoff; #endif - ++mip->mi_complen; + mip->mi_complen++; if (flags & WF_COMPROOT) { - ++mip->mi_compextra; + mip->mi_compextra++; } // For NOBREAK we need to try all NOBREAK languages, at least // to find the ".add" file(s). - for (int lpi = 0; lpi < mip->mi_win->w_s->b_langp.ga_len; ++lpi) { + for (int lpi = 0; lpi < mip->mi_win->w_s->b_langp.ga_len; lpi++) { if (slang->sl_nobreak) { mip->mi_lp = LANGP_ENTRY(mip->mi_win->w_s->b_langp, lpi); if (mip->mi_lp->lp_slang->sl_fidxs == NULL @@ -980,9 +837,9 @@ static void find_word(matchinf_T *mip, int mode) break; } } - --mip->mi_complen; + mip->mi_complen--; if (flags & WF_COMPROOT) { - --mip->mi_compextra; + mip->mi_compextra--; } mip->mi_lp = save_lp; @@ -1050,18 +907,15 @@ static void find_word(matchinf_T *mip, int mode) /// end of ptr[wlen] and the second part matches after it. /// /// @param gap &sl_comppat -static bool match_checkcompoundpattern(char_u *ptr, int wlen, garray_T *gap) +bool match_checkcompoundpattern(char_u *ptr, int wlen, garray_T *gap) { - char_u *p; - int len; - for (int i = 0; i + 1 < gap->ga_len; i += 2) { - p = ((char_u **)gap->ga_data)[i + 1]; + char_u *p = ((char_u **)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]; - len = (int)STRLEN(p); + int len = (int)STRLEN(p); if (len <= wlen && STRNCMP(ptr + wlen - len, p, len) == 0) { return true; } @@ -1072,7 +926,7 @@ static bool match_checkcompoundpattern(char_u *ptr, int wlen, garray_T *gap) // Returns true if "flags" is a valid sequence of compound flags and "word" // does not have too many syllables. -static bool can_compound(slang_T *slang, const char_u *word, const char_u *flags) +bool can_compound(slang_T *slang, const char_u *word, const char_u *flags) FUNC_ATTR_NONNULL_ALL { char_u uflags[MAXWLEN * 2] = { 0 }; @@ -1101,48 +955,18 @@ static bool can_compound(slang_T *slang, const char_u *word, const char_u *flags return true; } -// Returns true when the sequence of flags in "compflags" plus "flag" can -// possibly form a valid compounded word. This also checks the COMPOUNDRULE -// lines if they don't contain wildcards. -static bool can_be_compound(trystate_T *sp, slang_T *slang, char_u *compflags, int flag) -{ - // If the flag doesn't appear in sl_compstartflags or sl_compallflags - // then it can't possibly compound. - if (!byte_in_str(sp->ts_complen == sp->ts_compsplit - ? slang->sl_compstartflags : slang->sl_compallflags, flag)) { - return false; - } - - // If there are no wildcards, we can check if the flags collected so far - // possibly can form a match with COMPOUNDRULE patterns. This only - // makes sense when we have two or more words. - if (slang->sl_comprules != NULL && sp->ts_complen > sp->ts_compsplit) { - compflags[sp->ts_complen] = (char_u)flag; - compflags[sp->ts_complen + 1] = NUL; - bool v = match_compoundrule(slang, compflags + sp->ts_compsplit); - compflags[sp->ts_complen] = NUL; - return v; - } - - return true; -} - // Returns true if the compound flags in compflags[] match the start of any // 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. -static bool match_compoundrule(slang_T *slang, char_u *compflags) +bool match_compoundrule(slang_T *slang, char_u *compflags) { - char_u *p; - int i; - int c; - // loop over all the COMPOUNDRULE entries - for (p = slang->sl_comprules; *p != NUL; ++p) { + for (char_u *p = 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 (i = 0;; ++i) { - c = compflags[i]; + for (int i = 0;; i++) { + int c = compflags[i]; if (c == NUL) { // found a rule that matches for the flags we have so far return true; @@ -1154,7 +978,7 @@ static bool match_compoundrule(slang_T *slang, char_u *compflags) bool match = false; // compare against all the flags in [] - ++p; + p++; while (*p != ']' && *p != NUL) { if (*p++ == c) { match = true; @@ -1166,7 +990,7 @@ static bool match_compoundrule(slang_T *slang, char_u *compflags) } else if (*p != c) { break; // flag of word doesn't match flag in pattern } - ++p; + p++; } // Skip to the next "/", where the next pattern starts. @@ -1188,15 +1012,12 @@ static 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 -static int valid_word_prefix(int totprefcnt, int arridx, int flags, char_u *word, slang_T *slang, - bool cond_req) +int valid_word_prefix(int totprefcnt, int arridx, int flags, char_u *word, slang_T *slang, + bool cond_req) { - int prefcnt; - int pidx; - int prefid = (int)((unsigned)flags >> 24); - for (prefcnt = totprefcnt - 1; prefcnt >= 0; prefcnt--) { - pidx = slang->sl_pidxs[arridx + prefcnt]; + for (int prefcnt = totprefcnt - 1; prefcnt >= 0; prefcnt--) { + int pidx = slang->sl_pidxs[arridx + prefcnt]; // Check the prefix ID. if (prefid != (pidx & 0xff)) { @@ -1236,30 +1057,23 @@ static int valid_word_prefix(int totprefcnt, int arridx, int flags, char_u *word static void find_prefix(matchinf_T *mip, int mode) { idx_T arridx = 0; - int len; int wlen = 0; - int flen; - int c; - char_u *ptr; - idx_T lo, hi, m; slang_T *slang = mip->mi_lp->lp_slang; - char_u *byts; - idx_T *idxs; - byts = slang->sl_pbyts; + char_u *byts = slang->sl_pbyts; if (byts == NULL) { return; // array is empty } // We use the case-folded word here, since prefixes are always // case-folded. - ptr = mip->mi_fword; - flen = mip->mi_fwordlen; // available case-folded bytes + char_u *ptr = mip->mi_fword; + int flen = mip->mi_fwordlen; // available case-folded bytes if (mode == FIND_COMPOUND) { // Skip over the previously found word(s). ptr += mip->mi_compoff; flen -= mip->mi_compoff; } - idxs = slang->sl_pidxs; + idx_T *idxs = slang->sl_pidxs; // Repeat advancing in the tree until: // - there is a byte that doesn't match, @@ -1270,7 +1084,7 @@ static void find_prefix(matchinf_T *mip, int mode) flen = fold_more(mip); } - len = byts[arridx++]; + int len = byts[arridx++]; // If the first possible byte is a zero the prefix could end here. // Check if the following word matches and supports the prefix. @@ -1282,8 +1096,8 @@ static void find_prefix(matchinf_T *mip, int mode) mip->mi_prefarridx = arridx; mip->mi_prefcnt = len; while (len > 0 && byts[arridx] == 0) { - ++arridx; - --len; + arridx++; + len--; } mip->mi_prefcnt -= len; @@ -1310,11 +1124,11 @@ static void find_prefix(matchinf_T *mip, int mode) } // Perform a binary search in the list of accepted bytes. - c = ptr[wlen]; - lo = arridx; - hi = arridx + len - 1; + int c = ptr[wlen]; + idx_T lo = arridx; + idx_T hi = arridx + len - 1; while (lo < hi) { - m = (lo + hi) / 2; + idx_T m = (lo + hi) / 2; if (byts[m] > c) { hi = m - 1; } else if (byts[m] < c) { @@ -1332,8 +1146,8 @@ static void find_prefix(matchinf_T *mip, int mode) // Continue at the child (if there is one). arridx = idxs[lo]; - ++wlen; - --flen; + wlen++; + flen--; } } @@ -1342,10 +1156,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) { - int flen; - char_u *p; - - p = mip->mi_fend; + char_u *p = mip->mi_fend; do { MB_PTR_ADV(mip->mi_fend); } while (*mip->mi_fend != NUL && spell_iswordp(mip->mi_fend, mip->mi_win)); @@ -1358,7 +1169,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); - 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; } @@ -1368,7 +1179,7 @@ static int fold_more(matchinf_T *mip) /// /// @param wordflags Flags for the checked word. /// @param treeflags Flags for the word in the spell tree. -static bool spell_valid_case(int wordflags, int treeflags) +bool spell_valid_case(int wordflags, int treeflags) { return (wordflags == WF_ALLCAP && (treeflags & WF_FIXCAP) == 0) || ((treeflags & (WF_ALLCAP | WF_KEEPCAP)) == 0 @@ -1377,7 +1188,7 @@ static bool spell_valid_case(int wordflags, int treeflags) } // Returns true if spell checking is not enabled. -static bool no_spell_checking(win_T *wp) +bool no_spell_checking(win_T *wp) { if (!wp->w_p_spell || *wp->w_s->b_p_spl == NUL || GA_EMPTY(&wp->w_s->b_langp)) { @@ -1400,17 +1211,12 @@ static bool no_spell_checking(win_T *wp) /// @return 0 if not found, length of the badly spelled word otherwise. size_t spell_move_to(win_T *wp, int dir, bool allwords, bool curline, hlf_T *attrp) { - linenr_T lnum; pos_T found_pos; size_t found_len = 0; - char_u *line; - char_u *p; - char_u *endp; hlf_T attr = HLF_COUNT; size_t len; int has_syntax = syntax_present(wp); int col; - bool can_spell; char_u *buf = NULL; size_t buflen = 0; int skip = 0; @@ -1431,11 +1237,11 @@ size_t spell_move_to(win_T *wp, int dir, bool allwords, bool curline, hlf_T *att // We concatenate the start of the next line, so that wrapped words work // (e.g. "et<line-break>cetera"). Doesn't work when searching backwards // though... - lnum = wp->w_cursor.lnum; + linenr_T lnum = wp->w_cursor.lnum; clearpos(&found_pos); while (!got_int) { - line = ml_get_buf(wp->w_buffer, lnum, false); + char_u *line = ml_get_buf(wp->w_buffer, lnum, false); len = STRLEN(line); if (buflen < len + MAXWLEN + 2) { @@ -1452,10 +1258,10 @@ 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 = (int)getwhitecols(line); + capcol = (int)getwhitecols((char *)line); } else if (curline && wp == curwin) { // For spellbadword(): check if first word needs a capital. - col = (int)getwhitecols(line); + col = (int)getwhitecols((char *)line); if (check_need_cap(lnum, col)) { capcol = col; } @@ -1475,8 +1281,8 @@ size_t spell_move_to(win_T *wp, int dir, bool allwords, bool curline, hlf_T *att ml_get_buf(wp->w_buffer, lnum + 1, false), MAXWLEN); } - p = buf + skip; - endp = buf + len; + char_u *p = buf + skip; + char_u *endp = buf + len; while (p < endp) { // When searching backward don't search after the cursor. Unless // we wrapped around the end of the buffer. @@ -1502,10 +1308,11 @@ size_t spell_move_to(win_T *wp, int dir, bool allwords, bool curline, hlf_T *att || ((colnr_T)(curline ? p - buf + (ptrdiff_t)len : p - buf) > wp->w_cursor.col)) { + bool can_spell; if (has_syntax) { col = (int)(p - buf); (void)syn_get_id(wp, lnum, (colnr_T)col, - FALSE, &can_spell, FALSE); + false, &can_spell, false); if (!can_spell) { attr = HLF_COUNT; } @@ -1515,9 +1322,11 @@ size_t spell_move_to(win_T *wp, int dir, bool allwords, bool curline, hlf_T *att if (can_spell) { found_one = true; - found_pos.lnum = lnum; - found_pos.col = (int)(p - buf); - found_pos.coladd = 0; + found_pos = (pos_T) { + .lnum = lnum, + .col = (int)(p - buf), + .coladd = 0 + }; if (dir == FORWARD) { // No need to search further. wp->w_cursor = found_pos; @@ -1581,7 +1390,7 @@ size_t spell_move_to(win_T *wp, int dir, bool allwords, bool curline, hlf_T *att capcol = -1; } else { if (lnum < wp->w_buffer->b_ml.ml_line_count) { - ++lnum; + lnum++; } else if (!p_ws) { break; // at first line and 'nowrapscan' } else { @@ -1609,7 +1418,7 @@ size_t spell_move_to(win_T *wp, int dir, bool allwords, bool curline, hlf_T *att } // Capcol skips over the inserted space. - --capcol; + capcol--; // But after empty line check first word in next line if (empty_line) { @@ -1630,10 +1439,7 @@ size_t spell_move_to(win_T *wp, int dir, bool allwords, bool curline, hlf_T *att // to skip those bytes if the word was OK. void spell_cat_line(char_u *buf, char_u *line, int maxlen) { - char_u *p; - int n; - - p = (char_u *)skipwhite((char *)line); + char_u *p = (char_u *)skipwhite((char *)line); while (vim_strchr("*#/\"\t", *p) != NULL) { p = (char_u *)skipwhite((char *)p + 1); } @@ -1641,7 +1447,7 @@ void spell_cat_line(char_u *buf, char_u *line, int maxlen) if (*p != NUL) { // Only worth concatenating if there is something else than spaces to // concatenate. - n = (int)(p - line) + 1; + int n = (int)(p - line) + 1; if (n < maxlen - 1) { memset(buf, ' ', (size_t)n); STRLCPY(buf + n, p, maxlen - n); @@ -1656,7 +1462,6 @@ static void spell_load_lang(char_u *lang) char fname_enc[85]; int r; spelload_T sl; - int round; // Copy the language name to pass it to spell_load_cb() as a cookie. // It's truncated when an error is detected. @@ -1666,7 +1471,7 @@ static void spell_load_lang(char_u *lang) // We may retry when no spell file is found for the language, an // autocommand may load it then. - for (round = 1; round <= 2; ++round) { + 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, "spell/%s.%s.spl", lang, spell_enc()); @@ -1713,7 +1518,7 @@ static void spell_load_lang(char_u *lang) char_u *spell_enc(void) { if (STRLEN(p_enc) < 60 && STRCMP(p_enc, "iso-8859-15") != 0) { - return p_enc; + return (char_u *)p_enc; } return (char_u *)"latin1"; } @@ -1797,7 +1602,7 @@ void slang_clear(slang_T *lp) GA_DEEP_CLEAR(gap, salitem_T, free_salitem); } - for (int i = 0; i < lp->sl_prefixcnt; ++i) { + for (int i = 0; i < lp->sl_prefixcnt; i++) { vim_regfree(lp->sl_prefprog[i]); } lp->sl_prefixcnt = 0; @@ -1846,9 +1651,7 @@ 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; - - slang = spell_load_file((char_u *)fname, slp->sl_lang, NULL, false); + slang_T *slang = spell_load_file((char_u *)fname, slp->sl_lang, NULL, false); if (slang != NULL) { // When a previously loaded file has NOBREAK also use it for the // ".add" files. @@ -1871,9 +1674,6 @@ static void spell_load_cb(char *fname, void *cookie) /// @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) { - hash_T hash; - hashitem_T *hi; - wordcount_T *wc; char_u buf[MAXWLEN]; char_u *p; @@ -1886,9 +1686,10 @@ void count_common_word(slang_T *lp, char_u *word, int len, uint8_t count) p = buf; } - hash = hash_hash(p); + wordcount_T *wc; + hash_T hash = hash_hash(p); const size_t p_len = STRLEN(p); - hi = hash_lookup(&lp->sl_wordcount, (const char *)p, p_len, hash); + 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); @@ -1903,45 +1704,11 @@ void count_common_word(slang_T *lp, char_u *word, int len, uint8_t count) } } -/// Adjust the score of common words. -/// -/// @param split word was split, less bonus -static int score_wordcount_adj(slang_T *slang, int score, char_u *word, bool split) -{ - wordcount_T *wc; - int bonus; - 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; - } - return score; -} - // Returns true if byte "n" appears in "str". // Like strchr() but independent of locale. bool byte_in_str(char_u *str, int n) { - char_u *p; - - for (p = str; *p != NUL; ++p) { + for (char_u *p = str; *p != NUL; p++) { if (*p == n) { return true; } @@ -1953,19 +1720,16 @@ bool byte_in_str(char_u *str, int n) // in "slang->sl_syl_items". int init_syl_tab(slang_T *slang) { - char_u *p; - char_u *s; - int l; - ga_init(&slang->sl_syl_items, sizeof(syl_item_T), 4); - p = (char_u *)vim_strchr((char *)slang->sl_syllable, '/'); + char_u *p = (char_u *)vim_strchr((char *)slang->sl_syllable, '/'); while (p != NULL) { *p++ = NUL; if (*p == NUL) { // trailing slash break; } - s = p; + char_u *s = p; p = (char_u *)vim_strchr((char *)p, '/'); + int l; if (p == NULL) { l = (int)STRLEN(s); } else { @@ -1991,8 +1755,6 @@ static int count_syllables(slang_T *slang, const char_u *word) int cnt = 0; bool skip = false; int len; - syl_item_T *syl; - int c; if (slang->sl_syllable == NULL) { return 0; @@ -2008,24 +1770,24 @@ static int count_syllables(slang_T *slang, const char_u *word) // Find longest match of syllable items. len = 0; - for (int i = 0; i < slang->sl_syl_items.ga_len; ++i) { - syl = ((syl_item_T *)slang->sl_syl_items.ga_data) + i; + 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) { len = syl->sy_len; } } if (len != 0) { // found a match, count syllable - ++cnt; + cnt++; skip = false; } else { // No recognized syllable item, at least a syllable char then? - c = utf_ptr2char((char *)p); + int c = utf_ptr2char((char *)p); len = utfc_ptr2len((char *)p); if (vim_strchr((char *)slang->sl_syllable, c) == NULL) { skip = false; // No, search for next syllable } else if (!skip) { - ++cnt; // Yes, count it + cnt++; // Yes, count it skip = true; // don't count following syllable chars } } @@ -2033,12 +1795,12 @@ static int count_syllables(slang_T *slang, const char_u *word) return cnt; } -// Parse 'spelllang' and set w_s->b_langp accordingly. -// Returns NULL if it's OK, an error message otherwise. +/// Parse 'spelllang' and set w_s->b_langp accordingly. +/// @return NULL if it's OK, an untranslated error message otherwise. char *did_set_spelllang(win_T *wp) { garray_T ga; - char_u *splp; + char *splp; char_u *region; char_u region_cp[3]; bool filename; @@ -2050,7 +1812,7 @@ char *did_set_spelllang(win_T *wp) int len; char_u *p; int round; - char_u *spf; + char *spf; char_u *use_region = NULL; bool dont_use_region = false; bool nobreak = false; @@ -2075,18 +1837,18 @@ char *did_set_spelllang(win_T *wp) // Make a copy of 'spelllang', the SpellFileMissing autocommands may change // it under our fingers. - spl_copy = vim_strsave(wp->w_s->b_p_spl); + spl_copy = vim_strsave((char_u *)wp->w_s->b_p_spl); wp->w_s->b_cjk = 0; // Loop over comma separated language names. - for (splp = spl_copy; *splp != NUL;) { + for (splp = (char *)spl_copy; *splp != NUL;) { // Get one language name. - copy_option_part((char **)&splp, (char *)lang, MAXWLEN, ","); + copy_option_part(&splp, (char *)lang, MAXWLEN, ","); region = NULL; len = (int)STRLEN(lang); - if (!valid_spelllang(lang)) { + if (!valid_spelllang((char *)lang)) { continue; } @@ -2114,7 +1876,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, (char *)slang->sl_fname, false, true) + if (path_full_compare((char *)lang, slang->sl_fname, false, true) == kEqualFiles) { break; } @@ -2163,7 +1925,7 @@ 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, (char *)slang->sl_fname, false, true) == kEqualFiles + ? path_full_compare((char *)lang, slang->sl_fname, false, true) == kEqualFiles : STRICMP(lang, slang->sl_name) == 0) { region_mask = REGION_ALL; if (!filename && region != NULL) { @@ -2205,7 +1967,7 @@ char *did_set_spelllang(win_T *wp) // round 2: load second name in 'spellfile. // etc. spf = curwin->w_s->b_p_spf; - for (round = 0; round == 0 || *spf != NUL; ++round) { + for (round = 0; round == 0 || *spf != NUL; round++) { if (round == 0) { // Internal wordlist, if there is one. if (int_wordlist == NULL) { @@ -2214,12 +1976,12 @@ char *did_set_spelllang(win_T *wp) int_wordlist_spl(spf_name); } else { // One entry in 'spellfile'. - copy_option_part((char **)&spf, (char *)spf_name, MAXPATHL - 5, ","); + copy_option_part(&spf, (char *)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; + for (c = 0; c < ga.ga_len; c++) { + p = (char_u *)LANGP_ENTRY(ga, c)->lp_slang->sl_fname; if (p != NULL && path_full_compare((char *)spf_name, (char *)p, false, true) == kEqualFiles) { break; @@ -2232,7 +1994,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, (char *)slang->sl_fname, false, true) + if (path_full_compare((char *)spf_name, slang->sl_fname, false, true) == kEqualFiles) { break; } @@ -2290,7 +2052,7 @@ char *did_set_spelllang(win_T *wp) // For each language figure out what language to use for sound folding and // REP items. If the language doesn't support it itself use another one // with the same name. E.g. for "en-math" use "en". - for (int i = 0; i < ga.ga_len; ++i) { + for (int i = 0; i < ga.ga_len; i++) { lp = LANGP_ENTRY(ga, i); // sound folding @@ -2299,7 +2061,7 @@ char *did_set_spelllang(win_T *wp) lp->lp_sallang = lp->lp_slang; } else { // find first similar language that does sound folding - for (int j = 0; j < ga.ga_len; ++j) { + 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, @@ -2316,7 +2078,7 @@ char *did_set_spelllang(win_T *wp) lp->lp_replang = lp->lp_slang; } else { // find first similar language that has REP items - for (int j = 0; j < ga.ga_len; ++j) { + 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, @@ -2327,7 +2089,7 @@ char *did_set_spelllang(win_T *wp) } } } - redraw_later(wp, NOT_VALID); + redraw_later(wp, UPD_NOT_VALID); theend: xfree(spl_copy); @@ -2338,7 +2100,7 @@ theend: // Clear the midword characters for buffer "buf". static void clear_midword(win_T *wp) { - memset(wp->w_s->b_spell_ismw, 0, 256); + CLEAR_FIELD(wp->w_s->b_spell_ismw); XFREE_CLEAR(wp->w_s->b_spell_ismw_mb); } @@ -2358,13 +2120,13 @@ static void use_midword(slang_T *lp, win_T *wp) wp->w_s->b_spell_ismw[c] = true; } else if (wp->w_s->b_spell_ismw_mb == NULL) { // First multi-byte char in "b_spell_ismw_mb". - wp->w_s->b_spell_ismw_mb = vim_strnsave(p, (size_t)l); + wp->w_s->b_spell_ismw_mb = (char *)vim_strnsave(p, (size_t)l); } else { // Append multi-byte chars to "b_spell_ismw_mb". const int n = (int)STRLEN(wp->w_s->b_spell_ismw_mb); - char_u *bp = vim_strnsave(wp->w_s->b_spell_ismw_mb, (size_t)n + (size_t)l); + char_u *bp = vim_strnsave((char_u *)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; + wp->w_s->b_spell_ismw_mb = (char *)bp; STRLCPY(bp + n, p, l + 1); } p += l; @@ -2403,9 +2165,6 @@ int captype(char_u *word, char_u *end) FUNC_ATTR_NONNULL_ARG(1) { char_u *p; - int firstcap; - bool allcap; - bool past_second = false; // past second word char // find first letter for (p = word; !spell_iswordp_nmw(p, curwin); MB_PTR_ADV(p)) { @@ -2414,7 +2173,9 @@ int captype(char_u *word, char_u *end) } } int c = mb_ptr2char_adv((const char_u **)&p); - firstcap = allcap = SPELL_ISUPPER(c); + bool allcap; + bool firstcap = allcap = SPELL_ISUPPER(c); + bool past_second = false; // past second word char // Need to check all letters to find a word with mixed upper/lower. // But a word with an upper char only at start is a ONECAP. @@ -2444,57 +2205,11 @@ int captype(char_u *word, char_u *end) return 0; } -// Like captype() but for a KEEPCAP word add ONECAP if the word starts with a -// capital. So that make_case_word() can turn WOrd into Word. -// Add ALLCAP for "WOrD". -static int badword_captype(char_u *word, char_u *end) - FUNC_ATTR_NONNULL_ALL -{ - int flags = captype(word, 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 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; - } - } - return flags; -} - // Delete the internal wordlist and its .spl file. void spell_delete_wordlist(void) { - char_u fname[MAXPATHL] = { 0 }; - if (int_wordlist != NULL) { + char_u fname[MAXPATHL] = { 0 }; os_remove((char *)int_wordlist); int_wordlist_spl(fname); os_remove((char *)fname); @@ -2505,15 +2220,13 @@ void spell_delete_wordlist(void) // Free all languages. void spell_free_all(void) { - slang_T *slang; - // Go through all buffers and handle 'spelllang'. <VN> FOR_ALL_BUFFERS(buf) { ga_clear(&buf->b_s.b_langp); } while (first_lang != NULL) { - slang = first_lang; + slang_T *slang = first_lang; first_lang = slang->sl_next; slang_free(slang); } @@ -2547,36 +2260,6 @@ void spell_reload(void) } } -// Opposite of offset2bytes(). -// "pp" points to the bytes and is advanced over it. -// Returns the offset. -static int bytes2offset(char_u **pp) -{ - char_u *p = *pp; - int nr; - int c; - - c = *p++; - if ((c & 0x80) == 0x00) { // 1 byte - nr = c - 1; - } else if ((c & 0xc0) == 0x80) { // 2 bytes - nr = (c & 0x3f) - 1; - nr = nr * 255 + (*p++ - 1); - } else if ((c & 0xe0) == 0xc0) { // 3 bytes - nr = (c & 0x1f) - 1; - nr = nr * 255 + (*p++ - 1); - nr = nr * 255 + (*p++ - 1); - } else { // 4 bytes - nr = (c & 0x0f) - 1; - nr = nr * 255 + (*p++ - 1); - nr = nr * 255 + (*p++ - 1); - nr = nr * 255 + (*p++ - 1); - } - - *pp = p; - return nr; -} - // Open a spell buffer. This is a nameless buffer that is not in the buffer // list and only contains text lines. Can use a swapfile to reduce memory // use. @@ -2600,7 +2283,7 @@ buf_T *open_spellbuf(void) void close_spellbuf(buf_T *buf) { if (buf != NULL) { - ml_close(buf, TRUE); + ml_close(buf, true); xfree(buf); } } @@ -2608,28 +2291,26 @@ void close_spellbuf(buf_T *buf) // Init the chartab used for spelling for ASCII. void clear_spell_chartab(spelltab_T *sp) { - int i; + // Init everything to false (zero). + CLEAR_FIELD(sp->st_isw); + CLEAR_FIELD(sp->st_isu); - // Init everything to false. - memset(sp->st_isw, false, sizeof(sp->st_isw)); - memset(sp->st_isu, false, sizeof(sp->st_isu)); - - for (i = 0; i < 256; i++) { + for (int i = 0; i < 256; i++) { sp->st_fold[i] = (char_u)i; sp->st_upper[i] = (char_u)i; } // We include digits. A word shouldn't start with a digit, but handling // that is done separately. - for (i = '0'; i <= '9'; ++i) { + for (int i = '0'; i <= '9'; i++) { sp->st_isw[i] = true; } - for (i = 'A'; i <= 'Z'; ++i) { + for (int i = 'A'; i <= 'Z'; i++) { sp->st_isw[i] = true; sp->st_isu[i] = true; sp->st_fold[i] = (char_u)(i + 0x20); } - for (i = 'a'; i <= 'z'; ++i) { + for (int i = 'a'; i <= 'z'; i++) { sp->st_isw[i] = true; sp->st_upper[i] = (char_u)(i - 0x20); } @@ -2641,11 +2322,9 @@ void clear_spell_chartab(spelltab_T *sp) // locale. For utf-8 we don't use isalpha() but our own functions. void init_spell_chartab(void) { - int i; - did_set_spelltab = false; clear_spell_chartab(&spelltab); - for (i = 128; i < 256; i++) { + for (int i = 128; i < 256; i++) { int f = utf_fold(i); int u = mb_toupper(i); @@ -2665,11 +2344,9 @@ void init_spell_chartab(void) /// Thus this only works properly when past the first character of the word. /// /// @param wp Buffer used. -static bool spell_iswordp(const char_u *p, const win_T *wp) +bool spell_iswordp(const char_u *p, const win_T *wp) FUNC_ATTR_NONNULL_ALL { - int c; - const int l = utfc_ptr2len((char *)p); const char_u *s = p; if (l == 1) { @@ -2678,16 +2355,16 @@ static bool spell_iswordp(const char_u *p, const win_T *wp) s = p + 1; // skip a mid-word character } } else { - c = utf_ptr2char((char *)p); + int c = utf_ptr2char((char *)p); if (c < 256 ? wp->w_s->b_spell_ismw[c] : (wp->w_s->b_spell_ismw_mb != NULL - && vim_strchr((char *)wp->w_s->b_spell_ismw_mb, c) != NULL)) { + && vim_strchr(wp->w_s->b_spell_ismw_mb, c) != NULL)) { s = p + l; } } - c = utf_ptr2char((char *)s); + int c = utf_ptr2char((char *)s); if (c > 255) { return spell_mb_isword_class(mb_get_class(s), wp); } @@ -2728,7 +2405,7 @@ static bool spell_iswordp_w(const int *p, const win_T *wp) if (*p < 256 ? wp->w_s->b_spell_ismw[*p] : (wp->w_s->b_spell_ismw_mb != NULL - && vim_strchr((char *)wp->w_s->b_spell_ismw_mb, + && vim_strchr(wp->w_s->b_spell_ismw_mb, *p) != NULL)) { s = p + 1; } else { @@ -2783,313 +2460,20 @@ int spell_casefold(const win_T *wp, char_u *str, int len, char_u *buf, int bufle return OK; } -// values for sps_flags -#define SPS_BEST 1 -#define SPS_FAST 2 -#define SPS_DOUBLE 4 - -static int sps_flags = SPS_BEST; // flags from 'spellsuggest' -static int sps_limit = 9999; // max nr of suggestions given - -// Check the 'spellsuggest' option. Return FAIL if it's wrong. -// Sets "sps_flags" and "sps_limit". -int spell_check_sps(void) -{ - char_u *p; - char_u *s; - char_u buf[MAXPATHL]; - int f; - - sps_flags = 0; - sps_limit = 9999; - - for (p = p_sps; *p != NUL;) { - copy_option_part((char **)&p, (char *)buf, MAXPATHL, ","); - - f = 0; - if (ascii_isdigit(*buf)) { - s = buf; - sps_limit = getdigits_int((char **)&s, true, 0); - if (*s != NUL && !ascii_isdigit(*s)) { - f = -1; - } - } else if (STRCMP(buf, "best") == 0) { - f = SPS_BEST; - } else if (STRCMP(buf, "fast") == 0) { - f = SPS_FAST; - } else if (STRCMP(buf, "double") == 0) { - f = SPS_DOUBLE; - } else if (STRNCMP(buf, "expr:", 5) != 0 - && STRNCMP(buf, "file:", 5) != 0) { - f = -1; - } - - if (f == -1 || (sps_flags != 0 && f != 0)) { - sps_flags = SPS_BEST; - sps_limit = 9999; - return FAIL; - } - if (f != 0) { - sps_flags = f; - } - } - - if (sps_flags == 0) { - sps_flags = SPS_BEST; - } - - return OK; -} - -// "z=": Find badly spelled word under or after the cursor. -// Give suggestions for the properly spelled word. -// In Visual mode use the highlighted word as the bad word. -// When "count" is non-zero use that suggestion. -void spell_suggest(int count) -{ - char_u *line; - pos_T prev_cursor = curwin->w_cursor; - char_u wcopy[MAXWLEN + 2]; - char_u *p; - int c; - suginfo_T sug; - suggest_T *stp; - int mouse_used; - int need_cap; - int limit; - int selected = count; - int badlen = 0; - int msg_scroll_save = msg_scroll; - const int wo_spell_save = curwin->w_p_spell; - - if (!curwin->w_p_spell) { - did_set_spelllang(curwin); - curwin->w_p_spell = true; - } - - if (*curwin->w_s->b_p_spl == NUL) { - emsg(_(e_no_spell)); - return; - } - - if (VIsual_active) { - // Use the Visually selected text as the bad word. But reject - // a multi-line selection. - if (curwin->w_cursor.lnum != VIsual.lnum) { - vim_beep(BO_SPELL); - return; - } - badlen = (int)curwin->w_cursor.col - (int)VIsual.col; - if (badlen < 0) { - badlen = -badlen; - } else { - curwin->w_cursor.col = VIsual.col; - } - badlen++; - end_visual_mode(); - } else - // Find the start of the badly spelled word. - if (spell_move_to(curwin, FORWARD, true, true, NULL) == 0 - || curwin->w_cursor.col > prev_cursor.col) { - // No bad word or it starts after the cursor: use the word under the - // cursor. - curwin->w_cursor = prev_cursor; - line = get_cursor_line_ptr(); - p = line + curwin->w_cursor.col; - // Backup to before start of word. - while (p > line && spell_iswordp_nmw(p, curwin)) { - MB_PTR_BACK(line, p); - } - // Forward to start of word. - while (*p != NUL && !spell_iswordp_nmw(p, curwin)) { - MB_PTR_ADV(p); - } - - if (!spell_iswordp_nmw(p, curwin)) { // No word found. - beep_flush(); - return; - } - curwin->w_cursor.col = (colnr_T)(p - line); - } - - // Get the word and its length. - - // Figure out if the word should be capitalised. - need_cap = check_need_cap(curwin->w_cursor.lnum, curwin->w_cursor.col); - - // Make a copy of current line since autocommands may free the line. - line = vim_strsave(get_cursor_line_ptr()); - - // Get the list of suggestions. Limit to 'lines' - 2 or the number in - // 'spellsuggest', whatever is smaller. - if (sps_limit > Rows - 2) { - limit = Rows - 2; - } else { - limit = sps_limit; - } - spell_find_suggest(line + curwin->w_cursor.col, badlen, &sug, limit, - true, need_cap, true); - - if (GA_EMPTY(&sug.su_ga)) { - msg(_("Sorry, no suggestions")); - } else if (count > 0) { - if (count > sug.su_ga.ga_len) { - smsg(_("Sorry, only %" PRId64 " suggestions"), - (int64_t)sug.su_ga.ga_len); - } - } else { - // When 'rightleft' is set the list is drawn right-left. - cmdmsg_rl = curwin->w_p_rl; - if (cmdmsg_rl) { - msg_col = Columns - 1; - } - - // List the suggestions. - msg_start(); - msg_row = Rows - 1; // for when 'cmdheight' > 1 - lines_left = Rows; // avoid more prompt - vim_snprintf((char *)IObuff, IOSIZE, _("Change \"%.*s\" to:"), - sug.su_badlen, sug.su_badptr); - 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", - sug.su_badlen, sug.su_badptr); - } - msg_puts((const char *)IObuff); - msg_clr_eos(); - msg_putchar('\n'); - - msg_scroll = TRUE; - for (int i = 0; i < sug.su_ga.ga_len; ++i) { - stp = &SUG(sug.su_ga, i); - - // 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); - 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); - } - vim_snprintf((char *)IObuff, IOSIZE, "%2d", i + 1); - if (cmdmsg_rl) { - rl_mirror(IObuff); - } - msg_puts((const char *)IObuff); - - vim_snprintf((char *)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\""), - stp->st_orglen, sug.su_badptr); - msg_puts((const char *)IObuff); - } - - if (p_verbose > 0) { - // Add the score. - if (sps_flags & (SPS_DOUBLE | SPS_BEST)) { - vim_snprintf((char *)IObuff, IOSIZE, " (%s%d - %d)", - stp->st_salscore ? "s " : "", - stp->st_score, stp->st_altscore); - } else { - vim_snprintf((char *)IObuff, IOSIZE, " (%d)", - stp->st_score); - } - if (cmdmsg_rl) { - // Mirror the numbers, but keep the leading space. - rl_mirror(IObuff + 1); - } - msg_advance(30); - msg_puts((const char *)IObuff); - } - msg_putchar('\n'); - } - - cmdmsg_rl = FALSE; - msg_col = 0; - // Ask for choice. - selected = prompt_for_number(&mouse_used); - - if (ui_has(kUIMessages)) { - ui_call_msg_clear(); - } - - if (mouse_used) { - selected -= lines_left; - } - lines_left = Rows; // avoid more prompt - // don't delay for 'smd' in normal_cmd() - msg_scroll = msg_scroll_save; - } - - if (selected > 0 && selected <= sug.su_ga.ga_len && u_save_cursor() == OK) { - // Save the from and to text for :spellrepall. - XFREE_CLEAR(repl_from); - XFREE_CLEAR(repl_to); - - stp = &SUG(sug.su_ga, selected - 1); - if (sug.su_badlen > stp->st_orglen) { - // Replacing less than "su_badlen", append the remainder to - // repl_to. - repl_from = vim_strnsave(sug.su_badptr, (size_t)sug.su_badlen); - vim_snprintf((char *)IObuff, IOSIZE, "%s%.*s", stp->st_word, - sug.su_badlen - stp->st_orglen, - sug.su_badptr + stp->st_orglen); - repl_to = vim_strsave(IObuff); - } else { - // Replacing su_badlen or more, use the whole word. - repl_from = vim_strnsave(sug.su_badptr, (size_t)stp->st_orglen); - repl_to = vim_strsave(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 - line); - memmove(p, line, (size_t)c); - STRCPY(p + c, stp->st_word); - STRCAT(p, sug.su_badptr + stp->st_orglen); - - // For redo we use a change-word command. - ResetRedobuff(); - AppendToRedobuff("ciw"); - AppendToRedobuffLit((char *)p + c, - stp->st_wordlen + sug.su_badlen - stp->st_orglen); - AppendCharToRedobuff(ESC); - - // "p" may be freed here - ml_replace(curwin->w_cursor.lnum, (char *)p, false); - curwin->w_cursor.col = c; - - inserted_bytes(curwin->w_cursor.lnum, c, stp->st_orglen, stp->st_wordlen); - } else { - curwin->w_cursor = prev_cursor; - } - - spell_find_cleanup(&sug); - xfree(line); - curwin->w_p_spell = wo_spell_save; -} - // Check if the word at line "lnum" column "col" is required to start with a // capital. This uses 'spellcapcheck' of the current buffer. -static bool check_need_cap(linenr_T lnum, colnr_T col) +bool check_need_cap(linenr_T lnum, colnr_T col) { bool need_cap = false; - char_u *line; - char_u *line_copy = NULL; - char_u *p; - colnr_T endcol; - regmatch_T regmatch; if (curwin->w_s->b_cap_prog == NULL) { return false; } - line = get_cursor_line_ptr(); - endcol = 0; - if (getwhitecols(line) >= (int)col) { + char_u *line = get_cursor_line_ptr(); + char_u *line_copy = NULL; + colnr_T endcol = 0; + if (getwhitecols((char *)line) >= (int)col) { // At start of line, check if previous line is empty or sentence // ends there. if (lnum == 1) { @@ -3100,7 +2484,7 @@ static bool check_need_cap(linenr_T lnum, colnr_T col) need_cap = true; } else { // Append a space in place of the line break. - line_copy = concat_str(line, (char_u *)" "); + line_copy = (char_u *)concat_str((char *)line, " "); line = line_copy; endcol = (colnr_T)STRLEN(line); } @@ -3111,9 +2495,11 @@ static bool check_need_cap(linenr_T lnum, colnr_T col) if (endcol > 0) { // Check if sentence ends before the bad word. - regmatch.regprog = curwin->w_s->b_cap_prog; - regmatch.rm_ic = FALSE; - p = line + endcol; + regmatch_T regmatch = { + .regprog = curwin->w_s->b_cap_prog, + .rm_ic = false + }; + char_u *p = line + endcol; for (;;) { MB_PTR_BACK(line, p); if (p == line || spell_iswordp_nmw(p, curwin)) { @@ -3137,10 +2523,6 @@ static bool check_need_cap(linenr_T lnum, colnr_T col) void ex_spellrepall(exarg_T *eap) { pos_T pos = curwin->w_cursor; - char_u *frompat; - int addlen; - char_u *line; - char_u *p; bool save_ws = p_ws; linenr_T prev_lnum = 0; @@ -3148,10 +2530,11 @@ void ex_spellrepall(exarg_T *eap) emsg(_("E752: No previous spell replacement")); return; } - addlen = (int)(STRLEN(repl_to) - STRLEN(repl_from)); + int addlen = (int)(STRLEN(repl_to) - STRLEN(repl_from)); - frompat = xmalloc(STRLEN(repl_from) + 7); - sprintf((char *)frompat, "\\V\\<%s\\>", repl_from); + size_t frompatlen = STRLEN(repl_from) + 7; + char_u *frompat = xmalloc(frompatlen); + snprintf((char *)frompat, frompatlen, "\\V\\<%s\\>", repl_from); p_ws = false; sub_nsubs = 0; @@ -3165,10 +2548,10 @@ void ex_spellrepall(exarg_T *eap) // Only replace when the right word isn't there yet. This happens // when changing "etc" to "etc.". - line = get_cursor_line_ptr(); + char_u *line = get_cursor_line_ptr(); if (addlen <= 0 || STRNCMP(line + curwin->w_cursor.col, repl_to, STRLEN(repl_to)) != 0) { - p = xmalloc(STRLEN(line) + (size_t)addlen + 1); + char_u *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)); @@ -3176,10 +2559,10 @@ void ex_spellrepall(exarg_T *eap) changed_bytes(curwin->w_cursor.lnum, curwin->w_cursor.col); if (curwin->w_cursor.lnum != prev_lnum) { - ++sub_nlines; + sub_nlines++; prev_lnum = curwin->w_cursor.lnum; } - ++sub_nsubs; + sub_nsubs++; } curwin->w_cursor.col += (colnr_T)STRLEN(repl_to); } @@ -3195,336 +2578,6 @@ void ex_spellrepall(exarg_T *eap) } } -/// Find spell suggestions for "word". Return them in the growarray "*gap" as -/// a list of allocated strings. -/// -/// @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) -{ - suginfo_T sug; - suggest_T *stp; - char_u *wcopy; - - spell_find_suggest(word, 0, &sug, maxcount, false, need_cap, interactive); - - // Make room in "gap". - ga_init(gap, sizeof(char_u *), sug.su_ga.ga_len + 1); - ga_grow(gap, sug.su_ga.ga_len); - for (int i = 0; i < sug.su_ga.ga_len; ++i) { - stp = &SUG(sug.su_ga, i); - - // 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); - 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; - } - - spell_find_cleanup(&sug); -} - -/// Find spell suggestions for the word at the start of "badptr". -/// Return the suggestions in "su->su_ga". -/// The maximum number of suggestions is "maxcount". -/// Note: does use info for the current window. -/// This is based on the mechanisms of Aspell, but completely reimplemented. -/// -/// @param badlen length of bad word or 0 if unknown -/// @param banbadword don't include badword in suggestions -/// @param need_cap word should start with capital -static void spell_find_suggest(char_u *badptr, int badlen, suginfo_T *su, int maxcount, - bool banbadword, bool need_cap, bool interactive) -{ - hlf_T attr = HLF_COUNT; - char_u buf[MAXPATHL]; - char_u *p; - bool do_combine = false; - char_u *sps_copy; - static bool expr_busy = false; - int c; - langp_T *lp; - bool did_intern = false; - - // Set the info in "*su". - memset(su, 0, sizeof(suginfo_T)); - ga_init(&su->su_ga, (int)sizeof(suggest_T), 10); - ga_init(&su->su_sga, (int)sizeof(suggest_T), 10); - if (*badptr == NUL) { - return; - } - hash_init(&su->su_banned); - - su->su_badptr = badptr; - if (badlen != 0) { - su->su_badlen = badlen; - } else { - size_t tmplen = spell_check(curwin, su->su_badptr, &attr, NULL, false); - assert(tmplen <= INT_MAX); - su->su_badlen = (int)tmplen; - } - su->su_maxcount = maxcount; - su->su_maxscore = SCORE_MAXINIT; - - if (su->su_badlen >= MAXWLEN) { - su->su_badlen = MAXWLEN - 1; // just in case - } - STRLCPY(su->su_badword, su->su_badptr, su->su_badlen + 1); - (void)spell_casefold(curwin, su->su_badptr, su->su_badlen, su->su_fbadword, - MAXWLEN); - - // TODO(vim): make this work if the case-folded text is longer than the - // original text. Currently an illegal byte causes wrong pointer - // computations. - 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); - if (need_cap) { - su->su_badflags |= WF_ONECAP; - } - - // Find the default language for sound folding. We simply use the first - // one in 'spelllang' that supports sound folding. That's good for when - // using multiple files for one language, it's not that bad when mixing - // languages (e.g., "pl,en"). - for (int i = 0; i < curbuf->b_s.b_langp.ga_len; ++i) { - lp = LANGP_ENTRY(curbuf->b_s.b_langp, i); - if (lp->lp_sallang != NULL) { - su->su_sallang = lp->lp_sallang; - break; - } - } - - // 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); - } - - // 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); - if (!SPELL_ISUPPER(c) && attr == HLF_COUNT) { - make_case_word(su->su_badword, buf, WF_ONECAP); - add_suggestion(su, &su->su_ga, 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); - } - - // Make a copy of 'spellsuggest', because the expression may change it. - sps_copy = vim_strsave(p_sps); - - // Loop over the items in 'spellsuggest'. - for (p = sps_copy; *p != NUL;) { - copy_option_part((char **)&p, (char *)buf, MAXPATHL, ","); - - 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); - expr_busy = false; - } - } else if (STRNCMP(buf, "file:", 5) == 0) { - // Use list of suggestions in a file. - spell_suggest_file(su, buf + 5); - } else if (!did_intern) { - // Use internal method once. - spell_suggest_intern(su, interactive); - if (sps_flags & SPS_DOUBLE) { - do_combine = true; - } - did_intern = true; - } - } - - xfree(sps_copy); - - if (do_combine) { - // Combine the two list of suggestions. This must be done last, - // because sorting changes the order again. - score_combine(su); - } -} - -// Find suggestions by evaluating expression "expr". -static void spell_suggest_expr(suginfo_T *su, char_u *expr) -{ - int score; - const char *p; - - // The work is split up in a few parts to avoid having to export - // suginfo_T. - // First evaluate the expression and get the resulting list. - list_T *const list = eval_spell_expr((char *)su->su_badword, (char *)expr); - if (list != NULL) { - // Loop over the items in the list. - TV_LIST_ITER(list, li, { - if (TV_LIST_ITEM_TV(li)->v_type == VAR_LIST) { - // Get the word and the score from the items. - score = get_spellword(TV_LIST_ITEM_TV(li)->vval.v_list, &p); - if (score >= 0 && score <= su->su_maxscore) { - add_suggestion(su, &su->su_ga, (const char_u *)p, su->su_badlen, - score, 0, true, su->su_sallang, false); - } - } - }); - tv_list_unref(list); - } - - // Remove bogus suggestions, sort and truncate at "maxcount". - check_suggestions(su, &su->su_ga); - (void)cleanup_suggestions(&su->su_ga, su->su_maxscore, su->su_maxcount); -} - -// Find suggestions in file "fname". Used for "file:" in 'spellsuggest'. -static void spell_suggest_file(suginfo_T *su, char_u *fname) -{ - FILE *fd; - char_u line[MAXWLEN * 2]; - char_u *p; - int len; - char_u cword[MAXWLEN]; - - // Open the file. - fd = os_fopen((char *)fname, "r"); - if (fd == NULL) { - semsg(_(e_notopen), fname); - return; - } - - // Read it line by line. - while (!vim_fgets(line, MAXWLEN * 2, fd) && !got_int) { - line_breakcheck(); - - p = (char_u *)vim_strchr((char *)line, '/'); - if (p == NULL) { - continue; // No Tab found, just skip the line. - } - *p++ = NUL; - if (STRICMP(su->su_badword, line) == 0) { - // Match! Isolate the good word, until CR or NL. - for (len = 0; p[len] >= ' '; len++) {} - p[len] = NUL; - - // 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); - p = cword; - } - - add_suggestion(su, &su->su_ga, p, su->su_badlen, - SCORE_FILE, 0, true, su->su_sallang, false); - } - } - - fclose(fd); - - // Remove bogus suggestions, sort and truncate at "maxcount". - check_suggestions(su, &su->su_ga); - (void)cleanup_suggestions(&su->su_ga, su->su_maxscore, su->su_maxcount); -} - -// Find suggestions for the internal method indicated by "sps_flags". -static void spell_suggest_intern(suginfo_T *su, bool interactive) -{ - // Load the .sug file(s) that are available and not done yet. - suggest_load_files(); - - // 1. Try special cases, such as repeating a word: "the the" -> "the". - // - // Set a maximum score to limit the combination of operations that is - // tried. - suggest_try_special(su); - - // 2. Try inserting/deleting/swapping/changing a letter, use REP entries - // from the .aff file and inserting a space (split the word). - suggest_try_change(su); - - // For the resulting top-scorers compute the sound-a-like score. - if (sps_flags & SPS_DOUBLE) { - score_comp_sal(su); - } - - // 3. Try finding sound-a-like words. - if ((sps_flags & SPS_FAST) == 0) { - if (sps_flags & SPS_BEST) { - // Adjust the word score for the suggestions found so far for how - // they sounds like. - rescore_suggestions(su); - } - - // While going through the soundfold tree "su_maxscore" is the score - // for the soundfold word, limits the changes that are being tried, - // and "su_sfmaxscore" the rescored score, which is set by - // cleanup_suggestions(). - // First find words with a small edit distance, because this is much - // faster and often already finds the top-N suggestions. If we didn't - // find many suggestions try again with a higher edit distance. - // "sl_sounddone" is used to avoid doing the same word twice. - suggest_try_soundalike_prep(); - su->su_maxscore = SCORE_SFMAX1; - su->su_sfmaxscore = SCORE_MAXINIT * 3; - suggest_try_soundalike(su); - if (su->su_ga.ga_len < SUG_CLEAN_COUNT(su)) { - // We didn't find enough matches, try again, allowing more - // changes to the soundfold word. - su->su_maxscore = SCORE_SFMAX2; - suggest_try_soundalike(su); - if (su->su_ga.ga_len < SUG_CLEAN_COUNT(su)) { - // Still didn't find enough matches, try again, allowing even - // more changes to the soundfold word. - su->su_maxscore = SCORE_SFMAX3; - suggest_try_soundalike(su); - } - } - su->su_maxscore = su->su_sfmaxscore; - suggest_try_soundalike_finish(); - } - - // When CTRL-C was hit while searching do show the results. Only clear - // got_int when using a command, not for spellsuggest(). - os_breakcheck(); - if (interactive && got_int) { - (void)vgetc(); - got_int = FALSE; - } - - if ((sps_flags & SPS_DOUBLE) == 0 && su->su_ga.ga_len != 0) { - if (sps_flags & SPS_BEST) { - // Adjust the word score for how it sounds like. - rescore_suggestions(su); - } - - // Remove bogus suggestions, sort and truncate at "maxcount". - check_suggestions(su, &su->su_ga); - (void)cleanup_suggestions(&su->su_ga, su->su_maxscore, su->su_maxcount); - } -} - -// Free the info put in "*su" by spell_find_suggest(). -static void spell_find_cleanup(suginfo_T *su) -{ -#define FREE_SUG_WORD(sug) xfree((sug)->st_word) - // Free the suggestions. - GA_DEEP_CLEAR(&su->su_ga, suggest_T, FREE_SUG_WORD); - GA_DEEP_CLEAR(&su->su_sga, suggest_T, FREE_SUG_WORD); - - // Free the banned words. - hash_clear_all(&su->su_banned, 0); -} - /// Make a copy of "word", with the first letter upper or lower cased, to /// "wcopy[MAXWLEN]". "word" must not be empty. /// The result is NUL terminated. @@ -3547,7 +2600,7 @@ void onecap_copy(char_u *word, char_u *wcopy, bool upper) // Make a copy of "word" with all the letters upper cased into // "wcopy[MAXWLEN]". The result is NUL terminated. -static void allcap_copy(char_u *word, char_u *wcopy) +void allcap_copy(char_u *word, char_u *wcopy) { char_u *d = wcopy; for (char_u *s = word; *s != NUL;) { @@ -3571,1383 +2624,9 @@ static void allcap_copy(char_u *word, char_u *wcopy) *d = NUL; } -// Try finding suggestions by recognizing specific situations. -static void suggest_try_special(suginfo_T *su) -{ - int c; - char_u word[MAXWLEN]; - - // Recognize a word that is repeated: "the the". - char_u *p = skiptowhite(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) { - // 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; - - // Give a soundalike score of 0, compute the score as if deleting one - // character. - add_suggestion(su, &su->su_ga, word, su->su_badlen, - RESCORE(SCORE_REP, 0), 0, true, su->su_sallang, false); - } -} - -// Measure how much time is spent in each state. -// Output is dumped in "suggestprof". - -#ifdef SUGGEST_PROFILE -proftime_T current; -proftime_T total; -proftime_T times[STATE_FINAL + 1]; -long counts[STATE_FINAL + 1]; - -static void prof_init(void) -{ - for (int i = 0; i <= STATE_FINAL; i++) { - profile_zero(×[i]); - counts[i] = 0; - } - profile_start(¤t); - profile_start(&total); -} - -// call before changing state -static void prof_store(state_T state) -{ - profile_end(¤t); - profile_add(×[state], ¤t); - counts[state]++; - profile_start(¤t); -} -# define PROF_STORE(state) prof_store(state); - -static void prof_report(char *name) -{ - FILE *fd = fopen("suggestprof", "a"); - - profile_end(&total); - fprintf(fd, "-----------------------\n"); - fprintf(fd, "%s: %s\n", name, profile_msg(&total)); - for (int i = 0; i <= STATE_FINAL; i++) { - fprintf(fd, "%d: %s ("%" PRId64)\n", i, profile_msg(×[i]), counts[i]); - } - fclose(fd); -} -#else -# define PROF_STORE(state) -#endif - -// 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 - int n; - char_u *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); - p = su->su_badptr + su->su_badlen; - (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); - if (n < MAXWLEN) { - fword[n] = NUL; - } - - for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len; ++lpi) { - lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi); - - // If reloading a spell file fails it's still in the list but - // everything has been cleared. - if (lp->lp_slang->sl_fbyts == NULL) { - continue; - } - - // Try it for this language. Will add possible suggestions. - // -#ifdef SUGGEST_PROFILE - prof_init(); -#endif - suggest_trie_walk(su, lp, fword, false); -#ifdef SUGGEST_PROFILE - prof_report("try_change"); -#endif - } -} - -// Check the maximum score, if we go over it we won't try this change. -#define TRY_DEEPER(su, stack, depth, add) \ - ((depth) < MAXWLEN - 1 && (stack)[depth].ts_score + (add) < (su)->su_maxscore) - -// Try finding suggestions by adding/removing/swapping letters. -// -// This uses a state machine. At each node in the tree we try various -// operations. When trying if an operation works "depth" is increased and the -// stack[] is used to store info. This allows combinations, thus insert one -// character, replace one and delete another. The number of changes is -// limited by su->su_maxscore. -// -// After implementing this I noticed an article by Kemal Oflazer that -// describes something similar: "Error-tolerant Finite State Recognition with -// Applications to Morphological Analysis and Spelling Correction" (1996). -// The implementation in the article is simplified and requires a stack of -// unknown depth. The implementation here only needs a stack depth equal to -// the length of the word. -// -// This is also used for the sound-folded word, "soundfold" is true then. -// The mechanism is the same, but we find a match with a sound-folded word -// that comes from one or more original words. Each of these words may be -// added, this is done by add_sound_suggest(). -// Don't use: -// the prefix tree or the keep-case tree -// "su->su_badlen" -// anything to do with upper and lower case -// anything to do with word or non-word characters ("spell_iswordp()") -// banned words -// word flags (rare, region, compounding) -// 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) -{ - char_u tword[MAXWLEN]; // good word collected so far - trystate_T stack[MAXWLEN]; - char_u preword[MAXWLEN * 3] = { 0 }; // word found with proper case; - // concatenation of prefix compound - // words and split word. NUL terminated - // when going deeper but not when coming - // back. - char_u compflags[MAXWLEN]; // compound flags, one for each word - trystate_T *sp; - int newscore; - int score; - char_u *byts, *fbyts, *pbyts; - idx_T *idxs, *fidxs, *pidxs; - int depth; - int c, c2, c3; - int n = 0; - int flags; - garray_T *gap; - idx_T arridx; - int len; - char_u *p; - fromto_T *ftp; - int fl = 0, tl; - int repextra = 0; // extra bytes in fword[] from REP item - slang_T *slang = lp->lp_slang; - int fword_ends; - bool goodword_ends; -#ifdef DEBUG_TRIEWALK - // Stores the name of the change made at each level. - char_u changename[MAXWLEN][80]; -#endif - int breakcheckcount = 1000; - bool compound_ok; - - // Go through the whole case-fold tree, try changes at each node. - // "tword[]" contains the word collected from nodes in the tree. - // "fword[]" the word we are trying to match with (initially the bad - // word). - depth = 0; - sp = &stack[0]; - memset(sp, 0, sizeof(trystate_T)); // -V512 - sp->ts_curi = 1; - - if (soundfold) { - // Going through the soundfold tree. - byts = fbyts = slang->sl_sbyts; - idxs = fidxs = slang->sl_sidxs; - pbyts = NULL; - pidxs = NULL; - sp->ts_prefixdepth = PFD_NOPREFIX; - sp->ts_state = STATE_START; - } 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; - fidxs = slang->sl_fidxs; - pbyts = slang->sl_pbyts; - pidxs = slang->sl_pidxs; - if (pbyts != NULL) { - byts = pbyts; - idxs = pidxs; - sp->ts_prefixdepth = PFD_PREFIXTREE; - sp->ts_state = STATE_NOPREFIX; // try without prefix first - } else { - byts = fbyts; - idxs = fidxs; - sp->ts_prefixdepth = PFD_NOPREFIX; - sp->ts_state = STATE_START; - } - } - - // The loop may take an indefinite amount of time. Break out after five - // sectonds. TODO(vim): add an option for the time limit. - proftime_T time_limit = profile_setlimit(5000); - - // Loop to find all suggestions. At each round we either: - // - For the current state try one operation, advance "ts_curi", - // increase "depth". - // - When a state is done go to the next, set "ts_state". - // - When all states are tried decrease "depth". - while (depth >= 0 && !got_int) { - sp = &stack[depth]; - switch (sp->ts_state) { - case STATE_START: - case STATE_NOPREFIX: - // Start of node: Deal with NUL bytes, which means - // tword[] may end here. - arridx = sp->ts_arridx; // current node in the tree - len = byts[arridx]; // bytes in this node - arridx += sp->ts_curi; // index of current byte - - if (sp->ts_prefixdepth == PFD_PREFIXTREE) { - // Skip over the NUL bytes, we use them later. - for (n = 0; n < len && byts[arridx + n] == 0; n++) {} - sp->ts_curi = (int16_t)(sp->ts_curi + n); - - // Always past NUL bytes now. - n = (int)sp->ts_state; - PROF_STORE(sp->ts_state) - sp->ts_state = STATE_ENDNUL; - sp->ts_save_badflags = (char_u)su->su_badflags; - - // At end of a prefix or at start of prefixtree: check for - // following word. - if (depth < MAXWLEN - 1 && (byts[arridx] == 0 || n == STATE_NOPREFIX)) { - // Set su->su_badflags to the caps type at this position. - // Use the caps type until here for the prefix itself. - n = nofold_len(fword, sp->ts_fidx, su->su_badptr); - 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); -#ifdef DEBUG_TRIEWALK - sprintf(changename[depth], "prefix"); -#endif - go_deeper(stack, depth, 0); - ++depth; - sp = &stack[depth]; - sp->ts_prefixdepth = (char_u)(depth - 1); - byts = fbyts; - idxs = fidxs; - sp->ts_arridx = 0; - - // Move the prefix to preword[] with the right case - // and make find_keepcap_word() works. - tword[sp->ts_twordlen] = NUL; - make_case_word(tword + sp->ts_splitoff, - preword + sp->ts_prewordlen, flags); - sp->ts_prewordlen = (char_u)STRLEN(preword); - sp->ts_splitoff = sp->ts_twordlen; - } - break; - } - - if (sp->ts_curi > len || byts[arridx] != 0) { - // Past bytes in node and/or past NUL bytes. - PROF_STORE(sp->ts_state) - sp->ts_state = STATE_ENDNUL; - sp->ts_save_badflags = (char_u)su->su_badflags; - break; - } - - // End of word in tree. - ++sp->ts_curi; // eat one NUL byte - - flags = (int)idxs[arridx]; - - // Skip words with the NOSUGGEST flag. - if (flags & WF_NOSUGGEST) { - break; - } - - fword_ends = (fword[sp->ts_fidx] == NUL - || (soundfold - ? ascii_iswhite(fword[sp->ts_fidx]) - : !spell_iswordp(fword + sp->ts_fidx, curwin))); - tword[sp->ts_twordlen] = NUL; - - if (sp->ts_prefixdepth <= PFD_NOTSPECIAL - && (sp->ts_flags & TSF_PREFIXOK) == 0 - && pbyts != NULL) { - // There was a prefix before the word. Check that the prefix - // can be used with this word. - // Count the length of the NULs in the prefix. If there are - // none this must be the first try without a prefix. - n = stack[sp->ts_prefixdepth].ts_arridx; - len = pbyts[n++]; - for (c = 0; c < len && pbyts[n + c] == 0; c++) {} - if (c > 0) { - c = valid_word_prefix(c, n, flags, - tword + sp->ts_splitoff, slang, false); - if (c == 0) { - break; - } - - // Use the WF_RARE flag for a rare prefix. - if (c & WF_RAREPFX) { - flags |= WF_RARE; - } - - // Tricky: when checking for both prefix and compounding - // we run into the prefix flag first. - // Remember that it's OK, so that we accept the prefix - // when arriving at a compound flag. - sp->ts_flags |= TSF_PREFIXOK; - } - } - - // Check NEEDCOMPOUND: can't use word without compounding. Do try - // appending another compound word below. - if (sp->ts_complen == sp->ts_compsplit && fword_ends - && (flags & WF_NEEDCOMP)) { - goodword_ends = false; - } else { - goodword_ends = true; - } - - p = NULL; - compound_ok = true; - if (sp->ts_complen > sp->ts_compsplit) { - if (slang->sl_nobreak) { - // There was a word before this word. When there was no - // change in this word (it was correct) add the first word - // as a suggestion. If this word was corrected too, we - // 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, - tword + sp->ts_splitoff, - sp->ts_fidx - sp->ts_splitfidx) == 0) { - preword[sp->ts_prewordlen] = NUL; - newscore = score_wordcount_adj(slang, sp->ts_score, - preword + sp->ts_prewordlen, - sp->ts_prewordlen > 0); - // Add the suggestion if the score isn't too bad. - if (newscore <= su->su_maxscore) { - add_suggestion(su, &su->su_ga, preword, - sp->ts_splitfidx - repextra, - newscore, 0, false, - lp->lp_sallang, false); - } - break; - } - } else { - // There was a compound word before this word. If this - // word does not support compounding then give up - // (splitting is tried for the word without compound - // flag). - if (((unsigned)flags >> 24) == 0 - || sp->ts_twordlen - sp->ts_splitoff - < slang->sl_compminlen) { - break; - } - // For multi-byte chars check character length against - // COMPOUNDMIN. - if (slang->sl_compminlen > 0 - && mb_charlen(tword + sp->ts_splitoff) - < slang->sl_compminlen) { - break; - } - - 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); - - // Verify CHECKCOMPOUNDPATTERN rules. - if (match_checkcompoundpattern(preword, sp->ts_prewordlen, - &slang->sl_comppat)) { - compound_ok = false; - } - - if (compound_ok) { - p = preword; - while (*skiptowhite(p) != NUL) { - p = (char_u *)skipwhite((char *)skiptowhite(p)); - } - 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; - } - } - - // Get pointer to last char of previous word. - p = preword + sp->ts_prewordlen; - MB_PTR_BACK(preword, p); - } - } - - // Form the word with proper case in preword. - // If there is a word from a previous split, append. - // For the soundfold tree don't change the case, simply append. - if (soundfold) { - 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, - 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)) { - c = WF_ONECAP; - } - c |= flags; - - // When appending a compound word after a word character don't - // use Onecap. - if (p != NULL && spell_iswordp_nmw(p, curwin)) { - c &= ~WF_ONECAP; - } - make_case_word(tword + sp->ts_splitoff, - 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, preword + sp->ts_prewordlen); - break; - } - if ((sp->ts_complen == sp->ts_compsplit - && WAS_BANNED(su, (char *)preword + sp->ts_prewordlen)) - || WAS_BANNED(su, (char *)preword)) { - if (slang->sl_compprog == NULL) { - break; - } - // the word so far was banned but we may try compounding - goodword_ends = false; - } - } - - newscore = 0; - if (!soundfold) { // soundfold words don't have flags - if ((flags & WF_REGION) - && (((unsigned)flags >> 16) & (unsigned)lp->lp_region) == 0) { - newscore += SCORE_REGION; - } - if (flags & WF_RARE) { - newscore += SCORE_RARE; - } - - if (!spell_valid_case(su->su_badflags, - captype(preword + sp->ts_prewordlen, NULL))) { - newscore += SCORE_ICASE; - } - } - - // TODO: how about splitting in the soundfold tree? - if (fword_ends - && goodword_ends - && sp->ts_fidx >= sp->ts_fidxtry - && compound_ok) { - // The badword also ends: add suggestions. -#ifdef DEBUG_TRIEWALK - if (soundfold && STRCMP(preword, "smwrd") == 0) { - int j; - - // print the stack of changes that brought us here - smsg("------ %s -------", fword); - for (j = 0; j < depth; ++j) { - smsg("%s", changename[j]); - } - } -#endif - if (soundfold) { - // For soundfolded words we need to find the original - // words, the edit distance and then add them. - 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 = preword + STRLEN(preword); - MB_PTR_BACK(preword, p); - if (spell_iswordp(p, curwin)) { - newscore += SCORE_NONWORD; - } - } - - // Give a bonus to words seen before. - score = score_wordcount_adj(slang, - sp->ts_score + newscore, - preword + sp->ts_prewordlen, - sp->ts_prewordlen > 0); - - // Add the suggestion if the score isn't too bad. - if (score <= su->su_maxscore) { - add_suggestion(su, &su->su_ga, preword, - sp->ts_fidx - repextra, - score, 0, false, lp->lp_sallang, false); - - if (su->su_badflags & WF_MIXCAP) { - // We really don't know if the word should be - // upper or lower case, add both. - c = captype(preword, NULL); - if (c == 0 || c == WF_ALLCAP) { - make_case_word(tword + sp->ts_splitoff, - preword + sp->ts_prewordlen, - c == 0 ? WF_ALLCAP : 0); - - add_suggestion(su, &su->su_ga, preword, - sp->ts_fidx - repextra, - score + SCORE_ICASE, 0, false, - lp->lp_sallang, false); - } - } - } - } - } - - // Try word split and/or compounding. - if ((sp->ts_fidx >= sp->ts_fidxtry || fword_ends) - // Don't split in the middle of a character - && (sp->ts_tcharlen == 0)) { - bool try_compound; - int try_split; - - // If past the end of the bad word don't try a split. - // Otherwise try changing the next word. E.g., find - // suggestions for "the the" where the second "the" is - // different. It's done like a split. - // TODO: word split for soundfold words - try_split = (sp->ts_fidx - repextra < su->su_badlen) - && !soundfold; - - // Get here in several situations: - // 1. The word in the tree ends: - // If the word allows compounding try that. Otherwise try - // a split by inserting a space. For both check that a - // valid words starts at fword[sp->ts_fidx]. - // For NOBREAK do like compounding to be able to check if - // the next word is valid. - // 2. The badword does end, but it was due to a change (e.g., - // a swap). No need to split, but do check that the - // following word is valid. - // 3. The badword and the word in the tree end. It may still - // be possible to compound another (short) word. - try_compound = false; - if (!soundfold - && !slang->sl_nocompoundsugs - && slang->sl_compprog != NULL - && ((unsigned)flags >> 24) != 0 - && sp->ts_twordlen - sp->ts_splitoff - >= slang->sl_compminlen - && (slang->sl_compminlen == 0 - || mb_charlen(tword + sp->ts_splitoff) - >= slang->sl_compminlen) - && (slang->sl_compsylmax < MAXWLEN - || sp->ts_complen + 1 - sp->ts_compsplit - < slang->sl_compmax) - && (can_be_compound(sp, slang, compflags, (int)((unsigned)flags >> 24)))) { - try_compound = true; - compflags[sp->ts_complen] = (char_u)((unsigned)flags >> 24); - compflags[sp->ts_complen + 1] = NUL; - } - - // For NOBREAK we never try splitting, it won't make any word - // valid. - if (slang->sl_nobreak && !slang->sl_nocompoundsugs) { - try_compound = true; - } else if (!fword_ends - && try_compound - && (sp->ts_flags & TSF_DIDSPLIT) == 0) { - // If we could add a compound word, and it's also possible to - // split at this point, do the split first and set - // TSF_DIDSPLIT to avoid doing it again. - try_compound = false; - sp->ts_flags |= TSF_DIDSPLIT; - --sp->ts_curi; // do the same NUL again - compflags[sp->ts_complen] = NUL; - } else { - sp->ts_flags &= (char_u) ~TSF_DIDSPLIT; - } - - if (try_split || try_compound) { - if (!try_compound && (!fword_ends || !goodword_ends)) { - // If we're going to split need to check that the - // words so far are valid for compounding. If there - // is only one word it must not have the NEEDCOMPOUND - // flag. - if (sp->ts_complen == sp->ts_compsplit - && (flags & WF_NEEDCOMP)) { - break; - } - p = preword; - while (*skiptowhite(p) != NUL) { - p = (char_u *)skipwhite((char *)skiptowhite(p)); - } - if (sp->ts_complen > sp->ts_compsplit - && !can_compound(slang, p, - compflags + sp->ts_compsplit)) { - break; - } - - if (slang->sl_nosplitsugs) { - newscore += SCORE_SPLIT_NO; - } else { - newscore += SCORE_SPLIT; - } - - // Give a bonus to words seen before. - newscore = score_wordcount_adj(slang, newscore, - preword + sp->ts_prewordlen, true); - } - - if (TRY_DEEPER(su, stack, depth, newscore)) { - go_deeper(stack, depth, newscore); -#ifdef DEBUG_TRIEWALK - if (!try_compound && !fword_ends) { - sprintf(changename[depth], "%.*s-%s: split", - sp->ts_twordlen, tword, fword + sp->ts_fidx); - } else { - sprintf(changename[depth], "%.*s-%s: compound", - sp->ts_twordlen, tword, fword + sp->ts_fidx); - } -#endif - // Save things to be restored at STATE_SPLITUNDO. - sp->ts_save_badflags = (char_u)su->su_badflags; - PROF_STORE(sp->ts_state) - sp->ts_state = STATE_SPLITUNDO; - - ++depth; - sp = &stack[depth]; - - // Append a space to preword when splitting. - if (!try_compound && !fword_ends) { - STRCAT(preword, " "); - } - sp->ts_prewordlen = (char_u)STRLEN(preword); - sp->ts_splitoff = sp->ts_twordlen; - sp->ts_splitfidx = sp->ts_fidx; - - // If the badword has a non-word character at this - // position skip it. That means replacing the - // non-word character with a space. Always skip a - // character when the word ends. But only when the - // good word can end. - if (((!try_compound && !spell_iswordp_nmw(fword - + sp->ts_fidx, - curwin)) - || fword_ends) - && fword[sp->ts_fidx] != NUL - && goodword_ends) { - int l; - - l = utfc_ptr2len((char *)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); - sp->ts_prewordlen = (char_u)(sp->ts_prewordlen + l); - preword[sp->ts_prewordlen] = NUL; - } else { - sp->ts_score -= SCORE_SPLIT - SCORE_SUBST; - } - sp->ts_fidx = (char_u)(sp->ts_fidx + l); - } - - // When compounding include compound flag in - // compflags[] (already set above). When splitting we - // may start compounding over again. - if (try_compound) { - ++sp->ts_complen; - } else { - sp->ts_compsplit = sp->ts_complen; - } - sp->ts_prefixdepth = PFD_NOPREFIX; - - // 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); - - // Restart at top of the tree. - sp->ts_arridx = 0; - - // If there are postponed prefixes, try these too. - if (pbyts != NULL) { - byts = pbyts; - idxs = pidxs; - sp->ts_prefixdepth = PFD_PREFIXTREE; - PROF_STORE(sp->ts_state) - sp->ts_state = STATE_NOPREFIX; - } - } - } - } - break; - - case STATE_SPLITUNDO: - // Undo the changes done for word split or compound word. - su->su_badflags = sp->ts_save_badflags; - - // Continue looking for NUL bytes. - PROF_STORE(sp->ts_state) - sp->ts_state = STATE_START; - - // In case we went into the prefix tree. - byts = fbyts; - idxs = fidxs; - break; - - case STATE_ENDNUL: - // Past the NUL bytes in the node. - su->su_badflags = sp->ts_save_badflags; - if (fword[sp->ts_fidx] == NUL - && sp->ts_tcharlen == 0) { - // The badword ends, can't use STATE_PLAIN. - PROF_STORE(sp->ts_state) - sp->ts_state = STATE_DEL; - break; - } - PROF_STORE(sp->ts_state) - sp->ts_state = STATE_PLAIN; - FALLTHROUGH; - - case STATE_PLAIN: - // Go over all possible bytes at this node, add each to tword[] - // and use child node. "ts_curi" is the index. - arridx = sp->ts_arridx; - if (sp->ts_curi > byts[arridx]) { - // Done all bytes at this node, do next state. When still at - // already changed bytes skip the other tricks. - PROF_STORE(sp->ts_state) - if (sp->ts_fidx >= sp->ts_fidxtry) { - sp->ts_state = STATE_DEL; - } else { - sp->ts_state = STATE_FINAL; - } - } else { - arridx += sp->ts_curi++; - c = byts[arridx]; - - // Normal byte, go one level deeper. If it's not equal to the - // byte in the bad word adjust the score. But don't even try - // 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] - || (sp->ts_tcharlen > 0 - && sp->ts_isdiff != DIFF_NONE)) { - newscore = 0; - } else { - newscore = SCORE_SUBST; - } - if ((newscore == 0 - || (sp->ts_fidx >= sp->ts_fidxtry - && ((sp->ts_flags & TSF_DIDDEL) == 0 - || c != fword[sp->ts_delidx]))) - && TRY_DEEPER(su, stack, depth, newscore)) { - go_deeper(stack, depth, newscore); -#ifdef DEBUG_TRIEWALK - if (newscore > 0) { - sprintf(changename[depth], "%.*s-%s: subst %c to %c", - sp->ts_twordlen, tword, fword + sp->ts_fidx, - fword[sp->ts_fidx], c); - } else { - sprintf(changename[depth], "%.*s-%s: accept %c", - sp->ts_twordlen, tword, fword + sp->ts_fidx, - fword[sp->ts_fidx]); - } -#endif - ++depth; - sp = &stack[depth]; - if (fword[sp->ts_fidx] != NUL) { - sp->ts_fidx++; - } - tword[sp->ts_twordlen++] = (char_u)c; - sp->ts_arridx = idxs[arridx]; - if (newscore == SCORE_SUBST) { - sp->ts_isdiff = DIFF_YES; - } - // Multi-byte characters are a bit complicated to - // handle: They differ when any of the bytes differ - // and then their length may also differ. - if (sp->ts_tcharlen == 0) { - // First byte. - sp->ts_tcharidx = 0; - sp->ts_tcharlen = MB_BYTE2LEN(c); - sp->ts_fcharstart = (char_u)(sp->ts_fidx - 1); - sp->ts_isdiff = (newscore != 0) - ? DIFF_YES : DIFF_NONE; - } else if (sp->ts_isdiff == DIFF_INSERT && sp->ts_fidx > 0) { - // When inserting trail bytes don't advance in the - // bad word. - sp->ts_fidx--; - } - if (++sp->ts_tcharidx == sp->ts_tcharlen) { - // Last byte of character. - if (sp->ts_isdiff == DIFF_YES) { - // 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)); - - // 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 - + sp->ts_fcharstart))) { - sp->ts_score -= SCORE_SUBST - SCORE_SUBCOMP; - } else if (!soundfold - && slang->sl_has_map - && similar_chars(slang, - utf_ptr2char((char *)tword + sp->ts_twordlen - - sp->ts_tcharlen), - utf_ptr2char((char *)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); - if (utf_iscomposing(c)) { - // Inserting a composing char doesn't - // count that much. - sp->ts_score -= SCORE_INS - SCORE_INSCOMP; - } else { - // If the previous character was the same, - // thus doubling a character, give a bonus - // to the score. Also for the soundfold - // tree (might seem illogical but does - // give better scores). - MB_PTR_BACK(tword, p); - if (c == utf_ptr2char((char *)p)) { - sp->ts_score -= SCORE_INS - SCORE_INSDUP; - } - } - } - - // Starting a new char, reset the length. - sp->ts_tcharlen = 0; - } - } - } - break; - - case STATE_DEL: - // When past the first byte of a multi-byte char don't try - // delete/insert/swap a character. - if (sp->ts_tcharlen > 0) { - PROF_STORE(sp->ts_state) - sp->ts_state = STATE_FINAL; - break; - } - // Try skipping one character in the bad word (delete it). - PROF_STORE(sp->ts_state) - sp->ts_state = STATE_INS_PREP; - sp->ts_curi = 1; - if (soundfold && sp->ts_fidx == 0 && fword[sp->ts_fidx] == '*') { - // Deleting a vowel at the start of a word counts less, see - // soundalike_score(). - newscore = 2 * SCORE_DEL / 3; - } else { - newscore = SCORE_DEL; - } - if (fword[sp->ts_fidx] != NUL - && TRY_DEEPER(su, stack, depth, newscore)) { - go_deeper(stack, depth, newscore); -#ifdef DEBUG_TRIEWALK - sprintf(changename[depth], "%.*s-%s: delete %c", - sp->ts_twordlen, tword, fword + sp->ts_fidx, - fword[sp->ts_fidx]); -#endif - ++depth; - - // Remember what character we deleted, so that we can avoid - // inserting it again. - stack[depth].ts_flags |= TSF_DIDDEL; - stack[depth].ts_delidx = sp->ts_fidx; - - // Advance over the character in fword[]. Give a bonus to the - // 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); - stack[depth].ts_fidx = - (char_u)(stack[depth].ts_fidx + utfc_ptr2len((char *)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)) { - stack[depth].ts_score -= SCORE_DEL - SCORE_DELDUP; - } - - break; - } - FALLTHROUGH; - - case STATE_INS_PREP: - if (sp->ts_flags & TSF_DIDDEL) { - // If we just deleted a byte then inserting won't make sense, - // a substitute is always cheaper. - PROF_STORE(sp->ts_state) - sp->ts_state = STATE_SWAP; - break; - } - - // skip over NUL bytes - n = sp->ts_arridx; - for (;;) { - if (sp->ts_curi > byts[n]) { - // Only NUL bytes at this node, go to next state. - PROF_STORE(sp->ts_state) - sp->ts_state = STATE_SWAP; - break; - } - if (byts[n + sp->ts_curi] != NUL) { - // Found a byte to insert. - PROF_STORE(sp->ts_state) - sp->ts_state = STATE_INS; - break; - } - ++sp->ts_curi; - } - break; - - case STATE_INS: - // Insert one byte. Repeat this for each possible byte at this - // node. - n = sp->ts_arridx; - if (sp->ts_curi > byts[n]) { - // Done all bytes at this node, go to next state. - PROF_STORE(sp->ts_state) - sp->ts_state = STATE_SWAP; - break; - } - - // Do one more byte at this node, but: - // - Skip NUL bytes. - // - Skip the byte if it's equal to the byte in the word, - // accepting that byte is always better. - n += sp->ts_curi++; - c = byts[n]; - if (soundfold && sp->ts_twordlen == 0 && c == '*') { - // Inserting a vowel at the start of a word counts less, - // see soundalike_score(). - newscore = 2 * SCORE_INS / 3; - } else { - newscore = SCORE_INS; - } - if (c != fword[sp->ts_fidx] - && TRY_DEEPER(su, stack, depth, newscore)) { - go_deeper(stack, depth, newscore); -#ifdef DEBUG_TRIEWALK - sprintf(changename[depth], "%.*s-%s: insert %c", - sp->ts_twordlen, tword, fword + sp->ts_fidx, - c); -#endif - ++depth; - sp = &stack[depth]; - tword[sp->ts_twordlen++] = (char_u)c; - sp->ts_arridx = idxs[n]; - fl = MB_BYTE2LEN(c); - if (fl > 1) { - // There are following bytes for the same character. - // We must find all bytes before trying - // delete/insert/swap/etc. - sp->ts_tcharlen = (char_u)fl; - sp->ts_tcharidx = 1; - sp->ts_isdiff = DIFF_INSERT; - } - if (fl == 1) { - // If the previous character was the same, thus doubling a - // character, give a bonus to the score. Also for - // soundfold words (illogical but does give a better - // score). - if (sp->ts_twordlen >= 2 - && tword[sp->ts_twordlen - 2] == c) { - sp->ts_score -= SCORE_INS - SCORE_INSDUP; - } - } - } - break; - - case STATE_SWAP: - // Swap two bytes in the bad word: "12" -> "21". - // We change "fword" here, it's changed back afterwards at - // STATE_UNSWAP. - p = fword + sp->ts_fidx; - c = *p; - if (c == NUL) { - // End of word, can't swap or replace. - PROF_STORE(sp->ts_state) - sp->ts_state = STATE_FINAL; - break; - } - - // Don't swap if the first character is not a word character. - // SWAP3 etc. also don't make sense then. - if (!soundfold && !spell_iswordp(p, curwin)) { - PROF_STORE(sp->ts_state) - sp->ts_state = STATE_REP_INI; - break; - } - - n = utf_ptr2len((char *)p); - c = utf_ptr2char((char *)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); - } - - // When the second character is NUL we can't swap. - if (c2 == NUL) { - PROF_STORE(sp->ts_state) - sp->ts_state = STATE_REP_INI; - break; - } - - // When characters are identical, swap won't do anything. - // Also get here if the second char is not a word character. - if (c == c2) { - PROF_STORE(sp->ts_state) - sp->ts_state = STATE_SWAP3; - break; - } - if (TRY_DEEPER(su, stack, depth, SCORE_SWAP)) { - go_deeper(stack, depth, SCORE_SWAP); -#ifdef DEBUG_TRIEWALK - snprintf(changename[depth], sizeof(changename[0]), - "%.*s-%s: swap %c and %c", - sp->ts_twordlen, tword, fword + sp->ts_fidx, - c, c2); -#endif - PROF_STORE(sp->ts_state) - sp->ts_state = STATE_UNSWAP; - depth++; - fl = utf_char2len(c2); - memmove(p, p + n, (size_t)fl); - utf_char2bytes(c, (char *)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. - PROF_STORE(sp->ts_state) - sp->ts_state = STATE_REP_INI; - } - break; - - 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); - - FALLTHROUGH; - - case STATE_SWAP3: - // 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); - if (!soundfold && !spell_iswordp(p + n + fl, curwin)) { - c3 = c; // don't swap non-word char - } else { - c3 = utf_ptr2char((char *)p + n + fl); - } - - // When characters are identical: "121" then SWAP3 result is - // identical, ROT3L result is same as SWAP: "211", ROT3L result is - // same as SWAP on next char: "112". Thus skip all swapping. - // Also skip when c3 is NUL. - // Also get here when the third character is not a word character. - // Second character may any char: "a.b" -> "b.a" - if (c == c3 || c3 == NUL) { - PROF_STORE(sp->ts_state) - sp->ts_state = STATE_REP_INI; - break; - } - if (TRY_DEEPER(su, stack, depth, SCORE_SWAP3)) { - go_deeper(stack, depth, SCORE_SWAP3); -#ifdef DEBUG_TRIEWALK - sprintf(changename[depth], "%.*s-%s: swap3 %c and %c", - sp->ts_twordlen, tword, fword + sp->ts_fidx, - c, c3); -#endif - PROF_STORE(sp->ts_state) - sp->ts_state = STATE_UNSWAP3; - 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); - stack[depth].ts_fidxtry = (char_u)(sp->ts_fidx + n + fl + tl); - } else { - PROF_STORE(sp->ts_state) - sp->ts_state = STATE_REP_INI; - } - break; - - 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); - memmove(p + fl + tl, p, (size_t)n); - utf_char2bytes(c, (char *)p); - utf_char2bytes(c2, (char *)p + tl); - p = p + tl; - - if (!soundfold && !spell_iswordp(p, curwin)) { - // Middle char is not a word char, skip the rotate. First and - // third char were already checked at swap and swap3. - PROF_STORE(sp->ts_state) - sp->ts_state = STATE_REP_INI; - break; - } - - // Rotate three characters left: "123" -> "231". We change - // "fword" here, it's changed back afterwards at STATE_UNROT3L. - if (TRY_DEEPER(su, stack, depth, SCORE_SWAP3)) { - go_deeper(stack, depth, SCORE_SWAP3); -#ifdef DEBUG_TRIEWALK - p = fword + sp->ts_fidx; - sprintf(changename[depth], "%.*s-%s: rotate left %c%c%c", - sp->ts_twordlen, tword, fword + sp->ts_fidx, - p[0], p[1], p[2]); -#endif - PROF_STORE(sp->ts_state) - 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); - memmove(p, p + n, (size_t)fl); - utf_char2bytes(c, (char *)p + fl); - stack[depth].ts_fidxtry = (char_u)(sp->ts_fidx + n + fl); - } else { - PROF_STORE(sp->ts_state) - sp->ts_state = STATE_REP_INI; - } - break; - - 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); - memmove(p + tl, p, (size_t)n); - utf_char2bytes(c, (char *)p); - - // Rotate three bytes right: "123" -> "312". We change "fword" - // here, it's changed back afterwards at STATE_UNROT3R. - if (TRY_DEEPER(su, stack, depth, SCORE_SWAP3)) { - go_deeper(stack, depth, SCORE_SWAP3); -#ifdef DEBUG_TRIEWALK - p = fword + sp->ts_fidx; - sprintf(changename[depth], "%.*s-%s: rotate right %c%c%c", - sp->ts_twordlen, tword, fword + sp->ts_fidx, - p[0], p[1], p[2]); -#endif - PROF_STORE(sp->ts_state) - 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); - memmove(p + tl, p, (size_t)n); - utf_char2bytes(c, (char *)p); - stack[depth].ts_fidxtry = (char_u)(sp->ts_fidx + n + tl); - } else { - PROF_STORE(sp->ts_state) - sp->ts_state = STATE_REP_INI; - } - break; - - 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); - memmove(p, p + tl, (size_t)n); - utf_char2bytes(c, (char *)p + n); - - FALLTHROUGH; - - case STATE_REP_INI: - // Check if matching with REP items from the .aff file would work. - // Quickly skip if: - // - there are no REP items and we are not in the soundfold trie - // - the score is going to be too high anyway - // - already applied a REP item or swapped here - if ((lp->lp_replang == NULL && !soundfold) - || sp->ts_score + SCORE_REP >= su->su_maxscore - || sp->ts_fidx < sp->ts_fidxtry) { - PROF_STORE(sp->ts_state) - sp->ts_state = STATE_FINAL; - break; - } - - // 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]]; - } else { - sp->ts_curi = lp->lp_replang->sl_rep_first[fword[sp->ts_fidx]]; - } - - if (sp->ts_curi < 0) { - PROF_STORE(sp->ts_state) - sp->ts_state = STATE_FINAL; - break; - } - - PROF_STORE(sp->ts_state) - sp->ts_state = STATE_REP; - FALLTHROUGH; - - case STATE_REP: - // Try matching with REP items from the .aff file. For each match - // replace the characters and check if the resulting word is - // valid. - p = fword + sp->ts_fidx; - - if (soundfold) { - gap = &slang->sl_repsal; - } else { - gap = &lp->lp_replang->sl_rep; - } - while (sp->ts_curi < gap->ga_len) { - 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; - break; - } - 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 - sprintf(changename[depth], "%.*s-%s: replace %s with %s", - sp->ts_twordlen, tword, fword + sp->ts_fidx, - ftp->ft_from, ftp->ft_to); -#endif - // Need to undo this afterwards. - PROF_STORE(sp->ts_state) - sp->ts_state = STATE_REP_UNDO; - - // Change the "from" to the "to" string. - ++depth; - fl = (int)STRLEN(ftp->ft_from); - tl = (int)STRLEN(ftp->ft_to); - if (fl != tl) { - STRMOVE(p + tl, p + fl); - repextra += tl - fl; - } - memmove(p, ftp->ft_to, (size_t)tl); - stack[depth].ts_fidxtry = (char_u)(sp->ts_fidx + tl); - stack[depth].ts_tcharlen = 0; - break; - } - } - - if (sp->ts_curi >= gap->ga_len && sp->ts_state == STATE_REP) { - // No (more) matches. - PROF_STORE(sp->ts_state) - sp->ts_state = STATE_FINAL; - } - - break; - - case STATE_REP_UNDO: - // Undo a REP replacement and continue with the next one. - if (soundfold) { - gap = &slang->sl_repsal; - } else { - 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); - p = fword + sp->ts_fidx; - if (fl != tl) { - STRMOVE(p + fl, p + tl); - repextra -= tl - fl; - } - memmove(p, ftp->ft_from, (size_t)fl); - PROF_STORE(sp->ts_state) - sp->ts_state = STATE_REP; - break; - - default: - // Did all possible states at this level, go up one level. - --depth; - - if (depth >= 0 && stack[depth].ts_prefixdepth == PFD_PREFIXTREE) { - // Continue in or go back to the prefix tree. - byts = pbyts; - idxs = pidxs; - } - - // Don't check for CTRL-C too often, it takes time. - if (--breakcheckcount == 0) { - os_breakcheck(); - breakcheckcount = 1000; - if (profile_passed_limit(time_limit)) { - got_int = true; - } - } - } - } -} - -// Go one level deeper in the tree. -static void go_deeper(trystate_T *stack, int depth, int score_add) -{ - stack[depth + 1] = stack[depth]; - stack[depth + 1].ts_state = STATE_START; - stack[depth + 1].ts_score = stack[depth].ts_score + score_add; - stack[depth + 1].ts_curi = 1; // start just after length byte - stack[depth + 1].ts_flags = 0; -} - // 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". -static int nofold_len(char_u *fword, int flen, char_u *word) +int nofold_len(char_u *fword, int flen, char_u *word) { char_u *p; int i = 0; @@ -4961,677 +2640,8 @@ static int nofold_len(char_u *fword, int flen, char_u *word) return (int)(p - word); } -// "fword" is a good word with case folded. Find the matching keep-case -// 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) -{ - char_u uword[MAXWLEN]; // "fword" in upper-case - int depth; - idx_T tryidx; - - // The following arrays are used at each depth in the tree. - idx_T arridx[MAXWLEN]; - int round[MAXWLEN]; - int fwordidx[MAXWLEN]; - int uwordidx[MAXWLEN]; - int kwordlen[MAXWLEN]; - - int flen, ulen; - int l; - int len; - int c; - idx_T lo, hi, m; - char_u *p; - char_u *byts = slang->sl_kbyts; // array with bytes of the words - idx_T *idxs = slang->sl_kidxs; // array with indexes - - if (byts == NULL) { - // array is empty: "cannot happen" - *kword = NUL; - return; - } - - // Make an all-cap version of "fword". - allcap_copy(fword, uword); - - // Each character needs to be tried both case-folded and upper-case. - // All this gets very complicated if we keep in mind that changing case - // may change the byte length of a multi-byte character... - depth = 0; - arridx[0] = 0; - round[0] = 0; - fwordidx[0] = 0; - uwordidx[0] = 0; - kwordlen[0] = 0; - while (depth >= 0) { - if (fword[fwordidx[depth]] == NUL) { - // We are at the end of "fword". If the tree allows a word to end - // here we have found a match. - if (byts[arridx[depth] + 1] == 0) { - kword[kwordlen[depth]] = NUL; - return; - } - - // kword is getting too long, continue one level up - --depth; - } else if (++round[depth] > 2) { - // tried both fold-case and upper-case character, continue one - // level up - --depth; - } 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]); - ulen = utf_ptr2len((char *)uword + uwordidx[depth]); - if (round[depth] == 1) { - p = fword + fwordidx[depth]; - l = flen; - } else { - p = uword + uwordidx[depth]; - l = ulen; - } - - for (tryidx = arridx[depth]; l > 0; --l) { - // Perform a binary search in the list of accepted bytes. - len = byts[tryidx++]; - c = *p++; - lo = tryidx; - hi = tryidx + len - 1; - while (lo < hi) { - m = (lo + hi) / 2; - if (byts[m] > c) { - hi = m - 1; - } else if (byts[m] < c) { - lo = m + 1; - } else { - lo = hi = m; - break; - } - } - - // Stop if there is no matching byte. - if (hi < lo || byts[lo] != c) { - break; - } - - // Continue at the child (if there is one). - tryidx = idxs[lo]; - } - - if (l == 0) { - // Found the matching char. Copy it to "kword" and go a - // level deeper. - if (round[depth] == 1) { - STRNCPY(kword + kwordlen[depth], fword + fwordidx[depth], - flen); - kwordlen[depth + 1] = kwordlen[depth] + flen; - } else { - STRNCPY(kword + kwordlen[depth], uword + uwordidx[depth], - ulen); - kwordlen[depth + 1] = kwordlen[depth] + ulen; - } - fwordidx[depth + 1] = fwordidx[depth] + flen; - uwordidx[depth + 1] = uwordidx[depth] + ulen; - - ++depth; - arridx[depth] = tryidx; - round[depth] = 0; - } - } - } - - // Didn't find it: "cannot happen". - *kword = NUL; -} - -// Compute the sound-a-like score for suggestions in su->su_ga and add them to -// su->su_sga. -static void score_comp_sal(suginfo_T *su) -{ - langp_T *lp; - char_u badsound[MAXWLEN]; - int i; - suggest_T *stp; - suggest_T *sstp; - int score; - - ga_grow(&su->su_sga, su->su_ga.ga_len); - - // Use the sound-folding of the first language that supports it. - for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len; ++lpi) { - 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); - - for (i = 0; i < su->su_ga.ga_len; ++i) { - stp = &SUG(su->su_ga, i); - - // Case-fold the suggested word, sound-fold it and compute the - // sound-a-like score. - score = stp_sal_score(stp, su, lp->lp_slang, badsound); - if (score < SCORE_MAXMAX) { - // Add the suggestion. - sstp = &SUG(su->su_sga, su->su_sga.ga_len); - sstp->st_word = vim_strsave(stp->st_word); - sstp->st_wordlen = stp->st_wordlen; - sstp->st_score = score; - sstp->st_altscore = 0; - sstp->st_orglen = stp->st_orglen; - ++su->su_sga.ga_len; - } - } - break; - } - } -} - -// Combine the list of suggestions in su->su_ga and su->su_sga. -// They are entwined. -static void score_combine(suginfo_T *su) -{ - garray_T ga; - garray_T *gap; - langp_T *lp; - suggest_T *stp; - char_u *p; - char_u badsound[MAXWLEN]; - int round; - slang_T *slang = NULL; - - // Add the alternate score to su_ga. - for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len; ++lpi) { - lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi); - if (!GA_EMPTY(&lp->lp_slang->sl_sal)) { - // soundfold the bad word - slang = lp->lp_slang; - spell_soundfold(slang, su->su_fbadword, true, badsound); - - for (int i = 0; i < su->su_ga.ga_len; ++i) { - stp = &SUG(su->su_ga, i); - stp->st_altscore = stp_sal_score(stp, su, slang, badsound); - if (stp->st_altscore == SCORE_MAXMAX) { - stp->st_score = (stp->st_score * 3 + SCORE_BIG) / 4; - } else { - stp->st_score = (stp->st_score * 3 - + stp->st_altscore) / 4; - } - stp->st_salscore = false; - } - break; - } - } - - if (slang == NULL) { // Using "double" without sound folding. - (void)cleanup_suggestions(&su->su_ga, su->su_maxscore, - su->su_maxcount); - return; - } - - // 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, stp->st_word); - if (stp->st_score == SCORE_MAXMAX) { - stp->st_score = (SCORE_BIG * 7 + stp->st_altscore) / 8; - } else { - stp->st_score = (stp->st_score * 7 + stp->st_altscore) / 8; - } - stp->st_salscore = true; - } - - // Remove bad suggestions, sort the suggestions and truncate at "maxcount" - // for both lists. - check_suggestions(su, &su->su_ga); - (void)cleanup_suggestions(&su->su_ga, su->su_maxscore, su->su_maxcount); - check_suggestions(su, &su->su_sga); - (void)cleanup_suggestions(&su->su_sga, su->su_maxscore, su->su_maxcount); - - ga_init(&ga, (int)sizeof(suginfo_T), 1); - ga_grow(&ga, su->su_ga.ga_len + su->su_sga.ga_len); - - stp = &SUG(ga, 0); - for (int i = 0; i < su->su_ga.ga_len || i < su->su_sga.ga_len; ++i) { - // round 1: get a suggestion from su_ga - // round 2: get a suggestion from su_sga - for (round = 1; round <= 2; ++round) { - gap = round == 1 ? &su->su_ga : &su->su_sga; - if (i < gap->ga_len) { - // Don't add a word if it's already there. - p = SUG(*gap, i).st_word; - int j; - for (j = 0; j < ga.ga_len; ++j) { - if (STRCMP(stp[j].st_word, p) == 0) { - break; - } - } - if (j == ga.ga_len) { - stp[ga.ga_len++] = SUG(*gap, i); - } else { - xfree(p); - } - } - } - } - - ga_clear(&su->su_ga); - ga_clear(&su->su_sga); - - // Truncate the list to the number of suggestions that will be displayed. - if (ga.ga_len > su->su_maxcount) { - for (int i = su->su_maxcount; i < ga.ga_len; ++i) { - xfree(stp[i].st_word); - } - ga.ga_len = su->su_maxcount; - } - - su->su_ga = ga; -} - -/// For the goodword in "stp" compute the soundalike score compared to the -/// badword. -/// -/// @param badsound sound-folded badword -static int stp_sal_score(suggest_T *stp, suginfo_T *su, slang_T *slang, char_u *badsound) -{ - char_u *p; - char_u *pbad; - char_u *pgood; - char_u badsound2[MAXWLEN]; - char_u fword[MAXWLEN]; - char_u goodsound[MAXWLEN]; - char_u goodword[MAXWLEN]; - int lendiff; - - lendiff = su->su_badlen - stp->st_orglen; - if (lendiff >= 0) { - pbad = badsound; - } else { - // soundfold the bad word with more characters following - (void)spell_casefold(curwin, su->su_badptr, stp->st_orglen, 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 - // removing the space. Don't do it when the good word also contains a - // space. - if (ascii_iswhite(su->su_badptr[su->su_badlen]) - && *skiptowhite(stp->st_word) == NUL) { - for (p = fword; *(p = skiptowhite(p)) != NUL;) { - STRMOVE(p, p + 1); - } - } - - spell_soundfold(slang, fword, true, badsound2); - pbad = badsound2; - } - - if (lendiff > 0 && stp->st_wordlen + lendiff < MAXWLEN) { - // 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; - } else { - pgood = stp->st_word; - } - - // Sound-fold the word and compute the score for the difference. - spell_soundfold(slang, pgood, false, goodsound); - - return soundalike_score(goodsound, pbad); -} - -static sftword_T dumsft; -#define HIKEY2SFT(p) ((sftword_T *)((p) - (dumsft.sft_word - (char_u *)&dumsft))) -#define HI2SFT(hi) HIKEY2SFT((hi)->hi_key) - -// Prepare for calling suggest_try_soundalike(). -static void suggest_try_soundalike_prep(void) -{ - langp_T *lp; - slang_T *slang; - - // Do this for all languages that support sound folding and for which a - // .sug file has been loaded. - for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len; ++lpi) { - lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi); - slang = lp->lp_slang; - if (!GA_EMPTY(&slang->sl_sal) && slang->sl_sbyts != NULL) { - // prepare the hashtable used by add_sound_suggest() - hash_init(&slang->sl_sounddone); - } - } -} - -// Find suggestions by comparing the word in a sound-a-like form. -// Note: This doesn't support postponed prefixes. -static void suggest_try_soundalike(suginfo_T *su) -{ - char_u salword[MAXWLEN]; - langp_T *lp; - slang_T *slang; - - // Do this for all languages that support sound folding and for which a - // .sug file has been loaded. - for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len; ++lpi) { - lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi); - 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); - - // try all kinds of inserts/deletes/swaps/etc. - // TODO: also soundfold the next words, so that we can try joining - // and splitting -#ifdef SUGGEST_PROFILE - prof_init(); -#endif - suggest_trie_walk(su, lp, salword, true); -#ifdef SUGGEST_PROFILE - prof_report("soundalike"); -#endif - } - } -} - -// Finish up after calling suggest_try_soundalike(). -static void suggest_try_soundalike_finish(void) -{ - langp_T *lp; - slang_T *slang; - int todo; - hashitem_T *hi; - - // Do this for all languages that support sound folding and for which a - // .sug file has been loaded. - for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len; ++lpi) { - lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi); - slang = lp->lp_slang; - if (!GA_EMPTY(&slang->sl_sal) && slang->sl_sbyts != NULL) { - // Free the info about handled words. - todo = (int)slang->sl_sounddone.ht_used; - for (hi = slang->sl_sounddone.ht_array; todo > 0; ++hi) { - if (!HASHITEM_EMPTY(hi)) { - xfree(HI2SFT(hi)); - --todo; - } - } - - // Clear the hashtable, it may also be used by another region. - hash_clear(&slang->sl_sounddone); - hash_init(&slang->sl_sounddone); - } - } -} - -/// A match with a soundfolded word is found. Add the good word(s) that -/// produce this soundfolded word. -/// -/// @param score soundfold score -static void add_sound_suggest(suginfo_T *su, char_u *goodword, int score, langp_T *lp) -{ - slang_T *slang = lp->lp_slang; // language for sound folding - int sfwordnr; - char_u *nrline; - int orgnr; - char_u theword[MAXWLEN]; - int i; - int wlen; - char_u *byts; - idx_T *idxs; - int n; - int wordcount; - int wc; - int goodscore; - hash_T hash; - hashitem_T *hi; - sftword_T *sft; - int bc, gc; - int limit; - - // It's very well possible that the same soundfold word is found several - // times with different scores. Since the following is quite slow only do - // 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); - 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); - } else { - sft = HI2SFT(hi); - if (score >= sft->sft_score) { - return; - } - sft->sft_score = (int16_t)score; - } - - // Find the word nr in the soundfold tree. - sfwordnr = soundfold_find(slang, goodword); - if (sfwordnr < 0) { - internal_error("add_sound_suggest()"); - return; - } - - // Go over the list of good words that produce this soundfold word - nrline = ml_get_buf(slang->sl_sugbuf, (linenr_T)sfwordnr + 1, false); - orgnr = 0; - while (*nrline != NUL) { - // The wordnr was stored in a minimal nr of bytes as an offset to the - // previous wordnr. - orgnr += bytes2offset(&nrline); - - byts = slang->sl_fbyts; - idxs = slang->sl_fidxs; - - // Lookup the word "orgnr" one of the two tries. - n = 0; - wordcount = 0; - for (wlen = 0; wlen < MAXWLEN - 3; ++wlen) { - i = 1; - if (wordcount == orgnr && byts[n + 1] == NUL) { - break; // found end of word - } - if (byts[n + 1] == NUL) { - ++wordcount; - } - - // skip over the NUL bytes - for (; byts[n + i] == NUL; ++i) { - if (i > byts[n]) { // safety check - STRCPY(theword + wlen, "BAD"); - wlen += 3; - goto badword; - } - } - - // One of the siblings must have the word. - for (; i < byts[n]; ++i) { - wc = idxs[idxs[n + i]]; // nr of words under this byte - if (wordcount + wc > orgnr) { - break; - } - wordcount += wc; - } - - theword[wlen] = byts[n + i]; - n = idxs[n + i]; - } -badword: - theword[wlen] = NUL; - - // Go over the possible flags and regions. - for (; i <= byts[n] && byts[n + i] == NUL; ++i) { - char_u cword[MAXWLEN]; - char_u *p; - int flags = (int)idxs[n + i]; - - // Skip words with the NOSUGGEST flag - if (flags & WF_NOSUGGEST) { - continue; - } - - if (flags & WF_KEEPCAP) { - // Must find the word in the keep-case tree. - find_keepcap_word(slang, theword, 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); - p = cword; - } else { - p = theword; - } - } - - // Add the suggestion. - if (sps_flags & SPS_DOUBLE) { - // Add the suggestion if the score isn't too bad. - if (score <= su->su_maxscore) { - add_suggestion(su, &su->su_sga, p, su->su_badlen, - score, 0, false, slang, false); - } - } else { - // Add a penalty for words in another region. - if ((flags & WF_REGION) - && (((unsigned)flags >> 16) & (unsigned)lp->lp_region) == 0) { - goodscore = SCORE_REGION; - } else { - goodscore = 0; - } - - // Add a small penalty for changing the first letter from - // lower to upper case. Helps for "tath" -> "Kath", which is - // less common than "tath" -> "path". Don't do it when the - // letter is the same, that has already been counted. - gc = utf_ptr2char((char *)p); - if (SPELL_ISUPPER(gc)) { - bc = utf_ptr2char((char *)su->su_badword); - if (!SPELL_ISUPPER(bc) - && SPELL_TOFOLD(bc) != SPELL_TOFOLD(gc)) { - goodscore += SCORE_ICASE / 2; - } - } - - // Compute the score for the good word. This only does letter - // insert/delete/swap/replace. REP items are not considered, - // which may make the score a bit higher. - // Use a limit for the score to make it work faster. Use - // MAXSCORE(), because RESCORE() will change the score. - // If the limit is very high then the iterative method is - // 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); - } else { - goodscore += spell_edit_score_limit(slang, su->su_badword, - p, limit); - } - - // When going over the limit don't bother to do the rest. - if (goodscore < SCORE_MAXMAX) { - // Give a bonus to words seen before. - goodscore = score_wordcount_adj(slang, goodscore, p, false); - - // Add the suggestion if the score isn't too bad. - goodscore = RESCORE(goodscore, score); - if (goodscore <= su->su_sfmaxscore) { - add_suggestion(su, &su->su_ga, p, su->su_badlen, - goodscore, score, true, slang, true); - } - } - } - } - } -} - -// Find word "word" in fold-case tree for "slang" and return the word number. -static int soundfold_find(slang_T *slang, char_u *word) -{ - idx_T arridx = 0; - int len; - int wlen = 0; - int c; - char_u *ptr = word; - char_u *byts; - idx_T *idxs; - int wordnr = 0; - - byts = slang->sl_sbyts; - idxs = slang->sl_sidxs; - - for (;;) { - // First byte is the number of possible bytes. - len = byts[arridx++]; - - // If the first possible byte is a zero the word could end here. - // If the word ends we found the word. If not skip the NUL bytes. - c = ptr[wlen]; - if (byts[arridx] == NUL) { - if (c == NUL) { - break; - } - - // Skip over the zeros, there can be several. - while (len > 0 && byts[arridx] == NUL) { - ++arridx; - --len; - } - if (len == 0) { - return -1; // no children, word should have ended here - } - ++wordnr; - } - - // If the word ends we didn't find it. - if (c == NUL) { - return -1; - } - - // Perform a binary search in the list of accepted bytes. - if (c == TAB) { // <Tab> is handled like <Space> - c = ' '; - } - while (byts[arridx] < c) { - // The word count is in the first idxs[] entry of the child. - wordnr += idxs[idxs[arridx]]; - ++arridx; - if (--len == 0) { // end of the bytes, didn't find it - return -1; - } - } - if (byts[arridx] != c) { // didn't find the byte - return -1; - } - - // Continue at the child (if there is one). - arridx = idxs[arridx]; - ++wlen; - - // One space in the good word may stand for several spaces in the - // checked word. - if (c == ' ') { - while (ptr[wlen] == ' ' || ptr[wlen] == TAB) { - ++wlen; - } - } - } - - return wordnr; -} - // Copy "fword" to "cword", fixing case according to "flags". -static void make_case_word(char_u *fword, char_u *cword, int flags) +void make_case_word(char_u *fword, char_u *cword, int flags) { if (flags & WF_ALLCAP) { // Make it all upper-case @@ -5645,291 +2655,6 @@ static void make_case_word(char_u *fword, char_u *cword, int flags) } } -// Returns true if "c1" and "c2" are similar characters according to the MAP -// lines in the .aff file. -static bool similar_chars(slang_T *slang, int c1, int c2) -{ - int m1, m2; - char buf[MB_MAXBYTES + 1]; - hashitem_T *hi; - - if (c1 >= 256) { - buf[utf_char2bytes(c1, (char *)buf)] = 0; - hi = hash_find(&slang->sl_map_hash, buf); - if (HASHITEM_EMPTY(hi)) { - m1 = 0; - } else { - m1 = utf_ptr2char((char *)hi->hi_key + STRLEN(hi->hi_key) + 1); - } - } else { - m1 = slang->sl_map_array[c1]; - } - if (m1 == 0) { - return false; - } - - if (c2 >= 256) { - buf[utf_char2bytes(c2, (char *)buf)] = 0; - hi = hash_find(&slang->sl_map_hash, buf); - if (HASHITEM_EMPTY(hi)) { - m2 = 0; - } else { - m2 = utf_ptr2char((char *)hi->hi_key + STRLEN(hi->hi_key) + 1); - } - } else { - m2 = slang->sl_map_array[c2]; - } - - return m1 == m2; -} - -/// Adds a suggestion to the list of suggestions. -/// For a suggestion that is already in the list the lowest score is remembered. -/// -/// @param gap either su_ga or su_sga -/// @param badlenarg len of bad word replaced with "goodword" -/// @param had_bonus value for st_had_bonus -/// @param slang language for sound folding -/// @param maxsf su_maxscore applies to soundfold score, su_sfmaxscore to the total score. -static void add_suggestion(suginfo_T *su, garray_T *gap, const char_u *goodword, int badlenarg, - int score, int altscore, bool had_bonus, slang_T *slang, bool maxsf) -{ - int goodlen; // len of goodword changed - int badlen; // len of bad word changed - suggest_T *stp; - suggest_T new_sug; - - // Minimize "badlen" for consistency. Avoids that changing "the the" to - // "thee the" is added next to changing the first "the" the "thee". - const char_u *pgood = goodword + STRLEN(goodword); - char_u *pbad = su->su_badptr + badlenarg; - for (;;) { - goodlen = (int)(pgood - goodword); - badlen = (int)(pbad - su->su_badptr); - if (goodlen <= 0 || badlen <= 0) { - break; - } - MB_PTR_BACK(goodword, pgood); - MB_PTR_BACK(su->su_badptr, pbad); - if (utf_ptr2char((char *)pgood) != utf_ptr2char((char *)pbad)) { - break; - } - } - - if (badlen == 0 && goodlen == 0) { - // goodword doesn't change anything; may happen for "the the" changing - // the first "the" to itself. - return; - } - - int i; - if (GA_EMPTY(gap)) { - i = -1; - } else { - // Check if the word is already there. Also check the length that is - // being replaced "thes," -> "these" is a different suggestion from - // "thes" -> "these". - stp = &SUG(*gap, 0); - for (i = gap->ga_len; --i >= 0; ++stp) { - if (stp->st_wordlen == goodlen - && stp->st_orglen == badlen - && STRNCMP(stp->st_word, goodword, goodlen) == 0) { - // Found it. Remember the word with the lowest score. - if (stp->st_slang == NULL) { - stp->st_slang = slang; - } - - new_sug.st_score = score; - new_sug.st_altscore = altscore; - new_sug.st_had_bonus = had_bonus; - - if (stp->st_had_bonus != had_bonus) { - // Only one of the two had the soundalike score computed. - // Need to do that for the other one now, otherwise the - // scores can't be compared. This happens because - // suggest_try_change() doesn't compute the soundalike - // word to keep it fast, while some special methods set - // the soundalike score to zero. - if (had_bonus) { - rescore_one(su, stp); - } else { - new_sug.st_word = stp->st_word; - new_sug.st_wordlen = stp->st_wordlen; - new_sug.st_slang = stp->st_slang; - new_sug.st_orglen = badlen; - rescore_one(su, &new_sug); - } - } - - if (stp->st_score > new_sug.st_score) { - stp->st_score = new_sug.st_score; - stp->st_altscore = new_sug.st_altscore; - stp->st_had_bonus = new_sug.st_had_bonus; - } - break; - } - } - } - - if (i < 0) { - // Add a suggestion. - stp = GA_APPEND_VIA_PTR(suggest_T, gap); - stp->st_word = vim_strnsave(goodword, (size_t)goodlen); - stp->st_wordlen = goodlen; - stp->st_score = score; - stp->st_altscore = altscore; - stp->st_had_bonus = had_bonus; - stp->st_orglen = badlen; - stp->st_slang = slang; - - // If we have too many suggestions now, sort the list and keep - // the best suggestions. - if (gap->ga_len > SUG_MAX_COUNT(su)) { - if (maxsf) { - su->su_sfmaxscore = cleanup_suggestions(gap, - su->su_sfmaxscore, SUG_CLEAN_COUNT(su)); - } else { - su->su_maxscore = cleanup_suggestions(gap, - su->su_maxscore, SUG_CLEAN_COUNT(su)); - } - } - } -} - -/// Suggestions may in fact be flagged as errors. Esp. for banned words and -/// for split words, such as "the the". Remove these from the list here. -/// -/// @param gap either su_ga or su_sga -static void check_suggestions(suginfo_T *su, garray_T *gap) -{ - suggest_T *stp; - char_u longword[MAXWLEN + 1]; - int len; - hlf_T attr; - - if (gap->ga_len == 0) { - return; - } - 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); - len = stp[i].st_wordlen; - STRLCPY(longword + len, su->su_badptr + stp[i].st_orglen, - MAXWLEN - len + 1); - attr = HLF_COUNT; - (void)spell_check(curwin, longword, &attr, NULL, false); - if (attr != HLF_COUNT) { - // Remove this entry. - xfree(stp[i].st_word); - --gap->ga_len; - if (i < gap->ga_len) { - memmove(stp + i, stp + i + 1, sizeof(suggest_T) * (size_t)(gap->ga_len - i)); - } - } - } -} - -// Add a word to be banned. -static void add_banned(suginfo_T *su, char_u *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); - } -} - -// Recompute the score for all suggestions if sound-folding is possible. This -// is slow, thus only done for the final results. -static void rescore_suggestions(suginfo_T *su) -{ - if (su->su_sallang != NULL) { - for (int i = 0; i < su->su_ga.ga_len; ++i) { - rescore_one(su, &SUG(su->su_ga, i)); - } - } -} - -// Recompute the score for one suggestion if sound-folding is possible. -static void rescore_one(suginfo_T *su, suggest_T *stp) -{ - slang_T *slang = stp->st_slang; - char_u sal_badword[MAXWLEN]; - char_u *p; - - // Only rescore suggestions that have no sal score yet and do have a - // language. - if (slang != NULL && !GA_EMPTY(&slang->sl_sal) && !stp->st_had_bonus) { - if (slang == su->su_sallang) { - p = su->su_sal_badword; - } else { - spell_soundfold(slang, su->su_fbadword, true, sal_badword); - p = sal_badword; - } - - stp->st_altscore = stp_sal_score(stp, su, slang, p); - if (stp->st_altscore == SCORE_MAXMAX) { - stp->st_altscore = SCORE_BIG; - } - stp->st_score = RESCORE(stp->st_score, stp->st_altscore); - stp->st_had_bonus = true; - } -} - -// Function given to qsort() to sort the suggestions on st_score. -// First on "st_score", then "st_altscore" then alphabetically. -static int sug_compare(const void *s1, const void *s2) -{ - suggest_T *p1 = (suggest_T *)s1; - suggest_T *p2 = (suggest_T *)s2; - int n = p1->st_score - p2->st_score; - - if (n == 0) { - n = p1->st_altscore - p2->st_altscore; - if (n == 0) { - n = STRICMP(p1->st_word, p2->st_word); - } - } - return n; -} - -/// Cleanup the suggestions: -/// - Sort on score. -/// - Remove words that won't be displayed. -/// -/// @param keep nr of suggestions to keep -/// -/// @return the maximum score in the list or "maxscore" unmodified. -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); - - // 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; -} - /// Soundfold a string, for soundfold() /// /// @param[in] word Word to soundfold. @@ -5974,13 +2699,12 @@ char *eval_soundfold(const char *const word) /// @param[in,out] res destination for soundfolded word void spell_soundfold(slang_T *slang, char_u *inword, bool folded, char_u *res) { - char_u fword[MAXWLEN]; - char_u *word; - if (slang->sl_sofo) { // SOFOFROM and SOFOTO used spell_soundfold_sofo(slang, inword, res); } else { + char_u fword[MAXWLEN]; + char_u *word; // SAL items used. Requires the word to be case-folded. if (folded) { word = inword; @@ -6047,29 +2771,26 @@ static void spell_soundfold_wsal(slang_T *slang, char_u *inword, char_u *res) salitem_T *smp = (salitem_T *)slang->sl_sal.ga_data; int word[MAXWLEN] = { 0 }; int wres[MAXWLEN] = { 0 }; - int l; int *ws; int *pf; - int i, j, z; + int j, z; int reslen; - int n, k = 0; + int k = 0; int z0; int k0; int n0; - int c; int pri; int p0 = -333; int c0; bool did_white = false; - int wordlen; // Convert the multi-byte string to a wide-character string. // Remove accents, if wanted. We actually remove all non-word characters. // But keep white space. - wordlen = 0; + int wordlen = 0; for (const char_u *s = inword; *s != NUL;) { const char_u *t = s; - c = mb_cptr2char_adv(&s); + int c = mb_cptr2char_adv(&s); if (slang->sl_rem_accents) { if (utf_class(c) == 0) { if (did_white) { @@ -6088,13 +2809,14 @@ static void spell_soundfold_wsal(slang_T *slang, char_u *inword, char_u *res) } word[wordlen] = NUL; + int c; // This algorithm comes from Aspell phonet.cpp. // Converted from C++ to C. Added support for multi-byte chars. // Changed to keep spaces. - i = reslen = z = 0; + int i = reslen = z = 0; while ((c = word[i]) != NUL) { // Start with the first rule that has the character in the word. - n = slang->sl_sal_first[c & 0xff]; + int n = slang->sl_sal_first[c & 0xff]; z0 = 0; if (n >= 0) { @@ -6102,7 +2824,7 @@ static void spell_soundfold_wsal(slang_T *slang, char_u *inword, char_u *res) // If c is 0x300 need extra check for the end of the array, as // (c & 0xff) is NUL. for (; ((ws = smp[n].sm_lead_w)[0] & 0xff) == (c & 0xff) - && ws[0] != NUL; ++n) { + && ws[0] != NUL; n++) { // Quickly skip entries that don't match the word. Most // entries are less than three chars, optimize for that. if (c != ws[0]) { @@ -6114,7 +2836,7 @@ static void spell_soundfold_wsal(slang_T *slang, char_u *inword, char_u *res) continue; } if (k > 2) { - for (j = 2; j < k; ++j) { + for (j = 2; j < k; j++) { if (word[i + j] != ws[j]) { break; } @@ -6128,12 +2850,12 @@ static void spell_soundfold_wsal(slang_T *slang, char_u *inword, char_u *res) if ((pf = smp[n].sm_oneof_w) != NULL) { // Check for match with one of the chars in "sm_oneof". while (*pf != NUL && *pf != word[i + k]) { - ++pf; + pf++; } if (*pf == NUL) { continue; } - ++k; + k++; } char_u *s = smp[n].sm_rules; pri = 5; // default priority @@ -6175,7 +2897,7 @@ static void spell_soundfold_wsal(slang_T *slang, char_u *inword, char_u *res) // Test follow-up rule for "word[i + k]"; loop over // all entries with the same index byte. for (; ((ws = smp[n0].sm_lead_w)[0] & 0xff) - == (c0 & 0xff); ++n0) { + == (c0 & 0xff); n0++) { // Quickly skip entries that don't match the word. if (c0 != ws[0]) { continue; @@ -6187,7 +2909,7 @@ static void spell_soundfold_wsal(slang_T *slang, char_u *inword, char_u *res) } if (k0 > 2) { pf = word + i + k + 1; - for (j = 2; j < k0; ++j) { + for (j = 2; j < k0; j++) { if (*pf++ != ws[j]) { break; } @@ -6203,12 +2925,12 @@ static void spell_soundfold_wsal(slang_T *slang, char_u *inword, char_u *res) // Check for match with one of the chars in // "sm_oneof". while (*pf != NUL && *pf != word[i + k0]) { - ++pf; + pf++; } if (*pf == NUL) { continue; } - ++k0; + k0++; } p0 = 5; @@ -6329,8 +3051,8 @@ 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". - l = 0; - for (n = 0; n < reslen; n++) { + int l = 0; + for (int n = 0; n < reslen; n++) { l += utf_char2bytes(wres[n], (char *)res + l); if (l + MB_MAXBYTES > MAXWLEN) { break; @@ -6339,499 +3061,6 @@ static void spell_soundfold_wsal(slang_T *slang, char_u *inword, char_u *res) res[l] = NUL; } -/// Compute a score for two sound-a-like words. -/// This permits up to two inserts/deletes/swaps/etc. to keep things fast. -/// Instead of a generic loop we write out the code. That keeps it fast by -/// avoiding checks that will not be possible. -/// -/// @param goodstart sound-folded good word -/// @param badstart sound-folded bad word -static int soundalike_score(char_u *goodstart, char_u *badstart) -{ - char_u *goodsound = goodstart; - char_u *badsound = badstart; - int goodlen; - int badlen; - int n; - char_u *pl, *ps; - char_u *pl2, *ps2; - int score = 0; - - // Adding/inserting "*" at the start (word starts with vowel) shouldn't be - // counted so much, vowels in the middle of the word aren't counted at all. - if ((*badsound == '*' || *goodsound == '*') && *badsound != *goodsound) { - if ((badsound[0] == NUL && goodsound[1] == NUL) - || (goodsound[0] == NUL && badsound[1] == NUL)) { - // changing word with vowel to word without a sound - return SCORE_DEL; - } - if (badsound[0] == NUL || goodsound[0] == NUL) { - // more than two changes - return SCORE_MAXMAX; - } - - if (badsound[1] == goodsound[1] - || (badsound[1] != NUL - && goodsound[1] != NUL - && badsound[2] == goodsound[2])) { - // handle like a substitute - } else { - score = 2 * SCORE_DEL / 3; - if (*badsound == '*') { - ++badsound; - } else { - ++goodsound; - } - } - } - - goodlen = (int)STRLEN(goodsound); - badlen = (int)STRLEN(badsound); - - // Return quickly if the lengths are too different to be fixed by two - // changes. - n = goodlen - badlen; - if (n < -2 || n > 2) { - return SCORE_MAXMAX; - } - - if (n > 0) { - pl = goodsound; // goodsound is longest - ps = badsound; - } else { - pl = badsound; // badsound is longest - ps = goodsound; - } - - // Skip over the identical part. - while (*pl == *ps && *pl != NUL) { - ++pl; - ++ps; - } - - switch (n) { - case -2: - case 2: - // Must delete two characters from "pl". - ++pl; // first delete - while (*pl == *ps) { - ++pl; - ++ps; - } - // strings must be equal after second delete - if (STRCMP(pl + 1, ps) == 0) { - return score + SCORE_DEL * 2; - } - - // Failed to compare. - break; - - case -1: - case 1: - // Minimal one delete from "pl" required. - - // 1: delete - pl2 = pl + 1; - ps2 = ps; - while (*pl2 == *ps2) { - if (*pl2 == NUL) { // reached the end - return score + SCORE_DEL; - } - ++pl2; - ++ps2; - } - - // 2: delete then swap, then rest must be equal - if (pl2[0] == ps2[1] && pl2[1] == ps2[0] - && STRCMP(pl2 + 2, ps2 + 2) == 0) { - return score + SCORE_DEL + SCORE_SWAP; - } - - // 3: delete then substitute, then the rest must be equal - if (STRCMP(pl2 + 1, ps2 + 1) == 0) { - return score + SCORE_DEL + SCORE_SUBST; - } - - // 4: first swap then delete - if (pl[0] == ps[1] && pl[1] == ps[0]) { - pl2 = pl + 2; // swap, skip two chars - ps2 = ps + 2; - while (*pl2 == *ps2) { - ++pl2; - ++ps2; - } - // delete a char and then strings must be equal - if (STRCMP(pl2 + 1, ps2) == 0) { - return score + SCORE_SWAP + SCORE_DEL; - } - } - - // 5: first substitute then delete - pl2 = pl + 1; // substitute, skip one char - ps2 = ps + 1; - while (*pl2 == *ps2) { - ++pl2; - ++ps2; - } - // delete a char and then strings must be equal - if (STRCMP(pl2 + 1, ps2) == 0) { - return score + SCORE_SUBST + SCORE_DEL; - } - - // Failed to compare. - break; - - case 0: - // Lengths are equal, thus changes must result in same length: An - // insert is only possible in combination with a delete. - // 1: check if for identical strings - if (*pl == NUL) { - return score; - } - - // 2: swap - if (pl[0] == ps[1] && pl[1] == ps[0]) { - pl2 = pl + 2; // swap, skip two chars - ps2 = ps + 2; - while (*pl2 == *ps2) { - if (*pl2 == NUL) { // reached the end - return score + SCORE_SWAP; - } - ++pl2; - ++ps2; - } - // 3: swap and swap again - if (pl2[0] == ps2[1] && pl2[1] == ps2[0] - && STRCMP(pl2 + 2, ps2 + 2) == 0) { - return score + SCORE_SWAP + SCORE_SWAP; - } - - // 4: swap and substitute - if (STRCMP(pl2 + 1, ps2 + 1) == 0) { - return score + SCORE_SWAP + SCORE_SUBST; - } - } - - // 5: substitute - pl2 = pl + 1; - ps2 = ps + 1; - while (*pl2 == *ps2) { - if (*pl2 == NUL) { // reached the end - return score + SCORE_SUBST; - } - ++pl2; - ++ps2; - } - - // 6: substitute and swap - if (pl2[0] == ps2[1] && pl2[1] == ps2[0] - && STRCMP(pl2 + 2, ps2 + 2) == 0) { - return score + SCORE_SUBST + SCORE_SWAP; - } - - // 7: substitute and substitute - if (STRCMP(pl2 + 1, ps2 + 1) == 0) { - return score + SCORE_SUBST + SCORE_SUBST; - } - - // 8: insert then delete - pl2 = pl; - ps2 = ps + 1; - while (*pl2 == *ps2) { - ++pl2; - ++ps2; - } - if (STRCMP(pl2 + 1, ps2) == 0) { - return score + SCORE_INS + SCORE_DEL; - } - - // 9: delete then insert - pl2 = pl + 1; - ps2 = ps; - while (*pl2 == *ps2) { - ++pl2; - ++ps2; - } - if (STRCMP(pl2, ps2 + 1) == 0) { - return score + SCORE_INS + SCORE_DEL; - } - - // Failed to compare. - break; - } - - return SCORE_MAXMAX; -} - -// Compute the "edit distance" to turn "badword" into "goodword". The less -// deletes/inserts/substitutes/swaps are required the lower the score. -// -// The algorithm is described by Du and Chang, 1992. -// 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) -{ - int *cnt; - int j, i; - int t; - int bc, gc; - int pbc, pgc; - int wbadword[MAXWLEN]; - int wgoodword[MAXWLEN]; - - // Lengths with NUL. - int badlen; - int goodlen; - { - // 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;) { - wbadword[badlen++] = mb_cptr2char_adv(&p); - } - wbadword[badlen++] = 0; - goodlen = 0; - for (const char_u *p = goodword; *p != NUL;) { - wgoodword[goodlen++] = mb_cptr2char_adv(&p); - } - wgoodword[goodlen++] = 0; - } - - // We use "cnt" as an array: CNT(badword_idx, goodword_idx). -#define CNT(a, b) cnt[(a) + (b) * (badlen + 1)] - cnt = xmalloc(sizeof(int) * ((size_t)badlen + 1) * ((size_t)goodlen + 1)); - - CNT(0, 0) = 0; - for (j = 1; j <= goodlen; ++j) { - CNT(0, j) = CNT(0, j - 1) + SCORE_INS; - } - - for (i = 1; i <= badlen; ++i) { - CNT(i, 0) = CNT(i - 1, 0) + SCORE_DEL; - for (j = 1; j <= goodlen; j++) { - bc = wbadword[i - 1]; - gc = wgoodword[j - 1]; - if (bc == gc) { - CNT(i, j) = CNT(i - 1, j - 1); - } else { - // Use a better score when there is only a case difference. - if (SPELL_TOFOLD(bc) == SPELL_TOFOLD(gc)) { - CNT(i, j) = SCORE_ICASE + CNT(i - 1, j - 1); - } else { - // For a similar character use SCORE_SIMILAR. - if (slang != NULL - && slang->sl_has_map - && similar_chars(slang, gc, bc)) { - CNT(i, j) = SCORE_SIMILAR + CNT(i - 1, j - 1); - } else { - CNT(i, j) = SCORE_SUBST + CNT(i - 1, j - 1); - } - } - - if (i > 1 && j > 1) { - pbc = wbadword[i - 2]; - pgc = wgoodword[j - 2]; - if (bc == pgc && pbc == gc) { - t = SCORE_SWAP + CNT(i - 2, j - 2); - if (t < CNT(i, j)) { - CNT(i, j) = t; - } - } - } - t = SCORE_DEL + CNT(i - 1, j); - if (t < CNT(i, j)) { - CNT(i, j) = t; - } - t = SCORE_INS + CNT(i, j - 1); - if (t < CNT(i, j)) { - CNT(i, j) = t; - } - } - } - } - - i = CNT(badlen - 1, goodlen - 1); - xfree(cnt); - return i; -} - -// Like spell_edit_score(), but with a limit on the score to make it faster. -// May return SCORE_MAXMAX when the score is higher than "limit". -// -// This uses a stack for the edits still to be tried. -// The idea comes from Aspell leditdist.cpp. Rewritten in C and added support -// for multi-byte characters. -static int spell_edit_score_limit(slang_T *slang, char_u *badword, char_u *goodword, int limit) -{ - return spell_edit_score_limit_w(slang, badword, goodword, limit); -} - -// 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) -{ - limitscore_T stack[10]; // allow for over 3 * 2 edits - int stackidx; - int bi, gi; - int bi2, gi2; - int bc, gc; - int score; - int score_off; - int minscore; - int round; - int wbadword[MAXWLEN]; - int wgoodword[MAXWLEN]; - - // 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;) { - wbadword[bi++] = mb_cptr2char_adv(&p); - } - wbadword[bi++] = 0; - gi = 0; - for (const char_u *p = goodword; *p != NUL;) { - wgoodword[gi++] = mb_cptr2char_adv(&p); - } - wgoodword[gi++] = 0; - - // The idea is to go from start to end over the words. So long as - // characters are equal just continue, this always gives the lowest score. - // When there is a difference try several alternatives. Each alternative - // increases "score" for the edit distance. Some of the alternatives are - // pushed unto a stack and tried later, some are tried right away. At the - // end of the word the score for one alternative is known. The lowest - // possible score is stored in "minscore". - stackidx = 0; - bi = 0; - gi = 0; - score = 0; - minscore = limit + 1; - - for (;;) { - // Skip over an equal part, score remains the same. - for (;;) { - bc = wbadword[bi]; - gc = wgoodword[gi]; - - if (bc != gc) { // stop at a char that's different - break; - } - if (bc == NUL) { // both words end - if (score < minscore) { - minscore = score; - } - goto pop; // do next alternative - } - ++bi; - ++gi; - } - - if (gc == NUL) { // goodword ends, delete badword chars - do { - if ((score += SCORE_DEL) >= minscore) { - goto pop; // do next alternative - } - } while (wbadword[++bi] != NUL); - minscore = score; - } else if (bc == NUL) { // badword ends, insert badword chars - do { - if ((score += SCORE_INS) >= minscore) { - goto pop; // do next alternative - } - } while (wgoodword[++gi] != NUL); - minscore = score; - } else { // both words continue - // If not close to the limit, perform a change. Only try changes - // that may lead to a lower score than "minscore". - // round 0: try deleting a char from badword - // round 1: try inserting a char in badword - for (round = 0; round <= 1; ++round) { - score_off = score + (round == 0 ? SCORE_DEL : SCORE_INS); - if (score_off < minscore) { - if (score_off + SCORE_EDIT_MIN >= minscore) { - // Near the limit, rest of the words must match. We - // can check that right now, no need to push an item - // onto the stack. - bi2 = bi + 1 - round; - gi2 = gi + round; - while (wgoodword[gi2] == wbadword[bi2]) { - if (wgoodword[gi2] == NUL) { - minscore = score_off; - break; - } - ++bi2; - ++gi2; - } - } else { - // try deleting a character from badword later - stack[stackidx].badi = bi + 1 - round; - stack[stackidx].goodi = gi + round; - stack[stackidx].score = score_off; - ++stackidx; - } - } - } - - if (score + SCORE_SWAP < minscore) { - // If swapping two characters makes a match then the - // substitution is more expensive, thus there is no need to - // try both. - if (gc == wbadword[bi + 1] && bc == wgoodword[gi + 1]) { - // Swap two characters, that is: skip them. - gi += 2; - bi += 2; - score += SCORE_SWAP; - continue; - } - } - - // Substitute one character for another which is the same - // thing as deleting a character from both goodword and badword. - // Use a better score when there is only a case difference. - if (SPELL_TOFOLD(bc) == SPELL_TOFOLD(gc)) { - score += SCORE_ICASE; - } else { - // For a similar character use SCORE_SIMILAR. - if (slang != NULL - && slang->sl_has_map - && similar_chars(slang, gc, bc)) { - score += SCORE_SIMILAR; - } else { - score += SCORE_SUBST; - } - } - - if (score < minscore) { - // Do the substitution. - ++gi; - ++bi; - continue; - } - } -pop: - // Get here to try the next alternative, pop it from the stack. - if (stackidx == 0) { // stack is empty, finished - break; - } - - // pop an item from the stack - --stackidx; - gi = stack[stackidx].goodi; - bi = stack[stackidx].badi; - score = stack[stackidx].score; - } - - // When the score goes over "limit" it may actually be much higher. - // Return a very large number to avoid going below the limit when giving a - // bonus. - if (minscore > limit) { - return SCORE_MAXMAX; - } - return minscore; -} - // ":spellinfo" void ex_spellinfo(exarg_T *eap) { @@ -6863,20 +3092,19 @@ void ex_spellinfo(exarg_T *eap) // ":spelldump" void ex_spelldump(exarg_T *eap) { - char *spl; - long dummy; - if (no_spell_checking(curwin)) { return; } + char *spl; + long dummy; (void)get_option_value("spl", &dummy, &spl, OPT_LOCAL); // Create a new empty buffer in a new window. do_cmdline_cmd("new"); // enable spelling locally in the new window - set_option_value("spell", true, "", OPT_LOCAL); - set_option_value("spl", dummy, spl, OPT_LOCAL); + set_option_value_give_err("spell", true, "", OPT_LOCAL); + set_option_value_give_err("spl", dummy, spl, OPT_LOCAL); xfree(spl); if (!buf_is_empty(curbuf)) { @@ -6889,7 +3117,7 @@ void ex_spelldump(exarg_T *eap) if (curbuf->b_ml.ml_line_count > 1) { ml_delete(curbuf->b_ml.ml_line_count, false); } - redraw_later(curwin, NOT_VALID); + redraw_later(curwin, UPD_NOT_VALID); } /// Go through all possible words and: @@ -6912,7 +3140,6 @@ void spell_dump_compl(char_u *pat, int ic, Direction *dir, int dumpflags_arg) char_u *byts; idx_T *idxs; linenr_T lnum = 0; - int round; int depth; int n; int flags; @@ -6940,7 +3167,7 @@ void spell_dump_compl(char_u *pat, int ic, Direction *dir, int dumpflags_arg) // Find out if we can support regions: All languages must support the same // regions or none at all. - for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len; ++lpi) { + for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len; lpi++) { lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi); p = lp->lp_slang->sl_regions; if (p[0] != 0) { @@ -6963,7 +3190,7 @@ void spell_dump_compl(char_u *pat, int ic, Direction *dir, int dumpflags_arg) } // Loop over all files loaded for the entries in 'spelllang'. - for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len; ++lpi) { + for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len; lpi++) { lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi); slang = lp->lp_slang; if (slang->sl_fbyts == NULL) { // reloading failed @@ -6985,7 +3212,7 @@ void spell_dump_compl(char_u *pat, int ic, Direction *dir, int dumpflags_arg) // round 1: case-folded tree // round 2: keep-case tree - for (round = 1; round <= 2; ++round) { + for (int round = 1; round <= 2; round++) { if (round == 1) { dumpflags &= ~DUMPFLAG_KEEPCASE; byts = slang->sl_fbyts; @@ -7005,13 +3232,13 @@ void spell_dump_compl(char_u *pat, int ic, Direction *dir, int dumpflags_arg) && (pat == NULL || !ins_compl_interrupted())) { if (curi[depth] > byts[arridx[depth]]) { // Done all bytes at this node, go up one level. - --depth; + depth--; line_breakcheck(); ins_compl_check_keys(50, false); } else { // Do one more byte at this node. n = arridx[depth] + curi[depth]; - ++curi[depth]; + curi[depth]++; c = byts[n]; if (c == 0 || depth >= MAXWLEN - 1) { // End of word or reached maximum length, deal with the @@ -7038,7 +3265,7 @@ void spell_dump_compl(char_u *pat, int ic, Direction *dir, int dumpflags_arg) dump_word(slang, word, pat, dir, dumpflags, flags, lnum); if (pat == NULL) { - ++lnum; + lnum++; } } @@ -7063,7 +3290,7 @@ void spell_dump_compl(char_u *pat, int ic, Direction *dir, int dumpflags_arg) assert(depth >= 0); if (depth <= patlen && mb_strnicmp(word, pat, (size_t)depth) != 0) { - --depth; + depth--; } } } @@ -7079,10 +3306,8 @@ static void dump_word(slang_T *slang, char_u *word, char_u *pat, Direction *dir, { bool keepcap = false; char_u *p; - char_u *tw; char_u cword[MAXWLEN]; char_u badword[MAXWLEN + 10]; - int i; int flags = wordflags; if (dumpflags & DUMPFLAG_ONECAP) { @@ -7104,7 +3329,7 @@ static void dump_word(slang_T *slang, char_u *word, char_u *pat, Direction *dir, keepcap = true; } } - tw = p; + char_u *tw = p; if (pat == NULL) { // Add flags and regions after a slash. @@ -7120,7 +3345,7 @@ static void dump_word(slang_T *slang, char_u *word, char_u *pat, Direction *dir, STRCAT(badword, "?"); } if (flags & WF_REGION) { - for (i = 0; i < 7; i++) { + for (int i = 0; i < 7; i++) { if (flags & (0x10000 << i)) { const size_t badword_len = STRLEN(badword); snprintf((char *)badword + badword_len, @@ -7171,46 +3396,40 @@ static linenr_T dump_prefixes(slang_T *slang, char_u *word, char_u *pat, Directi char_u prefix[MAXWLEN]; char_u word_up[MAXWLEN]; bool has_word_up = false; - int c; - char_u *byts; - idx_T *idxs; linenr_T lnum = startlnum; - int depth; - int n; - int len; - int i; // If the word starts with a lower-case letter make the word with an // upper-case letter in word_up[]. - c = utf_ptr2char((char *)word); + int c = utf_ptr2char((char *)word); if (SPELL_TOUPPER(c) != c) { onecap_copy(word, word_up, true); has_word_up = true; } - byts = slang->sl_pbyts; - idxs = slang->sl_pidxs; + char_u *byts = 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[]. // When at the end of a prefix check that it supports "flags". - depth = 0; + int depth = 0; arridx[0] = 0; curi[0] = 1; while (depth >= 0 && !got_int) { - n = arridx[depth]; - len = byts[n]; + int n = arridx[depth]; + int len = byts[n]; if (curi[depth] > len) { // Done all bytes at this node, go up one level. - --depth; + depth--; line_breakcheck(); } else { // Do one more byte at this node. n += curi[depth]; - ++curi[depth]; + curi[depth]++; c = byts[n]; if (c == 0) { // End of prefix, find out how many IDs there are. - for (i = 1; i < len; ++i) { + int i; + for (i = 1; i < len; i++) { if (byts[n + i] != 0) { break; } @@ -7221,10 +3440,9 @@ static linenr_T dump_prefixes(slang_T *slang, char_u *word, char_u *pat, Directi if (c != 0) { STRLCPY(prefix + depth, word, MAXWLEN - depth); dump_word(slang, prefix, pat, dir, dumpflags, - (c & WF_RAREPFX) ? (flags | WF_RARE) - : flags, lnum); + (c & WF_RAREPFX) ? (flags | WF_RARE) : flags, lnum); if (lnum != 0) { - ++lnum; + lnum++; } } @@ -7237,10 +3455,9 @@ static linenr_T dump_prefixes(slang_T *slang, char_u *word, char_u *pat, Directi if (c != 0) { STRLCPY(prefix + depth, word_up, MAXWLEN - depth); dump_word(slang, prefix, pat, dir, dumpflags, - (c & WF_RAREPFX) ? (flags | WF_RARE) - : flags, lnum); + (c & WF_RAREPFX) ? (flags | WF_RARE) : flags, lnum); if (lnum != 0) { - ++lnum; + lnum++; } } } @@ -7276,16 +3493,14 @@ char_u *spell_to_word_end(char_u *start, win_T *win) // Returns the column number of the word. int spell_word_start(int startcol) { - char_u *line; - char_u *p; - int col = 0; - if (no_spell_checking(curwin)) { return startcol; } + char_u *line = get_cursor_line_ptr(); + char_u *p; + // Find a word character before "startcol". - line = get_cursor_line_ptr(); for (p = line + startcol; p > line;) { MB_PTR_BACK(line, p); if (spell_iswordp_nmw(p, curwin)) { @@ -7293,6 +3508,8 @@ int spell_word_start(int startcol) } } + int col = 0; + // Go back to start of the word. while (p > line) { col = (int)(p - line); @@ -7327,3 +3544,70 @@ int expand_spelling(linenr_T lnum, char_u *pat, char ***matchp) *matchp = ga.ga_data; return ga.ga_len; } + +/// @return true if "val" is a valid 'spelllang' value. +bool valid_spelllang(const char *val) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ + return valid_name(val, ".-_,@"); +} + +/// @return true if "val" is a valid 'spellfile' value. +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 != ' ') { + return false; + } + } + return true; +} + +char *did_set_spell_option(bool is_spellfile) +{ + char *errmsg = NULL; + + if (is_spellfile) { + int l = (int)STRLEN(curwin->w_s->b_p_spf); + if (l > 0 + && (l < 4 || STRCMP(curwin->w_s->b_p_spf + l - 4, ".add") != 0)) { + errmsg = e_invarg; + } + } + + 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; + } + } + } + + return errmsg; +} + +/// Set curbuf->b_cap_prog to the regexp program for 'spellcapcheck'. +/// Return error message when failed, NULL when OK. +char *compile_cap_prog(synblock_T *synblock) + FUNC_ATTR_NONNULL_ALL +{ + regprog_T *rp = synblock->b_cap_prog; + + if (synblock->b_p_spc == NULL || *synblock->b_p_spc == NUL) { + synblock->b_cap_prog = NULL; + } else { + // Prepend a ^ so that we only match at one column + char *re = concat_str("^", synblock->b_p_spc); + synblock->b_cap_prog = vim_regcomp(re, RE_MAGIC); + xfree(re); + if (synblock->b_cap_prog == NULL) { + synblock->b_cap_prog = rp; // restore the previous program + return e_invarg; + } + } + + vim_regfree(rp); + return NULL; +} diff --git a/src/nvim/spell_defs.h b/src/nvim/spell_defs.h index 222d103f5d..27b9777dc2 100644 --- a/src/nvim/spell_defs.h +++ b/src/nvim/spell_defs.h @@ -35,6 +35,8 @@ typedef int idx_T; #define WF_FIXCAP 0x40 // keep-case word, allcap not allowed #define WF_KEEPCAP 0x80 // keep-case word +#define WF_CAPMASK (WF_ONECAP | WF_ALLCAP | WF_KEEPCAP | WF_FIXCAP) + // for <flags2>, shifted up one byte to be used in wn_flags #define WF_HAS_AFF 0x0100 // word includes affix #define WF_NEEDCOMP 0x0200 // word only valid in compound @@ -113,9 +115,9 @@ typedef struct slang_S slang_T; struct slang_S { slang_T *sl_next; // next language - char_u *sl_name; // language name "en", "en.rare", "nl", etc. - char_u *sl_fname; // name of .spl file - bool sl_add; // true if it's a .add file. + char_u *sl_name; // language name "en", "en.rare", "nl", etc. + 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 long sl_fbyts_len; // length of sl_fbyts @@ -208,56 +210,6 @@ typedef struct { char_u st_upper[256]; // chars: upper case } spelltab_T; -// For finding suggestions: At each node in the tree these states are tried: -typedef enum { - STATE_START = 0, // At start of node check for NUL bytes (goodword - // ends); if badword ends there is a match, otherwise - // try splitting word. - STATE_NOPREFIX, // try without prefix - STATE_SPLITUNDO, // Undo splitting. - STATE_ENDNUL, // Past NUL bytes at start of the node. - STATE_PLAIN, // Use each byte of the node. - STATE_DEL, // Delete a byte from the bad word. - STATE_INS_PREP, // Prepare for inserting bytes. - STATE_INS, // Insert a byte in the bad word. - STATE_SWAP, // Swap two bytes. - STATE_UNSWAP, // Undo swap two characters. - STATE_SWAP3, // Swap two characters over three. - STATE_UNSWAP3, // Undo Swap two characters over three. - STATE_UNROT3L, // Undo rotate three characters left - STATE_UNROT3R, // Undo rotate three characters right - STATE_REP_INI, // Prepare for using REP items. - STATE_REP, // Use matching REP items from the .aff file. - STATE_REP_UNDO, // Undo a REP item replacement. - STATE_FINAL, // End of this node. -} state_T; - -// Struct to keep the state at each level in suggest_try_change(). -typedef struct trystate_S { - state_T ts_state; // state at this level, STATE_ - int ts_score; // score - idx_T ts_arridx; // index in tree array, start of node - int16_t ts_curi; // index in list of child nodes - char_u ts_fidx; // index in fword[], case-folded bad word - char_u ts_fidxtry; // ts_fidx at which bytes may be changed - char_u ts_twordlen; // valid length of tword[] - char_u ts_prefixdepth; // stack depth for end of prefix or - // PFD_PREFIXTREE or PFD_NOPREFIX - char_u ts_flags; // TSF_ flags - char_u ts_tcharlen; // number of bytes in tword character - char_u ts_tcharidx; // current byte index in tword character - char_u ts_isdiff; // DIFF_ values - char_u ts_fcharstart; // index in fword where badword char started - char_u ts_prewordlen; // length of word in "preword[]" - char_u ts_splitoff; // index in "tword" after last split - char_u ts_splitfidx; // "ts_fidx" at word split - char_u ts_complen; // nr of compound words used - char_u ts_compsplit; // index for "compflags" where word was spit - char_u ts_save_badflags; // su_badflags saved here - char_u ts_delidx; // index in fword for char that was deleted, - // valid when "ts_flags" has TSF_DIDDEL -} trystate_T; - // Use our own character-case definitions, because the current locale may // differ from what the .spl file uses. // These must not be called with negative number! @@ -290,4 +242,17 @@ typedef enum { SPELL_ADD_RARE = 2, } SpellAddType; +typedef struct wordcount_S { + uint16_t wc_count; ///< nr of times word was seen + char_u wc_word[1]; ///< word, actually longer +} wordcount_T; + +#define WC_KEY_OFF offsetof(wordcount_T, wc_word) +#define HI2WC(hi) ((wordcount_T *)((hi)->hi_key - WC_KEY_OFF)) +#define MAXWORDCOUNT 0xffff + +// Remember what "z?" replaced. +extern char_u *repl_from; +extern char_u *repl_to; + #endif // NVIM_SPELL_DEFS_H diff --git a/src/nvim/spellfile.c b/src/nvim/spellfile.c index 9f21e24d4c..0f8034312e 100644 --- a/src/nvim/spellfile.c +++ b/src/nvim/spellfile.c @@ -230,9 +230,11 @@ #include <stdio.h> #include <wctype.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/fileio.h" #include "nvim/memline.h" @@ -242,7 +244,7 @@ #include "nvim/os/os.h" #include "nvim/path.h" #include "nvim/regexp.h" -#include "nvim/screen.h" +#include "nvim/runtime.h" #include "nvim/spell.h" #include "nvim/spell_defs.h" #include "nvim/spellfile.h" @@ -438,7 +440,7 @@ typedef struct spellinfo_S { sblock_T *si_blocks; // memory blocks used long si_blocks_cnt; // memory blocks allocated - int si_did_emsg; // TRUE when ran out of memory + int si_did_emsg; // true when ran out of memory long si_compress_cnt; // words to add before lowering // compression limit @@ -452,7 +454,7 @@ typedef struct spellinfo_S { int si_ascii; // handling only ASCII words int si_add; // addition file - int si_clear_chartab; // when TRUE clear char tables + int si_clear_chartab; // when true clear char tables int si_region; // region mask vimconv_T si_conv; // for conversion to 'encoding' int si_memtot; // runtime memory used @@ -576,11 +578,10 @@ slang_T *spell_load_file(char_u *fname, char_u *lang, slang_T *old_lp, bool sile char_u *p; int n; int len; - char_u *save_sourcing_name = (char_u *)sourcing_name; - linenr_T save_sourcing_lnum = sourcing_lnum; slang_T *lp = NULL; int c = 0; int res; + bool did_estack_push = false; fd = os_fopen((char *)fname, "r"); if (fd == NULL) { @@ -603,7 +604,7 @@ slang_T *spell_load_file(char_u *fname, char_u *lang, slang_T *old_lp, bool sile lp = slang_alloc(lang); // Remember the file name, used to reload the file when it's updated. - lp->sl_fname = vim_strsave(fname); + lp->sl_fname = (char *)vim_strsave(fname); // Check for .add.spl. lp->sl_add = strstr(path_tail((char *)fname), SPL_FNAME_ADD) != NULL; @@ -612,8 +613,8 @@ slang_T *spell_load_file(char_u *fname, char_u *lang, slang_T *old_lp, bool sile } // Set sourcing_name, so that error messages mention the file name. - sourcing_name = (char *)fname; - sourcing_lnum = 0; + estack_push(ETYPE_SPELL, (char *)fname, 0); + did_estack_push = true; // <HEADER>: <fileID> const int scms_ret = spell_check_magic_string(fd); @@ -809,8 +810,9 @@ endOK: if (fd != NULL) { fclose(fd); } - sourcing_name = (char *)save_sourcing_name; - sourcing_lnum = save_sourcing_lnum; + if (did_estack_push) { + estack_pop(); + } return lp; } @@ -838,27 +840,27 @@ static void tree_count_words(char_u *byts, idx_T *idxs) wordcount[depth - 1] += wordcount[depth]; } - --depth; + depth--; fast_breakcheck(); } else { // Do one more byte at this node. n = arridx[depth] + curi[depth]; - ++curi[depth]; + curi[depth]++; c = byts[n]; if (c == 0) { // End of word, count it. - ++wordcount[depth]; + wordcount[depth]++; // Skip over any other NUL bytes (same word with different // flags). while (byts[n + 1] == 0) { - ++n; - ++curi[depth]; + n++; + curi[depth]++; } } else { // Normal char, go one level deeper to count the words. - ++depth; + depth++; arridx[depth] = idxs[n]; curi[depth] = 1; wordcount[depth] = 0; @@ -867,12 +869,12 @@ static void tree_count_words(char_u *byts, idx_T *idxs) } } -// Load the .sug files for languages that have one and weren't loaded yet. +/// Load the .sug files for languages that have one and weren't loaded yet. void suggest_load_files(void) { langp_T *lp; slang_T *slang; - char_u *dotp; + char *dotp; FILE *fd; char_u buf[MAXWLEN]; int i; @@ -883,7 +885,7 @@ void suggest_load_files(void) int c; // Do this for all languages that support sound folding. - for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len; ++lpi) { + for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len; lpi++) { lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi); slang = lp->lp_slang; if (slang->sl_sugtime != 0 && !slang->sl_sugloaded) { @@ -892,12 +894,12 @@ void suggest_load_files(void) // don't try again and again. slang->sl_sugloaded = true; - dotp = STRRCHR(slang->sl_fname, '.'); + dotp = strrchr(slang->sl_fname, '.'); if (dotp == NULL || FNAMECMP(dotp, ".spl") != 0) { continue; } STRCPY(dotp, ".sug"); - fd = os_fopen((char *)slang->sl_fname, "r"); + fd = os_fopen(slang->sl_fname, "r"); if (fd == NULL) { goto nextone; } @@ -958,7 +960,7 @@ someerror: // Read all the wordnr lists into the buffer, one NUL terminated // list per line. ga_init(&ga, 1, 100); - for (wordnr = 0; wordnr < wcount; ++wordnr) { + for (wordnr = 0; wordnr < wcount; wordnr++) { ga.ga_len = 0; for (;;) { c = getc(fd); // <sugline> @@ -1140,10 +1142,10 @@ static int read_rep_section(FILE *fd, garray_T *gap, int16_t *first) } // Fill the first-index table. - for (int i = 0; i < 256; ++i) { + for (int i = 0; i < 256; i++) { first[i] = -1; } - for (int i = 0; i < gap->ga_len; ++i) { + 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; @@ -1198,7 +1200,7 @@ static int read_sal_section(FILE *fd, slang_T *slang) // Read up to the first special char into sm_lead. int i = 0; - for (; i < ccnt; ++i) { + for (; i < ccnt; i++) { c = getc(fd); // <salfrom> if (vim_strchr("0123456789(-<^$", c) != NULL) { break; @@ -1211,7 +1213,7 @@ static int read_sal_section(FILE *fd, slang_T *slang) // Put (abc) chars in sm_oneof, if any. if (c == '(') { smp->sm_oneof = p; - for (++i; i < ccnt; ++i) { + for (++i; i < ccnt; i++) { c = getc(fd); // <salfrom> if (c == ')') { break; @@ -1297,7 +1299,7 @@ static int read_words_section(FILE *fd, slang_T *lp, int len) while (done < len) { // Read one word at a time. - for (i = 0;; ++i) { + for (i = 0;; i++) { c = getc(fd); if (c == EOF) { return SP_TRUNCERROR; @@ -1369,21 +1371,21 @@ static int read_compound(FILE *fd, slang_T *slang, int len) if (todo < 2) { return SP_FORMERROR; // need at least two bytes } - --todo; + todo--; c = getc(fd); // <compmax> if (c < 2) { c = MAXWLEN; } slang->sl_compmax = c; - --todo; + todo--; c = getc(fd); // <compminlen> if (c < 1) { c = 0; } slang->sl_compminlen = c; - --todo; + todo--; c = getc(fd); // <compsylmax> if (c < 1) { c = MAXWLEN; @@ -1394,9 +1396,9 @@ static int read_compound(FILE *fd, slang_T *slang, int len) if (c != 0) { ungetc(c, fd); // be backwards compatible with Vim 7.0b } else { - --todo; + todo--; c = getc(fd); // only use the lower byte for now - --todo; + todo--; slang->sl_compoptions = c; gap = &slang->sl_comppat; @@ -1408,8 +1410,7 @@ static int read_compound(FILE *fd, slang_T *slang, int len) ga_init(gap, sizeof(char_u *), c); ga_grow(gap, c); while (--c >= 0) { - ((char_u **)(gap->ga_data))[gap->ga_len++] = - read_cnt_string(fd, 1, &cnt); + ((char **)(gap->ga_data))[gap->ga_len++] = (char *)read_cnt_string(fd, 1, &cnt); // <comppatlen> <comppattext> if (cnt < 0) { return cnt; @@ -1596,7 +1597,7 @@ static void set_sal_first(slang_T *lp) garray_T *gap = &lp->sl_sal; sfirst = lp->sl_sal_first; - for (int i = 0; i < 256; ++i) { + for (int i = 0; i < 256; i++) { sfirst[i] = -1; } smp = (salitem_T *)gap->ga_data; @@ -1731,7 +1732,7 @@ static idx_T read_tree_node(FILE *fd, char_u *byts, idx_T *idxs, int maxidx, idx byts[idx++] = (char_u)len; // Read the byte values, flag/region bytes and shared indexes. - for (i = 1; i <= len; ++i) { + for (i = 1; i <= len; i++) { c = getc(fd); // <byte> if (c < 0) { return SP_TRUNCERROR; @@ -1794,7 +1795,7 @@ static idx_T read_tree_node(FILE *fd, char_u *byts, idx_T *idxs, int maxidx, idx // Recursively read the children for non-shared siblings. // Skip the end-of-word ones (zero byte value) and the shared ones (and // remove SHARED_MASK) - for (i = 1; i <= len; ++i) { + for (i = 1; i <= len; i++) { if (byts[startidx + i] != 0) { if (idxs[startidx + i] & SHARED_MASK) { idxs[startidx + i] &= ~SHARED_MASK; @@ -1821,13 +1822,13 @@ static void spell_reload_one(char_u *fname, bool added_word) bool didit = false; for (slang = first_lang; slang != NULL; slang = slang->sl_next) { - if (path_full_compare((char *)fname, (char *)slang->sl_fname, false, true) == kEqualFiles) { + if (path_full_compare((char *)fname, slang->sl_fname, false, true) == kEqualFiles) { slang_clear(slang); if (spell_load_file(fname, NULL, slang, false) == NULL) { // reloading failed, clear the language slang_clear(slang); } - redraw_all_later(SOME_VALID); + redraw_all_later(UPD_SOME_VALID); didit = true; } } @@ -1862,7 +1863,7 @@ static long compress_added = 500000; // word count // Sets "sps_flags". int spell_check_msm(void) { - char *p = (char *)p_msm; + char *p = p_msm; long start = 0; long incr = 0; long added = 0; @@ -1923,7 +1924,7 @@ static void spell_clear_flags(wordnode_T *node) wordnode_T *np; for (np = node; np != NULL; np = np->wn_sibling) { - np->wn_u1.index = FALSE; + np->wn_u1.index = false; spell_clear_flags(np->wn_child); } } @@ -1939,7 +1940,7 @@ static void spell_print_node(wordnode_T *node, int depth) msg((char_u *)line2); msg((char_u *)line3); } else { - node->wn_u1.index = TRUE; + node->wn_u1.index = true; if (node->wn_byte != NUL) { if (node->wn_child != NULL) { @@ -2041,7 +2042,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) } vim_snprintf((char *)IObuff, IOSIZE, _("Reading affix file %s..."), fname); - spell_message(spin, IObuff); + spell_message(spin, (char *)IObuff); // Only do REP lines when not done in another .aff file already. do_rep = GA_EMPTY(&spin->si_rep); @@ -2064,7 +2065,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) // Read all the lines in the file one by one. while (!vim_fgets(rline, MAXLINELEN, fd) && !got_int) { line_breakcheck(); - ++lnum; + lnum++; // Skip comment lines. if (*rline == '#') { @@ -2074,7 +2075,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 = string_convert(&spin->si_conv, rline, NULL); + pc = (char_u *)string_convert(&spin->si_conv, (char *)rline, NULL); if (pc == NULL) { smsg(_("Conversion failure for word in %s line %d: %s"), fname, lnum, rline); @@ -2091,7 +2092,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) itemcnt = 0; for (p = line;;) { while (*p != NUL && *p <= ' ') { // skip white space and CR/NL - ++p; + p++; } if (*p == NUL) { break; @@ -2103,11 +2104,11 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) // A few items have arbitrary text argument, don't split them. if (itemcnt == 2 && spell_info_item(items[0])) { while (*p >= ' ' || *p == TAB) { // skip until CR/NL - ++p; + p++; } } else { while (*p > ' ') { // skip until white space or CR/NL - ++p; + p++; } } if (*p == NUL) { @@ -2120,10 +2121,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 = enc_canonize(items[1]); + aff->af_enc = (char_u *)enc_canonize((char *)items[1]); if (!spin->si_ascii - && convert_setup(&spin->si_conv, aff->af_enc, - p_enc) == FAIL) { + && convert_setup(&spin->si_conv, (char *)aff->af_enc, p_enc) == FAIL) { smsg(_("Conversion in %s not supported: from %s to %s"), fname, aff->af_enc, p_enc); } @@ -2167,9 +2167,8 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) STRCAT(p, " "); STRCAT(p, items[1]); spin->si_info = p; - } else if (is_aff_rule(items, itemcnt, "MIDWORD", 2) - && midword == NULL) { - midword = getroom_save(spin, items[1]); + } else if (is_aff_rule(items, itemcnt, "MIDWORD", 2) && midword == NULL) { + midword = (char_u *)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 } @@ -2300,22 +2299,19 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) // Only add the couple if it isn't already there. for (i = 0; i < gap->ga_len - 1; i += 2) { - if (STRCMP(((char_u **)(gap->ga_data))[i], items[1]) == 0 - && STRCMP(((char_u **)(gap->ga_data))[i + 1], - items[2]) == 0) { + if (STRCMP(((char **)(gap->ga_data))[i], items[1]) == 0 + && STRCMP(((char **)(gap->ga_data))[i + 1], items[2]) == 0) { break; } } if (i >= gap->ga_len) { ga_grow(gap, 2); - ((char_u **)(gap->ga_data))[gap->ga_len++] - = getroom_save(spin, items[1]); - ((char_u **)(gap->ga_data))[gap->ga_len++] - = getroom_save(spin, 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 = getroom_save(spin, items[1]); + syllable = (char_u *)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)) { @@ -2387,7 +2383,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) // Check for the "S" flag, which apparently means that another // block with the same affix name is following. if (itemcnt > lasti && STRCMP(items[lasti], "S") == 0) { - ++lasti; + lasti++; cur_aff->ah_follows = true; } else { cur_aff->ah_follows = false; @@ -2448,10 +2444,10 @@ 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 = getroom_save(spin, items[2]); + aff_entry->ae_chop = (char_u *)getroom_save(spin, items[2]); } if (STRCMP(items[3], "0") != 0) { - aff_entry->ae_add = getroom_save(spin, items[3]); + aff_entry->ae_add = (char_u *)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, '/'); @@ -2471,7 +2467,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) if (STRCMP(items[4], ".") != 0) { char_u buf[MAXLINELEN]; - aff_entry->ae_cond = getroom_save(spin, items[4]); + aff_entry->ae_cond = (char_u *)getroom_save(spin, items[4]); if (*items[0] == 'P') { sprintf((char *)buf, "^%s", items[4]); } else { @@ -2520,7 +2516,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) if (aff_entry->ae_cond != NULL) { char_u buf[MAXLINELEN]; onecap_copy(items[4], buf, true); - aff_entry->ae_cond = getroom_save(spin, buf); + aff_entry->ae_cond = (char_u *)getroom_save(spin, buf); if (aff_entry->ae_cond != NULL) { sprintf((char *)buf, "^%s", aff_entry->ae_cond); @@ -2550,7 +2546,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) idx = spin->si_prefcond.ga_len; pp = GA_APPEND_VIA_PTR(char_u *, &spin->si_prefcond); *pp = (aff_entry->ae_cond == NULL) ? - NULL : getroom_save(spin, aff_entry->ae_cond); + NULL : (char_u *)getroom_save(spin, aff_entry->ae_cond); } // Add the prefix to the prefix tree. @@ -2582,7 +2578,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) // Didn't actually use ah_newID, backup si_newprefID. if (aff_todo == 0 && !did_postpone_prefix) { - --spin->si_newprefID; + spin->si_newprefID--; cur_aff->ah_newID = 0; } } @@ -2676,10 +2672,10 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) } } else if (is_aff_rule(items, itemcnt, "SOFOFROM", 2) && sofofrom == NULL) { - sofofrom = getroom_save(spin, items[1]); + sofofrom = (char_u *)getroom_save(spin, items[1]); } else if (is_aff_rule(items, itemcnt, "SOFOTO", 2) && sofoto == NULL) { - sofoto = getroom_save(spin, items[1]); + sofoto = (char_u *)getroom_save(spin, items[1]); } else if (STRCMP(items[0], "COMMON") == 0) { int i; @@ -2809,7 +2805,7 @@ static void aff_process_flags(afffile_T *affile, affentry_T *entry) } } if (affile->af_flagtype == AFT_NUM && *p == ',') { - ++p; + p++; } } if (*entry->ae_flags == NUL) { @@ -2945,7 +2941,7 @@ static void process_compflags(spellinfo_T *spin, afffile_T *aff, char_u *compfla *tp++ = (char_u)id; } if (aff->af_flagtype == AFT_NUM && *p == ',') { - ++p; + p++; } } } @@ -2968,7 +2964,7 @@ 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) { - char_u *p; + char *p; unsigned n; switch (flagtype) { @@ -2977,7 +2973,7 @@ static bool flag_in_afflist(int flagtype, char_u *afflist, unsigned flag) case AFT_CAPLONG: case AFT_LONG: - for (p = afflist; *p != NUL;) { + for (p = (char *)afflist; *p != NUL;) { n = (unsigned)mb_ptr2char_adv((const char_u **)&p); if ((flagtype == AFT_LONG || (n >= 'A' && n <= 'Z')) && *p != NUL) { @@ -2990,8 +2986,8 @@ static bool flag_in_afflist(int flagtype, char_u *afflist, unsigned flag) break; case AFT_NUM: - for (p = afflist; *p != NUL;) { - int digits = getdigits_int((char **)&p, true, 0); + for (p = (char *)afflist; *p != NUL;) { + int digits = getdigits_int(&p, true, 0); assert(digits >= 0); n = (unsigned int)digits; if (n == 0) { @@ -3045,9 +3041,9 @@ static void add_fromto(spellinfo_T *spin, garray_T *gap, char_u *from, char_u *t fromto_T *ftp = GA_APPEND_VIA_PTR(fromto_T, gap); (void)spell_casefold(curwin, from, (int)STRLEN(from), word, MAXWLEN); - ftp->ft_from = getroom_save(spin, word); + ftp->ft_from = (char_u *)getroom_save(spin, word); (void)spell_casefold(curwin, to, (int)STRLEN(to), word, MAXWLEN); - ftp->ft_to = getroom_save(spin, word); + ftp->ft_to = (char_u *)getroom_save(spin, word); } // Converts a boolean argument in a SAL line to true or false; @@ -3070,9 +3066,9 @@ static void spell_free_aff(afffile_T *aff) // All this trouble to free the "ae_prog" items... for (ht = &aff->af_pref;; ht = &aff->af_suff) { todo = (int)ht->ht_used; - for (hi = ht->ht_array; todo > 0; ++hi) { + for (hi = ht->ht_array; todo > 0; hi++) { if (!HASHITEM_EMPTY(hi)) { - --todo; + todo--; ah = HI2AH(hi); for (ae = ah->ah_first; ae != NULL; ae = ae->ae_next) { vim_regfree(ae->ae_prog); @@ -3127,7 +3123,7 @@ static int spell_read_dic(spellinfo_T *spin, char_u *fname, afffile_T *affile) vim_snprintf((char *)IObuff, IOSIZE, _("Reading dictionary file %s..."), fname); - spell_message(spin, IObuff); + spell_message(spin, (char *)IObuff); // start with a message for the first line spin->si_msg_count = 999999; @@ -3142,7 +3138,7 @@ static int spell_read_dic(spellinfo_T *spin, char_u *fname, afffile_T *affile) // the hashtable. while (!vim_fgets(line, MAXLINELEN, fd) && !got_int) { line_breakcheck(); - ++lnum; + lnum++; if (line[0] == '#' || line[0] == '/') { continue; // comment line } @@ -3150,7 +3146,7 @@ static int spell_read_dic(spellinfo_T *spin, char_u *fname, afffile_T *affile) // the word is kept to allow multi-word terms like "et al.". l = (int)STRLEN(line); while (l > 0 && line[l - 1] <= ' ') { - --l; + l--; } if (l == 0) { continue; // empty line @@ -3159,7 +3155,7 @@ static int spell_read_dic(spellinfo_T *spin, char_u *fname, afffile_T *affile) // Convert from "SET" to 'encoding' when needed. if (spin->si_conv.vc_type != CONV_NONE) { - pc = string_convert(&spin->si_conv, line, NULL); + pc = (char_u *)string_convert(&spin->si_conv, (char *)line, NULL); if (pc == NULL) { smsg(_("Conversion failure for word in %s line %d: %s"), fname, lnum, line); @@ -3186,7 +3182,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)) { - ++non_ascii; + non_ascii++; xfree(pc); continue; } @@ -3210,7 +3206,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 = getroom_save(spin, w); + dw = (char_u *)getroom_save(spin, w); if (dw == NULL) { retval = FAIL; xfree(pc); @@ -3227,7 +3223,7 @@ static int spell_read_dic(spellinfo_T *spin, char_u *fname, afffile_T *affile) smsg(_("First duplicate word in %s line %d: %s"), fname, lnum, dw); } - ++duplicate; + duplicate++; } else { hash_add_item(&ht, hi, dw, hash); } @@ -3362,7 +3358,7 @@ static int get_pfxlist(afffile_T *affile, char_u *afflist, char_u *store_afflist } } if (affile->af_flagtype == AFT_NUM && *p == ',') { - ++p; + p++; } } @@ -3392,7 +3388,7 @@ static void get_compflags(afffile_T *affile, char_u *afflist, char_u *store_affl } } if (affile->af_flagtype == AFT_NUM && *p == ',') { - ++p; + p++; } } @@ -3436,9 +3432,9 @@ static int store_aff_word(spellinfo_T *spin, char_u *word, char_u *afflist, afff int use_condit; todo = (int)ht->ht_used; - for (hi = ht->ht_array; todo > 0 && retval == OK; ++hi) { + for (hi = ht->ht_array; todo > 0 && retval == OK; hi++) { if (!HASHITEM_EMPTY(hi)) { - --todo; + todo--; ah = HI2AH(hi); // Check that the affix combines, if required, and that the word @@ -3542,8 +3538,8 @@ static int store_aff_word(spellinfo_T *spin, char_u *word, char_u *afflist, afff // Combine the prefix IDs. Avoid adding the // same ID twice. - for (i = 0; i < pfxlen; ++i) { - for (j = 0; j < use_pfxlen; ++j) { + for (i = 0; i < pfxlen; i++) { + for (j = 0; j < use_pfxlen; j++) { if (pfxlist[i] == use_pfxlist[j]) { break; } @@ -3564,9 +3560,8 @@ static int store_aff_word(spellinfo_T *spin, char_u *word, char_u *afflist, afff // Combine the list of compound flags. // Concatenate them to the prefix IDs list. // Avoid adding the same ID twice. - for (i = pfxlen; pfxlist[i] != NUL; ++i) { - for (j = use_pfxlen; - use_pfxlist[j] != NUL; ++j) { + for (i = pfxlen; pfxlist[i] != NUL; i++) { + for (j = use_pfxlen; use_pfxlist[j] != NUL; j++) { if (pfxlist[i] == use_pfxlist[j]) { break; } @@ -3681,12 +3676,12 @@ static int spell_read_wordfile(spellinfo_T *spin, char_u *fname) } vim_snprintf((char *)IObuff, IOSIZE, _("Reading word file %s..."), fname); - spell_message(spin, IObuff); + spell_message(spin, (char *)IObuff); // Read all the lines in the file one by one. while (!vim_fgets(rline, MAXLINELEN, fd) && !got_int) { line_breakcheck(); - ++lnum; + lnum++; // Skip comment lines. if (*rline == '#') { @@ -3696,7 +3691,7 @@ 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; + l--; } if (l == 0) { continue; // empty or blank line @@ -3706,7 +3701,7 @@ 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 = string_convert(&spin->si_conv, rline, NULL); + pc = (char_u *)string_convert(&spin->si_conv, (char *)rline, NULL); if (pc == NULL) { smsg(_("Conversion failure for word in %s line %ld: %s"), fname, lnum, rline); @@ -3719,7 +3714,7 @@ static int spell_read_wordfile(spellinfo_T *spin, char_u *fname) } if (*line == '/') { - ++line; + line++; if (STRNCMP(line, "encoding=", 9) == 0) { if (spin->si_conv.vc_type != CONV_NONE) { smsg(_("Duplicate /encoding= line ignored in %s line %ld: %s"), @@ -3728,14 +3723,13 @@ static int spell_read_wordfile(spellinfo_T *spin, char_u *fname) smsg(_("/encoding= line after word ignored in %s line %ld: %s"), fname, lnum, line - 1); } else { - char_u *enc; + char *enc; // Setup for conversion to 'encoding'. line += 9; - enc = enc_canonize(line); + enc = enc_canonize((char *)line); if (!spin->si_ascii - && convert_setup(&spin->si_conv, enc, - p_enc) == FAIL) { + && convert_setup(&spin->si_conv, enc, p_enc) == FAIL) { smsg(_("Conversion in %s not supported: from %s to %s"), fname, line, p_enc); } @@ -3802,13 +3796,13 @@ static int spell_read_wordfile(spellinfo_T *spin, char_u *fname) fname, lnum, p); break; } - ++p; + p++; } } // Skip non-ASCII words when "spin->si_ascii" is true. if (spin->si_ascii && has_non_ascii(line)) { - ++non_ascii; + non_ascii++; continue; } @@ -3826,7 +3820,7 @@ static int spell_read_wordfile(spellinfo_T *spin, char_u *fname) if (spin->si_ascii && non_ascii > 0) { vim_snprintf((char *)IObuff, IOSIZE, _("Ignored %d words with non-ASCII characters"), non_ascii); - spell_message(spin, IObuff); + spell_message(spin, (char *)IObuff); } return retval; @@ -3860,7 +3854,7 @@ static void *getroom(spellinfo_T *spin, size_t len, bool align) bl->sb_next = spin->si_blocks; spin->si_blocks = bl; bl->sb_used = 0; - ++spin->si_blocks_cnt; + spin->si_blocks_cnt++; } p = bl->sb_data + bl->sb_used; @@ -3869,9 +3863,10 @@ static void *getroom(spellinfo_T *spin, size_t len, bool align) return p; } -// Make a copy of a string into memory allocated with getroom(). -// Returns NULL when out of memory. -static char_u *getroom_save(spellinfo_T *spin, char_u *s) +/// 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) { const size_t s_size = STRLEN(s) + 1; return memcpy(getroom(spin, s_size, false), s, s_size); @@ -3899,13 +3894,13 @@ static wordnode_T *wordtree_alloc(spellinfo_T *spin) /// Return true if "word" contains valid word characters. /// Control characters and trailing '/' are invalid. Space is OK. -static bool valid_spell_word(const char_u *word, const char_u *end) +static bool valid_spell_word(const char *word, const char *end) { - if (!utf_valid_string(word, end)) { + if (!utf_valid_string((char_u *)word, (char_u *)end)) { return false; } - for (const char_u *p = word; *p != NUL && p < end; p += utfc_ptr2len((const char *)p)) { - if (*p < ' ' || (p[0] == '/' && p[1] == NUL)) { + for (const char *p = word; *p != NUL && p < end; p += utfc_ptr2len(p)) { + if ((uint8_t)(*p) < ' ' || (p[0] == '/' && p[1] == NUL)) { return false; } } @@ -3933,7 +3928,7 @@ static int store_word(spellinfo_T *spin, char_u *word, int flags, int region, co int res = OK; // Avoid adding illegal bytes to the word tree. - if (!valid_spell_word(word, word + len)) { + if (!valid_spell_word((char *)word, (char *)word + len)) { return FAIL; } @@ -3947,7 +3942,7 @@ static int store_word(spellinfo_T *spin, char_u *word, int flags, int region, co break; } } - ++spin->si_foldwcount; + spin->si_foldwcount++; if (res == OK && (ct == WF_KEEPCAP || (flags & WF_KEEPCAP))) { for (const char_u *p = pfxlist; res == OK; p++) { @@ -3959,7 +3954,7 @@ static int store_word(spellinfo_T *spin, char_u *word, int flags, int region, co break; } } - ++spin->si_keepwcount; + spin->si_keepwcount++; } return res; } @@ -3978,12 +3973,12 @@ static int tree_add_word(spellinfo_T *spin, char_u *word, wordnode_T *root, int int i; // Add each byte of the word to the tree, including the NUL at the end. - for (i = 0;; ++i) { + for (i = 0;; i++) { // When there is more than one reference to this node we need to make // a copy, so that we can modify it. Copy the whole list of siblings // (we don't optimize for a partly shared list of siblings). if (node != NULL && node->wn_refs > 1) { - --node->wn_refs; + node->wn_refs--; copyprev = prev; for (copyp = node; copyp != NULL; copyp = copyp->wn_sibling) { // Allocate a new node and copy the info. @@ -3993,7 +3988,7 @@ static int tree_add_word(spellinfo_T *spin, char_u *word, wordnode_T *root, int } np->wn_child = copyp->wn_child; if (np->wn_child != NULL) { - ++np->wn_child->wn_refs; // child gets extra ref + np->wn_child->wn_refs++; // child gets extra ref } np->wn_byte = copyp->wn_byte; if (np->wn_byte == NUL) { @@ -4080,7 +4075,7 @@ static int tree_add_word(spellinfo_T *spin, char_u *word, wordnode_T *root, int #endif // count nr of words added since last message - ++spin->si_msg_count; + spin->si_msg_count++; if (spin->si_compress_cnt > 1) { if (--spin->si_compress_cnt == 1) { @@ -4146,8 +4141,8 @@ static wordnode_T *get_wordnode(spellinfo_T *spin) } else { n = spin->si_first_free; spin->si_first_free = n->wn_child; - memset(n, 0, sizeof(wordnode_T)); - --spin->si_free_count; + CLEAR_POINTER(n); + spin->si_free_count--; } #ifdef SPELL_PRINTTREE if (n != NULL) { @@ -4173,9 +4168,9 @@ static int deref_wordnode(spellinfo_T *spin, wordnode_T *node) cnt += deref_wordnode(spin, np->wn_child); } free_wordnode(spin, np); - ++cnt; + cnt++; } - ++cnt; // length field + cnt++; // length field } return cnt; } @@ -4187,7 +4182,7 @@ static void free_wordnode(spellinfo_T *spin, wordnode_T *n) { n->wn_child = spin->si_first_free; spin->si_first_free = n; - ++spin->si_free_count; + spin->si_free_count++; } // Compress a tree: find tails that are identical and can be shared. @@ -4218,7 +4213,7 @@ static void wordtree_compress(spellinfo_T *spin, wordnode_T *root, const char *n vim_snprintf((char *)IObuff, IOSIZE, _("Compressed %s of %ld nodes; %ld (%ld%%) remaining"), name, tot, tot - n, perc); - spell_message(spin, IObuff); + spell_message(spin, (char *)IObuff); } #ifdef SPELL_PRINTTREE spell_print_tree(root->wn_sibling); @@ -4248,7 +4243,7 @@ static long node_compress(spellinfo_T *spin, wordnode_T *node, hashtab_T *ht, lo // Note that with "child" we mean not just the node that is pointed to, // but the whole list of siblings of which the child node is the first. for (np = node; np != NULL && !got_int; np = np->wn_sibling) { - ++len; + len++; if ((child = np->wn_child) != NULL) { // Compress the child first. This fills hashkey. compressed += node_compress(spin, child, ht, tot); @@ -4266,7 +4261,7 @@ static long node_compress(spellinfo_T *spin, wordnode_T *node, hashtab_T *ht, lo // Found one! Now use that child in place of the // current one. This means the current child and all // its siblings is unlinked from the tree. - ++tp->wn_refs; + tp->wn_refs++; compressed += deref_wordnode(spin, child); np->wn_child = tp; break; @@ -4352,14 +4347,15 @@ static int rep_compare(const void *s1, const void *s2) return STRCMP(p1->ft_from, p2->ft_from); } -// Write the Vim .spl file "fname". -// Return OK/FAIL. -static int write_vim_spell(spellinfo_T *spin, char_u *fname) +/// Write the Vim .spl file "fname". +/// +/// @return OK/FAIL. +static int write_vim_spell(spellinfo_T *spin, char *fname) { int retval = OK; int regionmask; - FILE *fd = os_fopen((char *)fname, "w"); + FILE *fd = os_fopen(fname, "w"); if (fd == NULL) { semsg(_(e_notopen), fname); return FAIL; @@ -4424,7 +4420,7 @@ static int write_vim_spell(spellinfo_T *spin, char_u *fname) put_bytes(fd, 1 + 128 + 2 + l, 4); // <sectionlen> fputc(128, fd); // <charflagslen> - for (size_t i = 128; i < 256; ++i) { + for (size_t i = 128; i < 256; i++) { flags = 0; if (spelltab.st_isw[i]) { flags |= CF_WORD; @@ -4468,7 +4464,7 @@ static int write_vim_spell(spellinfo_T *spin, char_u *fname) // round 1: SN_REP section // round 2: SN_SAL section (unless SN_SOFO is used) // round 3: SN_REPSAL section - for (unsigned int round = 1; round <= 3; ++round) { + for (unsigned int round = 1; round <= 3; round++) { garray_T *gap; if (round == 1) { gap = &spin->si_rep; @@ -4502,13 +4498,13 @@ static int write_vim_spell(spellinfo_T *spin, char_u *fname) // Compute the length of what follows. size_t l = 2; // count <repcount> or <salcount> assert(gap->ga_len >= 0); - for (size_t i = 0; i < (size_t)gap->ga_len; ++i) { + 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> } if (round == 2) { - ++l; // count <salflags> + l++; // count <salflags> } put_bytes(fd, l, 4); // <sectionlen> @@ -4527,11 +4523,11 @@ static int write_vim_spell(spellinfo_T *spin, char_u *fname) } put_bytes(fd, (uintmax_t)gap->ga_len, 2); // <repcount> or <salcount> - for (size_t i = 0; i < (size_t)gap->ga_len; ++i) { + for (size_t i = 0; i < (size_t)gap->ga_len; i++) { // <rep> : <repfromlen> <repfrom> <reptolen> <repto> // <sal> : <salfromlen> <salfrom> <saltolen> <salto> fromto_T *ftp = &((fromto_T *)gap->ga_data)[i]; - for (unsigned int rr = 1; rr <= 2; ++rr) { + for (unsigned int rr = 1; rr <= 2; rr++) { char_u *p = rr == 1 ? ftp->ft_from : ftp->ft_to; l = STRLEN(p); assert(l < INT_MAX); @@ -4568,20 +4564,20 @@ static int write_vim_spell(spellinfo_T *spin, char_u *fname) // round 1: count the bytes // round 2: write the bytes - for (unsigned int round = 1; round <= 2; ++round) { + for (unsigned int round = 1; round <= 2; round++) { size_t todo; size_t len = 0; hashitem_T *hi; todo = spin->si_commonwords.ht_used; - for (hi = spin->si_commonwords.ht_array; todo > 0; ++hi) { + for (hi = spin->si_commonwords.ht_array; todo > 0; hi++) { if (!HASHITEM_EMPTY(hi)) { size_t l = STRLEN(hi->hi_key) + 1; len += l; if (round == 2) { // <word> fwv &= fwrite(hi->hi_key, l, 1, fd); } - --todo; + todo--; } } if (round == 1) { @@ -4644,8 +4640,8 @@ static int write_vim_spell(spellinfo_T *spin, char_u *fname) 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_u **)(spin->si_comppat.ga_data))[i]) + 1; + for (size_t i = 0; i < (size_t)spin->si_comppat.ga_len; i++) { + l += STRLEN(((char **)(spin->si_comppat.ga_data))[i]) + 1; } put_bytes(fd, l + 7, 4); // <sectionlen> @@ -4655,8 +4651,8 @@ static int write_vim_spell(spellinfo_T *spin, char_u *fname) putc(0, fd); // for Vim 7.0b compatibility putc(spin->si_compoptions, fd); // <compoptions> put_bytes(fd, (uintmax_t)spin->si_comppat.ga_len, 2); // <comppatcount> - for (size_t i = 0; i < (size_t)spin->si_comppat.ga_len; ++i) { - char_u *p = ((char_u **)(spin->si_comppat.ga_data))[i]; + for (size_t i = 0; i < (size_t)spin->si_comppat.ga_len; i++) { + char *p = ((char **)(spin->si_comppat.ga_data))[i]; assert(STRLEN(p) < INT_MAX); putc((int)STRLEN(p), fd); // <comppatlen> fwv &= fwrite(p, STRLEN(p), 1, fd); // <comppattext> @@ -4691,7 +4687,7 @@ static int write_vim_spell(spellinfo_T *spin, char_u *fname) // <LWORDTREE> <KWORDTREE> <PREFIXTREE> spin->si_memtot = 0; - for (unsigned int round = 1; round <= 3; ++round) { + for (unsigned int round = 1; round <= 3; round++) { wordnode_T *tree; if (round == 1) { tree = spin->si_foldroot->wn_sibling; @@ -4782,7 +4778,7 @@ static int put_node(FILE *fd, wordnode_T *node, int idx, int regionmask, bool pr // Count the number of siblings. int siblingcount = 0; for (wordnode_T *np = node; np != NULL; np = np->wn_sibling) { - ++siblingcount; + siblingcount++; } // Write the sibling count. @@ -4880,12 +4876,12 @@ void ex_mkspell(exarg_T *eap) { int fcount; char **fnames; - char_u *arg = (char_u *)eap->arg; + char *arg = eap->arg; bool ascii = false; if (STRNCMP(arg, "-ascii", 6) == 0) { ascii = true; - arg = (char_u *)skipwhite((char *)arg + 6); + arg = skipwhite(arg + 6); } // Expand all the remaining arguments (e.g., $VIMRUNTIME). @@ -4898,7 +4894,7 @@ void ex_mkspell(exarg_T *eap) // Create the .sug file. // Uses the soundfold info in "spin". // Writes the file with the name "wfname", with ".spl" changed to ".sug". -static void spell_make_sugfile(spellinfo_T *spin, char_u *wfname) +static void spell_make_sugfile(spellinfo_T *spin, char *wfname) { char_u *fname = NULL; int len; @@ -4911,14 +4907,14 @@ static void spell_make_sugfile(spellinfo_T *spin, char_u *wfname) // of the code for the soundfolding stuff. // It might have been done already by spell_reload_one(). for (slang = first_lang; slang != NULL; slang = slang->sl_next) { - if (path_full_compare((char *)wfname, (char *)slang->sl_fname, false, true) + if (path_full_compare(wfname, slang->sl_fname, false, true) == kEqualFiles) { break; } } if (slang == NULL) { - spell_message(spin, (char_u *)_("Reading back spell file...")); - slang = spell_load_file(wfname, NULL, NULL, false); + spell_message(spin, _("Reading back spell file...")); + slang = spell_load_file((char_u *)wfname, NULL, NULL, false); if (slang == NULL) { return; } @@ -4935,7 +4931,7 @@ static void spell_make_sugfile(spellinfo_T *spin, char_u *wfname) // Go through the trie of good words, soundfold each word and add it to // the soundfold trie. - spell_message(spin, (char_u *)_("Performing soundfolding...")); + spell_message(spin, _("Performing soundfolding...")); if (sug_filltree(spin, slang) == FAIL) { goto theend; } @@ -4952,7 +4948,7 @@ static void spell_make_sugfile(spellinfo_T *spin, char_u *wfname) (int64_t)spin->si_spellbuf->b_ml.ml_line_count); // Compress the soundfold trie. - spell_message(spin, (char_u *)_(msg_compressing)); + spell_message(spin, _(msg_compressing)); wordtree_compress(spin, spin->si_foldroot, "case-folded"); // Write the .sug file. @@ -5012,12 +5008,12 @@ static int sug_filltree(spellinfo_T *spin, slang_T *slang) wordcount[depth - 1] += wordcount[depth]; } - --depth; + depth--; line_breakcheck(); } else { // Do one more byte at this node. n = arridx[depth] + curi[depth]; - ++curi[depth]; + curi[depth]++; c = byts[n]; if (c == 0) { @@ -5033,8 +5029,8 @@ static int sug_filltree(spellinfo_T *spin, slang_T *slang) return FAIL; } - ++words_done; - ++wordcount[depth]; + words_done++; + wordcount[depth]++; // Reset the block count each time to avoid compression // kicking in. @@ -5198,7 +5194,7 @@ static void sug_write(spellinfo_T *spin, char_u *fname) vim_snprintf((char *)IObuff, IOSIZE, _("Writing suggestion file %s..."), fname); - spell_message(spin, IObuff); + spell_message(spin, (char *)IObuff); // <SUGHEADER>: <fileID> <versionnr> <timestamp> if (fwrite(VIMSUGMAGIC, VIMSUGMAGICL, (size_t)1, fd) != 1) { // <fileID> @@ -5235,7 +5231,7 @@ static void sug_write(spellinfo_T *spin, char_u *fname) assert(wcount >= 0); put_bytes(fd, (uintmax_t)wcount, 4); // <sugwcount> - for (linenr_T lnum = 1; lnum <= wcount; ++lnum) { + for (linenr_T lnum = 1; lnum <= wcount; lnum++) { // <sugline>: <sugnr> ... NUL char_u *line = ml_get_buf(spin->si_spellbuf, lnum, false); size_t len = STRLEN(line) + 1; @@ -5254,7 +5250,7 @@ static void sug_write(spellinfo_T *spin, char_u *fname) vim_snprintf((char *)IObuff, IOSIZE, _("Estimated runtime memory use: %d bytes"), spin->si_memtot); - spell_message(spin, IObuff); + spell_message(spin, (char *)IObuff); theend: // close the file @@ -5273,7 +5269,6 @@ theend: static void mkspell(int fcount, char **fnames, bool ascii, bool over_write, bool added_word) { char_u *fname = NULL; - char_u *wfname; char **innames; int incount; afffile_T *(afile[MAXREGIONS]); @@ -5282,7 +5277,7 @@ static void mkspell(int fcount, char **fnames, bool ascii, bool over_write, bool bool error = false; spellinfo_T spin; - memset(&spin, 0, sizeof(spin)); + CLEAR_FIELD(spin); spin.si_verbose = !added_word; spin.si_ascii = ascii; spin.si_followup = true; @@ -5301,7 +5296,7 @@ static void mkspell(int fcount, char **fnames, bool ascii, bool over_write, bool innames = &fnames[fcount == 1 ? 0 : 1]; incount = fcount - 1; - wfname = xmalloc(MAXPATHL); + char *wfname = xmalloc(MAXPATHL); if (fcount >= 1) { len = (int)STRLEN(fnames[0]); @@ -5309,42 +5304,42 @@ static void mkspell(int fcount, char **fnames, bool ascii, bool over_write, bool // For ":mkspell path/en.latin1.add" output file is // "path/en.latin1.add.spl". incount = 1; - vim_snprintf((char *)wfname, MAXPATHL, "%s.spl", fnames[0]); + vim_snprintf(wfname, MAXPATHL, "%s.spl", fnames[0]); } else if (fcount == 1) { // For ":mkspell path/vim" output file is "path/vim.latin1.spl". incount = 1; - vim_snprintf((char *)wfname, MAXPATHL, SPL_FNAME_TMPL, + vim_snprintf(wfname, MAXPATHL, SPL_FNAME_TMPL, fnames[0], spin.si_ascii ? (char_u *)"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); } else { // Name should be language, make the file name from it. - vim_snprintf((char *)wfname, MAXPATHL, SPL_FNAME_TMPL, + vim_snprintf(wfname, MAXPATHL, SPL_FNAME_TMPL, fnames[0], spin.si_ascii ? (char_u *)"ascii" : spell_enc()); } // Check for .ascii.spl. - if (strstr(path_tail((char *)wfname), SPL_FNAME_ASCII) != NULL) { + if (strstr(path_tail(wfname), SPL_FNAME_ASCII) != NULL) { spin.si_ascii = true; } // Check for .add.spl. - if (strstr(path_tail((char *)wfname), SPL_FNAME_ADD) != NULL) { + if (strstr(path_tail(wfname), SPL_FNAME_ADD) != NULL) { spin.si_add = true; } } if (incount <= 0) { emsg(_(e_invarg)); // need at least output and input names - } else if (vim_strchr(path_tail((char *)wfname), '_') != NULL) { + } else if (vim_strchr(path_tail(wfname), '_') != NULL) { emsg(_("E751: Output file name must not have region name")); } else if (incount > MAXREGIONS) { semsg(_("E754: Only up to %d regions supported"), MAXREGIONS); } else { // Check for overwriting before doing things that may take a lot of // time. - if (!over_write && os_path_exists(wfname)) { + if (!over_write && os_path_exists((char_u *)wfname)) { emsg(_(e_exists)); goto theend; } @@ -5357,7 +5352,7 @@ static void mkspell(int fcount, char **fnames, bool ascii, bool over_write, bool // Init the aff and dic pointers. // Get the region names if there are more than 2 arguments. - for (i = 0; i < incount; ++i) { + for (i = 0; i < incount; i++) { afile[i] = NULL; if (incount > 1) { @@ -5389,7 +5384,7 @@ static void mkspell(int fcount, char **fnames, bool ascii, bool over_write, bool // Read all the .aff and .dic files. // Text is converted to 'encoding'. // Words are stored in the case-folded and keep-case trees. - for (i = 0; i < incount && !error; ++i) { + for (i = 0; i < incount && !error; i++) { spin.si_conv.vc_type = CONV_NONE; spin.si_region = 1 << i; @@ -5426,7 +5421,7 @@ static void mkspell(int fcount, char **fnames, bool ascii, bool over_write, bool if (!error && !got_int) { // Combine tails in the tree. - spell_message(&spin, (char_u *)_(msg_compressing)); + spell_message(&spin, _(msg_compressing)); wordtree_compress(&spin, spin.si_foldroot, "case-folded"); wordtree_compress(&spin, spin.si_keeproot, "keep-case"); wordtree_compress(&spin, spin.si_prefroot, "prefixes"); @@ -5436,18 +5431,18 @@ static void mkspell(int fcount, char **fnames, bool ascii, bool over_write, bool // Write the info in the spell file. vim_snprintf((char *)IObuff, IOSIZE, _("Writing spell file %s..."), wfname); - spell_message(&spin, IObuff); + spell_message(&spin, (char *)IObuff); error = write_vim_spell(&spin, wfname) == FAIL; - spell_message(&spin, (char_u *)_("Done!")); + spell_message(&spin, _("Done!")); vim_snprintf((char *)IObuff, IOSIZE, _("Estimated runtime memory use: %d bytes"), spin.si_memtot); - spell_message(&spin, IObuff); + spell_message(&spin, (char *)IObuff); // If the file is loaded need to reload it. if (!error) { - spell_reload_one(wfname, added_word); + spell_reload_one((char_u *)wfname, added_word); } } @@ -5461,7 +5456,7 @@ static void mkspell(int fcount, char **fnames, bool ascii, bool over_write, bool hash_clear_all(&spin.si_commonwords, 0); // Free the .aff file structures. - for (i = 0; i < incount; ++i) { + for (i = 0; i < incount; i++) { if (afile[i] != NULL) { spell_free_aff(afile[i]); } @@ -5484,14 +5479,14 @@ theend: // Display a message for spell file processing when 'verbose' is set or using // ":mkspell". "str" can be IObuff. -static void spell_message(const spellinfo_T *spin, char_u *str) +static void spell_message(const spellinfo_T *spin, char *str) FUNC_ATTR_NONNULL_ALL { if (spin->si_verbose || p_verbose > 2) { if (!spin->si_verbose) { verbose_enter(); } - msg((char *)str); + msg(str); ui_flush(); if (!spin->si_verbose) { verbose_leave(); @@ -5529,7 +5524,7 @@ void spell_add_word(char_u *word, int len, SpellAddType what, int idx, bool undo int i; char_u *spf; - if (!valid_spell_word(word, word + len)) { + if (!valid_spell_word((char *)word, (char *)word + len)) { emsg(_(e_illegal_character_in_word)); return; } @@ -5555,7 +5550,7 @@ void spell_add_word(char_u *word, int len, SpellAddType what, int idx, bool undo } fnamebuf = xmalloc(MAXPATHL); - for (spf = curwin->w_s->b_p_spf, i = 1; *spf != NUL; i++) { + for (spf = (char_u *)curwin->w_s->b_p_spf, i = 1; *spf != NUL; i++) { copy_option_part((char **)&spf, (char *)fnamebuf, MAXPATHL, ","); if (i == idx) { break; @@ -5669,7 +5664,7 @@ void spell_add_word(char_u *word, int len, SpellAddType what, int idx, bool undo buf_reload(buf, buf->b_orig_mode, false); } - redraw_all_later(SOME_VALID); + redraw_all_later(UPD_SOME_VALID); } xfree(fnamebuf); } @@ -5683,14 +5678,14 @@ static void init_spellfile(void) char_u *rtp; char_u *lend; bool aspath = false; - char_u *lstart = curbuf->b_s.b_p_spl; + 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 = curwin->w_s->b_p_spl; *lend != NUL + for (lend = (char_u *)curwin->w_s->b_p_spl; *lend != NUL && vim_strchr(",._", *lend) == NULL; lend++) { if (vim_ispathsep(*lend)) { aspath = true; @@ -5700,13 +5695,12 @@ static void init_spellfile(void) // Loop over all entries in 'runtimepath'. Use the first one where we // are allowed to write. - rtp = p_rtp; + rtp = (char_u *)p_rtp; while (*rtp != NUL) { 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 - curbuf->b_s.b_p_spl); + STRLCPY(buf, curbuf->b_s.b_p_spl, lstart - (char_u *)curbuf->b_s.b_p_spl); } else { // Copy the path from 'runtimepath' to buf[]. copy_option_part((char **)&rtp, (char *)buf, MAXPATHL, ","); @@ -5715,8 +5709,7 @@ static void init_spellfile(void) // 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 - curbuf->b_s.b_p_spl + 1); + 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); @@ -5730,14 +5723,14 @@ static void init_spellfile(void) "/%.*s", (int)(lend - lstart), lstart); } l = (int)STRLEN(buf); - fname = LANGP_ENTRY(curwin->w_s->b_langp, 0) + 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("spellfile", 0L, (const char *)buf, OPT_LOCAL); + set_option_value_give_err("spellfile", 0L, (const char *)buf, OPT_LOCAL); break; } aspath = false; @@ -5761,7 +5754,7 @@ static void set_spell_charflags(char_u *flags, int cnt, char_u *fol) clear_spell_chartab(&new_st); - for (i = 0; i < 128; ++i) { + for (i = 0; i < 128; i++) { if (i < cnt) { new_st.st_isw[i + 128] = (flags[i] & CF_WORD) != 0; new_st.st_isu[i + 128] = (flags[i] & CF_UPPER) != 0; @@ -5785,7 +5778,7 @@ static int set_spell_finish(spelltab_T *new_st) if (did_set_spelltab) { // check that it's the same table - for (i = 0; i < 256; ++i) { + for (i = 0; i < 256; i++) { if (spelltab.st_isw[i] != new_st->st_isw[i] || spelltab.st_isu[i] != new_st->st_isu[i] || spelltab.st_fold[i] != new_st->st_fold[i] @@ -5815,7 +5808,7 @@ static int write_spell_prefcond(FILE *fd, garray_T *gap, size_t *fwv) size_t totlen = 2 + (size_t)gap->ga_len; // <prefcondcnt> and <condlen> bytes for (int i = 0; i < gap->ga_len; i++) { // <prefcond> : <condlen> <condstr> - char_u *p = ((char_u **)gap->ga_data)[i]; + char *p = ((char **)gap->ga_data)[i]; if (p != NULL) { size_t len = STRLEN(p); if (fd != NULL) { @@ -5848,7 +5841,7 @@ static void set_map_str(slang_T *lp, char_u *map) lp->sl_has_map = true; // Init the array and hash tables empty. - for (i = 0; i < 256; ++i) { + for (i = 0; i < 256; i++) { lp->sl_map_array[i] = 0; } hash_init(&lp->sl_map_hash); diff --git a/src/nvim/spellsuggest.c b/src/nvim/spellsuggest.c new file mode 100644 index 0000000000..23c8c621b5 --- /dev/null +++ b/src/nvim/spellsuggest.c @@ -0,0 +1,3800 @@ +// 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 + +// spellsuggest.c: functions for spelling suggestions + +#include "nvim/ascii.h" +#include "nvim/change.h" +#include "nvim/charset.h" +#include "nvim/cursor.h" +#include "nvim/eval.h" +#include "nvim/fileio.h" +#include "nvim/garray.h" +#include "nvim/getchar.h" +#include "nvim/hashtab.h" +#include "nvim/input.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/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/ui.h" +#include "nvim/undo.h" +#include "nvim/vim.h" + +// Use this to adjust the score after finding suggestions, based on the +// suggested word sounding like the bad word. This is much faster than doing +// it for every possible suggestion. +// Disadvantage: When "the" is typed as "hte" it sounds quite different ("@" +// vs "ht") and goes down in the list. +// Used when 'spellsuggest' is set to "best". +#define RESCORE(word_score, sound_score) ((3 * (word_score) + (sound_score)) / 4) + +// Do the opposite: based on a maximum end score and a known sound score, +// compute the maximum word score that can be used. +#define MAXSCORE(word_score, sound_score) ((4 * (word_score) - (sound_score)) / 3) + +// only used for su_badflags +#define WF_MIXCAP 0x20 // mix of upper and lower case: macaRONI + +/// Information used when looking for suggestions. +typedef struct suginfo_S { + garray_T su_ga; ///< suggestions, contains "suggest_T" + int su_maxcount; ///< max. number of suggestions displayed + 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 + 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_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 +} suginfo_T; + +/// One word suggestion. Used in "si_ga". +typedef struct { + char_u *st_word; ///< suggested word, allocated string + int st_wordlen; ///< STRLEN(st_word) + int st_orglen; ///< length of replaced text + int st_score; ///< lower is better + int st_altscore; ///< used when st_score compares equal + bool st_salscore; ///< st_score is for soundalike + bool st_had_bonus; ///< bonus already included in score + slang_T *st_slang; ///< language used for sound folding +} suggest_T; + +#define SUG(ga, i) (((suggest_T *)(ga).ga_data)[i]) + +// True if a word appears in the list of banned words. +#define WAS_BANNED(su, word) (!HASHITEM_EMPTY(hash_find(&(su)->su_banned, word))) + +// Number of suggestions kept when cleaning up. We need to keep more than +// what is displayed, because when rescore_suggestions() is called the score +// may change and wrong suggestions may be removed later. +#define SUG_CLEAN_COUNT(su) ((su)->su_maxcount < \ + 130 ? 150 : (su)->su_maxcount + 20) + +// Threshold for sorting and cleaning up suggestions. Don't want to keep lots +// of suggestions that are not going to be displayed. +#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 + +// 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 + +#define SCORE_BIG (SCORE_INS * 3) // big difference +#define SCORE_MAXMAX 999999 // accept any score +#define 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 +#define SCORE_EDIT_MIN SCORE_SIMILAR + +/// For finding suggestions: At each node in the tree these states are tried: +typedef enum { + STATE_START = 0, ///< At start of node check for NUL bytes (goodword + ///< ends); if badword ends there is a match, otherwise + ///< try splitting word. + STATE_NOPREFIX, ///< try without prefix + STATE_SPLITUNDO, ///< Undo splitting. + STATE_ENDNUL, ///< Past NUL bytes at start of the node. + STATE_PLAIN, ///< Use each byte of the node. + STATE_DEL, ///< Delete a byte from the bad word. + STATE_INS_PREP, ///< Prepare for inserting bytes. + STATE_INS, ///< Insert a byte in the bad word. + STATE_SWAP, ///< Swap two bytes. + STATE_UNSWAP, ///< Undo swap two characters. + STATE_SWAP3, ///< Swap two characters over three. + STATE_UNSWAP3, ///< Undo Swap two characters over three. + STATE_UNROT3L, ///< Undo rotate three characters left + STATE_UNROT3R, ///< Undo rotate three characters right + STATE_REP_INI, ///< Prepare for using REP items. + STATE_REP, ///< Use matching REP items from the .aff file. + STATE_REP_UNDO, ///< Undo a REP item replacement. + STATE_FINAL, ///< End of this node. +} state_T; + +/// Struct to keep the state at each level in suggest_try_change(). +typedef struct trystate_S { + state_T ts_state; ///< state at this level, STATE_ + int ts_score; ///< score + idx_T ts_arridx; ///< index in tree array, start of node + int16_t ts_curi; ///< index in list of child nodes + char_u ts_fidx; ///< index in fword[], case-folded bad word + char_u ts_fidxtry; ///< ts_fidx at which bytes may be changed + char_u ts_twordlen; ///< valid length of tword[] + char_u ts_prefixdepth; ///< stack depth for end of prefix or + ///< PFD_PREFIXTREE or PFD_NOPREFIX + char_u ts_flags; ///< TSF_ flags + char_u ts_tcharlen; ///< number of bytes in tword character + char_u ts_tcharidx; ///< current byte index in tword character + char_u ts_isdiff; ///< DIFF_ values + char_u ts_fcharstart; ///< index in fword where badword char started + char_u ts_prewordlen; ///< length of word in "preword[]" + char_u ts_splitoff; ///< index in "tword" after last split + char_u ts_splitfidx; ///< "ts_fidx" at word split + char_u ts_complen; ///< nr of compound words used + char_u ts_compsplit; ///< index for "compflags" where word was spit + char_u ts_save_badflags; ///< su_badflags saved here + char_u ts_delidx; ///< index in fword for char that was deleted, + ///< valid when "ts_flags" has TSF_DIDDEL +} 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 + +// 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 + +// 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 + +static long spell_suggest_timeout = 5000; + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "spellsuggest.c.generated.h" +#endif + +/// Returns true when the sequence of flags in "compflags" plus "flag" can +/// possibly form a valid compounded word. This also checks the COMPOUNDRULE +/// lines if they don't contain wildcards. +static bool can_be_compound(trystate_T *sp, slang_T *slang, char_u *compflags, int flag) +{ + // If the flag doesn't appear in sl_compstartflags or sl_compallflags + // then it can't possibly compound. + if (!byte_in_str(sp->ts_complen == sp->ts_compsplit + ? slang->sl_compstartflags : slang->sl_compallflags, flag)) { + return false; + } + + // If there are no wildcards, we can check if the flags collected so far + // possibly can form a match with COMPOUNDRULE patterns. This only + // makes sense when we have two or more words. + if (slang->sl_comprules != NULL && sp->ts_complen > sp->ts_compsplit) { + compflags[sp->ts_complen] = (char_u)flag; + compflags[sp->ts_complen + 1] = NUL; + bool v = match_compoundrule(slang, compflags + sp->ts_compsplit); + compflags[sp->ts_complen] = NUL; + return v; + } + + return true; +} + +/// Adjust the score of common words. +/// +/// @param split word was split, less bonus +static int score_wordcount_adj(slang_T *slang, int score, char_u *word, bool split) +{ + wordcount_T *wc; + int bonus; + 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; + } + return score; +} + +/// Like captype() but for a KEEPCAP word add ONECAP if the word starts with a +/// capital. So that make_case_word() can turn WOrd into Word. +/// Add ALLCAP for "WOrD". +static int badword_captype(char_u *word, char_u *end) + FUNC_ATTR_NONNULL_ALL +{ + int flags = captype(word, 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 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; + } + } + return flags; +} + +/// Opposite of offset2bytes(). +/// "pp" points to the bytes and is advanced over it. +/// +/// @return the offset. +static int bytes2offset(char_u **pp) +{ + char_u *p = *pp; + int nr; + int c; + + c = *p++; + if ((c & 0x80) == 0x00) { // 1 byte + nr = c - 1; + } else if ((c & 0xc0) == 0x80) { // 2 bytes + nr = (c & 0x3f) - 1; + nr = nr * 255 + (*p++ - 1); + } else if ((c & 0xe0) == 0xc0) { // 3 bytes + nr = (c & 0x1f) - 1; + nr = nr * 255 + (*p++ - 1); + nr = nr * 255 + (*p++ - 1); + } else { // 4 bytes + nr = (c & 0x0f) - 1; + nr = nr * 255 + (*p++ - 1); + nr = nr * 255 + (*p++ - 1); + nr = nr * 255 + (*p++ - 1); + } + + *pp = p; + return nr; +} + +// values for sps_flags +#define SPS_BEST 1 +#define SPS_FAST 2 +#define SPS_DOUBLE 4 + +static int sps_flags = SPS_BEST; ///< flags from 'spellsuggest' +static int sps_limit = 9999; ///< max nr of suggestions given + +/// Check the 'spellsuggest' option. Return FAIL if it's wrong. +/// Sets "sps_flags" and "sps_limit". +int spell_check_sps(void) +{ + char *p; + char *s; + char_u buf[MAXPATHL]; + int f; + + sps_flags = 0; + sps_limit = 9999; + + for (p = p_sps; *p != NUL;) { + copy_option_part(&p, (char *)buf, MAXPATHL, ","); + + f = 0; + if (ascii_isdigit(*buf)) { + s = (char *)buf; + sps_limit = getdigits_int(&s, true, 0); + if (*s != NUL && !ascii_isdigit(*s)) { + f = -1; + } + } else if (STRCMP(buf, "best") == 0) { + f = SPS_BEST; + } else if (STRCMP(buf, "fast") == 0) { + 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 + || (!ascii_isdigit(buf[8]) + && !(buf[8] == '-' && ascii_isdigit(buf[9]))))) { + f = -1; + } + + if (f == -1 || (sps_flags != 0 && f != 0)) { + sps_flags = SPS_BEST; + sps_limit = 9999; + return FAIL; + } + if (f != 0) { + sps_flags = f; + } + } + + if (sps_flags == 0) { + sps_flags = SPS_BEST; + } + + return OK; +} + +/// "z=": Find badly spelled word under or after the cursor. +/// Give suggestions for the properly spelled word. +/// In Visual mode use the highlighted word as the bad word. +/// When "count" is non-zero use that suggestion. +void spell_suggest(int count) +{ + char_u *line; + pos_T prev_cursor = curwin->w_cursor; + char_u wcopy[MAXWLEN + 2]; + char_u *p; + int c; + suginfo_T sug; + suggest_T *stp; + int mouse_used; + int need_cap; + int limit; + int selected = count; + int badlen = 0; + int msg_scroll_save = msg_scroll; + const int wo_spell_save = curwin->w_p_spell; + + if (!curwin->w_p_spell) { + did_set_spelllang(curwin); + curwin->w_p_spell = true; + } + + if (*curwin->w_s->b_p_spl == NUL) { + emsg(_(e_no_spell)); + return; + } + + if (VIsual_active) { + // Use the Visually selected text as the bad word. But reject + // a multi-line selection. + if (curwin->w_cursor.lnum != VIsual.lnum) { + vim_beep(BO_SPELL); + return; + } + badlen = (int)curwin->w_cursor.col - (int)VIsual.col; + if (badlen < 0) { + badlen = -badlen; + } else { + curwin->w_cursor.col = VIsual.col; + } + badlen++; + end_visual_mode(); + // 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) { + // No bad word or it starts after the cursor: use the word under the + // cursor. + curwin->w_cursor = prev_cursor; + line = get_cursor_line_ptr(); + p = line + curwin->w_cursor.col; + // Backup to before start of word. + while (p > line && spell_iswordp_nmw(p, curwin)) { + MB_PTR_BACK(line, p); + } + // Forward to start of word. + while (*p != NUL && !spell_iswordp_nmw(p, curwin)) { + MB_PTR_ADV(p); + } + + if (!spell_iswordp_nmw(p, curwin)) { // No word found. + beep_flush(); + return; + } + curwin->w_cursor.col = (colnr_T)(p - line); + } + + // Get the word and its length. + + // Figure out if the word should be capitalised. + need_cap = check_need_cap(curwin->w_cursor.lnum, curwin->w_cursor.col); + + // Make a copy of current line since autocommands may free the line. + line = vim_strsave(get_cursor_line_ptr()); + spell_suggest_timeout = 5000; + + // Get the list of suggestions. Limit to 'lines' - 2 or the number in + // 'spellsuggest', whatever is smaller. + if (sps_limit > Rows - 2) { + limit = Rows - 2; + } else { + limit = sps_limit; + } + spell_find_suggest(line + curwin->w_cursor.col, badlen, &sug, limit, + true, need_cap, true); + + if (GA_EMPTY(&sug.su_ga)) { + msg(_("Sorry, no suggestions")); + } else if (count > 0) { + if (count > sug.su_ga.ga_len) { + smsg(_("Sorry, only %" PRId64 " suggestions"), + (int64_t)sug.su_ga.ga_len); + } + } else { + // When 'rightleft' is set the list is drawn right-left. + cmdmsg_rl = curwin->w_p_rl; + if (cmdmsg_rl) { + msg_col = Columns - 1; + } + + // List the suggestions. + msg_start(); + msg_row = Rows - 1; // for when 'cmdheight' > 1 + lines_left = Rows; // avoid more prompt + vim_snprintf((char *)IObuff, IOSIZE, _("Change \"%.*s\" to:"), + sug.su_badlen, sug.su_badptr); + 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", + sug.su_badlen, sug.su_badptr); + } + msg_puts((const char *)IObuff); + msg_clr_eos(); + msg_putchar('\n'); + + msg_scroll = true; + for (int i = 0; i < sug.su_ga.ga_len; i++) { + stp = &SUG(sug.su_ga, i); + + // 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); + 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); + } + vim_snprintf((char *)IObuff, IOSIZE, "%2d", i + 1); + if (cmdmsg_rl) { + rl_mirror(IObuff); + } + msg_puts((const char *)IObuff); + + vim_snprintf((char *)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\""), + stp->st_orglen, sug.su_badptr); + msg_puts((const char *)IObuff); + } + + if (p_verbose > 0) { + // Add the score. + if (sps_flags & (SPS_DOUBLE | SPS_BEST)) { + vim_snprintf((char *)IObuff, IOSIZE, " (%s%d - %d)", + stp->st_salscore ? "s " : "", + stp->st_score, stp->st_altscore); + } else { + vim_snprintf((char *)IObuff, IOSIZE, " (%d)", + stp->st_score); + } + if (cmdmsg_rl) { + // Mirror the numbers, but keep the leading space. + rl_mirror(IObuff + 1); + } + msg_advance(30); + msg_puts((const char *)IObuff); + } + msg_putchar('\n'); + } + + cmdmsg_rl = false; + msg_col = 0; + // Ask for choice. + selected = prompt_for_number(&mouse_used); + + if (ui_has(kUIMessages)) { + ui_call_msg_clear(); + } + + if (mouse_used) { + selected -= lines_left; + } + lines_left = Rows; // avoid more prompt + // don't delay for 'smd' in normal_cmd() + msg_scroll = msg_scroll_save; + } + + if (selected > 0 && selected <= sug.su_ga.ga_len && u_save_cursor() == OK) { + // Save the from and to text for :spellrepall. + XFREE_CLEAR(repl_from); + XFREE_CLEAR(repl_to); + + stp = &SUG(sug.su_ga, selected - 1); + if (sug.su_badlen > stp->st_orglen) { + // Replacing less than "su_badlen", append the remainder to + // repl_to. + repl_from = vim_strnsave(sug.su_badptr, (size_t)sug.su_badlen); + vim_snprintf((char *)IObuff, IOSIZE, "%s%.*s", stp->st_word, + sug.su_badlen - stp->st_orglen, + sug.su_badptr + stp->st_orglen); + repl_to = vim_strsave(IObuff); + } else { + // Replacing su_badlen or more, use the whole word. + repl_from = vim_strnsave(sug.su_badptr, (size_t)stp->st_orglen); + repl_to = vim_strsave(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 - line); + memmove(p, line, (size_t)c); + STRCPY(p + c, stp->st_word); + STRCAT(p, sug.su_badptr + stp->st_orglen); + + // For redo we use a change-word command. + ResetRedobuff(); + AppendToRedobuff("ciw"); + AppendToRedobuffLit((char *)p + c, + stp->st_wordlen + sug.su_badlen - stp->st_orglen); + AppendCharToRedobuff(ESC); + + // "p" may be freed here + ml_replace(curwin->w_cursor.lnum, (char *)p, false); + curwin->w_cursor.col = c; + + inserted_bytes(curwin->w_cursor.lnum, c, stp->st_orglen, stp->st_wordlen); + } else { + curwin->w_cursor = prev_cursor; + } + + spell_find_cleanup(&sug); + xfree(line); + curwin->w_p_spell = wo_spell_save; +} + +/// Find spell suggestions for "word". Return them in the growarray "*gap" as +/// a list of allocated strings. +/// +/// @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) +{ + suginfo_T sug; + suggest_T *stp; + char_u *wcopy; + + spell_find_suggest(word, 0, &sug, maxcount, false, need_cap, interactive); + + // Make room in "gap". + ga_init(gap, sizeof(char_u *), sug.su_ga.ga_len + 1); + ga_grow(gap, sug.su_ga.ga_len); + for (int i = 0; i < sug.su_ga.ga_len; i++) { + stp = &SUG(sug.su_ga, i); + + // 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); + 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; + } + + spell_find_cleanup(&sug); +} + +/// Find spell suggestions for the word at the start of "badptr". +/// Return the suggestions in "su->su_ga". +/// The maximum number of suggestions is "maxcount". +/// Note: does use info for the current window. +/// This is based on the mechanisms of Aspell, but completely reimplemented. +/// +/// @param badlen length of bad word or 0 if unknown +/// @param banbadword don't include badword in suggestions +/// @param need_cap word should start with capital +static void spell_find_suggest(char_u *badptr, int badlen, suginfo_T *su, int maxcount, + bool banbadword, bool need_cap, bool interactive) +{ + hlf_T attr = HLF_COUNT; + char_u buf[MAXPATHL]; + char *p; + bool do_combine = false; + char_u *sps_copy; + static bool expr_busy = false; + int c; + langp_T *lp; + bool did_intern = false; + + // Set the info in "*su". + CLEAR_POINTER(su); + ga_init(&su->su_ga, (int)sizeof(suggest_T), 10); + ga_init(&su->su_sga, (int)sizeof(suggest_T), 10); + if (*badptr == NUL) { + return; + } + hash_init(&su->su_banned); + + su->su_badptr = badptr; + if (badlen != 0) { + su->su_badlen = badlen; + } else { + size_t tmplen = spell_check(curwin, su->su_badptr, &attr, NULL, false); + assert(tmplen <= INT_MAX); + su->su_badlen = (int)tmplen; + } + su->su_maxcount = maxcount; + su->su_maxscore = SCORE_MAXINIT; + + if (su->su_badlen >= MAXWLEN) { + su->su_badlen = MAXWLEN - 1; // just in case + } + STRLCPY(su->su_badword, su->su_badptr, su->su_badlen + 1); + (void)spell_casefold(curwin, su->su_badptr, su->su_badlen, su->su_fbadword, + MAXWLEN); + + // TODO(vim): make this work if the case-folded text is longer than the + // original text. Currently an illegal byte causes wrong pointer + // computations. + 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); + if (need_cap) { + su->su_badflags |= WF_ONECAP; + } + + // Find the default language for sound folding. We simply use the first + // one in 'spelllang' that supports sound folding. That's good for when + // using multiple files for one language, it's not that bad when mixing + // languages (e.g., "pl,en"). + for (int i = 0; i < curbuf->b_s.b_langp.ga_len; i++) { + lp = LANGP_ENTRY(curbuf->b_s.b_langp, i); + if (lp->lp_sallang != NULL) { + su->su_sallang = lp->lp_sallang; + break; + } + } + + // 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); + } + + // 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); + if (!SPELL_ISUPPER(c) && attr == HLF_COUNT) { + make_case_word(su->su_badword, buf, WF_ONECAP); + add_suggestion(su, &su->su_ga, 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); + } + + // Make a copy of 'spellsuggest', because the expression may change it. + sps_copy = vim_strsave((char_u *)p_sps); + + // Loop over the items in 'spellsuggest'. + for (p = (char *)sps_copy; *p != NUL;) { + copy_option_part(&p, (char *)buf, MAXPATHL, ","); + + 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); + expr_busy = false; + } + } 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) { + // Limit the time searching for suggestions. + spell_suggest_timeout = atol((char *)buf + 8); + } else if (!did_intern) { + // Use internal method once. + spell_suggest_intern(su, interactive); + if (sps_flags & SPS_DOUBLE) { + do_combine = true; + } + did_intern = true; + } + } + + xfree(sps_copy); + + if (do_combine) { + // Combine the two list of suggestions. This must be done last, + // because sorting changes the order again. + score_combine(su); + } +} + +/// Find suggestions by evaluating expression "expr". +static void spell_suggest_expr(suginfo_T *su, char_u *expr) +{ + int score; + const char *p; + + // The work is split up in a few parts to avoid having to export + // suginfo_T. + // First evaluate the expression and get the resulting list. + list_T *const list = eval_spell_expr((char *)su->su_badword, (char *)expr); + if (list != NULL) { + // Loop over the items in the list. + TV_LIST_ITER(list, li, { + if (TV_LIST_ITEM_TV(li)->v_type == VAR_LIST) { + // Get the word and the score from the items. + score = get_spellword(TV_LIST_ITEM_TV(li)->vval.v_list, &p); + if (score >= 0 && score <= su->su_maxscore) { + add_suggestion(su, &su->su_ga, (const char_u *)p, su->su_badlen, + score, 0, true, su->su_sallang, false); + } + } + }); + tv_list_unref(list); + } + + // Remove bogus suggestions, sort and truncate at "maxcount". + check_suggestions(su, &su->su_ga); + (void)cleanup_suggestions(&su->su_ga, su->su_maxscore, su->su_maxcount); +} + +/// Find suggestions in file "fname". Used for "file:" in 'spellsuggest'. +static void spell_suggest_file(suginfo_T *su, char_u *fname) +{ + FILE *fd; + char_u line[MAXWLEN * 2]; + char_u *p; + int len; + char_u cword[MAXWLEN]; + + // Open the file. + fd = os_fopen((char *)fname, "r"); + if (fd == NULL) { + semsg(_(e_notopen), fname); + return; + } + + // Read it line by line. + while (!vim_fgets(line, MAXWLEN * 2, fd) && !got_int) { + line_breakcheck(); + + p = (char_u *)vim_strchr((char *)line, '/'); + if (p == NULL) { + continue; // No Tab found, just skip the line. + } + *p++ = NUL; + if (STRICMP(su->su_badword, line) == 0) { + // Match! Isolate the good word, until CR or NL. + for (len = 0; p[len] >= ' '; len++) {} + p[len] = NUL; + + // 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); + p = cword; + } + + add_suggestion(su, &su->su_ga, p, su->su_badlen, + SCORE_FILE, 0, true, su->su_sallang, false); + } + } + + fclose(fd); + + // Remove bogus suggestions, sort and truncate at "maxcount". + check_suggestions(su, &su->su_ga); + (void)cleanup_suggestions(&su->su_ga, su->su_maxscore, su->su_maxcount); +} + +/// Find suggestions for the internal method indicated by "sps_flags". +static void spell_suggest_intern(suginfo_T *su, bool interactive) +{ + // Load the .sug file(s) that are available and not done yet. + suggest_load_files(); + + // 1. Try special cases, such as repeating a word: "the the" -> "the". + // + // Set a maximum score to limit the combination of operations that is + // tried. + suggest_try_special(su); + + // 2. Try inserting/deleting/swapping/changing a letter, use REP entries + // from the .aff file and inserting a space (split the word). + suggest_try_change(su); + + // For the resulting top-scorers compute the sound-a-like score. + if (sps_flags & SPS_DOUBLE) { + score_comp_sal(su); + } + + // 3. Try finding sound-a-like words. + if ((sps_flags & SPS_FAST) == 0) { + if (sps_flags & SPS_BEST) { + // Adjust the word score for the suggestions found so far for how + // they sounds like. + rescore_suggestions(su); + } + + // While going through the soundfold tree "su_maxscore" is the score + // for the soundfold word, limits the changes that are being tried, + // and "su_sfmaxscore" the rescored score, which is set by + // cleanup_suggestions(). + // First find words with a small edit distance, because this is much + // faster and often already finds the top-N suggestions. If we didn't + // find many suggestions try again with a higher edit distance. + // "sl_sounddone" is used to avoid doing the same word twice. + suggest_try_soundalike_prep(); + su->su_maxscore = SCORE_SFMAX1; + su->su_sfmaxscore = SCORE_MAXINIT * 3; + suggest_try_soundalike(su); + if (su->su_ga.ga_len < SUG_CLEAN_COUNT(su)) { + // We didn't find enough matches, try again, allowing more + // changes to the soundfold word. + su->su_maxscore = SCORE_SFMAX2; + suggest_try_soundalike(su); + if (su->su_ga.ga_len < SUG_CLEAN_COUNT(su)) { + // Still didn't find enough matches, try again, allowing even + // more changes to the soundfold word. + su->su_maxscore = SCORE_SFMAX3; + suggest_try_soundalike(su); + } + } + su->su_maxscore = su->su_sfmaxscore; + suggest_try_soundalike_finish(); + } + + // When CTRL-C was hit while searching do show the results. Only clear + // got_int when using a command, not for spellsuggest(). + os_breakcheck(); + if (interactive && got_int) { + (void)vgetc(); + got_int = false; + } + + if ((sps_flags & SPS_DOUBLE) == 0 && su->su_ga.ga_len != 0) { + if (sps_flags & SPS_BEST) { + // Adjust the word score for how it sounds like. + rescore_suggestions(su); + } + + // Remove bogus suggestions, sort and truncate at "maxcount". + check_suggestions(su, &su->su_ga); + (void)cleanup_suggestions(&su->su_ga, su->su_maxscore, su->su_maxcount); + } +} + +/// Free the info put in "*su" by spell_find_suggest(). +static void spell_find_cleanup(suginfo_T *su) +{ +#define FREE_SUG_WORD(sug) xfree((sug)->st_word) + // Free the suggestions. + GA_DEEP_CLEAR(&su->su_ga, suggest_T, FREE_SUG_WORD); + GA_DEEP_CLEAR(&su->su_sga, suggest_T, FREE_SUG_WORD); + + // Free the banned words. + hash_clear_all(&su->su_banned, 0); +} + +/// Try finding suggestions by recognizing specific situations. +static void suggest_try_special(suginfo_T *su) +{ + int 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) { + // 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; + + // Give a soundalike score of 0, compute the score as if deleting one + // character. + add_suggestion(su, &su->su_ga, word, su->su_badlen, + RESCORE(SCORE_REP, 0), 0, true, su->su_sallang, false); + } +} + +// Measure how much time is spent in each state. +// Output is dumped in "suggestprof". + +#ifdef SUGGEST_PROFILE +proftime_T current; +proftime_T total; +proftime_T times[STATE_FINAL + 1]; +long counts[STATE_FINAL + 1]; + +static void prof_init(void) +{ + for (int i = 0; i <= STATE_FINAL; i++) { + profile_zero(×[i]); + counts[i] = 0; + } + profile_start(¤t); + profile_start(&total); +} + +/// call before changing state +static void prof_store(state_T state) +{ + profile_end(¤t); + profile_add(×[state], ¤t); + counts[state]++; + profile_start(¤t); +} +# define PROF_STORE(state) prof_store(state); + +static void prof_report(char *name) +{ + FILE *fd = fopen("suggestprof", "a"); + + profile_end(&total); + fprintf(fd, "-----------------------\n"); + fprintf(fd, "%s: %s\n", name, profile_msg(&total)); + for (int i = 0; i <= STATE_FINAL; i++) { + fprintf(fd, "%d: %s ("%" PRId64)\n", i, profile_msg(×[i]), counts[i]); + } + fclose(fd); +} +#else +# define PROF_STORE(state) +#endif + +/// 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 + int n; + char_u *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); + p = su->su_badptr + su->su_badlen; + (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); + if (n < MAXWLEN) { + fword[n] = NUL; + } + + for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len; lpi++) { + lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi); + + // If reloading a spell file fails it's still in the list but + // everything has been cleared. + if (lp->lp_slang->sl_fbyts == NULL) { + continue; + } + + // Try it for this language. Will add possible suggestions. +#ifdef SUGGEST_PROFILE + prof_init(); +#endif + suggest_trie_walk(su, lp, fword, false); +#ifdef SUGGEST_PROFILE + prof_report("try_change"); +#endif + } +} + +// Check the maximum score, if we go over it we won't try this change. +#define TRY_DEEPER(su, stack, depth, add) \ + ((depth) < MAXWLEN - 1 && (stack)[depth].ts_score + (add) < (su)->su_maxscore) + +/// Try finding suggestions by adding/removing/swapping letters. +/// +/// This uses a state machine. At each node in the tree we try various +/// operations. When trying if an operation works "depth" is increased and the +/// stack[] is used to store info. This allows combinations, thus insert one +/// character, replace one and delete another. The number of changes is +/// limited by su->su_maxscore. +/// +/// After implementing this I noticed an article by Kemal Oflazer that +/// describes something similar: "Error-tolerant Finite State Recognition with +/// Applications to Morphological Analysis and Spelling Correction" (1996). +/// The implementation in the article is simplified and requires a stack of +/// unknown depth. The implementation here only needs a stack depth equal to +/// the length of the word. +/// +/// This is also used for the sound-folded word, "soundfold" is true then. +/// The mechanism is the same, but we find a match with a sound-folded word +/// that comes from one or more original words. Each of these words may be +/// added, this is done by add_sound_suggest(). +/// Don't use: +/// the prefix tree or the keep-case tree +/// "su->su_badlen" +/// anything to do with upper and lower case +/// anything to do with word or non-word characters ("spell_iswordp()") +/// banned words +/// word flags (rare, region, compounding) +/// 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) +{ + char_u tword[MAXWLEN]; // good word collected so far + trystate_T stack[MAXWLEN]; + char_u preword[MAXWLEN * 3] = { 0 }; // word found with proper case; + // concatenation of prefix compound + // words and split word. NUL terminated + // when going deeper but not when coming + // back. + char_u compflags[MAXWLEN]; // compound flags, one for each word + trystate_T *sp; + int newscore; + int score; + char_u *byts, *fbyts, *pbyts; + idx_T *idxs, *fidxs, *pidxs; + int depth; + int c, c2, c3; + int n = 0; + int flags; + garray_T *gap; + idx_T arridx; + int len; + char_u *p; + fromto_T *ftp; + int fl = 0, tl; + int repextra = 0; // extra bytes in fword[] from REP item + slang_T *slang = lp->lp_slang; + int fword_ends; + bool goodword_ends; +#ifdef DEBUG_TRIEWALK + // Stores the name of the change made at each level. + char_u changename[MAXWLEN][80]; +#endif + int breakcheckcount = 1000; + bool compound_ok; + + // Go through the whole case-fold tree, try changes at each node. + // "tword[]" contains the word collected from nodes in the tree. + // "fword[]" the word we are trying to match with (initially the bad + // word). + depth = 0; + sp = &stack[0]; + CLEAR_POINTER(sp); // -V1068 + sp->ts_curi = 1; + + if (soundfold) { + // Going through the soundfold tree. + byts = fbyts = slang->sl_sbyts; + idxs = fidxs = slang->sl_sidxs; + pbyts = NULL; + pidxs = NULL; + sp->ts_prefixdepth = PFD_NOPREFIX; + sp->ts_state = STATE_START; + } 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; + fidxs = slang->sl_fidxs; + pbyts = slang->sl_pbyts; + pidxs = slang->sl_pidxs; + if (pbyts != NULL) { + byts = pbyts; + idxs = pidxs; + sp->ts_prefixdepth = PFD_PREFIXTREE; + sp->ts_state = STATE_NOPREFIX; // try without prefix first + } else { + byts = fbyts; + idxs = fidxs; + sp->ts_prefixdepth = PFD_NOPREFIX; + sp->ts_state = STATE_START; + } + } + + // The loop may take an indefinite amount of time. Break out after some + // time. + proftime_T time_limit; + if (spell_suggest_timeout > 0) { + time_limit = profile_setlimit(spell_suggest_timeout); + } + + // Loop to find all suggestions. At each round we either: + // - For the current state try one operation, advance "ts_curi", + // increase "depth". + // - When a state is done go to the next, set "ts_state". + // - When all states are tried decrease "depth". + while (depth >= 0 && !got_int) { + sp = &stack[depth]; + switch (sp->ts_state) { + case STATE_START: + case STATE_NOPREFIX: + // Start of node: Deal with NUL bytes, which means + // tword[] may end here. + arridx = sp->ts_arridx; // current node in the tree + len = byts[arridx]; // bytes in this node + arridx += sp->ts_curi; // index of current byte + + if (sp->ts_prefixdepth == PFD_PREFIXTREE) { + // Skip over the NUL bytes, we use them later. + for (n = 0; n < len && byts[arridx + n] == 0; n++) {} + sp->ts_curi = (int16_t)(sp->ts_curi + n); + + // Always past NUL bytes now. + n = (int)sp->ts_state; + PROF_STORE(sp->ts_state) + sp->ts_state = STATE_ENDNUL; + sp->ts_save_badflags = (char_u)su->su_badflags; + + // At end of a prefix or at start of prefixtree: check for + // following word. + if (depth < MAXWLEN - 1 && (byts[arridx] == 0 || n == STATE_NOPREFIX)) { + // Set su->su_badflags to the caps type at this position. + // Use the caps type until here for the prefix itself. + n = nofold_len(fword, sp->ts_fidx, su->su_badptr); + 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); +#ifdef DEBUG_TRIEWALK + sprintf(changename[depth], "prefix"); // NOLINT(runtime/printf) +#endif + go_deeper(stack, depth, 0); + depth++; + sp = &stack[depth]; + sp->ts_prefixdepth = (char_u)(depth - 1); + byts = fbyts; + idxs = fidxs; + sp->ts_arridx = 0; + + // Move the prefix to preword[] with the right case + // and make find_keepcap_word() works. + tword[sp->ts_twordlen] = NUL; + make_case_word(tword + sp->ts_splitoff, + preword + sp->ts_prewordlen, flags); + sp->ts_prewordlen = (char_u)STRLEN(preword); + sp->ts_splitoff = sp->ts_twordlen; + } + break; + } + + if (sp->ts_curi > len || byts[arridx] != 0) { + // Past bytes in node and/or past NUL bytes. + PROF_STORE(sp->ts_state) + sp->ts_state = STATE_ENDNUL; + sp->ts_save_badflags = (char_u)su->su_badflags; + break; + } + + // End of word in tree. + sp->ts_curi++; // eat one NUL byte + + flags = (int)idxs[arridx]; + + // Skip words with the NOSUGGEST flag. + if (flags & WF_NOSUGGEST) { + break; + } + + fword_ends = (fword[sp->ts_fidx] == NUL + || (soundfold + ? ascii_iswhite(fword[sp->ts_fidx]) + : !spell_iswordp(fword + sp->ts_fidx, curwin))); + tword[sp->ts_twordlen] = NUL; + + if (sp->ts_prefixdepth <= PFD_NOTSPECIAL + && (sp->ts_flags & TSF_PREFIXOK) == 0 + && pbyts != NULL) { + // There was a prefix before the word. Check that the prefix + // can be used with this word. + // Count the length of the NULs in the prefix. If there are + // none this must be the first try without a prefix. + n = stack[sp->ts_prefixdepth].ts_arridx; + len = pbyts[n++]; + for (c = 0; c < len && pbyts[n + c] == 0; c++) {} + if (c > 0) { + c = valid_word_prefix(c, n, flags, + tword + sp->ts_splitoff, slang, false); + if (c == 0) { + break; + } + + // Use the WF_RARE flag for a rare prefix. + if (c & WF_RAREPFX) { + flags |= WF_RARE; + } + + // Tricky: when checking for both prefix and compounding + // we run into the prefix flag first. + // Remember that it's OK, so that we accept the prefix + // when arriving at a compound flag. + sp->ts_flags |= TSF_PREFIXOK; + } + } + + // Check NEEDCOMPOUND: can't use word without compounding. Do try + // appending another compound word below. + if (sp->ts_complen == sp->ts_compsplit && fword_ends + && (flags & WF_NEEDCOMP)) { + goodword_ends = false; + } else { + goodword_ends = true; + } + + p = NULL; + compound_ok = true; + if (sp->ts_complen > sp->ts_compsplit) { + if (slang->sl_nobreak) { + // There was a word before this word. When there was no + // change in this word (it was correct) add the first word + // as a suggestion. If this word was corrected too, we + // 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, + tword + sp->ts_splitoff, + sp->ts_fidx - sp->ts_splitfidx) == 0) { + preword[sp->ts_prewordlen] = NUL; + newscore = score_wordcount_adj(slang, sp->ts_score, + preword + sp->ts_prewordlen, + sp->ts_prewordlen > 0); + // Add the suggestion if the score isn't too bad. + if (newscore <= su->su_maxscore) { + add_suggestion(su, &su->su_ga, preword, + sp->ts_splitfidx - repextra, + newscore, 0, false, + lp->lp_sallang, false); + } + break; + } + } else { + // There was a compound word before this word. If this + // word does not support compounding then give up + // (splitting is tried for the word without compound + // flag). + if (((unsigned)flags >> 24) == 0 + || sp->ts_twordlen - sp->ts_splitoff + < slang->sl_compminlen) { + break; + } + // For multi-byte chars check character length against + // COMPOUNDMIN. + if (slang->sl_compminlen > 0 + && mb_charlen(tword + sp->ts_splitoff) + < slang->sl_compminlen) { + break; + } + + 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); + + // Verify CHECKCOMPOUNDPATTERN rules. + if (match_checkcompoundpattern(preword, sp->ts_prewordlen, + &slang->sl_comppat)) { + compound_ok = false; + } + + if (compound_ok) { + p = preword; + while (*skiptowhite((char *)p) != NUL) { + p = (char_u *)skipwhite(skiptowhite((char *)p)); + } + 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; + } + } + + // Get pointer to last char of previous word. + p = preword + sp->ts_prewordlen; + MB_PTR_BACK(preword, p); + } + } + + // Form the word with proper case in preword. + // If there is a word from a previous split, append. + // For the soundfold tree don't change the case, simply append. + if (soundfold) { + 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, + 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)) { + c = WF_ONECAP; + } + c |= flags; + + // When appending a compound word after a word character don't + // use Onecap. + if (p != NULL && spell_iswordp_nmw(p, curwin)) { + c &= ~WF_ONECAP; + } + make_case_word(tword + sp->ts_splitoff, + 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, preword + sp->ts_prewordlen); + break; + } + if ((sp->ts_complen == sp->ts_compsplit + && WAS_BANNED(su, (char *)preword + sp->ts_prewordlen)) + || WAS_BANNED(su, (char *)preword)) { + if (slang->sl_compprog == NULL) { + break; + } + // the word so far was banned but we may try compounding + goodword_ends = false; + } + } + + newscore = 0; + if (!soundfold) { // soundfold words don't have flags + if ((flags & WF_REGION) + && (((unsigned)flags >> 16) & (unsigned)lp->lp_region) == 0) { + newscore += SCORE_REGION; + } + if (flags & WF_RARE) { + newscore += SCORE_RARE; + } + + if (!spell_valid_case(su->su_badflags, + captype(preword + sp->ts_prewordlen, NULL))) { + newscore += SCORE_ICASE; + } + } + + // TODO(vim): how about splitting in the soundfold tree? + if (fword_ends + && goodword_ends + && sp->ts_fidx >= sp->ts_fidxtry + && compound_ok) { + // The badword also ends: add suggestions. +#ifdef DEBUG_TRIEWALK + if (soundfold && STRCMP(preword, "smwrd") == 0) { + int j; + + // print the stack of changes that brought us here + smsg("------ %s -------", fword); + for (j = 0; j < depth; j++) { + smsg("%s", changename[j]); + } + } +#endif + if (soundfold) { + // For soundfolded words we need to find the original + // words, the edit distance and then add them. + 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 = preword + STRLEN(preword); + MB_PTR_BACK(preword, p); + if (spell_iswordp(p, curwin)) { + newscore += SCORE_NONWORD; + } + } + + // Give a bonus to words seen before. + score = score_wordcount_adj(slang, + sp->ts_score + newscore, + preword + sp->ts_prewordlen, + sp->ts_prewordlen > 0); + + // Add the suggestion if the score isn't too bad. + if (score <= su->su_maxscore) { + add_suggestion(su, &su->su_ga, preword, + sp->ts_fidx - repextra, + score, 0, false, lp->lp_sallang, false); + + if (su->su_badflags & WF_MIXCAP) { + // We really don't know if the word should be + // upper or lower case, add both. + c = captype(preword, NULL); + if (c == 0 || c == WF_ALLCAP) { + make_case_word(tword + sp->ts_splitoff, + preword + sp->ts_prewordlen, + c == 0 ? WF_ALLCAP : 0); + + add_suggestion(su, &su->su_ga, preword, + sp->ts_fidx - repextra, + score + SCORE_ICASE, 0, false, + lp->lp_sallang, false); + } + } + } + } + } + + // Try word split and/or compounding. + if ((sp->ts_fidx >= sp->ts_fidxtry || fword_ends) + // Don't split in the middle of a character + && (sp->ts_tcharlen == 0)) { + bool try_compound; + int try_split; + + // If past the end of the bad word don't try a split. + // Otherwise try changing the next word. E.g., find + // suggestions for "the the" where the second "the" is + // different. It's done like a split. + // TODO(vim): word split for soundfold words + try_split = (sp->ts_fidx - repextra < su->su_badlen) + && !soundfold; + + // Get here in several situations: + // 1. The word in the tree ends: + // If the word allows compounding try that. Otherwise try + // a split by inserting a space. For both check that a + // valid words starts at fword[sp->ts_fidx]. + // For NOBREAK do like compounding to be able to check if + // the next word is valid. + // 2. The badword does end, but it was due to a change (e.g., + // a swap). No need to split, but do check that the + // following word is valid. + // 3. The badword and the word in the tree end. It may still + // be possible to compound another (short) word. + try_compound = false; + if (!soundfold + && !slang->sl_nocompoundsugs + && slang->sl_compprog != NULL + && ((unsigned)flags >> 24) != 0 + && sp->ts_twordlen - sp->ts_splitoff + >= slang->sl_compminlen + && (slang->sl_compminlen == 0 + || mb_charlen(tword + sp->ts_splitoff) + >= slang->sl_compminlen) + && (slang->sl_compsylmax < MAXWLEN + || sp->ts_complen + 1 - sp->ts_compsplit + < slang->sl_compmax) + && (can_be_compound(sp, slang, compflags, (int)((unsigned)flags >> 24)))) { + try_compound = true; + compflags[sp->ts_complen] = (char_u)((unsigned)flags >> 24); + compflags[sp->ts_complen + 1] = NUL; + } + + // For NOBREAK we never try splitting, it won't make any word + // valid. + if (slang->sl_nobreak && !slang->sl_nocompoundsugs) { + try_compound = true; + } else if (!fword_ends + && try_compound + && (sp->ts_flags & TSF_DIDSPLIT) == 0) { + // If we could add a compound word, and it's also possible to + // split at this point, do the split first and set + // TSF_DIDSPLIT to avoid doing it again. + try_compound = false; + sp->ts_flags |= TSF_DIDSPLIT; + sp->ts_curi--; // do the same NUL again + compflags[sp->ts_complen] = NUL; + } else { + sp->ts_flags &= (char_u) ~TSF_DIDSPLIT; + } + + if (try_split || try_compound) { + if (!try_compound && (!fword_ends || !goodword_ends)) { + // If we're going to split need to check that the + // words so far are valid for compounding. If there + // is only one word it must not have the NEEDCOMPOUND + // flag. + if (sp->ts_complen == sp->ts_compsplit + && (flags & WF_NEEDCOMP)) { + break; + } + p = preword; + while (*skiptowhite((char *)p) != NUL) { + p = (char_u *)skipwhite(skiptowhite((char *)p)); + } + if (sp->ts_complen > sp->ts_compsplit + && !can_compound(slang, p, + compflags + sp->ts_compsplit)) { + break; + } + + if (slang->sl_nosplitsugs) { + newscore += SCORE_SPLIT_NO; + } else { + newscore += SCORE_SPLIT; + } + + // Give a bonus to words seen before. + newscore = score_wordcount_adj(slang, newscore, + preword + sp->ts_prewordlen, true); + } + + if (TRY_DEEPER(su, stack, depth, newscore)) { + go_deeper(stack, depth, newscore); +#ifdef DEBUG_TRIEWALK + if (!try_compound && !fword_ends) { + sprintf(changename[depth], "%.*s-%s: split", // NOLINT(runtime/printf) + sp->ts_twordlen, tword, fword + sp->ts_fidx); + } else { + sprintf(changename[depth], "%.*s-%s: compound", // NOLINT(runtime/printf) + sp->ts_twordlen, tword, fword + sp->ts_fidx); + } +#endif + // Save things to be restored at STATE_SPLITUNDO. + sp->ts_save_badflags = (char_u)su->su_badflags; + PROF_STORE(sp->ts_state) + sp->ts_state = STATE_SPLITUNDO; + + depth++; + sp = &stack[depth]; + + // Append a space to preword when splitting. + if (!try_compound && !fword_ends) { + STRCAT(preword, " "); + } + sp->ts_prewordlen = (char_u)STRLEN(preword); + sp->ts_splitoff = sp->ts_twordlen; + sp->ts_splitfidx = sp->ts_fidx; + + // If the badword has a non-word character at this + // position skip it. That means replacing the + // non-word character with a space. Always skip a + // character when the word ends. But only when the + // good word can end. + if (((!try_compound && !spell_iswordp_nmw(fword + + sp->ts_fidx, + curwin)) + || fword_ends) + && fword[sp->ts_fidx] != NUL + && goodword_ends) { + int l; + + l = utfc_ptr2len((char *)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); + sp->ts_prewordlen = (char_u)(sp->ts_prewordlen + l); + preword[sp->ts_prewordlen] = NUL; + } else { + sp->ts_score -= SCORE_SPLIT - SCORE_SUBST; + } + sp->ts_fidx = (char_u)(sp->ts_fidx + l); + } + + // When compounding include compound flag in + // compflags[] (already set above). When splitting we + // may start compounding over again. + if (try_compound) { + sp->ts_complen++; + } else { + sp->ts_compsplit = sp->ts_complen; + } + sp->ts_prefixdepth = PFD_NOPREFIX; + + // 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); + + // Restart at top of the tree. + sp->ts_arridx = 0; + + // If there are postponed prefixes, try these too. + if (pbyts != NULL) { + byts = pbyts; + idxs = pidxs; + sp->ts_prefixdepth = PFD_PREFIXTREE; + PROF_STORE(sp->ts_state) + sp->ts_state = STATE_NOPREFIX; + } + } + } + } + break; + + case STATE_SPLITUNDO: + // Undo the changes done for word split or compound word. + su->su_badflags = sp->ts_save_badflags; + + // Continue looking for NUL bytes. + PROF_STORE(sp->ts_state) + sp->ts_state = STATE_START; + + // In case we went into the prefix tree. + byts = fbyts; + idxs = fidxs; + break; + + case STATE_ENDNUL: + // Past the NUL bytes in the node. + su->su_badflags = sp->ts_save_badflags; + if (fword[sp->ts_fidx] == NUL + && sp->ts_tcharlen == 0) { + // The badword ends, can't use STATE_PLAIN. + PROF_STORE(sp->ts_state) + sp->ts_state = STATE_DEL; + break; + } + PROF_STORE(sp->ts_state) + sp->ts_state = STATE_PLAIN; + FALLTHROUGH; + + case STATE_PLAIN: + // Go over all possible bytes at this node, add each to tword[] + // and use child node. "ts_curi" is the index. + arridx = sp->ts_arridx; + if (sp->ts_curi > byts[arridx]) { + // Done all bytes at this node, do next state. When still at + // already changed bytes skip the other tricks. + PROF_STORE(sp->ts_state) + if (sp->ts_fidx >= sp->ts_fidxtry) { + sp->ts_state = STATE_DEL; + } else { + sp->ts_state = STATE_FINAL; + } + } else { + arridx += sp->ts_curi++; + c = byts[arridx]; + + // Normal byte, go one level deeper. If it's not equal to the + // byte in the bad word adjust the score. But don't even try + // 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] + || (sp->ts_tcharlen > 0 + && sp->ts_isdiff != DIFF_NONE)) { + newscore = 0; + } else { + newscore = SCORE_SUBST; + } + if ((newscore == 0 + || (sp->ts_fidx >= sp->ts_fidxtry + && ((sp->ts_flags & TSF_DIDDEL) == 0 + || c != fword[sp->ts_delidx]))) + && TRY_DEEPER(su, stack, depth, newscore)) { + go_deeper(stack, depth, newscore); +#ifdef DEBUG_TRIEWALK + if (newscore > 0) { + sprintf(changename[depth], "%.*s-%s: subst %c to %c", // NOLINT(runtime/printf) + sp->ts_twordlen, tword, fword + sp->ts_fidx, + fword[sp->ts_fidx], c); + } else { + sprintf(changename[depth], "%.*s-%s: accept %c", // NOLINT(runtime/printf) + sp->ts_twordlen, tword, fword + sp->ts_fidx, + fword[sp->ts_fidx]); + } +#endif + depth++; + sp = &stack[depth]; + if (fword[sp->ts_fidx] != NUL) { + sp->ts_fidx++; + } + tword[sp->ts_twordlen++] = (char_u)c; + sp->ts_arridx = idxs[arridx]; + if (newscore == SCORE_SUBST) { + sp->ts_isdiff = DIFF_YES; + } + // Multi-byte characters are a bit complicated to + // handle: They differ when any of the bytes differ + // and then their length may also differ. + if (sp->ts_tcharlen == 0) { + // First byte. + sp->ts_tcharidx = 0; + sp->ts_tcharlen = MB_BYTE2LEN(c); + sp->ts_fcharstart = (char_u)(sp->ts_fidx - 1); + sp->ts_isdiff = (newscore != 0) + ? DIFF_YES : DIFF_NONE; + } else if (sp->ts_isdiff == DIFF_INSERT && sp->ts_fidx > 0) { + // When inserting trail bytes don't advance in the + // bad word. + sp->ts_fidx--; + } + if (++sp->ts_tcharidx == sp->ts_tcharlen) { + // Last byte of character. + if (sp->ts_isdiff == DIFF_YES) { + // 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)); + + // 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 + + sp->ts_fcharstart))) { + sp->ts_score -= SCORE_SUBST - SCORE_SUBCOMP; + } else if (!soundfold + && slang->sl_has_map + && similar_chars(slang, + utf_ptr2char((char *)tword + sp->ts_twordlen - + sp->ts_tcharlen), + utf_ptr2char((char *)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); + if (utf_iscomposing(c)) { + // Inserting a composing char doesn't + // count that much. + sp->ts_score -= SCORE_INS - SCORE_INSCOMP; + } else { + // If the previous character was the same, + // thus doubling a character, give a bonus + // to the score. Also for the soundfold + // tree (might seem illogical but does + // give better scores). + MB_PTR_BACK(tword, p); + if (c == utf_ptr2char((char *)p)) { + sp->ts_score -= SCORE_INS - SCORE_INSDUP; + } + } + } + + // Starting a new char, reset the length. + sp->ts_tcharlen = 0; + } + } + } + break; + + case STATE_DEL: + // When past the first byte of a multi-byte char don't try + // delete/insert/swap a character. + if (sp->ts_tcharlen > 0) { + PROF_STORE(sp->ts_state) + sp->ts_state = STATE_FINAL; + break; + } + // Try skipping one character in the bad word (delete it). + PROF_STORE(sp->ts_state) + sp->ts_state = STATE_INS_PREP; + sp->ts_curi = 1; + if (soundfold && sp->ts_fidx == 0 && fword[sp->ts_fidx] == '*') { + // Deleting a vowel at the start of a word counts less, see + // soundalike_score(). + newscore = 2 * SCORE_DEL / 3; + } else { + newscore = SCORE_DEL; + } + if (fword[sp->ts_fidx] != NUL + && TRY_DEEPER(su, stack, depth, newscore)) { + go_deeper(stack, depth, newscore); +#ifdef DEBUG_TRIEWALK + sprintf(changename[depth], "%.*s-%s: delete %c", // NOLINT(runtime/printf) + sp->ts_twordlen, tword, fword + sp->ts_fidx, + fword[sp->ts_fidx]); +#endif + depth++; + + // Remember what character we deleted, so that we can avoid + // inserting it again. + stack[depth].ts_flags |= TSF_DIDDEL; + stack[depth].ts_delidx = sp->ts_fidx; + + // Advance over the character in fword[]. Give a bonus to the + // 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); + stack[depth].ts_fidx = + (char_u)(stack[depth].ts_fidx + utfc_ptr2len((char *)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)) { + stack[depth].ts_score -= SCORE_DEL - SCORE_DELDUP; + } + + break; + } + FALLTHROUGH; + + case STATE_INS_PREP: + if (sp->ts_flags & TSF_DIDDEL) { + // If we just deleted a byte then inserting won't make sense, + // a substitute is always cheaper. + PROF_STORE(sp->ts_state) + sp->ts_state = STATE_SWAP; + break; + } + + // skip over NUL bytes + n = sp->ts_arridx; + for (;;) { + if (sp->ts_curi > byts[n]) { + // Only NUL bytes at this node, go to next state. + PROF_STORE(sp->ts_state) + sp->ts_state = STATE_SWAP; + break; + } + if (byts[n + sp->ts_curi] != NUL) { + // Found a byte to insert. + PROF_STORE(sp->ts_state) + sp->ts_state = STATE_INS; + break; + } + sp->ts_curi++; + } + break; + + case STATE_INS: + // Insert one byte. Repeat this for each possible byte at this + // node. + n = sp->ts_arridx; + if (sp->ts_curi > byts[n]) { + // Done all bytes at this node, go to next state. + PROF_STORE(sp->ts_state) + sp->ts_state = STATE_SWAP; + break; + } + + // Do one more byte at this node, but: + // - Skip NUL bytes. + // - Skip the byte if it's equal to the byte in the word, + // accepting that byte is always better. + n += sp->ts_curi++; + c = byts[n]; + if (soundfold && sp->ts_twordlen == 0 && c == '*') { + // Inserting a vowel at the start of a word counts less, + // see soundalike_score(). + newscore = 2 * SCORE_INS / 3; + } else { + newscore = SCORE_INS; + } + if (c != fword[sp->ts_fidx] + && TRY_DEEPER(su, stack, depth, newscore)) { + go_deeper(stack, depth, newscore); +#ifdef DEBUG_TRIEWALK + sprintf(changename[depth], "%.*s-%s: insert %c", // NOLINT(runtime/printf) + sp->ts_twordlen, tword, fword + sp->ts_fidx, + c); +#endif + depth++; + sp = &stack[depth]; + tword[sp->ts_twordlen++] = (char_u)c; + sp->ts_arridx = idxs[n]; + fl = MB_BYTE2LEN(c); + if (fl > 1) { + // There are following bytes for the same character. + // We must find all bytes before trying + // delete/insert/swap/etc. + sp->ts_tcharlen = (char_u)fl; + sp->ts_tcharidx = 1; + sp->ts_isdiff = DIFF_INSERT; + } + if (fl == 1) { + // If the previous character was the same, thus doubling a + // character, give a bonus to the score. Also for + // soundfold words (illogical but does give a better + // score). + if (sp->ts_twordlen >= 2 + && tword[sp->ts_twordlen - 2] == c) { + sp->ts_score -= SCORE_INS - SCORE_INSDUP; + } + } + } + break; + + case STATE_SWAP: + // Swap two bytes in the bad word: "12" -> "21". + // We change "fword" here, it's changed back afterwards at + // STATE_UNSWAP. + p = fword + sp->ts_fidx; + c = *p; + if (c == NUL) { + // End of word, can't swap or replace. + PROF_STORE(sp->ts_state) + sp->ts_state = STATE_FINAL; + break; + } + + // Don't swap if the first character is not a word character. + // SWAP3 etc. also don't make sense then. + if (!soundfold && !spell_iswordp(p, curwin)) { + PROF_STORE(sp->ts_state) + sp->ts_state = STATE_REP_INI; + break; + } + + n = utf_ptr2len((char *)p); + c = utf_ptr2char((char *)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); + } + + // When the second character is NUL we can't swap. + if (c2 == NUL) { + PROF_STORE(sp->ts_state) + sp->ts_state = STATE_REP_INI; + break; + } + + // When characters are identical, swap won't do anything. + // Also get here if the second char is not a word character. + if (c == c2) { + PROF_STORE(sp->ts_state) + sp->ts_state = STATE_SWAP3; + break; + } + if (TRY_DEEPER(su, stack, depth, SCORE_SWAP)) { + go_deeper(stack, depth, SCORE_SWAP); +#ifdef DEBUG_TRIEWALK + snprintf(changename[depth], sizeof(changename[0]), + "%.*s-%s: swap %c and %c", + sp->ts_twordlen, tword, fword + sp->ts_fidx, + c, c2); +#endif + PROF_STORE(sp->ts_state) + sp->ts_state = STATE_UNSWAP; + depth++; + fl = utf_char2len(c2); + memmove(p, p + n, (size_t)fl); + utf_char2bytes(c, (char *)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. + PROF_STORE(sp->ts_state) + sp->ts_state = STATE_REP_INI; + } + break; + + 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); + + FALLTHROUGH; + + case STATE_SWAP3: + // 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); + if (!soundfold && !spell_iswordp(p + n + fl, curwin)) { + c3 = c; // don't swap non-word char + } else { + c3 = utf_ptr2char((char *)p + n + fl); + } + + // When characters are identical: "121" then SWAP3 result is + // identical, ROT3L result is same as SWAP: "211", ROT3L result is + // same as SWAP on next char: "112". Thus skip all swapping. + // Also skip when c3 is NUL. + // Also get here when the third character is not a word character. + // Second character may any char: "a.b" -> "b.a" + if (c == c3 || c3 == NUL) { + PROF_STORE(sp->ts_state) + sp->ts_state = STATE_REP_INI; + break; + } + if (TRY_DEEPER(su, stack, depth, SCORE_SWAP3)) { + go_deeper(stack, depth, SCORE_SWAP3); +#ifdef DEBUG_TRIEWALK + sprintf(changename[depth], "%.*s-%s: swap3 %c and %c", // NOLINT(runtime/printf) + sp->ts_twordlen, tword, fword + sp->ts_fidx, + c, c3); +#endif + PROF_STORE(sp->ts_state) + sp->ts_state = STATE_UNSWAP3; + 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); + stack[depth].ts_fidxtry = (char_u)(sp->ts_fidx + n + fl + tl); + } else { + PROF_STORE(sp->ts_state) + sp->ts_state = STATE_REP_INI; + } + break; + + 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); + memmove(p + fl + tl, p, (size_t)n); + utf_char2bytes(c, (char *)p); + utf_char2bytes(c2, (char *)p + tl); + p = p + tl; + + if (!soundfold && !spell_iswordp(p, curwin)) { + // Middle char is not a word char, skip the rotate. First and + // third char were already checked at swap and swap3. + PROF_STORE(sp->ts_state) + sp->ts_state = STATE_REP_INI; + break; + } + + // Rotate three characters left: "123" -> "231". We change + // "fword" here, it's changed back afterwards at STATE_UNROT3L. + if (TRY_DEEPER(su, stack, depth, SCORE_SWAP3)) { + go_deeper(stack, depth, SCORE_SWAP3); +#ifdef DEBUG_TRIEWALK + p = fword + sp->ts_fidx; + sprintf(changename[depth], "%.*s-%s: rotate left %c%c%c", // NOLINT(runtime/printf) + sp->ts_twordlen, tword, fword + sp->ts_fidx, + p[0], p[1], p[2]); +#endif + PROF_STORE(sp->ts_state) + 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); + memmove(p, p + n, (size_t)fl); + utf_char2bytes(c, (char *)p + fl); + stack[depth].ts_fidxtry = (char_u)(sp->ts_fidx + n + fl); + } else { + PROF_STORE(sp->ts_state) + sp->ts_state = STATE_REP_INI; + } + break; + + 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); + memmove(p + tl, p, (size_t)n); + utf_char2bytes(c, (char *)p); + + // Rotate three bytes right: "123" -> "312". We change "fword" + // here, it's changed back afterwards at STATE_UNROT3R. + if (TRY_DEEPER(su, stack, depth, SCORE_SWAP3)) { + go_deeper(stack, depth, SCORE_SWAP3); +#ifdef DEBUG_TRIEWALK + p = fword + sp->ts_fidx; + sprintf(changename[depth], "%.*s-%s: rotate right %c%c%c", // NOLINT(runtime/printf) + sp->ts_twordlen, tword, fword + sp->ts_fidx, + p[0], p[1], p[2]); +#endif + PROF_STORE(sp->ts_state) + 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); + memmove(p + tl, p, (size_t)n); + utf_char2bytes(c, (char *)p); + stack[depth].ts_fidxtry = (char_u)(sp->ts_fidx + n + tl); + } else { + PROF_STORE(sp->ts_state) + sp->ts_state = STATE_REP_INI; + } + break; + + 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); + memmove(p, p + tl, (size_t)n); + utf_char2bytes(c, (char *)p + n); + + FALLTHROUGH; + + case STATE_REP_INI: + // Check if matching with REP items from the .aff file would work. + // Quickly skip if: + // - there are no REP items and we are not in the soundfold trie + // - the score is going to be too high anyway + // - already applied a REP item or swapped here + if ((lp->lp_replang == NULL && !soundfold) + || sp->ts_score + SCORE_REP >= su->su_maxscore + || sp->ts_fidx < sp->ts_fidxtry) { + PROF_STORE(sp->ts_state) + sp->ts_state = STATE_FINAL; + break; + } + + // 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]]; + } else { + sp->ts_curi = lp->lp_replang->sl_rep_first[fword[sp->ts_fidx]]; + } + + if (sp->ts_curi < 0) { + PROF_STORE(sp->ts_state) + sp->ts_state = STATE_FINAL; + break; + } + + PROF_STORE(sp->ts_state) + sp->ts_state = STATE_REP; + FALLTHROUGH; + + case STATE_REP: + // Try matching with REP items from the .aff file. For each match + // replace the characters and check if the resulting word is + // valid. + p = fword + sp->ts_fidx; + + if (soundfold) { + gap = &slang->sl_repsal; + } else { + gap = &lp->lp_replang->sl_rep; + } + while (sp->ts_curi < gap->ga_len) { + 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; + break; + } + 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 + sprintf(changename[depth], "%.*s-%s: replace %s with %s", // NOLINT(runtime/printf) + sp->ts_twordlen, tword, fword + sp->ts_fidx, + ftp->ft_from, ftp->ft_to); +#endif + // Need to undo this afterwards. + PROF_STORE(sp->ts_state) + sp->ts_state = STATE_REP_UNDO; + + // Change the "from" to the "to" string. + depth++; + fl = (int)STRLEN(ftp->ft_from); + tl = (int)STRLEN(ftp->ft_to); + if (fl != tl) { + STRMOVE(p + tl, p + fl); + repextra += tl - fl; + } + memmove(p, ftp->ft_to, (size_t)tl); + stack[depth].ts_fidxtry = (char_u)(sp->ts_fidx + tl); + stack[depth].ts_tcharlen = 0; + break; + } + } + + if (sp->ts_curi >= gap->ga_len && sp->ts_state == STATE_REP) { + // No (more) matches. + PROF_STORE(sp->ts_state) + sp->ts_state = STATE_FINAL; + } + + break; + + case STATE_REP_UNDO: + // Undo a REP replacement and continue with the next one. + if (soundfold) { + gap = &slang->sl_repsal; + } else { + 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); + p = fword + sp->ts_fidx; + if (fl != tl) { + STRMOVE(p + fl, p + tl); + repextra -= tl - fl; + } + memmove(p, ftp->ft_from, (size_t)fl); + PROF_STORE(sp->ts_state) + sp->ts_state = STATE_REP; + break; + + default: + // Did all possible states at this level, go up one level. + depth--; + + if (depth >= 0 && stack[depth].ts_prefixdepth == PFD_PREFIXTREE) { + // Continue in or go back to the prefix tree. + byts = pbyts; + idxs = pidxs; + } + + // Don't check for CTRL-C too often, it takes time. + if (--breakcheckcount == 0) { + os_breakcheck(); + breakcheckcount = 1000; + if (spell_suggest_timeout > 0 && profile_passed_limit(time_limit)) { + got_int = true; + } + } + } + } +} + +/// Go one level deeper in the tree. +static void go_deeper(trystate_T *stack, int depth, int score_add) +{ + stack[depth + 1] = stack[depth]; + stack[depth + 1].ts_state = STATE_START; + stack[depth + 1].ts_score = stack[depth].ts_score + score_add; + stack[depth + 1].ts_curi = 1; // start just after length byte + stack[depth + 1].ts_flags = 0; +} + +/// "fword" is a good word with case folded. Find the matching keep-case +/// 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) +{ + char_u uword[MAXWLEN]; // "fword" in upper-case + int depth; + idx_T tryidx; + + // The following arrays are used at each depth in the tree. + idx_T arridx[MAXWLEN]; + int round[MAXWLEN]; + int fwordidx[MAXWLEN]; + int uwordidx[MAXWLEN]; + int kwordlen[MAXWLEN]; + + int flen, ulen; + int l; + int len; + int c; + idx_T lo, hi, m; + char_u *p; + char_u *byts = slang->sl_kbyts; // array with bytes of the words + idx_T *idxs = slang->sl_kidxs; // array with indexes + + if (byts == NULL) { + // array is empty: "cannot happen" + *kword = NUL; + return; + } + + // Make an all-cap version of "fword". + allcap_copy(fword, uword); + + // Each character needs to be tried both case-folded and upper-case. + // All this gets very complicated if we keep in mind that changing case + // may change the byte length of a multi-byte character... + depth = 0; + arridx[0] = 0; + round[0] = 0; + fwordidx[0] = 0; + uwordidx[0] = 0; + kwordlen[0] = 0; + while (depth >= 0) { + if (fword[fwordidx[depth]] == NUL) { + // We are at the end of "fword". If the tree allows a word to end + // here we have found a match. + if (byts[arridx[depth] + 1] == 0) { + kword[kwordlen[depth]] = NUL; + return; + } + + // kword is getting too long, continue one level up + depth--; + } else if (++round[depth] > 2) { + // tried both fold-case and upper-case character, continue one + // level up + depth--; + } 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]); + ulen = utf_ptr2len((char *)uword + uwordidx[depth]); + if (round[depth] == 1) { + p = fword + fwordidx[depth]; + l = flen; + } else { + p = uword + uwordidx[depth]; + l = ulen; + } + + for (tryidx = arridx[depth]; l > 0; l--) { + // Perform a binary search in the list of accepted bytes. + len = byts[tryidx++]; + c = *p++; + lo = tryidx; + hi = tryidx + len - 1; + while (lo < hi) { + m = (lo + hi) / 2; + if (byts[m] > c) { + hi = m - 1; + } else if (byts[m] < c) { + lo = m + 1; + } else { + lo = hi = m; + break; + } + } + + // Stop if there is no matching byte. + if (hi < lo || byts[lo] != c) { + break; + } + + // Continue at the child (if there is one). + tryidx = idxs[lo]; + } + + if (l == 0) { + // 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); + kwordlen[depth + 1] = kwordlen[depth] + flen; + } else { + STRNCPY(kword + kwordlen[depth], uword + uwordidx[depth], // NOLINT(runtime/printf) + ulen); + kwordlen[depth + 1] = kwordlen[depth] + ulen; + } + fwordidx[depth + 1] = fwordidx[depth] + flen; + uwordidx[depth + 1] = uwordidx[depth] + ulen; + + depth++; + arridx[depth] = tryidx; + round[depth] = 0; + } + } + } + + // Didn't find it: "cannot happen". + *kword = NUL; +} + +/// Compute the sound-a-like score for suggestions in su->su_ga and add them to +/// su->su_sga. +static void score_comp_sal(suginfo_T *su) +{ + langp_T *lp; + char_u badsound[MAXWLEN]; + int i; + suggest_T *stp; + suggest_T *sstp; + int score; + + ga_grow(&su->su_sga, su->su_ga.ga_len); + + // Use the sound-folding of the first language that supports it. + for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len; lpi++) { + 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); + + for (i = 0; i < su->su_ga.ga_len; i++) { + stp = &SUG(su->su_ga, i); + + // Case-fold the suggested word, sound-fold it and compute the + // sound-a-like score. + score = stp_sal_score(stp, su, lp->lp_slang, badsound); + if (score < SCORE_MAXMAX) { + // Add the suggestion. + sstp = &SUG(su->su_sga, su->su_sga.ga_len); + sstp->st_word = vim_strsave(stp->st_word); + sstp->st_wordlen = stp->st_wordlen; + sstp->st_score = score; + sstp->st_altscore = 0; + sstp->st_orglen = stp->st_orglen; + su->su_sga.ga_len++; + } + } + break; + } + } +} + +/// Combine the list of suggestions in su->su_ga and su->su_sga. +/// They are entwined. +static void score_combine(suginfo_T *su) +{ + garray_T ga; + garray_T *gap; + langp_T *lp; + suggest_T *stp; + char_u *p; + char_u badsound[MAXWLEN]; + int round; + slang_T *slang = NULL; + + // Add the alternate score to su_ga. + for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len; lpi++) { + lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi); + if (!GA_EMPTY(&lp->lp_slang->sl_sal)) { + // soundfold the bad word + slang = lp->lp_slang; + spell_soundfold(slang, su->su_fbadword, true, badsound); + + for (int i = 0; i < su->su_ga.ga_len; i++) { + stp = &SUG(su->su_ga, i); + stp->st_altscore = stp_sal_score(stp, su, slang, badsound); + if (stp->st_altscore == SCORE_MAXMAX) { + stp->st_score = (stp->st_score * 3 + SCORE_BIG) / 4; + } else { + stp->st_score = (stp->st_score * 3 + stp->st_altscore) / 4; + } + stp->st_salscore = false; + } + break; + } + } + + if (slang == NULL) { // Using "double" without sound folding. + (void)cleanup_suggestions(&su->su_ga, su->su_maxscore, + su->su_maxcount); + return; + } + + // 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, stp->st_word); + if (stp->st_score == SCORE_MAXMAX) { + stp->st_score = (SCORE_BIG * 7 + stp->st_altscore) / 8; + } else { + stp->st_score = (stp->st_score * 7 + stp->st_altscore) / 8; + } + stp->st_salscore = true; + } + + // Remove bad suggestions, sort the suggestions and truncate at "maxcount" + // for both lists. + check_suggestions(su, &su->su_ga); + (void)cleanup_suggestions(&su->su_ga, su->su_maxscore, su->su_maxcount); + check_suggestions(su, &su->su_sga); + (void)cleanup_suggestions(&su->su_sga, su->su_maxscore, su->su_maxcount); + + ga_init(&ga, (int)sizeof(suginfo_T), 1); + ga_grow(&ga, su->su_ga.ga_len + su->su_sga.ga_len); + + stp = &SUG(ga, 0); + for (int i = 0; i < su->su_ga.ga_len || i < su->su_sga.ga_len; i++) { + // round 1: get a suggestion from su_ga + // round 2: get a suggestion from su_sga + for (round = 1; round <= 2; round++) { + gap = round == 1 ? &su->su_ga : &su->su_sga; + if (i < gap->ga_len) { + // Don't add a word if it's already there. + p = SUG(*gap, i).st_word; + int j; + for (j = 0; j < ga.ga_len; j++) { + if (STRCMP(stp[j].st_word, p) == 0) { + break; + } + } + if (j == ga.ga_len) { + stp[ga.ga_len++] = SUG(*gap, i); + } else { + xfree(p); + } + } + } + } + + ga_clear(&su->su_ga); + ga_clear(&su->su_sga); + + // Truncate the list to the number of suggestions that will be displayed. + if (ga.ga_len > su->su_maxcount) { + for (int i = su->su_maxcount; i < ga.ga_len; i++) { + xfree(stp[i].st_word); + } + ga.ga_len = su->su_maxcount; + } + + su->su_ga = ga; +} + +/// For the goodword in "stp" compute the soundalike score compared to the +/// badword. +/// +/// @param badsound sound-folded badword +static int stp_sal_score(suggest_T *stp, suginfo_T *su, slang_T *slang, char_u *badsound) +{ + char_u *p; + char_u *pbad; + char_u *pgood; + char_u badsound2[MAXWLEN]; + char_u fword[MAXWLEN]; + char_u goodsound[MAXWLEN]; + char_u goodword[MAXWLEN]; + int lendiff; + + lendiff = su->su_badlen - stp->st_orglen; + if (lendiff >= 0) { + pbad = badsound; + } else { + // soundfold the bad word with more characters following + (void)spell_casefold(curwin, su->su_badptr, stp->st_orglen, 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 + // removing the space. Don't do it when the good word also contains a + // space. + if (ascii_iswhite(su->su_badptr[su->su_badlen]) + && *skiptowhite((char *)stp->st_word) == NUL) { + for (p = fword; *(p = (char_u *)skiptowhite((char *)p)) != NUL;) { + STRMOVE(p, p + 1); + } + } + + spell_soundfold(slang, fword, true, badsound2); + pbad = badsound2; + } + + if (lendiff > 0 && stp->st_wordlen + lendiff < MAXWLEN) { + // 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; + } else { + pgood = stp->st_word; + } + + // Sound-fold the word and compute the score for the difference. + spell_soundfold(slang, pgood, false, goodsound); + + return soundalike_score(goodsound, pbad); +} + +/// structure used to store soundfolded words that add_sound_suggest() has +/// handled already. +typedef struct { + int16_t sft_score; ///< lowest score used + char_u sft_word[1]; ///< soundfolded word, actually longer +} sftword_T; + +static sftword_T dumsft; +#define HIKEY2SFT(p) ((sftword_T *)((p) - (dumsft.sft_word - (char_u *)&dumsft))) +#define HI2SFT(hi) HIKEY2SFT((hi)->hi_key) + +/// Prepare for calling suggest_try_soundalike(). +static void suggest_try_soundalike_prep(void) +{ + langp_T *lp; + slang_T *slang; + + // Do this for all languages that support sound folding and for which a + // .sug file has been loaded. + for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len; lpi++) { + lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi); + slang = lp->lp_slang; + if (!GA_EMPTY(&slang->sl_sal) && slang->sl_sbyts != NULL) { + // prepare the hashtable used by add_sound_suggest() + hash_init(&slang->sl_sounddone); + } + } +} + +/// Find suggestions by comparing the word in a sound-a-like form. +/// Note: This doesn't support postponed prefixes. +static void suggest_try_soundalike(suginfo_T *su) +{ + char_u salword[MAXWLEN]; + langp_T *lp; + slang_T *slang; + + // Do this for all languages that support sound folding and for which a + // .sug file has been loaded. + for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len; lpi++) { + lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi); + 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); + + // try all kinds of inserts/deletes/swaps/etc. + // TODO(vim): also soundfold the next words, so that we can try joining + // and splitting +#ifdef SUGGEST_PROFILE + prof_init(); +#endif + suggest_trie_walk(su, lp, salword, true); +#ifdef SUGGEST_PROFILE + prof_report("soundalike"); +#endif + } + } +} + +/// Finish up after calling suggest_try_soundalike(). +static void suggest_try_soundalike_finish(void) +{ + langp_T *lp; + slang_T *slang; + int todo; + hashitem_T *hi; + + // Do this for all languages that support sound folding and for which a + // .sug file has been loaded. + for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len; lpi++) { + lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi); + slang = lp->lp_slang; + if (!GA_EMPTY(&slang->sl_sal) && slang->sl_sbyts != NULL) { + // Free the info about handled words. + todo = (int)slang->sl_sounddone.ht_used; + for (hi = slang->sl_sounddone.ht_array; todo > 0; hi++) { + if (!HASHITEM_EMPTY(hi)) { + xfree(HI2SFT(hi)); + todo--; + } + } + + // Clear the hashtable, it may also be used by another region. + hash_clear(&slang->sl_sounddone); + hash_init(&slang->sl_sounddone); + } + } +} + +/// A match with a soundfolded word is found. Add the good word(s) that +/// produce this soundfolded word. +/// +/// @param score soundfold score +static void add_sound_suggest(suginfo_T *su, char_u *goodword, int score, langp_T *lp) +{ + slang_T *slang = lp->lp_slang; // language for sound folding + int sfwordnr; + char_u *nrline; + int orgnr; + char_u theword[MAXWLEN]; + int i; + int wlen; + char_u *byts; + idx_T *idxs; + int n; + int wordcount; + int wc; + int goodscore; + hash_T hash; + hashitem_T *hi; + sftword_T *sft; + int bc, gc; + int limit; + + // It's very well possible that the same soundfold word is found several + // times with different scores. Since the following is quite slow only do + // 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); + 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); + } else { + sft = HI2SFT(hi); + if (score >= sft->sft_score) { + return; + } + sft->sft_score = (int16_t)score; + } + + // Find the word nr in the soundfold tree. + sfwordnr = soundfold_find(slang, goodword); + if (sfwordnr < 0) { + internal_error("add_sound_suggest()"); + return; + } + + // Go over the list of good words that produce this soundfold word + nrline = ml_get_buf(slang->sl_sugbuf, (linenr_T)sfwordnr + 1, false); + orgnr = 0; + while (*nrline != NUL) { + // The wordnr was stored in a minimal nr of bytes as an offset to the + // previous wordnr. + orgnr += bytes2offset(&nrline); + + byts = slang->sl_fbyts; + idxs = slang->sl_fidxs; + + // Lookup the word "orgnr" one of the two tries. + n = 0; + wordcount = 0; + for (wlen = 0; wlen < MAXWLEN - 3; wlen++) { + i = 1; + if (wordcount == orgnr && byts[n + 1] == NUL) { + break; // found end of word + } + if (byts[n + 1] == NUL) { + wordcount++; + } + + // skip over the NUL bytes + for (; byts[n + i] == NUL; i++) { + if (i > byts[n]) { // safety check + STRCPY(theword + wlen, "BAD"); + wlen += 3; + goto badword; + } + } + + // One of the siblings must have the word. + for (; i < byts[n]; i++) { + wc = idxs[idxs[n + i]]; // nr of words under this byte + if (wordcount + wc > orgnr) { + break; + } + wordcount += wc; + } + + theword[wlen] = byts[n + i]; + n = idxs[n + i]; + } +badword: + theword[wlen] = NUL; + + // Go over the possible flags and regions. + for (; i <= byts[n] && byts[n + i] == NUL; i++) { + char_u cword[MAXWLEN]; + char_u *p; + int flags = (int)idxs[n + i]; + + // Skip words with the NOSUGGEST flag + if (flags & WF_NOSUGGEST) { + continue; + } + + if (flags & WF_KEEPCAP) { + // Must find the word in the keep-case tree. + find_keepcap_word(slang, theword, 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); + p = cword; + } else { + p = theword; + } + } + + // Add the suggestion. + if (sps_flags & SPS_DOUBLE) { + // Add the suggestion if the score isn't too bad. + if (score <= su->su_maxscore) { + add_suggestion(su, &su->su_sga, p, su->su_badlen, + score, 0, false, slang, false); + } + } else { + // Add a penalty for words in another region. + if ((flags & WF_REGION) + && (((unsigned)flags >> 16) & (unsigned)lp->lp_region) == 0) { + goodscore = SCORE_REGION; + } else { + goodscore = 0; + } + + // Add a small penalty for changing the first letter from + // lower to upper case. Helps for "tath" -> "Kath", which is + // less common than "tath" -> "path". Don't do it when the + // letter is the same, that has already been counted. + gc = utf_ptr2char((char *)p); + if (SPELL_ISUPPER(gc)) { + bc = utf_ptr2char((char *)su->su_badword); + if (!SPELL_ISUPPER(bc) + && SPELL_TOFOLD(bc) != SPELL_TOFOLD(gc)) { + goodscore += SCORE_ICASE / 2; + } + } + + // Compute the score for the good word. This only does letter + // insert/delete/swap/replace. REP items are not considered, + // which may make the score a bit higher. + // Use a limit for the score to make it work faster. Use + // MAXSCORE(), because RESCORE() will change the score. + // If the limit is very high then the iterative method is + // 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); + } else { + goodscore += spell_edit_score_limit(slang, su->su_badword, + p, limit); + } + + // When going over the limit don't bother to do the rest. + if (goodscore < SCORE_MAXMAX) { + // Give a bonus to words seen before. + goodscore = score_wordcount_adj(slang, goodscore, p, false); + + // Add the suggestion if the score isn't too bad. + goodscore = RESCORE(goodscore, score); + if (goodscore <= su->su_sfmaxscore) { + add_suggestion(su, &su->su_ga, p, su->su_badlen, + goodscore, score, true, slang, true); + } + } + } + } + } +} + +/// Find word "word" in fold-case tree for "slang" and return the word number. +static int soundfold_find(slang_T *slang, char_u *word) +{ + idx_T arridx = 0; + int len; + int wlen = 0; + int c; + char_u *ptr = word; + char_u *byts; + idx_T *idxs; + int wordnr = 0; + + byts = slang->sl_sbyts; + idxs = slang->sl_sidxs; + + for (;;) { + // First byte is the number of possible bytes. + len = byts[arridx++]; + + // If the first possible byte is a zero the word could end here. + // If the word ends we found the word. If not skip the NUL bytes. + c = ptr[wlen]; + if (byts[arridx] == NUL) { + if (c == NUL) { + break; + } + + // Skip over the zeros, there can be several. + while (len > 0 && byts[arridx] == NUL) { + arridx++; + len--; + } + if (len == 0) { + return -1; // no children, word should have ended here + } + wordnr++; + } + + // If the word ends we didn't find it. + if (c == NUL) { + return -1; + } + + // Perform a binary search in the list of accepted bytes. + if (c == TAB) { // <Tab> is handled like <Space> + c = ' '; + } + while (byts[arridx] < c) { + // The word count is in the first idxs[] entry of the child. + wordnr += idxs[idxs[arridx]]; + arridx++; + if (--len == 0) { // end of the bytes, didn't find it + return -1; + } + } + if (byts[arridx] != c) { // didn't find the byte + return -1; + } + + // Continue at the child (if there is one). + arridx = idxs[arridx]; + wlen++; + + // One space in the good word may stand for several spaces in the + // checked word. + if (c == ' ') { + while (ptr[wlen] == ' ' || ptr[wlen] == TAB) { + wlen++; + } + } + } + + return wordnr; +} + +/// Returns true if "c1" and "c2" are similar characters according to the MAP +/// lines in the .aff file. +static bool similar_chars(slang_T *slang, int c1, int c2) +{ + int m1, m2; + char buf[MB_MAXBYTES + 1]; + hashitem_T *hi; + + if (c1 >= 256) { + buf[utf_char2bytes(c1, (char *)buf)] = 0; + hi = hash_find(&slang->sl_map_hash, buf); + if (HASHITEM_EMPTY(hi)) { + m1 = 0; + } else { + m1 = utf_ptr2char((char *)hi->hi_key + STRLEN(hi->hi_key) + 1); + } + } else { + m1 = slang->sl_map_array[c1]; + } + if (m1 == 0) { + return false; + } + + if (c2 >= 256) { + buf[utf_char2bytes(c2, (char *)buf)] = 0; + hi = hash_find(&slang->sl_map_hash, buf); + if (HASHITEM_EMPTY(hi)) { + m2 = 0; + } else { + m2 = utf_ptr2char((char *)hi->hi_key + STRLEN(hi->hi_key) + 1); + } + } else { + m2 = slang->sl_map_array[c2]; + } + + return m1 == m2; +} + +/// Adds a suggestion to the list of suggestions. +/// For a suggestion that is already in the list the lowest score is remembered. +/// +/// @param gap either su_ga or su_sga +/// @param badlenarg len of bad word replaced with "goodword" +/// @param had_bonus value for st_had_bonus +/// @param slang language for sound folding +/// @param maxsf su_maxscore applies to soundfold score, su_sfmaxscore to the total score. +static void add_suggestion(suginfo_T *su, garray_T *gap, const char_u *goodword, int badlenarg, + int score, int altscore, bool had_bonus, slang_T *slang, bool maxsf) +{ + int goodlen; // len of goodword changed + int badlen; // len of bad word changed + suggest_T *stp; + suggest_T new_sug; + + // Minimize "badlen" for consistency. Avoids that changing "the the" to + // "thee the" is added next to changing the first "the" the "thee". + const char_u *pgood = goodword + STRLEN(goodword); + char_u *pbad = su->su_badptr + badlenarg; + for (;;) { + goodlen = (int)(pgood - goodword); + badlen = (int)(pbad - su->su_badptr); + if (goodlen <= 0 || badlen <= 0) { + break; + } + MB_PTR_BACK(goodword, pgood); + MB_PTR_BACK(su->su_badptr, pbad); + if (utf_ptr2char((char *)pgood) != utf_ptr2char((char *)pbad)) { + break; + } + } + + if (badlen == 0 && goodlen == 0) { + // goodword doesn't change anything; may happen for "the the" changing + // the first "the" to itself. + return; + } + + int i; + if (GA_EMPTY(gap)) { + i = -1; + } else { + // Check if the word is already there. Also check the length that is + // being replaced "thes," -> "these" is a different suggestion from + // "thes" -> "these". + stp = &SUG(*gap, 0); + for (i = gap->ga_len; --i >= 0; stp++) { + if (stp->st_wordlen == goodlen + && stp->st_orglen == badlen + && STRNCMP(stp->st_word, goodword, goodlen) == 0) { + // Found it. Remember the word with the lowest score. + if (stp->st_slang == NULL) { + stp->st_slang = slang; + } + + new_sug.st_score = score; + new_sug.st_altscore = altscore; + new_sug.st_had_bonus = had_bonus; + + if (stp->st_had_bonus != had_bonus) { + // Only one of the two had the soundalike score computed. + // Need to do that for the other one now, otherwise the + // scores can't be compared. This happens because + // suggest_try_change() doesn't compute the soundalike + // word to keep it fast, while some special methods set + // the soundalike score to zero. + if (had_bonus) { + rescore_one(su, stp); + } else { + new_sug.st_word = stp->st_word; + new_sug.st_wordlen = stp->st_wordlen; + new_sug.st_slang = stp->st_slang; + new_sug.st_orglen = badlen; + rescore_one(su, &new_sug); + } + } + + if (stp->st_score > new_sug.st_score) { + stp->st_score = new_sug.st_score; + stp->st_altscore = new_sug.st_altscore; + stp->st_had_bonus = new_sug.st_had_bonus; + } + break; + } + } + } + + if (i < 0) { + // Add a suggestion. + stp = GA_APPEND_VIA_PTR(suggest_T, gap); + stp->st_word = vim_strnsave(goodword, (size_t)goodlen); + stp->st_wordlen = goodlen; + stp->st_score = score; + stp->st_altscore = altscore; + stp->st_had_bonus = had_bonus; + stp->st_orglen = badlen; + stp->st_slang = slang; + + // If we have too many suggestions now, sort the list and keep + // the best suggestions. + if (gap->ga_len > SUG_MAX_COUNT(su)) { + if (maxsf) { + su->su_sfmaxscore = cleanup_suggestions(gap, + su->su_sfmaxscore, SUG_CLEAN_COUNT(su)); + } else { + su->su_maxscore = cleanup_suggestions(gap, + su->su_maxscore, SUG_CLEAN_COUNT(su)); + } + } + } +} + +/// Suggestions may in fact be flagged as errors. Esp. for banned words and +/// for split words, such as "the the". Remove these from the list here. +/// +/// @param gap either su_ga or su_sga +static void check_suggestions(suginfo_T *su, garray_T *gap) +{ + suggest_T *stp; + char_u longword[MAXWLEN + 1]; + int len; + hlf_T attr; + + if (gap->ga_len == 0) { + return; + } + 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); + len = stp[i].st_wordlen; + STRLCPY(longword + len, su->su_badptr + stp[i].st_orglen, + MAXWLEN - len + 1); + attr = HLF_COUNT; + (void)spell_check(curwin, longword, &attr, NULL, false); + if (attr != HLF_COUNT) { + // Remove this entry. + xfree(stp[i].st_word); + gap->ga_len--; + if (i < gap->ga_len) { + memmove(stp + i, stp + i + 1, sizeof(suggest_T) * (size_t)(gap->ga_len - i)); + } + } + } +} + +/// Add a word to be banned. +static void add_banned(suginfo_T *su, char_u *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); + } +} + +/// Recompute the score for all suggestions if sound-folding is possible. This +/// is slow, thus only done for the final results. +static void rescore_suggestions(suginfo_T *su) +{ + if (su->su_sallang != NULL) { + for (int i = 0; i < su->su_ga.ga_len; i++) { + rescore_one(su, &SUG(su->su_ga, i)); + } + } +} + +/// Recompute the score for one suggestion if sound-folding is possible. +static void rescore_one(suginfo_T *su, suggest_T *stp) +{ + slang_T *slang = stp->st_slang; + char_u sal_badword[MAXWLEN]; + char_u *p; + + // Only rescore suggestions that have no sal score yet and do have a + // language. + if (slang != NULL && !GA_EMPTY(&slang->sl_sal) && !stp->st_had_bonus) { + if (slang == su->su_sallang) { + p = su->su_sal_badword; + } else { + spell_soundfold(slang, su->su_fbadword, true, sal_badword); + p = sal_badword; + } + + stp->st_altscore = stp_sal_score(stp, su, slang, p); + if (stp->st_altscore == SCORE_MAXMAX) { + stp->st_altscore = SCORE_BIG; + } + stp->st_score = RESCORE(stp->st_score, stp->st_altscore); + stp->st_had_bonus = true; + } +} + +/// Function given to qsort() to sort the suggestions on st_score. +/// First on "st_score", then "st_altscore" then alphabetically. +static int sug_compare(const void *s1, const void *s2) +{ + suggest_T *p1 = (suggest_T *)s1; + suggest_T *p2 = (suggest_T *)s2; + int n = p1->st_score - p2->st_score; + + if (n == 0) { + n = p1->st_altscore - p2->st_altscore; + if (n == 0) { + n = STRICMP(p1->st_word, p2->st_word); + } + } + return n; +} + +/// Cleanup the suggestions: +/// - Sort on score. +/// - Remove words that won't be displayed. +/// +/// @param keep nr of suggestions to keep +/// +/// @return the maximum score in the list or "maxscore" unmodified. +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); + + // 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; +} + +/// Compute a score for two sound-a-like words. +/// This permits up to two inserts/deletes/swaps/etc. to keep things fast. +/// Instead of a generic loop we write out the code. That keeps it fast by +/// avoiding checks that will not be possible. +/// +/// @param goodstart sound-folded good word +/// @param badstart sound-folded bad word +static int soundalike_score(char_u *goodstart, char_u *badstart) +{ + char_u *goodsound = goodstart; + char_u *badsound = badstart; + int goodlen; + int badlen; + int n; + char_u *pl, *ps; + char_u *pl2, *ps2; + int score = 0; + + // Adding/inserting "*" at the start (word starts with vowel) shouldn't be + // counted so much, vowels in the middle of the word aren't counted at all. + if ((*badsound == '*' || *goodsound == '*') && *badsound != *goodsound) { + if ((badsound[0] == NUL && goodsound[1] == NUL) + || (goodsound[0] == NUL && badsound[1] == NUL)) { + // changing word with vowel to word without a sound + return SCORE_DEL; + } + if (badsound[0] == NUL || goodsound[0] == NUL) { + // more than two changes + return SCORE_MAXMAX; + } + + if (badsound[1] == goodsound[1] + || (badsound[1] != NUL + && goodsound[1] != NUL + && badsound[2] == goodsound[2])) { + // handle like a substitute + } else { + score = 2 * SCORE_DEL / 3; + if (*badsound == '*') { + badsound++; + } else { + goodsound++; + } + } + } + + goodlen = (int)STRLEN(goodsound); + badlen = (int)STRLEN(badsound); + + // Return quickly if the lengths are too different to be fixed by two + // changes. + n = goodlen - badlen; + if (n < -2 || n > 2) { + return SCORE_MAXMAX; + } + + if (n > 0) { + pl = goodsound; // goodsound is longest + ps = badsound; + } else { + pl = badsound; // badsound is longest + ps = goodsound; + } + + // Skip over the identical part. + while (*pl == *ps && *pl != NUL) { + pl++; + ps++; + } + + switch (n) { + case -2: + case 2: + // Must delete two characters from "pl". + pl++; // first delete + while (*pl == *ps) { + pl++; + ps++; + } + // strings must be equal after second delete + if (STRCMP(pl + 1, ps) == 0) { + return score + SCORE_DEL * 2; + } + + // Failed to compare. + break; + + case -1: + case 1: + // Minimal one delete from "pl" required. + + // 1: delete + pl2 = pl + 1; + ps2 = ps; + while (*pl2 == *ps2) { + if (*pl2 == NUL) { // reached the end + return score + SCORE_DEL; + } + pl2++; + ps2++; + } + + // 2: delete then swap, then rest must be equal + if (pl2[0] == ps2[1] && pl2[1] == ps2[0] + && STRCMP(pl2 + 2, ps2 + 2) == 0) { + return score + SCORE_DEL + SCORE_SWAP; + } + + // 3: delete then substitute, then the rest must be equal + if (STRCMP(pl2 + 1, ps2 + 1) == 0) { + return score + SCORE_DEL + SCORE_SUBST; + } + + // 4: first swap then delete + if (pl[0] == ps[1] && pl[1] == ps[0]) { + pl2 = pl + 2; // swap, skip two chars + ps2 = ps + 2; + while (*pl2 == *ps2) { + pl2++; + ps2++; + } + // delete a char and then strings must be equal + if (STRCMP(pl2 + 1, ps2) == 0) { + return score + SCORE_SWAP + SCORE_DEL; + } + } + + // 5: first substitute then delete + pl2 = pl + 1; // substitute, skip one char + ps2 = ps + 1; + while (*pl2 == *ps2) { + pl2++; + ps2++; + } + // delete a char and then strings must be equal + if (STRCMP(pl2 + 1, ps2) == 0) { + return score + SCORE_SUBST + SCORE_DEL; + } + + // Failed to compare. + break; + + case 0: + // Lengths are equal, thus changes must result in same length: An + // insert is only possible in combination with a delete. + // 1: check if for identical strings + if (*pl == NUL) { + return score; + } + + // 2: swap + if (pl[0] == ps[1] && pl[1] == ps[0]) { + pl2 = pl + 2; // swap, skip two chars + ps2 = ps + 2; + while (*pl2 == *ps2) { + if (*pl2 == NUL) { // reached the end + return score + SCORE_SWAP; + } + pl2++; + ps2++; + } + // 3: swap and swap again + if (pl2[0] == ps2[1] && pl2[1] == ps2[0] + && STRCMP(pl2 + 2, ps2 + 2) == 0) { + return score + SCORE_SWAP + SCORE_SWAP; + } + + // 4: swap and substitute + if (STRCMP(pl2 + 1, ps2 + 1) == 0) { + return score + SCORE_SWAP + SCORE_SUBST; + } + } + + // 5: substitute + pl2 = pl + 1; + ps2 = ps + 1; + while (*pl2 == *ps2) { + if (*pl2 == NUL) { // reached the end + return score + SCORE_SUBST; + } + pl2++; + ps2++; + } + + // 6: substitute and swap + if (pl2[0] == ps2[1] && pl2[1] == ps2[0] + && STRCMP(pl2 + 2, ps2 + 2) == 0) { + return score + SCORE_SUBST + SCORE_SWAP; + } + + // 7: substitute and substitute + if (STRCMP(pl2 + 1, ps2 + 1) == 0) { + return score + SCORE_SUBST + SCORE_SUBST; + } + + // 8: insert then delete + pl2 = pl; + ps2 = ps + 1; + while (*pl2 == *ps2) { + pl2++; + ps2++; + } + if (STRCMP(pl2 + 1, ps2) == 0) { + return score + SCORE_INS + SCORE_DEL; + } + + // 9: delete then insert + pl2 = pl + 1; + ps2 = ps; + while (*pl2 == *ps2) { + pl2++; + ps2++; + } + if (STRCMP(pl2, ps2 + 1) == 0) { + return score + SCORE_INS + SCORE_DEL; + } + + // Failed to compare. + break; + } + + return SCORE_MAXMAX; +} + +/// Compute the "edit distance" to turn "badword" into "goodword". The less +/// deletes/inserts/substitutes/swaps are required the lower the score. +/// +/// The algorithm is described by Du and Chang, 1992. +/// 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) +{ + int *cnt; + int j, i; + int t; + int bc, gc; + int pbc, pgc; + int wbadword[MAXWLEN]; + int wgoodword[MAXWLEN]; + + // Lengths with NUL. + int badlen; + int goodlen; + { + // 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;) { + wbadword[badlen++] = mb_cptr2char_adv(&p); + } + wbadword[badlen++] = 0; + goodlen = 0; + for (const char_u *p = goodword; *p != NUL;) { + wgoodword[goodlen++] = mb_cptr2char_adv(&p); + } + wgoodword[goodlen++] = 0; + } + + // We use "cnt" as an array: CNT(badword_idx, goodword_idx). +#define CNT(a, b) cnt[(a) + (b) * (badlen + 1)] + cnt = xmalloc(sizeof(int) * ((size_t)badlen + 1) * ((size_t)goodlen + 1)); + + CNT(0, 0) = 0; + for (j = 1; j <= goodlen; j++) { + CNT(0, j) = CNT(0, j - 1) + SCORE_INS; + } + + for (i = 1; i <= badlen; i++) { + CNT(i, 0) = CNT(i - 1, 0) + SCORE_DEL; + for (j = 1; j <= goodlen; j++) { + bc = wbadword[i - 1]; + gc = wgoodword[j - 1]; + if (bc == gc) { + CNT(i, j) = CNT(i - 1, j - 1); + } else { + // Use a better score when there is only a case difference. + if (SPELL_TOFOLD(bc) == SPELL_TOFOLD(gc)) { + CNT(i, j) = SCORE_ICASE + CNT(i - 1, j - 1); + } else { + // For a similar character use SCORE_SIMILAR. + if (slang != NULL + && slang->sl_has_map + && similar_chars(slang, gc, bc)) { + CNT(i, j) = SCORE_SIMILAR + CNT(i - 1, j - 1); + } else { + CNT(i, j) = SCORE_SUBST + CNT(i - 1, j - 1); + } + } + + if (i > 1 && j > 1) { + pbc = wbadword[i - 2]; + pgc = wgoodword[j - 2]; + if (bc == pgc && pbc == gc) { + t = SCORE_SWAP + CNT(i - 2, j - 2); + if (t < CNT(i, j)) { + CNT(i, j) = t; + } + } + } + t = SCORE_DEL + CNT(i - 1, j); + if (t < CNT(i, j)) { + CNT(i, j) = t; + } + t = SCORE_INS + CNT(i, j - 1); + if (t < CNT(i, j)) { + CNT(i, j) = t; + } + } + } + } + + i = CNT(badlen - 1, goodlen - 1); + xfree(cnt); + return i; +} + +typedef struct { + int badi; + int goodi; + int score; +} limitscore_T; + +/// Like spell_edit_score(), but with a limit on the score to make it faster. +/// May return SCORE_MAXMAX when the score is higher than "limit". +/// +/// This uses a stack for the edits still to be tried. +/// The idea comes from Aspell leditdist.cpp. Rewritten in C and added support +/// for multi-byte characters. +static int spell_edit_score_limit(slang_T *slang, char_u *badword, char_u *goodword, int limit) +{ + return spell_edit_score_limit_w(slang, badword, goodword, limit); +} + +/// 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) +{ + limitscore_T stack[10]; // allow for over 3 * 2 edits + int stackidx; + int bi, gi; + int bi2, gi2; + int bc, gc; + int score; + int score_off; + int minscore; + int round; + int wbadword[MAXWLEN]; + int wgoodword[MAXWLEN]; + + // 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;) { + wbadword[bi++] = mb_cptr2char_adv(&p); + } + wbadword[bi++] = 0; + gi = 0; + for (const char_u *p = goodword; *p != NUL;) { + wgoodword[gi++] = mb_cptr2char_adv(&p); + } + wgoodword[gi++] = 0; + + // The idea is to go from start to end over the words. So long as + // characters are equal just continue, this always gives the lowest score. + // When there is a difference try several alternatives. Each alternative + // increases "score" for the edit distance. Some of the alternatives are + // pushed unto a stack and tried later, some are tried right away. At the + // end of the word the score for one alternative is known. The lowest + // possible score is stored in "minscore". + stackidx = 0; + bi = 0; + gi = 0; + score = 0; + minscore = limit + 1; + + for (;;) { + // Skip over an equal part, score remains the same. + for (;;) { + bc = wbadword[bi]; + gc = wgoodword[gi]; + + if (bc != gc) { // stop at a char that's different + break; + } + if (bc == NUL) { // both words end + if (score < minscore) { + minscore = score; + } + goto pop; // do next alternative + } + bi++; + gi++; + } + + if (gc == NUL) { // goodword ends, delete badword chars + do { + if ((score += SCORE_DEL) >= minscore) { + goto pop; // do next alternative + } + } while (wbadword[++bi] != NUL); + minscore = score; + } else if (bc == NUL) { // badword ends, insert badword chars + do { + if ((score += SCORE_INS) >= minscore) { + goto pop; // do next alternative + } + } while (wgoodword[++gi] != NUL); + minscore = score; + } else { // both words continue + // If not close to the limit, perform a change. Only try changes + // that may lead to a lower score than "minscore". + // round 0: try deleting a char from badword + // round 1: try inserting a char in badword + for (round = 0; round <= 1; round++) { + score_off = score + (round == 0 ? SCORE_DEL : SCORE_INS); + if (score_off < minscore) { + if (score_off + SCORE_EDIT_MIN >= minscore) { + // Near the limit, rest of the words must match. We + // can check that right now, no need to push an item + // onto the stack. + bi2 = bi + 1 - round; + gi2 = gi + round; + while (wgoodword[gi2] == wbadword[bi2]) { + if (wgoodword[gi2] == NUL) { + minscore = score_off; + break; + } + bi2++; + gi2++; + } + } else { + // try deleting a character from badword later + stack[stackidx].badi = bi + 1 - round; + stack[stackidx].goodi = gi + round; + stack[stackidx].score = score_off; + stackidx++; + } + } + } + + if (score + SCORE_SWAP < minscore) { + // If swapping two characters makes a match then the + // substitution is more expensive, thus there is no need to + // try both. + if (gc == wbadword[bi + 1] && bc == wgoodword[gi + 1]) { + // Swap two characters, that is: skip them. + gi += 2; + bi += 2; + score += SCORE_SWAP; + continue; + } + } + + // Substitute one character for another which is the same + // thing as deleting a character from both goodword and badword. + // Use a better score when there is only a case difference. + if (SPELL_TOFOLD(bc) == SPELL_TOFOLD(gc)) { + score += SCORE_ICASE; + } else { + // For a similar character use SCORE_SIMILAR. + if (slang != NULL + && slang->sl_has_map + && similar_chars(slang, gc, bc)) { + score += SCORE_SIMILAR; + } else { + score += SCORE_SUBST; + } + } + + if (score < minscore) { + // Do the substitution. + gi++; + bi++; + continue; + } + } +pop: + // Get here to try the next alternative, pop it from the stack. + if (stackidx == 0) { // stack is empty, finished + break; + } + + // pop an item from the stack + stackidx--; + gi = stack[stackidx].goodi; + bi = stack[stackidx].badi; + score = stack[stackidx].score; + } + + // When the score goes over "limit" it may actually be much higher. + // Return a very large number to avoid going below the limit when giving a + // bonus. + if (minscore > limit) { + return SCORE_MAXMAX; + } + return minscore; +} diff --git a/src/nvim/spellsuggest.h b/src/nvim/spellsuggest.h new file mode 100644 index 0000000000..8813a5b3f1 --- /dev/null +++ b/src/nvim/spellsuggest.h @@ -0,0 +1,9 @@ +#ifndef NVIM_SPELLSUGGEST_H +#define NVIM_SPELLSUGGEST_H + +#include "nvim/garray.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "spellsuggest.h.generated.h" +#endif +#endif // NVIM_SPELLSUGGEST_H diff --git a/src/nvim/state.c b/src/nvim/state.c index d6cca71ad8..61740800a1 100644 --- a/src/nvim/state.c +++ b/src/nvim/state.c @@ -5,6 +5,7 @@ #include "nvim/ascii.h" #include "nvim/autocmd.h" +#include "nvim/drawscreen.h" #include "nvim/eval.h" #include "nvim/ex_docmd.h" #include "nvim/getchar.h" @@ -15,7 +16,6 @@ #include "nvim/option.h" #include "nvim/option_defs.h" #include "nvim/os/input.h" -#include "nvim/screen.h" #include "nvim/state.h" #include "nvim/ui.h" #include "nvim/vim.h" diff --git a/src/nvim/statusline.c b/src/nvim/statusline.c new file mode 100644 index 0000000000..9b441ba0a9 --- /dev/null +++ b/src/nvim/statusline.c @@ -0,0 +1,1807 @@ +// 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/assert.h" +#include "nvim/autocmd.h" +#include "nvim/buffer.h" +#include "nvim/charset.h" +#include "nvim/drawscreen.h" +#include "nvim/eval.h" +#include "nvim/eval/vars.h" +#include "nvim/grid.h" +#include "nvim/highlight.h" +#include "nvim/highlight_group.h" +#include "nvim/move.h" +#include "nvim/option.h" +#include "nvim/optionstr.h" +#include "nvim/statusline.h" +#include "nvim/ui.h" +#include "nvim/undo.h" +#include "nvim/vim.h" +#include "nvim/window.h" + +// Determines how deeply nested %{} blocks will be evaluated in statusline. +#define MAX_STL_EVAL_DEPTH 100 + +/// Enumeration specifying the valid numeric bases that can +/// be used when printing numbers in the status line. +typedef enum { + kNumBaseDecimal = 10, + kNumBaseHexadecimal = 16, +} NumberBase; + +/// Redraw the status line of window `wp`. +/// +/// If inversion is possible we use it. Else '=' characters are used. +void win_redr_status(win_T *wp) +{ + int row; + int col; + char_u *p; + int len; + int fillchar; + int attr; + int width; + int this_ru_col; + bool is_stl_global = global_stl_height() > 0; + static bool busy = false; + + // May get here recursively when 'statusline' (indirectly) + // invokes ":redrawstatus". Simply ignore the call then. + if (busy + // Also ignore if wildmenu is showing. + || (wild_menu_showing != 0 && !ui_has(kUIWildmenu))) { + return; + } + busy = true; + + wp->w_redr_status = false; + if (wp->w_status_height == 0 && !(is_stl_global && wp == curwin)) { + // no status line, either global statusline is enabled or the window is a last window + redraw_cmdline = true; + } else if (!redrawing()) { + // Don't redraw right now, do it later. Don't update status line when + // popup menu is visible and may be drawn over it + wp->w_redr_status = true; + } else if (*p_stl != NUL || *wp->w_p_stl != NUL) { + // redraw custom status line + redraw_custom_statusline(wp); + } else { + fillchar = fillchar_status(&attr, wp); + width = is_stl_global ? Columns : wp->w_width; + + get_trans_bufname(wp->w_buffer); + p = (char_u *)NameBuff; + len = (int)STRLEN(p); + + if (bt_help(wp->w_buffer) + || wp->w_p_pvw + || bufIsChanged(wp->w_buffer) + || wp->w_buffer->b_p_ro) { + *(p + len++) = ' '; + } + if (bt_help(wp->w_buffer)) { + snprintf((char *)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); + } + if (bufIsChanged(wp->w_buffer)) { + snprintf((char *)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]")); + // 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; + } + if (this_ru_col <= 1) { + p = (char_u *)"<"; // No room for file name! + len = 1; + } else { + int clen = 0, i; + + // Count total number of display cells. + clen = (int)mb_string2cells((char *)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); + } + len = clen; + if (i > 0) { + p = p + i - 1; + *p = '<'; + len++; + } + } + + 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_fill(&default_grid, row, row + 1, len + col, + this_ru_col + col, fillchar, fillchar, attr); + + if (get_keymap_str(wp, "<%s>", (char *)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); + } + + // May need to draw the character below the vertical separator. + if (wp->w_vsep_width != 0 && wp->w_status_height != 0 && redrawing()) { + if (stl_connected(wp)) { + fillchar = fillchar_status(&attr, wp); + } else { + fillchar = fillchar_vsep(wp, &attr); + } + grid_putchar(&default_grid, fillchar, W_ENDROW(wp), W_ENDCOL(wp), attr); + } + busy = false; +} + +void win_redr_winbar(win_T *wp) +{ + static bool entered = false; + + // Return when called recursively. This can happen when the winbar contains an expression + // that triggers a redraw. + if (entered) { + return; + } + entered = true; + + 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; +} + +void win_redr_ruler(win_T *wp, bool always) +{ + bool is_stl_global = global_stl_height() > 0; + static bool did_show_ext_ruler = false; + + // If 'ruler' off, don't do anything + if (!p_ru) { + return; + } + + // Check if cursor.lnum is valid, since win_redr_ruler() may be called + // after deleting lines, before cursor.lnum is corrected. + if (wp->w_cursor.lnum > wp->w_buffer->b_ml.ml_line_count) { + return; + } + + // Don't draw the ruler while doing insert-completion, it might overwrite + // the (long) mode message. + if (wp == lastwin && lastwin->w_status_height == 0 && !is_stl_global) { + if (edit_submode != NULL) { + return; + } + } + + 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; + } + + // Check if not in Insert mode and the line is empty (will show "0-1"). + int empty_line = false; + if ((State & MODE_INSERT) == 0 && *ml_get_buf(wp->w_buffer, wp->w_cursor.lnum, false) == NUL) { + empty_line = true; + } + + // Only draw the ruler when something changed. + validate_virtcol_win(wp); + if (redraw_cmdline + || always + || wp->w_cursor.lnum != wp->w_ru_cursor.lnum + || wp->w_cursor.col != wp->w_ru_cursor.col + || wp->w_virtcol != wp->w_ru_virtcol + || wp->w_cursor.coladd != wp->w_ru_cursor.coladd + || wp->w_topline != wp->w_ru_topline + || wp->w_buffer->b_ml.ml_line_count != wp->w_ru_line_count + || wp->w_topfill != wp->w_ru_topfill + || empty_line != wp->w_ru_empty) { + int width; + int row; + int fillchar; + int attr; + int off; + bool part_of_status = false; + + if (wp->w_status_height) { + row = W_ENDROW(wp); + fillchar = fillchar_status(&attr, wp); + off = wp->w_wincol; + width = wp->w_width; + part_of_status = true; + } else if (is_stl_global) { + row = Rows - (int)p_ch - 1; + fillchar = fillchar_status(&attr, wp); + off = 0; + width = Columns; + part_of_status = true; + } else { + row = Rows - 1; + fillchar = ' '; + attr = HL_ATTR(HLF_MSG); + width = Columns; + off = 0; + } + + if (!part_of_status && !ui_has_messages()) { + return; + } + + // In list mode virtcol needs to be recomputed + colnr_T virtcol = wp->w_virtcol; + if (wp->w_p_list && wp->w_p_lcs_chars.tab1 == NUL) { + wp->w_p_list = false; + getvvcol(wp, &wp->w_cursor, NULL, &virtcol, NULL); + wp->w_p_list = true; + } + +#define RULER_BUF_LEN 70 + char buffer[RULER_BUF_LEN]; + + // Some sprintfs return the length, some return a pointer. + // To avoid portability problems we use strlen() here. + vim_snprintf(buffer, RULER_BUF_LEN, "%" PRId64 ",", + (wp->w_buffer->b_ml.ml_flags & + ML_EMPTY) ? (int64_t)0L : (int64_t)wp->w_cursor.lnum); + size_t len = STRLEN(buffer); + col_print(buffer + len, RULER_BUF_LEN - len, + empty_line ? 0 : (int)wp->w_cursor.col + 1, + (int)virtcol + 1); + + // Add a "50%" if there is room for it. + // On the last line, don't print in the last column (scrolls the + // screen up on some terminals). + int i = (int)STRLEN(buffer); + get_rel_pos(wp, buffer + i + 1, RULER_BUF_LEN - i - 1); + int o = i + vim_strsize(buffer + i + 1); + if (wp->w_status_height == 0 && !is_stl_global) { // can't use last char of screen + o++; + } + int this_ru_col = ru_col - (Columns - width); + if (this_ru_col < 0) { + this_ru_col = 0; + } + // Never use more than half the window/screen width, leave the other half + // for the filename. + if (this_ru_col < (width + 1) / 2) { + this_ru_col = (width + 1) / 2; + } + if (this_ru_col + o < width) { + // Need at least 3 chars left for get_rel_pos() + NUL. + while (this_ru_col + o < width && RULER_BUF_LEN > i + 4) { + i += utf_char2bytes(fillchar, buffer + i); + o++; + } + get_rel_pos(wp, buffer + i, RULER_BUF_LEN - i); + } + + if (ui_has(kUIMessages) && !part_of_status) { + MAXSIZE_TEMP_ARRAY(content, 1); + MAXSIZE_TEMP_ARRAY(chunk, 2); + ADD_C(chunk, INTEGER_OBJ(attr)); + ADD_C(chunk, STRING_OBJ(cstr_as_string((char *)buffer))); + ADD_C(content, ARRAY_OBJ(chunk)); + ui_call_msg_ruler(content); + did_show_ext_ruler = true; + } else { + if (did_show_ext_ruler) { + ui_call_msg_ruler((Array)ARRAY_DICT_INIT); + did_show_ext_ruler = false; + } + // Truncate at window boundary. + o = 0; + for (i = 0; buffer[i] != NUL; i += utfc_ptr2len(buffer + i)) { + o += utf_ptr2cells(buffer + i); + if (this_ru_col + o > width) { + buffer[i] = NUL; + break; + } + } + + ScreenGrid *grid = part_of_status ? &default_grid : &msg_grid_adj; + grid_puts(grid, buffer, row, this_ru_col + off, attr); + grid_fill(grid, row, row + 1, + this_ru_col + off + (int)STRLEN(buffer), off + width, fillchar, + fillchar, attr); + } + + wp->w_ru_cursor = wp->w_cursor; + wp->w_ru_virtcol = wp->w_virtcol; + wp->w_ru_empty = (char)empty_line; + wp->w_ru_topline = wp->w_topline; + wp->w_ru_line_count = wp->w_buffer->b_ml.ml_line_count; + wp->w_ru_topfill = wp->w_topfill; + } +} + +/// Get the character to use in a status line. Get its attributes in "*attr". +int fillchar_status(int *attr, win_T *wp) +{ + int fill; + bool is_curwin = (wp == curwin); + if (is_curwin) { + *attr = win_hl_attr(wp, HLF_S); + fill = wp->w_p_fcs_chars.stl; + } else { + *attr = win_hl_attr(wp, HLF_SNC); + fill = wp->w_p_fcs_chars.stlnc; + } + // Use fill when there is highlighting, and highlighting of current + // window differs, or the fillchars differ, or this is not the + // current window + if (*attr != 0 && ((win_hl_attr(wp, HLF_S) != win_hl_attr(wp, HLF_SNC) + || !is_curwin || ONE_WINDOW) + || (wp->w_p_fcs_chars.stl != wp->w_p_fcs_chars.stlnc))) { + return fill; + } + if (is_curwin) { + return '^'; + } + return '='; +} + +/// Redraw the status line according to 'statusline' and take care of any +/// errors encountered. +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. + if (entered) { + return; + } + 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 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_u *stl; + 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; + + 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 = (char_u *)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 = (char_u *)((*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 (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; + use_sandbox = was_set_insecurely(wp, "winbar", 0); + + 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)); + } + } 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 (draw_ruler) { + stl = (char_u *)p_ruf; + // advance past any leading group spec - implicit in ru_col + if (*stl == '%') { + if (*++stl == '-') { + stl++; + } + if (atoi((char *)stl)) { + while (ascii_isdigit(*stl)) { + stl++; + } + } + if (*stl++ != '(') { + stl = (char_u *)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); + } + + use_sandbox = was_set_insecurely(wp, "rulerformat", 0); + } else { + if (*wp->w_p_stl != NUL) { + stl = (char_u *)wp->w_p_stl; + } else { + stl = (char_u *)p_stl; + } + use_sandbox = was_set_insecurely(wp, "statusline", *wp->w_p_stl == NUL ? 0 : OPT_LOCAL); + } + + 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 = vim_strsave(stl); + width = + build_stl_str_hl(ewp, buf, sizeof(buf), (char *)stl, use_sandbox, + fillchar, maxwidth, &hltab, &tabtab); + xfree(stl); + ewp->w_p_crb = p_crb_save; + + // 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); + + // 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; + + if (click_defs == NULL) { + goto theend; + } + + 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; + } + } + while (col < maxwidth) { + click_defs[col++] = cur_click_def; + } + +theend: + entered = false; +} + +/// Build a string from the status line items in "fmt". +/// Return length of string in screen cells. +/// +/// Normally works for window "wp", except when working for 'tabline' then it +/// is "curwin". +/// +/// Items are drawn interspersed with the text that surrounds it +/// Specials: %-<wid>(xxx%) => group, %= => separation marker, %< => truncation +/// Item: %-<minwid>.<maxwid><itemch> All but <itemch> are optional +/// +/// If maxwidth is not zero, the string will be filled at any middle marker +/// or truncated if too long, fillchar is used for all whitespace. +/// +/// @param wp The window to build a statusline for +/// @param out The output buffer to write the statusline to +/// Note: This should not be NameBuff +/// @param outlen The length of the output buffer +/// @param fmt The statusline format string +/// @param use_sandbox Use a sandboxed environment when evaluating fmt +/// @param fillchar Character to use when filling empty space in the statusline +/// @param maxwidth The maximum width to make the statusline +/// @param hltab HL attributes (can be NULL) +/// @param tabtab Tab clicks definition (can be NULL). +/// +/// @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) +{ + static size_t stl_items_len = 20; // Initial value, grows as needed. + static stl_item_t *stl_items = NULL; + static int *stl_groupitems = NULL; + static stl_hlrec_t *stl_hltab = NULL; + static StlClickRecord *stl_tabtab = NULL; + static int *stl_separator_locations = NULL; + +#define TMPLEN 70 + char buf_tmp[TMPLEN]; + char win_tmp[TMPLEN]; + char *usefmt = fmt; + const int save_must_redraw = must_redraw; + const int save_redr_type = curwin->w_redr_type; + + if (stl_items == NULL) { + stl_items = xmalloc(sizeof(stl_item_t) * stl_items_len); + stl_groupitems = xmalloc(sizeof(int) * stl_items_len); + + // Allocate one more, because the last element is used to indicate the + // end of the list. + stl_hltab = xmalloc(sizeof(stl_hlrec_t) * (stl_items_len + 1)); + stl_tabtab = xmalloc(sizeof(StlClickRecord) * (stl_items_len + 1)); + + stl_separator_locations = xmalloc(sizeof(int) * stl_items_len); + } + + // 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] == '!') { + typval_T tv = { + .v_type = VAR_NUMBER, + .vval.v_number = wp->handle, + }; + set_var(S_LEN("g:statusline_winid"), &tv, false); + + usefmt = eval_to_string_safe(fmt + 2, NULL, use_sandbox); + if (usefmt == NULL) { + usefmt = fmt; + } + + do_unlet(S_LEN("g:statusline_winid"), true); + } + + if (fillchar == 0) { + fillchar = ' '; + } + + // The cursor in windows other than the current one isn't always + // up-to-date, esp. because of autocommands and timers. + linenr_T lnum = wp->w_cursor.lnum; + if (lnum > wp->w_buffer->b_ml.ml_line_count) { + lnum = wp->w_buffer->b_ml.ml_line_count; + wp->w_cursor.lnum = lnum; + } + + // Get line & check if empty (cursorpos will show "0-1"). + const char *line_ptr = (char *)ml_get_buf(wp->w_buffer, lnum, false); + bool empty_line = (*line_ptr == NUL); + + // Get the byte value now, in case we need it below. This is more + // efficient than making a copy of the line. + int byteval; + const size_t len = STRLEN(line_ptr); + if (wp->w_cursor.col > (colnr_T)len) { + // Line may have changed since checking the cursor column, or the lnum + // was adjusted above. + wp->w_cursor.col = (colnr_T)len; + wp->w_cursor.coladd = 0; + byteval = 0; + } else { + byteval = utf_ptr2char(line_ptr + wp->w_cursor.col); + } + + int groupdepth = 0; + int evaldepth = 0; + + int curitem = 0; + bool prevchar_isflag = true; + bool prevchar_isitem = false; + + // out_p is the current position in the output buffer + char *out_p = out; + + // out_end_p is the last valid character in the output buffer + // Note: The null termination character must occur here or earlier, + // so any user-visible characters must occur before here. + char *out_end_p = (out + outlen) - 1; + + // Proceed character by character through the statusline format string + // fmt_p is the current position in the input buffer + for (char *fmt_p = usefmt; *fmt_p != NUL;) { + if (curitem == (int)stl_items_len) { + size_t new_len = stl_items_len * 3 / 2; + + stl_items = xrealloc(stl_items, sizeof(stl_item_t) * new_len); + stl_groupitems = xrealloc(stl_groupitems, sizeof(int) * new_len); + stl_hltab = xrealloc(stl_hltab, sizeof(stl_hlrec_t) * (new_len + 1)); + stl_tabtab = xrealloc(stl_tabtab, sizeof(StlClickRecord) * (new_len + 1)); + stl_separator_locations = + xrealloc(stl_separator_locations, sizeof(int) * new_len); + + stl_items_len = new_len; + } + + if (*fmt_p != '%') { + prevchar_isflag = prevchar_isitem = false; + } + + // Copy the formatting verbatim until we reach the end of the string + // or find a formatting item (denoted by `%`) + // or run out of room in our output buffer. + while (*fmt_p != NUL && *fmt_p != '%' && out_p < out_end_p) { + *out_p++ = *fmt_p++; + } + + // If we have processed the entire format string or run out of + // room in our output buffer, exit the loop. + if (*fmt_p == NUL || out_p >= out_end_p) { + break; + } + + // The rest of this loop will handle a single `%` item. + // Note: We increment here to skip over the `%` character we are currently + // on so we can process the item's contents. + fmt_p++; + + // Ignore `%` at the end of the format string + if (*fmt_p == NUL) { + break; + } + + // Two `%` in a row is the escape sequence to print a + // single `%` in the output buffer. + if (*fmt_p == '%') { + *out_p++ = *fmt_p++; + prevchar_isflag = prevchar_isitem = false; + continue; + } + + // STL_SEPARATE: Separation place between left and right aligned items. + if (*fmt_p == STL_SEPARATE) { + fmt_p++; + // Ignored when we are inside of a grouping + if (groupdepth > 0) { + continue; + } + stl_items[curitem].type = Separate; + stl_items[curitem++].start = out_p; + continue; + } + + // STL_TRUNCMARK: Where to begin truncating if the statusline is too long. + if (*fmt_p == STL_TRUNCMARK) { + fmt_p++; + stl_items[curitem].type = Trunc; + stl_items[curitem++].start = out_p; + continue; + } + + // The end of a grouping + if (*fmt_p == ')') { + fmt_p++; + // Ignore if we are not actually inside a group currently + if (groupdepth < 1) { + continue; + } + groupdepth--; + + // Determine how long the group is. + // Note: We set the current output position to null + // so `vim_strsize` will work. + char *t = stl_items[stl_groupitems[groupdepth]].start; + *out_p = NUL; + long group_len = vim_strsize(t); + + // If the group contained internal items + // and the group did not have a minimum width, + // and if there were no normal items in the group, + // move the output pointer back to where the group started. + // Note: This erases any non-item characters that were in the group. + // Otherwise there would be no reason to do this step. + if (curitem > stl_groupitems[groupdepth] + 1 + && stl_items[stl_groupitems[groupdepth]].minwid == 0) { + // remove group if all items are empty and highlight group + // doesn't change + int group_start_userhl = 0; + int group_end_userhl = 0; + int n; + for (n = stl_groupitems[groupdepth] - 1; n >= 0; n--) { + if (stl_items[n].type == Highlight) { + group_start_userhl = group_end_userhl = stl_items[n].minwid; + break; + } + } + for (n = stl_groupitems[groupdepth] + 1; n < curitem; n++) { + if (stl_items[n].type == Normal) { + break; + } + if (stl_items[n].type == Highlight) { + group_end_userhl = stl_items[n].minwid; + } + } + if (n == curitem && group_start_userhl == group_end_userhl) { + // empty group + out_p = t; + group_len = 0; + for (n = stl_groupitems[groupdepth] + 1; n < curitem; n++) { + // do not use the highlighting from the removed group + if (stl_items[n].type == Highlight) { + stl_items[n].type = Empty; + } + // adjust the start position of TabPage to the next + // item position + if (stl_items[n].type == TabPage) { + stl_items[n].start = out_p; + } + } + } + } + + // If the group is longer than it is allowed to be + // truncate by removing bytes from the start of the group text. + if (group_len > stl_items[stl_groupitems[groupdepth]].maxwid) { + // { Determine the number of bytes to remove + + // Find the first character that should be included. + long n = 0; + while (group_len >= stl_items[stl_groupitems[groupdepth]].maxwid) { + group_len -= ptr2cells(t + n); + n += utfc_ptr2len(t + n); + } + // } + + // Prepend the `<` to indicate that the output was truncated. + *t = '<'; + + // { Move the truncated output + memmove(t + 1, t + n, (size_t)(out_p - (t + n))); + out_p = out_p - n + 1; + // Fill up space left over by half a double-wide char. + while (++group_len < stl_items[stl_groupitems[groupdepth]].minwid) { + MB_CHAR2BYTES(fillchar, out_p); + } + // } + + // correct the start of the items for the truncation + for (int idx = stl_groupitems[groupdepth] + 1; idx < curitem; idx++) { + // Shift everything back by the number of removed bytes + // Minus one for the leading '<' added above. + stl_items[idx].start -= n - 1; + + // If the item was partially or completely truncated, set its + // start to the start of the group + if (stl_items[idx].start < t) { + stl_items[idx].start = t; + } + } + // 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; + // If the group is left-aligned, add characters to the right. + if (min_group_width < 0) { + min_group_width = 0 - min_group_width; + while (group_len++ < min_group_width && out_p < out_end_p) { + MB_CHAR2BYTES(fillchar, out_p); + } + // If the group is right-aligned, shift everything to the right and + // prepend with filler characters. + } else { + // { Move the group to the right + 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); + } + out_p += group_len; + // } + + // Adjust item start positions + for (int n = stl_groupitems[groupdepth] + 1; n < curitem; n++) { + stl_items[n].start += group_len; + } + + // Prepend the fill characters + for (; group_len > 0; group_len--) { + MB_CHAR2BYTES(fillchar, t); + } + } + } + continue; + } + int minwid = 0; + int maxwid = 9999; + bool left_align = false; + + // Denotes that numbers should be left-padded with zeros + bool zeropad = (*fmt_p == '0'); + if (zeropad) { + fmt_p++; + } + + // Denotes that the item should be left-aligned. + // This is tracked by using a negative length. + if (*fmt_p == '-') { + fmt_p++; + left_align = true; + } + + // The first digit group is the item's min width + if (ascii_isdigit(*fmt_p)) { + minwid = getdigits_int(&fmt_p, false, 0); + } + + // User highlight groups override the min width field + // to denote the styling to use. + if (*fmt_p == STL_USER_HL) { + stl_items[curitem].type = Highlight; + stl_items[curitem].start = out_p; + stl_items[curitem].minwid = minwid > 9 ? 1 : minwid; + fmt_p++; + curitem++; + continue; + } + + // TABPAGE pairs are used to denote a region that when clicked will + // either switch to or close a tab. + // + // Ex: tabline=%0Ttab\ zero%X + // This tabline has a TABPAGENR item with minwid `0`, + // which is then closed with a TABCLOSENR item. + // Clicking on this region with mouse enabled will switch to tab 0. + // Setting the minwid to a different value will switch + // to that tab, if it exists + // + // Ex: tabline=%1Xtab\ one%X + // This tabline has a TABCLOSENR item with minwid `1`, + // which is then closed with a TABCLOSENR item. + // Clicking on this region with mouse enabled will close tab 0. + // This is determined by the following formula: + // tab to close = (1 - minwid) + // This is because for TABPAGENR we use `minwid` = `tab number`. + // For TABCLOSENR we store the tab number as a negative value. + // Because 0 is a valid TABPAGENR value, we have to + // start our numbering at `-1`. + // So, `-1` corresponds to us wanting to close tab `0` + // + // Note: These options are only valid when creating a tabline. + if (*fmt_p == STL_TABPAGENR || *fmt_p == STL_TABCLOSENR) { + if (*fmt_p == STL_TABCLOSENR) { + if (minwid == 0) { + // %X ends the close label, go back to the previous tab label nr. + for (long n = curitem - 1; n >= 0; n--) { + if (stl_items[n].type == TabPage && stl_items[n].minwid >= 0) { + minwid = stl_items[n].minwid; + break; + } + } + } else { + // close nrs are stored as negative values + minwid = -minwid; + } + } + stl_items[curitem].type = TabPage; + stl_items[curitem].start = out_p; + stl_items[curitem].minwid = minwid; + fmt_p++; + curitem++; + continue; + } + + if (*fmt_p == STL_CLICK_FUNC) { + fmt_p++; + char *t = fmt_p; + while (*fmt_p != STL_CLICK_FUNC && *fmt_p) { + fmt_p++; + } + if (*fmt_p != STL_CLICK_FUNC) { + break; + } + 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].minwid = minwid; + fmt_p++; + curitem++; + continue; + } + + // Denotes the end of the minwid + // the maxwid may follow immediately after + if (*fmt_p == '.') { + fmt_p++; + if (ascii_isdigit(*fmt_p)) { + maxwid = getdigits_int(&fmt_p, false, 50); + } + } + + // Bound the minimum width at 50. + // Make the number negative to denote left alignment of the item + minwid = (minwid > 50 ? 50 : minwid) * (left_align ? -1 : 1); + + // Denotes the start of a new group + if (*fmt_p == '(') { + stl_groupitems[groupdepth++] = curitem; + stl_items[curitem].type = Group; + stl_items[curitem].start = out_p; + stl_items[curitem].minwid = minwid; + stl_items[curitem].maxwid = maxwid; + fmt_p++; + curitem++; + continue; + } + + // Denotes end of expanded %{} block + if (*fmt_p == '}' && evaldepth > 0) { + fmt_p++; + evaldepth--; + continue; + } + + // An invalid item was specified. + // Continue processing on the next character of the format string. + if (vim_strchr(STL_ALL, *fmt_p) == NULL) { + fmt_p++; + continue; + } + + // The status line item type + char opt = *fmt_p++; + + // OK - now for the real work + NumberBase base = kNumBaseDecimal; + bool itemisflag = false; + bool fillable = true; + long num = -1; + char *str = NULL; + switch (opt) { + case STL_FILEPATH: + case STL_FULLPATH: + case STL_FILENAME: + // Set fillable to false so that ' ' in the filename will not + // get replaced with the fillchar + fillable = false; + if (buf_spname(wp->w_buffer) != NULL) { + STRLCPY(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); + } + trans_characters((char *)NameBuff, MAXPATHL); + if (opt != STL_FILENAME) { + str = (char *)NameBuff; + } else { + str = path_tail((char *)NameBuff); + } + break; + case STL_VIM_EXPR: // '{' + { + char *block_start = fmt_p - 1; + int reevaluate = (*fmt_p == '%'); + itemisflag = true; + + if (reevaluate) { + fmt_p++; + } + + // Attempt to copy the expression to evaluate into + // the output buffer as a null-terminated string. + char *t = out_p; + while ((*fmt_p != '}' || (reevaluate && fmt_p[-1] != '%')) + && *fmt_p != NUL && out_p < out_end_p) { + *out_p++ = *fmt_p++; + } + if (*fmt_p != '}') { // missing '}' or out of space + break; + } + fmt_p++; + if (reevaluate) { + out_p[-1] = 0; // remove the % at the end of %{% expr %} + } else { + *out_p = 0; + } + + // Move our position in the output buffer + // to the beginning of the expression + out_p = t; + + // { Evaluate the expression + + // Store the current buffer number as a string variable + vim_snprintf(buf_tmp, sizeof(buf_tmp), "%d", curbuf->b_fnum); + set_internal_string_var("g:actual_curbuf", buf_tmp); + vim_snprintf((char *)win_tmp, sizeof(win_tmp), "%d", curwin->handle); + set_internal_string_var("g:actual_curwin", (char *)win_tmp); + + buf_T *const save_curbuf = curbuf; + win_T *const save_curwin = curwin; + const int save_VIsual_active = VIsual_active; + curwin = wp; + curbuf = wp->w_buffer; + // Visual mode is only valid in the current window. + if (curwin != save_curwin) { + VIsual_active = false; + } + + // Note: The result stored in `t` is unused. + str = eval_to_string_safe(out_p, &t, use_sandbox); + + curwin = save_curwin; + curbuf = save_curbuf; + VIsual_active = save_VIsual_active; + + // Remove the variable we just stored + do_unlet(S_LEN("g:actual_curbuf"), true); + do_unlet(S_LEN("g:actual_curwin"), true); + + // } + + // Check if the evaluated result is a number. + // If so, convert the number to an int and free the string. + if (str != NULL && *str != 0) { + if (*skipdigits(str) == NUL) { + num = atoi(str); + XFREE_CLEAR(str); + itemisflag = false; + } + } + + // If the output of the expression needs to be evaluated + // replace the %{} block with the result of evaluation + if (reevaluate && str != NULL && *str != 0 + && strchr((const char *)str, '%') != NULL + && evaldepth < MAX_STL_EVAL_DEPTH) { + size_t parsed_usefmt = (size_t)(block_start - usefmt); + size_t str_length = STRLEN(str); + size_t fmt_length = STRLEN(fmt_p); + size_t new_fmt_len = parsed_usefmt + str_length + fmt_length + 3; + char *new_fmt = xmalloc(new_fmt_len * sizeof(char)); + char *new_fmt_p = new_fmt; + + new_fmt_p = (char *)memcpy(new_fmt_p, usefmt, parsed_usefmt) + parsed_usefmt; + new_fmt_p = (char *)memcpy(new_fmt_p, str, str_length) + str_length; + new_fmt_p = (char *)memcpy(new_fmt_p, "%}", 2) + 2; + new_fmt_p = (char *)memcpy(new_fmt_p, fmt_p, fmt_length) + fmt_length; + *new_fmt_p = 0; + new_fmt_p = NULL; + + if (usefmt != fmt) { + xfree(usefmt); + } + XFREE_CLEAR(str); + usefmt = new_fmt; + fmt_p = usefmt + parsed_usefmt; + evaldepth++; + continue; + } + break; + } + + case STL_LINE: + num = (wp->w_buffer->b_ml.ml_flags & ML_EMPTY) + ? 0L : (long)(wp->w_cursor.lnum); + break; + + case STL_NUMLINES: + num = wp->w_buffer->b_ml.ml_line_count; + break; + + case STL_COLUMN: + num = (State & MODE_INSERT) == 0 && empty_line ? 0 : (int)wp->w_cursor.col + 1; + break; + + case STL_VIRTCOL: + case STL_VIRTCOL_ALT: { + colnr_T virtcol = wp->w_virtcol + 1; + // Don't display %V if it's the same as %c. + if (opt == STL_VIRTCOL_ALT + && (virtcol == (colnr_T)((State & MODE_INSERT) == 0 && empty_line + ? 0 : (int)wp->w_cursor.col + 1))) { + break; + } + num = (long)virtcol; + break; + } + + case STL_PERCENTAGE: + num = (int)(((long)wp->w_cursor.lnum * 100L) / + (long)wp->w_buffer->b_ml.ml_line_count); + break; + + case STL_ALTPERCENT: + // Store the position percentage in our temporary buffer. + // Note: We cannot store the value in `num` because + // `get_rel_pos` can return a named position. Ex: "Top" + get_rel_pos(wp, buf_tmp, TMPLEN); + str = buf_tmp; + break; + + case STL_ARGLISTSTAT: + fillable = false; + + // Note: This is important because `append_arg_number` starts appending + // at the end of the null-terminated string. + // Setting the first byte to null means it will place the argument + // number string at the beginning of the buffer. + buf_tmp[0] = 0; + + // Note: The call will only return true if it actually + // appended data to the `buf_tmp` buffer. + if (append_arg_number(wp, buf_tmp, (int)sizeof(buf_tmp), false)) { + str = buf_tmp; + } + break; + + case STL_KEYMAP: + fillable = false; + if (get_keymap_str(wp, "<%s>", buf_tmp, TMPLEN)) { + str = buf_tmp; + } + break; + case STL_PAGENUM: + num = printer_page_num; + break; + + case STL_BUFNO: + num = wp->w_buffer->b_fnum; + break; + + case STL_OFFSET_X: + base = kNumBaseHexadecimal; + FALLTHROUGH; + case STL_OFFSET: { + long l = ml_find_line_or_offset(wp->w_buffer, wp->w_cursor.lnum, NULL, + false); + num = (wp->w_buffer->b_ml.ml_flags & ML_EMPTY) || l < 0 ? + 0L : l + 1 + ((State & MODE_INSERT) == 0 && empty_line ? + 0 : (int)wp->w_cursor.col); + break; + } + case STL_BYTEVAL_X: + base = kNumBaseHexadecimal; + FALLTHROUGH; + case STL_BYTEVAL: + num = byteval; + if (num == NL) { + num = 0; + } else if (num == CAR && get_fileformat(wp->w_buffer) == EOL_MAC) { + num = NL; + } + break; + + case STL_ROFLAG: + case STL_ROFLAG_ALT: + itemisflag = true; + if (wp->w_buffer->b_p_ro) { + str = (opt == STL_ROFLAG_ALT) ? ",RO" : _("[RO]"); + } + break; + + case STL_HELPFLAG: + case STL_HELPFLAG_ALT: + itemisflag = true; + if (wp->w_buffer->b_help) { + str = (opt == STL_HELPFLAG_ALT) ? ",HLP" : _("[Help]"); + } + break; + + case STL_FILETYPE: + // Copy the filetype if it is not null and the formatted string will fit + // in the temporary buffer + // (including the brackets and null terminating character) + if (*wp->w_buffer->b_p_ft != NUL + && STRLEN(wp->w_buffer->b_p_ft) < TMPLEN - 3) { + vim_snprintf(buf_tmp, sizeof(buf_tmp), "[%s]", + wp->w_buffer->b_p_ft); + str = buf_tmp; + } + break; + + case STL_FILETYPE_ALT: + itemisflag = true; + // Copy the filetype if it is not null and the formatted string will fit + // in the temporary buffer + // (including the comma and null terminating character) + if (*wp->w_buffer->b_p_ft != NUL + && STRLEN(wp->w_buffer->b_p_ft) < TMPLEN - 2) { + 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); + } + str = buf_tmp; + } + break; + case STL_PREVIEWFLAG: + case STL_PREVIEWFLAG_ALT: + itemisflag = true; + if (wp->w_p_pvw) { + str = (opt == STL_PREVIEWFLAG_ALT) ? ",PRV" : _("[Preview]"); + } + break; + + case STL_QUICKFIX: + if (bt_quickfix(wp->w_buffer)) { + str = wp->w_llist_ref ? _(msg_loclist) : _(msg_qflist); + } + break; + + case STL_MODIFIED: + case STL_MODIFIED_ALT: + itemisflag = true; + switch ((opt == STL_MODIFIED_ALT) + + bufIsChanged(wp->w_buffer) * 2 + + (!MODIFIABLE(wp->w_buffer)) * 4) { + case 2: + str = "[+]"; break; + case 3: + str = ",+"; break; + case 4: + str = "[-]"; break; + case 5: + str = ",-"; break; + case 6: + str = "[+-]"; break; + case 7: + str = ",+-"; break; + } + break; + + case STL_HIGHLIGHT: { + // { The name of the highlight is surrounded by `#` + char *t = fmt_p; + while (*fmt_p != '#' && *fmt_p != NUL) { + fmt_p++; + } + // } + + // Create a highlight item based on the name + if (*fmt_p == '#') { + stl_items[curitem].type = Highlight; + stl_items[curitem].start = out_p; + stl_items[curitem].minwid = -syn_name2id_len(t, (size_t)(fmt_p - t)); + curitem++; + fmt_p++; + } + continue; + } + } + + // If we made it this far, the item is normal and starts at + // our current position in the output buffer. + // Non-normal items would have `continued`. + stl_items[curitem].start = out_p; + stl_items[curitem].type = Normal; + + // Copy the item string into the output buffer + if (str != NULL && *str) { + // { Skip the leading `,` or ` ` if the item is a flag + // and the proper conditions are met + char *t = str; + if (itemisflag) { + if ((t[0] && t[1]) + && ((!prevchar_isitem && *t == ',') + || (prevchar_isflag && *t == ' '))) { + t++; + } + prevchar_isflag = true; + } + // } + + long l = vim_strsize(t); + + // If this item is non-empty, record that the last thing + // we put in the output buffer was an item + if (l > 0) { + prevchar_isitem = true; + } + + // If the item is too wide, truncate it from the beginning + if (l > maxwid) { + while (l >= maxwid) { + l -= ptr2cells(t); + t += utfc_ptr2len(t); + } + + // Early out if there isn't enough room for the truncation marker + if (out_p >= out_end_p) { + break; + } + + // Add the truncation marker + *out_p++ = '<'; + } + + // If the item is right aligned and not wide enough, + // pad with fill characters. + if (minwid > 0) { + for (; l < minwid && out_p < out_end_p; l++) { + // Don't put a "-" in front of a digit. + if (l + 1 == minwid && fillchar == '-' && ascii_isdigit(*t)) { + *out_p++ = ' '; + } else { + MB_CHAR2BYTES(fillchar, out_p); + } + } + minwid = 0; + } else { + // Note: The negative value denotes a left aligned item. + // Here we switch the minimum width back to a positive value. + minwid *= -1; + } + + // { Copy the string text into the output buffer + for (; *t && out_p < out_end_p; t++) { + // Change a space by fillchar, unless fillchar is '-' and a + // digit follows. + if (fillable && *t == ' ' + && (!ascii_isdigit(*(t + 1)) || fillchar != '-')) { + MB_CHAR2BYTES(fillchar, out_p); + } else { + *out_p++ = *t; + } + } + // } + + // For left-aligned items, fill any remaining space with the fillchar + for (; l < minwid && out_p < out_end_p; l++) { + MB_CHAR2BYTES(fillchar, out_p); + } + + // Otherwise if the item is a number, copy that to the output buffer. + } else if (num >= 0) { + if (out_p + 20 > out_end_p) { + break; // not sufficient space + } + prevchar_isitem = true; + + // { Build the formatting string + char nstr[20]; + char *t = nstr; + if (opt == STL_VIRTCOL_ALT) { + *t++ = '-'; + minwid--; + } + *t++ = '%'; + if (zeropad) { + *t++ = '0'; + } + + // Note: The `*` means we take the width as one of the arguments + *t++ = '*'; + *t++ = base == kNumBaseHexadecimal ? 'X' : 'd'; + *t = 0; + // } + + // { Determine how many characters the number will take up when printed + // Note: We have to cast the base because the compiler uses + // unsigned ints for the enum values. + long num_chars = 1; + for (long n = num; n >= (int)base; n /= (int)base) { + num_chars++; + } + + // VIRTCOL_ALT takes up an extra character because + // of the `-` we added above. + if (opt == STL_VIRTCOL_ALT) { + num_chars++; + } + // } + + assert(out_end_p >= out_p); + size_t remaining_buf_len = (size_t)(out_end_p - out_p) + 1; + + // If the number is going to take up too much room + // Figure out the approximate number in "scientific" type notation. + // Ex: 14532 with maxwid of 4 -> '14>3' + if (num_chars > maxwid) { + // Add two to the width because the power piece will take + // two extra characters + num_chars += 2; + + // How many extra characters there are + long n = num_chars - maxwid; + + // { Reduce the number by base^n + while (num_chars-- > maxwid) { + num /= (long)base; + } + // } + + // { Add the format string for the exponent bit + *t++ = '>'; + *t++ = '%'; + // Use the same base as the first number + *t = t[-3]; + *++t = 0; + // } + + vim_snprintf(out_p, remaining_buf_len, nstr, 0, num, n); + } else { + vim_snprintf(out_p, remaining_buf_len, nstr, minwid, num); + } + + // Advance the output buffer position to the end of the + // number we just printed + out_p += STRLEN(out_p); + + // Otherwise, there was nothing to print so mark the item as empty + } else { + stl_items[curitem].type = Empty; + } + + // Only free the string buffer if we allocated it. + // Note: This is not needed if `str` is pointing at `tmp` + if (opt == STL_VIM_EXPR) { + XFREE_CLEAR(str); + } + + if (num >= 0 || (!itemisflag && str && *str)) { + prevchar_isflag = false; // Item not NULL, but not a flag + } + + // Item processed, move to the next + curitem++; + } + + *out_p = NUL; + int itemcnt = curitem; + + // Free the format buffer if we allocated it internally + if (usefmt != fmt) { + xfree(usefmt); + } + + // We have now processed the entire statusline format string. + // What follows is post-processing to handle alignment and highlighting. + + int width = vim_strsize(out); + if (maxwidth > 0 && width > maxwidth) { + // Result is too long, must truncate somewhere. + int item_idx = 0; + char *trunc_p; + + // If there are no items, truncate from beginning + if (itemcnt == 0) { + trunc_p = out; + + // Otherwise, look for the truncation item + } else { + // Default to truncating at the first item + trunc_p = stl_items[0].start; + item_idx = 0; + + for (int i = 0; i < itemcnt; i++) { + if (stl_items[i].type == Trunc) { + // Truncate at %< stl_items. + trunc_p = stl_items[i].start; + item_idx = i; + break; + } + } + } + + // If the truncation point we found is beyond the maximum + // length of the string, truncate the end of the string. + if (width - vim_strsize(trunc_p) >= maxwidth) { + // Walk from the beginning of the + // string to find the last character that will fit. + trunc_p = out; + width = 0; + for (;;) { + width += ptr2cells(trunc_p); + if (width >= maxwidth) { + break; + } + + // Note: Only advance the pointer if the next + // character will fit in the available output space + trunc_p += utfc_ptr2len(trunc_p); + } + + // Ignore any items in the statusline that occur after + // the truncation point + for (int i = 0; i < itemcnt; i++) { + if (stl_items[i].start > trunc_p) { + itemcnt = i; + break; + } + } + + // Truncate the output + *trunc_p++ = '>'; + *trunc_p = 0; + + // Truncate at the truncation point we found + } else { + // { Determine how many bytes to remove + long trunc_len = 0; + while (width >= maxwidth) { + width -= ptr2cells(trunc_p + trunc_len); + trunc_len += utfc_ptr2len(trunc_p + trunc_len); + } + // } + + // { Truncate the string + char *trunc_end_p = trunc_p + trunc_len; + STRMOVE(trunc_p + 1, trunc_end_p); + + // Put a `<` to mark where we truncated at + *trunc_p = '<'; + + if (width + 1 < maxwidth) { + // Advance the pointer to the end of the string + trunc_p = trunc_p + STRLEN(trunc_p); + } + + // Fill up for half a double-wide character. + while (++width < maxwidth) { + MB_CHAR2BYTES(fillchar, trunc_p); + *trunc_p = NUL; + } + // } + + // { Change the start point for items based on + // their position relative to our truncation point + + // Note: The offset is one less than the truncation length because + // the truncation marker `<` is not counted. + long item_offset = trunc_len - 1; + + for (int i = item_idx; i < itemcnt; i++) { + // Items starting at or after the end of the truncated section need + // to be moved backwards. + if (stl_items[i].start >= trunc_end_p) { + stl_items[i].start -= item_offset; + // Anything inside the truncated area is set to start + // at the `<` truncation character. + } else { + stl_items[i].start = trunc_p; + } + } + // } + } + width = maxwidth; + + // If there is room left in our statusline, and room left in our buffer, + // add characters at the separate marker (if there is one) to + // fill up the available space. + } else if (width < maxwidth + && STRLEN(out) + (size_t)(maxwidth - width) + 1 < outlen) { + // Find how many separators there are, which we will use when + // figuring out how many groups there are. + int num_separators = 0; + for (int i = 0; i < itemcnt; i++) { + if (stl_items[i].type == Separate) { + // Create an array of the start location for each + // separator mark. + stl_separator_locations[num_separators] = i; + num_separators++; + } + } + + // If we have separated groups, then we deal with it now + if (num_separators) { + int standard_spaces = (maxwidth - width) / num_separators; + int final_spaces = (maxwidth - width) - + standard_spaces * (num_separators - 1); + + for (int i = 0; i < num_separators; i++) { + int dislocation = (i == (num_separators - 1)) ? final_spaces : standard_spaces; + dislocation *= utf_char2len(fillchar); + char *start = stl_items[stl_separator_locations[i]].start; + char *seploc = start + dislocation; + STRMOVE(seploc, start); + for (char *s = start; s < seploc;) { + MB_CHAR2BYTES(fillchar, s); + } + + for (int item_idx = stl_separator_locations[i] + 1; + item_idx < itemcnt; + item_idx++) { + stl_items[item_idx].start += dislocation; + } + } + + width = maxwidth; + } + } + + // Store the info about highlighting. + if (hltab != NULL) { + *hltab = stl_hltab; + stl_hlrec_t *sp = stl_hltab; + for (long l = 0; l < itemcnt; l++) { + if (stl_items[l].type == Highlight) { + sp->start = stl_items[l].start; + sp->userhl = stl_items[l].minwid; + sp++; + } + } + sp->start = NULL; + sp->userhl = 0; + } + + // Store the info about tab pages labels. + if (tabtab != NULL) { + *tabtab = stl_tabtab; + StlClickRecord *cur_tab_rec = stl_tabtab; + for (long l = 0; l < itemcnt; l++) { + if (stl_items[l].type == TabPage) { + cur_tab_rec->start = stl_items[l].start; + if (stl_items[l].minwid == 0) { + cur_tab_rec->def.type = kStlClickDisabled; + cur_tab_rec->def.tabnr = 0; + } else { + int tabnr = stl_items[l].minwid; + if (stl_items[l].minwid > 0) { + cur_tab_rec->def.type = kStlClickTabSwitch; + } else { + cur_tab_rec->def.type = kStlClickTabClose; + tabnr = -tabnr; + } + cur_tab_rec->def.tabnr = tabnr; + } + cur_tab_rec->def.func = NULL; + cur_tab_rec++; + } else if (stl_items[l].type == ClickFunc) { + cur_tab_rec->start = stl_items[l].start; + cur_tab_rec->def.type = kStlClickFuncRun; + cur_tab_rec->def.tabnr = stl_items[l].minwid; + cur_tab_rec->def.func = stl_items[l].cmd; + cur_tab_rec++; + } + } + cur_tab_rec->start = NULL; + cur_tab_rec->def.type = kStlClickDisabled; + cur_tab_rec->def.tabnr = 0; + cur_tab_rec->def.func = NULL; + } + + // When inside update_screen we do not want redrawing a statusline, ruler, + // title, etc. to trigger another redraw, it may cause an endless loop. + if (updating_screen) { + must_redraw = save_must_redraw; + curwin->w_redr_type = save_redr_type; + } + + return width; +} diff --git a/src/nvim/statusline.h b/src/nvim/statusline.h new file mode 100644 index 0000000000..357a9a821f --- /dev/null +++ b/src/nvim/statusline.h @@ -0,0 +1,10 @@ +#ifndef NVIM_STATUSLINE_H +#define NVIM_STATUSLINE_H + +#include "nvim/buffer_defs.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "statusline.h.generated.h" +#endif + +#endif // NVIM_STATUSLINE_H diff --git a/src/nvim/strings.c b/src/nvim/strings.c index 22effaade0..16691d0ded 100644 --- a/src/nvim/strings.c +++ b/src/nvim/strings.c @@ -104,10 +104,10 @@ char_u *vim_strsave_escaped_ext(const char_u *string, const char_u *esc_chars, c p += l - 1; continue; } - if (vim_strchr((char *)esc_chars, *p) != NULL || (bsl && rem_backslash(p))) { + if (vim_strchr((char *)esc_chars, *p) != NULL || (bsl && rem_backslash((char *)p))) { length++; // count a backslash } - ++length; // count an ordinary char + length++; // count an ordinary char } char_u *escaped_string = xmalloc(length); @@ -120,7 +120,7 @@ char_u *vim_strsave_escaped_ext(const char_u *string, const char_u *esc_chars, c p += l - 1; // skip multibyte char continue; } - if (vim_strchr((char *)esc_chars, *p) != NULL || (bsl && rem_backslash(p))) { + if (vim_strchr((char *)esc_chars, *p) != NULL || (bsl && rem_backslash((char *)p))) { *p2++ = cc; } *p2++ = *p; @@ -191,7 +191,7 @@ char *vim_strnsave_unquoted(const char *const string, const size_t length) char_u *vim_strsave_shellescape(const char_u *string, bool do_special, bool do_newline) FUNC_ATTR_NONNULL_RET FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_ALL { - char_u *d; + char *d; char_u *escaped_string; size_t l; int csh_like; @@ -222,13 +222,13 @@ char_u *vim_strsave_shellescape(const char_u *string, bool do_special, bool do_n } if ((*p == '\n' && (csh_like || do_newline)) || (*p == '!' && (csh_like || do_special))) { - ++length; // insert backslash + length++; // insert backslash if (csh_like && do_special) { - ++length; // insert backslash + length++; // insert backslash } } if (do_special && find_cmdline_var(p, &l) >= 0) { - ++length; // insert backslash + length++; // insert backslash p += l - 1; } if (*p == '\\' && fish_like) { @@ -238,7 +238,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 = escaped_string; + d = (char *)escaped_string; // add opening quote #ifdef WIN32 @@ -248,7 +248,7 @@ char_u *vim_strsave_shellescape(const char_u *string, bool do_special, bool do_n #endif *d++ = '\''; - for (const char_u *p = string; *p != NUL;) { + for (const char *p = (char *)string; *p != NUL;) { #ifdef WIN32 if (!p_ssl) { if (*p == '"') { @@ -264,7 +264,7 @@ char_u *vim_strsave_shellescape(const char_u *string, bool do_special, bool do_n *d++ = '\\'; *d++ = '\''; *d++ = '\''; - ++p; + p++; continue; } if ((*p == '\n' && (csh_like || do_newline)) @@ -276,7 +276,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(p, &l) >= 0) { + if (do_special && find_cmdline_var((char_u *)p, &l) >= 0) { *d++ = '\\'; // insert backslash while (--l != SIZE_MAX) { // copy the var *d++ = *p++; @@ -431,8 +431,8 @@ int vim_stricmp(const char *s1, const char *s2) if (*s1 == NUL) { break; // strings match until NUL } - ++s1; - ++s2; + s1++; + s2++; } return 0; // strings match } @@ -457,9 +457,9 @@ int vim_strnicmp(const char *s1, const char *s2, size_t len) if (*s1 == NUL) { break; // strings match until NUL } - ++s1; - ++s2; - --len; + s1++; + s2++; + len--; } return 0; // strings match } @@ -503,7 +503,7 @@ static int sort_compare(const void *s1, const void *s2) void sort_strings(char **files, int count) { - qsort((void *)files, (size_t)count, sizeof(char_u *), sort_compare); + qsort((void *)files, (size_t)count, sizeof(char *), sort_compare); } /* @@ -516,7 +516,7 @@ bool has_non_ascii(const char_u *s) const char_u *p; if (s != NULL) { - for (p = s; *p != NUL; ++p) { + for (p = s; *p != NUL; p++) { if (*p >= 128) { return true; } @@ -540,14 +540,12 @@ bool has_non_ascii_len(const char *const s, const size_t len) return false; } -/* - * Concatenate two strings and return the result in allocated memory. - */ -char_u *concat_str(const char_u *restrict str1, const char_u *restrict str2) +/// Concatenate two strings and return the result in allocated memory. +char *concat_str(const char *restrict str1, const char *restrict str2) FUNC_ATTR_NONNULL_RET FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_ALL { size_t l = STRLEN(str1); - char_u *dest = xmalloc(l + STRLEN(str2) + 1); + char *dest = xmalloc(l + STRLEN(str2) + 1); STRCPY(dest, str1); STRCPY(dest + l, str2); return dest; @@ -1511,15 +1509,15 @@ int kv_do_printf(StringBuilder *str, const char *fmt, ...) /// Reverse text into allocated memory. /// /// @return the allocated string. -char_u *reverse_text(char_u *s) +char *reverse_text(char *s) FUNC_ATTR_NONNULL_RET { // Reverse the pattern. size_t len = STRLEN(s); - char_u *rev = xmalloc(len + 1); + char *rev = xmalloc(len + 1); size_t rev_i = len; for (size_t s_i = 0; s_i < len; s_i++) { - const int mb_len = utfc_ptr2len((char *)s + s_i); + const int mb_len = utfc_ptr2len(s + s_i); rev_i -= (size_t)mb_len; memmove(rev + rev_i, s + s_i, (size_t)mb_len); s_i += (size_t)mb_len - 1; diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index 4ec4a57d68..f055cc9b0e 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -14,12 +14,13 @@ #include "nvim/api/private/helpers.h" #include "nvim/ascii.h" +#include "nvim/autocmd.h" #include "nvim/buffer.h" #include "nvim/charset.h" #include "nvim/cursor_shape.h" +#include "nvim/drawscreen.h" #include "nvim/eval.h" #include "nvim/eval/vars.h" -#include "nvim/ex_cmds2.h" #include "nvim/ex_docmd.h" #include "nvim/fileio.h" #include "nvim/fold.h" @@ -36,13 +37,14 @@ #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/os/time.h" #include "nvim/os_unix.h" #include "nvim/path.h" +#include "nvim/profile.h" #include "nvim/regexp.h" -#include "nvim/screen.h" #include "nvim/sign.h" #include "nvim/strings.h" #include "nvim/syntax.h" @@ -378,7 +380,7 @@ void syntax_start(win_T *wp, linenr_T lnum) && current_lnum < syn_buf->b_ml.ml_line_count) { (void)syn_finish_line(false); if (!current_state_stored) { - ++current_lnum; + current_lnum++; (void)store_current_state(); } @@ -607,7 +609,7 @@ 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) { + for (; start_lnum > 1; start_lnum--) { line = ml_get(start_lnum - 1); if (*line == NUL || *(line + STRLEN(line) - 1) != '\\') { break; @@ -685,7 +687,7 @@ static void syn_sync(win_T *wp, linenr_T start_lnum, synstate_T *last_valid) */ validate_current_state(); - for (current_lnum = lnum; current_lnum < end_lnum; ++current_lnum) { + for (current_lnum = lnum; current_lnum < end_lnum; current_lnum++) { syn_start_line(); for (;;) { had_sync_point = syn_finish_line(true); @@ -723,7 +725,7 @@ static void syn_sync(win_T *wp, linenr_T start_lnum, synstate_T *last_valid) } else if (found_m_endpos.col > current_col) { current_col = found_m_endpos.col; } else { - ++current_col; + current_col++; } // syn_current_attr() will have skipped the check for @@ -731,7 +733,7 @@ static void syn_sync(win_T *wp, linenr_T start_lnum, synstate_T *last_valid) // careful not to go past the NUL. prev_current_col = current_col; if (syn_getcurline()[current_col] != NUL) { - ++current_col; + current_col++; } check_state_ends(); current_col = prev_current_col; @@ -810,9 +812,7 @@ static void restore_chartab(char_u *chartab) } } -/* - * Return TRUE if the line-continuation pattern matches in line "lnum". - */ +/// 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) { @@ -830,7 +830,7 @@ static int syn_match_linecont(linenr_T lnum) restore_chartab(buf_chartab); return r; } - return FALSE; + return false; } /* @@ -876,7 +876,7 @@ static void syn_update_ends(bool startofline) cur_si->si_m_endpos.lnum = 0; cur_si->si_m_endpos.col = 0; cur_si->si_h_endpos = cur_si->si_m_endpos; - cur_si->si_ends = TRUE; + cur_si->si_ends = true; } } } @@ -892,7 +892,7 @@ static void syn_update_ends(bool startofline) */ int i = current_state.ga_len - 1; if (keepend_level >= 0) { - for (; i > keepend_level; --i) { + for (; i > keepend_level; i--) { if (CUR_STATE(i).si_flags & HL_EXTEND) { break; } @@ -1029,7 +1029,7 @@ static void syn_stack_alloc(void) // Move the states from the old array to the new one. for (from = syn_block->b_sst_first; from != NULL; from = from->sst_next) { - ++to; + to++; *to = *from; to->sst_next = to + 1; } @@ -1184,7 +1184,7 @@ static void syn_stack_free_entry(synblock_T *block, synstate_T *p) clear_syn_state(p); p->sst_next = block->b_sst_firstfree; block->b_sst_firstfree = p; - ++block->b_sst_freecount; + block->b_sst_freecount++; } /* @@ -1223,7 +1223,7 @@ static synstate_T *store_current_state(void) * If the current state contains a start or end pattern that continues * from the previous line, we can't use it. Don't store it then. */ - for (i = current_state.ga_len - 1; i >= 0; --i) { + for (i = current_state.ga_len - 1; i >= 0; i--) { cur_si = &CUR_STATE(i); if (cur_si->si_h_startpos.lnum >= current_lnum || cur_si->si_m_endpos.lnum >= current_lnum @@ -1271,7 +1271,7 @@ static synstate_T *store_current_state(void) // list, after *sp p = syn_block->b_sst_firstfree; syn_block->b_sst_firstfree = p->sst_next; - --syn_block->b_sst_freecount; + syn_block->b_sst_freecount--; if (sp == NULL) { // Insert in front of the list p->sst_next = syn_block->b_sst_first; @@ -1300,7 +1300,7 @@ static synstate_T *store_current_state(void) } else { bp = sp->sst_union.sst_stack; } - for (i = 0; i < sp->sst_stacksize; ++i) { + for (i = 0; i < sp->sst_stacksize; i++) { bp[i].bs_idx = CUR_STATE(i).si_idx; bp[i].bs_flags = (int)CUR_STATE(i).si_flags; bp[i].bs_seqnr = CUR_STATE(i).si_seqnr; @@ -1334,7 +1334,7 @@ static void load_current_state(synstate_T *from) } else { bp = from->sst_union.sst_stack; } - for (i = 0; i < from->sst_stacksize; ++i) { + for (i = 0; i < from->sst_stacksize; i++) { CUR_STATE(i).si_idx = bp[i].bs_idx; CUR_STATE(i).si_flags = bp[i].bs_flags; CUR_STATE(i).si_seqnr = bp[i].bs_seqnr; @@ -1343,7 +1343,7 @@ static void load_current_state(synstate_T *from) if (keepend_level < 0 && (CUR_STATE(i).si_flags & HL_KEEPEND)) { keepend_level = i; } - CUR_STATE(i).si_ends = FALSE; + CUR_STATE(i).si_ends = false; CUR_STATE(i).si_m_lnum = 0; if (CUR_STATE(i).si_idx >= 0) { CUR_STATE(i).si_next_list = @@ -1500,7 +1500,7 @@ bool syntax_check_changed(linenr_T lnum) /* * Store the current state in b_sst_array[] for later use. */ - ++current_lnum; + current_lnum++; (void)store_current_state(); } } @@ -1549,7 +1549,7 @@ static bool syn_finish_line(const bool syncing) /// "col" is normally 0 for the first use in a line, and increments by one each /// time. It's allowed to skip characters and to stop before the end of the /// line. But only a "col" after a previously used column is allowed. -/// When "can_spell" is not NULL set it to TRUE when spell-checking should be +/// When "can_spell" is not NULL set it to true when spell-checking should be /// done. /// /// @param keep_state keep state of char at "col" @@ -1634,11 +1634,9 @@ static int syn_current_attr(const bool syncing, const bool displaying, bool *con bool zero_width_next_list = false; garray_T zero_width_next_ga; - /* - * No character, no attributes! Past end of line? - * Do try matching with an empty line (could be the start of a region). - */ - line = syn_getcurline(); + // 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(); if (line[current_col] == NUL && current_col != 0) { /* * If we found a match after the last column, use it. @@ -1706,12 +1704,10 @@ static int syn_current_attr(const bool syncing, const bool displaying, bool *con if (syn_block->b_syn_containedin || cur_si == NULL || cur_si->si_cont_list != NULL) { - /* - * 2. Check for keywords, if on a keyword char after a non-keyword - * char. Don't do this when syncing. - */ + // 2. Check for keywords, if on a keyword char after a non-keyword + // char. Don't do this when syncing. if (do_keywords) { - line = syn_getcurline(); + line = (char_u *)syn_getcurline(); const char_u *cur_pos = line + current_col; if (vim_iswordp_buf(cur_pos, syn_buf) && (current_col == 0 @@ -1730,7 +1726,7 @@ static int syn_current_attr(const bool syncing, const bool displaying, bool *con cur_si->si_m_endpos.col = endcol; cur_si->si_h_endpos.lnum = current_lnum; cur_si->si_h_endpos.col = endcol; - cur_si->si_ends = TRUE; + cur_si->si_ends = true; cur_si->si_end_idx = 0; cur_si->si_flags = flags; cur_si->si_seqnr = next_seqnr++; @@ -1979,13 +1975,11 @@ static int syn_current_attr(const bool syncing, const bool displaying, bool *con * Handle searching for nextgroup match. */ if (current_next_list != NULL && !keep_next_list) { - /* - * If a nextgroup was not found, continue looking for one if: - * - this is an empty line and the "skipempty" option was given - * - we are on white space and the "skipwhite" option was given - */ + // If a nextgroup was not found, continue looking for one if: + // - 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 = syn_getcurline(); + line = (char_u *)syn_getcurline(); if (((current_next_flags & HL_SKIPWHITE) && ascii_iswhite(line[current_col])) || ((current_next_flags & HL_SKIPEMPTY) @@ -2022,7 +2016,7 @@ static int syn_current_attr(const bool syncing, const bool displaying, bool *con current_flags = 0; current_seqnr = 0; if (cur_si != NULL) { - for (int idx = current_state.ga_len - 1; idx >= 0; --idx) { + for (int idx = current_state.ga_len - 1; idx >= 0; idx--) { sip = &CUR_STATE(idx); if ((current_lnum > sip->si_h_startpos.lnum || (current_lnum == sip->si_h_startpos.lnum @@ -2044,10 +2038,8 @@ static int syn_current_attr(const bool syncing, const bool displaying, bool *con if (can_spell != NULL) { struct sp_syn sps; - /* - * set "can_spell" to TRUE if spell checking is supposed to be - * done in the current item. - */ + // set "can_spell" to true if spell checking is supposed to be + // done in the current item. if (syn_block->b_spell_cluster_id == 0) { // There is no @Spell cluster: Do spelling for items without // @NoSpell cluster. @@ -2095,9 +2087,9 @@ static int syn_current_attr(const bool syncing, const bool displaying, bool *con check_state_ends(); if (!GA_EMPTY(¤t_state) && syn_getcurline()[current_col] != NUL) { - ++current_col; + current_col++; check_state_ends(); - --current_col; + current_col--; } } } else if (can_spell != NULL) { @@ -2110,7 +2102,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 = syn_getcurline())[current_col] != NUL + && (line = (char_u *)syn_getcurline())[current_col] != NUL && line[current_col + 1] == NUL && !(current_next_flags & (HL_SKIPNL | HL_SKIPEMPTY))) { current_next_list = NULL; @@ -2190,7 +2182,7 @@ static stateitem_T *push_next_match(void) } else { cur_si->si_m_endpos = next_match_m_endpos; cur_si->si_h_endpos = next_match_h_endpos; - cur_si->si_ends = TRUE; + cur_si->si_ends = true; cur_si->si_flags |= next_match_flags; cur_si->si_eoe_pos = next_match_eoe_pos; cur_si->si_end_idx = next_match_end_idx; @@ -2214,7 +2206,7 @@ static stateitem_T *push_next_match(void) cur_si->si_m_lnum = current_lnum; cur_si->si_m_endpos = next_match_eos_pos; cur_si->si_h_endpos = next_match_eos_pos; - cur_si->si_ends = TRUE; + cur_si->si_ends = true; cur_si->si_end_idx = 0; cur_si->si_flags = HL_MATCH; cur_si->si_seqnr = next_seqnr++; @@ -2404,7 +2396,7 @@ static void check_keepend(void) * won't do anything. If there is no "extend" item "i" will be * "keepend_level" and all "keepend" items will work normally. */ - for (i = current_state.ga_len - 1; i > keepend_level; --i) { + for (i = current_state.ga_len - 1; i > keepend_level; i--) { if (CUR_STATE(i).si_flags & HL_EXTEND) { break; } @@ -2414,13 +2406,13 @@ static void check_keepend(void) maxpos.col = 0; maxpos_h.lnum = 0; maxpos_h.col = 0; - for (; i < current_state.ga_len; ++i) { + for (; i < current_state.ga_len; i++) { sip = &CUR_STATE(i); if (maxpos.lnum != 0) { limit_pos_zero(&sip->si_m_endpos, &maxpos); limit_pos_zero(&sip->si_h_endpos, &maxpos_h); limit_pos_zero(&sip->si_eoe_pos, &maxpos); - sip->si_ends = TRUE; + sip->si_ends = true; } if (sip->si_ends && (sip->si_flags & HL_KEEPEND)) { if (maxpos.lnum == 0 @@ -2480,12 +2472,12 @@ static void update_si_end(stateitem_T *sip, int startcol, bool force) // No end pattern matched. if (SYN_ITEMS(syn_block)[sip->si_idx].sp_flags & HL_ONELINE) { // a "oneline" never continues in the next line - sip->si_ends = TRUE; + sip->si_ends = true; sip->si_m_endpos.lnum = current_lnum; sip->si_m_endpos.col = (colnr_T)STRLEN(syn_getcurline()); } else { // continues in the next line - sip->si_ends = FALSE; + sip->si_ends = false; sip->si_m_endpos.lnum = 0; } sip->si_h_endpos = sip->si_m_endpos; @@ -2494,7 +2486,7 @@ static void update_si_end(stateitem_T *sip, int startcol, bool force) sip->si_m_endpos = endpos; sip->si_h_endpos = hl_endpos; sip->si_eoe_pos = end_endpos; - sip->si_ends = TRUE; + sip->si_ends = true; sip->si_end_idx = end_idx; } } @@ -2506,7 +2498,7 @@ static void update_si_end(stateitem_T *sip, int startcol, bool force) static void push_current_state(int idx) { stateitem_T *p = GA_APPEND_VIA_PTR(stateitem_T, ¤t_state); - memset(p, 0, sizeof(*p)); + CLEAR_POINTER(p); p->si_idx = idx; } @@ -2517,7 +2509,7 @@ static void pop_current_state(void) { if (!GA_EMPTY(¤t_state)) { unref_extmatch(CUR_STATE(current_state.ga_len - 1).si_extmatch); - --current_state.ga_len; + current_state.ga_len--; } // after the end of a pattern, try matching a keyword or pattern next_match_idx = -1; @@ -2582,7 +2574,7 @@ static void find_endpos(int idx, lpos_T *startpos, lpos_T *m_endpos, lpos_T *hl_ if (spp->sp_type != SPTYPE_START) { break; } - ++idx; + idx++; } /* @@ -2590,7 +2582,7 @@ static void find_endpos(int idx, lpos_T *startpos, lpos_T *m_endpos, lpos_T *hl_ */ if (spp->sp_type == SPTYPE_SKIP) { spp_skip = spp; - ++idx; + idx++; } else { spp_skip = NULL; } @@ -2611,7 +2603,7 @@ static void find_endpos(int idx, lpos_T *startpos, lpos_T *m_endpos, lpos_T *hl_ * Find end pattern that matches first after "matchcol". */ best_idx = -1; - for (idx = start_idx; idx < syn_block->b_syn_patterns.ga_len; ++idx) { + for (idx = start_idx; idx < syn_block->b_syn_patterns.ga_len; idx++) { int lc_col = matchcol; spp = &(SYN_ITEMS(syn_block)[idx]); @@ -2876,17 +2868,15 @@ static void syn_add_start_off(lpos_T *result, regmmatch_T *regmatch, synpat_T *s result->col = col; } -/* - * Get current line in syntax buffer. - */ -static char_u *syn_getcurline(void) +/// Get current line in syntax buffer. +static char *syn_getcurline(void) { - return ml_get_buf(syn_buf, current_lnum, false); + return (char *)ml_get_buf(syn_buf, current_lnum, false); } /* * Call vim_regexec() to find a match with "rmp" in "syn_buf". - * Returns TRUE when there is a match. + * Returns true when there is a match. */ static int syn_regexec(regmmatch_T *rmp, linenr_T lnum, colnr_T col, syn_time_T *st) { @@ -2914,9 +2904,9 @@ static int syn_regexec(regmmatch_T *rmp, linenr_T lnum, colnr_T col, syn_time_T if (profile_cmp(pt, st->slowest) < 0) { st->slowest = pt; } - ++st->count; + st->count++; if (r > 0) { - ++st->match; + st->match++; } } if (timed_out && !syn_win->w_s->b_syn_slow) { @@ -2927,9 +2917,9 @@ static int syn_regexec(regmmatch_T *rmp, linenr_T lnum, colnr_T col, syn_time_T if (r > 0) { rmp->startpos[0].lnum += lnum; rmp->endpos[0].lnum += lnum; - return TRUE; + return true; } - return FALSE; + return false; } /// Check one position in a line for a matching keyword. @@ -3018,12 +3008,12 @@ static void syn_cmd_conceal(exarg_T *eap, int syncing) char_u *arg = (char_u *)eap->arg; char_u *next; - eap->nextcmd = (char *)find_nextcmd(arg); + eap->nextcmd = find_nextcmd((char *)arg); if (eap->skip) { return; } - next = skiptowhite(arg); + next = (char_u *)skiptowhite((char *)arg); if (*arg == NUL) { if (curwin->w_s->b_syn_conceal) { msg("syntax conceal on"); @@ -3047,12 +3037,12 @@ static void syn_cmd_case(exarg_T *eap, int syncing) char_u *arg = (char_u *)eap->arg; char_u *next; - eap->nextcmd = (char *)find_nextcmd(arg); + eap->nextcmd = find_nextcmd((char *)arg); if (eap->skip) { return; } - next = skiptowhite(arg); + next = (char_u *)skiptowhite((char *)arg); if (*arg == NUL) { if (curwin->w_s->b_syn_ic) { msg("syntax case ignore"); @@ -3074,7 +3064,7 @@ static void syn_cmd_foldlevel(exarg_T *eap, int syncing) char_u *arg = (char_u *)eap->arg; char_u *arg_end; - eap->nextcmd = (char *)find_nextcmd(arg); + eap->nextcmd = find_nextcmd((char *)arg); if (eap->skip) { return; } @@ -3091,7 +3081,7 @@ static void syn_cmd_foldlevel(exarg_T *eap, int syncing) return; } - arg_end = skiptowhite(arg); + arg_end = (char_u *)skiptowhite((char *)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) { @@ -3115,12 +3105,12 @@ static void syn_cmd_spell(exarg_T *eap, int syncing) char_u *arg = (char_u *)eap->arg; char_u *next; - eap->nextcmd = (char *)find_nextcmd(arg); + eap->nextcmd = find_nextcmd((char *)arg); if (eap->skip) { return; } - next = skiptowhite(arg); + next = (char_u *)skiptowhite((char *)arg); if (*arg == NUL) { if (curwin->w_s->b_syn_spell == SYNSPL_TOP) { msg("syntax spell toplevel"); @@ -3141,7 +3131,7 @@ static void syn_cmd_spell(exarg_T *eap, int syncing) } // assume spell checking changed, force a redraw - redraw_later(curwin, NOT_VALID); + redraw_later(curwin, UPD_NOT_VALID); } /// Handle ":syntax iskeyword" command. @@ -3160,7 +3150,7 @@ static void syn_cmd_iskeyword(exarg_T *eap, int syncing) msg_puts("\n"); if (curwin->w_s->b_syn_isk != empty_option) { msg_puts("syntax iskeyword "); - msg_outtrans((char *)curwin->w_s->b_syn_isk); + msg_outtrans(curwin->w_s->b_syn_isk); } else { msg_outtrans(_("syntax iskeyword not set")); } @@ -3170,18 +3160,18 @@ static void syn_cmd_iskeyword(exarg_T *eap, int syncing) clear_string_option(&curwin->w_s->b_syn_isk); } else { memmove(save_chartab, curbuf->b_chartab, (size_t)32); - save_isk = curbuf->b_p_isk; - curbuf->b_p_isk = vim_strsave(arg); + save_isk = (char_u *)curbuf->b_p_isk; + curbuf->b_p_isk = (char *)vim_strsave(arg); buf_init_chartab(curbuf, false); memmove(curwin->w_s->b_syn_chartab, curbuf->b_chartab, (size_t)32); memmove(curbuf->b_chartab, save_chartab, (size_t)32); clear_string_option(&curwin->w_s->b_syn_isk); curwin->w_s->b_syn_isk = curbuf->b_p_isk; - curbuf->b_p_isk = save_isk; + curbuf->b_p_isk = (char *)save_isk; } } - redraw_later(curwin, NOT_VALID); + redraw_later(curwin, UPD_NOT_VALID); } /* @@ -3280,7 +3270,7 @@ static void syn_remove_pattern(synblock_T *block, int idx) spp = &(SYN_ITEMS(block)[idx]); if (spp->sp_flags & HL_FOLD) { - --block->b_syn_folditems; + block->b_syn_folditems--; } syn_clear_pattern(block, idx); memmove(spp, spp + 1, sizeof(synpat_T) * (size_t)(block->b_syn_patterns.ga_len - idx - 1)); @@ -3322,7 +3312,7 @@ static void syn_cmd_clear(exarg_T *eap, int syncing) char_u *arg_end; int id; - eap->nextcmd = (char *)find_nextcmd(arg); + eap->nextcmd = find_nextcmd((char *)arg); if (eap->skip) { return; } @@ -3355,7 +3345,7 @@ static void syn_cmd_clear(exarg_T *eap, int syncing) * Clear the group IDs that are in the argument. */ while (!ends_excmd(*arg)) { - arg_end = skiptowhite(arg); + arg_end = (char_u *)skiptowhite((char *)arg); if (*arg == '@') { id = syn_scl_namen2id(arg + 1, (int)(arg_end - arg - 1)); if (id == 0) { @@ -3381,7 +3371,7 @@ static void syn_cmd_clear(exarg_T *eap, int syncing) arg = (char_u *)skipwhite((char *)arg_end); } } - redraw_curbuf_later(SOME_VALID); + redraw_curbuf_later(UPD_SOME_VALID); syn_stack_free_all(curwin->w_s); // Need to recompute all syntax. } @@ -3469,13 +3459,13 @@ void syn_maybe_enable(void) /// Handle ":syntax [list]" command: list current syntax words. /// -/// @param syncing when TRUE: list syncing items +/// @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; - eap->nextcmd = (char *)find_nextcmd(arg); + eap->nextcmd = find_nextcmd((char *)arg); if (eap->skip) { return; } @@ -3524,7 +3514,7 @@ static void syn_cmd_list(exarg_T *eap, int syncing) for (int id = 1; id <= highlight_num_groups() && !got_int; id++) { syn_list_one(id, syncing, false); } - for (int id = 0; id < curwin->w_s->b_syn_clusters.ga_len && !got_int; ++id) { + for (int id = 0; id < curwin->w_s->b_syn_clusters.ga_len && !got_int; id++) { syn_list_cluster(id); } } else { @@ -3532,7 +3522,7 @@ static void syn_cmd_list(exarg_T *eap, int syncing) * List the group IDs and syntax clusters that are in the argument. */ while (!ends_excmd(*arg) && !got_int) { - arg_end = skiptowhite(arg); + arg_end = (char_u *)skiptowhite((char *)arg); if (*arg == '@') { int id = syn_scl_namen2id(arg + 1, (int)(arg_end - arg - 1)); if (id == 0) { @@ -3653,7 +3643,7 @@ static void syn_list_one(const int id, const bool syncing, const bool link_only) && SYN_ITEMS(curwin->w_s)[idx].sp_type == SPTYPE_END) { put_pattern("end", '=', &SYN_ITEMS(curwin->w_s)[idx++], attr); } - --idx; + idx--; msg_putchar(' '); } syn_list_flags(namelist1, spp->sp_flags, attr); @@ -3700,7 +3690,7 @@ static void syn_list_flags(struct name_list *nlist, int flags, int attr) { int i; - for (i = 0; nlist[i].flag != 0; ++i) { + for (i = 0; nlist[i].flag != 0; i++) { if (flags & nlist[i].flag) { msg_puts_attr(nlist[i].name, attr); msg_putchar(' '); @@ -3923,11 +3913,11 @@ static void syn_clear_keyword(int id, hashtab_T *ht) hash_lock(ht); todo = (int)ht->ht_used; - for (hi = ht->ht_array; todo > 0; ++hi) { + for (hi = ht->ht_array; todo > 0; hi++) { if (HASHITEM_EMPTY(hi)) { continue; } - --todo; + todo--; kp_prev = NULL; for (kp = HI2KE(hi); kp != NULL;) { if (kp->k_syn.id == id) { @@ -3965,9 +3955,9 @@ static void clear_keywtab(hashtab_T *ht) keyentry_T *kp_next; todo = (int)ht->ht_used; - for (hi = ht->ht_array; todo > 0; ++hi) { + for (hi = ht->ht_array; todo > 0; hi++) { if (!HASHITEM_EMPTY(hi)) { - --todo; + todo--; for (kp = HI2KE(hi); kp != NULL; kp = kp_next) { kp_next = kp->ke_next; xfree(kp->next_list); @@ -4004,7 +3994,7 @@ static void add_keyword(char_u *const name, const int id, const int flags, kp->k_char = conceal_char; kp->k_syn.cont_in_list = copy_id_list(cont_in_list); if (cont_in_list != NULL) { - curwin->w_s->b_syn_containedin = TRUE; + curwin->w_s->b_syn_containedin = true; } kp->next_list = copy_id_list(next_list); @@ -4037,17 +4027,13 @@ static void add_keyword(char_u *const name, const int id, const int flags, /// /// @return a pointer to the first argument. /// Return NULL if the end of the command was found instead of further args. -static char_u *get_group_name(char_u *arg, char_u **name_end) +static char *get_group_name(char *arg, char **name_end) { - char_u *rest; - *name_end = skiptowhite(arg); - rest = (char_u *)skipwhite((char *)(*name_end)); + char *rest = skipwhite(*name_end); - /* - * Check if there are enough arguments. The first argument may be a - * pattern, where '|' is allowed, so only check for NUL. - */ + // Check if there are enough arguments. The first argument may be a + // pattern, where '|' is allowed, so only check for NUL. if (ends_excmd(*arg) || *rest == NUL) { return NULL; } @@ -4061,11 +4047,11 @@ static char_u *get_group_name(char_u *arg, char_u **name_end) /// /// @param arg next argument to be checked /// @param opt various things -/// @param skip TRUE if skipping over command +/// @param skip true if skipping over command /// /// @return a pointer to the next argument (which isn't an option). /// Return NULL for any error; -static char_u *get_syn_options(char_u *arg, syn_opt_arg_T *opt, int *conceal_char, int skip) +static char *get_syn_options(char *arg, syn_opt_arg_T *opt, int *conceal_char, int skip) { char_u *gname_start, *gname; int syn_id; @@ -4118,8 +4104,8 @@ static char_u *get_syn_options(char_u *arg, syn_opt_arg_T *opt, int *conceal_cha for (fidx = ARRAY_SIZE(flagtab); --fidx >= 0;) { p = flagtab[fidx].name; int i; - for (i = 0, len = 0; p[i] != NUL; i += 2, ++len) { - if (arg[len] != (char_u)p[i] && arg[len] != (char_u)p[i + 1]) { + for (i = 0, len = 0; p[i] != NUL; i += 2, len++) { + if (arg[len] != p[i] && arg[len] != p[i + 1]) { break; } } @@ -4159,16 +4145,16 @@ static char_u *get_syn_options(char_u *arg, syn_opt_arg_T *opt, int *conceal_cha } } else if (flagtab[fidx].argtype == 11 && arg[5] == '=') { // cchar=? - *conceal_char = utf_ptr2char((char *)arg + 6); - arg += utfc_ptr2len((char *)arg + 6) - 1; + *conceal_char = utf_ptr2char(arg + 6); + arg += utfc_ptr2len(arg + 6) - 1; if (!vim_isprintc_strict(*conceal_char)) { emsg(_("E844: invalid cchar value")); return NULL; } - arg = (char_u *)skipwhite((char *)arg + 7); + arg = skipwhite(arg + 7); } else { opt->flags |= flagtab[fidx].flags; - arg = (char_u *)skipwhite((char *)arg + len); + arg = skipwhite(arg + len); if (flagtab[fidx].flags == HL_SYNC_HERE || flagtab[fidx].flags == HL_SYNC_THERE) { @@ -4176,12 +4162,12 @@ static char_u *get_syn_options(char_u *arg, syn_opt_arg_T *opt, int *conceal_cha emsg(_("E393: group[t]here not accepted here")); return NULL; } - gname_start = arg; + gname_start = (char_u *)arg; arg = skiptowhite(arg); - if (gname_start == arg) { + if (gname_start == (char_u *)arg) { return NULL; } - gname = vim_strnsave(gname_start, (size_t)(arg - gname_start)); + gname = vim_strnsave(gname_start, (size_t)((char_u *)arg - gname_start)); if (STRCMP(gname, "NONE") == 0) { *opt->sync_idx = NONE_IDX; } else { @@ -4202,7 +4188,7 @@ static char_u *get_syn_options(char_u *arg, syn_opt_arg_T *opt, int *conceal_cha } xfree(gname); - arg = (char_u *)skipwhite((char *)arg); + arg = skipwhite(arg); } else if (flagtab[fidx].flags == HL_FOLD && foldmethodIsSyntax(curwin)) { // Need to update folds later. @@ -4242,33 +4228,33 @@ static void syn_incl_toplevel(int id, int *flagsp) */ static void syn_cmd_include(exarg_T *eap, int syncing) { - char_u *arg = (char_u *)eap->arg; + char *arg = eap->arg; int sgl_id = 1; - char_u *group_name_end; - char_u *rest; + char *group_name_end; + char *rest; char *errormsg = NULL; int prev_toplvl_grp; int prev_syn_inc_tag; bool source = false; - eap->nextcmd = (char *)find_nextcmd(arg); + eap->nextcmd = find_nextcmd(arg); if (eap->skip) { return; } if (arg[0] == '@') { - ++arg; + arg++; rest = get_group_name(arg, &group_name_end); if (rest == NULL) { emsg(_("E397: Filename required")); return; } - sgl_id = syn_check_cluster(arg, (int)(group_name_end - arg)); + sgl_id = syn_check_cluster((char_u *)arg, (int)(group_name_end - arg)); if (sgl_id == 0) { return; } // separate_nextcmd() and expand_filename() depend on this - eap->arg = (char *)rest; + eap->arg = rest; } /* @@ -4316,13 +4302,13 @@ static void syn_cmd_include(exarg_T *eap, int syncing) */ static void syn_cmd_keyword(exarg_T *eap, int syncing) { - char_u *arg = (char_u *)eap->arg; - char_u *group_name_end; + char *arg = eap->arg; + char *group_name_end; int syn_id; - char_u *rest; - char_u *keyword_copy = NULL; - char_u *p; - char_u *kw; + char *rest; + char *keyword_copy = NULL; + char *p; + char *kw; syn_opt_arg_T syn_opt_arg; int cnt; int conceal_char = NUL; @@ -4333,7 +4319,7 @@ static void syn_cmd_keyword(exarg_T *eap, int syncing) if (eap->skip) { syn_id = -1; } else { - syn_id = syn_check_group((char *)arg, (size_t)(group_name_end - arg)); + syn_id = syn_check_group(arg, (size_t)(group_name_end - arg)); } if (syn_id != 0) { // Allocate a buffer, for removing backslashes in the keyword. @@ -4352,7 +4338,7 @@ static void syn_cmd_keyword(exarg_T *eap, int syncing) // 1: collect the options and copy the keywords to keyword_copy. cnt = 0; p = keyword_copy; - for (; rest != NULL && !ends_excmd(*rest); rest = (char_u *)skipwhite((char *)rest)) { + for (; rest != NULL && !ends_excmd(*rest); rest = skipwhite(rest)) { rest = get_syn_options(rest, &syn_opt_arg, &conceal_char, eap->skip); if (rest == NULL || ends_excmd(*rest)) { break; @@ -4374,11 +4360,11 @@ static void syn_cmd_keyword(exarg_T *eap, int syncing) // 2: Add an entry for each keyword. for (kw = keyword_copy; --cnt >= 0; kw += STRLEN(kw) + 1) { - for (p = (char_u *)vim_strchr((char *)kw, '[');;) { + for (p = vim_strchr(kw, '[');;) { if (p != NULL) { *p = NUL; } - add_keyword(kw, syn_id, syn_opt_arg.flags, + add_keyword((char_u *)kw, syn_id, syn_opt_arg.flags, syn_opt_arg.cont_in_list, syn_opt_arg.next_list, conceal_char); if (p == NULL) { @@ -4397,7 +4383,7 @@ static void syn_cmd_keyword(exarg_T *eap, int syncing) kw = p + 1; break; // skip over the "]" } - const int l = utfc_ptr2len((char *)p + 1); + const int l = utfc_ptr2len(p + 1); memmove(p, p + 1, (size_t)l); p += l; @@ -4413,12 +4399,12 @@ error: } if (rest != NULL) { - eap->nextcmd = (char *)check_nextcmd(rest); + eap->nextcmd = (char *)check_nextcmd((char_u *)rest); } else { semsg(_(e_invarg2), arg); } - redraw_curbuf_later(SOME_VALID); + redraw_curbuf_later(UPD_SOME_VALID); syn_stack_free_all(curwin->w_s); // Need to recompute all syntax. } @@ -4426,12 +4412,11 @@ error: /// /// Also ":syntax sync match {name} [[grouphere | groupthere] {group-name}] .." /// -/// @param syncing TRUE for ":syntax sync match .. " +/// @param syncing true for ":syntax sync match .. " static void syn_cmd_match(exarg_T *eap, int syncing) { - char_u *arg = (char_u *)eap->arg; - char_u *group_name_end; - char_u *rest; + char *arg = eap->arg; + char *group_name_end; synpat_T item; // the item found in the line int syn_id; syn_opt_arg_T syn_opt_arg; @@ -4439,7 +4424,7 @@ static void syn_cmd_match(exarg_T *eap, int syncing) int conceal_char = NUL; // Isolate the group name, check for validity - rest = get_group_name(arg, &group_name_end); + char *rest = get_group_name(arg, &group_name_end); // Get options before the pattern syn_opt_arg.flags = 0; @@ -4453,8 +4438,8 @@ static void syn_cmd_match(exarg_T *eap, int syncing) // get the pattern. init_syn_patterns(); - memset(&item, 0, sizeof(item)); - rest = get_syn_pattern(rest, &item); + CLEAR_FIELD(item); + rest = get_syn_pattern((char_u *)rest, &item); if (vim_regcomp_had_eol() && !(syn_opt_arg.flags & HL_EXCLUDENL)) { syn_opt_arg.flags |= HL_HAS_EOL; } @@ -4466,11 +4451,11 @@ static void syn_cmd_match(exarg_T *eap, int syncing) /* * Check for trailing command and illegal trailing arguments. */ - eap->nextcmd = (char *)check_nextcmd(rest); + eap->nextcmd = (char *)check_nextcmd((char_u *)rest); if (!ends_excmd(*rest) || eap->skip) { rest = NULL; } else { - if ((syn_id = syn_check_group((char *)arg, (size_t)(group_name_end - arg))) != 0) { + if ((syn_id = syn_check_group(arg, (size_t)(group_name_end - arg))) != 0) { syn_incl_toplevel(syn_id, &syn_opt_arg.flags); /* * Store the pattern in the syn_items list @@ -4488,7 +4473,7 @@ static void syn_cmd_match(exarg_T *eap, int syncing) spp->sp_syn.cont_in_list = syn_opt_arg.cont_in_list; spp->sp_cchar = conceal_char; if (syn_opt_arg.cont_in_list != NULL) { - curwin->w_s->b_syn_containedin = TRUE; + curwin->w_s->b_syn_containedin = true; } spp->sp_next_list = syn_opt_arg.next_list; @@ -4497,10 +4482,10 @@ static void syn_cmd_match(exarg_T *eap, int syncing) curwin->w_s->b_syn_sync_flags |= SF_MATCH; } if (syn_opt_arg.flags & HL_FOLD) { - ++curwin->w_s->b_syn_folditems; + curwin->w_s->b_syn_folditems++; } - redraw_curbuf_later(SOME_VALID); + redraw_curbuf_later(UPD_SOME_VALID); syn_stack_free_all(curwin->w_s); // Need to recompute all syntax. return; // don't free the progs and patterns now } @@ -4524,15 +4509,15 @@ static void syn_cmd_match(exarg_T *eap, int syncing) /// Handle ":syntax region {group-name} [matchgroup={group-name}] /// start {start} .. [skip {skip}] end {end} .. [{options}]". /// -/// @param syncing TRUE for ":syntax sync region .." +/// @param syncing true for ":syntax sync region .." static void syn_cmd_region(exarg_T *eap, int syncing) { - char_u *arg = (char_u *)eap->arg; - char_u *group_name_end; - char_u *rest; // next arg, NULL on error - char_u *key_end; - char_u *key = NULL; - char_u *p; + char *arg = eap->arg; + char *group_name_end; + char *rest; // next arg, NULL on error + char *key_end; + char *key = NULL; + char *p; int item; #define ITEM_START 0 #define ITEM_SKIP 1 @@ -4583,10 +4568,10 @@ static void syn_cmd_region(exarg_T *eap, int syncing) // must be a pattern or matchgroup then key_end = rest; while (*key_end && !ascii_iswhite(*key_end) && *key_end != '=') { - ++key_end; + key_end++; } xfree(key); - key = vim_strnsave_up(rest, (size_t)(key_end - rest)); + key = (char *)vim_strnsave_up((char_u *)rest, (size_t)(key_end - rest)); if (STRCMP(key, "MATCHGROUP") == 0) { item = ITEM_MATCHGROUP; } else if (STRCMP(key, "START") == 0) { @@ -4602,13 +4587,13 @@ static void syn_cmd_region(exarg_T *eap, int syncing) } else { break; } - rest = (char_u *)skipwhite((char *)key_end); + rest = skipwhite(key_end); if (*rest != '=') { rest = NULL; semsg(_("E398: Missing '=': %s"), arg); break; } - rest = (char_u *)skipwhite((char *)rest + 1); + rest = skipwhite(rest + 1); if (*rest == NUL) { not_enough = true; break; @@ -4619,13 +4604,13 @@ static void syn_cmd_region(exarg_T *eap, int syncing) if ((p - rest == 4 && STRNCMP(rest, "NONE", 4) == 0) || eap->skip) { matchgroup_id = 0; } else { - matchgroup_id = syn_check_group((char *)rest, (size_t)(p - rest)); + matchgroup_id = syn_check_group(rest, (size_t)(p - rest)); if (matchgroup_id == 0) { illegal = true; break; } } - rest = (char_u *)skipwhite((char *)p); + rest = skipwhite(p); } else { /* * Allocate room for a syn_pattern, and link it in the list of @@ -4646,7 +4631,7 @@ static void syn_cmd_region(exarg_T *eap, int syncing) assert(item == ITEM_SKIP || item == ITEM_END); reg_do_extmatch = REX_USE; } - rest = get_syn_pattern(rest, ppp->pp_synp); + rest = get_syn_pattern((char_u *)rest, ppp->pp_synp); reg_do_extmatch = 0; if (item == ITEM_END && vim_regcomp_had_eol() && !(syn_opt_arg.flags & HL_EXCLUDENL)) { @@ -4673,18 +4658,18 @@ static void syn_cmd_region(exarg_T *eap, int syncing) * Check for trailing garbage or command. * If OK, add the item. */ - eap->nextcmd = (char *)check_nextcmd(rest); + eap->nextcmd = (char *)check_nextcmd((char_u *)rest); if (!ends_excmd(*rest) || eap->skip) { rest = NULL; } else { ga_grow(&(curwin->w_s->b_syn_patterns), pat_count); - if ((syn_id = syn_check_group((char *)arg, (size_t)(group_name_end - arg))) != 0) { + if ((syn_id = syn_check_group(arg, (size_t)(group_name_end - arg))) != 0) { syn_incl_toplevel(syn_id, &syn_opt_arg.flags); /* * Store the start/skip/end in the syn_items list */ int idx = curwin->w_s->b_syn_patterns.ga_len; - for (item = ITEM_START; item <= ITEM_END; ++item) { + for (item = ITEM_START; item <= ITEM_END; item++) { for (ppp = pat_ptrs[item]; ppp != NULL; ppp = ppp->pp_next) { SYN_ITEMS(curwin->w_s)[idx] = *(ppp->pp_synp); SYN_ITEMS(curwin->w_s)[idx].sp_syncing = syncing; @@ -4703,20 +4688,20 @@ static void syn_cmd_region(exarg_T *eap, int syncing) SYN_ITEMS(curwin->w_s)[idx].sp_syn.cont_in_list = syn_opt_arg.cont_in_list; if (syn_opt_arg.cont_in_list != NULL) { - curwin->w_s->b_syn_containedin = TRUE; + curwin->w_s->b_syn_containedin = true; } SYN_ITEMS(curwin->w_s)[idx].sp_next_list = syn_opt_arg.next_list; } - ++curwin->w_s->b_syn_patterns.ga_len; - ++idx; + curwin->w_s->b_syn_patterns.ga_len++; + idx++; if (syn_opt_arg.flags & HL_FOLD) { - ++curwin->w_s->b_syn_folditems; + curwin->w_s->b_syn_folditems++; } } } - redraw_curbuf_later(SOME_VALID); + redraw_curbuf_later(UPD_SOME_VALID); syn_stack_free_all(curwin->w_s); // Need to recompute all syntax. success = true; // don't free the progs and patterns now } @@ -4726,7 +4711,7 @@ static void syn_cmd_region(exarg_T *eap, int syncing) /* * Free the allocated memory. */ - for (item = ITEM_START; item <= ITEM_END; ++item) { + for (item = ITEM_START; item <= ITEM_END; item++) { for (ppp = pat_ptrs[item]; ppp != NULL; ppp = ppp_next) { if (!success && ppp->pp_synp != NULL) { vim_regfree(ppp->pp_synp->sp_prog); @@ -4945,7 +4930,7 @@ static int syn_add_cluster(char_u *name) syn_cluster_T *scp = GA_APPEND_VIA_PTR(syn_cluster_T, &curwin->w_s->b_syn_clusters); - memset(scp, 0, sizeof(*scp)); + CLEAR_POINTER(scp); scp->scl_name = name; scp->scl_name_u = vim_strsave_up(name); scp->scl_list = NULL; @@ -4966,14 +4951,14 @@ static int syn_add_cluster(char_u *name) */ static void syn_cmd_cluster(exarg_T *eap, int syncing) { - char_u *arg = (char_u *)eap->arg; - char_u *group_name_end; - char_u *rest; + char *arg = eap->arg; + char *group_name_end; + char *rest; bool got_clstr = false; int opt_len; int list_op; - eap->nextcmd = (char *)find_nextcmd(arg); + eap->nextcmd = find_nextcmd(arg); if (eap->skip) { return; } @@ -4981,7 +4966,7 @@ static void syn_cmd_cluster(exarg_T *eap, int syncing) rest = get_group_name(arg, &group_name_end); if (rest != NULL) { - int scl_id = syn_check_cluster(arg, (int)(group_name_end - arg)); + int scl_id = syn_check_cluster((char_u *)arg, (int)(group_name_end - arg)); if (scl_id == 0) { return; } @@ -5019,7 +5004,7 @@ static void syn_cmd_cluster(exarg_T *eap, int syncing) } if (got_clstr) { - redraw_curbuf_later(SOME_VALID); + redraw_curbuf_later(UPD_SOME_VALID); syn_stack_free_all(curwin->w_s); // Need to recompute all. } } @@ -5041,14 +5026,13 @@ static void init_syn_patterns(void) ga_set_growsize(&curwin->w_s->b_syn_patterns, 10); } -/* - * Get one pattern for a ":syntax match" or ":syntax region" command. - * Stores the pattern and program in a synpat_T. - * Returns a pointer to the next argument, or NULL in case of an error. - */ -static char_u *get_syn_pattern(char_u *arg, synpat_T *ci) +/// Get one pattern for a ":syntax match" or ":syntax region" command. +/// Stores the pattern and program in a synpat_T. +/// +/// @return a pointer to the next argument, or NULL in case of an error. +static char *get_syn_pattern(char_u *arg, synpat_T *ci) { - char_u *end; + char *end; int *p; int idx; char *cpo_save; @@ -5058,17 +5042,17 @@ static char_u *get_syn_pattern(char_u *arg, synpat_T *ci) return NULL; } - end = skip_regexp(arg + 1, *arg, TRUE, NULL); - if (*end != *arg) { // end delimiter not found + end = skip_regexp((char *)arg + 1, *arg, true, NULL); + if (*end != (char)(*arg)) { // end delimiter not found semsg(_("E401: Pattern delimiter not found: %s"), arg); return NULL; } // store the pattern and compiled regexp program - ci->sp_pattern = vim_strnsave(arg + 1, (size_t)(end - arg) - 1); + ci->sp_pattern = vim_strnsave(arg + 1, (size_t)(end - (char *)arg) - 1); // Make 'cpoptions' empty, to avoid the 'l' flag cpo_save = p_cpo; - p_cpo = ""; + p_cpo = empty_option; ci->sp_prog = vim_regcomp((char *)ci->sp_pattern, RE_MAGIC); p_cpo = cpo_save; @@ -5081,7 +5065,7 @@ static char_u *get_syn_pattern(char_u *arg, synpat_T *ci) /* * Check for a match, highlight or region offset. */ - ++end; + end++; do { for (idx = SPO_COUNT; --idx >= 0;) { if (STRNCMP(end, spo_name_tab[idx], 3) == 0) { @@ -5106,7 +5090,7 @@ static char_u *get_syn_pattern(char_u *arg, synpat_T *ci) ci->sp_off_flags |= (int16_t)(1 << idx); if (idx == SPO_LC_OFF) { // lc=99 end += 3; - *p = getdigits_int((char **)&end, true, 0); + *p = getdigits_int(&end, true, 0); // "lc=" offset automatically sets "ms=" offset if (!(ci->sp_off_flags & (1 << SPO_MS_OFF))) { @@ -5117,16 +5101,16 @@ static char_u *get_syn_pattern(char_u *arg, synpat_T *ci) end += 4; if (*end == '+') { end++; - *p = getdigits_int((char **)&end, true, 0); // positive offset + *p = getdigits_int(&end, true, 0); // positive offset } else if (*end == '-') { end++; - *p = -getdigits_int((char **)&end, true, 0); // negative offset + *p = -getdigits_int(&end, true, 0); // negative offset } } if (*end != ',') { break; } - ++end; + end++; } } } while (idx >= 0); @@ -5135,7 +5119,7 @@ static char_u *get_syn_pattern(char_u *arg, synpat_T *ci) semsg(_("E402: Garbage after pattern: %s"), arg); return NULL; } - return (char_u *)skipwhite((char *)end); + return skipwhite(end); } /* @@ -5144,7 +5128,7 @@ static char_u *get_syn_pattern(char_u *arg, synpat_T *ci) static void syn_cmd_sync(exarg_T *eap, int syncing) { char_u *arg_start = (char_u *)eap->arg; - char_u *arg_end; + char *arg_end; char_u *key = NULL; char_u *next_arg; int illegal = false; @@ -5152,26 +5136,26 @@ static void syn_cmd_sync(exarg_T *eap, int syncing) char *cpo_save; if (ends_excmd(*arg_start)) { - syn_cmd_list(eap, TRUE); + syn_cmd_list(eap, true); return; } while (!ends_excmd(*arg_start)) { - arg_end = skiptowhite(arg_start); - next_arg = (char_u *)skipwhite((char *)arg_end); + arg_end = skiptowhite((char *)arg_start); + next_arg = (char_u *)skipwhite(arg_end); xfree(key); - key = vim_strnsave_up(arg_start, (size_t)(arg_end - arg_start)); + key = vim_strnsave_up(arg_start, (size_t)(arg_end - (char *)arg_start)); if (STRCMP(key, "CCOMMENT") == 0) { if (!eap->skip) { curwin->w_s->b_syn_sync_flags |= SF_CCOMMENT; } if (!ends_excmd(*next_arg)) { - arg_end = skiptowhite(next_arg); + arg_end = skiptowhite((char *)next_arg); if (!eap->skip) { curwin->w_s->b_syn_sync_id = - (int16_t)syn_check_group((char *)next_arg, (size_t)(arg_end - next_arg)); + (int16_t)syn_check_group((char *)next_arg, (size_t)(arg_end - (char *)next_arg)); } - next_arg = (char_u *)skipwhite((char *)arg_end); + next_arg = (char_u *)skipwhite(arg_end); } else if (!eap->skip) { curwin->w_s->b_syn_sync_id = (int16_t)syn_name2id("Comment"); } @@ -5180,17 +5164,17 @@ static void syn_cmd_sync(exarg_T *eap, int syncing) || STRNCMP(key, "MAXLINES", 8) == 0 || STRNCMP(key, "LINEBREAKS", 10) == 0) { if (key[4] == 'S') { - arg_end = key + 6; + arg_end = (char *)key + 6; } else if (key[0] == 'L') { - arg_end = key + 11; + arg_end = (char *)key + 11; } else { - arg_end = key + 9; + arg_end = (char *)key + 9; } if (arg_end[-1] != '=' || !ascii_isdigit(*arg_end)) { - illegal = TRUE; + illegal = true; break; } - linenr_T n = getdigits_int32((char **)&arg_end, false, 0); + linenr_T n = getdigits_int32(&arg_end, false, 0); if (!eap->skip) { if (key[4] == 'B') { curwin->w_s->b_syn_sync_linebreaks = n; @@ -5212,26 +5196,26 @@ static void syn_cmd_sync(exarg_T *eap, int syncing) } if (curwin->w_s->b_syn_linecont_pat != NULL) { emsg(_("E403: syntax sync: line continuations pattern specified twice")); - finished = TRUE; + finished = true; break; } - arg_end = skip_regexp(next_arg + 1, *next_arg, TRUE, NULL); - if (*arg_end != *next_arg) { // end delimiter not found - illegal = TRUE; + arg_end = skip_regexp((char *)next_arg + 1, *next_arg, true, NULL); + if (*arg_end != (char)(*next_arg)) { // end delimiter not found + illegal = true; break; } if (!eap->skip) { // store the pattern and compiled regexp program curwin->w_s->b_syn_linecont_pat = - vim_strnsave(next_arg + 1, (size_t)(arg_end - next_arg) - 1); + (char *)vim_strnsave(next_arg + 1, (size_t)(arg_end - (char *)next_arg) - 1); curwin->w_s->b_syn_linecont_ic = curwin->w_s->b_syn_ic; // Make 'cpoptions' empty, to avoid the 'l' flag cpo_save = p_cpo; - p_cpo = ""; + p_cpo = empty_option; curwin->w_s->b_syn_linecont_prog = - vim_regcomp((char *)curwin->w_s->b_syn_linecont_pat, RE_MAGIC); + vim_regcomp(curwin->w_s->b_syn_linecont_pat, RE_MAGIC); p_cpo = cpo_save; syn_clear_time(&curwin->w_s->b_syn_linecont_time); @@ -5241,19 +5225,19 @@ static void syn_cmd_sync(exarg_T *eap, int syncing) break; } } - next_arg = (char_u *)skipwhite((char *)arg_end + 1); + next_arg = (char_u *)skipwhite(arg_end + 1); } else { eap->arg = (char *)next_arg; if (STRCMP(key, "MATCH") == 0) { - syn_cmd_match(eap, TRUE); + syn_cmd_match(eap, true); } else if (STRCMP(key, "REGION") == 0) { - syn_cmd_region(eap, TRUE); + syn_cmd_region(eap, true); } else if (STRCMP(key, "CLEAR") == 0) { - syn_cmd_clear(eap, TRUE); + syn_cmd_clear(eap, true); } else { - illegal = TRUE; + illegal = true; } - finished = TRUE; + finished = true; break; } arg_start = next_arg; @@ -5263,7 +5247,7 @@ static void syn_cmd_sync(exarg_T *eap, int syncing) semsg(_("E404: Illegal arguments: %s"), arg_start); } else if (!finished) { eap->nextcmd = (char *)check_nextcmd(arg_start); - redraw_curbuf_later(SOME_VALID); + redraw_curbuf_later(UPD_SOME_VALID); syn_stack_free_all(curwin->w_s); // Need to recompute all syntax. } } @@ -5277,7 +5261,7 @@ static void syn_cmd_sync(exarg_T *eap, int syncing) /// @param list where to store the resulting list, if not NULL, the list is silently skipped! /// /// @return FAIL for some error, OK for success. -static int get_id_list(char_u **const arg, const int keylen, int16_t **const list, const bool skip) +static int get_id_list(char **const arg, const int keylen, int16_t **const list, const bool skip) { char *p = NULL; char *end; @@ -5294,7 +5278,7 @@ static int get_id_list(char_u **const arg, const int keylen, int16_t **const lis // grow when a regexp is used. In that case round 1 is done once again. for (int round = 1; round <= 2; round++) { // skip "contains" - p = skipwhite((char *)(*arg) + keylen); + p = skipwhite(*arg + keylen); if (*p != '=') { semsg(_("E405: Missing equal sign: %s"), *arg); break; @@ -5362,7 +5346,7 @@ static int get_id_list(char_u **const arg, const int keylen, int16_t **const lis break; } - regmatch.rm_ic = TRUE; + 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)) { @@ -5401,7 +5385,7 @@ static int get_id_list(char_u **const arg, const int keylen, int16_t **const lis retval[count] = (int16_t)id; } } - ++count; + count++; } p = skipwhite(end); if (*p != ',') { @@ -5419,7 +5403,7 @@ static int get_id_list(char_u **const arg, const int keylen, int16_t **const lis } } - *arg = (char_u *)p; + *arg = p; if (failed || retval == NULL) { xfree(retval); return FAIL; @@ -5470,26 +5454,26 @@ static int in_id_list(stateitem_T *cur_si, int16_t *list, struct sp_syn *ssp, in static int depth = 0; int r; - // If ssp has a "containedin" list and "cur_si" is in it, return TRUE. + // If ssp has a "containedin" list and "cur_si" is in it, return true. if (cur_si != NULL && ssp->cont_in_list != NULL && !(cur_si->si_flags & HL_MATCH)) { // Ignore transparent items without a contains argument. Double check // that we don't go back past the first one. while ((cur_si->si_flags & HL_TRANS_CONT) && cur_si > (stateitem_T *)(current_state.ga_data)) { - --cur_si; + cur_si--; } // cur_si->si_idx is -1 for keywords, these never contain anything. if (cur_si->si_idx >= 0 && in_id_list(NULL, ssp->cont_in_list, &(SYN_ITEMS(syn_block)[cur_si->si_idx].sp_syn), SYN_ITEMS(syn_block)[cur_si->si_idx].sp_flags & HL_CONTAINED)) { - return TRUE; + return true; } } if (list == NULL) { - return FALSE; + return false; } /* @@ -5500,33 +5484,31 @@ static int in_id_list(stateitem_T *cur_si, int16_t *list, struct sp_syn *ssp, in return !contained; } - /* - * If the first item is "ALLBUT", return TRUE if "id" is NOT in the - * contains list. We also require that "id" is at the same ":syn include" - * level as the list. - */ + // If the first item is "ALLBUT", return true if "id" is NOT in the + // contains list. We also require that "id" is at the same ":syn include" + // level as the list. item = *list; if (item >= SYNID_ALLBUT && item < SYNID_CLUSTER) { if (item < SYNID_TOP) { // ALL or ALLBUT: accept all groups in the same file if (item - SYNID_ALLBUT != ssp->inc_tag) { - return FALSE; + return false; } } else if (item < SYNID_CONTAINED) { // TOP: accept all not-contained groups in the same file if (item - SYNID_TOP != ssp->inc_tag || contained) { - return FALSE; + return false; } } else { // CONTAINED: accept all contained groups in the same file if (item - SYNID_CONTAINED != ssp->inc_tag || !contained) { - return FALSE; + return false; } } item = *++list; - retval = FALSE; + retval = false; } else { - retval = TRUE; + retval = true; } /* @@ -5541,9 +5523,9 @@ static int in_id_list(stateitem_T *cur_si, int16_t *list, struct sp_syn *ssp, in // restrict recursiveness to 30 to avoid an endless loop for a // cluster that includes itself (indirectly) if (scl_list != NULL && depth < 30) { - ++depth; + depth++; r = in_id_list(NULL, scl_list, ssp, contained); - --depth; + depth--; if (r) { return retval; } @@ -5614,7 +5596,7 @@ void ex_syntax(exarg_T *eap) } xfree(subcmd_name); if (eap->skip) { - --emsg_skip; + emsg_skip--; } } @@ -5624,8 +5606,7 @@ void ex_ownsyntax(exarg_T *eap) char_u *new_value; if (curwin->w_s == &curwin->w_buffer->b_s) { - curwin->w_s = xmalloc(sizeof(synblock_T)); - memset(curwin->w_s, 0, sizeof(synblock_T)); + 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) @@ -5674,7 +5655,8 @@ static enum { EXP_SUBCMD, // expand ":syn" sub-commands EXP_CASE, // expand ":syn case" arguments EXP_SPELL, // expand ":syn spell" arguments - EXP_SYNC, // expand ":syn sync" arguments + EXP_SYNC, // expand ":syn sync" arguments + EXP_CLUSTER, // expand ":syn list @cluster" arguments } expand_what; /* @@ -5711,10 +5693,10 @@ void set_context_in_syntax_cmd(expand_T *xp, const char *arg) // (part of) subcommand already typed if (*arg != NUL) { - const char *p = (const char *)skiptowhite((const char_u *)arg); + const char *p = (const char *)skiptowhite(arg); if (*p != NUL) { // Past first word. xp->xp_pattern = skipwhite(p); - if (*skiptowhite((char_u *)xp->xp_pattern) != NUL) { + if (*skiptowhite(xp->xp_pattern) != NUL) { xp->xp_context = EXPAND_NOTHING; } else if (STRNICMP(arg, "case", p - arg) == 0) { expand_what = EXP_CASE; @@ -5722,10 +5704,16 @@ void set_context_in_syntax_cmd(expand_T *xp, const char *arg) 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 - || STRNICMP(arg, "list", p - arg) == 0) { + || STRNICMP(arg, "match", p - arg) == 0) { xp->xp_context = EXPAND_HIGHLIGHT; } else { xp->xp_context = EXPAND_NOTHING; @@ -5759,6 +5747,14 @@ char *get_syntax_name(expand_T *xp, int idx) "maxlines=", "minlines=", "region", NULL }; return sync_args[idx]; } + case EXP_CLUSTER: + if (idx < curwin->w_s->b_syn_clusters.ga_len) { + vim_snprintf(xp->xp_buf, EXPAND_BUF_LEN, "@%s", + SYN_CLSTR(curwin->w_s)[idx].scl_name); + return xp->xp_buf; + } else { + return NULL; + } } return NULL; } @@ -5831,7 +5827,7 @@ int syn_get_stack_item(int i) { if (i >= current_state.ga_len) { // Need to invalidate the state, because we didn't properly finish it - // for the last character, "keep_state" was TRUE. + // for the last character, "keep_state" was true. invalidate_current_state(); current_col = MAXCOL; return -1; @@ -5926,7 +5922,7 @@ static void syntime_clear(void) msg(_(msg_no_items)); return; } - for (int idx = 0; idx < curwin->w_s->b_syn_patterns.ga_len; ++idx) { + for (int idx = 0; idx < curwin->w_s->b_syn_patterns.ga_len; idx++) { spp = &(SYN_ITEMS(curwin->w_s)[idx]); syn_clear_time(&spp->sp_time); } @@ -5975,7 +5971,7 @@ static void syntime_report(void) proftime_T total_total = profile_zero(); int total_count = 0; time_entry_T *p; - for (int idx = 0; idx < curwin->w_s->b_syn_patterns.ga_len; ++idx) { + for (int idx = 0; idx < curwin->w_s->b_syn_patterns.ga_len; idx++) { synpat_T *spp = &(SYN_ITEMS(curwin->w_s)[idx]); if (spp->sp_time.count > 0) { p = GA_APPEND_VIA_PTR(time_entry_T, &ga); @@ -6001,7 +5997,7 @@ static void syntime_report(void) msg_puts_title(_(" TOTAL COUNT MATCH SLOWEST AVERAGE NAME PATTERN")); msg_puts("\n"); - for (int idx = 0; idx < ga.ga_len && !got_int; ++idx) { + for (int idx = 0; idx < ga.ga_len && !got_int; idx++) { p = ((time_entry_T *)ga.ga_data) + idx; msg_puts(profile_msg(p->total)); @@ -6032,7 +6028,7 @@ static void syntime_report(void) if (len > (int)STRLEN(p->pattern)) { len = (int)STRLEN(p->pattern); } - msg_outtrans_len(p->pattern, len); + msg_outtrans_len((char *)p->pattern, len); msg_puts("\n"); } ga_clear(&ga); diff --git a/src/nvim/tag.c b/src/nvim/tag.c index 5b799be381..5270412382 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -13,17 +13,19 @@ #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/edit.h" #include "nvim/eval.h" #include "nvim/ex_cmds.h" -#include "nvim/ex_cmds2.h" #include "nvim/ex_docmd.h" #include "nvim/ex_getln.h" #include "nvim/file_search.h" #include "nvim/fileio.h" #include "nvim/fold.h" #include "nvim/garray.h" +#include "nvim/help.h" #include "nvim/if_cscope.h" #include "nvim/input.h" #include "nvim/insexpand.h" @@ -33,6 +35,7 @@ #include "nvim/message.h" #include "nvim/move.h" #include "nvim/option.h" +#include "nvim/optionstr.h" #include "nvim/os/input.h" #include "nvim/os/os.h" #include "nvim/os/time.h" @@ -40,7 +43,7 @@ #include "nvim/path.h" #include "nvim/quickfix.h" #include "nvim/regexp.h" -#include "nvim/screen.h" +#include "nvim/runtime.h" #include "nvim/search.h" #include "nvim/strings.h" #include "nvim/tag.h" @@ -54,6 +57,7 @@ typedef struct tag_pointers { // filled in by parse_tag_line(): char_u *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 @@ -199,7 +203,7 @@ bool do_tag(char_u *tag, int type, int count, int forceit, int verbose) } prev_num_matches = num_matches; - free_string_option(nofile_fname); + free_string_option((char *)nofile_fname); nofile_fname = NULL; clearpos(&saved_fmark.mark); // shutup gcc 4.0 @@ -212,7 +216,7 @@ bool do_tag(char_u *tag, int type, int count, int forceit, int verbose) new_tag = true; if (g_do_tagpreview != 0) { tagstack_clear_entry(&ptag_entry); - ptag_entry.tagname = vim_strsave(tag); + ptag_entry.tagname = (char *)vim_strsave(tag); } } else { if (g_do_tagpreview != 0) { @@ -236,7 +240,7 @@ bool do_tag(char_u *tag, int type, int count, int forceit, int verbose) cur_fnum = ptag_entry.cur_fnum; } else { tagstack_clear_entry(&ptag_entry); - ptag_entry.tagname = vim_strsave(tag); + ptag_entry.tagname = (char *)vim_strsave(tag); } } else { /* @@ -258,7 +262,7 @@ bool do_tag(char_u *tag, int type, int count, int forceit, int verbose) } // put the tag name in the tag stack - tagstack[tagstackidx].tagname = vim_strsave(tag); + tagstack[tagstackidx].tagname = (char *)vim_strsave(tag); curwin->w_tagstacklen = tagstacklen; @@ -437,9 +441,9 @@ bool do_tag(char_u *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; + name = (char_u *)tagstack[tagstackidx].tagname; } else if (g_do_tagpreview != 0) { - name = ptag_entry.tagname; + name = (char_u *)ptag_entry.tagname; } else { name = tag; } @@ -465,7 +469,7 @@ bool do_tag(char_u *tag, int type, int count, int forceit, int verbose) // when the argument starts with '/', use it as a regexp if (!no_regexp && *name == '/') { flags = TAG_REGEXP; - ++name; + name++; } else { flags = TAG_NOIC; } @@ -480,8 +484,8 @@ bool do_tag(char_u *tag, int type, int count, int forceit, int verbose) flags |= TAG_NO_TAGFUNC; } - if (find_tags(name, &new_num_matches, &new_matches, flags, - max_num_matches, buf_ffname) == OK + if (find_tags((char *)name, &new_num_matches, &new_matches, flags, + max_num_matches, (char *)buf_ffname) == OK && new_num_matches < max_num_matches) { max_num_matches = MAXCOL; // If less than max_num_matches // found: all matches found. @@ -584,9 +588,9 @@ bool do_tag(char_u *tag, int type, int count, int forceit, int verbose) if (use_tfu && parse_match((char_u *)matches[cur_match], &tagp2) == OK && tagp2.user_data) { XFREE_CLEAR(tagstack[tagstackidx].user_data); - tagstack[tagstackidx].user_data = vim_strnsave(tagp2.user_data, - (size_t)(tagp2.user_data_end - - tagp2.user_data)); + tagstack[tagstackidx].user_data = (char *)vim_strnsave(tagp2.user_data, + (size_t)(tagp2.user_data_end - + tagp2.user_data)); } tagstackidx++; @@ -653,13 +657,13 @@ bool do_tag(char_u *tag, int type, int count, int forceit, int verbose) || cur_match < num_matches - 1))) { error_cur_match = cur_match; if (use_tagstack) { - --tagstackidx; + tagstackidx--; } if (type == DT_PREV) { - --cur_match; + cur_match--; } else { type = DT_NEXT; - ++cur_match; + cur_match++; } continue; } @@ -734,7 +738,7 @@ static void print_tag_list(int new_tag, int use_tagstack, int num_matches, char mt_names[matches[i][0] & MT_MASK]); msg_puts((char *)IObuff); if (tagp.tagkind != NULL) { - msg_outtrans_len(tagp.tagkind, + msg_outtrans_len((char *)tagp.tagkind, (int)(tagp.tagkind_end - tagp.tagkind)); } msg_advance(13); @@ -748,7 +752,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(p, HL_ATTR(HLF_D)); + msg_outtrans_attr((char *)p, HL_ATTR(HLF_D)); XFREE_CLEAR(p); } if (msg_col > 0) { @@ -790,7 +794,7 @@ static void print_tag_list(int new_tag, int use_tagstack, int num_matches, char } msg_advance(15); } - p = msg_outtrans_one(p, attr); + p = (char_u *)msg_outtrans_one((char *)p, attr); if (*p == TAB) { msg_puts_attr(" ", attr); break; @@ -848,7 +852,7 @@ static void print_tag_list(int new_tag, int use_tagstack, int num_matches, char msg_putchar(' '); p++; } else { - p = msg_outtrans_one(p, 0); + p = (char_u *)msg_outtrans_one((char *)p, 0); } // don't display the "$/;\"" and "$?;\"" @@ -1033,7 +1037,7 @@ void do_tags(exarg_T *eap) // Highlight title msg_puts_title(_("\n # TO tag FROM line in file/text")); - for (i = 0; i < tagstacklen; ++i) { + for (i = 0; i < tagstacklen; i++) { if (tagstack[i].tagname != NULL) { name = fm_getname(&(tagstack[i].fmark), 30); if (name == NULL) { // file name not available @@ -1048,7 +1052,7 @@ void do_tags(exarg_T *eap) tagstack[i].tagname, tagstack[i].fmark.mark.lnum); msg_outtrans((char *)IObuff); - msg_outtrans_attr(name, tagstack[i].fmark.fnum == curbuf->b_fnum + msg_outtrans_attr((char *)name, tagstack[i].fmark.fnum == curbuf->b_fnum ? HL_ATTR(HLF_D) : 0); xfree(name); } @@ -1076,9 +1080,9 @@ static int tag_strnicmp(char_u *s1, char_u *s2, size_t len) if (*s1 == NUL) { break; // strings match until NUL } - ++s1; - ++s2; - --len; + s1++; + s2++; + len--; } return 0; // strings match } @@ -1181,7 +1185,7 @@ static int find_tagfunc_tags(char_u *pat, garray_T *ga, int *match_count, int fl flags & TAG_REGEXP ? "r": ""); save_pos = curwin->w_cursor; - result = call_vim_function((char *)curbuf->b_p_tfu, 3, args, &rettv); + result = call_vim_function(curbuf->b_p_tfu, 3, args, &rettv); curwin->w_cursor = save_pos; // restore the cursor position d->dv_refcount--; @@ -1320,7 +1324,7 @@ static int find_tagfunc_tags(char_u *pat, garray_T *ga, int *match_count, int fl // Add all matches because tagfunc should do filtering. ga_grow(ga, 1); - ((char_u **)(ga->ga_data))[ga->ga_len++] = mfp; + ((char **)(ga->ga_data))[ga->ga_len++] = (char *)mfp; ntags++; result = OK; }); @@ -1362,8 +1366,8 @@ static int find_tagfunc_tags(char_u *pat, garray_T *ga, int *match_count, int fl /// @param matchesp return: array of matches found /// @param mincount MAXCOL: find all matches other: minimal number of matches */ /// @param buf_ffname name of buffer for priority -int find_tags(char_u *pat, int *num_matches, char ***matchesp, int flags, int mincount, - char_u *buf_ffname) +int find_tags(char *pat, int *num_matches, char ***matchesp, int flags, int mincount, + char *buf_ffname) { FILE *fp; char_u *lbuf; // line buffer @@ -1406,12 +1410,12 @@ int find_tags(char_u *pat, int *num_matches, char ***matchesp, int flags, int mi int cmplen; int match; // matches - int match_no_ic = 0; // matches with rm_ic == FALSE + int match_no_ic = 0; // matches with rm_ic == false int match_re; // match with regexp int matchoff = 0; int save_emsg_off; - char_u *mfp; + 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; @@ -1438,7 +1442,7 @@ int find_tags(char_u *pat, int *num_matches, char ***matchesp, int flags, int mi int help_only = (flags & TAG_HELP); int name_only = (flags & TAG_NAMES); int noic = (flags & TAG_NOIC); - int get_it_again = FALSE; + int get_it_again = false; int use_cscope = (flags & TAG_CSCOPE); int verbose = (flags & TAG_VERBOSE); int use_tfu = ((flags & TAG_NO_TAGFUNC) == 0); @@ -1456,17 +1460,17 @@ int find_tags(char_u *pat, int *num_matches, char ***matchesp, int flags, int mi p_ic = false; break; case TC_FOLLOWSCS: - p_ic = ignorecase(pat); + p_ic = ignorecase((char_u *)pat); break; case TC_SMART: - p_ic = ignorecase_opt(pat, true, true); + p_ic = ignorecase_opt((char_u *)pat, true, true); break; default: abort(); } help_save = curbuf->b_help; - orgpat.pat = pat; + orgpat.pat = (char_u *)pat; orgpat.regmatch.regprog = NULL; vimconv.vc_type = CONV_NONE; @@ -1476,7 +1480,7 @@ int find_tags(char_u *pat, int *num_matches, char ***matchesp, int flags, int mi 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_u *), 100); + ga_init(&ga_match[mtt], sizeof(char *), 100); hash_init(&ht_match[mtt]); } @@ -1500,8 +1504,8 @@ int find_tags(char_u *pat, int *num_matches, char ***matchesp, int flags, int mi if (orgpat.len > 3 && pat[orgpat.len - 3] == '@' && ASCII_ISALPHA(pat[orgpat.len - 2]) && ASCII_ISALPHA(pat[orgpat.len - 1])) { - saved_pat = vim_strnsave(pat, (size_t)orgpat.len - 3); - help_lang_find = &pat[orgpat.len - 2]; + saved_pat = vim_strnsave((char_u *)pat, (size_t)orgpat.len - 3); + help_lang_find = (char_u *)&pat[orgpat.len - 2]; orgpat.pat = saved_pat; orgpat.len -= 3; } @@ -1511,7 +1515,7 @@ int find_tags(char_u *pat, int *num_matches, char ***matchesp, int flags, int mi } save_emsg_off = emsg_off; - emsg_off = TRUE; // don't want error for invalid RE here + emsg_off = true; // don't want error for invalid RE here prepare_pats(&orgpat, has_re); emsg_off = save_emsg_off; if (has_re && orgpat.regmatch.regprog == NULL) { @@ -1520,12 +1524,12 @@ int find_tags(char_u *pat, int *num_matches, char ***matchesp, int flags, int mi // This is only to avoid a compiler warning for using search_info // uninitialised. - memset(&search_info, 0, 1); // -V512 + CLEAR_FIELD(search_info); if (*curbuf->b_p_tfu != NUL && use_tfu && !tfu_in_use) { tfu_in_use = true; - retval = find_tagfunc_tags(pat, &ga_match[0], &match_count, - flags, buf_ffname); + 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; @@ -1552,12 +1556,12 @@ int find_tags(char_u *pat, int *num_matches, char ***matchesp, int flags, int mi } orgpat.regmatch.rm_ic = ((p_ic || !noic) && (findall || orgpat.headlen == 0 || !p_tbs)); - for (round = 1; round <= 2; ++round) { + for (round = 1; round <= 2; round++) { linear = (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, tag_fname) == OK; + use_cscope || get_tagfname(&tn, first_file, (char *)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). @@ -1598,7 +1602,7 @@ int find_tags(char_u *pat, int *num_matches, char ***matchesp, int flags, int mi help_pri = 0; } else { help_pri = 1; - for (s = p_hlg; *s != NUL; ++s) { + for (s = p_hlg; *s != NUL; s++) { if (STRNICMP(s, help_lang, 2) == 0) { break; } @@ -1612,7 +1616,7 @@ int find_tags(char_u *pat, int *num_matches, char ***matchesp, int flags, int mi // unless found already. help_pri++; if (STRICMP(help_lang, "en") != 0) { - ++help_pri; + help_pri++; } } } @@ -1701,7 +1705,7 @@ int find_tags(char_u *pat, int *num_matches, char ***matchesp, int flags, int mi eof = vim_fgets(lbuf, lbuf_size, fp); } // skip empty and blank lines - while (!eof && vim_isblankline(lbuf)) { + while (!eof && vim_isblankline((char *)lbuf)) { search_info.curr_offset = vim_ftell(fp); eof = vim_fgets(lbuf, lbuf_size, fp); } @@ -1722,7 +1726,7 @@ int find_tags(char_u *pat, int *num_matches, char ***matchesp, int flags, int mi eof = use_cscope ? cs_fgets(lbuf, lbuf_size) : vim_fgets(lbuf, lbuf_size, fp); - } while (!eof && vim_isblankline(lbuf)); + } while (!eof && vim_isblankline((char *)lbuf)); if (eof) { break; // end of file @@ -1737,7 +1741,7 @@ line_read_in: // Convert every line. Converting the pattern from 'enc' to // the tags file encoding doesn't work, because characters are // not recognized. - conv_line = string_convert(&vimconv, lbuf, NULL); + 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; @@ -1777,7 +1781,7 @@ line_read_in: // encoding to 'encoding'. for (p = lbuf + 20; *p > ' ' && *p < 127; p++) {} *p = NUL; - convert_setup(&vimconv, lbuf + 20, p_enc); + convert_setup(&vimconv, (char *)lbuf + 20, p_enc); } // Read the next line. Unrecognized flags are ignored. @@ -1860,7 +1864,7 @@ parse_line: // For "normal" tags: Do a quick check if the tag matches. // This speeds up tag searching a lot! if (orgpat.headlen) { - memset(&tagp, 0, sizeof(tagp)); + CLEAR_FIELD(tagp); tagp.tagname = lbuf; tagp.tagname_end = (char_u *)vim_strchr((char *)lbuf, TAB); if (tagp.tagname_end == NULL) { @@ -2006,7 +2010,7 @@ parse_line: } // if tag length does not match, don't try comparing if (orgpat.len != cmplen) { - match = FALSE; + match = false; } else { if (orgpat.regmatch.rm_ic) { assert(cmplen >= 0); @@ -2023,7 +2027,7 @@ parse_line: /* * Has a regexp: Also find tags matching regexp. */ - match_re = FALSE; + match_re = false; if (!match && orgpat.regmatch.regprog != NULL) { int cc; @@ -2051,7 +2055,8 @@ parse_line: mtt = MT_GL_OTH; } else { // Decide in which array to store this match. - is_current = test_for_current(tagp.fname, tagp.fname_end, tag_fname, + is_current = test_for_current((char *)tagp.fname, (char *)tagp.fname_end, + (char *)tag_fname, buf_ffname); is_static = test_for_static(&tagp); @@ -2088,9 +2093,9 @@ parse_line: // The format is {tagname}@{lang}NUL{heuristic}NUL *tagp.tagname_end = NUL; len = (size_t)(tagp.tagname_end - tagp.tagname); - mfp = xmalloc(sizeof(char_u) + len + 10 + ML_EXTRA + 1); + mfp = xmalloc(sizeof(char) + len + 10 + ML_EXTRA + 1); - p = mfp; + p = (char_u *)mfp; STRCPY(p, tagp.tagname); p[len] = '@'; STRCPY(p + len + 1, help_lang); @@ -2122,7 +2127,7 @@ parse_line: get_it_again = false; } else { len = (size_t)(tagp.tagname_end - tagp.tagname); - mfp = xmalloc(sizeof(char_u) + len + 1); + mfp = xmalloc(sizeof(char) + len + 1); STRLCPY(mfp, tagp.tagname, len + 1); // if wanted, re-read line to get long form too @@ -2140,8 +2145,8 @@ parse_line: // 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_u) + len + 1); - p = mfp; + 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 @@ -2166,15 +2171,14 @@ parse_line: if (use_cscope) { hash++; } else { - hash = hash_hash(mfp); + 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, mfp, hash); + hash_add_item(&ht_match[mtt], hi, (char_u *)mfp, hash); ga_grow(&ga_match[mtt], 1); - ((char_u **)(ga_match[mtt].ga_data)) - [ga_match[mtt].ga_len++] = mfp; + ((char **)(ga_match[mtt].ga_data))[ga_match[mtt].ga_len++] = mfp; match_count++; } else { // duplicate tag, drop it @@ -2234,7 +2238,7 @@ parse_line: if (use_cscope) { break; } - orgpat.regmatch.rm_ic = TRUE; // try another time while ignoring case + orgpat.regmatch.rm_ic = true; // try another time while ignoring case } if (!stop_searching) { @@ -2258,29 +2262,29 @@ findtag_end: } if (match_count > 0) { - matches = xmalloc((size_t)match_count * sizeof(char_u *)); + 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_u **)(ga_match[mtt].ga_data))[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_u)(*mfp - 1); + *mfp = (char)(*mfp - 1); // change the TAG_SEP back to NUL - for (p = mfp + 1; *p != NUL; p++) { + for (p = (char_u *)mfp + 1; *p != NUL; p++) { if (*p == TAG_SEP) { *p = NUL; } } } - matches[match_count++] = (char *)mfp; + matches[match_count++] = mfp; } } @@ -2332,17 +2336,17 @@ void free_tag_stuff(void) /// For help files, use "tags" file only. /// /// @param tnp holds status info -/// @param first TRUE when first file name is wanted +/// @param first true when first file name is wanted /// @param buf pointer to buffer of MAXPATHL chars /// /// @return FAIL if no more tag file names, OK otherwise. -int get_tagfname(tagname_T *tnp, int first, char_u *buf) +int get_tagfname(tagname_T *tnp, int first, char *buf) { - char_u *fname = NULL; - char_u *r_ptr; + char *fname = NULL; + char *r_ptr; if (first) { - memset(tnp, 0, sizeof(tagname_T)); + CLEAR_POINTER(tnp); } if (curbuf->b_help) { @@ -2353,7 +2357,7 @@ int get_tagfname(tagname_T *tnp, int first, char_u *buf) */ if (first) { ga_clear_strings(&tag_fnames); - ga_init(&tag_fnames, (int)sizeof(char_u *), 10); + ga_init(&tag_fnames, (int)sizeof(char *), 10); do_in_runtimepath("doc/tags doc/tags-??", DIP_ALL, found_tagfile_cb, NULL); } @@ -2364,22 +2368,21 @@ int get_tagfname(tagname_T *tnp, int first, char_u *buf) if (tnp->tn_hf_idx > tag_fnames.ga_len || *p_hf == NUL) { return FAIL; } - ++tnp->tn_hf_idx; + tnp->tn_hf_idx++; STRCPY(buf, p_hf); STRCPY(path_tail((char *)buf), "tags"); #ifdef BACKSLASH_IN_FILENAME slash_adjust(buf); #endif - simplify_filename(buf); + simplify_filename((char_u *)buf); for (int i = 0; i < tag_fnames.ga_len; i++) { - if (STRCMP(buf, ((char_u **)(tag_fnames.ga_data))[i]) == 0) { + if (STRCMP(buf, ((char **)(tag_fnames.ga_data))[i]) == 0) { return FAIL; // avoid duplicate file names } } } else { - STRLCPY(buf, ((char_u **)(tag_fnames.ga_data))[tnp->tn_hf_idx++], - MAXPATHL); + STRLCPY(buf, ((char **)(tag_fnames.ga_data))[tnp->tn_hf_idx++], MAXPATHL); } return OK; } @@ -2387,25 +2390,24 @@ int get_tagfname(tagname_T *tnp, int first, char_u *buf) if (first) { // Init. We make a copy of 'tags', because autocommands may change // the value without notifying us. - tnp->tn_tags = vim_strsave((*curbuf->b_p_tags != NUL) - ? curbuf->b_p_tags : p_tags); - tnp->tn_np = tnp->tn_tags; + tnp->tn_tags = vim_strsave((*curbuf->b_p_tags != NUL) ? (char_u *)curbuf->b_p_tags : p_tags); + tnp->tn_np = (char *)tnp->tn_tags; } /* * Loop until we have found a file name that can be used. * There are two states: - * tnp->tn_did_filefind_init == FALSE: setup for next part in 'tags'. - * tnp->tn_did_filefind_init == TRUE: find next file in this part. + * tnp->tn_did_filefind_init == false: setup for next part in 'tags'. + * tnp->tn_did_filefind_init == true: find next file in this part. */ for (;;) { if (tnp->tn_did_filefind_init) { - fname = vim_findfile(tnp->tn_search_ctx); + fname = (char *)vim_findfile(tnp->tn_search_ctx); if (fname != NULL) { break; } - tnp->tn_did_filefind_init = FALSE; + tnp->tn_did_filefind_init = false; } else { char_u *filename = NULL; @@ -2420,22 +2422,22 @@ int get_tagfname(tagname_T *tnp, int first, char_u *buf) * Copy next file name into buf. */ buf[0] = NUL; - (void)copy_option_part((char **)&tnp->tn_np, (char *)buf, MAXPATHL - 1, " ,"); + (void)copy_option_part(&tnp->tn_np, buf, MAXPATHL - 1, " ,"); - r_ptr = vim_findfile_stopdir(buf); + r_ptr = (char *)vim_findfile_stopdir((char_u *)buf); // move the filename one char forward and truncate the // filepath with a NUL - filename = (char_u *)path_tail((char *)buf); + filename = (char_u *)path_tail(buf); STRMOVE(filename + 1, filename); *filename++ = NUL; - tnp->tn_search_ctx = vim_findfile_init(buf, filename, - r_ptr, 100, - FALSE, // don't free visited list + tnp->tn_search_ctx = vim_findfile_init((char_u *)buf, filename, + (char_u *)r_ptr, 100, + false, // don't free visited list FINDFILE_FILE, // we search for a file tnp->tn_search_ctx, true, (char_u *)curbuf->b_ffname); if (tnp->tn_search_ctx != NULL) { - tnp->tn_did_filefind_init = TRUE; + tnp->tn_did_filefind_init = true; } } } @@ -2459,7 +2461,7 @@ void tagname_free(tagname_T *tnp) /// Parse one line from the tags file. Find start/end of tag name, start/end of /// file name and start of search pattern. /// -/// If is_etag is TRUE, tagp->fname and tagp->fname_end are not set. +/// If is_etag is true, tagp->fname and tagp->fname_end are not set. /// /// @param lbuf line to be parsed /// @@ -2478,7 +2480,7 @@ static int parse_tag_line(char_u *lbuf, tagptrs_T *tagp) // Isolate file name, from first to second white space if (*p != NUL) { - ++p; + p++; } tagp->fname = p; p = (char_u *)vim_strchr((char *)p, TAB); @@ -2489,7 +2491,7 @@ static int parse_tag_line(char_u *lbuf, tagptrs_T *tagp) // find start of search command, after second white space if (*p != NUL) { - ++p; + p++; } if (*p == NUL) { return FAIL; @@ -2510,8 +2512,8 @@ static int parse_tag_line(char_u *lbuf, tagptrs_T *tagp) * Static tags produced by the new ctags program have the format: * 'tag file /pattern/;"<Tab>file:' " * - * Return TRUE if it is a static tag and adjust *tagname to the real tag. - * Return FALSE if it is not a static tag. + * Return true if it is a static tag and adjust *tagname to the real tag. + * Return false if it is not a static tag. */ static bool test_for_static(tagptrs_T *tagp) { @@ -2522,11 +2524,11 @@ static bool test_for_static(tagptrs_T *tagp) while ((p = (char_u *)vim_strchr((char *)p, '\t')) != NULL) { p++; if (STRNCMP(p, "file:", 5) == 0) { - return TRUE; + return true; } } - return FALSE; + return false; } // Returns the length of a matching tag line. @@ -2640,7 +2642,7 @@ 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 (false for cscope) /// /// @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) @@ -2720,7 +2722,7 @@ static int jumpto_tag(const char_u *lbuf_arg, int forceit, int keep_help) goto erret; } - ++RedrawingDisabled; + RedrawingDisabled++; if (l_g_do_tagpreview != 0) { postponed_split = 0; // don't split again below @@ -2732,7 +2734,7 @@ static int jumpto_tag(const char_u *lbuf_arg, int forceit, int keep_help) * into a fullpath */ if (!curwin->w_p_pvw) { - full_fname = (char_u *)FullName_save((char *)fname, FALSE); + full_fname = (char_u *)FullName_save((char *)fname, false); fname = full_fname; /* @@ -2825,15 +2827,15 @@ static int jumpto_tag(const char_u *lbuf_arg, int forceit, int keep_help) */ str = pbuf; if (pbuf[0] == '/' || pbuf[0] == '?') { - str = skip_regexp(pbuf + 1, pbuf[0], FALSE, NULL) + 1; + str = (char_u *)skip_regexp((char *)pbuf + 1, pbuf[0], false, NULL) + 1; } if (str > pbuf_end - 1) { // search command with nothing following save_p_ws = p_ws; save_p_ic = p_ic; save_p_scs = p_scs; p_ws = true; // need 'wrapscan' for backward searches - p_ic = FALSE; // don't ignore case now - p_scs = FALSE; + p_ic = false; // don't ignore case now + p_scs = false; save_lnum = curwin->w_cursor.lnum; if (tagp.tagline > 0) { // start search before line from "line:" field @@ -2944,7 +2946,7 @@ static int jumpto_tag(const char_u *lbuf_arg, int forceit, int keep_help) && curwin != curwin_save && win_valid(curwin_save)) { // Return cursor to where we were validate_cursor(); - redraw_later(curwin, VALID); + redraw_later(curwin, UPD_VALID); win_enter(curwin_save, true); } @@ -3011,27 +3013,25 @@ static char_u *expand_tag_fname(char_u *fname, char_u *const tag_fname, const bo return retval; } -/* - * Check if we have a tag for the buffer with name "buf_ffname". - * This is a bit slow, because of the full path compare in path_full_compare(). - * Return TRUE if tag for file "fname" if tag file "tag_fname" is for current - * file. - */ -static int test_for_current(char_u *fname, char_u *fname_end, char_u *tag_fname, char_u *buf_ffname) +/// Check if we have a tag for the buffer with name "buf_ffname". +/// This is a bit slow, because of the full path compare in path_full_compare(). +/// +/// @return true if tag for file "fname" if tag file "tag_fname" is for current +/// file. +static int test_for_current(char *fname, char *fname_end, char *tag_fname, char *buf_ffname) { int c; - int retval = FALSE; - char_u *fullname; + int retval = false; if (buf_ffname != NULL) { // if the buffer has a name { - c = *fname_end; + c = (unsigned char)(*fname_end); *fname_end = NUL; } - fullname = expand_tag_fname(fname, tag_fname, true); - retval = (path_full_compare((char *)fullname, (char *)buf_ffname, true, true) & kEqualFiles); + char *fullname = (char *)expand_tag_fname((char_u *)fname, (char_u *)tag_fname, true); + retval = (path_full_compare(fullname, buf_ffname, true, true) & kEqualFiles); xfree(fullname); - *fname_end = (char_u)c; + *fname_end = (char)c; } return retval; @@ -3051,7 +3051,7 @@ static int find_extra(char_u **pp) if (ascii_isdigit(*str)) { str = (char_u *)skipdigits((char *)str + 1); } else if (*str == '/' || *str == '?') { - str = skip_regexp(str + 1, *str, false, NULL); + str = (char_u *)skip_regexp((char *)str + 1, *str, false, NULL); if (*str != first_char) { str = NULL; } else { @@ -3107,13 +3107,13 @@ int expand_tags(int tagnames, char_u *pat, int *num_file, char ***file) extra_flag = 0; } if (pat[0] == '/') { - ret = find_tags(pat + 1, num_file, file, + ret = find_tags((char *)pat + 1, num_file, file, TAG_REGEXP | extra_flag | TAG_VERBOSE | TAG_NO_TAGFUNC, - TAG_MANY, (char_u *)curbuf->b_ffname); + TAG_MANY, curbuf->b_ffname); } else { - ret = find_tags(pat, num_file, file, + ret = find_tags((char *)pat, num_file, file, TAG_REGEXP | extra_flag | TAG_VERBOSE | TAG_NO_TAGFUNC | TAG_NOIC, - TAG_MANY, (char_u *)curbuf->b_ffname); + TAG_MANY, curbuf->b_ffname); } if (ret == OK && !tagnames) { // Reorganize the tags for display and matching as strings of: @@ -3150,8 +3150,7 @@ int expand_tags(int tagnames, char_u *pat, int *num_file, char ***file) /// /// @param start start of the value /// @param end after the value; can be NULL -static int add_tag_field(dict_T *dict, const char *field_name, const char_u *start, - const char_u *end) +static int add_tag_field(dict_T *dict, const char *field_name, const char *start, const char *end) FUNC_ATTR_NONNULL_ARG(1, 2) { int len = 0; @@ -3171,7 +3170,7 @@ static int add_tag_field(dict_T *dict, const char *field_name, const char_u *sta if (end == NULL) { end = start + STRLEN(start); while (end > start && (end[-1] == '\r' || end[-1] == '\n')) { - --end; + end--; } } len = (int)(end - start); @@ -3198,8 +3197,8 @@ int get_tags(list_T *list, char_u *pat, char_u *buf_fname) tagptrs_T tp; bool is_static; - ret = find_tags(pat, &num_matches, &matches, - TAG_REGEXP | TAG_NOIC, MAXCOL, buf_fname); + ret = find_tags((char *)pat, &num_matches, &matches, + TAG_REGEXP | TAG_NOIC, MAXCOL, (char *)buf_fname); if (ret == OK && num_matches > 0) { for (i = 0; i < num_matches; i++) { int parse_result = parse_match((char_u *)matches[i], &tp); @@ -3220,11 +3219,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, 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 + if (add_tag_field(dict, "name", (char *)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 || tv_dict_add_nr(dict, S_LEN("static"), is_static) == FAIL) { ret = FAIL; } @@ -3250,23 +3249,23 @@ int get_tags(list_T *list, char_u *pat, char_u *buf_fname) // separated by Tabs. n = p; while (*p != NUL && *p >= ' ' && *p < 127 && *p != ':') { - ++p; + p++; } len = (int)(p - n); if (*p == ':' && len > 0) { s = ++p; while (*p != NUL && *p >= ' ') { - ++p; + p++; } n[len] = NUL; - if (add_tag_field(dict, (char *)n, s, p) == FAIL) { + if (add_tag_field(dict, (char *)n, (char *)s, (char *)p) == FAIL) { ret = FAIL; } n[len] = ':'; } else { // Skip field without colon. while (*p != NUL && *p >= ' ') { - ++p; + p++; } } if (*p == NUL) { @@ -3365,7 +3364,7 @@ static void tagstack_push_item(win_T *wp, char_u *tagname, int cur_fnum, int cur } wp->w_tagstacklen++; - tagstack[idx].tagname = tagname; + tagstack[idx].tagname = (char *)tagname; tagstack[idx].cur_fnum = cur_fnum; tagstack[idx].cur_match = cur_match; if (tagstack[idx].cur_match < 0) { @@ -3373,7 +3372,7 @@ static void tagstack_push_item(win_T *wp, char_u *tagname, int cur_fnum, int cur } tagstack[idx].fmark.mark = mark; tagstack[idx].fmark.fnum = fnum; - tagstack[idx].user_data = user_data; + tagstack[idx].user_data = (char *)user_data; } // Add a list of items to the tag stack in the specified window diff --git a/src/nvim/tag.h b/src/nvim/tag.h index c8051e1dcc..0b4039afb6 100644 --- a/src/nvim/tag.h +++ b/src/nvim/tag.h @@ -35,7 +35,7 @@ // Structure used for get_tagfname(). typedef struct { char_u *tn_tags; // value of 'tags' when starting - char_u *tn_np; // current position in tn_tags + char *tn_np; // current position in tn_tags int tn_did_filefind_init; int tn_hf_idx; void *tn_search_ctx; diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index eb7c83d317..90966bcfad 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -44,15 +44,16 @@ #include "nvim/api/private/helpers.h" #include "nvim/ascii.h" +#include "nvim/autocmd.h" #include "nvim/buffer.h" #include "nvim/change.h" #include "nvim/cursor.h" -#include "nvim/edit.h" +#include "nvim/drawscreen.h" +#include "nvim/eval.h" #include "nvim/event/loop.h" #include "nvim/event/time.h" #include "nvim/ex_cmds.h" #include "nvim/ex_docmd.h" -#include "nvim/fileio.h" #include "nvim/getchar.h" #include "nvim/highlight.h" #include "nvim/highlight_group.h" @@ -68,8 +69,8 @@ #include "nvim/mouse.h" #include "nvim/move.h" #include "nvim/option.h" +#include "nvim/optionstr.h" #include "nvim/os/input.h" -#include "nvim/screen.h" #include "nvim/state.h" #include "nvim/terminal.h" #include "nvim/ui.h" @@ -417,7 +418,7 @@ bool terminal_enter(void) // placed at end of buffer to "follow" output. #11072 handle_T save_curwin = curwin->handle; bool save_w_p_cul = curwin->w_p_cul; - char_u *save_w_p_culopt = NULL; + char *save_w_p_culopt = NULL; char_u 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; @@ -425,7 +426,7 @@ bool terminal_enter(void) if (curwin->w_p_cul && curwin->w_p_culopt_flags & CULOPT_NBR) { if (STRCMP(curwin->w_p_culopt, "number")) { save_w_p_culopt = curwin->w_p_culopt; - curwin->w_p_culopt = (char_u *)xstrdup("number"); + curwin->w_p_culopt = xstrdup("number"); } curwin->w_p_culopt_flags = CULOPT_NBR; } else { @@ -681,7 +682,7 @@ static bool is_filter_char(int c) return !!(tpf_flags & flag); } -void terminal_paste(long count, char_u **y_array, size_t y_size) +void terminal_paste(long count, char **y_array, size_t y_size) { if (y_size == 0) { return; @@ -702,7 +703,7 @@ void terminal_paste(long count, char_u **y_array, size_t y_size) buff_len = len; } char_u *dst = buff; - char_u *src = y_array[j]; + char_u *src = (char_u *)y_array[j]; while (*src != '\0') { len = (size_t)utf_ptr2len((char *)src); int c = utf_ptr2char((char *)src); @@ -1374,7 +1375,7 @@ static bool send_mouse_event(Terminal *term, int c) curwin->w_redr_status = true; curwin = save_curwin; curbuf = curwin->w_buffer; - redraw_later(mouse_win, NOT_VALID); + redraw_later(mouse_win, UPD_NOT_VALID); invalidate_terminal(term, -1, -1); // Only need to exit focus if the scrolled window is the terminal window return mouse_win == curwin; diff --git a/src/nvim/testdir/runtest.vim b/src/nvim/testdir/runtest.vim index 6b16e888a9..ce23141c7a 100644 --- a/src/nvim/testdir/runtest.vim +++ b/src/nvim/testdir/runtest.vim @@ -114,6 +114,13 @@ if has('win32') let $PROMPT = '$P$G' endif +if has('mac') + " In MacOS, when starting a shell in a terminal, a bash deprecation warning + " message is displayed. This breaks the terminal test. Disable the warning + " message. + let $BASH_SILENCE_DEPRECATION_WARNING = 1 +endif + " Prepare for calling test_garbagecollect_now(). let v:testing = 1 @@ -257,6 +264,7 @@ endfunc func EarlyExit(test) " It's OK for the test we use to test the quit detection. if a:test != 'Test_zz_quit_detected()' + call add(v:errors, v:errmsg) call add(v:errors, 'Test caused Vim to exit: ' . a:test) endif @@ -417,7 +425,7 @@ for s:test in sort(s:tests) set belloff=all let prev_error = '' let total_errors = [] - let run_nr = 1 + let g:run_nr = 1 " A test can set g:test_is_flaky to retry running the test. let g:test_is_flaky = 0 @@ -436,10 +444,10 @@ for s:test in sort(s:tests) call add(s:messages, 'Found errors in ' . s:test . ':') call extend(s:messages, v:errors) - call add(total_errors, 'Run ' . run_nr . ':') + call add(total_errors, 'Run ' . g:run_nr . ':') call extend(total_errors, v:errors) - if run_nr == 5 || prev_error == v:errors[0] + if g:run_nr >= 5 || prev_error == v:errors[0] call add(total_errors, 'Flaky test failed too often, giving up') let v:errors = total_errors break @@ -454,7 +462,7 @@ for s:test in sort(s:tests) let prev_error = v:errors[0] let v:errors = [] - let run_nr += 1 + let g:run_nr += 1 call RunTheTest(s:test) diff --git a/src/nvim/testdir/setup.vim b/src/nvim/testdir/setup.vim index 6bc3607b69..472ed4ca14 100644 --- a/src/nvim/testdir/setup.vim +++ b/src/nvim/testdir/setup.vim @@ -5,7 +5,7 @@ if exists('s:did_load') set directory& set directory^=. set display= - set fillchars=vert:\|,fold:- + set fillchars=vert:\|,foldsep:\|,fold:- set formatoptions=tcq set fsync set laststatus=1 diff --git a/src/nvim/testdir/test_arglist.vim b/src/nvim/testdir/test_arglist.vim index ca7c8574cb..521c3fcd57 100644 --- a/src/nvim/testdir/test_arglist.vim +++ b/src/nvim/testdir/test_arglist.vim @@ -87,6 +87,10 @@ func Test_argadd() new arga call assert_equal(0, len(argv())) + + if has('unix') + call assert_fails('argadd `Xdoes_not_exist`', 'E479:') + endif endfunc func Test_argadd_empty_curbuf() @@ -408,6 +412,35 @@ func Test_argedit() bw! x endfunc +" Test for the :argdedupe command +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') + call assert_equal(['a', 'A', 'b', 'B'], argv()) + else + call assert_equal(['a', 'b'], argv()) + endif + args a b a c a b + last + argdedupe + next + call assert_equal('c', expand('%:t')) + %argd +endfunc + " Test for the :argdelete command func Test_argdelete() call Reset_arglist() @@ -420,6 +453,8 @@ func Test_argdelete() call assert_equal(['b'], argv()) call assert_fails('argdelete', 'E610:') call assert_fails('1,100argdelete', 'E16:') + call assert_fails('argdel /\)/', 'E55:') + call assert_fails('1argdel 1', 'E474:') call Reset_arglist() args a b c d @@ -427,6 +462,8 @@ func Test_argdelete() argdel call Assert_argc(['a', 'c', 'd']) %argdel + + call assert_fails('argdel does_not_exist', 'E480:') endfunc func Test_argdelete_completion() @@ -472,13 +509,16 @@ func Test_arglist_autocmd() new " redefine arglist; go to Xxx1 next! Xxx1 Xxx2 Xxx3 - " open window for all args + " open window for all args; Reading Xxx2 will change the arglist and the + " third window will get Xxx1: + " win 1: Xxx1 + " win 2: Xxx2 + " win 3: Xxx1 all call assert_equal('test file Xxx1', getline(1)) wincmd w wincmd w call assert_equal('test file Xxx1', getline(1)) - " should now be in Xxx2 rewind call assert_equal('test file Xxx2', getline(1)) diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim index 1c2f86a584..3064b199d9 100644 --- a/src/nvim/testdir/test_autocmd.vim +++ b/src/nvim/testdir/test_autocmd.vim @@ -341,6 +341,39 @@ func Test_WinScrolled_close_curwin() call delete('Xtestout') endfunc +func Test_WinScrolled_long_wrapped() + CheckRunVimInTerminal + + let lines =<< trim END + set scrolloff=0 + let height = winheight(0) + let width = winwidth(0) + let g:scrolled = 0 + au WinScrolled * let g:scrolled += 1 + call setline(1, repeat('foo', height * width)) + call cursor(1, height * width) + END + call writefile(lines, 'Xtest_winscrolled_long_wrapped') + let buf = RunVimInTerminal('-S Xtest_winscrolled_long_wrapped', {'rows': 6}) + + call term_sendkeys(buf, ":echo g:scrolled\<CR>") + call WaitForAssert({-> assert_match('^0 ', term_getline(buf, 6))}, 1000) + + call term_sendkeys(buf, 'gj') + call term_sendkeys(buf, ":echo g:scrolled\<CR>") + call WaitForAssert({-> assert_match('^1 ', term_getline(buf, 6))}, 1000) + + call term_sendkeys(buf, '0') + call term_sendkeys(buf, ":echo g:scrolled\<CR>") + call WaitForAssert({-> assert_match('^2 ', term_getline(buf, 6))}, 1000) + + call term_sendkeys(buf, '$') + call term_sendkeys(buf, ":echo g:scrolled\<CR>") + call WaitForAssert({-> assert_match('^3 ', term_getline(buf, 6))}, 1000) + + call delete('Xtest_winscrolled_long_wrapped') +endfunc + func Test_WinClosed() " Test that the pattern is matched against the closed window's ID, and both " <amatch> and <afile> are set to it. @@ -493,6 +526,26 @@ func Test_BufReadCmdHelpJump() au! BufReadCmd endfunc +" BufReadCmd is triggered for a "nofile" buffer. Check all values. +func Test_BufReadCmdNofile() + for val in ['nofile', + \ 'nowrite', + \ 'acwrite', + \ 'quickfix', + \ 'help', + \ 'prompt', + \ ] + new somefile + exe 'set buftype=' .. val + au BufReadCmd somefile call setline(1, 'triggered') + edit + call assert_equal('triggered', getline(1)) + + au! BufReadCmd + bwipe! + endfor +endfunc + func Test_augroup_deleted() " This caused a crash before E936 was introduced augroup x @@ -587,9 +640,26 @@ func Test_BufEnter() " On MS-Windows we can't edit the directory, make sure we wipe the right " buffer. bwipe! Xdir - call delete('Xdir', 'd') au! BufEnter + + " Editing a "nofile" buffer doesn't read the file but does trigger BufEnter + " for historic reasons. Also test other 'buftype' values. + for val in ['nofile', + \ 'nowrite', + \ 'acwrite', + \ 'quickfix', + \ 'help', + \ 'prompt', + \ ] + new somefile + exe 'set buftype=' .. val + au BufEnter somefile call setline(1, 'some text') + edit + call assert_equal('some text', getline(1)) + bwipe! + au! BufEnter + endfor endfunc " Closing a window might cause an endless loop @@ -1766,6 +1836,21 @@ func Test_BufReadCmd() au! BufWriteCmd endfunc +func Test_BufWriteCmd() + autocmd BufWriteCmd Xbufwritecmd let g:written = 1 + new + file Xbufwritecmd + set buftype=acwrite + call mkdir('Xbufwritecmd') + write + " BufWriteCmd should be triggered even if a directory has the same name + call assert_equal(1, g:written) + call delete('Xbufwritecmd', 'd') + unlet g:written + au! BufWriteCmd + bwipe! +endfunc + func SetChangeMarks(start, end) exe a:start .. 'mark [' exe a:end .. 'mark ]' @@ -2614,6 +2699,28 @@ func Test_BufWrite_lockmarks() call delete('Xtest2') endfunc +func Test_FileType_spell() + if !isdirectory('/tmp') + throw "Skipped: requires /tmp directory" + endif + + " this was crashing with an invalid free() + setglobal spellfile=/tmp/en.utf-8.add + augroup crash + autocmd! + autocmd BufNewFile,BufReadPost crashfile setf somefiletype + autocmd BufNewFile,BufReadPost crashfile set ft=anotherfiletype + autocmd FileType anotherfiletype setlocal spell + augroup END + func! NoCrash() abort + edit /tmp/crashfile + endfunc + call NoCrash() + + au! crash + setglobal spellfile= +endfunc + " Test closing a window or editing another buffer from a FileChangedRO handler " in a readonly buffer func Test_FileChangedRO_winclose() @@ -2702,6 +2809,30 @@ func Test_autocmd_FileReadCmd() delfunc ReadFileCmd endfunc +" Test for passing invalid arguments to autocmd +func Test_autocmd_invalid_args() + " Additional character after * for event + call assert_fails('autocmd *a Xfile set ff=unix', 'E215:') + augroup Test + augroup END + " Invalid autocmd event + call assert_fails('autocmd Bufabc Xfile set ft=vim', 'E216:') + " Invalid autocmd event in a autocmd group + call assert_fails('autocmd Test Bufabc Xfile set ft=vim', 'E216:') + augroup! Test + " Execute all autocmds + call assert_fails('doautocmd * BufEnter', 'E217:') + call assert_fails('augroup! x1a2b3', 'E367:') + call assert_fails('autocmd BufNew <buffer=999> pwd', 'E680:') +endfunc + +" Test for deep nesting of autocmds +func Test_autocmd_deep_nesting() + autocmd BufEnter Xfile doautocmd BufEnter Xfile + call assert_fails('doautocmd BufEnter Xfile', 'E218:') + autocmd! BufEnter Xfile +endfunc + " Tests for SigUSR1 autocmd event, which is only available on posix systems. func Test_autocmd_sigusr1() CheckUnix @@ -2715,6 +2846,59 @@ func Test_autocmd_sigusr1() unlet g:sigusr1_passed endfunc +" Test for BufReadPre autocmd deleting the file +func Test_BufReadPre_delfile() + augroup TestAuCmd + au! + autocmd BufReadPre Xfile call delete('Xfile') + augroup END + call writefile([], 'Xfile') + call assert_fails('new Xfile', 'E200:') + call assert_equal('Xfile', @%) + call assert_equal(1, &readonly) + call delete('Xfile') + augroup TestAuCmd + au! + augroup END + close! +endfunc + +" Test for BufReadPre autocmd changing the current buffer +func Test_BufReadPre_changebuf() + augroup TestAuCmd + au! + autocmd BufReadPre Xfile edit Xsomeotherfile + augroup END + call writefile([], 'Xfile') + call assert_fails('new Xfile', 'E201:') + call assert_equal('Xsomeotherfile', @%) + call assert_equal(1, &readonly) + call delete('Xfile') + augroup TestAuCmd + au! + augroup END + close! +endfunc + +" Test for BufWipeouti autocmd changing the current buffer when reading a file +" in an empty buffer with 'f' flag in 'cpo' +func Test_BufDelete_changebuf() + new + augroup TestAuCmd + au! + autocmd BufWipeout * let bufnr = bufadd('somefile') | exe "b " .. bufnr + augroup END + let save_cpo = &cpo + set cpo+=f + call assert_fails('r Xfile', 'E484:') + call assert_equal('somefile', @%) + let &cpo = save_cpo + augroup TestAuCmd + au! + augroup END + close! +endfunc + " Test for the temporary internal window used to execute autocmds func Test_autocmd_window() %bw! diff --git a/src/nvim/testdir/test_blob.vim b/src/nvim/testdir/test_blob.vim index af42b3857d..70529c14d5 100644 --- a/src/nvim/testdir/test_blob.vim +++ b/src/nvim/testdir/test_blob.vim @@ -294,7 +294,7 @@ func Test_blob_index() call assert_equal(2, index(0zDEADBEEF, 0xBE)) call assert_equal(-1, index(0zDEADBEEF, 0)) call assert_equal(2, index(0z11111111, 0x11, 2)) - call assert_equal(3, index(0z11110111, 0x11, 2)) + call assert_equal(3, 0z11110111->index(0x11, 2)) call assert_equal(2, index(0z11111111, 0x11, -2)) call assert_equal(3, index(0z11110111, 0x11, -2)) diff --git a/src/nvim/testdir/test_buffer.vim b/src/nvim/testdir/test_buffer.vim index 67be3e6747..4def3b5df9 100644 --- a/src/nvim/testdir/test_buffer.vim +++ b/src/nvim/testdir/test_buffer.vim @@ -154,6 +154,24 @@ func Test_bdelete_cmd() set nobuflisted enew call assert_fails('bdelete ' .. bnr, 'E516:') + + " Deleting more than one buffer + new Xbuf1 + new Xbuf2 + exe 'bdel ' .. bufnr('Xbuf2') .. ' ' .. bufnr('Xbuf1') + call assert_equal(1, winnr('$')) + call assert_equal(0, getbufinfo('Xbuf1')[0].loaded) + call assert_equal(0, getbufinfo('Xbuf2')[0].loaded) + + " Deleting more than one buffer and an invalid buffer + new Xbuf1 + new Xbuf2 + let cmd = "exe 'bdel ' .. bufnr('Xbuf2') .. ' xxx ' .. bufnr('Xbuf1')" + call assert_fails(cmd, 'E94:') + call assert_equal(2, winnr('$')) + call assert_equal(1, getbufinfo('Xbuf1')[0].loaded) + call assert_equal(0, getbufinfo('Xbuf2')[0].loaded) + %bwipe! endfunc @@ -168,6 +186,194 @@ func Test_buffer_error() %bwipe endfunc +" Test for the status messages displayed when unloading, deleting or wiping +" out buffers +func Test_buffer_statusmsg() + CheckEnglish + set report=1 + new Xbuf1 + new Xbuf2 + let bnr = bufnr() + exe "normal 2\<C-G>" + call assert_match('buf ' .. bnr .. ':', v:statusmsg) + bunload Xbuf1 Xbuf2 + call assert_equal('2 buffers unloaded', v:statusmsg) + bdel Xbuf1 Xbuf2 + call assert_equal('2 buffers deleted', v:statusmsg) + bwipe Xbuf1 Xbuf2 + call assert_equal('2 buffers wiped out', v:statusmsg) + set report& +endfunc + +" Test for quitting the 'swapfile exists' dialog with the split buffer +" command. +func Test_buffer_sbuf_cleanup() + call writefile([], 'Xfile') + " first open the file in a buffer + new Xfile + let bnr = bufnr() + close + " create the swap file + call writefile([], '.Xfile.swp') + " Remove the catch-all that runtest.vim adds + au! SwapExists + augroup BufTest + au! + autocmd SwapExists Xfile let v:swapchoice='q' + augroup END + exe 'sbuf ' . bnr + call assert_equal(1, winnr('$')) + call assert_equal(0, getbufinfo('Xfile')[0].loaded) + + " test for :sball + sball + call assert_equal(1, winnr('$')) + call assert_equal(0, getbufinfo('Xfile')[0].loaded) + + %bw! + set shortmess+=F + let v:statusmsg = '' + edit Xfile + call assert_equal('', v:statusmsg) + call assert_equal(1, winnr('$')) + call assert_equal(0, getbufinfo('Xfile')[0].loaded) + set shortmess& + + call delete('Xfile') + call delete('.Xfile.swp') + augroup BufTest + au! + augroup END + augroup! BufTest +endfunc + +" Test for deleting a modified buffer with :confirm +func Test_bdel_with_confirm() + " requires a UI to be active + throw 'Skipped: use test/functional/legacy/buffer_spec.lua' + CheckUnix + CheckNotGui + CheckFeature dialog_con + new + call setline(1, 'test') + call assert_fails('bdel', 'E89:') + call feedkeys('c', 'L') + confirm bdel + call assert_equal(2, winnr('$')) + call assert_equal(1, &modified) + call feedkeys('n', 'L') + confirm bdel + call assert_equal(1, winnr('$')) +endfunc + +" Test for editing another buffer from a modified buffer with :confirm +func Test_goto_buf_with_confirm() + " requires a UI to be active + throw 'Skipped: use test/functional/legacy/buffer_spec.lua' + CheckUnix + CheckNotGui + CheckFeature dialog_con + new Xfile + enew + call setline(1, 'test') + call assert_fails('b Xfile', 'E37:') + call feedkeys('c', 'L') + call assert_fails('confirm b Xfile', 'E37:') + call assert_equal(1, &modified) + call assert_equal('', @%) + call feedkeys('y', 'L') + call assert_fails('confirm b Xfile', 'E37:') + call assert_equal(1, &modified) + call assert_equal('', @%) + call feedkeys('n', 'L') + confirm b Xfile + call assert_equal('Xfile', @%) + close! +endfunc + +" Test for splitting buffer with 'switchbuf' +func Test_buffer_switchbuf() + new Xfile + wincmd w + set switchbuf=useopen + sbuf Xfile + call assert_equal(1, winnr()) + call assert_equal(2, winnr('$')) + set switchbuf=usetab + tabnew + sbuf Xfile + call assert_equal(1, tabpagenr()) + call assert_equal(2, tabpagenr('$')) + set switchbuf& + %bw +endfunc + +" Test for BufAdd autocommand wiping out the buffer +func Test_bufadd_autocmd_bwipe() + %bw! + augroup BufAdd_Wipe + au! + autocmd BufAdd Xfile %bw! + augroup END + edit Xfile + call assert_equal('', @%) + call assert_equal(0, bufexists('Xfile')) + augroup BufAdd_Wipe + au! + augroup END + augroup! BufAdd_Wipe +endfunc + +" Test for trying to load a buffer with text locked +" <C-\>e in the command line is used to lock the text +func Test_load_buf_with_text_locked() + new Xfile1 + edit Xfile2 + let cmd = ":\<C-\>eexecute(\"normal \<C-O>\")\<CR>\<C-C>" + call assert_fails("call feedkeys(cmd, 'xt')", 'E565:') + %bw! +endfunc + +" Test for using CTRL-^ to edit the alternative file keeping the cursor +" position with 'nostartofline'. Also test using the 'buf' command. +func Test_buffer_edit_altfile() + call writefile(repeat(['one two'], 50), 'Xfile1') + call writefile(repeat(['five six'], 50), 'Xfile2') + set nosol + edit Xfile1 + call cursor(25, 5) + edit Xfile2 + call cursor(30, 4) + exe "normal \<C-^>" + call assert_equal([0, 25, 5, 0], getpos('.')) + exe "normal \<C-^>" + call assert_equal([0, 30, 4, 0], getpos('.')) + buf Xfile1 + call assert_equal([0, 25, 5, 0], getpos('.')) + buf Xfile2 + call assert_equal([0, 30, 4, 0], getpos('.')) + set sol& + call delete('Xfile1') + call delete('Xfile2') +endfunc + +" Test for running the :sball command with a maximum window count and a +" modified buffer +func Test_sball_with_count() + %bw! + edit Xfile1 + call setline(1, ['abc']) + new Xfile2 + new Xfile3 + new Xfile4 + 2sball + call assert_equal(bufnr('Xfile4'), winbufnr(1)) + call assert_equal(bufnr('Xfile1'), winbufnr(2)) + call assert_equal(0, getbufinfo('Xfile2')[0].loaded) + call assert_equal(0, getbufinfo('Xfile3')[0].loaded) + %bw! +endfunc + func Test_badd_options() new SomeNewBuffer setlocal numberwidth=3 diff --git a/src/nvim/testdir/test_bufline.vim b/src/nvim/testdir/test_bufline.vim index ffb8e3facd..3b5bcbce89 100644 --- a/src/nvim/testdir/test_bufline.vim +++ b/src/nvim/testdir/test_bufline.vim @@ -19,7 +19,7 @@ func Test_setbufline_getbufline() let b = bufnr('%') wincmd w call assert_equal(1, setbufline(b, 5, ['x'])) - call assert_equal(1, setbufline(1234, 1, ['x'])) + call assert_equal(1, ['x']->setbufline(bufnr('$') + 1, 1)) call assert_equal(0, setbufline(b, 4, ['d', 'e'])) call assert_equal(['c'], b->getbufline(3)) call assert_equal(['d'], getbufline(b, 4)) @@ -187,4 +187,24 @@ func Test_deletebufline_select_mode() bwipe! endfunc +func Test_setbufline_startup_nofile() + let before =<< trim [CODE] + set shortmess+=F + file Xresult + set buftype=nofile + call setbufline('', 1, 'success') + [CODE] + let after =<< trim [CODE] + set buftype= + write + quit + [CODE] + + if !RunVim(before, after, '--clean') + return + endif + call assert_equal(['success'], readfile('Xresult')) + call delete('Xresult') +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_cd.vim b/src/nvim/testdir/test_cd.vim index a1e53df774..d6d44d1901 100644 --- a/src/nvim/testdir/test_cd.vim +++ b/src/nvim/testdir/test_cd.vim @@ -113,7 +113,7 @@ func Test_chdir_func() call assert_equal('z', fnamemodify(3->getcwd(2), ':t')) tabnext | wincmd t call assert_match('^\[tabpage\] .*/y$', trim(execute('verbose pwd'))) - call chdir('..') + eval '..'->chdir() call assert_equal('Xdir', fnamemodify(getcwd(1, 2), ':t')) call assert_equal('Xdir', fnamemodify(getcwd(2, 2), ':t')) call assert_equal('z', fnamemodify(getcwd(3, 2), ':t')) diff --git a/src/nvim/testdir/test_clientserver.vim b/src/nvim/testdir/test_clientserver.vim index edf36b413b..66ee776a90 100644 --- a/src/nvim/testdir/test_clientserver.vim +++ b/src/nvim/testdir/test_clientserver.vim @@ -2,6 +2,11 @@ source check.vim CheckFeature job + +if !has('clientserver') + call assert_fails('call remote_startserver("local")', 'E942:') +endif + CheckFeature clientserver source shared.vim @@ -66,8 +71,9 @@ func Test_client_server() call remote_send(name, ":gui -f\<CR>") endif " Wait for the server to be up and answering requests. - sleep 100m - call WaitForAssert({-> assert_true(name->remote_expr("v:version", "", 1) != "")}) + " When using valgrind this can be very, very slow. + sleep 1 + call WaitForAssert({-> assert_match('\d', name->remote_expr("v:version", "", 1))}, 10000) call remote_send(name, ":let testvar = 'maybe'\<CR>") call WaitForAssert({-> assert_equal('maybe', remote_expr(name, "testvar", "", 2))}) @@ -178,6 +184,7 @@ func Test_client_server() 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:') 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 7aac731709..4bfd22cb6c 100644 --- a/src/nvim/testdir/test_cmdline.vim +++ b/src/nvim/testdir/test_cmdline.vim @@ -3,6 +3,7 @@ source check.vim source screendump.vim source view_util.vim +source shared.vim func Test_complete_tab() call writefile(['testfile'], 'Xtestfile') @@ -126,6 +127,40 @@ func Test_wildmenu_screendump() call delete('XTest_wildmenu') endfunc +func Test_changing_cmdheight() + CheckScreendump + + let lines =<< trim END + set cmdheight=1 laststatus=2 + END + call writefile(lines, 'XTest_cmdheight') + + let buf = RunVimInTerminal('-S XTest_cmdheight', {'rows': 8}) + call term_sendkeys(buf, ":resize -3\<CR>") + call VerifyScreenDump(buf, 'Test_changing_cmdheight_1', {}) + + " using the space available doesn't change the status line + call term_sendkeys(buf, ":set cmdheight+=3\<CR>") + call VerifyScreenDump(buf, 'Test_changing_cmdheight_2', {}) + + " using more space moves the status line up + call term_sendkeys(buf, ":set cmdheight+=1\<CR>") + call VerifyScreenDump(buf, 'Test_changing_cmdheight_3', {}) + + " reducing cmdheight moves status line down + call term_sendkeys(buf, ":set cmdheight-=2\<CR>") + call VerifyScreenDump(buf, 'Test_changing_cmdheight_4', {}) + + " reducing window size and then setting cmdheight + call term_sendkeys(buf, ":resize -1\<CR>") + call term_sendkeys(buf, ":set cmdheight=1\<CR>") + call VerifyScreenDump(buf, 'Test_changing_cmdheight_5', {}) + + " clean up + call StopVimInTerminal(buf) + call delete('XTest_cmdheight') +endfunc + func Test_map_completion() if !has('cmdline_compl') return @@ -209,7 +244,7 @@ func Test_match_completion() 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 feedkeys(":match \<S-Tab>\<Home>\"\<CR>", 'xt') call assert_equal('"match none', getreg(':')) @@ -220,9 +255,7 @@ func Test_highlight_completion() return endif hi Aardig ctermfg=green - call feedkeys(":hi \<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') call assert_equal('"hi clear Aardig', getreg(':')) @@ -480,6 +513,7 @@ func Test_getcompletion() call delete('Xtags') set tags& + call assert_fails("call getcompletion('\\\\@!\\\\@=', 'buffer')", 'E871:') call assert_fails('call getcompletion("", "burp")', 'E475:') call assert_fails('call getcompletion("abc", [])', 'E475:') endfunc @@ -912,12 +946,26 @@ func Test_cmdline_complete_various() call feedkeys(":doautocmd User MyCmd a.c\<C-A>\<C-B>\"\<CR>", 'xt') call assert_equal("\"doautocmd User MyCmd a.c\<C-A>", @:) + " completion of autocmd group after comma + call feedkeys(":doautocmd BufNew,BufEn\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal("\"doautocmd BufNew,BufEnter", @:) + + " completion of file name in :doautocmd + call writefile([], 'Xfile1') + call writefile([], 'Xfile2') + call feedkeys(":doautocmd BufEnter Xfi\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal("\"doautocmd BufEnter Xfile1 Xfile2", @:) + call delete('Xfile1') + call delete('Xfile2') + " completion for the :augroup command - augroup XTest + augroup XTest.test augroup END call feedkeys(":augroup X\<C-A>\<C-B>\"\<CR>", 'xt') - call assert_equal("\"augroup XTest", @:) - augroup! XTest + call assert_equal("\"augroup XTest.test", @:) + call feedkeys(":au X\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal("\"au XTest.test", @:) + augroup! XTest.test " completion for the :unlet command call feedkeys(":unlet one two\<C-A>\<C-B>\"\<CR>", 'xt') @@ -1212,6 +1260,16 @@ func Test_cmdline_overstrike() let &encoding = encoding_save endfunc +func Test_cmdwin_bug() + let winid = win_getid() + sp + try + call feedkeys("q::call win_gotoid(" .. winid .. ")\<CR>:q\<CR>", 'x!') + catch /^Vim\%((\a\+)\)\=:E11/ + endtry + bw! +endfunc + func Test_cmdwin_restore() CheckScreendump @@ -1392,14 +1450,6 @@ func Test_cmdwin_jump_to_win() call assert_equal(1, winnr('$')) endfunc -" Test for backtick expression in the command line -func Test_cmd_backtick() - %argd - argadd `=['a', 'b', 'c']` - call assert_equal(['a', 'b', 'c'], argv()) - %argd -endfunc - func Test_cmdwin_tabpage() tabedit " v8.2.1919 isn't ported yet, so E492 is thrown after E11 here. @@ -1412,11 +1462,48 @@ func Test_cmdwin_tabpage() tabclose! endfunc +func Test_cmdwin_interrupted() + CheckScreendump + + " aborting the :smile output caused the cmdline window to use the current + " buffer. + let lines =<< trim [SCRIPT] + au WinNew * smile + [SCRIPT] + call writefile(lines, 'XTest_cmdwin') + + let buf = RunVimInTerminal('-S XTest_cmdwin', {'rows': 18}) + " open cmdwin + call term_sendkeys(buf, "q:") + call WaitForAssert({-> assert_match('-- More --', term_getline(buf, 18))}) + " quit more prompt for :smile command + call term_sendkeys(buf, "q") + call WaitForAssert({-> assert_match('^$', term_getline(buf, 18))}) + " execute a simple command + call term_sendkeys(buf, "aecho 'done'\<CR>") + call VerifyScreenDump(buf, 'Test_cmdwin_interrupted', {}) + + " clean up + call StopVimInTerminal(buf) + call delete('XTest_cmdwin') +endfunc + +" Test for backtick expression in the command line +func Test_cmd_backtick() + CheckNotMSWindows " FIXME: see #19297 + %argd + argadd `=['a', 'b', 'c']` + call assert_equal(['a', 'b', 'c'], argv()) + %argd + + argadd `echo abc def` + call assert_equal(['abc def'], argv()) + %argd +endfunc + " Test for the :! command func Test_cmd_bang() - if !has('unix') - return - endif + CheckUnix let lines =<< trim [SCRIPT] " Test for no previous command @@ -1758,6 +1845,36 @@ func Test_read_shellcmd() endif endfunc +" Test for going up and down the directory tree using 'wildmenu' +func Test_wildmenu_dirstack() + CheckUnix + %bw! + call mkdir('Xdir1/dir2/dir3', '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 + set wildmenu + + call feedkeys(":e \<Tab>\<C-B>\"\<CR>", 'xt') + call assert_equal('"e file3_1.txt', @:) + call feedkeys(":e \<Tab>\<Up>\<C-B>\"\<CR>", 'xt') + call assert_equal('"e ../dir3/', @:) + call feedkeys(":e \<Tab>\<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 feedkeys(":e \<Tab>\<Up>\<Up>\<Down>\<Down>\<C-B>\"\<CR>", 'xt') + call assert_equal('"e ../../dir2/dir3/file3_1.txt', @:) + + cd - + 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>. func Test_recalling_cmdline() @@ -1806,6 +1923,199 @@ func Test_recalling_cmdline() cunmap <Plug>(save-cmdline) endfunc +" Test for using a popup menu for the command line completion matches +" (wildoptions=pum) +func Test_wildmenu_pum() + CheckRunVimInTerminal + + let commands =<< trim [CODE] + set wildmenu + set wildoptions=pum + set shm+=I + set noruler + set noshowcmd + [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', {}) + + call term_sendkeys(buf, "\<Down>\<Down>") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_02', {}) + + call term_sendkeys(buf, "\<C-N>") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_03', {}) + + call term_sendkeys(buf, "\<C-P>") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_04', {}) + + 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 + " the longest match, the wildmenu should not be displayed. + 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 + 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 + call mkdir('Xdir/XdirA/XdirB', 'p') + call writefile([], 'Xdir/XfileA') + call writefile([], 'Xdir/XdirA/XfileB') + 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 + 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. + call mkdir('Xdir/あいう', 'p') + call writefile([], 'Xdir/あいう/abc') + call writefile([], 'Xdir/あいう/xyz') + call writefile([], 'Xdir/あいう/123') + + 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', {}) + + call term_sendkeys(buf, "\<C-U>\<CR>") + call StopVimInTerminal(buf) + call delete('Xtest') + call delete('Xdir', 'rf') +endfunc + " this was going over the end of IObuff func Test_report_error_with_composing() let caught = 'no' @@ -1875,4 +2185,67 @@ func Test_cmdline_redraw_tabline() call delete('Xcmdline_redraw_tabline') endfunc +func Test_wildmenu_pum_disable_while_shown() + set wildoptions=pum + set wildmenu + cnoremap <F2> <Cmd>set nowildmenu<CR> + call feedkeys(":sign \<Tab>\<F2>\<Esc>", 'tx') + call assert_equal(0, pumvisible()) + cunmap <F2> + set wildoptions& wildmenu& +endfunc + +func Test_setcmdline() + func SetText(text, pos) + autocmd CmdlineChanged * let g:cmdtype = expand('<afile>') + call assert_equal(0, setcmdline(a:text)) + call assert_equal(a:text, getcmdline()) + call assert_equal(len(a:text) + 1, getcmdpos()) + call assert_equal(getcmdtype(), g:cmdtype) + unlet g:cmdtype + autocmd! CmdlineChanged + + call assert_equal(0, setcmdline(a:text, a:pos)) + call assert_equal(a:text, getcmdline()) + call assert_equal(a:pos, getcmdpos()) + + call assert_fails('call setcmdline("' .. a:text .. '", -1)', 'E487:') + call assert_fails('call setcmdline({}, 0)', 'E928:') + call assert_fails('call setcmdline("' .. a:text .. '", {})', 'E728:') + + return '' + endfunc + + call feedkeys(":\<C-R>=SetText('set rtp?', 2)\<CR>\<CR>", 'xt') + call assert_equal('set rtp?', @:) + + call feedkeys(":let g:str = input('? ')\<CR>", 't') + call feedkeys("\<C-R>=SetText('foo', 4)\<CR>\<CR>", 'xt') + call assert_equal('foo', g:str) + unlet g:str + + delfunc SetText + + " setcmdline() returns 1 when not editing the command line. + call assert_equal(1, 'foo'->setcmdline()) + + " Called in custom function + func CustomComplete(A, L, P) + call assert_equal(0, setcmdline("DoCmd ")) + return "January\nFebruary\nMars\n" + endfunc + + com! -nargs=* -complete=custom,CustomComplete DoCmd : + call feedkeys(":DoCmd \<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"DoCmd January February Mars', @:) + delcom DoCmd + delfunc CustomComplete + + " Called in <expr> + cnoremap <expr>a setcmdline('let foo=') + call feedkeys(":a\<CR>", 'tx') + call assert_equal('let foo=0', @:) + cunmap a +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_comments.vim b/src/nvim/testdir/test_comments.vim new file mode 100644 index 0000000000..c34b85c42d --- /dev/null +++ b/src/nvim/testdir/test_comments.vim @@ -0,0 +1,277 @@ +" Tests for the various flags in the 'comments' option + +" Test for the 'n' flag in 'comments' +func Test_comment_nested() + new + setlocal comments=n:> fo+=ro + exe "normal i> B\nD\<C-C>ggOA\<C-C>joC\<C-C>Go\<BS>>>> F\nH" + exe "normal 5GOE\<C-C>6GoG" + let expected =<< trim END + > A + > B + > C + > D + >>>> E + >>>> F + >>>> G + >>>> H + END + call assert_equal(expected, getline(1, '$')) + close! +endfunc + +" Test for the 'b' flag in 'comments' +func Test_comment_blank() + new + setlocal comments=b:* fo+=ro + exe "normal i* E\nF\n\<BS>G\nH\<C-C>ggOC\<C-C>O\<BS>B\<C-C>OA\<C-C>2joD" + let expected =<< trim END + A + *B + * C + * D + * E + * F + *G + H + END + call assert_equal(expected, getline(1, '$')) + close! +endfunc + +" Test for the 'f' flag in 'comments' (only the first line has a comment +" string) +func Test_comment_firstline() + new + setlocal comments=f:- fo+=ro + exe "normal i- B\nD\<C-C>ggoC\<C-C>ggOA\<C-C>" + call assert_equal(['A', '- B', ' C', ' D'], getline(1, '$')) + %d + setlocal comments=:- + exe "normal i- B\nD\<C-C>ggoC\<C-C>ggOA\<C-C>" + call assert_equal(['- A', '- B', '- C', '- D'], getline(1, '$')) + close! +endfunc + +" Test for the 's', 'm' and 'e' flags in 'comments' +" Test for automatically adding comment leaders in insert mode +func Test_comment_threepiece() + new + setlocal expandtab + call setline(1, ["\t/*"]) + setlocal formatoptions=croql + call cursor(1, 3) + call feedkeys("A\<cr>\<cr>/", 'tnix') + call assert_equal(["\t/*", " *", " */"], getline(1, '$')) + + " If a comment ends in a single line, then don't add it in the next line + %d + call setline(1, '/* line1 */') + call feedkeys("A\<CR>next line", 'xt') + call assert_equal(['/* line1 */', 'next line'], getline(1, '$')) + + %d + " Copy the trailing indentation from the leader comment to a new line + setlocal autoindent noexpandtab + call feedkeys("a\t/*\tone\ntwo\n/", 'xt') + call assert_equal(["\t/*\tone", "\t *\ttwo", "\t */"], getline(1, '$')) + close! +endfunc + +" Test for the 'r' flag in 'comments' (right align comment) +func Test_comment_rightalign() + new + setlocal comments=sr:/***,m:**,ex-2:******/ fo+=ro + exe "normal i=\<C-C>o\t /***\nD\n/" + exe "normal 2GOA\<C-C>joB\<C-C>jOC\<C-C>joE\<C-C>GOF\<C-C>joG" + let expected =<< trim END + = + A + /*** + ** B + ** C + ** D + ** E + ** F + ******/ + G + END + call assert_equal(expected, getline(1, '$')) + close! +endfunc + +" Test for the 'O' flag in 'comments' +func Test_comment_O() + new + setlocal comments=Ob:* fo+=ro + exe "normal i* B\nD\<C-C>kOA\<C-C>joC" + let expected =<< trim END + A + * B + * C + * D + END + call assert_equal(expected, getline(1, '$')) + close! +endfunc + +" Test for using a multibyte character as a comment leader +func Test_comment_multibyte_leader() + new + let t =<< trim END + { + X + Xa + XaY + XY + XYZ + X Y + X YZ + XX + XXa + XXY + } + END + call setline(1, t) + call cursor(2, 1) + + set tw=2 fo=cqm comments=n:X + exe "normal gqgqjgqgqjgqgqjgqgqjgqgqjgqgqjgqgqjgqgqjgqgqjgqgq" + let t =<< trim END + X + Xa + XaY + XY + XYZ + X Y + X YZ + XX + XXa + XXY + END + exe "normal o\n" . join(t, "\n") + + let expected =<< trim END + { + X + Xa + Xa + XY + XY + XY + XZ + X Y + X Y + X Z + XX + XXa + XXY + + X + Xa + Xa + XY + XY + XY + XZ + X Y + X Y + X Z + XX + XXa + XXY + } + END + call assert_equal(expected, getline(1, '$')) + + set tw& fo& comments& + close! +endfunc + +" Test for a space character in 'comments' setting +func Test_comment_space() + new + setlocal comments=b:\ > fo+=ro + exe "normal i> B\nD\<C-C>ggOA\<C-C>joC" + exe "normal Go > F\nH\<C-C>kOE\<C-C>joG" + let expected =<< trim END + A + > B + C + D + > E + > F + > G + > H + END + call assert_equal(expected, getline(1, '$')) + close! +endfunc + +" Test for formatting lines with and without comments +func Test_comment_format_lines() + new + call setline(1, ['one', '/* two */', 'three']) + normal gggqG + call assert_equal(['one', '/* two */', 'three'], getline(1, '$')) + close! +endfunc + +" Test for using 'a' in 'formatoptions' with comments +func Test_comment_autoformat() + new + setlocal formatoptions+=a + call feedkeys("a- one\n- two\n", 'xt') + call assert_equal(['- one', '- two', ''], getline(1, '$')) + + %d + call feedkeys("a\none\n", 'xt') + call assert_equal(['', 'one', ''], getline(1, '$')) + + setlocal formatoptions+=aw + %d + call feedkeys("aone \ntwo\n", 'xt') + call assert_equal(['one two', ''], getline(1, '$')) + + %d + call feedkeys("aone\ntwo\n", 'xt') + call assert_equal(['one', 'two', ''], getline(1, '$')) + + close! +endfunc + +" Test for joining lines with comments ('j' flag in 'formatoptions') +func Test_comment_join_lines_fo_j() + new + setlocal fo+=j comments=:// + call setline(1, ['i++; // comment1', ' // comment2']) + normal J + call assert_equal('i++; // comment1 comment2', getline(1)) + setlocal fo-=j + call setline(1, ['i++; // comment1', ' // comment2']) + normal J + call assert_equal('i++; // comment1 // comment2', getline(1)) + " Test with nested comments + setlocal fo+=j comments=n:>,n:) + call setline(1, ['i++; > ) > ) comment1', ' > ) comment2']) + normal J + call assert_equal('i++; > ) > ) comment1 comment2', getline(1)) + close! +endfunc + +" Test for formatting lines where only the first line has a comment. +func Test_comment_format_firstline_comment() + new + setlocal formatoptions=tcq + call setline(1, ['- one two', 'three']) + normal gggqG + call assert_equal(['- one two three'], getline(1, '$')) + + %d + call setline(1, ['- one', '- two']) + normal gggqG + call assert_equal(['- one', '- two'], getline(1, '$')) + close! +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 3b8a5f27ad..f13842edc8 100644 --- a/src/nvim/testdir/test_cursor_func.vim +++ b/src/nvim/testdir/test_cursor_func.vim @@ -22,7 +22,7 @@ func Test_move_cursor() call cursor(3, 0) call assert_equal([3, 1, 0, 1], getcurpos()[1:]) " below last line goes to last line - call cursor(9, 1) + eval [9, 1]->cursor() call assert_equal([4, 1, 0, 1], getcurpos()[1:]) " pass string arguments call cursor('3', '3') diff --git a/src/nvim/testdir/test_debugger.vim b/src/nvim/testdir/test_debugger.vim index e038c0096a..2be94409ca 100644 --- a/src/nvim/testdir/test_debugger.vim +++ b/src/nvim/testdir/test_debugger.vim @@ -15,14 +15,18 @@ func CheckCWD() endfunc command! -nargs=0 -bar CheckCWD call CheckCWD() +" "options" argument can contain: +" 'msec' - time to wait for a match +" 'match' - "pattern" to use "lines" as pattern instead of text func CheckDbgOutput(buf, lines, options = {}) " Verify the expected output let lnum = 20 - len(a:lines) + let msec = get(a:options, 'msec', 1000) for l in a:lines if get(a:options, 'match', 'equal') ==# 'pattern' - call WaitForAssert({-> assert_match(l, term_getline(a:buf, lnum))}, 200) + call WaitForAssert({-> assert_match(l, term_getline(a:buf, lnum))}, msec) else - call WaitForAssert({-> assert_equal(l, term_getline(a:buf, lnum))}, 200) + call WaitForAssert({-> assert_equal(l, term_getline(a:buf, lnum))}, msec) endif let lnum += 1 endfor @@ -198,7 +202,7 @@ func Test_Debugger() " Start a debug session, so that reading the last line from the terminal " works properly. - call RunDbgCmd(buf, ':debug echo Foo()') + call RunDbgCmd(buf, ':debug echo Foo()', ['cmd: echo Foo()']) " No breakpoints call RunDbgCmd(buf, 'breakl', ['No breakpoints defined']) @@ -814,9 +818,10 @@ func Test_Backtrace_CmdLine() \ '-S Xtest1.vim -c "debug call GlobalFunction()"', \ {'wait_for_ruler': 0}) - " Need to wait for the vim-in-terminal to be ready + " Need to wait for the vim-in-terminal to be ready. + " With valgrind this can take quite long. call CheckDbgOutput(buf, ['command line', - \ 'cmd: call GlobalFunction()']) + \ 'cmd: call GlobalFunction()'], #{msec: 5000}) " At this point the ontly thing in the stack is the cmdline call RunDbgCmd(buf, 'backtrace', [ @@ -967,14 +972,14 @@ func Test_debug_backtrace_level() " set a breakpoint and source file1.vim let buf = RunVimInTerminal( \ '-c "breakadd file 1 Xtest1.vim" -S Xtest1.vim', - \ #{ wait_for_ruler: 0 } ) + \ #{wait_for_ruler: 0}) call CheckDbgOutput(buf, [ \ 'Breakpoint in "' .. file1 .. '" line 1', \ 'Entering Debug mode. Type "cont" to continue.', \ 'command line..script ' .. file1, \ 'line 1: let s:file1_var = ''file1''' - \ ]) + \ ], #{msec: 5000}) " step through the initial declarations call RunDbgCmd(buf, 'step', [ 'line 2: let g:global_var = ''global''' ] ) diff --git a/src/nvim/testdir/test_diffmode.vim b/src/nvim/testdir/test_diffmode.vim index 1dbbe578c5..1cb71664bd 100644 --- a/src/nvim/testdir/test_diffmode.vim +++ b/src/nvim/testdir/test_diffmode.vim @@ -137,7 +137,7 @@ func Common_vert_split() " Test diffoff diffoff! - 1wincmd 2 + 1wincmd w let &diff = 1 let &fdm = diff_fdm let &fdc = diff_fdc @@ -744,17 +744,13 @@ func Test_diff_hlID() call diff_hlID(-1, 1)->synIDattr("name")->assert_equal("") - call assert_equal(diff_hlID(1, 1), hlID("DiffChange")) call diff_hlID(1, 1)->synIDattr("name")->assert_equal("DiffChange") - call assert_equal(diff_hlID(1, 2), hlID("DiffText")) call diff_hlID(1, 2)->synIDattr("name")->assert_equal("DiffText") call diff_hlID(2, 1)->synIDattr("name")->assert_equal("") - call assert_equal(diff_hlID(3, 1), hlID("DiffAdd")) call diff_hlID(3, 1)->synIDattr("name")->assert_equal("DiffAdd") - call diff_hlID(4, 1)->synIDattr("name")->assert_equal("") + eval 4->diff_hlID(1)->synIDattr("name")->assert_equal("") wincmd w - call assert_equal(diff_hlID(1, 1), hlID("DiffChange")) call assert_equal(synIDattr(diff_hlID(1, 1), "name"), "DiffChange") call assert_equal(synIDattr(diff_hlID(2, 1), "name"), "") call assert_equal(synIDattr(diff_hlID(3, 1), "name"), "") diff --git a/src/nvim/testdir/test_digraph.vim b/src/nvim/testdir/test_digraph.vim index acc34e5e7c..f08dff8605 100644 --- a/src/nvim/testdir/test_digraph.vim +++ b/src/nvim/testdir/test_digraph.vim @@ -211,6 +211,8 @@ func Test_digraphs() call Put_Dig("00") call Put_Dig("el") call assert_equal(['␀', 'ü', '∞', 'l'], getline(line('.')-3,line('.'))) + call assert_fails('exe "digraph a\<Esc> 100"', 'E104:') + call assert_fails('exe "digraph \<Esc>a 100"', 'E104:') call assert_fails('digraph xy z', 'E39:') call assert_fails('digraph x', 'E1214:') bw! @@ -491,6 +493,17 @@ func Test_show_digraph_cp1251() bwipe! endfunc +" Test for error in a keymap file +func Test_loadkeymap_error() + if !has('keymap') + return + endif + call assert_fails('loadkeymap', 'E105:') + call writefile(['loadkeymap', 'a'], 'Xkeymap') + call assert_fails('source Xkeymap', 'E791:') + call delete('Xkeymap') +endfunc + " Test for the characters displayed on the screen when entering a digraph func Test_entering_digraph() CheckRunVimInTerminal diff --git a/src/nvim/testdir/test_display.vim b/src/nvim/testdir/test_display.vim index 6938abbc28..217bb5d781 100644 --- a/src/nvim/testdir/test_display.vim +++ b/src/nvim/testdir/test_display.vim @@ -309,6 +309,88 @@ func Test_eob_fillchars() close endfunc +" Test for 'foldopen', 'foldclose' and 'foldsep' in 'fillchars' +func Test_fold_fillchars() + new + set fdc=2 foldenable foldmethod=manual + call setline(1, ['one', 'two', 'three', 'four', 'five']) + 2,4fold + " First check for the default setting for a closed fold + let lines = ScreenLines([1, 3], 8) + let expected = [ + \ ' one ', + \ '+ +-- 3', + \ ' five ' + \ ] + call assert_equal(expected, lines) + normal 2Gzo + " check the characters for an open fold + let lines = ScreenLines([1, 5], 8) + let expected = [ + \ ' one ', + \ '- two ', + \ '| three ', + \ '| four ', + \ ' five ' + \ ] + call assert_equal(expected, lines) + + " change the setting + set fillchars=vert:\|,fold:-,eob:~,foldopen:[,foldclose:],foldsep:- + + " check the characters for an open fold + let lines = ScreenLines([1, 5], 8) + let expected = [ + \ ' one ', + \ '[ two ', + \ '- three ', + \ '- four ', + \ ' five ' + \ ] + call assert_equal(expected, lines) + + " check the characters for a closed fold + normal 2Gzc + let lines = ScreenLines([1, 3], 8) + let expected = [ + \ ' one ', + \ '] +-- 3', + \ ' five ' + \ ] + call assert_equal(expected, lines) + + %bw! + set fillchars& fdc& foldmethod& foldenable& +endfunc + +func Test_local_fillchars() + CheckScreendump + + let lines =<< trim END + call setline(1, ['window 1']->repeat(3)) + setlocal fillchars=stl:1,stlnc:a,vert:=,eob:x + vnew + call setline(1, ['window 2']->repeat(3)) + setlocal fillchars=stl:2,stlnc:b,vert:+,eob:y + new + wincmd J + call setline(1, ['window 3']->repeat(3)) + setlocal fillchars=stl:3,stlnc:c,vert:<,eob:z + vnew + call setline(1, ['window 4']->repeat(3)) + setlocal fillchars=stl:4,stlnc:d,vert:>,eob:o + END + call writefile(lines, 'Xdisplayfillchars') + let buf = RunVimInTerminal('-S Xdisplayfillchars', #{rows: 12}) + call VerifyScreenDump(buf, 'Test_display_fillchars_1', {}) + + call term_sendkeys(buf, ":wincmd k\r") + call VerifyScreenDump(buf, 'Test_display_fillchars_2', {}) + + call StopVimInTerminal(buf) + call delete('Xdisplayfillchars') +endfunc + func Test_display_linebreak_breakat() new vert resize 25 diff --git a/src/nvim/testdir/test_edit.vim b/src/nvim/testdir/test_edit.vim index a09346a595..679b877ef6 100644 --- a/src/nvim/testdir/test_edit.vim +++ b/src/nvim/testdir/test_edit.vim @@ -262,22 +262,6 @@ func Test_edit_09() bw! endfunc -func Test_edit_10() - " Test for starting selectmode - new - set selectmode=key keymodel=startsel - call setline(1, ['abc', 'def', 'ghi']) - call cursor(1, 4) - call feedkeys("A\<s-home>start\<esc>", 'txin') - call assert_equal(['startdef', 'ghi'], getline(1, '$')) - " start select mode again with gv - set selectmode=cmd - call feedkeys('gvabc', 'xt') - call assert_equal('abctdef', getline(1)) - set selectmode= keymodel= - bw! -endfunc - func Test_edit_11() " Test that indenting kicks in new @@ -729,23 +713,32 @@ endfunc func Test_edit_CTRL_N() " Check keyword completion - new - set complete=. - call setline(1, ['INFER', 'loWER', '', '', ]) - call cursor(3, 1) - call feedkeys("Ai\<c-n>\<cr>\<esc>", "tnix") - call feedkeys("ILO\<c-n>\<cr>\<esc>", 'tnix') - call assert_equal(['INFER', 'loWER', 'i', 'LO', '', ''], getline(1, '$')) - %d - call setline(1, ['INFER', 'loWER', '', '', ]) - call cursor(3, 1) - set ignorecase infercase - call feedkeys("Ii\<c-n>\<cr>\<esc>", "tnix") - call feedkeys("ILO\<c-n>\<cr>\<esc>", 'tnix') - call assert_equal(['INFER', 'loWER', 'infer', 'LOWER', '', ''], getline(1, '$')) - - set noignorecase noinfercase complete& - bw! + " for e in ['latin1', 'utf-8'] + for e in ['utf-8'] + exe 'set encoding=' .. e + new + set complete=. + call setline(1, ['INFER', 'loWER', '', '', ]) + call cursor(3, 1) + call feedkeys("Ai\<c-n>\<cr>\<esc>", "tnix") + call feedkeys("ILO\<c-n>\<cr>\<esc>", 'tnix') + call assert_equal(['INFER', 'loWER', 'i', 'LO', '', ''], getline(1, '$'), e) + %d + call setline(1, ['INFER', 'loWER', '', '', ]) + call cursor(3, 1) + set ignorecase infercase + call feedkeys("Ii\<c-n>\<cr>\<esc>", "tnix") + call feedkeys("ILO\<c-n>\<cr>\<esc>", 'tnix') + call assert_equal(['INFER', 'loWER', 'infer', 'LOWER', '', ''], getline(1, '$'), e) + set noignorecase noinfercase + %d + call setline(1, ['one word', 'two word']) + exe "normal! Goo\<C-P>\<C-X>\<C-P>" + call assert_equal('one word', getline(3)) + %d + set complete& + bw! + endfor endfunc func Test_edit_CTRL_O() @@ -909,6 +902,24 @@ func Test_edit_CTRL_T() bw! endfunc +" Test thesaurus completion with different encodings +func Test_thesaurus_complete_with_encoding() + call writefile(['angry furious mad enraged'], 'Xthesaurus') + set thesaurus=Xthesaurus + " for e in ['latin1', 'utf-8'] + for e in ['utf-8'] + exe 'set encoding=' .. e + new + call setline(1, 'mad') + call cursor(1, 1) + call feedkeys("A\<c-x>\<c-t>\<cr>\<esc>", 'tnix') + call assert_equal(['mad', ''], getline(1, '$')) + bw! + endfor + set thesaurus= + call delete('Xthesaurus') +endfunc + " Test 'thesaurusfunc' func MyThesaurus(findstart, base) let mythesaurus = [ @@ -1620,6 +1631,7 @@ func Test_edit_InsertLeave_undo() bwipe! au! InsertLeave call delete('XtestUndo') + call delete(undofile('XtestUndo')) set undofile& endfunc @@ -1687,11 +1699,11 @@ func Test_edit_noesckeys() endfunc " Test for running an invalid ex command in insert mode using CTRL-O -" Note that vim has a hard-coded sleep of 3 seconds. So this test will take -" more than 3 seconds to complete. func Test_edit_ctrl_o_invalid_cmd() new set showmode showcmd + " Avoid a sleep of 3 seconds. Zero might have side effects. + " call test_override('ui_delay', 50) let caught_e492 = 0 try call feedkeys("i\<C-O>:invalid\<CR>abc\<Esc>", "xt") @@ -1701,6 +1713,18 @@ func Test_edit_ctrl_o_invalid_cmd() call assert_equal(1, caught_e492) call assert_equal('abc', getline(1)) set showmode& showcmd& + " call test_override('ui_delay', 0) + close! +endfunc + +" Test for editing a file with a very long name +func Test_edit_illegal_filename() + CheckEnglish + new + redir => msg + exe 'edit ' . repeat('f', 5000) + redir END + call assert_match("Illegal file name$", split(msg, "\n")[0]) close! endfunc @@ -1763,6 +1787,102 @@ func Test_edit_is_a_directory() call delete(dirname, 'rf') endfunc +" Test for editing a file using invalid file encoding +func Test_edit_invalid_encoding() + CheckEnglish + call writefile([], 'Xfile') + redir => msg + new ++enc=axbyc Xfile + redir END + call assert_match('\[NOT converted\]', msg) + call delete('Xfile') + close! +endfunc + +" Test for the "charconvert" option +func Test_edit_charconvert() + CheckEnglish + call writefile(['one', 'two'], 'Xfile') + + " set 'charconvert' to a non-existing function + set charconvert=NonExitingFunc() + new + let caught_e117 = v:false + try + redir => msg + edit ++enc=axbyc Xfile + catch /E117:/ + let caught_e117 = v:true + finally + redir END + endtry + call assert_true(caught_e117) + call assert_equal(['one', 'two'], getline(1, '$')) + call assert_match("Conversion with 'charconvert' failed", msg) + close! + set charconvert& + + " 'charconvert' function doesn't create a output file + func Cconv1() + endfunc + set charconvert=Cconv1() + new + redir => msg + edit ++enc=axbyc Xfile + redir END + call assert_equal(['one', 'two'], getline(1, '$')) + call assert_match("can't read output of 'charconvert'", msg) + close! + delfunc Cconv1 + set charconvert& + + " 'charconvert' function to convert to upper case + func Cconv2() + let data = readfile(v:fname_in) + call map(data, 'toupper(v:val)') + call writefile(data, v:fname_out) + endfunc + set charconvert=Cconv2() + new Xfile + write ++enc=ucase Xfile1 + call assert_equal(['ONE', 'TWO'], readfile('Xfile1')) + call delete('Xfile1') + close! + delfunc Cconv2 + set charconvert& + + " 'charconvert' function removes the input file + func Cconv3() + call delete(v:fname_in) + endfunc + set charconvert=Cconv3() + new + call assert_fails('edit ++enc=lcase Xfile', 'E202:') + call assert_equal([''], getline(1, '$')) + close! + delfunc Cconv3 + set charconvert& + + call delete('Xfile') +endfunc + +" Test for editing a file without read permission +func Test_edit_file_no_read_perm() + CheckUnix + CheckNotBSD + call writefile(['one', 'two'], 'Xfile') + call setfperm('Xfile', '-w-------') + new + redir => msg + edit Xfile + redir END + call assert_equal(1, &readonly) + call assert_equal([''], getline(1, '$')) + call assert_match('\[Permission Denied\]', msg) + close! + call delete('Xfile') +endfunc + " Using :edit without leaving 'insertmode' should not cause Insert mode to be " re-entered immediately after <C-L> func Test_edit_insertmode_ex_edit() diff --git a/src/nvim/testdir/test_environ.vim b/src/nvim/testdir/test_environ.vim index dd34983ee5..d8344817f5 100644 --- a/src/nvim/testdir/test_environ.vim +++ b/src/nvim/testdir/test_environ.vim @@ -28,6 +28,26 @@ func Test_setenv() call assert_equal(v:null, getenv('TEST ENV')) endfunc +func Test_special_env() + " The value for $HOME is cached internally by Vim, ensure the value is up to + " date. + let orig_ENV = $HOME + + let $HOME = 'foo' + call assert_equal('foo', expand('~')) + " old $HOME value is kept until a new one is set + unlet $HOME + call assert_equal('foo', expand('~')) + + call setenv('HOME', 'bar') + call assert_equal('bar', expand('~')) + " old $HOME value is kept until a new one is set + call setenv('HOME', v:null) + call assert_equal('bar', expand('~')) + + let $HOME = orig_ENV +endfunc + func Test_external_env() call setenv('FOO', 'HelloWorld') if has('win32') diff --git a/src/nvim/testdir/test_eval_stuff.vim b/src/nvim/testdir/test_eval_stuff.vim index 811c6c946d..eff1376d3c 100644 --- a/src/nvim/testdir/test_eval_stuff.vim +++ b/src/nvim/testdir/test_eval_stuff.vim @@ -75,6 +75,18 @@ func Test_for_invalid() redraw endfunc +func Test_for_over_null_string() + let save_enc = &enc + " set enc=iso8859 + let cnt = 0 + for c in v:_null_string + let cnt += 1 + endfor + call assert_equal(0, cnt) + + let &enc = save_enc +endfunc + func Test_readfile_binary() new call setline(1, ['one', 'two', 'three']) diff --git a/src/nvim/testdir/test_excmd.vim b/src/nvim/testdir/test_excmd.vim index dac7a6989d..9a9e5c546b 100644 --- a/src/nvim/testdir/test_excmd.vim +++ b/src/nvim/testdir/test_excmd.vim @@ -568,10 +568,12 @@ endfunc " Test for the :verbose command func Test_verbose_cmd() - call assert_equal([' verbose=1'], split(execute('verbose set vbs'), "\n")) + set verbose=3 + call assert_match(' verbose=1\n\s*Last set from ', execute('verbose set vbs'), "\n") call assert_equal([' verbose=0'], split(execute('0verbose set vbs'), "\n")) - let l = execute("4verbose set verbose | set verbose") - call assert_equal([' verbose=4', ' verbose=0'], split(l, "\n")) + set verbose=0 + call assert_match(' verbose=4\n\s*Last set from .*\n verbose=0', + \ execute("4verbose set verbose | set verbose")) endfunc " Test for the :delete command and the related abbreviated commands diff --git a/src/nvim/testdir/test_exit.vim b/src/nvim/testdir/test_exit.vim index befcaec2b2..37be293950 100644 --- a/src/nvim/testdir/test_exit.vim +++ b/src/nvim/testdir/test_exit.vim @@ -95,7 +95,7 @@ func Test_exit_code() [CODE] if RunVim(before, ['quit'], '') - call assert_equal(['qp = null', 'ep = null', 'lp = 0', 'l = 0'], readfile('Xtestout')) + call assert_equal(['qp = v:null', 'ep = v:null', 'lp = 0', 'l = 0'], readfile('Xtestout')) endif call delete('Xtestout') diff --git a/src/nvim/testdir/test_expand.vim b/src/nvim/testdir/test_expand.vim index 383921bb82..aa131a49ff 100644 --- a/src/nvim/testdir/test_expand.vim +++ b/src/nvim/testdir/test_expand.vim @@ -1,6 +1,7 @@ " Test for expanding file names source shared.vim +source check.vim func Test_with_directories() call mkdir('Xdir1') @@ -77,10 +78,11 @@ func Test_expandcmd() edit a1a2a3.rb call assert_equal('make b1b2b3.rb a1a2a3 Xfile.o', expandcmd('make %:gs?a?b? %< #<.o')) - call assert_fails('call expandcmd("make <afile>")', 'E495:') - call assert_fails('call expandcmd("make <afile>")', 'E495:') + call assert_equal('make <afile>', expandcmd("make <afile>")) + call assert_equal('make <amatch>', expandcmd("make <amatch>")) + call assert_equal('make <abuf>', expandcmd("make <abuf>")) enew - call assert_fails('call expandcmd("make %")', 'E499:') + call assert_equal('make %', expandcmd("make %")) let $FOO="blue\tsky" call setline(1, "$FOO") call assert_equal("grep pat blue\tsky", expandcmd('grep pat <cfile>')) @@ -93,6 +95,11 @@ func Test_expandcmd() 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}')) + endif + unlet $FOO close! endfunc @@ -100,22 +107,30 @@ endfunc " Test for expanding <sfile>, <slnum> and <sflnum> outside of sourcing a script func Test_source_sfile() let lines =<< trim [SCRIPT] - :call assert_fails('echo expandcmd("<sfile>")', 'E498:') - :call assert_fails('echo expandcmd("<slnum>")', 'E842:') - :call assert_fails('echo expandcmd("<sflnum>")', 'E961:') - :call assert_fails('call expandcmd("edit <cfile>")', 'E446:') - :call assert_fails('call expandcmd("edit #")', 'E194:') - :call assert_fails('call expandcmd("edit #<2")', 'E684:') - :call assert_fails('call expandcmd("edit <cword>")', 'E348:') - :call assert_fails('call expandcmd("edit <cexpr>")', 'E348:') + :call assert_equal('<sfile>', expandcmd("<sfile>")) + :call assert_equal('<slnum>', expandcmd("<slnum>")) + :call assert_equal('<sflnum>', expandcmd("<sflnum>")) + :call assert_equal('edit <cfile>', expandcmd("edit <cfile>")) + :call assert_equal('edit #', expandcmd("edit #")) + :call assert_equal('edit #<2', expandcmd("edit #<2")) + :call assert_equal('edit <cword>', expandcmd("edit <cword>")) + :call assert_equal('edit <cexpr>', expandcmd("edit <cexpr>")) :call assert_fails('autocmd User MyCmd echo "<sfile>"', 'E498:') + : + :call assert_equal('', expand('<script>')) + :verbose echo expand('<script>') + :call add(v:errors, v:errmsg) + :verbose echo expand('<sfile>') + :call add(v:errors, v:errmsg) :call writefile(v:errors, 'Xresult') :qall! - [SCRIPT] call writefile(lines, 'Xscript') if RunVim([], [], '--clean -s Xscript') - call assert_equal([], readfile('Xresult')) + call assert_equal([ + \ 'E1274: No script file name to substitute for "<script>"', + \ 'E498: no :source file name to substitute for "<sfile>"'], + \ readfile('Xresult')) endif call delete('Xscript') call delete('Xresult') @@ -131,7 +146,72 @@ func Test_expand_filename_multicmd() call assert_equal(4, winnr('$')) call assert_equal('foo!', bufname(winbufnr(1))) call assert_equal('foo', bufname(winbufnr(2))) + call assert_fails('e %:s/.*//', 'E500:') %bwipe! endfunc +func Test_expandcmd_shell_nonomatch() + CheckNotMSWindows + call assert_equal('$*', expandcmd('$*')) +endfunc + +func Test_expand_script_source() + let lines0 =<< trim [SCRIPT] + call extend(g:script_level, [expand('<script>:t')]) + so Xscript1 + func F0() + call extend(g:func_level, [expand('<script>:t')]) + endfunc + + au User * call extend(g:au_level, [expand('<script>:t')]) + [SCRIPT] + + let lines1 =<< trim [SCRIPT] + call extend(g:script_level, [expand('<script>:t')]) + so Xscript2 + func F1() + call extend(g:func_level, [expand('<script>:t')]) + endfunc + + au User * call extend(g:au_level, [expand('<script>:t')]) + [SCRIPT] + + let lines2 =<< trim [SCRIPT] + call extend(g:script_level, [expand('<script>:t')]) + func F2() + call extend(g:func_level, [expand('<script>:t')]) + endfunc + + au User * call extend(g:au_level, [expand('<script>:t')]) + [SCRIPT] + + call writefile(lines0, 'Xscript0') + call writefile(lines1, 'Xscript1') + call writefile(lines2, 'Xscript2') + + " Check the expansion of <script> at different levels. + let g:script_level = [] + let g:func_level = [] + let g:au_level = [] + + so Xscript0 + call F0() + call F1() + call F2() + doautocmd User + + call assert_equal(['Xscript0', 'Xscript1', 'Xscript2'], g:script_level) + call assert_equal(['Xscript0', 'Xscript1', 'Xscript2'], g:func_level) + call assert_equal(['Xscript2', 'Xscript1', 'Xscript0'], g:au_level) + + unlet g:script_level g:func_level + delfunc F0 + delfunc F1 + delfunc F2 + + call delete('Xscript0') + call delete('Xscript1') + call delete('Xscript2') +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_expand_func.vim b/src/nvim/testdir/test_expand_func.vim index b48c2e8a19..80bfdb8553 100644 --- a/src/nvim/testdir/test_expand_func.vim +++ b/src/nvim/testdir/test_expand_func.vim @@ -37,17 +37,54 @@ func Test_expand_sflnum() delcommand Flnum endfunc -func Test_expand_sfile() +func Test_expand_sfile_and_stack() call assert_match('test_expand_func\.vim$', s:sfile) - call assert_match('^function .*\.\.Test_expand_sfile$', expand('<sfile>')) + let expected = 'script .*testdir/runtest.vim\[\d\+\]\.\.function RunTheTest\[\d\+\]\.\.Test_expand_sfile_and_stack' + call assert_match(expected .. '$', expand('<sfile>')) + call assert_match(expected .. '\[4\]$' , expand('<stack>')) " Call in script-local function - call assert_match('^function .*\.\.Test_expand_sfile\[5\]\.\.<SNR>\d\+_expand_sfile$', s:expand_sfile()) + call assert_match('script .*testdir/runtest.vim\[\d\+\]\.\.function RunTheTest\[\d\+\]\.\.Test_expand_sfile_and_stack\[7\]\.\.<SNR>\d\+_expand_sfile$', s:expand_sfile()) " Call in command command Sfile echo expand('<sfile>') - call assert_match('^function .*\.\.Test_expand_sfile$', trim(execute('Sfile'))) + call assert_match('script .*testdir/runtest.vim\[\d\+\]\.\.function RunTheTest\[\d\+\]\.\.Test_expand_sfile_and_stack$', trim(execute('Sfile'))) delcommand Sfile + + " Use <stack> from sourced script. + let lines =<< trim END + " comment here + let g:stack_value = expand('<stack>') + END + call writefile(lines, 'Xstack') + source Xstack + call assert_match('\<Xstack\[2\]$', g:stack_value) + unlet g:stack_value + call delete('Xstack') + + if exists('+shellslash') + call mkdir('Xshellslash') + let lines =<< trim END + let g:stack1 = expand('<stack>') + set noshellslash + let g:stack2 = expand('<stack>') + set shellslash + let g:stack3 = expand('<stack>') + END + call writefile(lines, 'Xshellslash/Xstack') + " Test that changing 'shellslash' always affects the result of expand() + " when sourcing a script multiple times. + for i in range(2) + source Xshellslash/Xstack + call assert_match('\<Xshellslash/Xstack\[1\]$', g:stack1) + call assert_match('\<Xshellslash\\Xstack\[3\]$', g:stack2) + call assert_match('\<Xshellslash/Xstack\[5\]$', g:stack3) + unlet g:stack1 + unlet g:stack2 + unlet g:stack3 + endfor + call delete('Xshellslash', 'rf') + endif endfunc func Test_expand_slnum() @@ -70,10 +107,15 @@ endfunc func Test_expand() new - call assert_equal("", expand('%:S')) + call assert_equal("", expand('%:S')) call assert_equal('3', '<slnum>'->expand()) call assert_equal(['4'], expand('<slnum>', v:false, v:true)) " Don't add any line above this, otherwise <slnum> will change. + call assert_equal("", expand('%')) + set verbose=1 + call assert_equal("", expand('%')) + set verbose=0 + call assert_equal("", expand('%:p')) quit endfunc diff --git a/src/nvim/testdir/test_expr.vim b/src/nvim/testdir/test_expr.vim index 5b10e691e5..15622cd6fe 100644 --- a/src/nvim/testdir/test_expr.vim +++ b/src/nvim/testdir/test_expr.vim @@ -547,6 +547,7 @@ func Test_funcref() call assert_fails('echo funcref("{")', 'E475:') let OneByRef = funcref("One", repeat(["foo"], 20)) call assert_fails('let OneByRef = funcref("One", repeat(["foo"], 21))', 'E118:') + call assert_fails('echo function("min") =~ function("min")', 'E694:') endfunc func Test_setmatches() diff --git a/src/nvim/testdir/test_filechanged.vim b/src/nvim/testdir/test_filechanged.vim index c6e781a1ef..fef0eb732f 100644 --- a/src/nvim/testdir/test_filechanged.vim +++ b/src/nvim/testdir/test_filechanged.vim @@ -140,7 +140,8 @@ func Test_FileChangedShell_edit() endfunc func Test_FileChangedShell_edit_dialog() - throw 'Skipped: requires a UI to be active' + " requires a UI to be active + throw 'Skipped: use test/functional/legacy/filechanged_spec.lua' CheckNotGui CheckUnix " Using low level feedkeys() does not work on MS-Windows. @@ -190,7 +191,8 @@ func Test_FileChangedShell_edit_dialog() endfunc func Test_file_changed_dialog() - throw 'Skipped: requires a UI to be active' + " requires a UI to be active + throw 'Skipped: use test/functional/legacy/filechanged_spec.lua' CheckUnix CheckNotGui au! FileChangedShell @@ -242,6 +244,15 @@ func Test_file_changed_dialog() call assert_equal(1, line('$')) call assert_equal('new line', getline(1)) + " File created after starting to edit it + call delete('Xchanged_d') + new Xchanged_d + call writefile(['one'], 'Xchanged_d') + call feedkeys('L', 'L') + checktime Xchanged_d + call assert_equal(['one'], getline(1, '$')) + close! + bwipe! call delete('Xchanged_d') endfunc diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim index eedad15e9e..68ce9148a4 100644 --- a/src/nvim/testdir/test_filetype.vim +++ b/src/nvim/testdir/test_filetype.vim @@ -71,6 +71,7 @@ let s:filename_checks = { \ 'asciidoc': ['file.asciidoc', 'file.adoc'], \ 'asn': ['file.asn', 'file.asn1'], \ 'asterisk': ['asterisk/file.conf', 'asterisk/file.conf-file', 'some-asterisk/file.conf', 'some-asterisk/file.conf-file'], + \ 'astro': ['file.astro'], \ 'atlas': ['file.atl', 'file.as'], \ 'autohotkey': ['file.ahk'], \ 'autoit': ['file.au3'], @@ -208,6 +209,7 @@ let s:filename_checks = { \ 'gdmo': ['file.mo', 'file.gdmo'], \ 'gdresource': ['file.tscn', 'file.tres'], \ 'gdscript': ['file.gd'], + \ 'gdshader': ['file.gdshader', 'file.shader'], \ 'gedcom': ['file.ged', 'lltxxxxx.txt', '/tmp/lltmp', '/tmp/lltmp-file', 'any/tmp/lltmp', 'any/tmp/lltmp-file'], \ 'gemtext': ['file.gmi', 'file.gemini'], \ 'gift': ['file.gift'], @@ -360,7 +362,7 @@ let s:filename_checks = { \ 'monk': ['file.isc', 'file.monk', 'file.ssc', 'file.tsc'], \ 'moo': ['file.moo'], \ 'moonscript': ['file.moon'], - \ 'mp': ['file.mp'], + \ 'mp': ['file.mp', 'file.mpxl', 'file.mpiv', 'file.mpvi'], \ 'mplayerconf': ['mplayer.conf', '/.mplayer/config', 'any/.mplayer/config'], \ 'mrxvtrc': ['mrxvtrc', '.mrxvtrc'], \ 'msidl': ['file.odl', 'file.mof'], @@ -441,6 +443,7 @@ let s:filename_checks = { \ 'python': ['file.py', 'file.pyw', '.pythonstartup', '.pythonrc', 'file.ptl', 'file.pyi', 'SConstruct'], \ 'ql': ['file.ql', 'file.qll'], \ 'quake': ['anybaseq2/file.cfg', 'anyid1/file.cfg', 'quake3/file.cfg', 'baseq2/file.cfg', 'id1/file.cfg', 'quake1/file.cfg', 'some-baseq2/file.cfg', 'some-id1/file.cfg', 'some-quake1/file.cfg'], + \ 'quarto': ['file.qmd'], \ 'r': ['file.r'], \ 'radiance': ['file.rad', 'file.mat'], \ 'raku': ['file.pm6', 'file.p6', 'file.t6', 'file.pod6', 'file.raku', 'file.rakumod', 'file.rakudoc', 'file.rakutest'], @@ -586,6 +589,9 @@ let s:filename_checks = { \ 'usw2kagtlog': ['usw2kagt.log', 'USW2KAGT.LOG', 'usw2kagt.file.log', 'USW2KAGT.FILE.LOG', 'file.usw2kagt.log', 'FILE.USW2KAGT.LOG'], \ 'vala': ['file.vala'], \ 'vb': ['file.sba', 'file.vb', 'file.vbs', 'file.dsm', 'file.ctl'], + \ 'vdmpp': ['file.vpp', 'file.vdmpp'], + \ 'vdmrt': ['file.vdmrt'], + \ 'vdmsl': ['file.vdm', 'file.vdmsl'], \ 'vera': ['file.vr', 'file.vri', 'file.vrh'], \ 'verilog': ['file.v'], \ 'verilogams': ['file.va', 'file.vams'], diff --git a/src/nvim/testdir/test_functions.vim b/src/nvim/testdir/test_functions.vim index c11e7b4fea..147eda5b0a 100644 --- a/src/nvim/testdir/test_functions.vim +++ b/src/nvim/testdir/test_functions.vim @@ -1420,12 +1420,15 @@ func Test_trim() call assert_equal("vim", trim(" vim ", " ", 0)) call assert_equal("vim ", trim(" vim ", " ", 1)) call assert_equal(" vim", trim(" vim ", " ", 2)) - call assert_fails('call trim(" vim ", " ", [])', 'E745:') - call assert_fails('call trim(" vim ", " ", -1)', 'E475:') - call assert_fails('call trim(" vim ", " ", 3)', 'E475:') + call assert_fails('eval trim(" vim ", " ", [])', 'E745:') + call assert_fails('eval trim(" vim ", " ", -1)', 'E475:') + call assert_fails('eval trim(" vim ", " ", 3)', 'E475:') + call assert_fails('eval trim(" vim ", 0)', 'E475:') let chars = join(map(range(1, 0x20) + [0xa0], {n -> n->nr2char()}), '') call assert_equal("x", trim(chars . "x" . chars)) + + call assert_fails('let c=trim([])', 'E730:') endfunc " Test for reg_recording() and reg_executing() @@ -1697,6 +1700,63 @@ func Test_platform_name() endif endfunc +" Test confirm({msg} [, {choices} [, {default} [, {type}]]]) +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 + + call feedkeys('o', 'L') + let a = confirm('Press O to proceed') + call assert_equal(1, a) + + call feedkeys('y', 'L') + let a = 'Are you sure?'->confirm("&Yes\n&No") + call assert_equal(1, a) + + call feedkeys('n', 'L') + let a = confirm('Are you sure?', "&Yes\n&No") + call assert_equal(2, a) + + " confirm() should return 0 when pressing CTRL-C. + call feedkeys("\<C-c>", 'L') + let a = confirm('Are you sure?', "&Yes\n&No") + call assert_equal(0, a) + + " <Esc> requires another character to avoid it being seen as the start of an + " escape sequence. Zero should be harmless. + eval "\<Esc>0"->feedkeys('L') + let a = confirm('Are you sure?', "&Yes\n&No") + call assert_equal(0, a) + + " Default choice is returned when pressing <CR>. + call feedkeys("\<CR>", 'L') + let a = confirm('Are you sure?', "&Yes\n&No") + call assert_equal(1, a) + + call feedkeys("\<CR>", 'L') + let a = confirm('Are you sure?', "&Yes\n&No", 2) + call assert_equal(2, a) + + call feedkeys("\<CR>", 'L') + let a = confirm('Are you sure?', "&Yes\n&No", 0) + call assert_equal(0, a) + + " Test with the {type} 4th argument + for type in ['Error', 'Question', 'Info', 'Warning', 'Generic'] + call feedkeys('y', 'L') + let a = confirm('Are you sure?', "&Yes\n&No\n", 1, type) + call assert_equal(1, a) + endfor + + call assert_fails('call confirm([])', 'E730:') + call assert_fails('call confirm("Are you sure?", [])', 'E730:') + call assert_fails('call confirm("Are you sure?", "&Yes\n&No\n", [])', 'E745:') + call assert_fails('call confirm("Are you sure?", "&Yes\n&No\n", 0, [])', 'E730:') +endfunc + func Test_readdir() call mkdir('Xdir') call writefile([], 'Xdir/foo.txt') @@ -1724,7 +1784,7 @@ func Test_readdir() let files = readdir('Xdir', {x -> len(add(l, x)) == 2 ? -1 : 1}) call assert_equal(1, len(files)) - call delete('Xdir', 'rf') + eval 'Xdir'->delete('rf') endfunc func Test_delete_rf() @@ -1767,6 +1827,16 @@ endfunc func Test_char2nr() call assert_equal(12354, char2nr('あ', 1)) + call assert_equal(120, 'x'->char2nr()) +endfunc + +func Test_charclass() + call assert_equal(0, charclass(' ')) + call assert_equal(1, charclass('.')) + call assert_equal(2, charclass('x')) + call assert_equal(3, charclass("\u203c")) + " this used to crash vim + call assert_equal(0, "xxx"[-1]->charclass()) endfunc func Test_eventhandler() @@ -1810,6 +1880,22 @@ func Test_bufadd_bufload() exe 'bwipe ' .. buf2 call assert_equal(0, bufexists(buf2)) + " When 'buftype' is "nofile" then bufload() does not read the file. + " Other values too. + for val in [['nofile', 0], + \ ['nowrite', 1], + \ ['acwrite', 1], + \ ['quickfix', 0], + \ ['help', 1], + \ ['prompt', 0], + \ ] + bwipe! XotherName + let buf = bufadd('XotherName') + call setbufvar(buf, '&bt', val[0]) + call bufload(buf) + call assert_equal(val[1] ? ['some', 'text'] : [''], getbufline(buf, 1, '$'), val[0]) + endfor + bwipe someName bwipe XotherName call assert_equal(0, bufexists('someName')) diff --git a/src/nvim/testdir/test_gf.vim b/src/nvim/testdir/test_gf.vim index feae44e5ee..b2bb189688 100644 --- a/src/nvim/testdir/test_gf.vim +++ b/src/nvim/testdir/test_gf.vim @@ -191,6 +191,22 @@ func Test_gf_error() au! InsertCharPre bwipe! + + " gf is not allowed when buffer is locked + new + augroup Test_gf + au! + au OptionSet diff norm! gf + augroup END + call setline(1, ['Xfile1', 'line2', 'line3', 'line4']) + " Nvim does not support test_override() + " call test_override('starting', 1) + " call assert_fails('diffthis', 'E788:') + " call test_override('starting', 0) + augroup Test_gf + au! + augroup END + bw! endfunc " If a file is not found by 'gf', then 'includeexpr' should be used to locate diff --git a/src/nvim/testdir/test_global.vim b/src/nvim/testdir/test_global.vim index 947f7efc7c..cb6851250c 100644 --- a/src/nvim/testdir/test_global.vim +++ b/src/nvim/testdir/test_global.vim @@ -9,7 +9,10 @@ func Test_yank_put_clipboard() set clipboard=unnamed g/^/normal yyp call assert_equal(['a', 'a', 'b', 'b', 'c', 'c'], getline(1, 6)) - + set clipboard=unnamed,unnamedplus + call setline(1, ['a', 'b', 'c']) + g/^/normal yyp + call assert_equal(['a', 'a', 'b', 'b', 'c', 'c'], getline(1, 6)) set clipboard& bwipe! endfunc diff --git a/src/nvim/testdir/test_goto.vim b/src/nvim/testdir/test_goto.vim index 49095400ef..6d029ffda2 100644 --- a/src/nvim/testdir/test_goto.vim +++ b/src/nvim/testdir/test_goto.vim @@ -122,6 +122,24 @@ func Test_gd() call XTest_goto_decl('gd', lines, 3, 14) endfunc +" Using gd to jump to a declaration in a fold +func Test_gd_with_fold() + new + let lines =<< trim END + #define ONE 1 + #define TWO 2 + #define THREE 3 + + TWO + END + call setline(1, lines) + 1,3fold + call feedkeys('Ggd', 'xt') + call assert_equal(2, line('.')) + call assert_equal(-1, foldclosedend(2)) + bw! +endfunc + func Test_gd_not_local() let lines =<< trim [CODE] int func1(void) diff --git a/src/nvim/testdir/test_highlight.vim b/src/nvim/testdir/test_highlight.vim index efdf44a0d6..8e808a00d0 100644 --- a/src/nvim/testdir/test_highlight.vim +++ b/src/nvim/testdir/test_highlight.vim @@ -731,7 +731,8 @@ func Test_1_highlight_Normalgroup_exists() endif endfunc -function Test_no_space_before_xxx() +" Do this test last, sometimes restoring the columns doesn't work +func Test_z_no_space_before_xxx() " Note: we need to create this highlight group in the test because it does not exist in Neovim execute('hi StatusLineTermNC ctermfg=green') let l:org_columns = &columns @@ -739,7 +740,7 @@ function Test_no_space_before_xxx() let l:hi_StatusLineTermNC = join(split(execute('hi StatusLineTermNC'))) call assert_match('StatusLineTermNC xxx', l:hi_StatusLineTermNC) let &columns = l:org_columns -endfunction +endfunc " Test for :highlight command errors func Test_highlight_cmd_errors() diff --git a/src/nvim/testdir/test_history.vim b/src/nvim/testdir/test_history.vim index feb521e232..f1c31dee04 100644 --- a/src/nvim/testdir/test_history.vim +++ b/src/nvim/testdir/test_history.vim @@ -95,6 +95,23 @@ function Test_History() call assert_fails('call histnr([])', 'E730:') call assert_fails('history xyz', 'E488:') call assert_fails('history ,abc', 'E488:') + call assert_fails('call histdel(":", "\\%(")', 'E53:') +endfunction + +function Test_history_truncates_long_entry() + " History entry short enough to fit on the screen should not be truncated. + call histadd(':', 'echo x' .. repeat('y', &columns - 17) .. 'z') + let a = execute('history : -1') + + call assert_match("^\n # cmd history\n" + \ .. "> *\\d\\+ echo x" .. repeat('y', &columns - 17) .. 'z$', a) + + " Long history entry should be truncated to fit on the screen, with, '...' + " inserted in the string to indicate the that there is truncation. + call histadd(':', 'echo x' .. repeat('y', &columns - 16) .. 'z') + let a = execute('history : -1') + call assert_match("^\n # cmd history\n" + \ .. "> *\\d\\+ echo xy\\+\.\.\.y\\+z$", a) endfunction function Test_Search_history_window() diff --git a/src/nvim/testdir/test_ins_complete.vim b/src/nvim/testdir/test_ins_complete.vim index 179218e48a..3e563f29f9 100644 --- a/src/nvim/testdir/test_ins_complete.vim +++ b/src/nvim/testdir/test_ins_complete.vim @@ -44,11 +44,11 @@ func Test_ins_complete() exe "normal o\<C-X>\<C-P>\<C-P>\<C-X>\<C-X>\<C-N>\<C-X>\<C-N>\<C-N>" call assert_equal('run1 run2', getline('.')) - set cpt=.,w,i + set cpt=.,\ ,w,i " i-add-expands and switches to local exe "normal OM\<C-N>\<C-X>\<C-N>\<C-X>\<C-N>\<C-X>\<C-X>\<C-X>\<C-P>" call assert_equal("Makefile\tto\trun3", getline('.')) - " add-expands lines (it would end in an empty line if it didn't ignored + " add-expands lines (it would end in an empty line if it didn't ignore " itself) exe "normal o\<C-X>\<C-L>\<C-X>\<C-L>\<C-P>\<C-P>" call assert_equal("Makefile\tto\trun3", getline('.')) @@ -68,6 +68,11 @@ func Test_ins_complete() call assert_equal('Xtest11.one', getline('.')) normal ddk + " Test for expanding a non-existing filename + exe "normal oa1b2X3Y4\<C-X>\<C-F>" + call assert_equal('a1b2X3Y4', getline('.')) + normal ddk + set cpt=w " checks make_cyclic in other window exe "normal oST\<C-N>\<C-P>\<C-P>\<C-P>\<C-P>" @@ -682,6 +687,21 @@ func Test_complete_func_error() call assert_equal([], complete_info(['items']).items) endfunc +" Test for recursively starting completion mode using complete() +func Test_recursive_complete_func() + func ListColors() + call complete(5, ["red", "blue"]) + return '' + endfunc + new + call setline(1, ['a1', 'a2']) + set complete=. + exe "normal Goa\<C-X>\<C-L>\<C-R>=ListColors()\<CR>\<C-N>" + call assert_equal('a2blue', getline(3)) + delfunc ListColors + bw! +endfunc + " Test for completing words following a completed word in a line func Test_complete_wrapscan() " complete words from another buffer @@ -721,6 +741,17 @@ func Test_complete_across_line() close! endfunc +" Test for completing words with a '.' at the end of a word. +func Test_complete_joinspaces() + new + call setline(1, ['one two.', 'three. four']) + set joinspaces + exe "normal Goon\<C-P>\<C-X>\<C-P>\<C-X>\<C-P>\<C-X>\<C-P>\<C-X>\<C-P>" + call assert_equal("one two. three. four", getline(3)) + set joinspaces& + bw! +endfunc + " Test for using CTRL-L to add one character when completing matching func Test_complete_add_onechar() new @@ -741,6 +772,39 @@ func Test_complete_add_onechar() close! endfunc +" Test for using CTRL-X CTRL-L to complete whole lines lines +func Test_complete_wholeline() + new + " complete one-line + call setline(1, ['a1', 'a2']) + exe "normal ggoa\<C-X>\<C-L>" + call assert_equal(['a1', 'a1', 'a2'], getline(1, '$')) + " go to the next match (wrapping around the buffer) + exe "normal 2GCa\<C-X>\<C-L>\<C-N>" + call assert_equal(['a1', 'a', 'a2'], getline(1, '$')) + " go to the next match + exe "normal 2GCa\<C-X>\<C-L>\<C-N>\<C-N>" + call assert_equal(['a1', 'a2', 'a2'], getline(1, '$')) + exe "normal 2GCa\<C-X>\<C-L>\<C-N>\<C-N>\<C-N>" + call assert_equal(['a1', 'a1', 'a2'], getline(1, '$')) + " repeat the test using CTRL-L + " go to the next match (wrapping around the buffer) + exe "normal 2GCa\<C-X>\<C-L>\<C-L>" + call assert_equal(['a1', 'a2', 'a2'], getline(1, '$')) + " go to the next match + exe "normal 2GCa\<C-X>\<C-L>\<C-L>\<C-L>" + call assert_equal(['a1', 'a', 'a2'], getline(1, '$')) + exe "normal 2GCa\<C-X>\<C-L>\<C-L>\<C-L>\<C-L>" + call assert_equal(['a1', 'a1', 'a2'], getline(1, '$')) + %d + " use CTRL-X CTRL-L to add one more line + call setline(1, ['a1', 'b1']) + setlocal complete=. + exe "normal ggOa\<C-X>\<C-L>\<C-X>\<C-L>\<C-X>\<C-L>" + call assert_equal(['a1', 'b1', '', 'a1', 'b1'], getline(1, '$')) + bw! +endfunc + " Test insert completion with 'cindent' (adjust the indent) func Test_complete_with_cindent() new @@ -829,6 +893,25 @@ func Test_complete_stop() close! endfunc +" Test for typing CTRL-R in insert completion mode to insert a register +" content. +func Test_complete_reginsert() + new + call setline(1, ['a1', 'a12', 'a123', 'a1234']) + + " if a valid CTRL-X mode key is returned from <C-R>=, then it should be + " processed. Otherwise, CTRL-X mode should be stopped and the key should be + " inserted. + exe "normal Goa\<C-P>\<C-R>=\"\\<C-P>\"\<CR>" + call assert_equal('a123', getline(5)) + let @r = "\<C-P>\<C-P>" + exe "normal GCa\<C-P>\<C-R>r" + call assert_equal('a12', getline(5)) + exe "normal GCa\<C-P>\<C-R>=\"x\"\<CR>" + call assert_equal('a1234x', getline(5)) + bw! +endfunc + func Test_issue_7021() CheckMSWindows @@ -842,6 +925,322 @@ func Test_issue_7021() set completeslash= endfunc +" Test for 'longest' setting in 'completeopt' with latin1 and utf-8 encodings +func Test_complete_longest_match() + " for e in ['latin1', 'utf-8'] + for e in ['utf-8'] + exe 'set encoding=' .. e + new + set complete=. + set completeopt=menu,longest + call setline(1, ['pfx_a1', 'pfx_a12', 'pfx_a123', 'pfx_b1']) + exe "normal Gopfx\<C-P>" + call assert_equal('pfx_', getline(5)) + bw! + endfor + + " Test for completing additional words with longest match set + new + call setline(1, ['abc1', 'abd2']) + exe "normal Goab\<C-P>\<C-X>\<C-P>" + call assert_equal('ab', getline(3)) + bw! + set complete& completeopt& +endfunc + +" Test for removing the first displayed completion match and selecting the +" match just before that. +func Test_complete_erase_firstmatch() + new + call setline(1, ['a12', 'a34', 'a56']) + set complete=. + exe "normal Goa\<C-P>\<BS>\<BS>3\<CR>" + call assert_equal('a34', getline('$')) + set complete& + bw! +endfunc + +" Test for completing words from unloaded buffers +func Test_complete_from_unloadedbuf() + call writefile(['abc'], "Xfile1") + call writefile(['def'], "Xfile2") + edit Xfile1 + edit Xfile2 + new | close + enew + bunload Xfile1 Xfile2 + set complete=u + " complete from an unloaded buffer + exe "normal! ia\<C-P>" + call assert_equal('abc', getline(1)) + exe "normal! od\<C-P>" + call assert_equal('def', getline(2)) + set complete& + %bw! + call delete("Xfile1") + call delete("Xfile2") +endfunc + +" Test for completing whole lines from unloaded buffers +func Test_complete_wholeline_unloadedbuf() + call writefile(['a line1', 'a line2', 'a line3'], "Xfile1") + edit Xfile1 + enew + set complete=u + exe "normal! ia\<C-X>\<C-L>\<C-P>" + call assert_equal('a line2', getline(1)) + %d + " completing from an unlisted buffer should fail + bdel Xfile1 + exe "normal! ia\<C-X>\<C-L>\<C-P>" + call assert_equal('a', getline(1)) + set complete& + %bw! + call delete("Xfile1") +endfunc + +" Test for completing words from unlisted buffers +func Test_complete_from_unlistedbuf() + call writefile(['abc'], "Xfile1") + call writefile(['def'], "Xfile2") + edit Xfile1 + edit Xfile2 + new | close + bdel Xfile1 Xfile2 + set complete=U + " complete from an unlisted buffer + exe "normal! ia\<C-P>" + call assert_equal('abc', getline(1)) + exe "normal! od\<C-P>" + call assert_equal('def', getline(2)) + set complete& + %bw! + call delete("Xfile1") + call delete("Xfile2") +endfunc + +" Test for completing whole lines from unlisted buffers +func Test_complete_wholeline_unlistedbuf() + call writefile(['a line1', 'a line2', 'a line3'], "Xfile1") + edit Xfile1 + enew + set complete=U + " completing from a unloaded buffer should fail + exe "normal! ia\<C-X>\<C-L>\<C-P>" + call assert_equal('a', getline(1)) + %d + bdel Xfile1 + exe "normal! ia\<C-X>\<C-L>\<C-P>" + call assert_equal('a line2', getline(1)) + set complete& + %bw! + call delete("Xfile1") +endfunc + +" Test for adding a multibyte character using CTRL-L in completion mode +func Test_complete_mbyte_char_add() + new + set complete=. + call setline(1, 'abė') + exe "normal! oa\<C-P>\<BS>\<BS>\<C-L>\<C-L>" + call assert_equal('abė', getline(2)) + " Test for a leader with multibyte character + %d + call setline(1, 'abėĕ') + exe "normal! oabė\<C-P>" + call assert_equal('abėĕ', getline(2)) + bw! +endfunc + +" Test for using <C-X><C-P> for local expansion even if 'complete' is set to +" not to complete matches from the local buffer. Also test using multiple +" <C-X> to cancel the current completion mode. +func Test_complete_local_expansion() + new + set complete=t + call setline(1, ['abc', 'def']) + exe "normal! Go\<C-X>\<C-P>" + call assert_equal("def", getline(3)) + exe "normal! Go\<C-P>" + call assert_equal("", getline(4)) + exe "normal! Go\<C-X>\<C-N>" + call assert_equal("abc", getline(5)) + exe "normal! Go\<C-N>" + call assert_equal("", getline(6)) + + " use multiple <C-X> to cancel the previous completion mode + exe "normal! Go\<C-P>\<C-X>\<C-P>" + call assert_equal("", getline(7)) + exe "normal! Go\<C-P>\<C-X>\<C-X>\<C-P>" + call assert_equal("", getline(8)) + exe "normal! Go\<C-P>\<C-X>\<C-X>\<C-X>\<C-P>" + call assert_equal("abc", getline(9)) + + " interrupt the current completion mode + set completeopt=menu,noinsert + exe "normal! Go\<C-X>\<C-F>\<C-X>\<C-X>\<C-P>\<C-Y>" + call assert_equal("abc", getline(10)) + + " when only one <C-X> is used to interrupt, do normal expansion + exe "normal! Go\<C-X>\<C-F>\<C-X>\<C-P>" + call assert_equal("", getline(11)) + set completeopt& + + " using two <C-X> in non-completion mode and restarting the same mode + exe "normal! God\<C-X>\<C-X>\<C-P>\<C-X>\<C-X>\<C-P>\<C-Y>" + call assert_equal("def", getline(12)) + + " test for adding a match from the original empty text + %d + call setline(1, 'abc def g') + exe "normal! o\<C-X>\<C-P>\<C-N>\<C-X>\<C-P>" + call assert_equal('def', getline(2)) + exe "normal! 0C\<C-X>\<C-N>\<C-P>\<C-X>\<C-N>" + call assert_equal('abc', getline(2)) + + bw! +endfunc + +" Test for undoing changes after a insert-mode completion +func Test_complete_undo() + new + set complete=. + " undo with 'ignorecase' + call setline(1, ['ABOVE', 'BELOW']) + set ignorecase + exe "normal! Goab\<C-G>u\<C-P>" + call assert_equal("ABOVE", getline(3)) + undo + call assert_equal("ab", getline(3)) + set ignorecase& + %d + " undo with longest match + set completeopt=menu,longest + call setline(1, ['above', 'about']) + exe "normal! Goa\<C-G>u\<C-P>" + call assert_equal("abo", getline(3)) + undo + call assert_equal("a", getline(3)) + set completeopt& + %d + " undo for line completion + call setline(1, ['above that change', 'below that change']) + exe "normal! Goabove\<C-G>u\<C-X>\<C-L>" + call assert_equal("above that change", getline(3)) + undo + call assert_equal("above", getline(3)) + + bw! +endfunc + +" Test for completing a very long word +func Test_complete_long_word() + set complete& + new + call setline(1, repeat('x', 950) .. ' one two three') + exe "normal! Gox\<C-X>\<C-P>\<C-X>\<C-P>\<C-X>\<C-P>\<C-X>\<C-P>" + call assert_equal(repeat('x', 950) .. ' one two three', getline(2)) + %d + " should fail when more than 950 characters are in a word + call setline(1, repeat('x', 951) .. ' one two three') + exe "normal! Gox\<C-X>\<C-P>\<C-X>\<C-P>\<C-X>\<C-P>\<C-X>\<C-P>" + call assert_equal(repeat('x', 951), getline(2)) + + " Test for adding a very long word to an existing completion + %d + call setline(1, ['abc', repeat('x', 1016) .. '012345']) + exe "normal! Goab\<C-P>\<C-X>\<C-P>" + call assert_equal('abc ' .. repeat('x', 1016) .. '0123', getline(3)) + bw! +endfunc + +" Test for some fields in the complete items used by complete() +func Test_complete_items() + func CompleteItems(idx) + let items = [[#{word: "one", dup: 1, user_data: 'u1'}, #{word: "one", dup: 1, user_data: 'u2'}], + \ [#{word: "one", dup: 0, user_data: 'u3'}, #{word: "one", dup: 0, user_data: 'u4'}], + \ [#{word: "one", icase: 1, user_data: 'u7'}, #{word: "oNE", icase: 1, user_data: 'u8'}], + \ [#{user_data: 'u9'}], + \ [#{word: "", user_data: 'u10'}], + \ [#{word: "", empty: 1, user_data: 'u11'}]] + call complete(col('.'), items[a:idx]) + return '' + endfunc + new + exe "normal! i\<C-R>=CompleteItems(0)\<CR>\<C-N>\<C-Y>" + call assert_equal('u2', v:completed_item.user_data) + call assert_equal('one', getline(1)) + exe "normal! o\<C-R>=CompleteItems(1)\<CR>\<C-Y>" + call assert_equal('u3', v:completed_item.user_data) + call assert_equal('one', getline(2)) + exe "normal! o\<C-R>=CompleteItems(1)\<CR>\<C-N>" + call assert_equal('', getline(3)) + set completeopt=menu,noinsert + exe "normal! o\<C-R>=CompleteItems(2)\<CR>one\<C-N>\<C-Y>" + call assert_equal('oNE', getline(4)) + call assert_equal('u8', v:completed_item.user_data) + set completeopt& + exe "normal! o\<C-R>=CompleteItems(3)\<CR>" + call assert_equal('', getline(5)) + exe "normal! o\<C-R>=CompleteItems(4)\<CR>" + call assert_equal('', getline(6)) + exe "normal! o\<C-R>=CompleteItems(5)\<CR>" + call assert_equal('', getline(7)) + call assert_equal('u11', v:completed_item.user_data) + " pass invalid argument to complete() + let cmd = "normal! o\<C-R>=complete(1, [[]])\<CR>" + call assert_fails('exe cmd', 'E730:') + bw! + delfunc CompleteItems +endfunc + +" Test for the "refresh" item in the dict returned by an insert completion +" function +func Test_complete_item_refresh_always() + let g:CallCount = 0 + func! Tcomplete(findstart, base) + if a:findstart + " locate the start of the word + let line = getline('.') + let start = col('.') - 1 + while start > 0 && line[start - 1] =~ '\a' + let start -= 1 + endwhile + return start + else + let g:CallCount += 1 + let res = ["update1", "update12", "update123"] + return #{words: res, refresh: 'always'} + endif + endfunc + new + set completeopt=menu,longest + set completefunc=Tcomplete + exe "normal! iup\<C-X>\<C-U>\<BS>\<BS>\<BS>\<BS>\<BS>" + call assert_equal('up', getline(1)) + call assert_equal(2, g:CallCount) + set completeopt& + set completefunc& + bw! + delfunc Tcomplete +endfunc + +" Test for completing from a thesaurus file without read permission +func Test_complete_unreadable_thesaurus_file() + CheckUnix + CheckNotRoot + + call writefile(['about', 'above'], 'Xfile') + call setfperm('Xfile', '---r--r--') + new + set complete=sXfile + exe "normal! ia\<C-P>" + call assert_equal('a', getline(1)) + bw! + call delete('Xfile') + set complete& +endfunc + " Test to ensure 'Scanning...' messages are not recorded in messages history func Test_z1_complete_no_history() new @@ -884,4 +1283,52 @@ func Test_complete_smartindent() delfunction! FooBarComplete endfunc +func Test_complete_overrun() + " this was going past the end of the copied text + new + sil norm si0s0 + bwipe! +endfunc + +func Test_infercase_very_long_line() + " this was truncating the line when inferring case + new + let longLine = "blah "->repeat(300) + let verylongLine = "blah "->repeat(400) + call setline(1, verylongLine) + call setline(2, longLine) + set ic infercase + exe "normal 2Go\<C-X>\<C-L>\<Esc>" + call assert_equal(longLine, getline(3)) + + " check that the too long text is NUL terminated + %del + norm o + norm 1987ax + exec "norm ox\<C-X>\<C-L>" + call assert_equal(repeat('x', 1987), getline(3)) + + bwipe! + set noic noinfercase +endfunc + +func Test_ins_complete_add() + " this was reading past the end of allocated memory + new + norm o + norm 7o + sil! norm o + + bwipe! +endfunc + +func Test_ins_complete_end_of_line() + " this was reading past the end of the line + new + norm 8oý + sil! norm o + + bwipe! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_lambda.vim b/src/nvim/testdir/test_lambda.vim index c1fe47d1c9..c178c87d3e 100644 --- a/src/nvim/testdir/test_lambda.vim +++ b/src/nvim/testdir/test_lambda.vim @@ -308,3 +308,21 @@ func Test_lambda_error() " This was causing a crash call assert_fails('ec{@{->{d->()()', 'E15') endfunc + +func Test_closure_error() + let l =<< trim END + func F1() closure + return 1 + endfunc + END + call writefile(l, 'Xscript') + let caught_932 = 0 + try + source Xscript + catch /E932:/ + let caught_932 = 1 + endtry + call assert_equal(1, caught_932) +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_listdict.vim b/src/nvim/testdir/test_listdict.vim index aa66d86af1..08c415a069 100644 --- a/src/nvim/testdir/test_listdict.vim +++ b/src/nvim/testdir/test_listdict.vim @@ -604,20 +604,23 @@ func Test_reverse_sort_uniq() call assert_equal(['-0', 'A11', 2, 'xaaa', 4, 'foo', 'foo6', 'foo', [0, 1, 2], 'x8', [0, 1, 2], 1.5], uniq(copy(l))) call assert_equal([1.5, [0, 1, 2], 'x8', [0, 1, 2], 'foo', 'foo6', 'foo', 4, 'xaaa', 2, 2, 'A11', '-0'], reverse(l)) call assert_equal([1.5, [0, 1, 2], 'x8', [0, 1, 2], 'foo', 'foo6', 'foo', 4, 'xaaa', 2, 2, 'A11', '-0'], reverse(reverse(l))) - call assert_equal(['-0', 'A11', 'foo', 'foo', 'foo6', 'x8', 'xaaa', 1.5, 2, 2, 4, [0, 1, 2], [0, 1, 2]], sort(l)) - call assert_equal([[0, 1, 2], [0, 1, 2], 4, 2, 2, 1.5, 'xaaa', 'x8', 'foo6', 'foo', 'foo', 'A11', '-0'], reverse(sort(l))) - call assert_equal(['-0', 'A11', 'foo', 'foo', 'foo6', 'x8', 'xaaa', 1.5, 2, 2, 4, [0, 1, 2], [0, 1, 2]], sort(reverse(sort(l)))) - call assert_equal(['-0', 'A11', 'foo', 'foo6', 'x8', 'xaaa', 1.5, 2, 4, [0, 1, 2]], uniq(sort(l))) - - let l=[7, 9, 'one', 18, 12, 22, 'two', 10.0e-16, -1, 'three', 0xff, 0.22, 'four'] - call assert_equal([-1, 'one', 'two', 'three', 'four', 1.0e-15, 0.22, 7, 9, 12, 18, 22, 255], sort(copy(l), 'n')) - - let l=[7, 9, 18, 12, 22, 10.0e-16, -1, 0xff, 0, -0, 0.22, 'bar', 'BAR', 'Bar', 'Foo', 'FOO', 'foo', 'FOOBAR', {}, []] - call assert_equal(['bar', 'BAR', 'Bar', 'Foo', 'FOO', 'foo', 'FOOBAR', -1, 0, 0, 0.22, 1.0e-15, 12, 18, 22, 255, 7, 9, [], {}], sort(copy(l), 1)) - call assert_equal(['bar', 'BAR', 'Bar', 'Foo', 'FOO', 'foo', 'FOOBAR', -1, 0, 0, 0.22, 1.0e-15, 12, 18, 22, 255, 7, 9, [], {}], sort(copy(l), 'i')) - call assert_equal(['BAR', 'Bar', 'FOO', 'FOOBAR', 'Foo', 'bar', 'foo', -1, 0, 0, 0.22, 1.0e-15, 12, 18, 22, 255, 7, 9, [], {}], sort(copy(l))) + if has('float') + call assert_equal(['-0', 'A11', 'foo', 'foo', 'foo6', 'x8', 'xaaa', 1.5, 2, 2, 4, [0, 1, 2], [0, 1, 2]], sort(l)) + call assert_equal([[0, 1, 2], [0, 1, 2], 4, 2, 2, 1.5, 'xaaa', 'x8', 'foo6', 'foo', 'foo', 'A11', '-0'], reverse(sort(l))) + call assert_equal(['-0', 'A11', 'foo', 'foo', 'foo6', 'x8', 'xaaa', 1.5, 2, 2, 4, [0, 1, 2], [0, 1, 2]], sort(reverse(sort(l)))) + call assert_equal(['-0', 'A11', 'foo', 'foo6', 'x8', 'xaaa', 1.5, 2, 4, [0, 1, 2]], uniq(sort(l))) + + let l = [7, 9, 'one', 18, 12, 22, 'two', 10.0e-16, -1, 'three', 0xff, 0.22, 'four'] + call assert_equal([-1, 'one', 'two', 'three', 'four', 1.0e-15, 0.22, 7, 9, 12, 18, 22, 255], sort(copy(l), 'n')) + + let l = [7, 9, 18, 12, 22, 10.0e-16, -1, 0xff, 0, -0, 0.22, 'bar', 'BAR', 'Bar', 'Foo', 'FOO', 'foo', 'FOOBAR', {}, []] + call assert_equal(['bar', 'BAR', 'Bar', 'Foo', 'FOO', 'foo', 'FOOBAR', -1, 0, 0, 0.22, 1.0e-15, 12, 18, 22, 255, 7, 9, [], {}], sort(copy(l), 1)) + call assert_equal(['bar', 'BAR', 'Bar', 'Foo', 'FOO', 'foo', 'FOOBAR', -1, 0, 0, 0.22, 1.0e-15, 12, 18, 22, 255, 7, 9, [], {}], sort(copy(l), 'i')) + call assert_equal(['BAR', 'Bar', 'FOO', 'FOOBAR', 'Foo', 'bar', 'foo', -1, 0, 0, 0.22, 1.0e-15, 12, 18, 22, 255, 7, 9, [], {}], sort(copy(l))) + endif call assert_fails('call reverse("")', 'E899:') + call assert_fails('call uniq([1, 2], {x, y -> []})', 'E882:') endfunc " reduce a list or a blob @@ -649,6 +652,8 @@ func Test_reduce() call assert_fails("call reduce({}, { acc, val -> acc + val }, 1)", 'E897:') call assert_fails("call reduce(0, { acc, val -> acc + val }, 1)", 'E897:') call assert_fails("call reduce('', { acc, val -> acc + val }, 1)", 'E897:') + call assert_fails("call reduce([1, 2], 'Xdoes_not_exist')", 'E117:') + call assert_fails("echo reduce(0z01, { acc, val -> 2 * acc + val }, '')", 'E39:') let g:lut = [1, 2, 3, 4] func EvilRemove() diff --git a/src/nvim/testdir/test_maparg.vim b/src/nvim/testdir/test_maparg.vim index dad4c81a7b..17c5f433ac 100644 --- a/src/nvim/testdir/test_maparg.vim +++ b/src/nvim/testdir/test_maparg.vim @@ -248,6 +248,8 @@ func Test_mapset() bwipe! call assert_fails('call mapset([], v:false, {})', 'E730:') + call assert_fails('call mapset("i", 0, "")', 'E715:') + call assert_fails('call mapset("i", 0, {})', 'E460:') endfunc func Check_ctrlb_map(d, check_alt) diff --git a/src/nvim/testdir/test_messages.vim b/src/nvim/testdir/test_messages.vim index a02d23b409..3a607ff533 100644 --- a/src/nvim/testdir/test_messages.vim +++ b/src/nvim/testdir/test_messages.vim @@ -95,6 +95,65 @@ func Test_echoerr() call test_ignore_error('RESET') endfunc +func Test_mode_message_at_leaving_insert_by_ctrl_c() + if !has('terminal') || has('gui_running') + return + endif + + " Set custom statusline built by user-defined function. + let testfile = 'Xtest.vim' + call writefile([ + \ 'func StatusLine() abort', + \ ' return ""', + \ 'endfunc', + \ 'set statusline=%!StatusLine()', + \ 'set laststatus=2', + \ ], testfile) + + let rows = 10 + let buf = term_start([GetVimProg(), '--clean', '-S', testfile], {'term_rows': rows}) + call term_wait(buf, 200) + call assert_equal('run', job_status(term_getjob(buf))) + + call term_sendkeys(buf, "i") + call WaitForAssert({-> assert_match('^-- INSERT --\s*$', term_getline(buf, rows))}) + call term_sendkeys(buf, "\<C-C>") + call WaitForAssert({-> assert_match('^\s*$', term_getline(buf, rows))}) + + call term_sendkeys(buf, ":qall!\<CR>") + call WaitForAssert({-> assert_equal('dead', job_status(term_getjob(buf)))}) + exe buf . 'bwipe!' + call delete(testfile) +endfunc + +func Test_mode_message_at_leaving_insert_with_esc_mapped() + if !has('terminal') || has('gui_running') + return + endif + + " Set custom statusline built by user-defined function. + let testfile = 'Xtest.vim' + call writefile([ + \ 'set laststatus=2', + \ 'inoremap <Esc> <Esc>00', + \ ], testfile) + + let rows = 10 + let buf = term_start([GetVimProg(), '--clean', '-S', testfile], {'term_rows': rows}) + call term_wait(buf, 200) + call assert_equal('run', job_status(term_getjob(buf))) + + call term_sendkeys(buf, "i") + call WaitForAssert({-> assert_match('^-- INSERT --\s*$', term_getline(buf, rows))}) + call term_sendkeys(buf, "\<Esc>") + call WaitForAssert({-> assert_match('^\s*$', term_getline(buf, rows))}) + + call term_sendkeys(buf, ":qall!\<CR>") + call WaitForAssert({-> assert_equal('dead', job_status(term_getjob(buf)))}) + exe buf . 'bwipe!' + call delete(testfile) +endfunc + func Test_echospace() set noruler noshowcmd laststatus=1 call assert_equal(&columns - 1, v:echospace) @@ -317,6 +376,7 @@ func Test_fileinfo_after_echo() endfunc func Test_cmdheight_zero() + enew set cmdheight=0 set showcmd redraw! @@ -366,10 +426,13 @@ func Test_cmdheight_zero() 7 call feedkeys(":\"\<C-R>=line('w0')\<CR>\<CR>", "xt") call assert_equal('"1', @:) - bwipe! + bwipe! + bwipe! set cmdheight& set showcmd& + tabnew + tabonly endfunc " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_normal.vim b/src/nvim/testdir/test_normal.vim index 7cb70aa2af..4f842189b6 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 screendump.vim func Setup_NewWindow() 10new @@ -123,31 +124,6 @@ func Test_normal01_keymodel() bw! endfunc -" Test for select mode -func Test_normal02_selectmode() - call Setup_NewWindow() - 50 - norm! gHy - call assert_equal('y51', getline('.')) - call setline(1, range(1,100)) - 50 - exe ":norm! V9jo\<c-g>y" - call assert_equal('y60', getline('.')) - " clean up - bw! -endfunc - -func Test_normal02_selectmode2() - " some basic select mode tests - call Setup_NewWindow() - 50 - " call feedkeys(":set im\n\<c-o>gHc\<c-o>:set noim\n", 'tx') - call feedkeys("i\<c-o>gHc\<esc>", 'tx') - call assert_equal('c51', getline('.')) - " clean up - bw! -endfunc - func Test_normal03_join() " basic join test call Setup_NewWindow() @@ -376,6 +352,70 @@ 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= @@ -491,6 +531,18 @@ func Test_normal11_showcmd() call assert_equal(3, line('$')) exe "norm! 0d3\<del>2l" call assert_equal('obar2foobar3', getline('.')) + " test for the visual block size displayed in the status line + call setline(1, ['aaaaa', 'bbbbb', 'ccccc']) + call feedkeys("ggl\<C-V>lljj", 'xt') + redraw! + call assert_match('3x3$', Screenline(&lines)) + call feedkeys("\<C-V>", 'xt') + " test for visually selecting a multi-byte character + call setline(1, ["\U2206"]) + call feedkeys("ggv", 'xt') + redraw! + call assert_match('1-3$', Screenline(&lines)) + call feedkeys("v", 'xt') bw! endfunc @@ -654,6 +706,19 @@ func Test_normal15_z_scroll_vert() call assert_equal(21, winsaveview()['topline']) call assert_equal([0, 21, 2, 0, 9], getcurpos()) + " Test for z+ with [count] greater than buffer size + 1 + norm! 1000z+ + call assert_equal(' 100', getline('.')) + call assert_equal(100, winsaveview()['topline']) + call assert_equal([0, 100, 2, 0, 9], getcurpos()) + + " Test for z+ from the last buffer line + norm! Gz.z+ + call assert_equal(' 100', getline('.')) + call assert_equal(100, winsaveview()['topline']) + call assert_equal([0, 100, 2, 0, 9], getcurpos()) + " Test for z^ norm! 22z+0 norm! z^ @@ -661,6 +726,12 @@ func Test_normal15_z_scroll_vert() call assert_equal(12, winsaveview()['topline']) call assert_equal([0, 21, 2, 0, 9], getcurpos()) + " Test for z^ from first buffer line + norm! ggz^ + call assert_equal('1', getline('.')) + call assert_equal(1, winsaveview()['topline']) + call assert_equal([0, 1, 1, 0, 1], getcurpos()) + " Test for [count]z^ 1 norm! 30z^ @@ -740,6 +811,19 @@ func Test_normal16_z_scroll_hor() norm! yl call assert_equal('z', @0) + " Test for zs and ze with folds + %fold + norm! $zs + call assert_equal(26, col('.')) + call assert_equal(0, winsaveview()['leftcol']) + norm! yl + call assert_equal('z', @0) + norm! ze + call assert_equal(26, col('.')) + call assert_equal(0, winsaveview()['leftcol']) + norm! yl + call assert_equal('z', @0) + " cleanup set wrap listchars=eol:$ bw! @@ -833,6 +917,19 @@ func Test_vert_scroll_cmds() normal! 4H call assert_equal(33, line('.')) + " Test for using a large count value + %d + call setline(1, range(1, 4)) + norm! 6H + call assert_equal(4, line('.')) + + " Test for 'M' with folded lines + %d + call setline(1, range(1, 20)) + 1,5fold + norm! LM + call assert_equal(12, line('.')) + " Test for the CTRL-E and CTRL-Y commands with folds %d call setline(1, range(1, 10)) @@ -851,6 +948,18 @@ func Test_vert_scroll_cmds() exe "normal \<C-Y>\<C-Y>" call assert_equal(h + 1, line('w$')) + " Test for CTRL-Y from the first line and CTRL-E from the last line + %d + set scrolloff=2 + call setline(1, range(1, 4)) + exe "normal gg\<C-Y>" + call assert_equal(1, line('w0')) + call assert_equal(1, line('.')) + exe "normal G4\<C-E>\<C-E>" + call assert_equal(4, line('w$')) + call assert_equal(4, line('.')) + set scrolloff& + " Using <PageUp> and <PageDown> in an empty buffer should beep %d call assert_beeps('exe "normal \<PageUp>"') @@ -899,6 +1008,18 @@ func Test_vert_scroll_cmds() exe "normal \<C-D>" call assert_equal(50, line('w0')) + " Test for <S-CR>. Page down. + %d + call setline(1, range(1, 100)) + call feedkeys("\<S-CR>", 'xt') + call assert_equal(14, line('w0')) + call assert_equal(28, line('w$')) + + " Test for <S-->. Page up. + call feedkeys("\<S-->", 'xt') + call assert_equal(1, line('w0')) + call assert_equal(15, line('w$')) + set foldenable& close! endfunc @@ -1213,6 +1334,13 @@ func Test_normal18_z_fold() norm! j call assert_equal('55', getline('.')) + " Test for zm with a count + 50 + set foldlevel=2 + norm! 3zm + call assert_equal(0, &foldlevel) + call assert_equal(49, foldclosed(line('.'))) + " Test for zM 48 set nofoldenable foldlevel=99 @@ -1327,6 +1455,7 @@ func Test_normal21_nv_hat() edit Xfoo | %bw call assert_fails(':buffer #', 'E86') call assert_fails(':execute "normal! \<C-^>"', 'E23') + call assert_fails("normal i\<C-R>#", 'E23:') " Test for the expected behavior when switching between two named buffers. edit Xfoo | edit Xbar @@ -1420,6 +1549,15 @@ func Test_normal23_K() set iskeyword-=% set iskeyword-=\| + " Currently doesn't work in Nvim, see #19436 + " Test for specifying a count to K + " 1 + " com! -nargs=* Kprog let g:Kprog_Args = <q-args> + " set keywordprg=:Kprog + " norm! 3K + " call assert_equal('3 version8', g:Kprog_Args) + " delcom Kprog + " Only expect "man" to work on Unix if !has("unix") || has('nvim') " Nvim K uses :terminal. #15398 let &keywordprg = k @@ -1867,7 +2005,31 @@ func Test_normal29_brace() bw! endfunc -" Test for ~ command +" Test for section movements +func Test_normal_section() + new + let lines =<< trim [END] + int foo() + { + if (1) + { + a = 1; + } + } + [END] + call setline(1, lines) + + " jumping to a folded line using [[ should open the fold + 2,3fold + call cursor(5, 1) + call feedkeys("[[", 'xt') + call assert_equal(2, line('.')) + call assert_equal(-1, foldclosedend(line('.'))) + + close! +endfunc + +" Test for changing case using u, U, gu, gU and ~ (tilde) commands func Test_normal30_changecase() new call append(0, 'This is a simple test: äüöß') @@ -1887,6 +2049,9 @@ func Test_normal30_changecase() call assert_equal('this is a SIMPLE TEST: ÄÜÖSS', getline('.')) norm! V~ call assert_equal('THIS IS A simple test: äüöss', getline('.')) + call assert_beeps('norm! c~') + %d + call assert_beeps('norm! ~') " Test for changing case across lines using 'whichwrap' call setline(1, ['aaaaaa', 'aaaaaa']) @@ -2038,9 +2203,9 @@ func Test_normal33_g_cmd2() call assert_equal(2, line('.')) call assert_fails(':norm! g;', 'E662') call assert_fails(':norm! g,', 'E663') - let &ul=&ul + let &ul = &ul call append('$', ['a', 'b', 'c', 'd']) - let &ul=&ul + let &ul = &ul call append('$', ['Z', 'Y', 'X', 'W']) let a = execute(':changes') call assert_match('2\s\+0\s\+2', a) @@ -2889,6 +3054,20 @@ func Test_message_when_using_ctrl_c() bwipe! endfunc +func Test_mode_updated_after_ctrl_c() + CheckScreendump + + let buf = RunVimInTerminal('', {'rows': 5}) + call term_sendkeys(buf, "i") + call term_sendkeys(buf, "\<C-O>") + " wait a moment so that the "-- (insert) --" message is displayed + call TermWait(buf, 50) + call term_sendkeys(buf, "\<C-C>") + call VerifyScreenDump(buf, 'Test_mode_updated_1', {}) + + call StopVimInTerminal(buf) +endfunc + " Test for '[m', ']m', '[M' and ']M' " Jumping to beginning and end of methods in Java-like languages func Test_java_motion() @@ -2897,25 +3076,26 @@ func Test_java_motion() call assert_beeps('normal! ]m') call assert_beeps('normal! [M') call assert_beeps('normal! ]M') - a -Piece of Java -{ - tt m1 { - t1; - } e1 - - tt m2 { - t2; - } e2 - - tt m3 { - if (x) - { - t3; - } - } e3 -} -. + let lines =<< trim [CODE] + Piece of Java + { + tt m1 { + t1; + } e1 + + tt m2 { + t2; + } e2 + + tt m3 { + if (x) + { + t3; + } + } e3 + } + [CODE] + call setline(1, lines) normal gg @@ -2968,14 +3148,21 @@ Piece of Java call assert_equal("{LF", getline('.')) call assert_equal([2, 2, 2], [line('.'), col('.'), virtcol('.')]) + call cursor(2, 1) + call assert_beeps('norm! 5]m') + + " jumping to a method in a fold should open the fold + 6,10fold + call feedkeys("gg3]m", 'xt') + call assert_equal([7, 8, 15], [line('.'), col('.'), virtcol('.')]) + call assert_equal(-1, foldclosedend(7)) + close! endfunc +" Tests for g cmds func Test_normal_gdollar_cmd() - if !has("jumplist") - return - endif - " Tests for g cmds + CheckFeature jumplist call Setup_NewWindow() " Make long lines that will wrap %s/$/\=repeat(' foobar', 10)/ @@ -3183,6 +3370,27 @@ func Test_normal_colon_op() 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() + new + set whichwrap& + call setline(1, ['one two', 'three four']) + exe "norm! $3d\<Space>" + call assert_equal(['one twhree four'], getline(1, '$')) + + call setline(1, ['one two', 'three four']) + exe "norm! $3c\<Space>x" + call assert_equal(['one twxhree four'], getline(1, '$')) + + set whichwrap+=l + call setline(1, ['one two', 'three four']) + exe "norm! $3x" + call assert_equal(['one twhree four'], getline(1, '$')) + close! + set whichwrap& +endfunc + " Test for 'w' and 'b' commands func Test_normal_word_move() new @@ -3256,6 +3464,19 @@ func Test_normal_vert_scroll_longline() close! endfunc +" Test for jumping in a file using % +func Test_normal_percent_jump() + new + call setline(1, range(1, 100)) + + " jumping to a folded line should open the fold + 25,75fold + call feedkeys('50%', 'xt') + call assert_equal(50, line('.')) + call assert_equal(-1, foldclosedend(50)) + close! +endfunc + " Some commands like yy, cc, dd, >>, << and !! accept a count after " typing the first letter of the command. func Test_normal_count_after_operator() diff --git a/src/nvim/testdir/test_options.vim b/src/nvim/testdir/test_options.vim index b10f0f5030..655d537336 100644 --- a/src/nvim/testdir/test_options.vim +++ b/src/nvim/testdir/test_options.vim @@ -234,6 +234,7 @@ func Test_complete() new call feedkeys("i\<C-N>\<Esc>", 'xt') bwipe! + call assert_fails('set complete=ix', 'E535:') set complete& endfun @@ -368,9 +369,17 @@ func Test_set_errors() call assert_fails('set sessionoptions=curdir,sesdir', 'E474:') call assert_fails('set foldmarker={{{,', 'E474:') call assert_fails('set sessionoptions=sesdir,curdir', 'E474:') - call assert_fails('set listchars=trail:· ambiwidth=double', 'E834:') + setlocal listchars=trail:· + call assert_fails('set ambiwidth=double', 'E834:') + setlocal listchars=trail:- + setglobal listchars=trail:· + call assert_fails('set ambiwidth=double', 'E834:') set listchars& - call assert_fails('set fillchars=stl:· ambiwidth=double', 'E835:') + setlocal fillchars=stl:· + call assert_fails('set ambiwidth=double', 'E835:') + setlocal fillchars=stl:- + setglobal fillchars=stl:· + call assert_fails('set ambiwidth=double', 'E835:') set fillchars& call assert_fails('set fileencoding=latin1,utf-8', 'E474:') set nomodifiable @@ -423,32 +432,37 @@ func Test_copy_context() endfunc func Test_set_ttytype() - " Nvim does not support 'ttytype'. - if !has('nvim') && !has('gui_running') && has('unix') - " Setting 'ttytype' used to cause a double-free when exiting vim and - " when vim is compiled with -DEXITFREE. - set ttytype=ansi - call assert_equal('ansi', &ttytype) - call assert_equal(&ttytype, &term) - set ttytype=xterm - call assert_equal('xterm', &ttytype) - call assert_equal(&ttytype, &term) - try - set ttytype= - call assert_report('set ttytype= did not fail') - catch /E529/ - endtry - - " Some systems accept any terminal name and return dumb settings, - " check for failure of finding the entry and for missing 'cm' entry. - try - set ttytype=xxx - call assert_report('set ttytype=xxx did not fail') - catch /E522\|E437/ - endtry - - set ttytype& - call assert_equal(&ttytype, &term) + throw "Skipped: Nvim does not support 'ttytype'" + CheckUnix + CheckNotGui + + " Setting 'ttytype' used to cause a double-free when exiting vim and + " when vim is compiled with -DEXITFREE. + set ttytype=ansi + call assert_equal('ansi', &ttytype) + call assert_equal(&ttytype, &term) + set ttytype=xterm + call assert_equal('xterm', &ttytype) + call assert_equal(&ttytype, &term) + try + set ttytype= + call assert_report('set ttytype= did not fail') + catch /E529/ + endtry + + " Some systems accept any terminal name and return dumb settings, + " check for failure of finding the entry and for missing 'cm' entry. + try + set ttytype=xxx + call assert_report('set ttytype=xxx did not fail') + catch /E522\|E437/ + endtry + + set ttytype& + call assert_equal(&ttytype, &term) + + if has('gui') && !has('gui_running') + call assert_fails('set term=gui', 'E531:') endif endfunc @@ -766,7 +780,13 @@ func Test_shell() CheckUnix let save_shell = &shell set shell= - call assert_fails('shell', 'E91:') + let caught_e91 = 0 + try + shell + catch /E91:/ + let caught_e91 = 1 + endtry + call assert_equal(1, caught_e91) let &shell = save_shell endfunc @@ -812,11 +832,16 @@ func Test_rightleftcmd() set rightleft& endfunc -" Test for the "debug" option +" Test for the 'debug' option func Test_debug_option() + " redraw to avoid matching previous messages + redraw set debug=beep exe "normal \<C-c>" call assert_equal('Beep!', Screenline(&lines)) + call assert_equal('line 4:', Screenline(&lines - 1)) + " only match the final colon in the line that shows the source + call assert_match(':$', Screenline(&lines - 2)) set debug& endfunc diff --git a/src/nvim/testdir/test_preview.vim b/src/nvim/testdir/test_preview.vim index 6c4ae414d3..b7b908e761 100644 --- a/src/nvim/testdir/test_preview.vim +++ b/src/nvim/testdir/test_preview.vim @@ -1,5 +1,8 @@ " Tests for the preview window +source check.vim +CheckFeature quickfix + func Test_Psearch() " this used to cause ml_get errors help @@ -13,6 +16,8 @@ func Test_Psearch() endfunc func Test_window_preview() + CheckFeature quickfix + " Open a preview window pedit Xa call assert_equal(2, winnr('$')) @@ -32,6 +37,8 @@ func Test_window_preview() endfunc func Test_window_preview_from_help() + CheckFeature quickfix + filetype on call writefile(['/* some C code */'], 'Xpreview.c') help diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index f6d573d76b..8c9e39570f 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -85,6 +85,12 @@ func s:setup_commands(cchar) endif endfunc +" This must be run before any error lists are created. +func Test_AA_cc_no_errors() + call assert_fails('cc', 'E42:') + call assert_fails('ll', 'E42:') +endfunc + " Tests for the :clist and :llist commands func XlistTests(cchar) call s:setup_commands(a:cchar) diff --git a/src/nvim/testdir/test_quotestar.vim b/src/nvim/testdir/test_quotestar.vim index 93865869fa..e3ca141328 100644 --- a/src/nvim/testdir/test_quotestar.vim +++ b/src/nvim/testdir/test_quotestar.vim @@ -98,8 +98,6 @@ func Do_test_quotestar_for_x11() " Running in a terminal and the GUI is available: Tell the server to open " the GUI and check that the remote command still works. - " Need to wait for the GUI to start up, otherwise the send hangs in trying - " to send to the terminal window. if has('gui_athena') || has('gui_motif') " For those GUIs, ignore the 'failed to create input context' error. call remote_send(name, ":call test_ignore_error('E285') | gui -f\<CR>") @@ -107,7 +105,10 @@ func Do_test_quotestar_for_x11() call remote_send(name, ":gui -f\<CR>") endif " Wait for the server in the GUI to be up and answering requests. + " First need to wait for the GUI to start up, otherwise the send hangs in + " trying to send to the terminal window. " On some systems and with valgrind this can be very slow. + sleep 1 call WaitForAssert({-> assert_match("1", remote_expr(name, "has('gui_running')", "", 1))}, 10000) call remote_send(name, ":let @* = 'maybe'\<CR>") diff --git a/src/nvim/testdir/test_regexp_utf8.vim b/src/nvim/testdir/test_regexp_utf8.vim index 191cd948ac..14b9724d67 100644 --- a/src/nvim/testdir/test_regexp_utf8.vim +++ b/src/nvim/testdir/test_regexp_utf8.vim @@ -522,8 +522,8 @@ endfunc func Test_search_with_end_offset() new call setline(1, ['', 'dog(a', 'cat(']) - exe "normal /(/e+" .. "\<CR>" - normal "ayn + exe "normal /(/e+\<CR>" + normal n"ayn call assert_equal("a\ncat(", @a) close! endfunc diff --git a/src/nvim/testdir/test_registers.vim b/src/nvim/testdir/test_registers.vim index 52e745438d..11dd3badb6 100644 --- a/src/nvim/testdir/test_registers.vim +++ b/src/nvim/testdir/test_registers.vim @@ -279,7 +279,12 @@ func Test_get_register() call feedkeys(":\<C-R>r\<Esc>", 'xt') call assert_equal("a\rb", histget(':', -1)) " Modified because of #6137 + call assert_fails('let r = getreg("=", [])', 'E745:') + call assert_fails('let r = getreg("=", 1, [])', 'E745:') enew! + + " Using a register in operator-pending mode should fail + call assert_beeps('norm! c"') endfunc func Test_set_register() @@ -426,6 +431,12 @@ func Test_execute_register() @ call assert_equal(3, i) + " try to execute expression register and use a backspace to cancel it + new + call feedkeys("@=\<BS>ax\<CR>y", 'xt') + call assert_equal(['x', 'y'], getline(1, '$')) + close! + " cannot execute a register in operator pending mode call assert_beeps('normal! c@r') endfunc diff --git a/src/nvim/testdir/test_selectmode.vim b/src/nvim/testdir/test_selectmode.vim index b483841060..f2cab45450 100644 --- a/src/nvim/testdir/test_selectmode.vim +++ b/src/nvim/testdir/test_selectmode.vim @@ -2,6 +2,156 @@ source shared.vim +" Test for select mode +func Test_selectmode_basic() + new + call setline(1, range(1,100)) + 50 + norm! gHy + call assert_equal('y51', getline('.')) + call setline(1, range(1,100)) + 50 + exe ":norm! V9jo\<c-g>y" + call assert_equal('y60', getline('.')) + call setline(1, range(1,100)) + 50 + " call feedkeys(":set im\n\<c-o>gHc\<c-o>:set noim\n", 'tx') + call feedkeys("i\<c-o>gHc\<esc>", 'tx') + call assert_equal('c51', getline('.')) + " clean up + bw! +endfunc + +" Test for starting selectmode +func Test_selectmode_start() + new + set selectmode=key keymodel=startsel + call setline(1, ['abc', 'def', 'ghi']) + call cursor(1, 4) + call feedkeys("A\<s-home>start\<esc>", 'txin') + call assert_equal(['startdef', 'ghi'], getline(1, '$')) + " start select mode again with gv + set selectmode=cmd + call feedkeys('gvabc', 'xt') + call assert_equal('abctdef', getline(1)) + set selectmode= keymodel= + bw! +endfunc + +" Test for characterwise select mode +func Test_characterwise_select_mode() + new + + " Select mode maps + snoremap <lt>End> <End> + snoremap <lt>Down> <Down> + snoremap <lt>Del> <Del> + + " characterwise select mode: delete middle line + call deletebufline('', 1, '$') + call append('$', ['a', 'b', 'c']) + exe "normal Gkkgh\<End>\<Del>" + call assert_equal(['', 'b', 'c'], getline(1, '$')) + + " characterwise select mode: delete middle two lines + call deletebufline('', 1, '$') + call append('$', ['a', 'b', 'c']) + exe "normal Gkkgh\<Down>\<End>\<Del>" + call assert_equal(['', 'c'], getline(1, '$')) + + " characterwise select mode: delete last line + call deletebufline('', 1, '$') + call append('$', ['a', 'b', 'c']) + exe "normal Ggh\<End>\<Del>" + call assert_equal(['', 'a', 'b', ''], getline(1, '$')) + + " characterwise select mode: delete last two lines + call deletebufline('', 1, '$') + call append('$', ['a', 'b', 'c']) + exe "normal Gkgh\<Down>\<End>\<Del>" + call assert_equal(['', 'a', ''], getline(1, '$')) + + " CTRL-H in select mode behaves like 'x' + call setline(1, 'abcdef') + exe "normal! gggh\<Right>\<Right>\<Right>\<C-H>" + call assert_equal('ef', getline(1)) + + " CTRL-O in select mode switches to visual mode for one command + call setline(1, 'abcdef') + exe "normal! gggh\<C-O>3lm" + call assert_equal('mef', getline(1)) + + sunmap <lt>End> + sunmap <lt>Down> + sunmap <lt>Del> + bwipe! +endfunc + +" Test for linewise select mode +func Test_linewise_select_mode() + new + + " linewise select mode: delete middle line + call append('$', ['a', 'b', 'c']) + exe "normal GkkgH\<Del>" + call assert_equal(['', 'b', 'c'], getline(1, '$')) + + " linewise select mode: delete middle two lines + call deletebufline('', 1, '$') + call append('$', ['a', 'b', 'c']) + exe "normal GkkgH\<Down>\<Del>" + call assert_equal(['', 'c'], getline(1, '$')) + + " linewise select mode: delete last line + call deletebufline('', 1, '$') + call append('$', ['a', 'b', 'c']) + exe "normal GgH\<Del>" + call assert_equal(['', 'a', 'b'], getline(1, '$')) + + " linewise select mode: delete last two lines + call deletebufline('', 1, '$') + call append('$', ['a', 'b', 'c']) + exe "normal GkgH\<Down>\<Del>" + call assert_equal(['', 'a'], getline(1, '$')) + + bwipe! +endfunc + +" Test for blockwise select mode (g CTRL-H) +func Test_blockwise_select_mode() + new + call setline(1, ['foo', 'bar']) + call feedkeys("g\<BS>\<Right>\<Down>mm", 'xt') + call assert_equal(['mmo', 'mmr'], getline(1, '$')) + close! +endfunc + +" Test for using visual mode maps in select mode +func Test_select_mode_map() + new + vmap <buffer> <F2> 3l + call setline(1, 'Test line') + call feedkeys("gh\<F2>map", 'xt') + call assert_equal('map line', getline(1)) + + vmap <buffer> <F2> ygV + call feedkeys("0gh\<Right>\<Right>\<F2>cwabc", 'xt') + call assert_equal('abc line', getline(1)) + + vmap <buffer> <F2> :<C-U>let v=100<CR> + call feedkeys("gggh\<Right>\<Right>\<F2>foo", 'xt') + call assert_equal('foo line', getline(1)) + + " reselect the select mode using gv from a visual mode map + vmap <buffer> <F2> gv + set selectmode=cmd + call feedkeys("0gh\<F2>map", 'xt') + call assert_equal('map line', getline(1)) + set selectmode& + + close! +endfunc + " Test for selecting a register with CTRL-R func Test_selectmode_register() new diff --git a/src/nvim/testdir/test_spell.vim b/src/nvim/testdir/test_spell.vim index 7744c5bcca..8ab8204b10 100644 --- a/src/nvim/testdir/test_spell.vim +++ b/src/nvim/testdir/test_spell.vim @@ -474,6 +474,16 @@ func Test_spellsuggest_option_expr() bwipe! endfunc +func Test_spellsuggest_timeout() + set spellsuggest=timeout:30 + set spellsuggest=timeout:-123 + set spellsuggest=timeout:999999 + call assert_fails('set spellsuggest=timeout', 'E474:') + call assert_fails('set spellsuggest=timeout:x', 'E474:') + call assert_fails('set spellsuggest=timeout:-x', 'E474:') + call assert_fails('set spellsuggest=timeout:--9', 'E474:') +endfunc + func Test_spellinfo() throw 'skipped: Nvim does not support enc=latin1' new diff --git a/src/nvim/testdir/test_spellfile.vim b/src/nvim/testdir/test_spellfile.vim index b028e9d969..dbffbafed9 100644 --- a/src/nvim/testdir/test_spellfile.vim +++ b/src/nvim/testdir/test_spellfile.vim @@ -25,6 +25,18 @@ func Test_spell_normal() let cnt=readfile('./Xspellfile.add') call assert_equal('goood', cnt[0]) + " zg should fail in operator-pending mode + call assert_beeps('norm! czg') + + " zg fails in visual mode when not able to get the visual text + call assert_beeps('norm! ggVjzg') + norm! V + + " zg fails for a non-identifier word + call append(line('$'), '###') + call assert_fails('norm! Gzg', 'E349:') + $d + " Test for zw 2 norm! $zw @@ -907,6 +919,33 @@ func Test_spellfile_COMMON() call delete('XtestCOMMON-utf8.spl') endfunc +" Test NOSUGGEST (see :help spell-COMMON) +func Test_spellfile_NOSUGGEST() + call writefile(['2', 'foo/X', 'fog'], 'XtestNOSUGGEST.dic') + call writefile(['NOSUGGEST X'], 'XtestNOSUGGEST.aff') + + mkspell! XtestNOSUGGEST-utf8.spl XtestNOSUGGEST + set spell spelllang=XtestNOSUGGEST-utf8.spl + + for goodword in ['foo', 'Foo', 'FOO', 'fog', 'Fog', 'FOG'] + call assert_equal(['', ''], spellbadword(goodword), goodword) + endfor + for badword in ['foO', 'fOO', 'fooo', 'foog', 'foofog', 'fogfoo'] + call assert_equal([badword, 'bad'], spellbadword(badword)) + endfor + + call assert_equal(['fog'], spellsuggest('fooo', 1)) + call assert_equal(['fog'], spellsuggest('fOo', 1)) + call assert_equal(['fog'], spellsuggest('foG', 1)) + call assert_equal(['fog'], spellsuggest('fogg', 1)) + + set spell& spelllang& + call delete('XtestNOSUGGEST.dic') + call delete('XtestNOSUGGEST.aff') + call delete('XtestNOSUGGEST-utf8.spl') +endfunc + + " Test CIRCUMFIX (see: :help spell-CIRCUMFIX) func Test_spellfile_CIRCUMFIX() " Example taken verbatim from https://github.com/hunspell/hunspell/tree/master/tests diff --git a/src/nvim/testdir/test_startup.vim b/src/nvim/testdir/test_startup.vim index 39fafbf7b4..880ca62685 100644 --- a/src/nvim/testdir/test_startup.vim +++ b/src/nvim/testdir/test_startup.vim @@ -603,7 +603,7 @@ func Test_invalid_args() call assert_equal(0, v:shell_error) if has('quickfix') - " Detect invalid repeated arguments '-t foo -t foo", '-q foo -q foo'. + " Detect invalid repeated arguments '-t foo -t foo', '-q foo -q foo'. for opt in ['-t', '-q'] let out = split(system(GetVimCommand() .. repeat(' ' .. opt .. ' foo', 2)), "\n") call assert_equal(1, v:shell_error) @@ -855,7 +855,7 @@ func Test_t_arg() call writefile([' first', ' second', ' third'], 'Xfile1') for t_arg in ['-t second', '-tsecond'] - if RunVim(before, after, '-t second') + if RunVim(before, after, t_arg) call assert_equal(['Xfile1:L2C5'], readfile('Xtestout'), t_arg) call delete('Xtestout') endif diff --git a/src/nvim/testdir/test_substitute.vim b/src/nvim/testdir/test_substitute.vim index f795d1c0cf..b3a80072d9 100644 --- a/src/nvim/testdir/test_substitute.vim +++ b/src/nvim/testdir/test_substitute.vim @@ -858,6 +858,44 @@ func Test_substitute_skipped_range() bwipe! endfunc +" Test using the 'gdefault' option (when on, flag 'g' is default on). +func Test_substitute_gdefault() + new + + " First check without 'gdefault' + call setline(1, 'foo bar foo') + s/foo/FOO/ + call assert_equal('FOO bar foo', getline(1)) + call setline(1, 'foo bar foo') + s/foo/FOO/g + call assert_equal('FOO bar FOO', getline(1)) + call setline(1, 'foo bar foo') + s/foo/FOO/gg + call assert_equal('FOO bar foo', getline(1)) + + " Then check with 'gdefault' + set gdefault + call setline(1, 'foo bar foo') + s/foo/FOO/ + call assert_equal('FOO bar FOO', getline(1)) + call setline(1, 'foo bar foo') + s/foo/FOO/g + call assert_equal('FOO bar foo', getline(1)) + call setline(1, 'foo bar foo') + s/foo/FOO/gg + call assert_equal('FOO bar FOO', getline(1)) + + " Setting 'compatible' should reset 'gdefault' + call assert_equal(1, &gdefault) + " set compatible + set nogdefault + call assert_equal(0, &gdefault) + set nocompatible + call assert_equal(0, &gdefault) + + bw! +endfunc + " This was using "old_sub" after it was freed. func Test_using_old_sub() " set compatible maxfuncdepth=10 diff --git a/src/nvim/testdir/test_swap.vim b/src/nvim/testdir/test_swap.vim index 923e1cbf50..34d1d585ce 100644 --- a/src/nvim/testdir/test_swap.vim +++ b/src/nvim/testdir/test_swap.vim @@ -2,6 +2,7 @@ source check.vim source shared.vim +source term_util.vim func s:swapname() return trim(execute('swapname')) @@ -374,24 +375,26 @@ func Test_swap_prompt_splitwin() call WaitForAssert({-> assert_match('^1$', term_getline(buf, 20))}) call StopVimInTerminal(buf) - " This caused Vim to crash when typing "q". - " TODO: it does not actually reproduce the crash. - call writefile(['au BufAdd * set virtualedit=all'], 'Xvimrc') - - let buf = RunVimInTerminal('-u Xvimrc Xfile1', {'rows': 20, 'wait_for_ruler': 0}) - call TermWait(buf) - call WaitForAssert({-> assert_match('^\[O\]pen Read-Only, (E)dit anyway, (R)ecover, (Q)uit, (A)bort:', term_getline(buf, 20))}) + " This caused Vim to crash when typing "q" at the swap file prompt. + let buf = RunVimInTerminal('-c "au bufadd * let foo_w = wincol()"', {'rows': 18}) + call term_sendkeys(buf, ":e Xfile1\<CR>") + call WaitForAssert({-> assert_match('More', term_getline(buf, 18))}) + call term_sendkeys(buf, " ") + call WaitForAssert({-> assert_match('^\[O\]pen Read-Only, (E)dit anyway, (R)ecover, (Q)uit, (A)bort:', term_getline(buf, 18))}) call term_sendkeys(buf, "q") + call TermWait(buf) + " check that Vim is still running + call term_sendkeys(buf, ":echo 'hello'\<CR>") + call WaitForAssert({-> assert_match('^hello', term_getline(buf, 18))}) + call term_sendkeys(buf, ":%bwipe!\<CR>") + call StopVimInTerminal(buf) %bwipe! call delete('Xfile1') - call delete('Xvimrc') endfunc func Test_swap_symlink() - if !has("unix") - return - endif + CheckUnix call writefile(['text'], 'Xtestfile') silent !ln -s -f Xtestfile Xtestlink diff --git a/src/nvim/testdir/test_syntax.vim b/src/nvim/testdir/test_syntax.vim index 7ba0149971..ccff01486e 100644 --- a/src/nvim/testdir/test_syntax.vim +++ b/src/nvim/testdir/test_syntax.vim @@ -188,22 +188,26 @@ func Test_syntax_completion() call assert_equal('"syn sync ccomment clear fromstart linebreaks= linecont lines= match maxlines= minlines= region', @:) " Check that clearing "Aap" avoids it showing up before Boolean. - hi Aap ctermfg=blue + hi @Aap ctermfg=blue call feedkeys(":syn list \<C-A>\<C-B>\"\<CR>", 'tx') - call assert_match('^"syn list Aap Boolean Character ', @:) - hi clear Aap + call assert_match('^"syn list @Aap @boolean @character ', @:) + hi clear @Aap call feedkeys(":syn list \<C-A>\<C-B>\"\<CR>", 'tx') - call assert_match('^"syn list Boolean Character ', @:) + call assert_match('^"syn list @boolean @character ', @:) call feedkeys(":syn match \<C-A>\<C-B>\"\<CR>", 'tx') - call assert_match('^"syn match Boolean Character ', @:) + call assert_match('^"syn match @boolean @character ', @:) + + syn cluster Aax contains=Aap + call feedkeys(":syn list @A\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_match('^"syn list @Aax', @:) endfunc func Test_echohl_completion() call feedkeys(":echohl no\<C-A>\<C-B>\"\<CR>", 'tx') " call assert_equal('"echohl NonText Normal none', @:) - call assert_equal('"echohl NonText Normal NormalFloat NormalNC none', @:) + call assert_equal('"echohl NonText Normal NormalFloat none', @:) endfunc func Test_syntax_arg_skipped() @@ -389,7 +393,7 @@ func Test_invalid_name() syn keyword Nop yes call assert_fails("syntax keyword Wr\x17ong bar", 'E669:') syntax keyword @Wrong bar - call assert_match('W18:', execute('1messages')) + call assert_fails("syntax keyword @#Wrong bar", 'E5248:') syn clear hi clear Nop hi clear @Wrong diff --git a/src/nvim/testdir/test_tabpage.vim b/src/nvim/testdir/test_tabpage.vim index 07f35ddb4f..6d468ec9de 100644 --- a/src/nvim/testdir/test_tabpage.vim +++ b/src/nvim/testdir/test_tabpage.vim @@ -670,15 +670,19 @@ func Test_tabline_tabmenu() call assert_equal(3, tabpagenr('$')) " go to tab page 2 in operator-pending mode (should beep) - call assert_beeps('call feedkeys("f" .. TabLineSelectPageCode(2), "Lx!")') + call assert_beeps('call feedkeys("c" .. TabLineSelectPageCode(2), "Lx!")') + call assert_equal(2, tabpagenr()) + call assert_equal(3, tabpagenr('$')) " open new tab page before tab page 1 in operator-pending mode (should beep) - call assert_beeps('call feedkeys("f" .. TabMenuNewItemCode(1), "Lx!")') + call assert_beeps('call feedkeys("c" .. TabMenuNewItemCode(1), "Lx!")') + call assert_equal(1, tabpagenr()) + call assert_equal(4, tabpagenr('$')) " open new tab page after tab page 3 in normal mode call feedkeys(TabMenuNewItemCode(4), "Lx!") call assert_equal(4, tabpagenr()) - call assert_equal(4, tabpagenr('$')) + call assert_equal(5, tabpagenr('$')) " go to tab page 2 in insert mode call feedkeys("i" .. TabLineSelectPageCode(2) .. "\<C-C>", "Lx!") @@ -686,17 +690,17 @@ func Test_tabline_tabmenu() " close tab page 2 in insert mode call feedkeys("i" .. TabMenuCloseItemCode(2) .. "\<C-C>", "Lx!") - call assert_equal(3, tabpagenr('$')) + call assert_equal(4, tabpagenr('$')) " open new tab page before tab page 3 in insert mode call feedkeys("i" .. TabMenuNewItemCode(3) .. "\<C-C>", "Lx!") call assert_equal(3, tabpagenr()) - call assert_equal(4, tabpagenr('$')) + call assert_equal(5, tabpagenr('$')) " open new tab page after tab page 4 in insert mode call feedkeys("i" .. TabMenuNewItemCode(5) .. "\<C-C>", "Lx!") call assert_equal(5, tabpagenr()) - call assert_equal(5, tabpagenr('$')) + call assert_equal(6, tabpagenr('$')) %bw! endfunc diff --git a/src/nvim/testdir/test_tagjump.vim b/src/nvim/testdir/test_tagjump.vim index 1dd656ece5..04c0218f74 100644 --- a/src/nvim/testdir/test_tagjump.vim +++ b/src/nvim/testdir/test_tagjump.vim @@ -1133,7 +1133,7 @@ endfunc " Test for [i, ]i, [I, ]I, [ CTRL-I, ] CTRL-I and CTRL-W i commands func Test_inc_search() new - call setline(1, ['1:foo', '2:foo', 'foo', '3:foo', '4:foo']) + call setline(1, ['1:foo', '2:foo', 'foo', '3:foo', '4:foo', '===']) call cursor(3, 1) " Test for [i and ]i @@ -1143,6 +1143,9 @@ func Test_inc_search() call assert_equal('3:foo', execute('normal ]i')) call assert_equal('4:foo', execute('normal 2]i')) call assert_fails('normal 3]i', 'E389:') + call assert_fails('normal G]i', 'E349:') + call assert_fails('normal [i', 'E349:') + call cursor(3, 1) " Test for :isearch call assert_equal('1:foo', execute('isearch foo')) @@ -1163,6 +1166,9 @@ func Test_inc_search() call assert_equal([ \ ' 1: 4 3:foo', \ ' 2: 5 4:foo'], split(execute('normal ]I'), "\n")) + call assert_fails('normal G]I', 'E349:') + call assert_fails('normal [I', 'E349:') + call cursor(3, 1) " Test for :ilist call assert_equal([ @@ -1188,6 +1194,9 @@ func Test_inc_search() exe "normal k2]\t" call assert_equal([5, 3], [line('.'), col('.')]) call assert_fails("normal 2k3]\t", 'E389:') + call assert_fails("normal G[\t", 'E349:') + call assert_fails("normal ]\t", 'E349:') + call cursor(3, 1) " Test for :ijump call cursor(3, 1) @@ -1212,6 +1221,8 @@ func Test_inc_search() close call assert_fails('3wincmd i', 'E387:') call assert_fails('6wincmd i', 'E389:') + call assert_fails("normal G\<C-W>i", 'E349:') + call cursor(3, 1) " Test for :isplit isplit foo diff --git a/src/nvim/testdir/test_textformat.vim b/src/nvim/testdir/test_textformat.vim index 0fc56083aa..4eb6e69adf 100644 --- a/src/nvim/testdir/test_textformat.vim +++ b/src/nvim/testdir/test_textformat.vim @@ -962,78 +962,6 @@ func Test_tw_2_fo_tm_noai() bwipe! endfunc -func Test_tw_2_fo_cqm_com() - new - let t =<< trim END - { - X - Xa - XaY - XY - XYZ - X Y - X YZ - XX - XXa - XXY - } - END - call setline(1, t) - call cursor(2, 1) - - set tw=2 fo=cqm comments=n:X - exe "normal gqgqjgqgqjgqgqjgqgqjgqgqjgqgqjgqgqjgqgqjgqgqjgqgq" - let t =<< trim END - X - Xa - XaY - XY - XYZ - X Y - X YZ - XX - XXa - XXY - END - exe "normal o\n" . join(t, "\n") - - let expected =<< trim END - { - X - Xa - Xa - XY - XY - XY - XZ - X Y - X Y - X Z - XX - XXa - XXY - - X - Xa - Xa - XY - XY - XY - XZ - X Y - X Y - X Z - XX - XXa - XXY - } - END - call assert_equal(expected, getline(1, '$')) - - set tw& fo& comments& - bwipe! -endfunc - func Test_tw_2_fo_tm_replace() new let t =<< trim END @@ -1161,140 +1089,6 @@ func Test_whichwrap_multi_byte() bwipe! endfunc -" Test for automatically adding comment leaders in insert mode -func Test_threepiece_comment() - new - setlocal expandtab - call setline(1, ["\t/*"]) - setlocal formatoptions=croql - call cursor(1, 3) - call feedkeys("A\<cr>\<cr>/", 'tnix') - call assert_equal(["\t/*", " *", " */"], getline(1, '$')) - - " If a comment ends in a single line, then don't add it in the next line - %d - call setline(1, '/* line1 */') - call feedkeys("A\<CR>next line", 'xt') - call assert_equal(['/* line1 */', 'next line'], getline(1, '$')) - - %d - " Copy the trailing indentation from the leader comment to a new line - setlocal autoindent noexpandtab - call feedkeys("a\t/*\tone\ntwo\n/", 'xt') - call assert_equal(["\t/*\tone", "\t *\ttwo", "\t */"], getline(1, '$')) - close! -endfunc - -" Test for the 'f' flag in 'comments' (only the first line has the comment -" string) -func Test_firstline_comment() - new - setlocal comments=f:- fo+=ro - exe "normal i- B\nD\<C-C>ggoC\<C-C>ggOA\<C-C>" - call assert_equal(['A', '- B', ' C', ' D'], getline(1, '$')) - %d - setlocal comments=:- - exe "normal i- B\nD\<C-C>ggoC\<C-C>ggOA\<C-C>" - call assert_equal(['- A', '- B', '- C', '- D'], getline(1, '$')) - %bw! -endfunc - -" Test for the 'r' flag in 'comments' (right align comment) -func Test_comment_rightalign() - new - setlocal comments=sr:/***,m:**,ex-2:******/ fo+=ro - exe "normal i=\<C-C>o\t /***\nD\n/" - exe "normal 2GOA\<C-C>joB\<C-C>jOC\<C-C>joE\<C-C>GOF\<C-C>joG" - let expected =<< trim END - = - A - /*** - ** B - ** C - ** D - ** E - ** F - ******/ - G - END - call assert_equal(expected, getline(1, '$')) - %bw! -endfunc - -" Test for the 'b' flag in 'comments' -func Test_comment_blank() - new - setlocal comments=b:* fo+=ro - exe "normal i* E\nF\n\<BS>G\nH\<C-C>ggOC\<C-C>O\<BS>B\<C-C>OA\<C-C>2joD" - let expected =<< trim END - A - *B - * C - * D - * E - * F - *G - H - END - call assert_equal(expected, getline(1, '$')) - %bw! -endfunc - -" Test for the 'n' flag in comments -func Test_comment_nested() - new - setlocal comments=n:> fo+=ro - exe "normal i> B\nD\<C-C>ggOA\<C-C>joC\<C-C>Go\<BS>>>> F\nH" - exe "normal 5GOE\<C-C>6GoG" - let expected =<< trim END - > A - > B - > C - > D - >>>> E - >>>> F - >>>> G - >>>> H - END - call assert_equal(expected, getline(1, '$')) - %bw! -endfunc - -" Test for a space character in 'comments' setting -func Test_comment_space() - new - setlocal comments=b:\ > fo+=ro - exe "normal i> B\nD\<C-C>ggOA\<C-C>joC" - exe "normal Go > F\nH\<C-C>kOE\<C-C>joG" - let expected =<< trim END - A - > B - C - D - > E - > F - > G - > H - END - call assert_equal(expected, getline(1, '$')) - %bw! -endfunc - -" Test for the 'O' flag in 'comments' -func Test_comment_O() - new - setlocal comments=Ob:* fo+=ro - exe "normal i* B\nD\<C-C>kOA\<C-C>joC" - let expected =<< trim END - A - * B - * C - * D - END - call assert_equal(expected, getline(1, '$')) - %bw! -endfunc - " Test for 'a' and 'w' flags in 'formatoptions' func Test_fo_a_w() new @@ -1334,25 +1128,6 @@ func Test_fo_a_w() %bw! endfunc -" Test for 'j' flag in 'formatoptions' -func Test_fo_j() - new - setlocal fo+=j comments=:// - call setline(1, ['i++; // comment1', ' // comment2']) - normal J - call assert_equal('i++; // comment1 comment2', getline(1)) - setlocal fo-=j - call setline(1, ['i++; // comment1', ' // comment2']) - normal J - call assert_equal('i++; // comment1 // comment2', getline(1)) - " Test with nested comments - setlocal fo+=j comments=n:>,n:) - call setline(1, ['i++; > ) > ) comment1', ' > ) comment2']) - normal J - call assert_equal('i++; > ) > ) comment1 comment2', getline(1)) - %bw! -endfunc - " Test for formatting lines using gq in visual mode func Test_visual_gq_format() new @@ -1487,53 +1262,6 @@ func Test_fo_2() close! endfunc -" Test for formatting lines where only the first line has a comment. -func Test_fo_gq_with_firstline_comment() - new - setlocal formatoptions=tcq - call setline(1, ['- one two', 'three']) - normal gggqG - call assert_equal(['- one two three'], getline(1, '$')) - - %d - call setline(1, ['- one', '- two']) - normal gggqG - call assert_equal(['- one', '- two'], getline(1, '$')) - close! -endfunc - -" Test for trying to join a comment line with a non-comment line -func Test_join_comments() - new - call setline(1, ['one', '/* two */', 'three']) - normal gggqG - call assert_equal(['one', '/* two */', 'three'], getline(1, '$')) - close! -endfunc - -" Test for using 'a' in 'formatoptions' with comments -func Test_autoformat_comments() - new - setlocal formatoptions+=a - call feedkeys("a- one\n- two\n", 'xt') - call assert_equal(['- one', '- two', ''], getline(1, '$')) - - %d - call feedkeys("a\none\n", 'xt') - call assert_equal(['', 'one', ''], getline(1, '$')) - - setlocal formatoptions+=aw - %d - call feedkeys("aone \ntwo\n", 'xt') - call assert_equal(['one two', ''], getline(1, '$')) - - %d - call feedkeys("aone\ntwo\n", 'xt') - call assert_equal(['one', 'two', ''], getline(1, '$')) - - close! -endfunc - " This was leaving the cursor after the end of a line. Complicated way to " have the problem show up with valgrind. func Test_correct_cursor_position() diff --git a/src/nvim/testdir/test_textobjects.vim b/src/nvim/testdir/test_textobjects.vim index eeb2946a8b..f21d6fcb99 100644 --- a/src/nvim/testdir/test_textobjects.vim +++ b/src/nvim/testdir/test_textobjects.vim @@ -1,7 +1,6 @@ " Test for textobjects source check.vim -CheckFeature textobjects func CpoM(line, useM, expected) new diff --git a/src/nvim/testdir/test_trycatch.vim b/src/nvim/testdir/test_trycatch.vim index 646594e482..d71bb5bbb8 100644 --- a/src/nvim/testdir/test_trycatch.vim +++ b/src/nvim/testdir/test_trycatch.vim @@ -2027,16 +2027,179 @@ func Test_try_catch_verbose() endtry redir END let expected = [ - \ 'Exception thrown: Vim(echo):E121: Undefined variable: i', - \ '', - \ 'Exception caught: Vim(echo):E121: Undefined variable: i', - \ '', - \ 'Exception finished: Vim(echo):E121: Undefined variable: i' - \ ] + \ 'Exception thrown: Vim(echo):E121: Undefined variable: i', '', + \ 'Exception caught: Vim(echo):E121: Undefined variable: i', '', + \ 'Exception finished: Vim(echo):E121: Undefined variable: i'] call assert_equal(expected, split(msg, "\n")) + + " Test for verbose messages displayed when an exception is discarded + redir => msg + try + try + throw 'abc' + finally + throw 'xyz' + endtry + catch + endtry + redir END + let expected = [ + \ 'Exception thrown: abc', '', + \ 'Exception made pending: abc', '', + \ 'Exception thrown: xyz', '', + \ 'Exception discarded: abc', '', + \ 'Exception caught: xyz', '', + \ 'Exception finished: xyz'] + call assert_equal(expected, split(msg, "\n")) + + " Test for messages displayed when :throw is resumed after :finally + redir => msg + try + try + throw 'abc' + finally + endtry + catch + endtry + redir END + let expected = [ + \ 'Exception thrown: abc', '', + \ 'Exception made pending: abc', '', + \ 'Exception resumed: abc', '', + \ 'Exception caught: abc', '', + \ 'Exception finished: abc'] + call assert_equal(expected, split(msg, "\n")) + + " Test for messages displayed when :break is resumed after :finally + redir => msg + for i in range(1) + try + break + finally + endtry + endfor + redir END + let expected = [':break made pending', '', ':break resumed'] + call assert_equal(expected, split(msg, "\n")) + + " Test for messages displayed when :continue is resumed after :finally + redir => msg + for i in range(1) + try + continue + finally + endtry + endfor + redir END + let expected = [':continue made pending', '', ':continue resumed'] + call assert_equal(expected, split(msg, "\n")) + + " Test for messages displayed when :return is resumed after :finally + func Xtest() + try + return 'vim' + finally + endtry + endfunc + redir => msg + call Xtest() + redir END + let expected = [ + \ 'calling Xtest()', '', + \ ':return vim made pending', '', + \ ':return vim resumed', '', + \ 'Xtest returning ''vim''', '', + \ 'continuing in Test_try_catch_verbose'] + call assert_equal(expected, split(msg, "\n")) + delfunc Xtest + + " Test for messages displayed when :finish is resumed after :finally + call writefile(['try', 'finish', 'finally', 'endtry'], 'Xscript') + redir => msg + source Xscript + redir END + let expected = [ + \ ':finish made pending', '', + \ ':finish resumed', '', + \ 'finished sourcing Xscript', + \ 'continuing in Test_try_catch_verbose'] + call assert_equal(expected, split(msg, "\n")[1:]) + call delete('Xscript') + + " Test for messages displayed when a pending :continue is discarded by an + " exception in a finally handler + redir => msg + try + for i in range(1) + try + continue + finally + throw 'abc' + endtry + endfor + catch + endtry + redir END + let expected = [ + \ ':continue made pending', '', + \ 'Exception thrown: abc', '', + \ ':continue discarded', '', + \ 'Exception caught: abc', '', + \ 'Exception finished: abc'] + call assert_equal(expected, split(msg, "\n")) + set verbose& endfunc +" Test for throwing an exception from a BufEnter autocmd {{{1 +func Test_BufEnter_exception() + augroup bufenter_exception + au! + autocmd BufEnter Xfile1 throw 'abc' + augroup END + + let caught_abc = 0 + try + sp Xfile1 + catch /^abc/ + let caught_abc = 1 + endtry + call assert_equal(1, caught_abc) + call assert_equal(1, winnr('$')) + + augroup bufenter_exception + au! + augroup END + augroup! bufenter_exception + %bwipe! + + " Test for recursively throwing exceptions in autocmds + augroup bufenter_exception + au! + autocmd BufEnter Xfile1 throw 'bufenter' + autocmd BufLeave Xfile1 throw 'bufleave' + augroup END + + let ex_count = 0 + try + try + sp Xfile1 + catch /^bufenter/ + let ex_count += 1 + endtry + catch /^bufleave/ + let ex_count += 10 + endtry + call assert_equal(10, ex_count) + call assert_equal(2, winnr('$')) + + augroup bufenter_exception + au! + augroup END + augroup! bufenter_exception + %bwipe! +endfunc + " Test for using throw in a called function with following error {{{1 func Test_user_command_throw_in_function_call() let lines =<< trim END diff --git a/src/nvim/testdir/test_undo.vim b/src/nvim/testdir/test_undo.vim index a9ec405aa4..eb47af08d7 100644 --- a/src/nvim/testdir/test_undo.vim +++ b/src/nvim/testdir/test_undo.vim @@ -336,7 +336,7 @@ func Test_undofile_earlier() " create undofile with timestamps older than Vim startup time. let t0 = localtime() - 43200 call test_settime(t0) - new Xfile + new XfileEarlier call feedkeys("ione\<Esc>", 'xt') set ul=100 call test_settime(t0 + 1) @@ -350,12 +350,12 @@ func Test_undofile_earlier() bwipe! " restore normal timestamps. call test_settime(0) - new Xfile + new XfileEarlier rundo Xundofile earlier 1d call assert_equal('', getline(1)) bwipe! - call delete('Xfile') + call delete('XfileEarlier') call delete('Xundofile') endfunc diff --git a/src/nvim/testdir/test_user_func.vim b/src/nvim/testdir/test_user_func.vim index 5231ef7b4f..c14624f5b4 100644 --- a/src/nvim/testdir/test_user_func.vim +++ b/src/nvim/testdir/test_user_func.vim @@ -169,3 +169,10 @@ endfunc func Test_failed_call_in_try() try | call UnknownFunc() | catch | endtry endfunc + +" Test for listing user-defined functions +func Test_function_list() + call assert_fails("function Xabc", 'E123:') +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_utf8.vim b/src/nvim/testdir/test_utf8.vim index 9b010a5dbc..ab3503c282 100644 --- a/src/nvim/testdir/test_utf8.vim +++ b/src/nvim/testdir/test_utf8.vim @@ -140,6 +140,51 @@ func Test_list2str_str2list_latin1() call assert_equal(s, sres) endfunc +func Test_setcellwidths() + call setcellwidths([ + \ [0x1330, 0x1330, 2], + \ [9999, 10000, 1], + \ [0x1337, 0x1339, 2], + \]) + + call assert_equal(2, strwidth("\u1330")) + call assert_equal(1, strwidth("\u1336")) + call assert_equal(2, strwidth("\u1337")) + call assert_equal(2, strwidth("\u1339")) + call assert_equal(1, strwidth("\u133a")) + + call setcellwidths([]) + + call assert_fails('call setcellwidths(1)', 'E714:') + + call assert_fails('call setcellwidths([1, 2, 0])', 'E1109:') + + call assert_fails('call setcellwidths([[0x101]])', 'E1110:') + call assert_fails('call setcellwidths([[0x101, 0x102]])', 'E1110:') + call assert_fails('call setcellwidths([[0x101, 0x102, 1, 4]])', 'E1110:') + call assert_fails('call setcellwidths([["a"]])', 'E1110:') + + call assert_fails('call setcellwidths([[0x102, 0x101, 1]])', 'E1111:') + + call assert_fails('call setcellwidths([[0x101, 0x102, 0]])', 'E1112:') + call assert_fails('call setcellwidths([[0x101, 0x102, 3]])', 'E1112:') + + call assert_fails('call setcellwidths([[0x111, 0x122, 1], [0x115, 0x116, 2]])', 'E1113:') + call assert_fails('call setcellwidths([[0x111, 0x122, 1], [0x122, 0x123, 2]])', 'E1113:') + + call assert_fails('call setcellwidths([[0x33, 0x44, 2]])', 'E1114:') + + set listchars=tab:--\\u2192 + call assert_fails('call setcellwidths([[0x2192, 0x2192, 2]])', 'E834:') + + set fillchars=stl:\\u2501 + call assert_fails('call setcellwidths([[0x2501, 0x2501, 2]])', 'E835:') + + set listchars& + set fillchars& + call setcellwidths([]) +endfunc + func Test_print_overlong() " Text with more composing characters than MB_MAXBYTES. new diff --git a/src/nvim/testdir/test_viminfo.vim b/src/nvim/testdir/test_viminfo.vim new file mode 100644 index 0000000000..2d6d598011 --- /dev/null +++ b/src/nvim/testdir/test_viminfo.vim @@ -0,0 +1,21 @@ + +" Test for errors in setting 'viminfo' +func Test_viminfo_option_error() + " Missing number + call assert_fails('set viminfo=\"', 'E526:') + for c in split("'/:<@s", '\zs') + call assert_fails('set viminfo=' .. c, 'E526:') + endfor + + " Missing comma + call assert_fails('set viminfo=%10!', 'E527:') + call assert_fails('set viminfo=!%10', 'E527:') + call assert_fails('set viminfo=h%10', 'E527:') + call assert_fails('set viminfo=c%10', 'E527:') + call assert_fails('set viminfo=:10%10', 'E527:') + + " Missing ' setting + call assert_fails('set viminfo=%10', 'E528:') +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_vimscript.vim b/src/nvim/testdir/test_vimscript.vim index de4629451b..97e879c9ef 100644 --- a/src/nvim/testdir/test_vimscript.vim +++ b/src/nvim/testdir/test_vimscript.vim @@ -1165,10 +1165,10 @@ func Test_type() " call assert_equal(0, 0 + v:none) call assert_equal(0, 0 + v:null) - call assert_equal('false', '' . v:false) - call assert_equal('true', '' . v:true) - " call assert_equal('none', '' . v:none) - call assert_equal('null', '' . v:null) + call assert_equal('v:false', '' . v:false) + call assert_equal('v:true', '' . v:true) + " call assert_equal('v:none', '' . v:none) + call assert_equal('v:null', '' . v:null) call assert_true(v:false == 0) call assert_false(v:false != 0) @@ -1573,6 +1573,23 @@ func Test_script_local_func() enew! | close endfunc +func Test_script_expand_sfile() + let lines =<< trim END + func s:snr() + return expand('<sfile>') + endfunc + let g:result = s:snr() + END + call writefile(lines, 'Xexpand') + source Xexpand + call assert_match('<SNR>\d\+_snr', g:result) + source Xexpand + call assert_match('<SNR>\d\+_snr', g:result) + + call delete('Xexpand') + unlet g:result +endfunc + func Test_compound_assignment_operators() " Test for number let x = 1 @@ -1812,6 +1829,9 @@ func Test_missing_end() endtry call assert_equal(1, caught_e733) + " Using endfunc with :if + call assert_fails('exe "if 1 | endfunc | endif"', 'E193:') + " Missing 'in' in a :for statement call assert_fails('for i range(1) | endfor', 'E690:') endfunc @@ -1858,6 +1878,15 @@ func Test_deep_nest() @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 [SCRIPT] call writefile(lines, 'Xscript') @@ -1865,20 +1894,31 @@ func Test_deep_nest() " Deep nesting of if ... endif call term_sendkeys(buf, ":call Test1()\n") + call term_wait(buf) call WaitForAssert({-> assert_match('^E579:', term_getline(buf, 5))}) " Deep nesting of for ... endfor call term_sendkeys(buf, ":call Test2()\n") + call term_wait(buf) call WaitForAssert({-> assert_match('^E585:', term_getline(buf, 5))}) " Deep nesting of while ... endwhile call term_sendkeys(buf, ":call Test3()\n") + call term_wait(buf) call WaitForAssert({-> assert_match('^E585:', term_getline(buf, 5))}) " Deep nesting of try ... endtry call term_sendkeys(buf, ":call Test4()\n") + call term_wait(buf) call WaitForAssert({-> assert_match('^E601:', term_getline(buf, 5))}) + " Deep nesting of function ... endfunction + call term_sendkeys(buf, ":call Test5()\n") + call term_wait(buf) + call WaitForAssert({-> assert_match('^E1058:', term_getline(buf, 4))}) + call term_sendkeys(buf, "\<C-C>\n") + call term_wait(buf) + "let l = '' "for i in range(1, 6) " let l ..= term_getline(buf, i) . "\n" diff --git a/src/nvim/testdir/test_visual.vim b/src/nvim/testdir/test_visual.vim index f9ac0e0884..9c1ad0c099 100644 --- a/src/nvim/testdir/test_visual.vim +++ b/src/nvim/testdir/test_visual.vim @@ -378,14 +378,17 @@ endfunc func Test_Visual_paragraph_textobject() new - call setline(1, ['First line.', - \ '', - \ 'Second line.', - \ 'Third line.', - \ 'Fourth line.', - \ 'Fifth line.', - \ '', - \ 'Sixth line.']) + let lines =<< trim [END] + First line. + + Second line. + Third line. + Fourth line. + Fifth line. + + Sixth line. + [END] + call setline(1, lines) " When start and end of visual area are identical, 'ap' or 'ip' select " the whole paragraph. @@ -639,6 +642,14 @@ func Test_characterwise_visual_mode() normal Gkvj$d call assert_equal(['', 'a', ''], getline(1, '$')) + " characterwise visual mode: use a count with the visual mode from the last + " line in the buffer + %d _ + call setline(1, ['one', 'two', 'three', 'four']) + norm! vj$y + norm! G1vy + call assert_equal('four', @") + " characterwise visual mode: replace a single character line and the eol %d _ call setline(1, "a") @@ -653,92 +664,6 @@ func Test_characterwise_visual_mode() bwipe! endfunc -func Test_characterwise_select_mode() - new - - " Select mode maps - snoremap <lt>End> <End> - snoremap <lt>Down> <Down> - snoremap <lt>Del> <Del> - - " characterwise select mode: delete middle line - call deletebufline('', 1, '$') - call append('$', ['a', 'b', 'c']) - exe "normal Gkkgh\<End>\<Del>" - call assert_equal(['', 'b', 'c'], getline(1, '$')) - - " characterwise select mode: delete middle two lines - call deletebufline('', 1, '$') - call append('$', ['a', 'b', 'c']) - exe "normal Gkkgh\<Down>\<End>\<Del>" - call assert_equal(['', 'c'], getline(1, '$')) - - " characterwise select mode: delete last line - call deletebufline('', 1, '$') - call append('$', ['a', 'b', 'c']) - exe "normal Ggh\<End>\<Del>" - call assert_equal(['', 'a', 'b', ''], getline(1, '$')) - - " characterwise select mode: delete last two lines - call deletebufline('', 1, '$') - call append('$', ['a', 'b', 'c']) - exe "normal Gkgh\<Down>\<End>\<Del>" - call assert_equal(['', 'a', ''], getline(1, '$')) - - " CTRL-H in select mode behaves like 'x' - call setline(1, 'abcdef') - exe "normal! gggh\<Right>\<Right>\<Right>\<C-H>" - call assert_equal('ef', getline(1)) - - " CTRL-O in select mode switches to visual mode for one command - call setline(1, 'abcdef') - exe "normal! gggh\<C-O>3lm" - call assert_equal('mef', getline(1)) - - sunmap <lt>End> - sunmap <lt>Down> - sunmap <lt>Del> - bwipe! -endfunc - -func Test_linewise_select_mode() - new - - " linewise select mode: delete middle line - call append('$', ['a', 'b', 'c']) - exe "normal GkkgH\<Del>" - call assert_equal(['', 'b', 'c'], getline(1, '$')) - - " linewise select mode: delete middle two lines - call deletebufline('', 1, '$') - call append('$', ['a', 'b', 'c']) - exe "normal GkkgH\<Down>\<Del>" - call assert_equal(['', 'c'], getline(1, '$')) - - " linewise select mode: delete last line - call deletebufline('', 1, '$') - call append('$', ['a', 'b', 'c']) - exe "normal GgH\<Del>" - call assert_equal(['', 'a', 'b'], getline(1, '$')) - - " linewise select mode: delete last two lines - call deletebufline('', 1, '$') - call append('$', ['a', 'b', 'c']) - exe "normal GkgH\<Down>\<Del>" - call assert_equal(['', 'a'], getline(1, '$')) - - bwipe! -endfunc - -" Test for blockwise select mode (g CTRL-H) -func Test_blockwise_select_mode() - new - call setline(1, ['foo', 'bar']) - call feedkeys("g\<BS>\<Right>\<Down>mm", 'xt') - call assert_equal(['mmo', 'mmr'], getline(1, '$')) - close! -endfunc - func Test_visual_mode_put() new @@ -778,16 +703,16 @@ func Test_visual_mode_put() bwipe! endfunc -func Test_select_mode_gv() +func Test_gv_with_exclusive_selection() new - " gv in exclusive select mode after operation + " gv with exclusive selection after an operation call append('$', ['zzz ', 'äà ']) set selection=exclusive normal Gkv3lyjv3lpgvcxxx call assert_equal(['', 'zzz ', 'xxx '], getline(1, '$')) - " gv in exclusive select mode without operation + " gv with exclusive selection without an operation call deletebufline('', 1, '$') call append('$', 'zzz ') set selection=exclusive @@ -1136,32 +1061,6 @@ func Test_star_register() close! endfunc -" Test for using visual mode maps in select mode -func Test_select_mode_map() - new - vmap <buffer> <F2> 3l - call setline(1, 'Test line') - call feedkeys("gh\<F2>map", 'xt') - call assert_equal('map line', getline(1)) - - vmap <buffer> <F2> ygV - call feedkeys("0gh\<Right>\<Right>\<F2>cwabc", 'xt') - call assert_equal('abc line', getline(1)) - - vmap <buffer> <F2> :<C-U>let v=100<CR> - call feedkeys("gggh\<Right>\<Right>\<F2>foo", 'xt') - call assert_equal('foo line', getline(1)) - - " reselect the select mode using gv from a visual mode map - vmap <buffer> <F2> gv - set selectmode=cmd - call feedkeys("0gh\<F2>map", 'xt') - call assert_equal('map line', getline(1)) - set selectmode& - - close! -endfunc - " Test for changing text in visual mode with 'exclusive' selection func Test_exclusive_selection() new @@ -1178,15 +1077,38 @@ func Test_exclusive_selection() close! endfunc -" Test for starting visual mode with a count. -" This test should be run without any previous visual modes. So this should be -" run as a first test. -func Test_AAA_start_visual_mode_with_count() - new - call setline(1, ['aaaaaaa', 'aaaaaaa', 'aaaaaaa', 'aaaaaaa']) - normal! gg2Vy - call assert_equal("aaaaaaa\naaaaaaa\n", @") - close! +" Test for starting linewise visual with a count. +" This test needs to be run without any previous visual mode. Otherwise the +" count will use the count from the previous visual mode. +func Test_linewise_visual_with_count() + let after =<< trim [CODE] + call setline(1, ['one', 'two', 'three', 'four']) + norm! 3Vy + call assert_equal("one\ntwo\nthree\n", @") + call writefile(v:errors, 'Xtestout') + qall! + [CODE] + if RunVim([], after, '') + call assert_equal([], readfile('Xtestout')) + call delete('Xtestout') + endif +endfunc + +" Test for starting characterwise visual with a count. +" This test needs to be run without any previous visual mode. Otherwise the +" count will use the count from the previous visual mode. +func Test_characterwise_visual_with_count() + let after =<< trim [CODE] + call setline(1, ['one two', 'three']) + norm! l5vy + call assert_equal("ne tw", @") + call writefile(v:errors, 'Xtestout') + qall! + [CODE] + if RunVim([], after, '') + call assert_equal([], readfile('Xtestout')) + call delete('Xtestout') + endif endfunc " Test for visually selecting an inner block (iB) @@ -1550,5 +1472,25 @@ func Test_visual_area_adjusted_when_hiding() bwipe! endfunc +func Test_switch_buffer_ends_visual_mode() + enew + call setline(1, 'foo') + set hidden + set virtualedit=all + let buf1 = bufnr() + enew + let buf2 = bufnr() + call setline(1, ['', '', '', '']) + call cursor(4, 5) + call feedkeys("\<C-V>3k4h", 'xt') + exe 'buffer' buf1 + call assert_equal('n', mode()) + + set nohidden + set virtualedit= + bwipe! + exe 'bwipe!' buf2 +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_writefile.vim b/src/nvim/testdir/test_writefile.vim index bfbba1f793..a8735bcaf1 100644 --- a/src/nvim/testdir/test_writefile.vim +++ b/src/nvim/testdir/test_writefile.vim @@ -128,6 +128,25 @@ func Test_nowrite_quit_split() bwipe Xfile endfunc +func Test_writefile_sync_arg() + " This doesn't check if fsync() works, only that the argument is accepted. + call writefile(['one'], 'Xtest', 's') + call writefile(['two'], 'Xtest', 'S') + call delete('Xtest') +endfunc + +func Test_writefile_sync_dev_stdout() + if !has('unix') + return + endif + if filewritable('/dev/stdout') + " Just check that this doesn't cause an error. + call writefile(['one'], '/dev/stdout', 's') + else + throw 'Skipped: /dev/stdout is not writable' + endif +endfunc + func Test_writefile_autowrite() set autowrite new @@ -237,29 +256,18 @@ func Test_write_errors() call delete('Xfile') endfunc -func Test_writefile_sync_dev_stdout() - if !has('unix') - return - endif - if filewritable('/dev/stdout') - " Just check that this doesn't cause an error. - call writefile(['one'], '/dev/stdout', 's') - else - throw 'Skipped: /dev/stdout is not writable' - endif -endfunc - -func Test_writefile_sync_arg() - " This doesn't check if fsync() works, only that the argument is accepted. - call writefile(['one'], 'Xtest', 's') - call writefile(['two'], 'Xtest', 'S') - call delete('Xtest') +" Test for writing a file using invalid file encoding +func Test_write_invalid_encoding() + new + call setline(1, 'abc') + call assert_fails('write ++enc=axbyc Xfile', 'E213:') + close! endfunc " Tests for reading and writing files with conversion for Win32. func Test_write_file_encoding() - CheckMSWindows throw 'skipped: Nvim does not support :w ++enc=cp1251' + CheckMSWindows let save_encoding = &encoding let save_fileencodings = &fileencodings set encoding& fileencodings& diff --git a/src/nvim/testing.c b/src/nvim/testing.c index 69b687e44f..4a252dca3e 100644 --- a/src/nvim/testing.c +++ b/src/nvim/testing.c @@ -7,6 +7,7 @@ #include "nvim/eval/encode.h" #include "nvim/ex_docmd.h" #include "nvim/os/os.h" +#include "nvim/runtime.h" #include "nvim/testing.h" #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -17,21 +18,23 @@ static void prepare_assert_error(garray_T *gap) { char buf[NUMBUFLEN]; + char *sname = estack_sfile(ESTACK_NONE); ga_init(gap, 1, 100); - if (sourcing_name != NULL) { - ga_concat(gap, (char *)sourcing_name); - if (sourcing_lnum > 0) { + if (sname != NULL) { + ga_concat(gap, sname); + if (SOURCING_LNUM > 0) { ga_concat(gap, " "); } } - if (sourcing_lnum > 0) { - vim_snprintf(buf, ARRAY_SIZE(buf), "line %" PRId64, (int64_t)sourcing_lnum); + if (SOURCING_LNUM > 0) { + vim_snprintf(buf, ARRAY_SIZE(buf), "line %" PRId64, (int64_t)SOURCING_LNUM); ga_concat(gap, buf); } - if (sourcing_name != NULL || sourcing_lnum > 0) { + if (sname != NULL || SOURCING_LNUM > 0) { ga_concat(gap, ": "); } + xfree(sname); } /// Append "p[clen]" to "gap", escaping unprintable characters. @@ -323,19 +326,19 @@ static int assert_beeps(typval_T *argvars, bool no_beep) } /// "assert_beeps(cmd [, error])" function -void f_assert_beeps(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_assert_beeps(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = assert_beeps(argvars, false); } /// "assert_nobeep(cmd [, error])" function -void f_assert_nobeep(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_assert_nobeep(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = assert_beeps(argvars, true); } /// "assert_equal(expected, actual[, msg])" function -void f_assert_equal(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_assert_equal(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = assert_equal_common(argvars, ASSERT_EQUAL); } @@ -430,19 +433,19 @@ static int assert_equalfile(typval_T *argvars) } /// "assert_equalfile(fname-one, fname-two[, msg])" function -void f_assert_equalfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_assert_equalfile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = assert_equalfile(argvars); } /// "assert_notequal(expected, actual[, msg])" function -void f_assert_notequal(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_assert_notequal(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = assert_equal_common(argvars, ASSERT_NOTEQUAL); } /// "assert_exception(string[, msg])" function -void f_assert_exception(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_assert_exception(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { garray_T ga; @@ -454,7 +457,7 @@ void f_assert_exception(typval_T *argvars, typval_T *rettv, FunPtr fptr) ga_clear(&ga); rettv->vval.v_number = 1; } else if (error != NULL - && strstr((char *)get_vim_var_str(VV_EXCEPTION), error) == NULL) { + && strstr(get_vim_var_str(VV_EXCEPTION), error) == NULL) { prepare_assert_error(&ga); fill_assert_error(&ga, &argvars[1], NULL, &argvars[0], get_vim_var_tv(VV_EXCEPTION), ASSERT_OTHER); @@ -465,7 +468,7 @@ void f_assert_exception(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "assert_fails(cmd [, error [, msg]])" function -void f_assert_fails(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_assert_fails(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const char *const cmd = tv_get_string_chk(&argvars[0]); garray_T ga; @@ -490,7 +493,7 @@ void f_assert_fails(typval_T *argvars, typval_T *rettv, FunPtr fptr) const char *const error = tv_get_string_buf_chk(&argvars[1], buf); if (error == NULL - || strstr((char *)get_vim_var_str(VV_ERRMSG), error) == NULL) { + || strstr(get_vim_var_str(VV_ERRMSG), error) == NULL) { prepare_assert_error(&ga); fill_assert_error(&ga, &argvars[2], NULL, &argvars[1], get_vim_var_tv(VV_ERRMSG), ASSERT_OTHER); @@ -510,7 +513,7 @@ void f_assert_fails(typval_T *argvars, typval_T *rettv, FunPtr fptr) } // "assert_false(actual[, msg])" function -void f_assert_false(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_assert_false(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = assert_bool(argvars, false); } @@ -571,25 +574,25 @@ static int assert_inrange(typval_T *argvars) } /// "assert_inrange(lower, upper[, msg])" function -void f_assert_inrange(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_assert_inrange(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = assert_inrange(argvars); } /// "assert_match(pattern, actual[, msg])" function -void f_assert_match(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_assert_match(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = assert_match_common(argvars, ASSERT_MATCH); } /// "assert_notmatch(pattern, actual[, msg])" function -void f_assert_notmatch(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_assert_notmatch(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = assert_match_common(argvars, ASSERT_NOTMATCH); } /// "assert_report(msg)" function -void f_assert_report(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_assert_report(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { garray_T ga; @@ -601,13 +604,13 @@ void f_assert_report(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "assert_true(actual[, msg])" function -void f_assert_true(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_assert_true(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = assert_bool(argvars, true); } /// "test_garbagecollect_now()" function -void f_test_garbagecollect_now(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_test_garbagecollect_now(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { // This is dangerous, any Lists and Dicts used internally may be freed // while still in use. @@ -615,7 +618,7 @@ void f_test_garbagecollect_now(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "test_write_list_log()" function -void f_test_write_list_log(typval_T *const argvars, typval_T *const rettv, FunPtr fptr) +void f_test_write_list_log(typval_T *const argvars, typval_T *const rettv, EvalFuncData fptr) { const char *const fname = tv_get_string_chk(&argvars[0]); if (fname == NULL) { diff --git a/src/nvim/textformat.c b/src/nvim/textformat.c new file mode 100644 index 0000000000..125146f712 --- /dev/null +++ b/src/nvim/textformat.c @@ -0,0 +1,1127 @@ +// 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 + +// textformat.c: text formatting functions + +#include <stdbool.h> + +#include "nvim/ascii.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/getchar.h" +#include "nvim/globals.h" +#include "nvim/indent.h" +#include "nvim/indent_c.h" +#include "nvim/mbyte.h" +#include "nvim/memline.h" +#include "nvim/move.h" +#include "nvim/normal.h" +#include "nvim/ops.h" +#include "nvim/option.h" +#include "nvim/os/input.h" +#include "nvim/pos.h" +#include "nvim/search.h" +#include "nvim/strings.h" +#include "nvim/textformat.h" +#include "nvim/textobject.h" +#include "nvim/undo.h" +#include "nvim/vim.h" +#include "nvim/window.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "textformat.c.generated.h" +#endif + +static bool did_add_space = false; ///< auto_format() added an extra space + ///< under the cursor + +#define WHITECHAR(cc) (ascii_iswhite(cc) \ + && !utf_iscomposing(utf_ptr2char((char *)get_cursor_pos_ptr() + 1))) + +/// Return true if format option 'x' is in effect. +/// Take care of no formatting when 'paste' is set. +bool has_format_option(int x) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ + if (p_paste) { + return false; + } + return vim_strchr(curbuf->b_p_fo, x) != NULL; +} + +/// Format text at the current insert position. +/// +/// If the INSCHAR_COM_LIST flag is present, then the value of second_indent +/// will be the comment leader length sent to open_line(). +/// +/// @param c character to be inserted (can be NUL) +void internal_format(int textwidth, int second_indent, int flags, bool format_only, int c) +{ + int cc; + int save_char = NUL; + bool haveto_redraw = false; + const bool fo_ins_blank = has_format_option(FO_INS_BLANK); + const bool fo_multibyte = has_format_option(FO_MBYTE_BREAK); + const bool fo_rigor_tw = has_format_option(FO_RIGOROUS_TW); + const bool fo_white_par = has_format_option(FO_WHITE_PAR); + bool first_line = true; + colnr_T leader_len; + bool no_leader = false; + int do_comments = (flags & INSCHAR_DO_COM); + int has_lbr = curwin->w_p_lbr; + + // make sure win_lbr_chartabsize() counts correctly + curwin->w_p_lbr = false; + + // When 'ai' is off we don't want a space under the cursor to be + // deleted. Replace it with an 'x' temporarily. + if (!curbuf->b_p_ai + && !(State & VREPLACE_FLAG)) { + cc = gchar_cursor(); + if (ascii_iswhite(cc)) { + save_char = cc; + pchar_cursor('x'); + } + } + + // Repeat breaking lines, until the current line is not too long. + while (!got_int) { + int startcol; // Cursor column at entry + int wantcol; // column at textwidth border + int foundcol; // column for start of spaces + int end_foundcol = 0; // column for start of word + colnr_T len; + colnr_T virtcol; + int orig_col = 0; + char_u *saved_text = NULL; + colnr_T col; + colnr_T end_col; + bool did_do_comment = false; + + virtcol = get_nolist_virtcol() + + char2cells(c != NUL ? c : gchar_cursor()); + if (virtcol <= (colnr_T)textwidth) { + break; + } + + if (no_leader) { + do_comments = false; + } else if (!(flags & INSCHAR_FORMAT) + && has_format_option(FO_WRAP_COMS)) { + do_comments = true; + } + + // Don't break until after the comment leader + if (do_comments) { + char_u *line = get_cursor_line_ptr(); + leader_len = get_leader_len((char *)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); + if (comment_start != MAXCOL) { + leader_len = get_leader_len((char *)line + comment_start, NULL, false, true); + if (leader_len != 0) { + leader_len += comment_start; + } + } + } + } else { + leader_len = 0; + } + + // If the line doesn't start with a comment leader, then don't + // start one in a following broken line. Avoids that a %word + // moved to the start of the next line causes all following lines + // to start with %. + if (leader_len == 0) { + no_leader = true; + } + if (!(flags & INSCHAR_FORMAT) + && leader_len == 0 + && !has_format_option(FO_WRAP)) { + break; + } + if ((startcol = curwin->w_cursor.col) == 0) { + break; + } + + // find column of textwidth border + coladvance((colnr_T)textwidth); + wantcol = curwin->w_cursor.col; + + curwin->w_cursor.col = startcol; + foundcol = 0; + int skip_pos = 0; + + // Find position to break at. + // Stop at first entered white when 'formatoptions' has 'v' + while ((!fo_ins_blank && !has_format_option(FO_INS_VI)) + || (flags & INSCHAR_FORMAT) + || curwin->w_cursor.lnum != Insstart.lnum + || curwin->w_cursor.col >= Insstart.col) { + if (curwin->w_cursor.col == startcol && c != NUL) { + cc = c; + } else { + cc = gchar_cursor(); + } + if (WHITECHAR(cc)) { + // remember position of blank just before text + end_col = curwin->w_cursor.col; + + // find start of sequence of blanks + int wcc = 0; // counter for whitespace chars + while (curwin->w_cursor.col > 0 && WHITECHAR(cc)) { + dec_cursor(); + cc = gchar_cursor(); + + // Increment count of how many whitespace chars in this + // group; we only need to know if it's more than one. + if (wcc < 2) { + wcc++; + } + } + if (curwin->w_cursor.col == 0 && WHITECHAR(cc)) { + break; // only spaces in front of text + } + + // Don't break after a period when 'formatoptions' has 'p' and + // there are less than two spaces. + if (has_format_option(FO_PERIOD_ABBR) && cc == '.' && wcc < 2) { + continue; + } + + // Don't break until after the comment leader + if (curwin->w_cursor.col < leader_len) { + break; + } + + if (has_format_option(FO_ONE_LETTER)) { + // do not break after one-letter words + if (curwin->w_cursor.col == 0) { + break; // one-letter word at begin + } + // do not break "#a b" when 'tw' is 2 + if (curwin->w_cursor.col <= leader_len) { + break; + } + col = curwin->w_cursor.col; + dec_cursor(); + cc = gchar_cursor(); + + if (WHITECHAR(cc)) { + continue; // one-letter, continue + } + curwin->w_cursor.col = col; + } + + inc_cursor(); + + end_foundcol = end_col + 1; + foundcol = curwin->w_cursor.col; + if (curwin->w_cursor.col <= (colnr_T)wantcol) { + break; + } + } else if ((cc >= 0x100 || !utf_allow_break_before(cc)) && fo_multibyte) { + int ncc; + bool allow_break; + + // Break after or before a multi-byte character. + if (curwin->w_cursor.col != startcol) { + // Don't break until after the comment leader + if (curwin->w_cursor.col < leader_len) { + break; + } + col = curwin->w_cursor.col; + inc_cursor(); + ncc = gchar_cursor(); + allow_break = utf_allow_break(cc, ncc); + + // If we have already checked this position, skip! + if (curwin->w_cursor.col != skip_pos && allow_break) { + foundcol = curwin->w_cursor.col; + end_foundcol = foundcol; + if (curwin->w_cursor.col <= (colnr_T)wantcol) { + break; + } + } + curwin->w_cursor.col = col; + } + + if (curwin->w_cursor.col == 0) { + break; + } + + ncc = cc; + col = curwin->w_cursor.col; + + dec_cursor(); + cc = gchar_cursor(); + + if (WHITECHAR(cc)) { + continue; // break with space + } + // Don't break until after the comment leader. + if (curwin->w_cursor.col < leader_len) { + break; + } + + curwin->w_cursor.col = col; + skip_pos = curwin->w_cursor.col; + + allow_break = utf_allow_break(cc, ncc); + + // Must handle this to respect line break prohibition. + if (allow_break) { + foundcol = curwin->w_cursor.col; + end_foundcol = foundcol; + } + if (curwin->w_cursor.col <= (colnr_T)wantcol) { + const bool ncc_allow_break = utf_allow_break_before(ncc); + + if (allow_break) { + break; + } + if (!ncc_allow_break && !fo_rigor_tw) { + // Enable at most 1 punct hang outside of textwidth. + if (curwin->w_cursor.col == startcol) { + // We are inserting a non-breakable char, postpone + // line break check to next insert. + end_foundcol = foundcol = 0; + break; + } + + // Neither cc nor ncc is NUL if we are here, so + // it's safe to inc_cursor. + col = curwin->w_cursor.col; + + inc_cursor(); + cc = ncc; + ncc = gchar_cursor(); + // handle insert + ncc = (ncc != NUL) ? ncc : c; + + allow_break = utf_allow_break(cc, ncc); + + if (allow_break) { + // Break only when we are not at end of line. + end_foundcol = foundcol = ncc == NUL? 0 : curwin->w_cursor.col; + break; + } + curwin->w_cursor.col = col; + } + } + } + if (curwin->w_cursor.col == 0) { + break; + } + dec_cursor(); + } + + if (foundcol == 0) { // no spaces, cannot break line + curwin->w_cursor.col = startcol; + break; + } + + // Going to break the line, remove any "$" now. + undisplay_dollar(); + + // Offset between cursor position and line break is used by replace + // stack functions. MODE_VREPLACE does not use this, and backspaces + // over the text instead. + if (State & VREPLACE_FLAG) { + orig_col = startcol; // Will start backspacing from here + } else { + replace_offset = startcol - end_foundcol; + } + + // adjust startcol for spaces that will be deleted and + // characters that will remain on top line + curwin->w_cursor.col = foundcol; + while ((cc = gchar_cursor(), WHITECHAR(cc)) + && (!fo_white_par || curwin->w_cursor.col < startcol)) { + inc_cursor(); + } + startcol -= curwin->w_cursor.col; + if (startcol < 0) { + startcol = 0; + } + + if (State & VREPLACE_FLAG) { + // In MODE_VREPLACE state, we will backspace over the text to be + // wrapped, so save a copy now to put on the next line. + saved_text = vim_strsave(get_cursor_pos_ptr()); + curwin->w_cursor.col = orig_col; + saved_text[startcol] = NUL; + + // Backspace over characters that will move to the next line + if (!fo_white_par) { + backspace_until_column(foundcol); + } + } else { + // put cursor after pos. to break line + if (!fo_white_par) { + curwin->w_cursor.col = foundcol; + } + } + + // Split the line just before the margin. + // Only insert/delete lines, but don't really redraw the window. + open_line(FORWARD, OPENLINE_DELSPACES + OPENLINE_MARKFIX + + (fo_white_par ? OPENLINE_KEEPTRAIL : 0) + + (do_comments ? OPENLINE_DO_COM : 0) + + OPENLINE_FORMAT + + ((flags & INSCHAR_COM_LIST) ? OPENLINE_COM_LIST : 0), + ((flags & INSCHAR_COM_LIST) ? second_indent : old_indent), + &did_do_comment); + if (!(flags & INSCHAR_COM_LIST)) { + old_indent = 0; + } + + // If a comment leader was inserted, may also do this on a following + // line. + if (did_do_comment) { + no_leader = false; + } + + replace_offset = 0; + if (first_line) { + if (!(flags & INSCHAR_COM_LIST)) { + // This section is for auto-wrap of numeric lists. When not + // in insert mode (i.e. format_lines()), the INSCHAR_COM_LIST + // flag will be set and open_line() will handle it (as seen + // above). The code here (and in get_number_indent()) will + // recognize comments if needed... + if (second_indent < 0 && has_format_option(FO_Q_NUMBER)) { + second_indent = get_number_indent(curwin->w_cursor.lnum - 1); + } + if (second_indent >= 0) { + if (State & VREPLACE_FLAG) { + change_indent(INDENT_SET, second_indent, false, NUL, true); + } else if (leader_len > 0 && second_indent - leader_len > 0) { + int padding = second_indent - leader_len; + + // We started at the first_line of a numbered list + // that has a comment. the open_line() function has + // inserted the proper comment leader and positioned + // the cursor at the end of the split line. Now we + // add the additional whitespace needed after the + // comment leader for the numbered list. + for (int i = 0; i < padding; i++) { + ins_str(" "); + } + changed_bytes(curwin->w_cursor.lnum, leader_len); + } else { + (void)set_indent(second_indent, SIN_CHANGED); + } + } + } + first_line = false; + } + + if (State & VREPLACE_FLAG) { + // In MODE_VREPLACE state we have backspaced over the text to be + // moved, now we re-insert it into the new line. + ins_bytes((char *)saved_text); + xfree(saved_text); + } else { + // Check if cursor is not past the NUL off the line, cindent + // may have added or removed indent. + curwin->w_cursor.col += startcol; + len = (colnr_T)STRLEN(get_cursor_line_ptr()); + if (curwin->w_cursor.col > len) { + curwin->w_cursor.col = len; + } + } + + haveto_redraw = true; + set_can_cindent(true); + // moved the cursor, don't autoindent or cindent now + did_ai = false; + did_si = false; + can_si = false; + can_si_back = false; + line_breakcheck(); + } + + if (save_char != NUL) { // put back space after cursor + pchar_cursor((char_u)save_char); + } + + curwin->w_p_lbr = has_lbr; + + if (!format_only && haveto_redraw) { + update_topline(curwin); + redraw_curbuf_later(UPD_VALID); + } +} + +/// Blank lines, and lines containing only the comment leader, are left +/// untouched by the formatting. The function returns true in this +/// case. It also returns true when a line starts with the end of a comment +/// ('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) +{ + char_u *flags = NULL; // init for GCC + char_u *ptr; + + ptr = ml_get(lnum); + if (do_comments) { + *leader_len = get_leader_len((char *)ptr, (char **)leader_flags, false, true); + } else { + *leader_len = 0; + } + + if (*leader_len > 0) { + // Search for 'e' flag in comment leader flags. + flags = *leader_flags; + while (*flags && *flags != ':' && *flags != COM_END) { + flags++; + } + } + + return *skipwhite((char *)ptr + *leader_len) == NUL + || (*leader_len > 0 && *flags == COM_END) + || startPS(lnum, NUL, false); +} + +/// @return true if line "lnum" ends in a white character. +static bool ends_in_white(linenr_T lnum) +{ + char_u *s = ml_get(lnum); + size_t l; + + if (*s == NUL) { + return false; + } + l = STRLEN(s) - 1; + return ascii_iswhite(s[l]); +} + +/// @return true if the two comment leaders given are the same. +/// +/// @param lnum The first line. White-space is ignored. +/// +/// @note the whole of 'leader1' must match 'leader2_len' characters from 'leader2'. +static bool same_leader(linenr_T lnum, int leader1_len, char_u *leader1_flags, int leader2_len, + char_u *leader2_flags) +{ + int idx1 = 0, idx2 = 0; + char_u *p; + char_u *line1; + char_u *line2; + + if (leader1_len == 0) { + return leader2_len == 0; + } + + // If first leader has 'f' flag, the lines can be joined only if the + // second line does not have a leader. + // If first leader has 'e' flag, the lines can never be joined. + // If first leader has 's' flag, the lines can only be joined if there is + // some text after it and the second line has the 'm' flag. + if (leader1_flags != NULL) { + for (p = leader1_flags; *p && *p != ':'; p++) { + if (*p == COM_FIRST) { + return leader2_len == 0; + } + if (*p == COM_END) { + return false; + } + if (*p == COM_START) { + if (*(ml_get(lnum) + leader1_len) == NUL) { + return false; + } + if (leader2_flags == NULL || leader2_len == 0) { + return false; + } + for (p = leader2_flags; *p && *p != ':'; p++) { + if (*p == COM_MIDDLE) { + return true; + } + } + return false; + } + } + } + + // Get current line and next line, compare the leaders. + // The first line has to be saved, only one line can be locked at a time. + line1 = vim_strsave(ml_get(lnum)); + for (idx1 = 0; ascii_iswhite(line1[idx1]); idx1++) {} + line2 = ml_get(lnum + 1); + for (idx2 = 0; idx2 < leader2_len; idx2++) { + if (!ascii_iswhite(line2[idx2])) { + if (line1[idx1++] != line2[idx2]) { + break; + } + } else { + while (ascii_iswhite(line1[idx1])) { + idx1++; + } + } + } + xfree(line1); + + return idx2 == leader2_len && idx1 == leader1_len; +} + +/// Used for auto-formatting. +/// +/// @return true when a paragraph starts in line "lnum". +/// false when the previous line is in the same paragraph. +static bool paragraph_start(linenr_T lnum) +{ + char_u *p; + int leader_len = 0; // leader len of current line + char_u *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 + + if (lnum <= 1) { + return true; // start of the file + } + p = ml_get(lnum - 1); + if (*p == NUL) { + return true; // after empty line + } + const bool do_comments = has_format_option(FO_Q_COMS); // format comments + if (fmt_check_par(lnum - 1, &leader_len, &leader_flags, do_comments)) { + return true; // after non-paragraph line + } + + if (fmt_check_par(lnum, &next_leader_len, &next_leader_flags, do_comments)) { + return true; // "lnum" is not a paragraph line + } + + if (has_format_option(FO_WHITE_PAR) && !ends_in_white(lnum - 1)) { + return true; // missing trailing space in previous line. + } + 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, leader_flags, + next_leader_len, next_leader_flags)) { + return true; // change of comment leader. + } + return false; +} + +/// Called after inserting or deleting text: When 'formatoptions' includes the +/// 'a' flag format from the current line until the end of the paragraph. +/// Keep the cursor at the same position relative to the text. +/// The caller must have saved the cursor line for undo, following ones will be +/// saved here. +/// +/// @param trailblank when true also format with trailing blank +/// @param prev_line may start in previous line +void auto_format(bool trailblank, bool prev_line) +{ + pos_T pos; + colnr_T len; + char_u *old; + char_u *new, *pnew; + int wasatend; + int cc; + + if (!has_format_option(FO_AUTO)) { + return; + } + + pos = curwin->w_cursor; + old = get_cursor_line_ptr(); + + // may remove added space + check_auto_format(false); + + // Don't format in Insert mode when the cursor is on a trailing blank, the + // user might insert normal text next. Also skip formatting when "1" is + // in 'formatoptions' and there is a single character before the cursor. + // Otherwise the line would be broken and when typing another non-white + // next they are not joined back together. + wasatend = (pos.col == (colnr_T)STRLEN(old)); + if (*old != NUL && !trailblank && wasatend) { + dec_cursor(); + cc = gchar_cursor(); + if (!WHITECHAR(cc) && curwin->w_cursor.col > 0 + && has_format_option(FO_ONE_LETTER)) { + dec_cursor(); + } + cc = gchar_cursor(); + if (WHITECHAR(cc)) { + curwin->w_cursor = pos; + return; + } + curwin->w_cursor = pos; + } + + // With the 'c' flag in 'formatoptions' and 't' missing: only format + // comments. + if (has_format_option(FO_WRAP_COMS) && !has_format_option(FO_WRAP) + && get_leader_len((char *)old, NULL, false, true) == 0) { + return; + } + + // May start formatting in a previous line, so that after "x" a word is + // moved to the previous line if it fits there now. Only when this is not + // the start of a paragraph. + if (prev_line && !paragraph_start(curwin->w_cursor.lnum)) { + curwin->w_cursor.lnum--; + if (u_save_cursor() == FAIL) { + return; + } + } + + // Do the formatting and restore the cursor position. "saved_cursor" will + // be adjusted for the text formatting. + saved_cursor = pos; + format_lines((linenr_T) - 1, false); + curwin->w_cursor = saved_cursor; + saved_cursor.lnum = 0; + + if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) { + // "cannot happen" + curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; + coladvance(MAXCOL); + } else { + check_cursor_col(); + } + + // Insert mode: If the cursor is now after the end of the line while it + // previously wasn't, the line was broken. Because of the rule above we + // need to add a space when 'w' is in 'formatoptions' to keep a paragraph + // formatted. + if (!wasatend && has_format_option(FO_WHITE_PAR)) { + new = get_cursor_line_ptr(); + len = (colnr_T)STRLEN(new); + if (curwin->w_cursor.col == len) { + pnew = vim_strnsave(new, (size_t)len + 2); + pnew[len] = ' '; + pnew[len + 1] = NUL; + ml_replace(curwin->w_cursor.lnum, (char *)pnew, false); + // remove the space later + did_add_space = true; + } else { + // may remove added space + check_auto_format(false); + } + } + + check_cursor(); +} + +/// When an extra space was added to continue a paragraph for auto-formatting, +/// delete it now. The space must be under the cursor, just after the insert +/// position. +/// +/// @param end_insert true when ending Insert mode +void check_auto_format(bool end_insert) +{ + int c = ' '; + int cc; + + if (did_add_space) { + cc = gchar_cursor(); + if (!WHITECHAR(cc)) { + // Somehow the space was removed already. + did_add_space = false; + } else { + if (!end_insert) { + inc_cursor(); + c = gchar_cursor(); + dec_cursor(); + } + if (c != NUL) { + // The space is no longer at the end of the line, delete it. + del_char(false); + did_add_space = false; + } + } + } +} + +/// Find out textwidth to be used for formatting: +/// if 'textwidth' option is set, use it +/// else if 'wrapmargin' option is set, use curwin->w_width_inner-'wrapmargin' +/// if invalid value, use 0. +/// Set default to window width (maximum 79) for "gq" operator. +/// +/// @param ff force formatting (for "gq" command) +int comp_textwidth(bool ff) +{ + int textwidth = (int)curbuf->b_p_tw; + if (textwidth == 0 && curbuf->b_p_wm) { + // The width is the window width minus 'wrapmargin' minus all the + // things that add to the margin. + textwidth = curwin->w_width_inner - (int)curbuf->b_p_wm; + if (cmdwin_type != 0) { + textwidth -= 1; + } + textwidth -= win_fdccol_count(curwin); + textwidth -= win_signcol_count(curwin); + + if (curwin->w_p_nu || curwin->w_p_rnu) { + textwidth -= 8; + } + } + if (textwidth < 0) { + textwidth = 0; + } + if (ff && textwidth == 0) { + textwidth = curwin->w_width_inner - 1; + if (textwidth > 79) { + textwidth = 79; + } + } + return textwidth; +} + +/// Implementation of the format operator 'gq'. +/// +/// @param keep_cursor keep cursor on same text char +void op_format(oparg_T *oap, bool keep_cursor) +{ + linenr_T old_line_count = curbuf->b_ml.ml_line_count; + + // Place the cursor where the "gq" or "gw" command was given, so that "u" + // can put it back there. + curwin->w_cursor = oap->cursor_start; + + if (u_save((linenr_T)(oap->start.lnum - 1), + (linenr_T)(oap->end.lnum + 1)) == FAIL) { + return; + } + curwin->w_cursor = oap->start; + + if (oap->is_VIsual) { + // When there is no change: need to remove the Visual selection + redraw_curbuf_later(UPD_INVERTED); + } + + if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0) { + // Set '[ mark at the start of the formatted area + curbuf->b_op_start = oap->start; + } + + // For "gw" remember the cursor position and put it back below (adjusted + // for joined and split lines). + if (keep_cursor) { + saved_cursor = oap->cursor_start; + } + + format_lines((linenr_T)oap->line_count, keep_cursor); + + // Leave the cursor at the first non-blank of the last formatted line. + // If the cursor was moved one line back (e.g. with "Q}") go to the next + // line, so "." will do the next lines. + if (oap->end_adjusted && curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) { + curwin->w_cursor.lnum++; + } + beginline(BL_WHITE | BL_FIX); + old_line_count = curbuf->b_ml.ml_line_count - old_line_count; + msgmore(old_line_count); + + if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0) { + // put '] mark on the end of the formatted area + curbuf->b_op_end = curwin->w_cursor; + } + + if (keep_cursor) { + curwin->w_cursor = saved_cursor; + saved_cursor.lnum = 0; + + // formatting may have made the cursor position invalid + check_cursor(); + } + + if (oap->is_VIsual) { + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_old_cursor_lnum != 0) { + // When lines have been inserted or deleted, adjust the end of + // the Visual area to be redrawn. + if (wp->w_old_cursor_lnum > wp->w_old_visual_lnum) { + wp->w_old_cursor_lnum += old_line_count; + } else { + wp->w_old_visual_lnum += old_line_count; + } + } + } + } +} + +/// Implementation of the format operator 'gq' for when using 'formatexpr'. +void op_formatexpr(oparg_T *oap) +{ + if (oap->is_VIsual) { + // When there is no change: need to remove the Visual selection + redraw_curbuf_later(UPD_INVERTED); + } + + if (fex_format(oap->start.lnum, oap->line_count, NUL) != 0) { + // As documented: when 'formatexpr' returns non-zero fall back to + // internal formatting. + op_format(oap, false); + } +} + +/// @param c character to be inserted +int fex_format(linenr_T lnum, long count, int c) +{ + int use_sandbox = was_set_insecurely(curwin, "formatexpr", OPT_LOCAL); + int r; + + // Set v:lnum to the first line number and v:count to the number of lines. + // Set v:char to the character to be inserted (can be NUL). + set_vim_var_nr(VV_LNUM, (varnumber_T)lnum); + set_vim_var_nr(VV_COUNT, (varnumber_T)count); + set_vim_var_char(c); + + // Make a copy, the option could be changed while calling it. + char *fex = xstrdup(curbuf->b_p_fex); + // Evaluate the function. + if (use_sandbox) { + sandbox++; + } + r = (int)eval_to_number(fex); + if (use_sandbox) { + sandbox--; + } + + set_vim_var_string(VV_CHAR, NULL, -1); + xfree(fex); + + return r; +} + +/// @param line_count number of lines to format, starting at the cursor position. +/// when negative, format until the end of the paragraph. +/// +/// Lines after the cursor line are saved for undo, caller must have saved the +/// first line. +/// +/// @param avoid_fex don't use 'formatexpr' +void format_lines(linenr_T line_count, bool avoid_fex) +{ + bool is_not_par; // current line not part of parag. + bool next_is_not_par; // next line not part of paragraph + bool is_end_par; // at end of paragraph + bool prev_is_end_par = false; // prev. line not part of parag. + 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 + bool advance = true; + int second_indent = -1; // indent for second line (comment aware) + bool first_par_line = true; + int smd_save; + long count; + bool need_set_indent = true; // set indent of next paragraph + linenr_T first_line = curwin->w_cursor.lnum; + bool force_format = false; + const int old_State = State; + + // length of a line to force formatting: 3 * 'tw' + const int max_len = comp_textwidth(true) * 3; + + // check for 'q', '2' and '1' in 'formatoptions' + const bool do_comments = has_format_option(FO_Q_COMS); // format comments + int do_comments_list = 0; // format comments with 'n' or '2' + const bool do_second_indent = has_format_option(FO_Q_SECOND); + const bool do_number_indent = has_format_option(FO_Q_NUMBER); + const bool do_trail_white = has_format_option(FO_WHITE_PAR); + + // Get info about the previous and current line. + if (curwin->w_cursor.lnum > 1) { + is_not_par = fmt_check_par(curwin->w_cursor.lnum - 1, + &leader_len, &leader_flags, do_comments); + } else { + is_not_par = true; + } + next_is_not_par = fmt_check_par(curwin->w_cursor.lnum, + &next_leader_len, &next_leader_flags, do_comments); + is_end_par = (is_not_par || next_is_not_par); + if (!is_end_par && do_trail_white) { + is_end_par = !ends_in_white(curwin->w_cursor.lnum - 1); + } + + curwin->w_cursor.lnum--; + for (count = line_count; count != 0 && !got_int; count--) { + // Advance to next paragraph. + if (advance) { + curwin->w_cursor.lnum++; + prev_is_end_par = is_end_par; + is_not_par = next_is_not_par; + leader_len = next_leader_len; + leader_flags = next_leader_flags; + } + + // The last line to be formatted. + if (count == 1 || curwin->w_cursor.lnum == curbuf->b_ml.ml_line_count) { + next_is_not_par = true; + next_leader_len = 0; + next_leader_flags = NULL; + } else { + next_is_not_par = fmt_check_par(curwin->w_cursor.lnum + 1, + &next_leader_len, &next_leader_flags, do_comments); + if (do_number_indent) { + next_is_start_par = + (get_number_indent(curwin->w_cursor.lnum + 1) > 0); + } + } + advance = true; + is_end_par = (is_not_par || next_is_not_par || next_is_start_par); + if (!is_end_par && do_trail_white) { + is_end_par = !ends_in_white(curwin->w_cursor.lnum); + } + + // Skip lines that are not in a paragraph. + if (is_not_par) { + if (line_count < 0) { + break; + } + } else { + // For the first line of a paragraph, check indent of second line. + // Don't do this for comments and empty lines. + if (first_par_line + && (do_second_indent || do_number_indent) + && prev_is_end_par + && curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) { + if (do_second_indent && !LINEEMPTY(curwin->w_cursor.lnum + 1)) { + if (leader_len == 0 && next_leader_len == 0) { + // no comment found + second_indent = + get_indent_lnum(curwin->w_cursor.lnum + 1); + } else { + second_indent = next_leader_len; + do_comments_list = 1; + } + } else if (do_number_indent) { + if (leader_len == 0 && next_leader_len == 0) { + // no comment found + second_indent = + get_number_indent(curwin->w_cursor.lnum); + } else { + // get_number_indent() is now "comment aware"... + second_indent = + get_number_indent(curwin->w_cursor.lnum); + do_comments_list = 1; + } + } + } + + // 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, leader_flags, + next_leader_len, + 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 + || check_linecomment((char *)get_cursor_line_ptr()) == MAXCOL) { + is_end_par = true; + } + } + + // If we have got to the end of a paragraph, or the line is + // getting long, format it. + if (is_end_par || force_format) { + if (need_set_indent) { + int indent = 0; // amount of indent needed + + // Replace indent in first line of a paragraph with minimal + // number of tabs and spaces, according to current options. + // For the very first formatted line keep the current + // indent. + if (curwin->w_cursor.lnum == first_line) { + indent = get_indent(); + } else if (curbuf->b_p_lisp) { + indent = get_lisp_indent(); + } else { + if (cindent_on()) { + indent = *curbuf->b_p_inde != NUL ? get_expr_indent() : get_c_indent(); + } else { + indent = get_indent(); + } + } + (void)set_indent(indent, SIN_CHANGED); + } + + // put cursor on last non-space + State = MODE_NORMAL; // don't go past end-of-line + coladvance(MAXCOL); + while (curwin->w_cursor.col && ascii_isspace(gchar_cursor())) { + dec_cursor(); + } + + // do the formatting, without 'showmode' + State = MODE_INSERT; // for open_line() + smd_save = p_smd; + p_smd = false; + insertchar(NUL, INSCHAR_FORMAT + + (do_comments ? INSCHAR_DO_COM : 0) + + (do_comments && do_comments_list ? INSCHAR_COM_LIST : 0) + + (avoid_fex ? INSCHAR_NO_FEX : 0), second_indent); + State = old_State; + p_smd = smd_save; + second_indent = -1; + // at end of par.: need to set indent of next par. + need_set_indent = is_end_par; + if (is_end_par) { + // When called with a negative line count, break at the + // end of the paragraph. + if (line_count < 0) { + break; + } + first_par_line = true; + } + force_format = false; + } + + // When still in same paragraph, join the lines together. But + // first delete the leader from the second line. + if (!is_end_par) { + advance = false; + curwin->w_cursor.lnum++; + curwin->w_cursor.col = 0; + if (line_count < 0 && u_save_cursor() == FAIL) { + break; + } + if (next_leader_len > 0) { + (void)del_bytes(next_leader_len, false, false); + mark_col_adjust(curwin->w_cursor.lnum, (colnr_T)0, 0L, + (long)-next_leader_len, 0); + } else if (second_indent > 0) { // the "leader" for FO_Q_SECOND + int indent = (int)getwhitecols_curline(); + + if (indent > 0) { + (void)del_bytes(indent, false, false); + mark_col_adjust(curwin->w_cursor.lnum, + (colnr_T)0, 0L, (long)-indent, 0); + } + } + curwin->w_cursor.lnum--; + if (do_join(2, true, false, false, false) == FAIL) { + beep_flush(); + break; + } + first_par_line = false; + // If the line is getting long, format it next time + if (STRLEN(get_cursor_line_ptr()) > (size_t)max_len) { + force_format = true; + } else { + force_format = false; + } + } + } + line_breakcheck(); + } +} diff --git a/src/nvim/textformat.h b/src/nvim/textformat.h new file mode 100644 index 0000000000..3c918a028b --- /dev/null +++ b/src/nvim/textformat.h @@ -0,0 +1,10 @@ +#ifndef NVIM_TEXTFORMAT_H +#define NVIM_TEXTFORMAT_H + +#include "nvim/normal.h" // for oparg_T +#include "nvim/pos.h" // for linenr_T + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "textformat.h.generated.h" +#endif +#endif // NVIM_TEXTFORMAT_H diff --git a/src/nvim/textobject.c b/src/nvim/textobject.c new file mode 100644 index 0000000000..02174edeb1 --- /dev/null +++ b/src/nvim/textobject.c @@ -0,0 +1,1742 @@ +// 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 + +// textobject.c: functions for text objects + +#include <stdbool.h> + +#include "nvim/ascii.h" +#include "nvim/cursor.h" +#include "nvim/drawscreen.h" +#include "nvim/edit.h" +#include "nvim/eval/funcs.h" +#include "nvim/fold.h" +#include "nvim/globals.h" +#include "nvim/indent.h" +#include "nvim/mark.h" +#include "nvim/mbyte.h" +#include "nvim/memline.h" +#include "nvim/normal.h" +#include "nvim/pos.h" +#include "nvim/search.h" +#include "nvim/textformat.h" +#include "nvim/textobject.h" +#include "nvim/vim.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "textobject.c.generated.h" +#endif + +/// Find the start of the next sentence, searching in the direction specified +/// by the "dir" argument. The cursor is positioned on the start of the next +/// sentence when found. If the next sentence is found, return OK. Return FAIL +/// otherwise. See ":h sentence" for the precise definition of a "sentence" +/// text object. +int findsent(Direction dir, long count) +{ + pos_T pos, tpos; + int c; + int (*func)(pos_T *); + bool noskip = false; // do not skip blanks + + pos = curwin->w_cursor; + if (dir == FORWARD) { + func = incl; + } else { + func = decl; + } + + while (count--) { + const pos_T prev_pos = pos; + + // if on an empty line, skip up to a non-empty line + if (gchar_pos(&pos) == NUL) { + do { + if ((*func)(&pos) == -1) { + break; + } + } while (gchar_pos(&pos) == NUL); + if (dir == FORWARD) { + goto found; + } + // if on the start of a paragraph or a section and searching forward, + // go to the next line + } else if (dir == FORWARD && pos.col == 0 + && startPS(pos.lnum, NUL, false)) { + if (pos.lnum == curbuf->b_ml.ml_line_count) { + return FAIL; + } + pos.lnum++; + goto found; + } else if (dir == BACKWARD) { + decl(&pos); + } + + // go back to the previous non-white non-punctuation character + bool found_dot = false; + while (c = gchar_pos(&pos), ascii_iswhite(c) + || vim_strchr(".!?)]\"'", c) != NULL) { + tpos = pos; + if (decl(&tpos) == -1 || (LINEEMPTY(tpos.lnum) && dir == FORWARD)) { + break; + } + if (found_dot) { + break; + } + if (vim_strchr(".!?", c) != NULL) { + found_dot = true; + } + if (vim_strchr(")]\"'", c) != NULL + && vim_strchr(".!?)]\"'", gchar_pos(&tpos)) == NULL) { + break; + } + decl(&pos); + } + + // remember the line where the search started + const int startlnum = pos.lnum; + const bool cpo_J = vim_strchr(p_cpo, CPO_ENDOFSENT) != NULL; + + for (;;) { // find end of sentence + c = gchar_pos(&pos); + if (c == NUL || (pos.col == 0 && startPS(pos.lnum, NUL, false))) { + if (dir == BACKWARD && pos.lnum != startlnum) { + pos.lnum++; + } + break; + } + if (c == '.' || c == '!' || c == '?') { + tpos = pos; + do { + if ((c = inc(&tpos)) == -1) { + break; + } + } while (vim_strchr(")]\"'", c = gchar_pos(&tpos)) + != NULL); + if (c == -1 || (!cpo_J && (c == ' ' || c == '\t')) || c == NUL + || (cpo_J && (c == ' ' && inc(&tpos) >= 0 + && gchar_pos(&tpos) == ' '))) { + pos = tpos; + if (gchar_pos(&pos) == NUL) { // skip NUL at EOL + inc(&pos); + } + break; + } + } + if ((*func)(&pos) == -1) { + if (count) { + return FAIL; + } + noskip = true; + break; + } + } +found: + // skip white space + while (!noskip && ((c = gchar_pos(&pos)) == ' ' || c == '\t')) { + if (incl(&pos) == -1) { + break; + } + } + + if (equalpos(prev_pos, pos)) { + // didn't actually move, advance one character and try again + if ((*func)(&pos) == -1) { + if (count) { + return FAIL; + } + break; + } + count++; + } + } + + setpcmark(); + curwin->w_cursor = pos; + return OK; +} + +/// Find the next paragraph or section in direction 'dir'. +/// Paragraphs are currently supposed to be separated by empty lines. +/// If 'what' is NUL we go to the next paragraph. +/// If 'what' is '{' or '}' we go to the next section. +/// If 'both' is true also stop at '}'. +/// +/// @param pincl Return: true if last char is to be included +/// +/// @return true if the next paragraph or section was found. +bool findpar(bool *pincl, int dir, long count, int what, bool both) +{ + linenr_T curr; + bool did_skip; // true after separating lines have been skipped + bool first; // true on first line + linenr_T fold_first; // first line of a closed fold + linenr_T fold_last; // last line of a closed fold + bool fold_skipped; // true if a closed fold was skipped this + // iteration + + curr = curwin->w_cursor.lnum; + + while (count--) { + did_skip = false; + for (first = true;; first = false) { + if (*ml_get(curr) != NUL) { + did_skip = true; + } + + // skip folded lines + fold_skipped = false; + if (first && hasFolding(curr, &fold_first, &fold_last)) { + curr = ((dir > 0) ? fold_last : fold_first) + dir; + fold_skipped = true; + } + + if (!first && did_skip && startPS(curr, what, both)) { + break; + } + + if (fold_skipped) { + curr -= dir; + } + if ((curr += dir) < 1 || curr > curbuf->b_ml.ml_line_count) { + if (count) { + return false; + } + curr -= dir; + break; + } + } + } + setpcmark(); + if (both && *ml_get(curr) == '}') { // include line with '}' + curr++; + } + curwin->w_cursor.lnum = curr; + if (curr == curbuf->b_ml.ml_line_count && what != '}') { + char_u *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) { + curwin->w_cursor.col--; + curwin->w_cursor.col -= utf_head_off(line, line + curwin->w_cursor.col); + *pincl = true; + } + } else { + curwin->w_cursor.col = 0; + } + return true; +} + +/// check if the string 's' is a nroff macro that is in option 'opt' +static bool inmacro(char_u *opt, char_u *s) +{ + char_u *macro; + + for (macro = opt; macro[0]; macro++) { + // Accept two characters in the option being equal to two characters + // in the line. A space in the option matches with a space in the + // line or the line having ended. + if ((macro[0] == s[0] + || (macro[0] == ' ' + && (s[0] == NUL || s[0] == ' '))) + && (macro[1] == s[1] + || ((macro[1] == NUL || macro[1] == ' ') + && (s[0] == NUL || s[1] == NUL || s[1] == ' ')))) { + break; + } + macro++; + if (macro[0] == NUL) { + break; + } + } + return macro[0] != NUL; +} + +/// startPS: return true if line 'lnum' is the start of a section or paragraph. +/// If 'para' is '{' or '}' only check for sections. +/// If 'both' is true also stop at '}' +bool startPS(linenr_T lnum, int para, bool both) +{ + char_u *s; + + s = ml_get(lnum); + if (*s == para || *s == '\f' || (both && *s == '}')) { + return true; + } + if (*s == '.' && (inmacro((char_u *)p_sections, s + 1) + || (!para && inmacro(p_para, s + 1)))) { + return true; + } + return false; +} + +// The following routines do the word searches performed by the 'w', 'W', +// 'b', 'B', 'e', and 'E' commands. + +// To perform these searches, characters are placed into one of three +// classes, and transitions between classes determine word boundaries. +// +// The classes are: +// +// 0 - white space +// 1 - punctuation +// 2 or higher - keyword characters (letters, digits and underscore) + +static bool cls_bigword; ///< true for "W", "B" or "E" + +/// cls() - returns the class of character at curwin->w_cursor +/// +/// If a 'W', 'B', or 'E' motion is being done (cls_bigword == true), chars +/// from class 2 and higher are reported as class 1 since only white space +/// boundaries are of interest. +static int cls(void) +{ + int c; + + c = gchar_cursor(); + if (c == ' ' || c == '\t' || c == NUL) { + return 0; + } + + c = utf_class(c); + + // If cls_bigword is true, report all non-blanks as class 1. + if (c != 0 && cls_bigword) { + return 1; + } + return c; +} + +/// fwd_word(count, type, eol) - move forward one word +/// +/// @return FAIL if the cursor was already at the end of the file. +/// If eol is true, last word stops at end of line (for operators). +/// +/// @param bigword "W", "E" or "B" +int fwd_word(long count, bool bigword, bool eol) +{ + int sclass; // starting class + int i; + int last_line; + + curwin->w_cursor.coladd = 0; + cls_bigword = bigword; + while (--count >= 0) { + // When inside a range of folded lines, move to the last char of the + // last line. + if (hasFolding(curwin->w_cursor.lnum, NULL, &curwin->w_cursor.lnum)) { + coladvance(MAXCOL); + } + sclass = cls(); + + // We always move at least one character, unless on the last + // character in the buffer. + last_line = (curwin->w_cursor.lnum == curbuf->b_ml.ml_line_count); + i = inc_cursor(); + if (i == -1 || (i >= 1 && last_line)) { // started at last char in file + return FAIL; + } + if (i >= 1 && eol && count == 0) { // started at last char in line + return OK; + } + + // Go one char past end of current word (if any) + if (sclass != 0) { + while (cls() == sclass) { + i = inc_cursor(); + if (i == -1 || (i >= 1 && eol && count == 0)) { + return OK; + } + } + } + + // go to next non-white + while (cls() == 0) { + // We'll stop if we land on a blank line + if (curwin->w_cursor.col == 0 && *get_cursor_line_ptr() == NUL) { + break; + } + + i = inc_cursor(); + if (i == -1 || (i >= 1 && eol && count == 0)) { + return OK; + } + } + } + return OK; +} + +/// bck_word() - move backward 'count' words +/// +/// If stop is true and we are already on the start of a word, move one less. +/// +/// Returns FAIL if top of the file was reached. +int bck_word(long count, bool bigword, bool stop) +{ + int sclass; // starting class + + curwin->w_cursor.coladd = 0; + cls_bigword = bigword; + while (--count >= 0) { + // When inside a range of folded lines, move to the first char of the + // first line. + if (hasFolding(curwin->w_cursor.lnum, &curwin->w_cursor.lnum, NULL)) { + curwin->w_cursor.col = 0; + } + sclass = cls(); + if (dec_cursor() == -1) { // started at start of file + return FAIL; + } + + if (!stop || sclass == cls() || sclass == 0) { + // Skip white space before the word. + // Stop on an empty line. + while (cls() == 0) { + if (curwin->w_cursor.col == 0 + && LINEEMPTY(curwin->w_cursor.lnum)) { + goto finished; + } + if (dec_cursor() == -1) { // hit start of file, stop here + return OK; + } + } + + // Move backward to start of this word. + if (skip_chars(cls(), BACKWARD)) { + return OK; + } + } + + inc_cursor(); // overshot - forward one +finished: + stop = false; + } + return OK; +} + +/// end_word() - move to the end of the word +/// +/// There is an apparent bug in the 'e' motion of the real vi. At least on the +/// System V Release 3 version for the 80386. Unlike 'b' and 'w', the 'e' +/// motion crosses blank lines. When the real vi crosses a blank line in an +/// 'e' motion, the cursor is placed on the FIRST character of the next +/// non-blank line. The 'E' command, however, works correctly. Since this +/// appears to be a bug, I have not duplicated it here. +/// +/// Returns FAIL if end of the file was reached. +/// +/// If stop is true and we are already on the end of a word, move one less. +/// If empty is true stop on an empty line. +int end_word(long count, bool bigword, bool stop, bool empty) +{ + int sclass; // starting class + + curwin->w_cursor.coladd = 0; + cls_bigword = bigword; + while (--count >= 0) { + // When inside a range of folded lines, move to the last char of the + // last line. + if (hasFolding(curwin->w_cursor.lnum, NULL, &curwin->w_cursor.lnum)) { + coladvance(MAXCOL); + } + sclass = cls(); + if (inc_cursor() == -1) { + return FAIL; + } + + // If we're in the middle of a word, we just have to move to the end + // of it. + if (cls() == sclass && sclass != 0) { + // Move forward to end of the current word + if (skip_chars(sclass, FORWARD)) { + return FAIL; + } + } else if (!stop || sclass == 0) { + // We were at the end of a word. Go to the end of the next word. + // First skip white space, if 'empty' is true, stop at empty line. + while (cls() == 0) { + if (empty && curwin->w_cursor.col == 0 + && LINEEMPTY(curwin->w_cursor.lnum)) { + goto finished; + } + if (inc_cursor() == -1) { // hit end of file, stop here + return FAIL; + } + } + + // Move forward to the end of this word. + if (skip_chars(cls(), FORWARD)) { + return FAIL; + } + } + dec_cursor(); // overshot - one char backward +finished: + stop = false; // we move only one word less + } + return OK; +} + +/// Move back to the end of the word. +/// +/// @param bigword true for "B" +/// @param eol if true, then stop at end of line. +/// +/// @return FAIL if start of the file was reached. +int bckend_word(long count, bool bigword, bool eol) +{ + int sclass; // starting class + int i; + + curwin->w_cursor.coladd = 0; + cls_bigword = bigword; + while (--count >= 0) { + sclass = cls(); + if ((i = dec_cursor()) == -1) { + return FAIL; + } + if (eol && i == 1) { + return OK; + } + + // Move backward to before the start of this word. + if (sclass != 0) { + while (cls() == sclass) { + if ((i = dec_cursor()) == -1 || (eol && i == 1)) { + return OK; + } + } + } + + // Move backward to end of the previous word + while (cls() == 0) { + if (curwin->w_cursor.col == 0 && LINEEMPTY(curwin->w_cursor.lnum)) { + break; + } + if ((i = dec_cursor()) == -1 || (eol && i == 1)) { + return OK; + } + } + } + return OK; +} + +/// Skip a row of characters of the same class. +/// +/// @return true when end-of-file reached, false otherwise. +static bool skip_chars(int cclass, int dir) +{ + while (cls() == cclass) { + if ((dir == FORWARD ? inc_cursor() : dec_cursor()) == -1) { + return true; + } + } + return false; +} + +/// Go back to the start of the word or the start of white space +static void back_in_line(void) +{ + int sclass; // starting class + + sclass = cls(); + for (;;) { + if (curwin->w_cursor.col == 0) { // stop at start of line + break; + } + dec_cursor(); + if (cls() != sclass) { // stop at start of word + inc_cursor(); + break; + } + } +} + +static void find_first_blank(pos_T *posp) +{ + int c; + + while (decl(posp) != -1) { + c = gchar_pos(posp); + if (!ascii_iswhite(c)) { + incl(posp); + break; + } + } +} + +/// Skip count/2 sentences and count/2 separating white spaces. +/// +/// @param at_start_sent cursor is at start of sentence +static void findsent_forward(long count, bool at_start_sent) +{ + while (count--) { + findsent(FORWARD, 1L); + if (at_start_sent) { + find_first_blank(&curwin->w_cursor); + } + if (count == 0 || at_start_sent) { + decl(&curwin->w_cursor); + } + at_start_sent = !at_start_sent; + } +} + +/// Find word under cursor, cursor at end. +/// Used while an operator is pending, and in Visual mode. +/// +/// @param include true: include word and white space +/// @param bigword false == word, true == WORD +int current_word(oparg_T *oap, long count, bool include, bool bigword) +{ + pos_T start_pos; + pos_T pos; + bool inclusive = true; + int include_white = false; + + cls_bigword = bigword; + clearpos(&start_pos); + + // Correct cursor when 'selection' is exclusive + if (VIsual_active && *p_sel == 'e' && lt(VIsual, curwin->w_cursor)) { + dec_cursor(); + } + + // When Visual mode is not active, or when the VIsual area is only one + // character, select the word and/or white space under the cursor. + if (!VIsual_active || equalpos(curwin->w_cursor, VIsual)) { + // Go to start of current word or white space. + back_in_line(); + start_pos = curwin->w_cursor; + + // If the start is on white space, and white space should be included + // (" word"), or start is not on white space, and white space should + // not be included ("word"), find end of word. + if ((cls() == 0) == include) { + if (end_word(1L, bigword, true, true) == FAIL) { + return FAIL; + } + } else { + // If the start is not on white space, and white space should be + // included ("word "), or start is on white space and white + // space should not be included (" "), find start of word. + // If we end up in the first column of the next line (single char + // word) back up to end of the line. + fwd_word(1L, bigword, true); + if (curwin->w_cursor.col == 0) { + decl(&curwin->w_cursor); + } else { + oneleft(); + } + + if (include) { + include_white = true; + } + } + + if (VIsual_active) { + // should do something when inclusive == false ! + VIsual = start_pos; + redraw_curbuf_later(UPD_INVERTED); // update the inversion + } else { + oap->start = start_pos; + oap->motion_type = kMTCharWise; + } + count--; + } + + // When count is still > 0, extend with more objects. + while (count > 0) { + inclusive = true; + if (VIsual_active && lt(curwin->w_cursor, VIsual)) { + // In Visual mode, with cursor at start: move cursor back. + if (decl(&curwin->w_cursor) == -1) { + return FAIL; + } + if (include != (cls() != 0)) { + if (bck_word(1L, bigword, true) == FAIL) { + return FAIL; + } + } else { + if (bckend_word(1L, bigword, true) == FAIL) { + return FAIL; + } + (void)incl(&curwin->w_cursor); + } + } else { + // Move cursor forward one word and/or white area. + if (incl(&curwin->w_cursor) == -1) { + return FAIL; + } + if (include != (cls() == 0)) { + if (fwd_word(1L, bigword, true) == FAIL && count > 1) { + return FAIL; + } + // If end is just past a new-line, we don't want to include + // the first character on the line. + // Put cursor on last char of white. + if (oneleft() == FAIL) { + inclusive = false; + } + } else { + if (end_word(1L, bigword, true, true) == FAIL) { + return FAIL; + } + } + } + count--; + } + + if (include_white && (cls() != 0 + || (curwin->w_cursor.col == 0 && !inclusive))) { + // If we don't include white space at the end, move the start + // to include some white space there. This makes "daw" work + // better on the last word in a sentence (and "2daw" on last-but-one + // word). Also when "2daw" deletes "word." at the end of the line + // (cursor is at start of next line). + // But don't delete white space at start of line (indent). + pos = curwin->w_cursor; // save cursor position + curwin->w_cursor = start_pos; + if (oneleft() == OK) { + back_in_line(); + if (cls() == 0 && curwin->w_cursor.col > 0) { + if (VIsual_active) { + VIsual = curwin->w_cursor; + } else { + oap->start = curwin->w_cursor; + } + } + } + curwin->w_cursor = pos; // put cursor back at end + } + + if (VIsual_active) { + if (*p_sel == 'e' && inclusive && ltoreq(VIsual, curwin->w_cursor)) { + inc_cursor(); + } + if (VIsual_mode == 'V') { + VIsual_mode = 'v'; + redraw_cmdline = true; // show mode later + } + } else { + oap->inclusive = inclusive; + } + + return OK; +} + +/// Find sentence(s) under the cursor, cursor at end. +/// When Visual active, extend it by one or more sentences. +int current_sent(oparg_T *oap, long count, bool include) +{ + pos_T start_pos; + pos_T pos; + bool start_blank; + int c; + bool at_start_sent; + long ncount; + + start_pos = curwin->w_cursor; + pos = start_pos; + findsent(FORWARD, 1L); // Find start of next sentence. + + // When the Visual area is bigger than one character: Extend it. + if (VIsual_active && !equalpos(start_pos, VIsual)) { +extend: + if (lt(start_pos, VIsual)) { + // Cursor at start of Visual area. + // Find out where we are: + // - in the white space before a sentence + // - in a sentence or just after it + // - at the start of a sentence + at_start_sent = true; + decl(&pos); + while (lt(pos, curwin->w_cursor)) { + c = gchar_pos(&pos); + if (!ascii_iswhite(c)) { + at_start_sent = false; + break; + } + incl(&pos); + } + if (!at_start_sent) { + findsent(BACKWARD, 1L); + if (equalpos(curwin->w_cursor, start_pos)) { + at_start_sent = true; // exactly at start of sentence + } else { + // inside a sentence, go to its end (start of next) + findsent(FORWARD, 1L); + } + } + if (include) { // "as" gets twice as much as "is" + count *= 2; + } + while (count--) { + if (at_start_sent) { + find_first_blank(&curwin->w_cursor); + } + c = gchar_cursor(); + if (!at_start_sent || (!include && !ascii_iswhite(c))) { + findsent(BACKWARD, 1L); + } + at_start_sent = !at_start_sent; + } + } else { + // Cursor at end of Visual area. + // Find out where we are: + // - just before a sentence + // - just before or in the white space before a sentence + // - in a sentence + incl(&pos); + at_start_sent = true; + if (!equalpos(pos, curwin->w_cursor)) { // not just before a sentence + at_start_sent = false; + while (lt(pos, curwin->w_cursor)) { + c = gchar_pos(&pos); + if (!ascii_iswhite(c)) { + at_start_sent = true; + break; + } + incl(&pos); + } + if (at_start_sent) { // in the sentence + findsent(BACKWARD, 1L); + } else { // in/before white before a sentence + curwin->w_cursor = start_pos; + } + } + + if (include) { // "as" gets twice as much as "is" + count *= 2; + } + findsent_forward(count, at_start_sent); + if (*p_sel == 'e') { + curwin->w_cursor.col++; + } + } + return OK; + } + + // If the cursor started on a blank, check if it is just before the start + // of the next sentence. + while (c = gchar_pos(&pos), ascii_iswhite(c)) { + incl(&pos); + } + if (equalpos(pos, curwin->w_cursor)) { + start_blank = true; + find_first_blank(&start_pos); // go back to first blank + } else { + start_blank = false; + findsent(BACKWARD, 1L); + start_pos = curwin->w_cursor; + } + if (include) { + ncount = count * 2; + } else { + ncount = count; + if (start_blank) { + ncount--; + } + } + if (ncount > 0) { + findsent_forward(ncount, true); + } else { + decl(&curwin->w_cursor); + } + + if (include) { + // If the blank in front of the sentence is included, exclude the + // blanks at the end of the sentence, go back to the first blank. + // If there are no trailing blanks, try to include leading blanks. + if (start_blank) { + find_first_blank(&curwin->w_cursor); + c = gchar_pos(&curwin->w_cursor); + if (ascii_iswhite(c)) { + decl(&curwin->w_cursor); + } + } else if (c = gchar_cursor(), !ascii_iswhite(c)) { + find_first_blank(&start_pos); + } + } + + if (VIsual_active) { + // Avoid getting stuck with "is" on a single space before a sentence. + if (equalpos(start_pos, curwin->w_cursor)) { + goto extend; + } + if (*p_sel == 'e') { + curwin->w_cursor.col++; + } + VIsual = start_pos; + VIsual_mode = 'v'; + redraw_cmdline = true; // show mode later + redraw_curbuf_later(UPD_INVERTED); // update the inversion + } else { + // include a newline after the sentence, if there is one + if (incl(&curwin->w_cursor) == -1) { + oap->inclusive = true; + } else { + oap->inclusive = false; + } + oap->start = start_pos; + oap->motion_type = kMTCharWise; + } + return OK; +} + +/// Find block under the cursor, cursor at end. +/// "what" and "other" are two matching parenthesis/brace/etc. +/// +/// @param include true == include white space +/// @param what '(', '{', etc. +/// @param other ')', '}', etc. +int current_block(oparg_T *oap, long count, bool include, int what, int other) +{ + pos_T old_pos; + pos_T *pos = NULL; + pos_T start_pos; + pos_T *end_pos; + pos_T old_start, old_end; + char *save_cpo; + bool sol = false; // '{' at start of line + + old_pos = curwin->w_cursor; + old_end = curwin->w_cursor; // remember where we started + old_start = old_end; + + // If we start on '(', '{', ')', '}', etc., use the whole block inclusive. + if (!VIsual_active || equalpos(VIsual, curwin->w_cursor)) { + setpcmark(); + if (what == '{') { // ignore indent + while (inindent(1)) { + if (inc_cursor() != 0) { + break; + } + } + } + if (gchar_cursor() == what) { + // cursor on '(' or '{', move cursor just after it + curwin->w_cursor.col++; + } + } else if (lt(VIsual, curwin->w_cursor)) { + old_start = VIsual; + curwin->w_cursor = VIsual; // cursor at low end of Visual + } else { + old_end = VIsual; + } + + // Search backwards for unclosed '(', '{', etc.. + // Put this position in start_pos. + // Ignore quotes here. Keep the "M" flag in 'cpo', as that is what the + // user wants. + save_cpo = p_cpo; + p_cpo = vim_strchr(p_cpo, CPO_MATCHBSL) != NULL ? "%M" : "%"; + if ((pos = findmatch(NULL, what)) != NULL) { + while (count-- > 0) { + if ((pos = findmatch(NULL, what)) == NULL) { + break; + } + curwin->w_cursor = *pos; + start_pos = *pos; // the findmatch for end_pos will overwrite *pos + } + } else { + while (count-- > 0) { + if ((pos = findmatchlimit(NULL, what, FM_FORWARD, 0)) == NULL) { + break; + } + curwin->w_cursor = *pos; + start_pos = *pos; // the findmatch for end_pos will overwrite *pos + } + } + p_cpo = save_cpo; + + // Search for matching ')', '}', etc. + // Put this position in curwin->w_cursor. + if (pos == NULL || (end_pos = findmatch(NULL, other)) == NULL) { + curwin->w_cursor = old_pos; + return FAIL; + } + curwin->w_cursor = *end_pos; + + // Try to exclude the '(', '{', ')', '}', etc. when "include" is false. + // If the ending '}', ')' or ']' is only preceded by indent, skip that + // indent. But only if the resulting area is not smaller than what we + // started with. + while (!include) { + incl(&start_pos); + sol = (curwin->w_cursor.col == 0); + decl(&curwin->w_cursor); + while (inindent(1)) { + sol = true; + if (decl(&curwin->w_cursor) != 0) { + break; + } + } + + // In Visual mode, when the resulting area is not bigger than what we + // started with, extend it to the next block, and then exclude again. + // Don't try to expand the area if the area is empty. + if (!lt(start_pos, old_start) && !lt(old_end, curwin->w_cursor) + && !equalpos(start_pos, curwin->w_cursor) + && VIsual_active) { + curwin->w_cursor = old_start; + decl(&curwin->w_cursor); + if ((pos = findmatch(NULL, what)) == NULL) { + curwin->w_cursor = old_pos; + return FAIL; + } + start_pos = *pos; + curwin->w_cursor = *pos; + if ((end_pos = findmatch(NULL, other)) == NULL) { + curwin->w_cursor = old_pos; + return FAIL; + } + curwin->w_cursor = *end_pos; + } else { + break; + } + } + + if (VIsual_active) { + if (*p_sel == 'e') { + inc(&curwin->w_cursor); + } + if (sol && gchar_cursor() != NUL) { + inc(&curwin->w_cursor); // include the line break + } + VIsual = start_pos; + VIsual_mode = 'v'; + redraw_curbuf_later(UPD_INVERTED); // update the inversion + showmode(); + } else { + oap->start = start_pos; + oap->motion_type = kMTCharWise; + oap->inclusive = false; + if (sol) { + incl(&curwin->w_cursor); + } else if (ltoreq(start_pos, curwin->w_cursor)) { + // Include the character under the cursor. + oap->inclusive = true; + } else { + // End is before the start (no text in between <>, [], etc.): don't + // operate on any text. + curwin->w_cursor = start_pos; + } + } + + return OK; +} + +/// @param end_tag when true, return true if the cursor is on "</aaa>". +/// +/// @return true if the cursor is on a "<aaa>" tag. Ignore "<aaa/>". +static bool in_html_tag(bool end_tag) +{ + char_u *line = get_cursor_line_ptr(); + char_u *p; + int c; + int lc = NUL; + pos_T pos; + + for (p = line + curwin->w_cursor.col; p > line;) { + if (*p == '<') { // find '<' under/before cursor + break; + } + MB_PTR_BACK(line, p); + if (*p == '>') { // find '>' before cursor + break; + } + } + if (*p != '<') { + return false; + } + + pos.lnum = curwin->w_cursor.lnum; + pos.col = (colnr_T)(p - line); + + MB_PTR_ADV(p); + if (end_tag) { + // check that there is a '/' after the '<' + return *p == '/'; + } + + // check that there is no '/' after the '<' + if (*p == '/') { + return false; + } + + // check that the matching '>' is not preceded by '/' + for (;;) { + if (inc(&pos) < 0) { + return false; + } + c = *ml_get_pos(&pos); + if (c == '>') { + break; + } + lc = c; + } + return lc != '/'; +} + +/// Find tag block under the cursor, cursor at end. +/// +/// @param include true == include white space +int current_tagblock(oparg_T *oap, long count_arg, bool include) +{ + long count = count_arg; + pos_T old_pos; + pos_T start_pos; + pos_T end_pos; + pos_T old_start, old_end; + char_u *p; + char_u *cp; + int len; + bool do_include = include; + bool save_p_ws = p_ws; + int retval = FAIL; + int is_inclusive = true; + + p_ws = false; + + old_pos = curwin->w_cursor; + old_end = curwin->w_cursor; // remember where we started + old_start = old_end; + if (!VIsual_active || *p_sel == 'e') { + decl(&old_end); // old_end is inclusive + } + + // If we start on "<aaa>" select that block. + if (!VIsual_active || equalpos(VIsual, curwin->w_cursor)) { + setpcmark(); + + // ignore indent + while (inindent(1)) { + if (inc_cursor() != 0) { + break; + } + } + + if (in_html_tag(false)) { + // cursor on start tag, move to its '>' + while (*get_cursor_pos_ptr() != '>') { + if (inc_cursor() < 0) { + break; + } + } + } else if (in_html_tag(true)) { + // cursor on end tag, move to just before it + while (*get_cursor_pos_ptr() != '<') { + if (dec_cursor() < 0) { + break; + } + } + dec_cursor(); + old_end = curwin->w_cursor; + } + } else if (lt(VIsual, curwin->w_cursor)) { + old_start = VIsual; + curwin->w_cursor = VIsual; // cursor at low end of Visual + } else { + old_end = VIsual; + } + +again: + // Search backwards for unclosed "<aaa>". + // Put this position in start_pos. + for (long n = 0; n < count; n++) { + if (do_searchpair("<[^ \t>/!]\\+\\%(\\_s\\_[^>]\\{-}[^/]>\\|$\\|\\_s\\=>\\)", + "", + "</[^>]*>", BACKWARD, NULL, 0, + NULL, (linenr_T)0, 0L) <= 0) { + curwin->w_cursor = old_pos; + goto theend; + } + } + start_pos = curwin->w_cursor; + + // Search for matching "</aaa>". First isolate the "aaa". + inc_cursor(); + p = get_cursor_pos_ptr(); + for (cp = p; + *cp != NUL && *cp != '>' && !ascii_iswhite(*cp); + MB_PTR_ADV(cp)) {} + len = (int)(cp - p); + if (len == 0) { + curwin->w_cursor = old_pos; + goto theend; + } + const size_t spat_len = (size_t)len + 39; + char *const spat = xmalloc(spat_len); + const size_t epat_len = (size_t)len + 9; + char *const epat = xmalloc(epat_len); + snprintf(spat, spat_len, + "<%.*s\\>\\%%(\\_s\\_[^>]\\{-}\\_[^/]>\\|\\_s\\?>\\)\\c", len, p); + snprintf(epat, epat_len, "</%.*s>\\c", len, p); + + const int r = (int)do_searchpair(spat, "", epat, FORWARD, NULL, 0, NULL, (linenr_T)0, 0L); + + xfree(spat); + xfree(epat); + + if (r < 1 || lt(curwin->w_cursor, old_end)) { + // Can't find other end or it's before the previous end. Could be a + // HTML tag that doesn't have a matching end. Search backwards for + // another starting tag. + count = 1; + curwin->w_cursor = start_pos; + goto again; + } + + if (do_include) { + // Include up to the '>'. + while (*get_cursor_pos_ptr() != '>') { + if (inc_cursor() < 0) { + break; + } + } + } else { + char_u *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 + if (*c == '<' && !VIsual_active && curwin->w_cursor.col == 0) { + // do not decrement cursor + is_inclusive = false; + } else if (*c == '<') { + dec_cursor(); + } + } + end_pos = curwin->w_cursor; + + if (!do_include) { + // Exclude the start tag. + curwin->w_cursor = start_pos; + while (inc_cursor() >= 0) { + if (*get_cursor_pos_ptr() == '>') { + inc_cursor(); + start_pos = curwin->w_cursor; + break; + } + } + curwin->w_cursor = end_pos; + + // If we are in Visual mode and now have the same text as before set + // "do_include" and try again. + if (VIsual_active + && equalpos(start_pos, old_start) + && equalpos(end_pos, old_end)) { + do_include = true; + curwin->w_cursor = old_start; + count = count_arg; + goto again; + } + } + + if (VIsual_active) { + // If the end is before the start there is no text between tags, select + // the char under the cursor. + if (lt(end_pos, start_pos)) { + curwin->w_cursor = start_pos; + } else if (*p_sel == 'e') { + inc_cursor(); + } + VIsual = start_pos; + VIsual_mode = 'v'; + redraw_curbuf_later(UPD_INVERTED); // update the inversion + showmode(); + } else { + oap->start = start_pos; + oap->motion_type = kMTCharWise; + if (lt(end_pos, start_pos)) { + // End is before the start: there is no text between tags; operate + // on an empty area. + curwin->w_cursor = start_pos; + oap->inclusive = false; + } else { + oap->inclusive = is_inclusive; + } + } + retval = OK; + +theend: + p_ws = save_p_ws; + return retval; +} + +/// @param include true == include white space +/// @param type 'p' for paragraph, 'S' for section +int current_par(oparg_T *oap, long count, bool include, int type) +{ + linenr_T start_lnum; + linenr_T end_lnum; + int white_in_front; + int dir; + int start_is_white; + int prev_start_is_white; + int retval = OK; + int do_white = false; + int t; + int i; + + if (type == 'S') { // not implemented yet + return FAIL; + } + + start_lnum = curwin->w_cursor.lnum; + + // When visual area is more than one line: extend it. + if (VIsual_active && start_lnum != VIsual.lnum) { +extend: + if (start_lnum < VIsual.lnum) { + dir = BACKWARD; + } else { + dir = FORWARD; + } + for (i = (int)count; --i >= 0;) { + if (start_lnum == + (dir == BACKWARD ? 1 : curbuf->b_ml.ml_line_count)) { + retval = FAIL; + break; + } + + prev_start_is_white = -1; + for (t = 0; t < 2; t++) { + start_lnum += dir; + start_is_white = linewhite(start_lnum); + if (prev_start_is_white == start_is_white) { + start_lnum -= dir; + break; + } + for (;;) { + if (start_lnum == (dir == BACKWARD + ? 1 : curbuf->b_ml.ml_line_count)) { + break; + } + if (start_is_white != linewhite(start_lnum + dir) + || (!start_is_white + && startPS(start_lnum + (dir > 0 + ? 1 : 0), 0, 0))) { + break; + } + start_lnum += dir; + } + if (!include) { + break; + } + if (start_lnum == (dir == BACKWARD + ? 1 : curbuf->b_ml.ml_line_count)) { + break; + } + prev_start_is_white = start_is_white; + } + } + curwin->w_cursor.lnum = start_lnum; + curwin->w_cursor.col = 0; + return retval; + } + + // First move back to the start_lnum of the paragraph or white lines + white_in_front = linewhite(start_lnum); + while (start_lnum > 1) { + if (white_in_front) { // stop at first white line + if (!linewhite(start_lnum - 1)) { + break; + } + } else { // stop at first non-white line of start of paragraph + if (linewhite(start_lnum - 1) || startPS(start_lnum, 0, 0)) { + break; + } + } + start_lnum--; + } + + // Move past the end of any white lines. + end_lnum = start_lnum; + while (end_lnum <= curbuf->b_ml.ml_line_count && linewhite(end_lnum)) { + end_lnum++; + } + + end_lnum--; + i = (int)count; + if (!include && white_in_front) { + i--; + } + while (i--) { + if (end_lnum == curbuf->b_ml.ml_line_count) { + return FAIL; + } + + if (!include) { + do_white = linewhite(end_lnum + 1); + } + + if (include || !do_white) { + end_lnum++; + // skip to end of paragraph + while (end_lnum < curbuf->b_ml.ml_line_count + && !linewhite(end_lnum + 1) + && !startPS(end_lnum + 1, 0, 0)) { + end_lnum++; + } + } + + if (i == 0 && white_in_front && include) { + break; + } + + // skip to end of white lines after paragraph + if (include || do_white) { + while (end_lnum < curbuf->b_ml.ml_line_count + && linewhite(end_lnum + 1)) { + end_lnum++; + } + } + } + + // If there are no empty lines at the end, try to find some empty lines at + // the start (unless that has been done already). + if (!white_in_front && !linewhite(end_lnum) && include) { + while (start_lnum > 1 && linewhite(start_lnum - 1)) { + start_lnum--; + } + } + + if (VIsual_active) { + // Problem: when doing "Vipipip" nothing happens in a single white + // line, we get stuck there. Trap this here. + if (VIsual_mode == 'V' && start_lnum == curwin->w_cursor.lnum) { + goto extend; + } + if (VIsual.lnum != start_lnum) { + VIsual.lnum = start_lnum; + VIsual.col = 0; + } + VIsual_mode = 'V'; + redraw_curbuf_later(UPD_INVERTED); // update the inversion + showmode(); + } else { + oap->start.lnum = start_lnum; + oap->start.col = 0; + oap->motion_type = kMTLineWise; + } + curwin->w_cursor.lnum = end_lnum; + curwin->w_cursor.col = 0; + + return OK; +} + +/// Search quote char from string line[col]. +/// Quote character escaped by one of the characters in "escape" is not counted +/// as a quote. +/// +/// @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) +{ + int c; + + for (;;) { + c = line[col]; + if (c == NUL) { + return -1; + } else if (escape != NULL && vim_strchr((char *)escape, c)) { + col++; + if (line[col] == NUL) { + return -1; + } + } else if (c == quotechar) { + break; + } + col += utfc_ptr2len((char *)line + col); + } + return col; +} + +/// Search backwards in "line" from column "col_start" to find "quotechar". +/// Quote character escaped by one of the characters in "escape" is not counted +/// as a quote. +/// +/// @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) +{ + int n; + + while (col_start > 0) { + 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) { + n++; + } + } + if (n & 1) { + col_start -= n; // uneven number of escape chars, skip it + } else if (line[col_start] == quotechar) { + break; + } + } + return col_start; +} + +/// Find quote under the cursor, cursor at end. +/// +/// @param include true == include quote char +/// @param quotechar Quote character +/// +/// @return true if found, else false. +bool current_quote(oparg_T *oap, long count, bool include, int quotechar) + FUNC_ATTR_NONNULL_ALL +{ + char_u *line = get_cursor_line_ptr(); + int col_end; + int col_start = curwin->w_cursor.col; + bool inclusive = false; + bool vis_empty = true; // Visual selection <= 1 char + bool vis_bef_curs = false; // Visual starts before cursor + bool did_exclusive_adj = false; // adjusted pos for 'selection' + bool inside_quotes = false; // Looks like "i'" done before + bool selected_quote = false; // Has quote inside selection + int i; + bool restore_vis_bef = false; // resotre VIsual on abort + + // When 'selection' is "exclusive" move the cursor to where it would be + // with 'selection' "inclusive", so that the logic is the same for both. + // The cursor then is moved forward after adjusting the area. + if (VIsual_active) { + // this only works within one line + if (VIsual.lnum != curwin->w_cursor.lnum) { + return false; + } + + vis_bef_curs = lt(VIsual, curwin->w_cursor); + vis_empty = equalpos(VIsual, curwin->w_cursor); + if (*p_sel == 'e') { + if (vis_bef_curs) { + dec_cursor(); + did_exclusive_adj = true; + } else if (!vis_empty) { + dec(&VIsual); + did_exclusive_adj = true; + } + vis_empty = equalpos(VIsual, curwin->w_cursor); + if (!vis_bef_curs && !vis_empty) { + // VIsual needs to be start of Visual selection. + pos_T t = curwin->w_cursor; + + curwin->w_cursor = VIsual; + VIsual = t; + vis_bef_curs = true; + restore_vis_bef = true; + } + } + } + + if (!vis_empty) { + // Check if the existing selection exactly spans the text inside + // quotes. + if (vis_bef_curs) { + inside_quotes = VIsual.col > 0 + && line[VIsual.col - 1] == quotechar + && line[curwin->w_cursor.col] != NUL + && 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 + && line[VIsual.col] != NUL + && line[VIsual.col + 1] == quotechar; + i = curwin->w_cursor.col; + col_end = VIsual.col; + } + + // Find out if we have a quote in the selection. + while (i <= col_end) { + // check for going over the end of the line, which can happen if + // the line was changed after the Visual area was selected. + if (line[i] == NUL) { + break; + } + if (line[i++] == quotechar) { + selected_quote = true; + break; + } + } + } + + if (!vis_empty && line[col_start] == quotechar) { + // Already selecting something and on a quote character. Find the + // next quoted string. + if (vis_bef_curs) { + // Assume we are on a closing quote: move to after the next + // opening quote. + col_start = find_next_quote(line, col_start + 1, quotechar, NULL); + if (col_start < 0) { + goto abort_search; + } + col_end = find_next_quote(line, col_start + 1, quotechar, (char_u *)curbuf->b_p_qe); + if (col_end < 0) { + // We were on a starting quote perhaps? + col_end = col_start; + col_start = curwin->w_cursor.col; + } + } else { + col_end = find_prev_quote(line, col_start, quotechar, NULL); + if (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) { + // 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) { + int first_col = col_start; + + if (!vis_empty) { + if (vis_bef_curs) { + first_col = find_next_quote(line, col_start, quotechar, NULL); + } else { + first_col = find_prev_quote(line, col_start, quotechar, NULL); + } + } + // The cursor is on a quote, we don't know if it's the opening or + // closing quote. Search from the start of the line to find out. + // Also do this when there is a Visual area, a' may leave the cursor + // in between two strings. + col_start = 0; + for (;;) { + // Find open quote character. + col_start = find_next_quote(line, col_start, quotechar, NULL); + if (col_start < 0 || col_start > first_col) { + goto abort_search; + } + // Find close quote character. + col_end = find_next_quote(line, col_start + 1, quotechar, (char_u *)curbuf->b_p_qe); + if (col_end < 0) { + goto abort_search; + } + // If is cursor between start and end quote character, it is + // target text object. + if (col_start <= first_col && first_col <= col_end) { + break; + } + col_start = col_end + 1; + } + } 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) { + // No quote before the cursor, look after the cursor. + col_start = find_next_quote(line, col_start, quotechar, NULL); + if (col_start < 0) { + goto abort_search; + } + } + + // Find close quote character. + col_end = find_next_quote(line, col_start + 1, quotechar, (char_u *)curbuf->b_p_qe); + if (col_end < 0) { + goto abort_search; + } + } + + // When "include" is true, include spaces after closing quote or before + // the starting quote. + if (include) { + if (ascii_iswhite(line[col_end + 1])) { + while (ascii_iswhite(line[col_end + 1])) { + col_end++; + } + } else { + while (col_start > 0 && ascii_iswhite(line[col_start - 1])) { + col_start--; + } + } + } + + // Set start position. After vi" another i" must include the ". + // For v2i" include the quotes. + if (!include && count < 2 && (vis_empty || !inside_quotes)) { + col_start++; + } + curwin->w_cursor.col = col_start; + if (VIsual_active) { + // Set the start of the Visual area when the Visual area was empty, we + // were just inside quotes or the Visual area didn't start at a quote + // and didn't include a quote. + if (vis_empty + || (vis_bef_curs + && !selected_quote + && (inside_quotes + || (line[VIsual.col] != quotechar + && (VIsual.col == 0 + || line[VIsual.col - 1] != quotechar))))) { + VIsual = curwin->w_cursor; + redraw_curbuf_later(UPD_INVERTED); + } + } else { + oap->start = curwin->w_cursor; + oap->motion_type = kMTCharWise; + } + + // Set end position. + curwin->w_cursor.col = col_end; + if ((include || count > 1 + // After vi" another i" must include the ". + || (!vis_empty && inside_quotes)) && inc_cursor() == 2) { + inclusive = true; + } + if (VIsual_active) { + if (vis_empty || vis_bef_curs) { + // decrement cursor when 'selection' is not exclusive + if (*p_sel != 'e') { + dec_cursor(); + } + } else { + // Cursor is at start of Visual area. Set the end of the Visual + // area when it was just inside quotes or it didn't end at a + // quote. + if (inside_quotes + || (!selected_quote + && line[VIsual.col] != quotechar + && (line[VIsual.col] == NUL + || line[VIsual.col + 1] != quotechar))) { + dec_cursor(); + VIsual = curwin->w_cursor; + } + curwin->w_cursor.col = col_start; + } + if (VIsual_mode == 'V') { + VIsual_mode = 'v'; + redraw_cmdline = true; // show mode later + } + } else { + // Set inclusive and other oap's flags. + oap->inclusive = inclusive; + } + + return true; + +abort_search: + if (VIsual_active && *p_sel == 'e') { + if (did_exclusive_adj) { + inc_cursor(); + } + if (restore_vis_bef) { + pos_T t = curwin->w_cursor; + + curwin->w_cursor = VIsual; + VIsual = t; + } + } + return false; +} diff --git a/src/nvim/textobject.h b/src/nvim/textobject.h new file mode 100644 index 0000000000..26f88613fd --- /dev/null +++ b/src/nvim/textobject.h @@ -0,0 +1,11 @@ +#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 + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "textobject.h.generated.h" +#endif +#endif // NVIM_TEXTOBJECT_H diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c index 61a59bcf06..d269878f46 100644 --- a/src/nvim/tui/input.c +++ b/src/nvim/tui/input.c @@ -233,12 +233,12 @@ static void tinput_wait_enqueue(void **argv) if (ui_client_channel_id) { Array args = ARRAY_DICT_INIT; Error err = ERROR_INIT; - ADD(args, STRING_OBJ(copy_string(keys))); + 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, NULL); + arena_mem_free(res_mem); } else { consumed = input_enqueue(keys); } @@ -398,6 +398,14 @@ static void forward_mouse_event(TermInput *input, TermKeyKey *key) button = last_pressed_button; } + if (ev == TERMKEY_MOUSE_UNKNOWN && !(key->code.mouse[0] & 0x20)) { + int code = key->code.mouse[0] & ~0x3c; + if (code == 66 || code == 67) { + ev = TERMKEY_MOUSE_PRESS; + button = code - 60; + } + } + if (button == 0 || (ev != TERMKEY_MOUSE_PRESS && ev != TERMKEY_MOUSE_DRAG && ev != TERMKEY_MOUSE_RELEASE)) { return; @@ -431,8 +439,11 @@ static void forward_mouse_event(TermInput *input, TermKeyKey *key) if (button == 4) { len += (size_t)snprintf(buf + len, sizeof(buf) - len, "ScrollWheelUp"); } else if (button == 5) { - len += (size_t)snprintf(buf + len, sizeof(buf) - len, - "ScrollWheelDown"); + len += (size_t)snprintf(buf + len, sizeof(buf) - len, "ScrollWheelDown"); + } else if (button == 6) { + len += (size_t)snprintf(buf + len, sizeof(buf) - len, "ScrollWheelLeft"); + } else if (button == 7) { + len += (size_t)snprintf(buf + len, sizeof(buf) - len, "ScrollWheelRight"); } else { len += (size_t)snprintf(buf + len, sizeof(buf) - len, "Mouse"); last_pressed_button = button; diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index e2289eb9ce..38e8c15762 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -171,7 +171,7 @@ UI *tui_start(void) ui->option_set = tui_option_set; ui->raw_line = tui_raw_line; - memset(ui->ui_ext, 0, sizeof(ui->ui_ext)); + CLEAR_FIELD(ui->ui_ext); ui->ui_ext[kUILinegrid] = true; ui->ui_ext[kUITermColors] = true; @@ -875,6 +875,53 @@ safe_move: ugrid_goto(grid, row, col); } +static void print_spaces(UI *ui, int width) +{ + TUIData *data = ui->data; + UGrid *grid = &data->grid; + + out(ui, data->space_buf, (size_t)width); + grid->col += width; + if (data->immediate_wrap_after_last_column) { + // Printing at the right margin immediately advances the cursor. + final_column_wrap(ui); + } +} + +/// Move cursor to the position given by `row` and `col` and print the character in `cell`. +/// This allows the grid and the host terminal to assume different widths of ambiguous-width chars. +/// +/// @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) +{ + TUIData *data = ui->data; + UGrid *grid = &data->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); + + 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); + } + + print_cell(ui, cell); + + if (is_ambiwidth) { + // Force repositioning cursor after printing an ambiguous-width character. + grid->row = -1; + } +} + static void clear_region(UI *ui, int top, int bot, int left, int right, int attr_id) { TUIData *data = ui->data; @@ -888,7 +935,7 @@ static void clear_region(UI *ui, int top, int bot, int left, int right, int attr && left == 0 && right == ui->width && bot == ui->height) { if (top == 0) { unibi_out(ui, unibi_clear_screen); - ugrid_goto(&data->grid, top, left); + ugrid_goto(grid, top, left); } else { cursor_goto(ui, top, 0); unibi_out(ui, unibi_clr_eos); @@ -905,12 +952,7 @@ static void clear_region(UI *ui, int top, int bot, int left, int right, int attr UNIBI_SET_NUM_VAR(data->params[0], width); unibi_out(ui, unibi_erase_chars); } else { - out(ui, data->space_buf, (size_t)width); - grid->col += width; - if (data->immediate_wrap_after_last_column) { - // Printing at the right margin immediately advances the cursor. - final_column_wrap(ui); - } + print_spaces(ui, width); } } } @@ -1302,8 +1344,8 @@ static void tui_flush(UI *ui) } UGRID_FOREACH_CELL(grid, row, r.left, clear_col, { - cursor_goto(ui, row, curcol); - print_cell(ui, cell); + print_cell_at_pos(ui, 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); @@ -1439,8 +1481,8 @@ static void tui_raw_line(UI *ui, Integer g, Integer linerow, Integer startcol, I grid->cells[linerow][c].attr = attrs[c - startcol]; } UGRID_FOREACH_CELL(grid, (int)linerow, (int)startcol, (int)endcol, { - cursor_goto(ui, (int)linerow, curcol); - print_cell(ui, cell); + print_cell_at_pos(ui, (int)linerow, curcol, cell, + curcol < endcol - 1 && (cell + 1)->data[0] == NUL); }); if (clearcol > endcol) { @@ -1458,8 +1500,8 @@ 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; - cursor_goto(ui, (int)linerow, grid->width - size); - print_cell(ui, &grid->cells[linerow][grid->width - size]); + print_cell_at_pos(ui, (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 diff --git a/src/nvim/types.h b/src/nvim/types.h index 00b9e6fc09..fb10bf21d9 100644 --- a/src/nvim/types.h +++ b/src/nvim/types.h @@ -22,12 +22,23 @@ typedef int handle_T; // absent callback etc. typedef int LuaRef; -typedef void (*FunPtr)(void); +/// Type used for VimL VAR_FLOAT values +typedef double float_T; + +typedef struct MsgpackRpcRequestHandler MsgpackRpcRequestHandler; + +typedef union { + float_T (*float_func)(float_T); + const MsgpackRpcRequestHandler *api_handler; + void *nullptr; +} EvalFuncData; typedef handle_T NS; typedef struct expand expand_T; +typedef uint64_t proftime_T; + typedef enum { kNone = -1, kFalse = 0, diff --git a/src/nvim/ui.c b/src/nvim/ui.c index 4fcfee1192..46f4d137c4 100644 --- a/src/nvim/ui.c +++ b/src/nvim/ui.c @@ -13,11 +13,12 @@ #include "nvim/cursor.h" #include "nvim/cursor_shape.h" #include "nvim/diff.h" +#include "nvim/drawscreen.h" #include "nvim/event/loop.h" -#include "nvim/ex_cmds2.h" #include "nvim/ex_getln.h" #include "nvim/fold.h" #include "nvim/garray.h" +#include "nvim/grid.h" #include "nvim/highlight.h" #include "nvim/log.h" #include "nvim/main.h" @@ -31,8 +32,7 @@ #include "nvim/os/signal.h" #include "nvim/os/time.h" #include "nvim/os_unix.h" -#include "nvim/popupmnu.h" -#include "nvim/screen.h" +#include "nvim/popupmenu.h" #include "nvim/ui.h" #include "nvim/ui_compositor.h" #include "nvim/vim.h" @@ -336,7 +336,7 @@ void vim_beep(unsigned val) // 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((char *)p_debug, 'e') != NULL) { + if (vim_strchr(p_debug, 'e') != NULL) { msg_source(HL_ATTR(HLF_W)); msg_attr(_("Beep!"), HL_ATTR(HLF_W)); } @@ -517,11 +517,10 @@ void ui_flush(void) } if (pending_mode_info_update) { Arena arena = ARENA_EMPTY; - arena_start(&arena, &ui_ext_fixblk); Array style = mode_style_array(&arena); bool enabled = (*p_guicursor != NUL); ui_call_mode_info_set(enabled, style); - arena_mem_free(arena_finish(&arena), &ui_ext_fixblk); + arena_mem_free(arena_finish(&arena)); pending_mode_info_update = false; } if (pending_mode_update && !starting) { @@ -566,7 +565,7 @@ void ui_check_mouse(void) // - 'a' is in 'mouse' and "c" is in MOUSE_A, or // - the current buffer is a help file and 'h' is in 'mouse' and we are in a // normal editing mode (not at hit-return message). - for (char_u *p = p_mouse; *p; p++) { + for (char_u *p = (char_u *)p_mouse; *p; p++) { switch (*p) { case 'a': if (vim_strchr(MOUSE_A, checkfor) != NULL) { @@ -663,6 +662,6 @@ void ui_grid_resize(handle_T grid_handle, int width, int height, Error *error) // non-positive indicates no request wp->w_height_request = MAX(height, 0); wp->w_width_request = MAX(width, 0); - win_set_inner_size(wp); + win_set_inner_size(wp, true); } } diff --git a/src/nvim/ui.h b/src/nvim/ui.h index 7dd2f5bce3..996b3467a6 100644 --- a/src/nvim/ui.h +++ b/src/nvim/ui.h @@ -47,8 +47,6 @@ enum { typedef int LineFlags; -EXTERN ArenaMem ui_ext_fixblk INIT(= NULL); - struct ui_t { bool rgb; bool override; ///< Force highest-requested UI capabilities. diff --git a/src/nvim/ui_bridge.c b/src/nvim/ui_bridge.c index 84098e9476..809d278029 100644 --- a/src/nvim/ui_bridge.c +++ b/src/nvim/ui_bridge.c @@ -194,9 +194,9 @@ static void ui_bridge_suspend_event(void **argv) static void ui_bridge_option_set(UI *ui, String name, Object value) { - String copy_name = copy_string(name); + String copy_name = copy_string(name, NULL); Object *copy_value = xmalloc(sizeof(Object)); - *copy_value = copy_object(value); + *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 diff --git a/src/nvim/ui_client.c b/src/nvim/ui_client.c index a586fec3bf..265c54f72d 100644 --- a/src/nvim/ui_client.c +++ b/src/nvim/ui_client.c @@ -55,7 +55,7 @@ UIClientHandler ui_client_get_redraw_handler(const char *name, size_t name_len, /// async 'redraw' events, which are expected when nvim acts as an ui client. /// get handled in msgpack_rpc/unpacker.c and directly dispatched to handlers /// of specific ui events, like ui_client_event_grid_resize and so on. -Object handle_ui_client_redraw(uint64_t channel_id, Array args, Error *error) +Object handle_ui_client_redraw(uint64_t channel_id, Array args, Arena *arena, Error *error) { api_set_error(error, kErrorTypeValidation, "'redraw' cannot be sent as a request"); return NIL; diff --git a/src/nvim/ui_compositor.c b/src/nvim/ui_compositor.c index 5df70d0d8e..2216e25db9 100644 --- a/src/nvim/ui_compositor.c +++ b/src/nvim/ui_compositor.c @@ -13,6 +13,7 @@ #include "nvim/api/private/helpers.h" #include "nvim/ascii.h" +#include "nvim/grid.h" #include "nvim/highlight.h" #include "nvim/highlight_group.h" #include "nvim/lib/kvec.h" @@ -22,8 +23,7 @@ #include "nvim/memory.h" #include "nvim/message.h" #include "nvim/os/os.h" -#include "nvim/popupmnu.h" -#include "nvim/screen.h" +#include "nvim/popupmenu.h" #include "nvim/ugrid.h" #include "nvim/ui.h" #include "nvim/ui_compositor.h" diff --git a/src/nvim/undo.c b/src/nvim/undo.c index 45c083b034..97a925f3ad 100644 --- a/src/nvim/undo.c +++ b/src/nvim/undo.c @@ -1,73 +1,71 @@ // 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 -/* - * undo.c: multi level undo facility - * - * The saved lines are stored in a list of lists (one for each buffer): - * - * b_u_oldhead------------------------------------------------+ - * | - * V - * +--------------+ +--------------+ +--------------+ - * b_u_newhead--->| u_header | | u_header | | u_header | - * | uh_next------>| uh_next------>| uh_next---->NULL - * NULL<--------uh_prev |<---------uh_prev |<---------uh_prev | - * | uh_entry | | uh_entry | | uh_entry | - * +--------|-----+ +--------|-----+ +--------|-----+ - * | | | - * V V V - * +--------------+ +--------------+ +--------------+ - * | u_entry | | u_entry | | u_entry | - * | ue_next | | ue_next | | ue_next | - * +--------|-----+ +--------|-----+ +--------|-----+ - * | | | - * V V V - * +--------------+ NULL NULL - * | u_entry | - * | ue_next | - * +--------|-----+ - * | - * V - * etc. - * - * Each u_entry list contains the information for one undo or redo. - * curbuf->b_u_curhead points to the header of the last undo (the next redo), - * or is NULL if nothing has been undone (end of the branch). - * - * For keeping alternate undo/redo branches the uh_alt field is used. Thus at - * each point in the list a branch may appear for an alternate to redo. The - * uh_seq field is numbered sequentially to be able to find a newer or older - * branch. - * - * +---------------+ +---------------+ - * b_u_oldhead --->| u_header | | u_header | - * | uh_alt_next ---->| uh_alt_next ----> NULL - * NULL <----- uh_alt_prev |<------ uh_alt_prev | - * | uh_prev | | uh_prev | - * +-----|---------+ +-----|---------+ - * | | - * V V - * +---------------+ +---------------+ - * | u_header | | u_header | - * | uh_alt_next | | uh_alt_next | - * b_u_newhead --->| uh_alt_prev | | uh_alt_prev | - * | uh_prev | | uh_prev | - * +-----|---------+ +-----|---------+ - * | | - * V V - * NULL +---------------+ +---------------+ - * | u_header | | u_header | - * | uh_alt_next ---->| uh_alt_next | - * | uh_alt_prev |<------ uh_alt_prev | - * | uh_prev | | uh_prev | - * +-----|---------+ +-----|---------+ - * | | - * etc. etc. - * - * - * All data is allocated and will all be freed when the buffer is unloaded. - */ +// undo.c: multi level undo facility + +// The saved lines are stored in a list of lists (one for each buffer): +// +// b_u_oldhead------------------------------------------------+ +// | +// V +// +--------------+ +--------------+ +--------------+ +// b_u_newhead--->| u_header | | u_header | | u_header | +// | uh_next------>| uh_next------>| uh_next---->NULL +// NULL<--------uh_prev |<---------uh_prev |<---------uh_prev | +// | uh_entry | | uh_entry | | uh_entry | +// +--------|-----+ +--------|-----+ +--------|-----+ +// | | | +// V V V +// +--------------+ +--------------+ +--------------+ +// | u_entry | | u_entry | | u_entry | +// | ue_next | | ue_next | | ue_next | +// +--------|-----+ +--------|-----+ +--------|-----+ +// | | | +// V V V +// +--------------+ NULL NULL +// | u_entry | +// | ue_next | +// +--------|-----+ +// | +// V +// etc. +// +// Each u_entry list contains the information for one undo or redo. +// curbuf->b_u_curhead points to the header of the last undo (the next redo), +// or is NULL if nothing has been undone (end of the branch). +// +// For keeping alternate undo/redo branches the uh_alt field is used. Thus at +// each point in the list a branch may appear for an alternate to redo. The +// uh_seq field is numbered sequentially to be able to find a newer or older +// branch. +// +// +---------------+ +---------------+ +// b_u_oldhead --->| u_header | | u_header | +// | uh_alt_next ---->| uh_alt_next ----> NULL +// NULL <----- uh_alt_prev |<------ uh_alt_prev | +// | uh_prev | | uh_prev | +// +-----|---------+ +-----|---------+ +// | | +// V V +// +---------------+ +---------------+ +// | u_header | | u_header | +// | uh_alt_next | | uh_alt_next | +// b_u_newhead --->| uh_alt_prev | | uh_alt_prev | +// | uh_prev | | uh_prev | +// +-----|---------+ +-----|---------+ +// | | +// V V +// NULL +---------------+ +---------------+ +// | u_header | | u_header | +// | uh_alt_next ---->| uh_alt_next | +// | uh_alt_prev |<------ uh_alt_prev | +// | uh_prev | | uh_prev | +// +-----|---------+ +-----|---------+ +// | | +// etc. etc. +// +// +// All data is allocated and will all be freed when the buffer is unloaded. // Uncomment the next line for including the u_check() function. This warns // for errors in the debug information. @@ -88,6 +86,7 @@ #include "nvim/buffer_updates.h" #include "nvim/change.h" #include "nvim/cursor.h" +#include "nvim/drawscreen.h" #include "nvim/edit.h" #include "nvim/ex_getln.h" #include "nvim/extmark.h" @@ -113,6 +112,12 @@ #include "nvim/types.h" #include "nvim/undo.h" +/// Structure passed around between undofile functions. +typedef struct { + buf_T *bi_buf; + FILE *bi_fp; +} bufinfo_T; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "undo.c.generated.h" #endif @@ -120,31 +125,25 @@ // used in undo_end() to report number of added and deleted lines static long u_newcount, u_oldcount; -/* - * When 'u' flag included in 'cpoptions', we behave like vi. Need to remember - * the action that "u" should do. - */ +// When 'u' flag included in 'cpoptions', we behave like vi. Need to remember +// the action that "u" should do. static bool undo_undoes = false; static int lastmark = 0; #if defined(U_DEBUG) -/* - * Check the undo structures for being valid. Print a warning when something - * looks wrong. - */ +// Check the undo structures for being valid. Print a warning when something +// looks wrong. static int seen_b_u_curhead; static int seen_b_u_newhead; static int header_count; static void u_check_tree(u_header_T *uhp, u_header_T *exp_uh_next, u_header_T *exp_uh_alt_prev) { - u_entry_T *uep; - if (uhp == NULL) { return; } - ++header_count; + header_count++; if (uhp == curbuf->b_u_curhead && ++seen_b_u_curhead > 1) { emsg("b_u_curhead found twice (looping?)"); return; @@ -170,7 +169,7 @@ static void u_check_tree(u_header_T *uhp, u_header_T *exp_uh_next, u_header_T *e } // Check the undo tree at this header. - for (uep = uhp->uh_entry; uep != NULL; uep = uep->ue_next) { + for (u_entry_T *uep = uhp->uh_entry; uep != NULL; uep = uep->ue_next) { if (uep->ue_magic != UE_MAGIC) { emsg("ue_magic wrong (may be using freed memory)"); break; @@ -209,11 +208,9 @@ static void u_check(int newhead_may_be_NULL) #endif -/* - * Save the current line for both the "u" and "U" command. - * Careful: may trigger autocommands that reload the buffer. - * Returns OK or FAIL. - */ +/// Save the current line for both the "u" and "U" command. +/// Careful: may trigger autocommands that reload the buffer. +/// Returns OK or FAIL. int u_save_cursor(void) { linenr_T cur = curwin->w_cursor.lnum; @@ -223,12 +220,10 @@ int u_save_cursor(void) return u_save(top, bot); } -/* - * Save the lines between "top" and "bot" for both the "u" and "U" command. - * "top" may be 0 and bot may be curbuf->b_ml.ml_line_count + 1. - * Careful: may trigger autocommands that reload the buffer. - * Returns FAIL when lines could not be saved, OK otherwise. - */ +/// Save the lines between "top" and "bot" for both the "u" and "U" command. +/// "top" may be 0 and bot may be curbuf->b_ml.ml_line_count + 1. +/// Careful: may trigger autocommands that reload the buffer. +/// Returns FAIL when lines could not be saved, OK otherwise. int u_save(linenr_T top, linenr_T bot) { if (top >= bot || bot > (curbuf->b_ml.ml_line_count + 1)) { @@ -242,35 +237,29 @@ int u_save(linenr_T top, linenr_T bot) return u_savecommon(curbuf, top, bot, (linenr_T)0, false); } -/* - * Save the line "lnum" (used by ":s" and "~" command). - * The line is replaced, so the new bottom line is lnum + 1. - * Careful: may trigger autocommands that reload the buffer. - * Returns FAIL when lines could not be saved, OK otherwise. - */ +/// Save the line "lnum" (used by ":s" and "~" command). +/// The line is replaced, so the new bottom line is lnum + 1. +/// Careful: may trigger autocommands that reload the buffer. +/// Returns FAIL when lines could not be saved, OK otherwise. int u_savesub(linenr_T lnum) { return u_savecommon(curbuf, lnum - 1, lnum + 1, lnum + 1, false); } -/* - * A new line is inserted before line "lnum" (used by :s command). - * The line is inserted, so the new bottom line is lnum + 1. - * Careful: may trigger autocommands that reload the buffer. - * Returns FAIL when lines could not be saved, OK otherwise. - */ +/// A new line is inserted before line "lnum" (used by :s command). +/// The line is inserted, so the new bottom line is lnum + 1. +/// Careful: may trigger autocommands that reload the buffer. +/// Returns FAIL when lines could not be saved, OK otherwise. int u_inssub(linenr_T lnum) { return u_savecommon(curbuf, lnum - 1, lnum, lnum + 1, false); } -/* - * Save the lines "lnum" - "lnum" + nlines (used by delete command). - * The lines are deleted, so the new bottom line is lnum, unless the buffer - * becomes empty. - * Careful: may trigger autocommands that reload the buffer. - * Returns FAIL when lines could not be saved, OK otherwise. - */ +/// Save the lines "lnum" - "lnum" + nlines (used by delete command). +/// The lines are deleted, so the new bottom line is lnum, unless the buffer +/// becomes empty. +/// Careful: may trigger autocommands that reload the buffer. +/// Returns FAIL when lines could not be saved, OK otherwise. int u_savedel(linenr_T lnum, long nlines) { return u_savecommon(curbuf, lnum - 1, lnum + (linenr_T)nlines, @@ -322,25 +311,15 @@ static inline void zero_fmark_additional_data(fmark_T *fmarks) } } -/* - * Common code for various ways to save text before a change. - * "top" is the line above the first changed line. - * "bot" is the line below the last changed line. - * "newbot" is the new bottom line. Use zero when not known. - * "reload" is TRUE when saving for a buffer reload. - * Careful: may trigger autocommands that reload the buffer. - * Returns FAIL when lines could not be saved, OK otherwise. - */ +/// Common code for various ways to save text before a change. +/// "top" is the line above the first changed line. +/// "bot" is the line below the last changed line. +/// "newbot" is the new bottom line. Use zero when not known. +/// "reload" is true when saving for a buffer reload. +/// Careful: may trigger autocommands that reload the buffer. +/// Returns FAIL when lines could not be saved, OK otherwise. int u_savecommon(buf_T *buf, linenr_T top, linenr_T bot, linenr_T newbot, int reload) { - linenr_T lnum; - long i; - u_header_T *uhp; - u_header_T *old_curhead; - u_entry_T *uep; - u_entry_T *prev_uep; - long size; - if (!reload) { // When making changes is not allowed return FAIL. It's a crude way // to make all change commands fail. @@ -365,16 +344,19 @@ int u_savecommon(buf_T *buf, linenr_T top, linenr_T bot, linenr_T newbot, int re } #ifdef U_DEBUG - u_check(FALSE); + u_check(false); #endif - size = bot - top - 1; + u_entry_T *uep; + u_entry_T *prev_uep; + long size = bot - top - 1; // If curbuf->b_u_synced == true make a new header. if (buf->b_u_synced) { // Need to create new entry in b_changelist. buf->b_new_change = true; + u_header_T *uhp; if (get_undolevel(buf) >= 0) { // Make a new header entry. Do this first so that we don't mess // up the undo info when out of memory. @@ -387,19 +369,15 @@ int u_savecommon(buf_T *buf, linenr_T top, linenr_T bot, linenr_T newbot, int re uhp = NULL; } - /* - * If we undid more than we redid, move the entry lists before and - * including curbuf->b_u_curhead to an alternate branch. - */ - old_curhead = buf->b_u_curhead; + // If we undid more than we redid, move the entry lists before and + // including curbuf->b_u_curhead to an alternate branch. + u_header_T *old_curhead = buf->b_u_curhead; if (old_curhead != NULL) { buf->b_u_newhead = old_curhead->uh_next.ptr; buf->b_u_curhead = NULL; } - /* - * free headers to keep the size right - */ + // free headers to keep the size right while (buf->b_u_numhead > get_undolevel(buf) && buf->b_u_oldhead != NULL) { u_header_T *uhfree = buf->b_u_oldhead; @@ -418,7 +396,7 @@ int u_savecommon(buf_T *buf, linenr_T top, linenr_T bot, linenr_T newbot, int re u_freebranch(buf, uhfree, &old_curhead); } #ifdef U_DEBUG - u_check(TRUE); + u_check(true); #endif } @@ -490,19 +468,17 @@ int u_savecommon(buf_T *buf, linenr_T top, linenr_T bot, linenr_T newbot, int re return OK; } - /* - * When saving a single line, and it has been saved just before, it - * doesn't make sense saving it again. Saves a lot of memory when - * making lots of changes inside the same line. - * This is only possible if the previous change didn't increase or - * decrease the number of lines. - * Check the ten last changes. More doesn't make sense and takes too - * long. - */ + // When saving a single line, and it has been saved just before, it + // doesn't make sense saving it again. Saves a lot of memory when + // making lots of changes inside the same line. + // This is only possible if the previous change didn't increase or + // decrease the number of lines. + // Check the ten last changes. More doesn't make sense and takes too + // long. if (size == 1) { uep = u_get_headentry(buf); prev_uep = NULL; - for (i = 0; i < 10; ++i) { + for (long i = 0; i < 10; i++) { if (uep == NULL) { break; } @@ -514,7 +490,7 @@ int u_savecommon(buf_T *buf, linenr_T top, linenr_T bot, linenr_T newbot, int re != (uep->ue_bot == 0 ? buf->b_ml.ml_line_count + 1 : uep->ue_bot)) - : uep->ue_lcount != buf->b_ml.ml_line_count) + : uep->ue_lcount != buf->b_ml.ml_line_count) || (uep->ue_size > 1 && top >= uep->ue_top && top + 2 <= uep->ue_top + uep->ue_size + 1)) { @@ -561,11 +537,9 @@ int u_savecommon(buf_T *buf, linenr_T top, linenr_T bot, linenr_T newbot, int re u_getbot(buf); } - /* - * add lines in front of entry list - */ + // add lines in front of entry list uep = xmalloc(sizeof(u_entry_T)); - memset(uep, 0, sizeof(u_entry_T)); + CLEAR_POINTER(uep); #ifdef U_DEBUG uep->ue_magic = UE_MAGIC; #endif @@ -585,7 +559,9 @@ 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); - for (i = 0, lnum = top + 1; i < size; ++i) { + linenr_T lnum; + long i; + for (i = 0, lnum = top + 1; i < size; i++) { fast_breakcheck(); if (got_int) { u_freeentry(uep, i); @@ -607,7 +583,7 @@ int u_savecommon(buf_T *buf, linenr_T top, linenr_T bot, linenr_T newbot, int re undo_undoes = false; #ifdef U_DEBUG - u_check(FALSE); + u_check(false); #endif return OK; } @@ -643,12 +619,9 @@ static char_u e_not_open[] = N_("E828: Cannot open undo file for writing: %s"); void u_compute_hash(buf_T *buf, char_u *hash) { context_sha256_T ctx; - linenr_T lnum; - char_u *p; - sha256_start(&ctx); - for (lnum = 1; lnum <= buf->b_ml.ml_line_count; lnum++) { - p = ml_get_buf(buf, lnum, false); + for (linenr_T lnum = 1; lnum <= buf->b_ml.ml_line_count; lnum++) { + char_u *p = ml_get_buf(buf, lnum, false); sha256_update(&ctx, p, (uint32_t)(STRLEN(p) + 1)); } sha256_finish(&ctx, hash); @@ -668,21 +641,14 @@ void u_compute_hash(buf_T *buf, char_u *hash) char *u_get_undo_file_name(const char *const buf_ffname, const bool reading) FUNC_ATTR_WARN_UNUSED_RESULT { - char *dirp; - char dir_name[MAXPATHL + 1]; - char *munged_name = NULL; - char *undo_file_name = NULL; const char *ffname = buf_ffname; -#ifdef HAVE_READLINK - char fname_buf[MAXPATHL]; -#endif - char *p; if (ffname == NULL) { return NULL; } #ifdef HAVE_READLINK + char fname_buf[MAXPATHL]; // Expand symlink in the file name, so that we put the undo file with the // actual file instead of with the symlink. if (resolve_symlink((const char_u *)ffname, (char_u *)fname_buf) == OK) { @@ -690,9 +656,13 @@ char *u_get_undo_file_name(const char *const buf_ffname, const bool reading) } #endif + char dir_name[MAXPATHL + 1]; + char *munged_name = NULL; + char *undo_file_name = NULL; + // Loop over 'undodir'. When reading find the first file that exists. // When not reading use the first directory that exists or ".". - dirp = (char *)p_udir; + char *dirp = (char *)p_udir; while (*dirp != NUL) { size_t dir_len = copy_option_part(&dirp, dir_name, MAXPATHL, ","); if (dir_len == 1 && dir_name[0] == '.') { @@ -710,12 +680,12 @@ char *u_get_undo_file_name(const char *const buf_ffname, const bool reading) dir_name[dir_len] = NUL; // Remove trailing pathseps from directory name - p = &dir_name[dir_len - 1]; + char *p = &dir_name[dir_len - 1]; while (vim_ispathsep(*p)) { *p-- = NUL; } - bool has_directory = os_isdir((char_u *)dir_name); + bool has_directory = os_isdir(dir_name); if (!has_directory && *dirp == NUL && !reading) { // Last directory in the list does not exist, create it. int ret; @@ -731,9 +701,9 @@ char *u_get_undo_file_name(const char *const buf_ffname, const bool reading) if (has_directory) { if (munged_name == NULL) { munged_name = xstrdup(ffname); - for (p = munged_name; *p != NUL; MB_PTR_ADV(p)) { - if (vim_ispathsep(*p)) { - *p = '%'; + for (char *c = munged_name; *c != NUL; MB_PTR_ADV(c)) { + if (vim_ispathsep(*c)) { + *c = '%'; } } } @@ -765,12 +735,9 @@ static void corruption_error(const char *const mesg, const char *const file_name static void u_free_uhp(u_header_T *uhp) { - u_entry_T *nuep; - u_entry_T *uep; - - uep = uhp->uh_entry; + u_entry_T *uep = uhp->uh_entry; while (uep != NULL) { - nuep = uep->ue_next; + u_entry_T *nuep = uep->ue_next; u_freeentry(uep, uep->ue_size); uep = nuep; } @@ -806,7 +773,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, buf->b_u_line_ptr, len)) { + if (len > 0 && !undo_write(bi, (char_u *)buf->b_u_line_ptr, len)) { return false; } undo_write_bytes(bi, (uintmax_t)buf->b_u_line_lnum, 4); @@ -895,7 +862,7 @@ static bool serialize_uhp(bufinfo_T *bi, u_header_T *uhp) static u_header_T *unserialize_uhp(bufinfo_T *bi, const char *file_name) { u_header_T *uhp = xmalloc(sizeof(u_header_T)); - memset(uhp, 0, sizeof(u_header_T)); + CLEAR_POINTER(uhp); #ifdef U_DEBUG uhp->uh_magic = UH_MAGIC; #endif @@ -1016,23 +983,21 @@ static bool serialize_extmark(bufinfo_T *bi, ExtmarkUndoObject extup) static ExtmarkUndoObject *unserialize_extmark(bufinfo_T *bi, bool *error, const char *filename) { - UndoObjectType type; uint8_t *buf = NULL; - size_t n_elems; ExtmarkUndoObject *extup = xmalloc(sizeof(ExtmarkUndoObject)); - type = (UndoObjectType)undo_read_4c(bi); + UndoObjectType type = (UndoObjectType)undo_read_4c(bi); extup->type = type; if (type == kExtmarkSplice) { - n_elems = (size_t)sizeof(ExtmarkSplice) / sizeof(uint8_t); + size_t n_elems = (size_t)sizeof(ExtmarkSplice) / sizeof(uint8_t); buf = xcalloc(n_elems, sizeof(uint8_t)); if (!undo_read(bi, buf, n_elems)) { goto error; } extup->data.splice = *(ExtmarkSplice *)buf; } else if (type == kExtmarkMove) { - n_elems = (size_t)sizeof(ExtmarkMove) / sizeof(uint8_t); + size_t n_elems = (size_t)sizeof(ExtmarkMove) / sizeof(uint8_t); buf = xcalloc(n_elems, sizeof(uint8_t)); if (!undo_read(bi, buf, n_elems)) { goto error; @@ -1083,7 +1048,7 @@ static bool serialize_uep(bufinfo_T *bi, u_entry_T *uep) static u_entry_T *unserialize_uep(bufinfo_T *bi, bool *error, const char *file_name) { u_entry_T *uep = xmalloc(sizeof(u_entry_T)); - memset(uep, 0, sizeof(u_entry_T)); + CLEAR_POINTER(uep); #ifdef U_DEBUG uep->ue_magic = UE_MAGIC; #endif @@ -1173,17 +1138,12 @@ static void unserialize_visualinfo(bufinfo_T *bi, visualinfo_T *info) void u_write_undo(const char *const name, const bool forceit, buf_T *const buf, char_u *const hash) FUNC_ATTR_NONNULL_ARG(3, 4) { - u_header_T *uhp; char *file_name; - int mark; #ifdef U_DEBUG int headers_written = 0; #endif - int fd; FILE *fp = NULL; - int perm; bool write_ok = false; - bufinfo_T bi; if (name == NULL) { file_name = u_get_undo_file_name(buf->b_ffname, false); @@ -1199,12 +1159,10 @@ void u_write_undo(const char *const name, const bool forceit, buf_T *const buf, file_name = (char *)name; } - /* - * Decide about the permission to use for the undo file. If the buffer - * has a name use the permission of the original file. Otherwise only - * allow the user to access the undo file. - */ - perm = 0600; + // Decide about the permission to use for the undo file. If the buffer + // has a name use the permission of the original file. Otherwise only + // allow the user to access the undo file. + int perm = 0600; if (buf->b_ffname != NULL) { perm = os_getperm((const char *)buf->b_ffname); if (perm < 0) { @@ -1215,6 +1173,8 @@ void u_write_undo(const char *const name, const bool forceit, buf_T *const buf, // Strip any sticky and executable bits. perm = perm & 0666; + int fd; + // If the undo file already exists, verify that it actually is an undo // file, and delete it. if (os_path_exists((char_u *)file_name)) { @@ -1279,15 +1239,13 @@ void u_write_undo(const char *const name, const bool forceit, buf_T *const buf, #ifdef U_DEBUG // Check there is no problem in undo info before writing. - u_check(FALSE); + u_check(false); #endif #ifdef UNIX - /* - * Try to set the group of the undo file same as the original file. If - * this fails, set the protection bits for the group same as the - * protection bits for others. - */ + // Try to set the group of the undo file same as the original file. If + // this fails, set the protection bits for the group same as the + // protection bits for others. FileInfo file_info_old; FileInfo file_info_new; if (buf->b_ffname != NULL @@ -1310,26 +1268,24 @@ void u_write_undo(const char *const name, const bool forceit, buf_T *const buf, // Undo must be synced. u_sync(true); - /* - * Write the header. - */ - bi.bi_buf = buf; - bi.bi_fp = fp; + // Write the header. + bufinfo_T bi = { + .bi_buf = buf, + .bi_fp = fp, + }; if (!serialize_header(&bi, hash)) { goto write_error; } - /* - * Iteratively serialize UHPs and their UEPs from the top down. - */ - mark = ++lastmark; - uhp = buf->b_u_oldhead; + // Iteratively serialize UHPs and their UEPs from the top down. + int mark = ++lastmark; + u_header_T *uhp = buf->b_u_oldhead; while (uhp != NULL) { // Serialize current UHP if we haven't seen it if (uhp->uh_walk != mark) { uhp->uh_walk = mark; #ifdef U_DEBUG - ++headers_written; + headers_written++; #endif if (!serialize_uhp(&bi, uhp)) { goto write_error; @@ -1437,9 +1393,10 @@ void u_read_undo(char *name, const char_u *hash, const char_u *orig_name FUNC_AT goto error; } - bufinfo_T bi; - bi.bi_buf = curbuf; - bi.bi_fp = fp; + bufinfo_T bi = { + .bi_buf = curbuf, + .bi_fp = fp, + }; // Read the undo file header. char_u magic_buf[UF_START_MAGIC_LEN]; @@ -1568,7 +1525,7 @@ void u_read_undo(char *name, const char_u *hash, const char_u *orig_name FUNC_AT // We have put all of the headers into a table. Now we iterate through the // table and swizzle each sequence number we have stored in uh_*_seq into // a pointer corresponding to the header with that sequence number. - short old_idx = -1, new_idx = -1, cur_idx = -1; + int16_t old_idx = -1, new_idx = -1, cur_idx = -1; for (int i = 0; i < num_head; i++) { u_header_T *uhp = uhp_table[i]; if (uhp == NULL) { @@ -1614,18 +1571,18 @@ void u_read_undo(char *name, const char_u *hash, const char_u *orig_name FUNC_AT } } if (old_header_seq > 0 && old_idx < 0 && uhp->uh_seq == old_header_seq) { - assert(i <= SHRT_MAX); - old_idx = (short)i; + assert(i <= INT16_MAX); + old_idx = (int16_t)i; SET_FLAG(i); } if (new_header_seq > 0 && new_idx < 0 && uhp->uh_seq == new_header_seq) { - assert(i <= SHRT_MAX); - new_idx = (short)i; + assert(i <= INT16_MAX); + new_idx = (int16_t)i; SET_FLAG(i); } if (cur_header_seq > 0 && cur_idx < 0 && uhp->uh_seq == cur_header_seq) { - assert(i <= SHRT_MAX); - cur_idx = (short)i; + assert(i <= INT16_MAX); + cur_idx = (int16_t)i; SET_FLAG(i); } } @@ -1636,7 +1593,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 = line_ptr; + curbuf->b_u_line_ptr = (char *)line_ptr; curbuf->b_u_line_lnum = line_lnum; curbuf->b_u_line_colnr = line_colnr; curbuf->b_u_numhead = num_head; @@ -1656,7 +1613,7 @@ void u_read_undo(char *name, const char_u *hash, const char_u *orig_name FUNC_AT } } xfree(uhp_table_used); - u_check(TRUE); + u_check(true); #endif if (name != NULL) { @@ -1781,17 +1738,13 @@ static uint8_t *undo_read_string(bufinfo_T *bi, size_t len) return ptr; } -/* - * If 'cpoptions' contains 'u': Undo the previous undo or redo (vi compatible). - * If 'cpoptions' does not contain 'u': Always undo. - */ +/// If 'cpoptions' contains 'u': Undo the previous undo or redo (vi compatible). +/// If 'cpoptions' does not contain 'u': Always undo. void u_undo(int count) { - /* - * If we get an undo command while executing a macro, we behave like the - * original vi. If this happens twice in one macro the result will not - * be compatible. - */ + // If we get an undo command while executing a macro, we behave like the + // original vi. If this happens twice in one macro the result will not + // be compatible. if (curbuf->b_u_synced == false) { u_sync(true); count = 1; @@ -1805,10 +1758,8 @@ void u_undo(int count) u_doit(count, false, true); } -/* - * If 'cpoptions' contains 'u': Repeat the previous undo or redo. - * If 'cpoptions' does not contain 'u': Always redo. - */ +/// If 'cpoptions' contains 'u': Repeat the previous undo or redo. +/// If 'cpoptions' does not contain 'u': Always redo. void u_redo(int count) { if (vim_strchr(p_cpo, CPO_UNDO) == NULL) { @@ -1870,8 +1821,6 @@ bool u_undo_and_forget(int count) /// @param do_buf_event If `true`, send the changedtick with the buffer updates static void u_doit(int startcount, bool quiet, bool do_buf_event) { - int count = startcount; - if (!undo_allowed(curbuf)) { return; } @@ -1881,6 +1830,8 @@ static void u_doit(int startcount, bool quiet, bool do_buf_event) if (curbuf->b_ml.ml_flags & ML_EMPTY) { u_oldcount = -1; } + + int count = startcount; while (count--) { // Do the change warning now, so that it triggers FileChangedRO when // needed. This may cause the file to be reloaded, that must happen @@ -1940,21 +1891,6 @@ static void u_doit(int startcount, bool quiet, bool do_buf_event) // "sec" must be false then. void undo_time(long step, bool sec, bool file, bool absolute) { - long target; - long closest; - long closest_start; - long closest_seq = 0; - long val; - u_header_T *uhp = NULL; - u_header_T *last; - int mark; - int nomark = 0; // shut up compiler - int round; - bool dosec = sec; - bool dofile = file; - bool above = false; - bool did_undo = true; - if (text_locked()) { text_locked_msg(); return; @@ -1971,6 +1907,14 @@ void undo_time(long step, bool sec, bool file, bool absolute) u_oldcount = -1; } + long target; + long closest; + u_header_T *uhp = NULL; + bool dosec = sec; + bool dofile = file; + bool above = false; + bool did_undo = true; + // "target" is the node below which we want to be. // Init "closest" to a value we can't reach. if (absolute) { @@ -2034,8 +1978,10 @@ void undo_time(long step, bool sec, bool file, bool absolute) } } } - closest_start = closest; - closest_seq = curbuf->b_u_seq_cur; + long closest_start = closest; + long closest_seq = curbuf->b_u_seq_cur; + int mark; + int nomark = 0; // shut up compiler // When "target" is 0; Back to origin. if (target == 0) { @@ -2043,15 +1989,13 @@ void undo_time(long step, bool sec, bool file, bool absolute) goto target_zero; } - /* - * May do this twice: - * 1. Search for "target", update "closest" to the best match found. - * 2. If "target" not found search for "closest". - * - * When using the closest time we use the sequence number in the second - * round, because there may be several entries with the same time. - */ - for (round = 1; round <= 2; round++) { + // May do this twice: + // 1. Search for "target", update "closest" to the best match found. + // 2. If "target" not found search for "closest". + // + // When using the closest time we use the sequence number in the second + // round, because there may be several entries with the same time. + for (int round = 1; round <= 2; round++) { // Find the path from the current state to where we want to go. The // desired state can be anywhere in the undo tree, need to go all over // it. We put "nomark" in uh_walk where we have been without success, @@ -2067,13 +2011,9 @@ void undo_time(long step, bool sec, bool file, bool absolute) while (uhp != NULL) { uhp->uh_walk = mark; - if (dosec) { - val = (long)(uhp->uh_time); - } else if (dofile) { - val = uhp->uh_save_nr; - } else { - val = uhp->uh_seq; - } + long val = dosec ? (long)(uhp->uh_time) : + dofile ? uhp->uh_save_nr + : uhp->uh_seq; if (round == 1 && !(dofile && val == 0)) { // Remember the header that is closest to the target. @@ -2081,17 +2021,17 @@ void undo_time(long step, bool sec, bool file, bool absolute) // "b_u_seq_cur"). When the timestamp is equal find the // highest/lowest sequence number. if ((step < 0 ? uhp->uh_seq <= curbuf->b_u_seq_cur - : uhp->uh_seq > curbuf->b_u_seq_cur) + : uhp->uh_seq > curbuf->b_u_seq_cur) && ((dosec && val == closest) ? (step < 0 ? uhp->uh_seq < closest_seq : uhp->uh_seq > closest_seq) - : closest == closest_start + : closest == closest_start || (val > target ? (closest > target ? val - target <= closest - target : val - target <= target - closest) - : (closest > target + : (closest > target ? target - val <= closest - target : target - val <= target - closest)))) { closest = val; @@ -2110,11 +2050,10 @@ void undo_time(long step, bool sec, bool file, bool absolute) if (uhp->uh_prev.ptr != NULL && uhp->uh_prev.ptr->uh_walk != nomark && uhp->uh_prev.ptr->uh_walk != mark) { uhp = uhp->uh_prev.ptr; - } - // go to alternate branch if we haven't been there - else if (uhp->uh_alt_next.ptr != NULL - && uhp->uh_alt_next.ptr->uh_walk != nomark - && uhp->uh_alt_next.ptr->uh_walk != mark) { + } else if (uhp->uh_alt_next.ptr != NULL + && uhp->uh_alt_next.ptr->uh_walk != nomark + && uhp->uh_alt_next.ptr->uh_walk != mark) { + // go to alternate branch if we haven't been there uhp = uhp->uh_alt_next.ptr; } else if (uhp->uh_next.ptr != NULL && uhp->uh_alt_prev.ptr == NULL // go up in the tree if we haven't been there and we are at the @@ -2208,7 +2147,7 @@ target_zero: } // Find the last branch with a mark, that's the one. - last = uhp; + u_header_T *last = uhp; while (last->uh_alt_next.ptr != NULL && last->uh_alt_next.ptr->uh_walk == mark) { last = last->uh_alt_next.ptr; @@ -2285,19 +2224,10 @@ target_zero: static void u_undoredo(int undo, bool do_buf_event) { char_u **newarray = NULL; - linenr_T oldsize; - linenr_T newsize; - linenr_T top, bot; - linenr_T lnum; linenr_T newlnum = MAXLNUM; - long i; - u_entry_T *uep, *nuep; + u_entry_T *nuep; u_entry_T *newlist = NULL; - int old_flags; - int new_flags; fmark_T namedm[NMARKS]; - visualinfo_T visualinfo; - bool empty_buffer; // buffer became empty u_header_T *curhead = curbuf->b_u_curhead; // Don't want autocommands using the undo structures here, they are @@ -2305,28 +2235,26 @@ static void u_undoredo(int undo, bool do_buf_event) block_autocmds(); #ifdef U_DEBUG - u_check(FALSE); + u_check(false); #endif - old_flags = curhead->uh_flags; - new_flags = (curbuf->b_changed ? UH_CHANGED : 0) - | ((curbuf->b_ml.ml_flags & ML_EMPTY) ? UH_EMPTYBUF : 0) - | (old_flags & UH_RELOAD); + int old_flags = curhead->uh_flags; + int new_flags = (curbuf->b_changed ? UH_CHANGED : 0) + | ((curbuf->b_ml.ml_flags & ML_EMPTY) ? UH_EMPTYBUF : 0) + | (old_flags & UH_RELOAD); setpcmark(); - /* - * save marks before undo/redo - */ + // save marks before undo/redo zero_fmark_additional_data(curbuf->b_namedm); memmove(namedm, curbuf->b_namedm, sizeof(curbuf->b_namedm[0]) * NMARKS); - visualinfo = curbuf->b_visual; + visualinfo_T visualinfo = curbuf->b_visual; curbuf->b_op_start.lnum = curbuf->b_ml.ml_line_count; curbuf->b_op_start.col = 0; curbuf->b_op_end.lnum = 0; curbuf->b_op_end.col = 0; - for (uep = curhead->uh_entry; uep != NULL; uep = nuep) { - top = uep->ue_top; - bot = uep->ue_bot; + for (u_entry_T *uep = curhead->uh_entry; uep != NULL; uep = nuep) { + linenr_T top = uep->ue_top; + linenr_T bot = uep->ue_bot; if (bot == 0) { bot = curbuf->b_ml.ml_line_count + 1; } @@ -2338,21 +2266,22 @@ static void u_undoredo(int undo, bool do_buf_event) return; } - oldsize = bot - top - 1; // number of lines before undo - newsize = (linenr_T)uep->ue_size; // number of lines after undo + linenr_T oldsize = bot - top - 1; // number of lines before undo + linenr_T newsize = (linenr_T)uep->ue_size; // number of lines after undo if (top < newlnum) { - /* If the saved cursor is somewhere in this undo block, move it to - * the remembered position. Makes "gwap" put the cursor back - * where it was. */ - lnum = curhead->uh_cursor.lnum; + // If the saved cursor is somewhere in this undo block, move it to + // the remembered position. Makes "gwap" put the cursor back + // where it was. + linenr_T lnum = curhead->uh_cursor.lnum; if (lnum >= top && lnum <= top + newsize + 1) { curwin->w_cursor = curhead->uh_cursor; newlnum = curwin->w_cursor.lnum - 1; } else { - /* Use the first line that actually changed. Avoids that - * undoing auto-formatting puts the cursor in the previous - * line. */ + // Use the first line that actually changed. Avoids that + // undoing auto-formatting puts the cursor in the previous + // line. + long i; for (i = 0; i < newsize && i < oldsize; i++) { if (STRCMP(uep->ue_array[i], ml_get(top + 1 + (linenr_T)i)) != 0) { break; @@ -2368,17 +2297,19 @@ static void u_undoredo(int undo, bool do_buf_event) } } - empty_buffer = false; + bool empty_buffer = false; // delete the lines between top and bot and save them in newarray if (oldsize > 0) { newarray = xmalloc(sizeof(char_u *) * (size_t)oldsize); // delete backwards, it goes faster in most cases - for (lnum = bot - 1, i = oldsize; --i >= 0; --lnum) { + long i; + linenr_T lnum; + for (lnum = bot - 1, i = oldsize; --i >= 0; lnum--) { // what can we do when we run out of memory? newarray[i] = u_save_line(lnum); - /* remember we deleted the last line in the buffer, and a - * dummy empty line will be inserted */ + // remember we deleted the last line in the buffer, and a + // dummy empty line will be inserted if (curbuf->b_ml.ml_line_count == 1) { empty_buffer = true; } @@ -2390,11 +2321,11 @@ static void u_undoredo(int undo, bool do_buf_event) // insert the lines in u_array between top and bot if (newsize) { - for (lnum = top, i = 0; i < newsize; ++i, ++lnum) { - /* - * If the file is empty, there is an empty line 1 that we - * should get rid of, by replacing it with the new line - */ + long i; + linenr_T lnum; + for (lnum = top, i = 0; i < newsize; i++, lnum++) { + // If the file is empty, there is an empty line 1 that we + // should get rid of, by replacing it with the new line if (empty_buffer && lnum == 0) { ml_replace((linenr_T)1, (char *)uep->ue_array[i], true); } else { @@ -2435,9 +2366,7 @@ static void u_undoredo(int undo, bool do_buf_event) uep->ue_array = newarray; uep->ue_bot = top + newsize + 1; - /* - * insert this entry in front of the new entry list - */ + // insert this entry in front of the new entry list nuep = uep->ue_next; uep->ue_next = newlist; newlist = uep; @@ -2454,13 +2383,13 @@ static void u_undoredo(int undo, bool do_buf_event) // Adjust Extmarks ExtmarkUndoObject undo_info; if (undo) { - for (i = (int)kv_size(curhead->uh_extmark) - 1; i > -1; i--) { + for (long i = (int)kv_size(curhead->uh_extmark) - 1; i > -1; i--) { undo_info = kv_A(curhead->uh_extmark, i); extmark_apply_undo(undo_info, undo); } // redo } else { - for (i = 0; i < (int)kv_size(curhead->uh_extmark); i++) { + for (long i = 0; i < (int)kv_size(curhead->uh_extmark); i++) { undo_info = kv_A(curhead->uh_extmark, i); extmark_apply_undo(undo_info, undo); } @@ -2490,10 +2419,8 @@ static void u_undoredo(int undo, bool do_buf_event) buf_updates_changedtick(curbuf); } - /* - * restore marks from before undo/redo - */ - for (i = 0; i < NMARKS; ++i) { + // restore marks from before undo/redo + for (long i = 0; i < NMARKS; i++) { if (curhead->uh_namedm[i].mark.lnum != 0) { free_fmark(curbuf->b_namedm[i]); curbuf->b_namedm[i] = curhead->uh_namedm[i]; @@ -2509,14 +2436,12 @@ static void u_undoredo(int undo, bool do_buf_event) curhead->uh_visual = visualinfo; } - /* - * If the cursor is only off by one line, put it at the same position as - * before starting the change (for the "o" command). - * Otherwise the cursor should go to the first undone line. - */ + // If the cursor is only off by one line, put it at the same position as + // before starting the change (for the "o" command). + // Otherwise the cursor should go to the first undone line. if (curhead->uh_cursor.lnum + 1 == curwin->w_cursor.lnum && curwin->w_cursor.lnum > 1) { - --curwin->w_cursor.lnum; + curwin->w_cursor.lnum--; } if (curwin->w_cursor.lnum <= curbuf->b_ml.ml_line_count) { if (curhead->uh_cursor.lnum == curwin->w_cursor.lnum) { @@ -2565,7 +2490,7 @@ static void u_undoredo(int undo, bool do_buf_event) unblock_autocmds(); #ifdef U_DEBUG - u_check(FALSE); + u_check(false); #endif } @@ -2577,10 +2502,6 @@ static void u_undoredo(int undo, bool do_buf_event) /// @param absolute used ":undo N" static void u_undo_end(bool did_undo, bool absolute, bool quiet) { - char *msgstr; - u_header_T *uhp; - char_u msgbuf[80]; - if ((fdo_flags & FDO_UNDO) && KeyTyped) { foldOpenCursor(); } @@ -2592,10 +2513,11 @@ static void u_undo_end(bool did_undo, bool absolute, bool quiet) } if (curbuf->b_ml.ml_flags & ML_EMPTY) { - --u_newcount; + u_newcount--; } u_oldcount -= u_newcount; + char *msgstr; if (u_oldcount == -1) { msgstr = N_("more line"); } else if (u_oldcount < 0) { @@ -2613,6 +2535,7 @@ static void u_undo_end(bool did_undo, bool absolute, bool quiet) } } + u_header_T *uhp; if (curbuf->b_u_curhead != NULL) { // For ":undo N" we prefer a "after #N" message. if (absolute && curbuf->b_u_curhead->uh_next.ptr != NULL) { @@ -2627,6 +2550,7 @@ static void u_undo_end(bool did_undo, bool absolute, bool quiet) uhp = curbuf->b_u_newhead; } + char_u msgbuf[80]; if (uhp == NULL) { *msgbuf = NUL; } else { @@ -2636,7 +2560,7 @@ static void u_undo_end(bool did_undo, bool absolute, bool quiet) { FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { if (wp->w_buffer == curbuf && wp->w_p_cole > 0) { - redraw_later(wp, NOT_VALID); + redraw_later(wp, UPD_NOT_VALID); } } } @@ -2657,9 +2581,8 @@ 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) { - struct tm curtime; - if (time(NULL) - tt >= 100) { + struct tm curtime; os_localtime_r(&tt, &curtime); if (time(NULL) - tt < (60L * 60L * 12L)) { // within 12 hours @@ -2695,27 +2618,20 @@ void u_sync(bool force) } } -/* - * ":undolist": List the leafs of the undo tree - */ +/// ":undolist": List the leafs of the undo tree void ex_undolist(exarg_T *eap) { - garray_T ga; - u_header_T *uhp; - int mark; - int nomark; int changes = 1; - /* - * 1: walk the tree to find all leafs, put the info in "ga". - * 2: sort the lines - * 3: display the list - */ - mark = ++lastmark; - nomark = ++lastmark; + // 1: walk the tree to find all leafs, put the info in "ga". + // 2: sort the lines + // 3: display the list + int mark = ++lastmark; + int nomark = ++lastmark; + garray_T ga; ga_init(&ga, (int)sizeof(char *), 20); - uhp = curbuf->b_u_oldhead; + u_header_T *uhp = curbuf->b_u_oldhead; while (uhp != NULL) { if (uhp->uh_prev.ptr == NULL && uhp->uh_walk != nomark && uhp->uh_walk != mark) { @@ -2736,12 +2652,11 @@ void ex_undolist(exarg_T *eap) if (uhp->uh_prev.ptr != NULL && uhp->uh_prev.ptr->uh_walk != nomark && uhp->uh_prev.ptr->uh_walk != mark) { uhp = uhp->uh_prev.ptr; - ++changes; - } - // go to alternate branch if we haven't been there - else if (uhp->uh_alt_next.ptr != NULL - && uhp->uh_alt_next.ptr->uh_walk != nomark - && uhp->uh_alt_next.ptr->uh_walk != mark) { + changes++; + } else if (uhp->uh_alt_next.ptr != NULL + && uhp->uh_alt_next.ptr->uh_walk != nomark + && uhp->uh_alt_next.ptr->uh_walk != mark) { + // go to alternate branch if we haven't been there uhp = uhp->uh_alt_next.ptr; } else if (uhp->uh_next.ptr != NULL && uhp->uh_alt_prev.ptr == NULL // go up in the tree if we haven't been there and we are at the @@ -2749,7 +2664,7 @@ void ex_undolist(exarg_T *eap) && uhp->uh_next.ptr->uh_walk != nomark && uhp->uh_next.ptr->uh_walk != mark) { uhp = uhp->uh_next.ptr; - --changes; + changes--; } else { // need to backtrack; mark this node as done uhp->uh_walk = nomark; @@ -2757,7 +2672,7 @@ void ex_undolist(exarg_T *eap) uhp = uhp->uh_alt_prev.ptr; } else { uhp = uhp->uh_next.ptr; - --changes; + changes--; } } } @@ -2783,9 +2698,7 @@ void ex_undolist(exarg_T *eap) } } -/* - * ":undojoin": continue adding to the last entry list - */ +/// ":undojoin": continue adding to the last entry list void ex_undojoin(exarg_T *eap) { if (curbuf->b_u_newhead == NULL) { @@ -2805,35 +2718,30 @@ void ex_undojoin(exarg_T *eap) } } -/* - * Called after writing or reloading the file and setting b_changed to FALSE. - * Now an undo means that the buffer is modified. - */ +/// Called after writing or reloading the file and setting b_changed to false. +/// Now an undo means that the buffer is modified. void u_unchanged(buf_T *buf) { u_unch_branch(buf->b_u_oldhead); buf->b_did_warn = false; } -/* - * After reloading a buffer which was saved for 'undoreload': Find the first - * line that was changed and set the cursor there. - */ +/// After reloading a buffer which was saved for 'undoreload': Find the first +/// line that was changed and set the cursor there. void u_find_first_changed(void) { u_header_T *uhp = curbuf->b_u_newhead; - u_entry_T *uep; - linenr_T lnum; if (curbuf->b_u_curhead != NULL || uhp == NULL) { return; // undid something in an autocmd? } // Check that the last undo block was for the whole file. - uep = uhp->uh_entry; + u_entry_T *uep = uhp->uh_entry; if (uep->ue_top != 0 || uep->ue_bot != 0) { return; } + linenr_T lnum; for (lnum = 1; lnum < curbuf->b_ml.ml_line_count && lnum <= uep->ue_size; lnum++) { if (STRCMP(ml_get_buf(curbuf, lnum, false), uep->ue_array[lnum - 1]) != 0) { @@ -2849,17 +2757,13 @@ void u_find_first_changed(void) } } -/* - * Increase the write count, store it in the last undo header, what would be - * used for "u". - */ +/// Increase the write count, store it in the last undo header, what would be +/// used for "u". void u_update_save_nr(buf_T *buf) { - u_header_T *uhp; - - ++buf->b_u_save_nr_last; + buf->b_u_save_nr_last++; buf->b_u_save_nr_cur = buf->b_u_save_nr_last; - uhp = buf->b_u_curhead; + u_header_T *uhp = buf->b_u_curhead; if (uhp != NULL) { uhp = uhp->uh_next.ptr; } else { @@ -2872,9 +2776,7 @@ void u_update_save_nr(buf_T *buf) static void u_unch_branch(u_header_T *uhp) { - u_header_T *uh; - - for (uh = uhp; uh != NULL; uh = uh->uh_prev.ptr) { + for (u_header_T *uh = uhp; uh != NULL; uh = uh->uh_prev.ptr) { uh->uh_flags |= UH_CHANGED; if (uh->uh_alt_next.ptr != NULL) { u_unch_branch(uh->uh_alt_next.ptr); // recursive @@ -2882,10 +2784,8 @@ static void u_unch_branch(u_header_T *uhp) } } -/* - * Get pointer to last added entry. - * If it's not valid, give an error message and return NULL. - */ +/// Get pointer to last added entry. +/// If it's not valid, give an error message and return NULL. static u_entry_T *u_get_headentry(buf_T *buf) { if (buf->b_u_newhead == NULL || buf->b_u_newhead->uh_entry == NULL) { @@ -2895,28 +2795,21 @@ static u_entry_T *u_get_headentry(buf_T *buf) return buf->b_u_newhead->uh_entry; } -/* - * u_getbot(): compute the line number of the previous u_save - * It is called only when b_u_synced is false. - */ +/// u_getbot(): compute the line number of the previous u_save +/// It is called only when b_u_synced is false. static void u_getbot(buf_T *buf) { - u_entry_T *uep; - linenr_T extra; - - uep = u_get_headentry(buf); // check for corrupt undo list + u_entry_T *uep = u_get_headentry(buf); // check for corrupt undo list if (uep == NULL) { return; } uep = buf->b_u_newhead->uh_getbot_entry; if (uep != NULL) { - /* - * the new ue_bot is computed from the number of lines that has been - * inserted (0 - deleted) since calling u_save. This is equal to the - * old line count subtracted from the current line count. - */ - extra = buf->b_ml.ml_line_count - uep->ue_lcount; + // the new ue_bot is computed from the number of lines that has been + // inserted (0 - deleted) since calling u_save. This is equal to the + // old line count subtracted from the current line count. + linenr_T extra = buf->b_ml.ml_line_count - uep->ue_lcount; uep->ue_bot = uep->ue_top + (linenr_T)uep->ue_size + 1 + extra; if (uep->ue_bot < 1 || uep->ue_bot > buf->b_ml.ml_line_count) { iemsg(_("E440: undo line missing")); @@ -2937,8 +2830,6 @@ static void u_getbot(buf_T *buf) /// @param uhpp if not NULL reset when freeing this header static void u_freeheader(buf_T *buf, u_header_T *uhp, u_header_T **uhpp) { - u_header_T *uhap; - // When there is an alternate redo list free that branch completely, // because we can never go there. if (uhp->uh_alt_next.ptr != NULL) { @@ -2959,7 +2850,7 @@ static void u_freeheader(buf_T *buf, u_header_T *uhp, u_header_T **uhpp) if (uhp->uh_prev.ptr == NULL) { buf->b_u_newhead = uhp->uh_next.ptr; } else { - for (uhap = uhp->uh_prev.ptr; uhap != NULL; + for (u_header_T *uhap = uhp->uh_prev.ptr; uhap != NULL; uhap = uhap->uh_alt_next.ptr) { uhap->uh_next.ptr = uhp->uh_next.ptr; } @@ -2973,8 +2864,6 @@ static void u_freeheader(buf_T *buf, u_header_T *uhp, u_header_T **uhpp) /// @param uhpp if not NULL reset when freeing this header static void u_freebranch(buf_T *buf, u_header_T *uhp, u_header_T **uhpp) { - u_header_T *tofree, *next; - // If this is the top branch we may need to use u_freeheader() to update // all the pointers. if (uhp == buf->b_u_oldhead) { @@ -2988,9 +2877,9 @@ static void u_freebranch(buf_T *buf, u_header_T *uhp, u_header_T **uhpp) uhp->uh_alt_prev.ptr->uh_alt_next.ptr = NULL; } - next = uhp; + u_header_T *next = uhp; while (next != NULL) { - tofree = next; + u_header_T *tofree = next; if (tofree->uh_alt_next.ptr != NULL) { u_freebranch(buf, tofree->uh_alt_next.ptr, uhpp); // recursive } @@ -3029,12 +2918,10 @@ static void u_freeentries(buf_T *buf, u_header_T *uhp, u_header_T **uhpp) uhp->uh_magic = 0; #endif xfree((char_u *)uhp); - --buf->b_u_numhead; + buf->b_u_numhead--; } -/* - * free entry 'uep' and 'n' lines in uep->ue_array[] - */ +/// free entry 'uep' and 'n' lines in uep->ue_array[] static void u_freeentry(u_entry_T *uep, long n) { while (n > 0) { @@ -3047,9 +2934,7 @@ static void u_freeentry(u_entry_T *uep, long n) xfree((char_u *)uep); } -/* - * invalidate the undo buffer; called when storage has already been released - */ +/// invalidate the undo buffer; called when storage has already been released void u_clearall(buf_T *buf) { buf->b_u_newhead = buf->b_u_oldhead = buf->b_u_curhead = NULL; @@ -3059,9 +2944,7 @@ void u_clearall(buf_T *buf) buf->b_u_line_lnum = 0; } -/* - * save the line "lnum" for the "U" command - */ +/// save the line "lnum" for the "U" command void u_saveline(linenr_T lnum) { if (lnum == curbuf->b_u_line_lnum) { // line is already saved @@ -3077,13 +2960,11 @@ void u_saveline(linenr_T lnum) } else { curbuf->b_u_line_colnr = 0; } - curbuf->b_u_line_ptr = u_save_line(lnum); + curbuf->b_u_line_ptr = (char *)u_save_line(lnum); } -/* - * clear the line saved for the "U" command - * (this is used externally for crossing a line while in insert mode) - */ +/// clear the line saved for the "U" command +/// (this is used externally for crossing a line while in insert mode) void u_clearline(void) { if (curbuf->b_u_line_ptr != NULL) { @@ -3092,17 +2973,12 @@ void u_clearline(void) } } -/* - * Implementation of the "U" command. - * Differentiation from vi: "U" can be undone with the next "U". - * We also allow the cursor to be in another line. - * Careful: may trigger autocommands that reload the buffer. - */ +/// Implementation of the "U" command. +/// Differentiation from vi: "U" can be undone with the next "U". +/// We also allow the cursor to be in another line. +/// Careful: may trigger autocommands that reload the buffer. void u_undoline(void) { - colnr_T t; - char_u *oldp; - if (curbuf->b_u_line_ptr == NULL || curbuf->b_u_line_lnum > curbuf->b_ml.ml_line_count) { beep_flush(); @@ -3115,15 +2991,15 @@ void u_undoline(void) return; } - oldp = u_save_line(curbuf->b_u_line_lnum); - ml_replace(curbuf->b_u_line_lnum, (char *)curbuf->b_u_line_ptr, true); + char_u *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), (colnr_T)STRLEN(curbuf->b_u_line_ptr), kExtmarkUndo); xfree(curbuf->b_u_line_ptr); - curbuf->b_u_line_ptr = oldp; + curbuf->b_u_line_ptr = (char *)oldp; - t = curbuf->b_u_line_colnr; + colnr_T t = curbuf->b_u_line_colnr; if (curwin->w_cursor.lnum == curbuf->b_u_line_lnum) { curbuf->b_u_line_colnr = curwin->w_cursor.col; } @@ -3132,9 +3008,7 @@ void u_undoline(void) check_cursor_col(); } -/* - * Free all allocated memory blocks for the buffer 'buf'. - */ +/// Free all allocated memory blocks for the buffer 'buf'. void u_blockfree(buf_T *buf) { while (buf->b_u_oldhead != NULL) { diff --git a/src/nvim/undo_defs.h b/src/nvim/undo_defs.h index d8470b07b1..4b64f97919 100644 --- a/src/nvim/undo_defs.h +++ b/src/nvim/undo_defs.h @@ -74,10 +74,4 @@ struct u_header { #define UH_EMPTYBUF 0x02 // buffer was empty #define UH_RELOAD 0x04 // buffer was reloaded -/// Structure passed around between undofile functions. -typedef struct { - buf_T *bi_buf; - FILE *bi_fp; -} bufinfo_T; - #endif // NVIM_UNDO_DEFS_H diff --git a/src/nvim/usercmd.c b/src/nvim/usercmd.c index 15197dc504..5b2101587f 100644 --- a/src/nvim/usercmd.c +++ b/src/nvim/usercmd.c @@ -12,10 +12,12 @@ #include "nvim/api/private/helpers.h" #include "nvim/ascii.h" #include "nvim/charset.h" +#include "nvim/eval.h" #include "nvim/ex_docmd.h" #include "nvim/garray.h" #include "nvim/lua/executor.h" #include "nvim/os/input.h" +#include "nvim/runtime.h" #include "nvim/usercmd.h" #include "nvim/window.h" @@ -104,7 +106,7 @@ static struct { /// Return NULL if there is no matching command. /// /// @param *p end of the command (possibly including count) -/// @param full set to TRUE for a full match +/// @param full set to true for a full match /// @param xp used for completion, NULL otherwise /// @param complp completion flags or NULL char *find_ucmd(exarg_T *eap, char *p, int *full, expand_T *xp, int *complp) @@ -125,7 +127,7 @@ char *find_ucmd(exarg_T *eap, char *p, int *full, expand_T *xp, int *complp) for (j = 0; j < gap->ga_len; j++) { uc = USER_CMD_GA(gap, j); cp = eap->cmd; - np = (char *)uc->uc_name; + np = uc->uc_name; k = 0; while (k < len && *np != NUL && *cp++ == *np++) { k++; @@ -165,9 +167,9 @@ char *find_ucmd(exarg_T *eap, char *p, int *full, expand_T *xp, int *complp) } if (xp != NULL) { xp->xp_luaref = uc->uc_compl_luaref; - xp->xp_arg = (char *)uc->uc_compl_arg; + xp->xp_arg = uc->uc_compl_arg; xp->xp_script_ctx = uc->uc_script_ctx; - xp->xp_script_ctx.sc_lnum += sourcing_lnum; + xp->xp_script_ctx.sc_lnum += SOURCING_LNUM; } // Do not search for further abbreviations // if this is an exact match. @@ -214,7 +216,7 @@ const char *set_context_in_user_cmd(expand_T *xp, const char *arg_in) // Check for attributes while (*arg == '-') { arg++; // Skip "-". - p = (const char *)skiptowhite((const char_u *)arg); + p = (const char *)skiptowhite(arg); if (*p == NUL) { // Cursor is still in the attribute. p = strchr(arg, '='); @@ -246,7 +248,7 @@ const char *set_context_in_user_cmd(expand_T *xp, const char *arg_in) } // After the attributes comes the new command name. - p = (const char *)skiptowhite((const char_u *)arg); + p = (const char *)skiptowhite(arg); if (*p == NUL) { xp->xp_context = EXPAND_USER_COMMANDS; xp->xp_pattern = (char *)arg; @@ -270,12 +272,12 @@ char *get_user_commands(expand_T *xp FUNC_ATTR_UNUSED, int idx) const buf_T *const buf = prevwin_curwin()->w_buffer; if (idx < buf->b_ucmds.ga_len) { - return (char *)USER_CMD_GA(&buf->b_ucmds, idx)->uc_name; + return USER_CMD_GA(&buf->b_ucmds, idx)->uc_name; } idx -= buf->b_ucmds.ga_len; if (idx < ucmds.ga_len) { - char *name = (char *)USER_CMD(idx)->uc_name; + char *name = USER_CMD(idx)->uc_name; for (int i = 0; i < buf->b_ucmds.ga_len; i++) { if (STRCMP(name, USER_CMD_GA(&buf->b_ucmds, i)->uc_name) == 0) { @@ -295,14 +297,14 @@ char *get_user_commands(expand_T *xp FUNC_ATTR_UNUSED, int idx) char *get_user_command_name(int idx, int cmdidx) { if (cmdidx == CMD_USER && idx < ucmds.ga_len) { - return (char *)USER_CMD(idx)->uc_name; + return USER_CMD(idx)->uc_name; } if (cmdidx == CMD_USER_BUF) { // In cmdwin, the alternative buffer should be used. const buf_T *const buf = prevwin_curwin()->w_buffer; if (idx < buf->b_ucmds.ga_len) { - return (char *)USER_CMD_GA(&buf->b_ucmds, idx)->uc_name; + return USER_CMD_GA(&buf->b_ucmds, idx)->uc_name; } } return NULL; @@ -529,7 +531,7 @@ static void uc_list(char *name, size_t name_len) } } - msg_outtrans_special(cmd->uc_rep, false, + msg_outtrans_special((char_u *)cmd->uc_rep, false, name_len == 0 ? Columns - 47 : 0); if (p_verbose > 0) { last_set_msg(cmd->uc_script_ctx); @@ -881,17 +883,17 @@ int uc_add_command(char *name, size_t name_len, const char *rep, uint32_t argt, gap->ga_len++; - cmd->uc_name = (char_u *)p; + cmd->uc_name = p; } - cmd->uc_rep = (char_u *)rep_buf; + cmd->uc_rep = rep_buf; cmd->uc_argt = argt; cmd->uc_def = def; cmd->uc_compl = compl; cmd->uc_script_ctx = current_sctx; - cmd->uc_script_ctx.sc_lnum += sourcing_lnum; + cmd->uc_script_ctx.sc_lnum += SOURCING_LNUM; nlua_set_sctx(&cmd->uc_script_ctx); - cmd->uc_compl_arg = (char_u *)compl_arg; + cmd->uc_compl_arg = compl_arg; cmd->uc_compl_luaref = compl_luaref; cmd->uc_preview_luaref = preview_luaref; cmd->uc_addr_type = addr_type; @@ -928,7 +930,7 @@ void ex_command(exarg_T *eap) // Check for attributes while (*p == '-') { p++; - end = (char *)skiptowhite((char_u *)p); + end = skiptowhite(p); if (uc_scan_attr(p, (size_t)(end - p), &argt, &def, &flags, &compl, (char_u **)&compl_arg, &addr_type_arg) == FAIL) { return; @@ -1059,11 +1061,11 @@ bool uc_split_args_iter(const char *arg, size_t arglen, size_t *end, char *buf, buf[l++] = arg[++pos]; } else { buf[l++] = arg[pos]; - if (ascii_iswhite(arg[pos + 1])) { - *end = pos + 1; - *len = l; - return false; - } + } + if (ascii_iswhite(arg[pos + 1])) { + *end = pos + 1; + *len = l; + return false; } } @@ -1162,7 +1164,7 @@ static char *uc_split_args(char *arg, char **args, size_t *arglens, size_t argc, *q++ = ' '; *q++ = '"'; } else { - mb_copy_char((const char_u **)&p, (char_u **)&q); + mb_copy_char((const char **)&p, &q); } } } else { @@ -1175,7 +1177,7 @@ static char *uc_split_args(char *arg, char **args, size_t *arglens, size_t argc, *q++ = '\\'; *q++ = *p++; } else { - mb_copy_char((const char_u **)&p, (char_u **)&q); + mb_copy_char((const char **)&p, &q); } } if (i != argc - 1) { @@ -1567,7 +1569,7 @@ int do_ucmd(exarg_T *eap, bool preview) // Second round: copy result into "buf". buf = NULL; for (;;) { - p = (char *)cmd->uc_rep; // source + p = cmd->uc_rep; // source q = buf; // destination totlen = 0; diff --git a/src/nvim/usercmd.h b/src/nvim/usercmd.h index 637862b216..4d2cf0d9de 100644 --- a/src/nvim/usercmd.h +++ b/src/nvim/usercmd.h @@ -4,14 +4,14 @@ #include "nvim/ex_cmds_defs.h" typedef struct ucmd { - char_u *uc_name; // The command name + char *uc_name; // The command name uint32_t uc_argt; // The argument type - char_u *uc_rep; // The command's replacement string + char *uc_rep; // The command's replacement string long uc_def; // The default value for a range/count int uc_compl; // completion type cmd_addr_T uc_addr_type; // The command's address type sctx_T uc_script_ctx; // SCTX where the command was defined - char_u *uc_compl_arg; // completion argument if any + char *uc_compl_arg; // completion argument if any LuaRef uc_compl_luaref; // Reference to Lua completion function LuaRef uc_preview_luaref; // Reference to Lua preview function LuaRef uc_luaref; // Reference to Lua function diff --git a/src/nvim/version.c b/src/nvim/version.c index 3ffae6592c..5888d4614e 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -14,12 +14,13 @@ #include "nvim/ascii.h" #include "nvim/buffer.h" #include "nvim/charset.h" +#include "nvim/drawscreen.h" +#include "nvim/grid.h" #include "nvim/iconv.h" #include "nvim/lua/executor.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/screen.h" #include "nvim/strings.h" #include "nvim/version.h" #include "nvim/vim.h" @@ -2015,7 +2016,7 @@ void ex_version(exarg_T *eap) /// Output a string for the version message. If it's going to wrap, output a /// newline, unless the message is too long to fit on the screen anyway. -/// When "wrap" is TRUE wrap the string in []. +/// When "wrap" is true wrap the string in []. /// @param s /// @param wrap static void version_msg_wrap(char *s, int wrap) @@ -2063,7 +2064,7 @@ static void list_features(void) /// List string items nicely aligned in columns. /// When "size" is < 0 then the last entry is marked with NULL. /// The entry with index "current" is inclosed in []. -void list_in_columns(char_u **items, int size, int current) +void list_in_columns(char **items, int size, int current) { int item_count = 0; int width = 0; @@ -2071,7 +2072,7 @@ void list_in_columns(char_u **items, int size, int current) // Find the length of the longest item, use that + 1 as the column width. int i; for (i = 0; size < 0 ? items[i] != NULL : i < size; i++) { - int l = vim_strsize((char *)items[i]) + (i == current ? 2 : 0); + int l = vim_strsize(items[i]) + (i == current ? 2 : 0); if (l > width) { width = l; @@ -2083,7 +2084,7 @@ void list_in_columns(char_u **items, int size, int current) if (Columns < width) { // Not enough screen columns - show one per line for (i = 0; i < item_count; i++) { - version_msg_wrap((char *)items[i], i == current); + version_msg_wrap(items[i], i == current); if (msg_col > 0 && i < item_count - 1) { msg_putchar('\n'); } @@ -2105,7 +2106,7 @@ void list_in_columns(char_u **items, int size, int current) if (idx == current) { msg_putchar('['); } - msg_puts((char *)items[idx]); + msg_puts(items[idx]); if (idx == current) { msg_putchar(']'); } @@ -2199,7 +2200,7 @@ void maybe_intro_message(void) if (buf_is_empty(curbuf) && (curbuf->b_fname == NULL) && (firstwin->w_next == NULL) - && (vim_strchr((char *)p_shm, SHM_INTRO) == NULL)) { + && (vim_strchr(p_shm, SHM_INTRO) == NULL)) { intro_message(false); } } @@ -2208,7 +2209,7 @@ void maybe_intro_message(void) /// Only used when starting Vim on an empty file, without a file name. /// Or with the ":intro" command (for Sven :-). /// -/// @param colon TRUE for ":intro" +/// @param colon true for ":intro" void intro_message(int colon) { int i; @@ -2255,7 +2256,7 @@ void intro_message(int colon) row = blanklines / 2; if (((row >= 2) && (Columns >= 50)) || colon) { - for (i = 0; i < (int)ARRAY_SIZE(lines); ++i) { + for (i = 0; i < (int)ARRAY_SIZE(lines); i++) { p = lines[i]; if (sponsor != 0) { @@ -2273,7 +2274,7 @@ void intro_message(int colon) } if (*p != NUL) { - do_intro_line(row, (char_u *)_(p), 0); + do_intro_line(row, _(p), 0); } row++; } @@ -2286,15 +2287,14 @@ void intro_message(int colon) } } -static void do_intro_line(long row, char_u *mesg, int attr) +static void do_intro_line(long row, char *mesg, int attr) { - long col; - char_u *p; + char *p; int l; int clen; // Center the message horizontally. - col = vim_strsize((char *)mesg); + long col = vim_strsize(mesg); col = (Columns - col) / 2; @@ -2309,8 +2309,8 @@ static void do_intro_line(long row, char_u *mesg, int attr) for (l = 0; p[l] != NUL && (l == 0 || (p[l] != '<' && p[l - 1] != '>')); l++) { - clen += ptr2cells((char *)p + l); - l += utfc_ptr2len((char *)p + l) - 1; + clen += ptr2cells(p + l); + l += utfc_ptr2len(p + l) - 1; } assert(row <= INT_MAX && col <= INT_MAX); grid_puts_len(&default_grid, p, l, (int)row, (int)col, @@ -2325,6 +2325,6 @@ static void do_intro_line(long row, char_u *mesg, int attr) void ex_intro(exarg_T *eap) { screenclear(); - intro_message(TRUE); - wait_return(TRUE); + intro_message(true); + wait_return(true); } diff --git a/src/nvim/vim.h b/src/nvim/vim.h index 31ac5a67ff..ec8d6ab153 100644 --- a/src/nvim/vim.h +++ b/src/nvim/vim.h @@ -167,15 +167,6 @@ enum { #define MIN_SWAP_PAGE_SIZE 1048 #define MAX_SWAP_PAGE_SIZE 50000 -// Boolean constants - -#ifndef TRUE -# define FALSE 0 // note: this is an int, not a long! -# define TRUE 1 -#endif - -#define MAYBE 2 // sometimes used for a variant on TRUE - #define STATUS_HEIGHT 1 // height of a status line under a window #define QF_WINHEIGHT 10 // default height for quickfix window @@ -199,6 +190,7 @@ enum { FOLD_TEXT_LEN = 51, }; //!< buffer size for get_foldtext() // Size in bytes of the hash used in the undo file. #define UNDO_HASH_SIZE 32 +#define CLEAR_FIELD(field) memset(&(field), 0, sizeof(field)) #define CLEAR_POINTER(ptr) memset((ptr), 0, sizeof(*(ptr))) // defines to avoid typecasts from (char_u *) to (char *) and back @@ -238,10 +230,7 @@ enum { FOLD_TEXT_LEN = 51, }; //!< buffer size for get_foldtext() # endif #endif -#define STRRCHR(s, c) (char_u *)strrchr((const char *)(s), (c)) - -#define STRCAT(d, s) strcat((char *)(d), (char *)(s)) -#define STRNCAT(d, s, n) strncat((char *)(d), (char *)(s), (size_t)(n)) +#define STRCAT(d, s) strcat((char *)(d), (char *)(s)) // NOLINT(runtime/printf) #define STRLCAT(d, s, n) xstrlcat((char *)(d), (char *)(s), (size_t)(n)) // Character used as separated in autoload function/variable names. @@ -274,8 +263,8 @@ enum { FOLD_TEXT_LEN = 51, }; //!< buffer size for get_foldtext() (const char *)(y), \ (size_t)(n)) -// Enums need a typecast to be used as array index (for Ultrix). -#define HL_ATTR(n) highlight_attr[(int)(n)] +// Enums need a typecast to be used as array index. +#define HL_ATTR(n) hl_attr_active[(int)(n)] /// Maximum number of bytes in a multi-byte character. It can be one 32-bit /// character of up to 6 bytes, or one 16-bit character of up to three bytes diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index fd7dc17ee3..387b9d61f2 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -1828,13 +1828,13 @@ static void parse_quoted_string(ParserState *const pstate, ExprASTNode *const no v_p += special_len; } else { is_unknown = true; - mb_copy_char((const char_u **)&p, (char_u **)&v_p); + mb_copy_char(&p, &v_p); } break; } default: is_unknown = true; - mb_copy_char((const char_u **)&p, (char_u **)&v_p); + mb_copy_char(&p, &v_p); break; } if (pstate->colors) { diff --git a/src/nvim/viml/parser/parser.h b/src/nvim/viml/parser/parser.h index b8835127e7..55f54cedbe 100644 --- a/src/nvim/viml/parser/parser.h +++ b/src/nvim/viml/parser/parser.h @@ -142,9 +142,7 @@ static inline void viml_preader_get_line(ParserInputReader *const preader, .allocated = true, .size = pline.size, }; - cpline.data = (char *)string_convert(&preader->conv, - (char_u *)pline.data, - &cpline.size); + cpline.data = string_convert(&preader->conv, (char *)pline.data, &cpline.size); if (pline.allocated) { xfree((void *)pline.data); } diff --git a/src/nvim/window.c b/src/nvim/window.c index b737215616..74ea1172ee 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -6,11 +6,14 @@ #include <stdbool.h> #include "nvim/api/private/helpers.h" +#include "nvim/api/vim.h" +#include "nvim/arglist.h" #include "nvim/ascii.h" #include "nvim/buffer.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/vars.h" @@ -25,7 +28,9 @@ #include "nvim/garray.h" #include "nvim/getchar.h" #include "nvim/globals.h" +#include "nvim/grid.h" #include "nvim/hashtab.h" +#include "nvim/highlight.h" #include "nvim/main.h" #include "nvim/mapping.h" #include "nvim/mark.h" @@ -37,13 +42,13 @@ #include "nvim/move.h" #include "nvim/normal.h" #include "nvim/option.h" +#include "nvim/optionstr.h" #include "nvim/os/os.h" #include "nvim/os_unix.h" #include "nvim/path.h" #include "nvim/plines.h" #include "nvim/quickfix.h" #include "nvim/regexp.h" -#include "nvim/screen.h" #include "nvim/search.h" #include "nvim/state.h" #include "nvim/strings.h" @@ -122,7 +127,7 @@ void do_window(int nchar, long Prenum, int xchar) { long Prenum1; win_T *wp; - char_u *ptr; + char *ptr; linenr_T lnum = -1; int type = FIND_DEFINE; size_t len; @@ -210,7 +215,7 @@ newwindow: case Ctrl_Q: case 'q': reset_VIsual_and_resel(); // stop Visual mode - cmd_with_count("quit", (char_u *)cbuf, sizeof(cbuf), Prenum); + cmd_with_count("quit", cbuf, sizeof(cbuf), Prenum); do_cmdline_cmd(cbuf); break; @@ -218,7 +223,7 @@ newwindow: case Ctrl_C: case 'c': reset_VIsual_and_resel(); // stop Visual mode - cmd_with_count("close", (char_u *)cbuf, sizeof(cbuf), Prenum); + cmd_with_count("close", cbuf, sizeof(cbuf), Prenum); do_cmdline_cmd(cbuf); break; @@ -251,7 +256,7 @@ newwindow: case 'o': CHECK_CMDWIN; reset_VIsual_and_resel(); // stop Visual mode - cmd_with_count("only", (char_u *)cbuf, sizeof(cbuf), Prenum); + cmd_with_count("only", cbuf, sizeof(cbuf), Prenum); do_cmdline_cmd(cbuf); break; @@ -483,14 +488,14 @@ newwindow: wingotofile: CHECK_CMDWIN; - ptr = grab_file_name(Prenum1, &lnum); + ptr = (char *)grab_file_name(Prenum1, &lnum); if (ptr != NULL) { tabpage_T *oldtab = curtab; win_T *oldwin = curwin; setpcmark(); if (win_split(0, 0) == OK) { RESET_BINDING(curwin); - if (do_ecmd(0, (char *)ptr, NULL, NULL, ECMD_LASTL, ECMD_HIDE, NULL) == FAIL) { + if (do_ecmd(0, ptr, NULL, NULL, ECMD_LASTL, ECMD_HIDE, NULL) == FAIL) { // Failed to open the file, close the window opened for it. win_close(curwin, false, false); goto_tabpage_win(oldtab, oldwin); @@ -518,9 +523,9 @@ wingotofile: } // Make a copy, if the line was changed it will be freed. - ptr = vim_strnsave(ptr, len); + ptr = xstrnsave(ptr, len); - find_pattern_in_path(ptr, 0, len, true, Prenum == 0, + find_pattern_in_path((char_u *)ptr, 0, len, true, Prenum == 0, type, Prenum1, ACTION_SPLIT, 1, MAXLNUM); xfree(ptr); curwin->w_set_curswant = true; @@ -617,12 +622,12 @@ wingotofile: } } -static void cmd_with_count(char *cmd, char_u *bufp, size_t bufsize, int64_t Prenum) +static void cmd_with_count(char *cmd, char *bufp, size_t bufsize, int64_t Prenum) { size_t len = STRLCPY(bufp, cmd, bufsize); if (Prenum > 0 && len < bufsize) { - vim_snprintf((char *)bufp + len, bufsize - len, "%" PRId64, Prenum); + vim_snprintf(bufp + len, bufsize - len, "%" PRId64, Prenum); } } @@ -698,16 +703,16 @@ win_T *win_new_float(win_T *wp, bool last, FloatConfig fconfig, Error *err) win_remove(wp, NULL); win_append(lastwin_nofloating(), wp); } - wp->w_floating = 1; + wp->w_floating = true; wp->w_status_height = 0; wp->w_winbar_height = 0; wp->w_hsep_height = 0; wp->w_vsep_width = 0; win_config_float(wp, fconfig); - win_set_inner_size(wp); + win_set_inner_size(wp, true); wp->w_pos_changed = true; - redraw_later(wp, VALID); + redraw_later(wp, UPD_VALID); return wp; } @@ -722,36 +727,38 @@ 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 = wp->w_p_fcs; + char_u *old = (char_u *)wp->w_p_fcs; wp->w_p_fcs = ((*old == NUL) - ? (char_u *)xstrdup("eob: ") - : concat_str(old, (char_u *)",eob: ")); - free_string_option(old); - } - if (wp->w_hl_ids[HLF_EOB] != -1) { - char_u *old = wp->w_p_winhl; - wp->w_p_winhl = ((*old == NUL) - ? (char_u *)xstrdup("EndOfBuffer:") - : concat_str(old, (char_u *)",EndOfBuffer:")); - free_string_option(old); + ? xstrdup("eob: ") + : concat_str((char *)old, ",eob: ")); + free_string_option((char *)old); } + // TODO(bfredl): this could use a highlight namespace directly, + // and avoid pecularities around window options + char_u *old = (char_u *)wp->w_p_winhl; + wp->w_p_winhl = ((*old == NUL) + ? xstrdup("EndOfBuffer:") + : concat_str((char *)old, ",EndOfBuffer:")); + free_string_option((char *)old); + parse_winhl_opt(wp); + // signcolumn: use 'auto' if (wp->w_p_scl[0] != 'a' || STRLEN(wp->w_p_scl) >= 8) { free_string_option(wp->w_p_scl); - wp->w_p_scl = (char_u *)xstrdup("auto"); + wp->w_p_scl = xstrdup("auto"); } // foldcolumn: use '0' if (wp->w_p_fdc[0] != '0') { free_string_option(wp->w_p_fdc); - wp->w_p_fdc = (char_u *)xstrdup("0"); + wp->w_p_fdc = xstrdup("0"); } // colorcolumn: cleared if (wp->w_p_cc != NULL && *wp->w_p_cc != NUL) { free_string_option(wp->w_p_cc); - wp->w_p_cc = (char_u *)xstrdup(""); + wp->w_p_cc = xstrdup(""); } } @@ -789,13 +796,13 @@ void win_config_float(win_T *wp, FloatConfig fconfig) wp->w_width = MIN(wp->w_width, Columns - win_border_width(wp)); } - win_set_inner_size(wp); - must_redraw = MAX(must_redraw, VALID); + win_set_inner_size(wp, true); + must_redraw = MAX(must_redraw, UPD_VALID); wp->w_pos_changed = true; if (change_external || change_border) { wp->w_hl_needs_update = true; - redraw_later(wp, NOT_VALID); + redraw_later(wp, UPD_NOT_VALID); } // compute initial position @@ -832,7 +839,7 @@ void win_config_float(win_T *wp, FloatConfig fconfig) // changing border style while keeping border only requires redrawing border if (fconfig.border) { wp->w_redr_border = true; - redraw_later(wp, VALID); + redraw_later(wp, UPD_VALID); } } @@ -921,7 +928,7 @@ void ui_ext_win_position(win_T *wp) wp->w_grid_alloc.focusable = wp->w_float_config.focusable; if (!valid) { wp->w_grid_alloc.valid = false; - redraw_later(wp, NOT_VALID); + redraw_later(wp, UPD_NOT_VALID); } } } else { @@ -1270,7 +1277,7 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) wp->w_floating = false; // non-floating window doesn't store float config or have a border. wp->w_float_config = FLOAT_CONFIG_INIT; - memset(wp->w_border_adj, 0, sizeof(wp->w_border_adj)); + CLEAR_FIELD(wp->w_border_adj); } /* @@ -1292,9 +1299,9 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) } else { curfrp = oldwin->w_frame; if (flags & WSP_BELOW) { - before = FALSE; + before = false; } else if (flags & WSP_ABOVE) { - before = TRUE; + before = true; } else if (flags & WSP_VERT) { before = !p_spr; } else { @@ -1467,8 +1474,8 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) // Both windows need redrawing. Update all status lines, in case they // show something related to the window count or position. - redraw_later(wp, NOT_VALID); - redraw_later(oldwin, NOT_VALID); + redraw_later(wp, UPD_NOT_VALID); + redraw_later(oldwin, UPD_NOT_VALID); status_redraw_all(); if (need_status) { @@ -1563,10 +1570,10 @@ static void win_init(win_T *newp, win_T *oldp, int flags) taggy_T *tag = &newp->w_tagstack[i]; *tag = oldp->w_tagstack[i]; if (tag->tagname != NULL) { - tag->tagname = vim_strsave(tag->tagname); + tag->tagname = xstrdup(tag->tagname); } if (tag->user_data != NULL) { - tag->user_data = vim_strsave(tag->user_data); + tag->user_data = xstrdup(tag->user_data); } } newp->w_tagstackidx = oldp->w_tagstackidx; @@ -1590,7 +1597,7 @@ static void win_init_some(win_T *newp, win_T *oldp) { // Use the same argument list. newp->w_alist = oldp->w_alist; - ++newp->w_alist->al_refcount; + newp->w_alist->al_refcount++; newp->w_arg_idx = oldp->w_arg_idx; // copy options from existing window @@ -1670,7 +1677,7 @@ int win_count(void) int count = 0; FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - ++count; + count++; } return count; } @@ -1716,7 +1723,7 @@ int make_windows(int count, bool vertical) block_autocmds(); // todo is number of windows left to create - for (todo = count - 1; todo > 0; --todo) { + for (todo = count - 1; todo > 0; todo--) { if (vertical) { if (win_split(curwin->w_width - (curwin->w_width - todo) / (todo + 1) - 1, WSP_VERT | WSP_ABOVE) == FAIL) { @@ -1830,8 +1837,8 @@ static void win_exchange(long Prenum) } win_enter(wp, true); - redraw_later(curwin, NOT_VALID); - redraw_later(wp, NOT_VALID); + redraw_later(curwin, UPD_NOT_VALID); + redraw_later(wp, UPD_NOT_VALID); } // rotate windows: if upwards true the second window becomes the first one @@ -1915,7 +1922,7 @@ static void win_rotate(bool upwards, int count) wp1->w_pos_changed = true; wp2->w_pos_changed = true; - redraw_all_later(NOT_VALID); + redraw_all_later(UPD_NOT_VALID); } /* @@ -2027,7 +2034,7 @@ void win_move_after(win_T *win1, win_T *win2) frame_append(win2->w_frame, win1->w_frame); (void)win_comp_pos(); // recompute w_winrow for all windows - redraw_later(curwin, NOT_VALID); + redraw_later(curwin, UPD_NOT_VALID); } win_enter(win1, false); @@ -2075,7 +2082,7 @@ static int get_maximum_wincount(frame_T *fr, int height) void win_equal(win_T *next_curwin, bool current, int dir) { if (dir == 0) { - dir = *p_ead; + dir = (unsigned char)(*p_ead); } win_equal_rec(next_curwin == NULL ? curwin : next_curwin, current, topframe, dir, 0, tabline_height(), @@ -2118,7 +2125,7 @@ static void win_equal_rec(win_T *next_curwin, bool current, frame_T *topfr, int frame_new_height(topfr, height, false, false); topfr->fr_win->w_wincol = col; frame_new_width(topfr, width, false, false); - redraw_all_later(NOT_VALID); + redraw_all_later(UPD_NOT_VALID); } } else if (topfr->fr_layout == FR_ROW) { topfr->fr_width = width; @@ -2193,7 +2200,7 @@ static void win_equal_rec(win_T *next_curwin, bool current, frame_T *topfr, int } if (has_next_curwin) { - --totwincount; // don't count curwin + totwincount--; // don't count curwin } } @@ -2323,7 +2330,7 @@ static void win_equal_rec(win_T *next_curwin, bool current, frame_T *topfr, int } if (has_next_curwin) { - --totwincount; // don't count curwin + totwincount--; // don't count curwin } } @@ -2429,7 +2436,7 @@ void entering_window(win_T *const win) void win_init_empty(win_T *wp) { - redraw_later(wp, NOT_VALID); + redraw_later(wp, UPD_NOT_VALID); wp->w_lines_valid = 0; wp->w_cursor.lnum = 1; wp->w_curswant = wp->w_cursor.col = 0; @@ -2459,7 +2466,7 @@ void close_windows(buf_T *buf, bool keep_curwin) tabpage_T *tp, *nexttp; int h = tabline_height(); - ++RedrawingDisabled; + 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. @@ -2496,7 +2503,7 @@ void close_windows(buf_T *buf, bool keep_curwin) } } - --RedrawingDisabled; + RedrawingDisabled--; redraw_tabline = true; if (h != tabline_height()) { @@ -2928,7 +2935,7 @@ int win_close(win_T *win, bool free_buf, bool force) } curwin->w_pos_changed = true; - redraw_all_later(NOT_VALID); + redraw_all_later(UPD_NOT_VALID); return OK; } @@ -3089,7 +3096,7 @@ void win_free_all(void) cmdwin_type = 0; while (first_tabpage->tp_next != NULL) { - tabpage_close(TRUE); + tabpage_close(true); } while (lastwin != NULL && lastwin->w_floating) { @@ -3688,7 +3695,7 @@ static void frame_add_vsep(const frame_T *frp) wp = frp->fr_win; if (wp->w_vsep_width == 0) { if (wp->w_width > 0) { // don't make it negative - --wp->w_width; + wp->w_width--; } wp->w_vsep_width = 1; } @@ -3820,7 +3827,7 @@ static int frame_minwidth(frame_T *topfrp, win_T *next_curwin) m = (int)p_wmw + topfrp->fr_win->w_vsep_width; // Current window is minimal one column wide if (p_wmw == 0 && topfrp->fr_win == curwin && next_curwin == NULL) { - ++m; + m++; } } } else if (topfrp->fr_layout == FR_COL) { @@ -4018,7 +4025,7 @@ static tabpage_T *alloc_tabpage(void) // Init t: variables. tp->tp_vars = tv_dict_alloc(); init_var_dict(tp->tp_vars, &tp->tp_winvar, VAR_SCOPE); - tp->tp_diff_invalid = TRUE; + tp->tp_diff_invalid = true; tp->tp_ch_used = p_ch; return tp; @@ -4030,7 +4037,7 @@ void free_tabpage(tabpage_T *tp) pmap_del(handle_T)(&tabpage_handles, tp->handle); diff_clear(tp); - for (idx = 0; idx < SNAP_COUNT; ++idx) { + for (idx = 0; idx < SNAP_COUNT; idx++) { clear_snapshot(tp, idx); } vars_clear(&tp->tp_vars->dv_hashtab); // free all t: variables @@ -4093,7 +4100,7 @@ int win_new_tabpage(int after, char_u *filename) n = 2; for (tp = first_tabpage; tp->tp_next != NULL && n < after; tp = tp->tp_next) { - ++n; + n++; } } newtp->tp_next = tp->tp_next; @@ -4108,7 +4115,7 @@ int win_new_tabpage(int after, char_u *filename) newtp->tp_topframe = topframe; last_status(false); - redraw_all_later(NOT_VALID); + redraw_all_later(UPD_NOT_VALID); tabpage_check_windows(old_curtab); @@ -4166,7 +4173,7 @@ int make_tabpages(int maxcount) */ block_autocmds(); - for (todo = count - 1; todo > 0; --todo) { + for (todo = count - 1; todo > 0; todo--) { if (win_new_tabpage(0, NULL) == FAIL) { break; } @@ -4239,7 +4246,7 @@ tabpage_T *find_tabpage(int n) int i = 1; for (tp = first_tabpage; tp != NULL && i != n; tp = tp->tp_next) { - ++i; + i++; } return tp; } @@ -4254,7 +4261,7 @@ int tabpage_index(tabpage_T *ftp) tabpage_T *tp; for (tp = first_tabpage; tp != NULL && tp != ftp; tp = tp->tp_next) { - ++i; + i++; } return i; } @@ -4336,6 +4343,11 @@ static void enter_tabpage(tabpage_T *tp, buf_T *old_curbuf, bool trigger_enter_a // Use the stored value of p_ch, so that it can be different for each tab page. if (p_ch != curtab->tp_ch_used) { clear_cmdline = true; + if (msg_grid.chars && p_ch < curtab->tp_ch_used) { + // TODO(bfredl): a bit expensive, should be enough to invalidate the + // region between the old and the new p_ch. + grid_invalidate(&msg_grid); + } } p_ch = curtab->tp_ch_used; @@ -4366,7 +4378,7 @@ static void enter_tabpage(tabpage_T *tp, buf_T *old_curbuf, bool trigger_enter_a } } - redraw_all_later(NOT_VALID); + redraw_all_later(UPD_NOT_VALID); } /// tells external UI that windows and inline floats in old_curtab are invisible @@ -4433,7 +4445,7 @@ void goto_tabpage(int n) // "gT": go to previous tab page, wrap around end. "N gT" repeats // this N times. ttp = curtab; - for (i = n; i < 0; ++i) { + for (i = n; i < 0; i++) { for (tp = first_tabpage; tp->tp_next != ttp && tp->tp_next != NULL; tp = tp->tp_next) {} ttp = tp; @@ -4516,7 +4528,7 @@ void tabpage_move(int nr) } for (tp = first_tabpage; tp->tp_next != NULL && n < nr; tp = tp->tp_next) { - ++n; + n++; } if (tp == curtab || (nr > 0 && tp->tp_next != NULL @@ -4829,7 +4841,7 @@ static void win_enter_ext(win_T *const wp, const int flags) } if (!curwin_invalid) { prevwin = curwin; // remember for CTRL-W p - curwin->w_redr_status = TRUE; + curwin->w_redr_status = true; } curwin = wp; curbuf = wp->w_buffer; @@ -4860,13 +4872,20 @@ static void win_enter_ext(win_T *const wp, const int flags) curwin->w_redr_status = true; redraw_tabline = true; if (restart_edit) { - redraw_later(curwin, VALID); // causes status line redraw + redraw_later(curwin, UPD_VALID); // causes status line redraw } - if (HL_ATTR(HLF_INACTIVE) - || (prevwin && prevwin->w_hl_ids[HLF_INACTIVE]) - || curwin->w_hl_ids[HLF_INACTIVE]) { - redraw_all_later(NOT_VALID); + // change background color according to NormalNC, + // but only if actually defined (otherwise no extra redraw) + if (curwin->w_hl_attr_normal != curwin->w_hl_attr_normalnc) { + // TODO(bfredl): eventually we should be smart enough + // to only recompose the window, not redraw it. + redraw_later(curwin, UPD_NOT_VALID); + } + if (prevwin) { + if (prevwin->w_hl_attr_normal != prevwin->w_hl_attr_normalnc) { + redraw_later(prevwin, UPD_NOT_VALID); + } } // set window height to desired minimal value @@ -5035,6 +5054,8 @@ static win_T *win_alloc(win_T *after, bool hidden) new_wp->w_float_config = FLOAT_CONFIG_INIT; new_wp->w_viewport_invalid = true; + new_wp->w_ns_hl = -1; + // use global option for global-local options new_wp->w_p_so = -1; new_wp->w_p_siso = -1; @@ -5175,7 +5196,7 @@ void win_free_grid(win_T *wp, bool reinit) grid_free(&wp->w_grid_alloc); if (reinit) { // if a float is turned into a split, the grid data structure will be reused - memset(&wp->w_grid_alloc, 0, sizeof(wp->w_grid_alloc)); + CLEAR_FIELD(wp->w_grid_alloc); } } @@ -5346,6 +5367,7 @@ void may_trigger_winscrolled(void) 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]; @@ -5359,6 +5381,7 @@ void may_trigger_winscrolled(void) if (win_valid_any_tab(wp)) { 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; } @@ -5454,7 +5477,7 @@ static void frame_comp_pos(frame_T *topfrp, int *row, int *col) // position changed, redraw wp->w_winrow = *row; wp->w_wincol = *col; - redraw_later(wp, NOT_VALID); + redraw_later(wp, UPD_NOT_VALID); wp->w_redr_status = true; wp->w_pos_changed = true; } @@ -5497,7 +5520,7 @@ void win_setheight_win(int height, win_T *win) if (win->w_floating) { win->w_float_config.height = height; win_config_float(win, win->w_float_config); - redraw_later(win, NOT_VALID); + redraw_later(win, UPD_NOT_VALID); } else { frame_setheight(win->w_frame, height + win->w_hsep_height + win->w_status_height); @@ -5517,7 +5540,7 @@ void win_setheight_win(int height, win_T *win) curtab->tp_ch_used = p_ch; msg_row = row; msg_col = 0; - redraw_all_later(NOT_VALID); + redraw_all_later(UPD_NOT_VALID); redraw_cmdline = true; } } @@ -5551,7 +5574,7 @@ static void frame_setheight(frame_T *curfrp, int height) } if (curfrp->fr_parent == NULL) { - // topframe: can only change the command line + // topframe: can only change the command line height 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 @@ -5580,7 +5603,7 @@ static void frame_setheight(frame_T *curfrp, int height) * 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 (run = 1; run <= 2; run++) { room = 0; room_reserved = 0; FOR_ALL_FRAMES(frp, curfrp->fr_parent->fr_child) { @@ -5653,7 +5676,7 @@ static void frame_setheight(frame_T *curfrp, int height) * that is not enough, takes lines from frames above the current * frame. */ - for (run = 0; run < 2; ++run) { + for (run = 0; run < 2; run++) { if (run == 0) { frp = curfrp->fr_next; // 1st run: start with next window } else { @@ -5719,13 +5742,13 @@ void win_setwidth_win(int width, win_T *wp) if (wp->w_floating) { wp->w_float_config.width = width; win_config_float(wp, wp->w_float_config); - redraw_later(wp, NOT_VALID); + redraw_later(wp, UPD_NOT_VALID); } else { frame_setwidth(wp->w_frame, width + wp->w_vsep_width); // recompute the window positions (void)win_comp_pos(); - redraw_all_later(NOT_VALID); + redraw_all_later(UPD_NOT_VALID); } } @@ -5772,7 +5795,7 @@ static void frame_setwidth(frame_T *curfrp, int width) * containing frame. * 2: compute the room available and adjust the width to it. */ - for (run = 1; run <= 2; ++run) { + for (run = 1; run <= 2; run++) { room = 0; room_reserved = 0; FOR_ALL_FRAMES(frp, curfrp->fr_parent->fr_child) { @@ -5825,7 +5848,7 @@ static void frame_setwidth(frame_T *curfrp, int width) * that is not enough, takes lines from frames left of the current * frame. */ - for (run = 0; run < 2; ++run) { + for (run = 0; run < 2; run++) { if (run == 0) { frp = curfrp->fr_next; // 1st run: start with next window } else { @@ -5914,6 +5937,13 @@ void win_drag_status_line(win_T *dragwin, int offset) int row; bool up; // if true, drag status line up, otherwise down int n; + static bool p_ch_was_zero = false; + + // 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; + } fr = dragwin->w_frame; curfr = fr; @@ -5964,6 +5994,8 @@ void win_drag_status_line(win_T *dragwin, int offset) room = Rows - cmdline_row; if (curfr->fr_next != NULL) { room -= (int)p_ch + global_stl_height(); + } else if (!p_ch_was_zero) { + room--; } if (room < 0) { room = 0; @@ -6019,9 +6051,9 @@ void win_drag_status_line(win_T *dragwin, int offset) clear_cmdline = true; } cmdline_row = row; - p_ch = MAX(Rows - cmdline_row, 0); + p_ch = MAX(Rows - cmdline_row, p_ch_was_zero ? 0 : 1); curtab->tp_ch_used = p_ch; - redraw_all_later(SOME_VALID); + redraw_all_later(UPD_SOME_VALID); showmode(); } @@ -6128,7 +6160,7 @@ void win_drag_vsep_line(win_T *dragwin, int offset) } } (void)win_comp_pos(); - redraw_all_later(NOT_VALID); + redraw_all_later(UPD_NOT_VALID); } #define FRACTION_MULT 16384L @@ -6162,7 +6194,7 @@ void win_new_height(win_T *wp, int height) wp->w_height = height; wp->w_pos_changed = true; - win_set_inner_size(wp); + win_set_inner_size(wp, true); } void scroll_to_fraction(win_T *wp, int prev_height) @@ -6225,7 +6257,7 @@ void scroll_to_fraction(win_T *wp, int prev_height) if (lnum == 1) { // first line in buffer is folded line_size = 1; - --sline; + sline--; break; } lnum--; @@ -6266,12 +6298,12 @@ void scroll_to_fraction(win_T *wp, int prev_height) } win_comp_scroll(wp); - redraw_later(wp, SOME_VALID); + redraw_later(wp, UPD_SOME_VALID); wp->w_redr_status = true; invalidate_botline_win(wp); } -void win_set_inner_size(win_T *wp) +void win_set_inner_size(win_T *wp, bool valid_cursor) { int width = wp->w_width_request; if (width == 0) { @@ -6285,7 +6317,7 @@ void win_set_inner_size(win_T *wp) } if (height != prev_height) { - if (height > 0) { + if (height > 0 && valid_cursor) { if (wp == curwin) { // w_wrow needs to be valid. When setting 'laststatus' this may // call win_new_height() recursively. @@ -6304,22 +6336,24 @@ void win_set_inner_size(win_T *wp) // There is no point in adjusting the scroll position when exiting. Some // values might be invalid. // Skip scroll_to_fraction() when 'cmdheight' was set to one from zero. - if (!exiting && !made_cmdheight_nonzero) { + if (!exiting && !made_cmdheight_nonzero && valid_cursor) { scroll_to_fraction(wp, prev_height); } - redraw_later(wp, NOT_VALID); // SOME_VALID?? + redraw_later(wp, UPD_NOT_VALID); // UPD_SOME_VALID?? } if (width != wp->w_width_inner) { wp->w_width_inner = width; wp->w_lines_valid = 0; - changed_line_abv_curs_win(wp); - invalidate_botline_win(wp); - if (wp == curwin) { - update_topline(wp); - curs_columns(wp, true); // validate w_wrow + if (valid_cursor) { + changed_line_abv_curs_win(wp); + invalidate_botline_win(wp); + if (wp == curwin) { + update_topline(wp); + curs_columns(wp, true); // validate w_wrow + } } - redraw_later(wp, NOT_VALID); + redraw_later(wp, UPD_NOT_VALID); } if (wp->w_buffer->terminal) { @@ -6346,7 +6380,7 @@ 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); + win_set_inner_size(wp, true); wp->w_redr_status = true; wp->w_pos_changed = true; @@ -6381,6 +6415,19 @@ void command_height(void) // p_ch was changed in another tab page. curtab->tp_ch_used = p_ch; + // If the space for the command line is already more than 'cmdheight' there + // is nothing to do (window size must have decreased). + if (p_ch > old_p_ch && cmdline_row <= Rows - p_ch) { + return; + } + + // If cmdline_row is smaller than what it is supposed to be for 'cmdheight' + // then set old_p_ch to what it would be, so that the windows get resized + // properly for the new value. + if (cmdline_row < Rows - p_ch) { + old_p_ch = Rows - cmdline_row; + } + // Find bottom frame with width of screen. frp = lastwin_nofloating()->w_frame; while (frp->fr_width != Columns && frp->fr_parent != NULL) { @@ -6465,17 +6512,17 @@ char_u *grab_file_name(long count, linenr_T *file_lnum) int options = FNAME_MESS | FNAME_EXP | FNAME_REL | FNAME_UNESC; if (VIsual_active) { size_t len; - char_u *ptr; + char *ptr; if (get_visual_text(NULL, &ptr, &len) == FAIL) { return NULL; } // Only recognize ":123" here if (file_lnum != NULL && ptr[len] == ':' && isdigit(ptr[len + 1])) { - char *p = (char *)ptr + len + 1; + char *p = ptr + len + 1; *file_lnum = (linenr_T)getdigits_long(&p, false, 0); } - return find_file_name_in_path(ptr, len, options, count, (char_u *)curbuf->b_ffname); + return find_file_name_in_path((char_u *)ptr, len, options, count, (char_u *)curbuf->b_ffname); } return file_name_at_cursor(options | FNAME_HYP, count, file_lnum); } @@ -6542,7 +6589,7 @@ char_u *file_name_in_line(char_u *line, int col, int options, long count, char_u /* * Search forward for the last char of the file name. - * Also allow "://" when ':' is not in 'isfname'. + * Also allow ":/" when ':' is not in 'isfname'. */ len = 0; while (vim_isfilec(ptr[len]) || (ptr[len] == '\\' && ptr[len + 1] == ' ') @@ -6561,7 +6608,7 @@ char_u *file_name_in_line(char_u *line, int col, int options, long count, char_u if (ptr[len] == '\\' && ptr[len + 1] == ' ') { // Skip over the "\" in "\ ". - ++len; + len++; } len += (size_t)(utfc_ptr2len(ptr + len)); } @@ -6572,7 +6619,7 @@ char_u *file_name_in_line(char_u *line, int col, int options, long count, char_u */ if (len > 2 && vim_strchr(".,:;!", ptr[len - 1]) != NULL && ptr[len - 2] != '.') { - --len; + len--; } if (file_lnum != NULL) { @@ -6726,7 +6773,7 @@ static void last_status_rec(frame_T *fr, bool statusline, bool is_stl_global) wp->w_hsep_height = 0; comp_col(); } - redraw_all_later(SOME_VALID); + redraw_all_later(UPD_SOME_VALID); } else { // For a column or row frame, recursively call this function for all child frames FOR_ALL_FRAMES(fp, fr->fr_child) { @@ -6738,9 +6785,11 @@ static void last_status_rec(frame_T *fr, bool statusline, bool is_stl_global) /// Add or remove window bar from window "wp". /// /// @param make_room Whether to resize frames to make room for winbar. +/// @param valid_cursor Whether the cursor is valid and should be used while +/// resizing. /// /// @return Success status. -int set_winbar_win(win_T *wp, bool make_room) +int set_winbar_win(win_T *wp, bool make_room, bool valid_cursor) { // Require the local value to be set in order to show winbar on a floating window. int winbar_height = wp->w_floating ? ((*wp->w_p_wbr != NUL) ? 1 : 0) @@ -6756,7 +6805,7 @@ int set_winbar_win(win_T *wp, bool make_room) } } wp->w_winbar_height = winbar_height; - win_set_inner_size(wp); + win_set_inner_size(wp, valid_cursor); wp->w_redr_status = wp->w_redr_status || winbar_height; if (winbar_height == 0) { @@ -6777,7 +6826,7 @@ int set_winbar_win(win_T *wp, bool make_room) void set_winbar(bool make_room) { FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (set_winbar_win(wp, make_room) == FAIL) { + if (set_winbar_win(wp, make_room, true) == FAIL) { break; } } @@ -7024,7 +7073,7 @@ void restore_snapshot(int idx, int close_curwin) if (wp != NULL && close_curwin) { win_goto(wp); } - redraw_all_later(NOT_VALID); + redraw_all_later(UPD_NOT_VALID); } clear_snapshot(curtab, idx); } @@ -7095,7 +7144,7 @@ int switch_win(switchwin_T *switchwin, win_T *win, tabpage_T *tp, bool no_displa // As switch_win() but without blocking autocommands. int switch_win_noblock(switchwin_T *switchwin, win_T *win, tabpage_T *tp, bool no_display) { - memset(switchwin, 0, sizeof(switchwin_T)); + CLEAR_POINTER(switchwin); switchwin->sw_curwin = curwin; if (win == curwin) { switchwin->sw_same_win = true; @@ -7228,6 +7277,88 @@ static bool frame_check_width(const frame_T *topfrp, int width) return true; } +/// Simple int comparison function for use with qsort() +static int int_cmp(const void *a, const void *b) +{ + return *(const int *)a - *(const int *)b; +} + +/// Handle setting 'colorcolumn' or 'textwidth' in window "wp". +/// +/// @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;) { + if (*s == '-' || *s == '+') { + // -N and +N: add to 'textwidth' + col = (*s == '-') ? -1 : 1; + s++; + if (!ascii_isdigit(*s)) { + return e_invarg; + } + col = col * getdigits_int(&s, true, 0); + if (wp->w_buffer->b_p_tw == 0) { + goto skip; // 'textwidth' not set, skip this item + } + assert((col >= 0 + && wp->w_buffer->b_p_tw <= INT_MAX - col + && wp->w_buffer->b_p_tw + col >= INT_MIN) + || (col < 0 + && wp->w_buffer->b_p_tw >= INT_MIN - col + && wp->w_buffer->b_p_tw + col <= INT_MAX)); + col += (int)wp->w_buffer->b_p_tw; + if (col < 0) { + goto skip; + } + } else if (ascii_isdigit(*s)) { + col = getdigits_int(&s, true, 0); + } else { + return e_invarg; + } + color_cols[count++] = col - 1; // 1-based to 0-based +skip: + if (*s == NUL) { + break; + } + if (*s != ',') { + return e_invarg; + } + if (*++s == NUL) { + return e_invarg; // illegal trailing comma as in "set cc=80," + } + } + + xfree(wp->w_p_cc_cols); + if (count == 0) { + wp->w_p_cc_cols = NULL; + } else { + wp->w_p_cc_cols = xmalloc(sizeof(int) * (count + 1)); + // sort the columns for faster usage on screen redraw inside + // win_line() + qsort(color_cols, count, sizeof(int), int_cmp); + + for (unsigned int i = 0; i < count; i++) { + // skip duplicates + if (j == 0 || wp->w_p_cc_cols[j - 1] != color_cols[i]) { + wp->w_p_cc_cols[j++] = color_cols[i]; + } + } + wp->w_p_cc_cols[j] = -1; // end marker + } + + return NULL; // no error +} + int win_getid(typval_T *argvars) { if (argvars[0].v_type == VAR_UNKNOWN) { @@ -7265,19 +7396,6 @@ int win_getid(typval_T *argvars) return 0; } -int win_gotoid(typval_T *argvars) -{ - int id = (int)tv_get_number(&argvars[0]); - - FOR_ALL_TAB_WINDOWS(tp, wp) { - if (wp->handle == id) { - goto_tabpage_win(tp, wp); - return 1; - } - } - return 0; -} - void win_get_tabwin(handle_T id, int *tabnr, int *winnr) { *tabnr = 0; diff --git a/test/functional/api/command_spec.lua b/test/functional/api/command_spec.lua index 7eb7ee73f9..440e93da0e 100644 --- a/test/functional/api/command_spec.lua +++ b/test/functional/api/command_spec.lua @@ -114,8 +114,8 @@ describe('nvim_create_user_command', function() ]] eq({ - args = [[this is a\ test]], - fargs = {"this", "is", "a test"}, + args = [[this\ is a\ test]], + fargs = {"this ", "is", "a test"}, bang = false, line1 = 1, line2 = 1, @@ -144,7 +144,7 @@ describe('nvim_create_user_command', function() count = 2, reg = "", }, exec_lua [=[ - vim.api.nvim_command([[CommandWithLuaCallback this is a\ test]]) + vim.api.nvim_command([[CommandWithLuaCallback this\ is a\ test]]) return result ]=]) @@ -326,7 +326,7 @@ describe('nvim_create_user_command', function() -- f-args doesn't split when command nargs is 1 or "?" exec_lua [[ result = {} - vim.api.nvim_create_user_command('CommandWithOneArg', function(opts) + vim.api.nvim_create_user_command('CommandWithOneOrNoArg', function(opts) result = opts end, { nargs = "?", @@ -366,7 +366,89 @@ describe('nvim_create_user_command', function() count = 2, reg = "", }, exec_lua [[ - vim.api.nvim_command('CommandWithOneArg hello I\'m one argument') + vim.api.nvim_command('CommandWithOneOrNoArg hello I\'m one argument') + return result + ]]) + + -- f-args is an empty table if no args were passed + eq({ + args = "", + fargs = {}, + bang = false, + line1 = 1, + line2 = 1, + mods = "", + smods = { + browse = false, + confirm = false, + emsg_silent = false, + hide = false, + keepalt = false, + keepjumps = false, + keepmarks = false, + keeppatterns = false, + lockmarks = false, + noautocmd = false, + noswapfile = false, + sandbox = false, + silent = false, + split = "", + tab = 0, + unsilent = false, + verbose = -1, + vertical = false, + }, + range = 0, + count = 2, + reg = "", + }, exec_lua [[ + vim.api.nvim_command('CommandWithOneOrNoArg') + return result + ]]) + + -- f-args is an empty table when the command nargs=0 + exec_lua [[ + result = {} + vim.api.nvim_create_user_command('CommandWithNoArgs', function(opts) + result = opts + end, { + nargs = 0, + bang = true, + count = 2, + }) + ]] + eq({ + args = "", + fargs = {}, + bang = false, + line1 = 1, + line2 = 1, + mods = "", + smods = { + browse = false, + confirm = false, + emsg_silent = false, + hide = false, + keepalt = false, + keepjumps = false, + keepmarks = false, + keeppatterns = false, + lockmarks = false, + noautocmd = false, + noswapfile = false, + sandbox = false, + silent = false, + split = "", + tab = 0, + unsilent = false, + verbose = -1, + vertical = false, + }, + range = 0, + count = 2, + reg = "", + }, exec_lua [[ + vim.cmd('CommandWithNoArgs') return result ]]) diff --git a/test/functional/api/highlight_spec.lua b/test/functional/api/highlight_spec.lua index c4197f0b3e..2730f7e23d 100644 --- a/test/functional/api/highlight_spec.lua +++ b/test/functional/api/highlight_spec.lua @@ -243,7 +243,7 @@ describe("API: set highlight", function() local function get_ns() local ns = meths.create_namespace('Test_set_hl') - meths._set_hl_ns(ns) + meths.set_hl_ns(ns) return ns end diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index fe623ff824..24d0b6da45 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -281,8 +281,8 @@ describe('API', function() ]]} end) - it('does\'t display messages when output=true', function() - local screen = Screen.new(40, 8) + it('doesn\'t display messages when output=true', function() + local screen = Screen.new(40, 6) screen:attach() screen:set_default_attr_ids({ [0] = {bold=true, foreground=Screen.colors.Blue}, @@ -294,9 +294,21 @@ describe('API', function() {0:~ }| {0:~ }| {0:~ }| + | + ]]} + exec([[ + func Print() + call nvim_exec('echo "hello"', v:true) + endfunc + ]]) + feed([[:echon 1 | call Print() | echon 5<CR>]]) + screen:expect{grid=[[ + ^ | {0:~ }| {0:~ }| - | + {0:~ }| + {0:~ }| + 15 | ]]} end) end) @@ -3656,6 +3668,55 @@ describe('API', function() :^ | ]]) end) + it('does not move cursor or change search history/pattern #19878 #19890', function() + meths.buf_set_lines(0, 0, -1, true, {'foo', 'bar', 'foo', 'bar'}) + eq({1, 0}, meths.win_get_cursor(0)) + eq('', funcs.getreg('/')) + eq('', funcs.histget('search')) + feed(':') -- call the API in cmdline mode to test whether it changes search history + eq({ + cmd = 'normal', + args = {'x'}, + bang = true, + range = {3, 4}, + count = -1, + reg = '', + addr = 'line', + magic = { + file = false, + bar = false, + }, + nargs = '+', + nextcmd = '', + mods = { + browse = false, + confirm = false, + emsg_silent = false, + filter = { + pattern = "", + force = false, + }, + hide = false, + keepalt = false, + keepjumps = false, + keepmarks = false, + keeppatterns = false, + lockmarks = false, + noautocmd = false, + noswapfile = false, + sandbox = false, + silent = false, + split = "", + tab = 0, + unsilent = false, + verbose = -1, + vertical = false, + } + }, meths.parse_cmd('+2;/bar/normal! x', {})) + eq({1, 0}, meths.win_get_cursor(0)) + eq('', funcs.getreg('/')) + eq('', funcs.histget('search')) + end) end) describe('nvim_cmd', function() it('works', function () @@ -3829,5 +3890,42 @@ describe('API', function() eq({'aa'}, meths.buf_get_lines(0, 0, 1, false)) assert_alive() end) + it("'make' command works when argument count isn't 1 #19696", function() + command('set makeprg=echo') + meths.cmd({ cmd = 'make' }, {}) + assert_alive() + meths.cmd({ cmd = 'make', args = { 'foo', 'bar' } }, {}) + assert_alive() + end) + it('doesn\'t display messages when output=true', function() + local screen = Screen.new(40, 6) + screen:attach() + screen:set_default_attr_ids({ + [0] = {bold=true, foreground=Screen.colors.Blue}, + }) + meths.cmd({cmd = 'echo', args = {[['hello']]}}, {output = true}) + screen:expect{grid=[[ + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]} + exec([[ + func Print() + call nvim_cmd(#{cmd: 'echo', args: ['"hello"']}, #{output: v:true}) + endfunc + ]]) + feed([[:echon 1 | call Print() | echon 5<CR>]]) + screen:expect{grid=[[ + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + 15 | + ]]} + end) end) end) diff --git a/test/functional/autocmd/winscrolled_spec.lua b/test/functional/autocmd/winscrolled_spec.lua index 5c1b758961..12b8e7c42d 100644 --- a/test/functional/autocmd/winscrolled_spec.lua +++ b/test/functional/autocmd/winscrolled_spec.lua @@ -61,6 +61,20 @@ describe('WinScrolled', function() 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 = {} diff --git a/test/functional/core/exit_spec.lua b/test/functional/core/exit_spec.lua index 4dba58dbfc..8cad7adfa6 100644 --- a/test/functional/core/exit_spec.lua +++ b/test/functional/core/exit_spec.lua @@ -3,6 +3,7 @@ local helpers = require('test.functional.helpers')(after_each) local assert_alive = helpers.assert_alive local command = helpers.command local feed_command = helpers.feed_command +local feed = helpers.feed local eval = helpers.eval local eq = helpers.eq local run = helpers.run @@ -36,11 +37,12 @@ describe('v:exiting', function() end run(on_request, nil, on_setup) end) - it('is 0 on exit from ex-mode involving try-catch', function() + it('is 0 on exit from Ex mode involving try-catch vim-patch:8.0.0184', function() local function on_setup() command('autocmd VimLeavePre * call rpcrequest('..cid..', "")') command('autocmd VimLeave * call rpcrequest('..cid..', "")') - feed_command('call feedkey("Q")','try', 'call NoFunction()', 'catch', 'echo "bye"', 'endtry', 'quit') + feed('gQ') + feed_command('try', 'call NoFunction()', 'catch', 'echo "bye"', 'endtry', 'quit') end local function on_request() eq(0, eval('v:exiting')) diff --git a/test/functional/core/job_spec.lua b/test/functional/core/job_spec.lua index 04fbb807be..02ff18bdda 100644 --- a/test/functional/core/job_spec.lua +++ b/test/functional/core/job_spec.lua @@ -21,6 +21,7 @@ local nvim_set = helpers.nvim_set local expect_twostreams = helpers.expect_twostreams 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') describe('jobs', function() @@ -229,8 +230,8 @@ describe('jobs', function() local dir = 'Xtest_not_executable_dir' mkdir(dir) funcs.setfperm(dir, 'rw-------') - eq('Vim(call):E475: Invalid argument: expected valid directory', - pcall_err(nvim, 'command', "call jobstart('pwd', {'cwd': '"..dir.."'})")) + matches('^Vim%(call%):E903: Process failed to start: permission denied: .*', + pcall_err(nvim, 'command', "call jobstart(['pwd'], {'cwd': '"..dir.."'})")) rmdir(dir) end) @@ -690,8 +691,8 @@ describe('jobs', function() -- jobstart() shares its v:servername with the child via $NVIM. eq('NVIM='..addr, get_env_in_child_job('NVIM')) -- $NVIM_LISTEN_ADDRESS is unset by server_init in the child. - eq('NVIM_LISTEN_ADDRESS=null', get_env_in_child_job('NVIM_LISTEN_ADDRESS')) - eq('NVIM_LISTEN_ADDRESS=null', get_env_in_child_job('NVIM_LISTEN_ADDRESS', + eq('NVIM_LISTEN_ADDRESS=v:null', get_env_in_child_job('NVIM_LISTEN_ADDRESS')) + eq('NVIM_LISTEN_ADDRESS=v:null', get_env_in_child_job('NVIM_LISTEN_ADDRESS', { NVIM_LISTEN_ADDRESS='Xtest_jobstart_env' })) -- User can explicitly set $NVIM_LOG_FILE, $VIM, $VIMRUNTIME. eq('NVIM_LOG_FILE=Xtest_jobstart_env', diff --git a/test/functional/editor/K_spec.lua b/test/functional/editor/K_spec.lua index 8ad81ac3d6..3b5580540f 100644 --- a/test/functional/editor/K_spec.lua +++ b/test/functional/editor/K_spec.lua @@ -1,6 +1,6 @@ local helpers = require('test.functional.helpers')(after_each) -local eq, clear, eval, feed, retry = - helpers.eq, helpers.clear, helpers.eval, helpers.feed, helpers.retry +local eq, clear, eval, feed, meths, retry = + helpers.eq, helpers.clear, helpers.eval, helpers.feed, helpers.meths, helpers.retry describe('K', function() local test_file = 'K_spec_out' @@ -58,4 +58,11 @@ describe('K', function() helpers.neq(bufnr, eval('bufnr()')) end) + it('empty string falls back to :help #19298', function() + meths.set_option('keywordprg', '') + meths.buf_set_lines(0, 0, -1, true, {'doesnotexist'}) + feed('K') + eq('E149: Sorry, no help for doesnotexist', meths.get_vvar('errmsg')) + end) + end) diff --git a/test/functional/editor/ctrl_c_spec.lua b/test/functional/editor/ctrl_c_spec.lua index 60131bf2a4..4548e1aa34 100644 --- a/test/functional/editor/ctrl_c_spec.lua +++ b/test/functional/editor/ctrl_c_spec.lua @@ -2,6 +2,7 @@ local helpers = require('test.functional.helpers')(after_each) local Screen = require('test.functional.ui.screen') local clear, feed, source = helpers.clear, helpers.feed, helpers.source local command = helpers.command +local poke_eventloop = helpers.poke_eventloop local sleep = helpers.sleep describe("CTRL-C (mapped)", function() @@ -57,11 +58,9 @@ describe("CTRL-C (mapped)", function() it('interrupts :sleep', function() command('nnoremap <C-C> <Nop>') feed(':sleep 100<CR>') - -- wait for :sleep to start - sleep(10) + poke_eventloop() -- wait for :sleep to start feed('foo<C-C>') - -- wait for input buffer to be flushed - sleep(10) + poke_eventloop() -- wait for input buffer to be flushed feed('i') screen:expect([[ ^ | @@ -77,10 +76,9 @@ describe("CTRL-C (mapped)", function() command('nnoremap <C-C> <Nop>') command('nmap <F2> <Ignore><F2>') feed('<F2>') - sleep(10) + sleep(10) -- wait for the key to enter typeahead feed('foo<C-C>') - -- wait for input buffer to be flushed - sleep(10) + poke_eventloop() -- wait for input buffer to be flushed feed('i') screen:expect([[ ^ | diff --git a/test/functional/editor/mode_insert_spec.lua b/test/functional/editor/mode_insert_spec.lua index e3d3cdbd85..cd51a65be3 100644 --- a/test/functional/editor/mode_insert_spec.lua +++ b/test/functional/editor/mode_insert_spec.lua @@ -6,12 +6,20 @@ local expect = helpers.expect local command = helpers.command local eq = helpers.eq local eval = helpers.eval +local curbuf_contents = helpers.curbuf_contents describe('insert-mode', function() before_each(function() clear() end) + it('indents only once after "!" keys #12894', function() + command('let counter = []') + command('set indentexpr=len(add(counter,0))') + feed('i<C-F>x') + eq(' x', curbuf_contents()) + end) + it('CTRL-@', function() -- Inserts last-inserted text, leaves insert-mode. insert('hello') diff --git a/test/functional/ex_cmds/normal_spec.lua b/test/functional/ex_cmds/normal_spec.lua index f6e7dd2b3a..009f1d6516 100644 --- a/test/functional/ex_cmds/normal_spec.lua +++ b/test/functional/ex_cmds/normal_spec.lua @@ -1,6 +1,7 @@ local helpers = require('test.functional.helpers')(after_each) local clear = helpers.clear local command = helpers.command +local funcs = helpers.funcs local feed = helpers.feed local expect = helpers.expect local eq = helpers.eq @@ -8,20 +9,30 @@ local eval = helpers.eval before_each(clear) -describe(':normal', function() +describe(':normal!', function() it('can get out of Insert mode if called from Ex mode #17924', function() feed('gQnormal! Ifoo<CR>') expect('foo') end) - it('normal! does not execute command in Ex mode when running out of characters', function() + it('does not execute command in Ex mode when running out of characters', function() command('let g:var = 0') command('normal! gQlet g:var = 1') eq(0, eval('g:var')) end) - it('normal! gQinsert does not hang #17980', function() + it('gQinsert does not hang #17980', function() command('normal! gQinsert') expect('') end) + + it('can stop Visual mode without closing cmdwin vim-patch:9.0.0234', function() + feed('q:') + feed('v') + eq('v', funcs.mode(1)) + eq(':', funcs.getcmdwintype()) + command('normal! \027') + eq('n', funcs.mode(1)) + eq(':', funcs.getcmdwintype()) + end) end) diff --git a/test/functional/ex_cmds/oldfiles_spec.lua b/test/functional/ex_cmds/oldfiles_spec.lua index 003ab64dd4..5f87c3cdd9 100644 --- a/test/functional/ex_cmds/oldfiles_spec.lua +++ b/test/functional/ex_cmds/oldfiles_spec.lua @@ -29,7 +29,6 @@ describe(':oldfiles', function() it('shows most recently used files', function() local screen = Screen.new(100, 5) screen:attach() - feed_command("set display-=msgsep") feed_command('edit testfile1') feed_command('edit testfile2') feed_command('wshada') @@ -37,7 +36,7 @@ describe(':oldfiles', function() local oldfiles = helpers.meths.get_vvar('oldfiles') feed_command('oldfiles') screen:expect([[ - testfile2 | + | 1: ]].. add_padding(oldfiles[1]) ..[[ | 2: ]].. add_padding(oldfiles[2]) ..[[ | | diff --git a/test/functional/ex_cmds/source_spec.lua b/test/functional/ex_cmds/source_spec.lua index 13a40fcc53..cd1f43f9fd 100644 --- a/test/functional/ex_cmds/source_spec.lua +++ b/test/functional/ex_cmds/source_spec.lua @@ -13,6 +13,10 @@ local exec_lua = helpers.exec_lua 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 describe(':source', function() before_each(function() @@ -39,6 +43,47 @@ describe(':source', function() os.remove(test_file) end) + it("changing 'shellslash' changes the result of expand()", function() + if not iswin() then + pending("'shellslash' only works on Windows") + return + end + meths.set_option('shellslash', false) + mkdir('Xshellslash') + + write_file([[Xshellslash/Xstack.vim]], [[ + let g:stack1 = expand('<stack>') + set shellslash + let g:stack2 = expand('<stack>') + set noshellslash + let g:stack3 = expand('<stack>') + ]]) + + for _ = 1, 2 do + command([[source Xshellslash/Xstack.vim]]) + matches([[Xshellslash\Xstack%.vim]], meths.get_var('stack1')) + matches([[Xshellslash/Xstack%.vim]], meths.get_var('stack2')) + matches([[Xshellslash\Xstack%.vim]], meths.get_var('stack3')) + end + + write_file([[Xshellslash/Xstack.lua]], [[ + vim.g.stack1 = vim.fn.expand('<stack>') + vim.o.shellslash = true + vim.g.stack2 = vim.fn.expand('<stack>') + vim.o.shellslash = false + vim.g.stack3 = vim.fn.expand('<stack>') + ]]) + + for _ = 1, 2 do + command([[source Xshellslash/Xstack.lua]]) + matches([[Xshellslash\Xstack%.lua]], meths.get_var('stack1')) + matches([[Xshellslash/Xstack%.lua]], meths.get_var('stack2')) + matches([[Xshellslash\Xstack%.lua]], meths.get_var('stack3')) + end + + rmdir('Xshellslash') + end) + it('current buffer', function() insert([[ let a = 2 @@ -117,11 +162,20 @@ describe(':source', function() it('can source lua files', function() local test_file = 'test.lua' - write_file (test_file, [[vim.g.sourced_lua = 1]]) - - exec('source ' .. test_file) + write_file(test_file, [[ + vim.g.sourced_lua = 1 + vim.g.sfile_value = vim.fn.expand('<sfile>') + vim.g.stack_value = vim.fn.expand('<stack>') + vim.g.script_value = vim.fn.expand('<script>') + ]]) + command('set shellslash') + command('source ' .. test_file) eq(1, eval('g:sourced_lua')) + matches([[/test%.lua$]], meths.get_var('sfile_value')) + matches([[/test%.lua$]], meths.get_var('stack_value')) + matches([[/test%.lua$]], meths.get_var('script_value')) + os.remove(test_file) end) @@ -153,13 +207,16 @@ describe(':source', function() it('can source current lua buffer without argument', function() local test_file = 'test.lua' - write_file (test_file, [[ + write_file(test_file, [[ vim.g.c = 10 vim.g.c = 11 vim.g.c = 12 a = [=[ \ 1 "\ 2]=] + vim.g.sfile_value = vim.fn.expand('<sfile>') + vim.g.stack_value = vim.fn.expand('<stack>') + vim.g.script_value = vim.fn.expand('<script>') ]]) command('edit '..test_file) @@ -167,6 +224,10 @@ describe(':source', function() eq(12, eval('g:c')) eq(' \\ 1\n "\\ 2', exec_lua('return _G.a')) + eq(':source (no file)', meths.get_var('sfile_value')) + eq(':source (no file)', meths.get_var('stack_value')) + eq(':source (no file)', meths.get_var('script_value')) + os.remove(test_file) end) diff --git a/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua b/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua index 4d984af41e..69404039ff 100644 --- a/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua +++ b/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua @@ -1,8 +1,8 @@ local Screen = require('test.functional.ui.screen') local helpers = require('test.functional.helpers')(after_each) local lfs = require('lfs') -local eq, eval, expect, source = - helpers.eq, helpers.eval, helpers.expect, helpers.source +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 @@ -10,6 +10,8 @@ local feed = helpers.feed local nvim_prog = helpers.nvim_prog local ok = helpers.ok local rmdir = helpers.rmdir +local new_argv = helpers.new_argv +local pesc = helpers.pesc local os_kill = helpers.os_kill local set_session = helpers.set_session local spawn = helpers.spawn @@ -55,11 +57,11 @@ describe(':preserve', function() set swapfile fileformat=unix undolevels=-1 ]] - source(init) + exec(init) command('edit! '..testfile) feed('isometext<esc>') command('preserve') - source('redir => g:swapname | silent swapname | redir END') + exec('redir => g:swapname | silent swapname | redir END') local swappath1 = eval('g:swapname') @@ -69,12 +71,12 @@ describe(':preserve', function() true) set_session(nvim2) - source(init) + exec(init) -- Use the "SwapExists" event to choose the (R)ecover choice at the dialog. command('autocmd SwapExists * let v:swapchoice = "r"') command('silent edit! '..testfile) - source('redir => g:swapname | silent swapname | redir END') + exec('redir => g:swapname | silent swapname | redir END') local swappath2 = eval('g:swapname') @@ -92,25 +94,28 @@ end) describe('swapfile detection', function() local swapdir = lfs.currentdir()..'/Xtest_swapdialog_dir' + local nvim0 + -- Put swapdir at the start of the 'directory' list. #1836 + -- Note: `set swapfile` *must* go after `set directory`: otherwise it may + -- attempt to create a swapfile in different directory. + local init = [[ + set directory^=]]..swapdir:gsub([[\]], [[\\]])..[[// + set swapfile fileformat=unix undolevels=-1 hidden + ]] before_each(function() - clear() + nvim0 = spawn(new_argv()) + set_session(nvim0) rmdir(swapdir) lfs.mkdir(swapdir) end) after_each(function() + set_session(nvim0) command('%bwipeout!') rmdir(swapdir) end) it('always show swapfile dialog #8840 #9027', function() local testfile = 'Xtest_swapdialog_file1' - -- Put swapdir at the start of the 'directory' list. #1836 - -- Note: `set swapfile` *must* go after `set directory`: otherwise it may - -- attempt to create a swapfile in different directory. - local init = [[ - set directory^=]]..swapdir:gsub([[\]], [[\\]])..[[// - set swapfile fileformat=unix undolevels=-1 hidden - ]] local expected_no_dialog = '^'..(' '):rep(256)..'|\n' for _=1,37 do @@ -119,19 +124,17 @@ describe('swapfile detection', function() expected_no_dialog = expected_no_dialog..testfile..(' '):rep(216)..'0,0-1 All|\n' expected_no_dialog = expected_no_dialog..(' '):rep(256)..'|\n' - source(init) + exec(init) command('edit! '..testfile) feed('isometext<esc>') command('preserve') - os_kill(eval('getpid()')) -- Start another Nvim instance. - local nvim2 = spawn({nvim_prog, '-u', 'NONE', '-i', 'NONE', '--embed'}, - true) + local nvim2 = spawn({nvim_prog, '-u', 'NONE', '-i', 'NONE', '--embed'}, true, nil, true) set_session(nvim2) local screen2 = Screen.new(256, 40) screen2:attach() - source(init) + exec(init) -- With shortmess+=F command('set shortmess+=F') @@ -176,5 +179,88 @@ describe('swapfile detection', function() } }) feed('<cr>') + + nvim2:close() + end) + + -- oldtest: Test_swap_prompt_splitwin() + it('selecting "q" in the attention prompt', function() + exec(init) + command('edit Xfile1') + command('preserve') -- should help to make sure the swap file exists + + local screen = Screen.new(75, 18) + screen:set_default_attr_ids({ + [0] = {bold = true, foreground = Screen.colors.Blue}, -- NonText + [1] = {bold = true, foreground = Screen.colors.SeaGreen}, -- MoreMsg + }) + + local nvim1 = spawn(new_argv(), true, nil, true) + set_session(nvim1) + screen:attach() + exec(init) + feed(':split Xfile1\n') + screen:expect({ + any = pesc('{1:[O]pen Read-Only, (E)dit anyway, (R)ecover, (Q)uit, (A)bort: }^') + }) + feed('q') + feed(':<CR>') + screen:expect([[ + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + : | + ]]) + nvim1:close() + + local nvim2 = spawn(new_argv(), true, nil, true) + set_session(nvim2) + screen:attach() + exec(init) + command('set more') + command('au bufadd * let foo_w = wincol()') + feed(':e Xfile1<CR>') + screen:expect({any = pesc('{1:-- More --}^')}) + feed('<Space>') + screen:expect({ + any = pesc('{1:[O]pen Read-Only, (E)dit anyway, (R)ecover, (Q)uit, (A)bort: }^') + }) + feed('q') + command([[echo 'hello']]) + screen:expect([[ + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + hello | + ]]) + nvim2:close() end) end) diff --git a/test/functional/ex_cmds/wincmd_spec.lua b/test/functional/ex_cmds/wincmd_spec.lua new file mode 100644 index 0000000000..b1f174f445 --- /dev/null +++ b/test/functional/ex_cmds/wincmd_spec.lua @@ -0,0 +1,13 @@ +local helpers = require("test.functional.helpers")(after_each) +local clear = helpers.clear +local eq = helpers.eq +local funcs = helpers.funcs +local command = helpers.command + +it(':wincmd accepts a count', function() + clear() + command('vsplit') + eq(1, funcs.winnr()) + command('wincmd 2 w') + eq(2, funcs.winnr()) +end) diff --git a/test/functional/fixtures/compdir/file1 b/test/functional/fixtures/wildpum/Xdir/XdirA/XdirB/XfileC index e69de29bb2..e69de29bb2 100644 --- a/test/functional/fixtures/compdir/file1 +++ b/test/functional/fixtures/wildpum/Xdir/XdirA/XdirB/XfileC diff --git a/test/functional/fixtures/compdir/file2 b/test/functional/fixtures/wildpum/Xdir/XdirA/XfileB index e69de29bb2..e69de29bb2 100644 --- a/test/functional/fixtures/compdir/file2 +++ b/test/functional/fixtures/wildpum/Xdir/XdirA/XfileB diff --git a/test/functional/fixtures/wildpum/Xdir/XfileA b/test/functional/fixtures/wildpum/Xdir/XfileA new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/test/functional/fixtures/wildpum/Xdir/XfileA diff --git a/test/functional/fixtures/wildpum/compdir/file1 b/test/functional/fixtures/wildpum/compdir/file1 new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/test/functional/fixtures/wildpum/compdir/file1 diff --git a/test/functional/fixtures/wildpum/compdir/file2 b/test/functional/fixtures/wildpum/compdir/file2 new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/test/functional/fixtures/wildpum/compdir/file2 diff --git a/test/functional/fixtures/wildpum/あいう/123 b/test/functional/fixtures/wildpum/あいう/123 new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/test/functional/fixtures/wildpum/あいう/123 diff --git a/test/functional/fixtures/wildpum/あいう/abc b/test/functional/fixtures/wildpum/あいう/abc new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/test/functional/fixtures/wildpum/あいう/abc diff --git a/test/functional/fixtures/wildpum/あいう/xyz b/test/functional/fixtures/wildpum/あいう/xyz new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/test/functional/fixtures/wildpum/あいう/xyz diff --git a/test/functional/helpers.lua b/test/functional/helpers.lua index 8c5a60657a..93fb0f245e 100644 --- a/test/functional/helpers.lua +++ b/test/functional/helpers.lua @@ -244,10 +244,12 @@ function module.run_session(lsession, request_cb, notification_cb, setup_cb, tim last_error = nil error(err) end + + return session.eof_err end function module.run(request_cb, notification_cb, setup_cb, timeout) - module.run_session(session, request_cb, notification_cb, setup_cb, timeout) + return module.run_session(session, request_cb, notification_cb, setup_cb, timeout) end function module.stop() @@ -759,6 +761,7 @@ function module.pending_c_parser(pending_fn) pending_fn 'no C parser, skipping' return true end + module.exec_lua [[vim._ts_remove_language 'c']] return false end diff --git a/test/functional/legacy/arglist_spec.lua b/test/functional/legacy/arglist_spec.lua index f90da16d7b..4d9e88c446 100644 --- a/test/functional/legacy/arglist_spec.lua +++ b/test/functional/legacy/arglist_spec.lua @@ -276,6 +276,6 @@ describe('argument list commands', function() 2 more files to edit. Quit anyway? | [Y]es, (N)o: ^ | ]]) - expect_exit(100, feed, 'Y') + expect_exit(1000, feed, 'Y') end) end) diff --git a/test/functional/legacy/buffer_spec.lua b/test/functional/legacy/buffer_spec.lua new file mode 100644 index 0000000000..acaa9a51f1 --- /dev/null +++ b/test/functional/legacy/buffer_spec.lua @@ -0,0 +1,59 @@ +local helpers = require('test.functional.helpers')(after_each) +local clear, source = helpers.clear, helpers.source +local call, eq, meths = helpers.call, helpers.eq, helpers.meths + +local function expected_empty() + eq({}, meths.get_vvar('errors')) +end + +describe('buffer', function() + before_each(function() + clear() + meths.ui_attach(80, 24, {}) + meths.set_option('hidden', false) + end) + + it('deleting a modified buffer with :confirm', function() + source([[ + func Test_bdel_with_confirm() + new + call setline(1, 'test') + call assert_fails('bdel', 'E89:') + call nvim_input('c') + confirm bdel + call assert_equal(2, winnr('$')) + call assert_equal(1, &modified) + call nvim_input('n') + confirm bdel + call assert_equal(1, winnr('$')) + endfunc + ]]) + call('Test_bdel_with_confirm') + expected_empty() + end) + + it('editing another buffer from a modified buffer with :confirm', function() + source([[ + func Test_goto_buf_with_confirm() + new Xfile + enew + call setline(1, 'test') + call assert_fails('b Xfile', 'E37:') + call nvim_input('c') + call assert_fails('confirm b Xfile', 'E37:') + call assert_equal(1, &modified) + call assert_equal('', @%) + call nvim_input('y') + call assert_fails('confirm b Xfile', 'E37:') + call assert_equal(1, &modified) + call assert_equal('', @%) + call nvim_input('n') + confirm b Xfile + call assert_equal('Xfile', @%) + close! + endfunc + ]]) + call('Test_goto_buf_with_confirm') + expected_empty() + end) +end) diff --git a/test/functional/legacy/cmdline_spec.lua b/test/functional/legacy/cmdline_spec.lua index cf02636890..99d2d0f30e 100644 --- a/test/functional/legacy/cmdline_spec.lua +++ b/test/functional/legacy/cmdline_spec.lua @@ -1,9 +1,11 @@ local helpers = require('test.functional.helpers')(after_each) local Screen = require('test.functional.ui.screen') local clear = helpers.clear +local command = helpers.command local feed = helpers.feed local feed_command = helpers.feed_command local exec = helpers.exec +local pesc = helpers.pesc describe('cmdline', function() before_each(clear) @@ -18,8 +20,6 @@ describe('cmdline', function() [3] = {reverse = true}; [4] = {bold = true, foreground = Screen.colors.Blue1}; } - -- TODO(bfredl): redraw with tabs is severly broken. fix it - feed_command [[ set display-=msgsep ]] feed_command([[call setline(1, range(30))]]) screen:expect([[ @@ -60,7 +60,7 @@ describe('cmdline', function() {4:~ }| | | - :tabnew | + | ]]} feed [[gt]] @@ -141,3 +141,81 @@ describe('cmdline', function() ]]) end) end) + +describe('cmdwin', function() + before_each(clear) + + -- oldtest: Test_cmdwin_interrupted() + it('still uses a new buffer when interrupting more prompt on open', function() + local screen = Screen.new(30, 16) + screen:set_default_attr_ids({ + [0] = {bold = true, foreground = Screen.colors.Blue}, -- NonText + [1] = {bold = true, reverse = true}, -- StatusLine + [2] = {reverse = true}, -- StatusLineNC + [3] = {bold = true, foreground = Screen.colors.SeaGreen}, -- MoreMsg + [4] = {bold = true}, -- ModeMsg + }) + screen:attach() + command('set more') + command('autocmd WinNew * highlight') + feed('q:') + screen:expect({any = pesc('{3:-- More --}^')}) + feed('q') + screen:expect([[ + | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {2:[No Name] }| + {0::}^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {1:[Command Line] }| + | + ]]) + feed([[aecho 'done']]) + screen:expect([[ + | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {2:[No Name] }| + {0::}echo 'done'^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {1:[Command Line] }| + {4:-- INSERT --} | + ]]) + feed('<CR>') + screen:expect([[ + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + done | + ]]) + end) +end) diff --git a/test/functional/legacy/ex_mode_spec.lua b/test/functional/legacy/ex_mode_spec.lua index a8f54c6939..f21c47e175 100644 --- a/test/functional/legacy/ex_mode_spec.lua +++ b/test/functional/legacy/ex_mode_spec.lua @@ -6,7 +6,7 @@ local eq = helpers.eq local eval = helpers.eval local feed = helpers.feed local meths = helpers.meths -local sleep = helpers.sleep +local poke_eventloop = helpers.poke_eventloop before_each(clear) @@ -143,7 +143,7 @@ describe('Ex mode', function() ^ | ]]) feed('<C-C>') - sleep(10) -- Wait for input to be flushed + poke_eventloop() -- Wait for input to be flushed feed('foo<CR>') screen:expect([[ Entering Ex mode. Type "visual" to go to Normal mode. | diff --git a/test/functional/legacy/excmd_spec.lua b/test/functional/legacy/excmd_spec.lua index 65957d85de..ece88d26bd 100644 --- a/test/functional/legacy/excmd_spec.lua +++ b/test/functional/legacy/excmd_spec.lua @@ -94,7 +94,7 @@ describe(':confirm command dialog', function() {3:Save changes to "Xbar"?} | {3:[Y]es, (N)o, Save (A)ll, (D)iscard All, (C)ancel: }^ | ]]) - expect_exit(100, feed, 'A') + expect_exit(1000, feed, 'A') eq('foo2\n', read_file('Xfoo')) eq('bar2\n', read_file('Xbar')) @@ -132,7 +132,7 @@ describe(':confirm command dialog', function() {3:Save changes to "Xbar"?} | {3:[Y]es, (N)o, Save (A)ll, (D)iscard All, (C)ancel: }^ | ]]) - expect_exit(100, feed, 'D') + expect_exit(1000, feed, 'D') eq('foo2\n', read_file('Xfoo')) eq('bar2\n', read_file('Xbar')) @@ -193,7 +193,7 @@ describe(':confirm command dialog', function() {3:Save changes to "Xfoo"?} | {3:[Y]es, (N)o, (C)ancel: }^ | ]]) - expect_exit(100, feed, 'Y') + expect_exit(1000, feed, 'Y') eq('foo4\n', read_file('Xfoo')) eq('bar2\n', read_file('Xbar')) diff --git a/test/functional/legacy/filechanged_spec.lua b/test/functional/legacy/filechanged_spec.lua index ecb861098c..1f23528d61 100644 --- a/test/functional/legacy/filechanged_spec.lua +++ b/test/functional/legacy/filechanged_spec.lua @@ -67,6 +67,15 @@ describe('file changed dialog', function() call assert_equal(1, line('$')) call assert_equal('new line', getline(1)) + " File created after starting to edit it + call delete('Xchanged_d') + new Xchanged_d + call writefile(['one'], 'Xchanged_d') + call nvim_input('L') + checktime Xchanged_d + call assert_equal(['one'], getline(1, '$')) + close! + bwipe! call delete('Xchanged_d') endfunc diff --git a/test/functional/legacy/gf_spec.lua b/test/functional/legacy/gf_spec.lua new file mode 100644 index 0000000000..f1b1790ba1 --- /dev/null +++ b/test/functional/legacy/gf_spec.lua @@ -0,0 +1,15 @@ +local helpers = require('test.functional.helpers')(after_each) +local clear = helpers.clear +local command = helpers.command +local eq = helpers.eq +local pcall_err = helpers.pcall_err + +describe('gf', function() + before_each(clear) + + 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')) + end) +end) diff --git a/test/functional/legacy/global_spec.lua b/test/functional/legacy/global_spec.lua index 9f4528530c..ff02c41e6c 100644 --- a/test/functional/legacy/global_spec.lua +++ b/test/functional/legacy/global_spec.lua @@ -3,7 +3,7 @@ local Screen = require('test.functional.ui.screen') local clear = helpers.clear local exec = helpers.exec local feed = helpers.feed -local sleep = helpers.sleep +local poke_eventloop = helpers.poke_eventloop before_each(clear) @@ -24,7 +24,7 @@ describe(':global', function() ]]) feed(':g/foo/norm :<C-V>;<CR>') - sleep(10) -- Wait for :sleep to start + poke_eventloop() -- Wait for :sleep to start feed('<C-C>') screen:expect([[ ^foo | @@ -37,7 +37,7 @@ describe(':global', function() -- Also test in Ex mode feed('gQg/foo/norm :<C-V>;<CR>') - sleep(10) -- Wait for :sleep to start + poke_eventloop() -- Wait for :sleep to start feed('<C-C>') screen:expect([[ {0: }| diff --git a/test/functional/legacy/mapping_spec.lua b/test/functional/legacy/mapping_spec.lua index 456acc12b5..c1f23ab0a6 100644 --- a/test/functional/legacy/mapping_spec.lua +++ b/test/functional/legacy/mapping_spec.lua @@ -131,11 +131,11 @@ describe('mapping', function() command('set selectmode=mouse') command('nnoremap <LeftDrag> <LeftDrag><Cmd><CR>') - sleep(10) + poke_eventloop() meths.input_mouse('left', 'press', '', 0, 0, 0) - sleep(10) + poke_eventloop() meths.input_mouse('left', 'drag', '', 0, 0, 1) - sleep(10) + poke_eventloop() eq('s', eval('mode()')) end) @@ -144,22 +144,22 @@ describe('mapping', function() command('inoremap <LeftDrag> <LeftDrag><Cmd>let g:dragged = 1<CR>') feed('i') - sleep(10) + poke_eventloop() meths.input_mouse('left', 'press', '', 0, 0, 0) - sleep(10) + poke_eventloop() meths.input_mouse('left', 'drag', '', 0, 0, 1) - sleep(10) + poke_eventloop() eq(1, eval('g:dragged')) eq('v', eval('mode()')) feed([[<C-\><C-N>]]) command([[inoremap <LeftDrag> <LeftDrag><C-\><C-N>]]) feed('i') - sleep(10) + poke_eventloop() meths.input_mouse('left', 'press', '', 0, 0, 0) - sleep(10) + poke_eventloop() meths.input_mouse('left', 'drag', '', 0, 0, 1) - sleep(10) + poke_eventloop() eq('n', eval('mode()')) end) diff --git a/test/functional/legacy/messages_spec.lua b/test/functional/legacy/messages_spec.lua index 51c2406933..159cf7a551 100644 --- a/test/functional/legacy/messages_spec.lua +++ b/test/functional/legacy/messages_spec.lua @@ -337,6 +337,95 @@ describe('messages', function() end) end) + describe('mode is cleared when', function() + before_each(function() + screen = Screen.new(40, 6) + screen:set_default_attr_ids({ + [1] = {bold = true, foreground = Screen.colors.Blue}, -- NonText + [2] = {bold = true}, -- ModeMsg + [3] = {bold = true, reverse=true}, -- StatusLine + }) + screen:attach() + end) + + -- oldtest: Test_mode_message_at_leaving_insert_by_ctrl_c() + it('leaving Insert mode with Ctrl-C vim-patch:8.1.1189', function() + exec([[ + func StatusLine() abort + return "" + endfunc + set statusline=%!StatusLine() + set laststatus=2 + ]]) + feed('i') + screen:expect([[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {3: }| + {2:-- INSERT --} | + ]]) + feed('<C-C>') + screen:expect([[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {3: }| + | + ]]) + end) + + -- oldtest: Test_mode_message_at_leaving_insert_with_esc_mapped() + it('leaving Insert mode with ESC in the middle of a mapping vim-patch:8.1.1192', function() + exec([[ + set laststatus=2 + inoremap <Esc> <Esc>00 + ]]) + feed('i') + screen:expect([[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {3:[No Name] }| + {2:-- INSERT --} | + ]]) + feed('<Esc>') + screen:expect([[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {3:[No Name] }| + | + ]]) + end) + + -- oldtest: Test_mode_updated_after_ctrl_c() + it('pressing Ctrl-C in i_CTRL-O', function() + feed('i<C-O>') + screen:expect([[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {2:-- (insert) --} | + ]]) + feed('<C-C>') + screen:expect([[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + end) + end) + -- oldtest: Test_ask_yesno() it('y/n prompt works', function() screen = Screen.new(75, 6) diff --git a/test/functional/lua/diagnostic_spec.lua b/test/functional/lua/diagnostic_spec.lua index f9647f5b6a..4226bcebac 100644 --- a/test/functional/lua/diagnostic_spec.lua +++ b/test/functional/lua/diagnostic_spec.lua @@ -128,6 +128,28 @@ describe('vim.diagnostic', function() eq('Diagnostic #1', result[1].message) end) + it('removes diagnostics from the cache when a buffer is removed', function() + eq(2, exec_lua [[ + vim.api.nvim_win_set_buf(0, diagnostic_bufnr) + local other_bufnr = vim.fn.bufadd('test | test') + local lines = vim.api.nvim_buf_get_lines(diagnostic_bufnr, 0, -1, true) + vim.api.nvim_buf_set_lines(other_bufnr, 0, 1, false, lines) + vim.cmd('bunload! ' .. other_bufnr) + + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { + make_error('Diagnostic #1', 1, 1, 1, 1), + make_error('Diagnostic #2', 2, 1, 2, 1), + }) + vim.diagnostic.set(diagnostic_ns, other_bufnr, { + make_error('Diagnostic #3', 3, 1, 3, 1), + }) + vim.api.nvim_set_current_buf(other_bufnr) + vim.opt_local.buflisted = true + vim.cmd('bwipeout!') + return #vim.diagnostic.get() + ]]) + end) + it('resolves buffer number 0 to the current buffer', function() eq(2, exec_lua [[ vim.api.nvim_set_current_buf(diagnostic_bufnr) diff --git a/test/functional/lua/luaeval_spec.lua b/test/functional/lua/luaeval_spec.lua index 1322155595..9f313eab9e 100644 --- a/test/functional/lua/luaeval_spec.lua +++ b/test/functional/lua/luaeval_spec.lua @@ -467,7 +467,6 @@ describe('v:lua', function() end end end - vim.api.nvim_buf_set_option(0, 'omnifunc', 'v:lua.mymod.omni') ]]) end) @@ -515,6 +514,7 @@ describe('v:lua', function() [5] = {bold = true, foreground = Screen.colors.SeaGreen4}, }) screen:attach() + meths.buf_set_option(0, 'omnifunc', 'v:lua.mymod.omni') feed('isome st<c-x><c-o>') screen:expect{grid=[[ some stuff^ | @@ -526,6 +526,9 @@ describe('v:lua', function() {1:~ }| {4:-- Omni completion (^O^N^P) }{5:match 1 of 3} | ]]} + meths.set_option('operatorfunc', 'v:lua.mymod.noisy') + feed('<Esc>g@g@') + eq("hey line", meths.get_current_line()) end) it('supports packages', function() diff --git a/test/functional/lua/overrides_spec.lua b/test/functional/lua/overrides_spec.lua index 9b51af1eec..32c1615a45 100644 --- a/test/functional/lua/overrides_spec.lua +++ b/test/functional/lua/overrides_spec.lua @@ -144,13 +144,14 @@ describe('debug.debug', function() before_each(function() screen = Screen.new() screen:attach() - screen:set_default_attr_ids({ - [0] = {bold=true, foreground=255}, - E = {foreground = Screen.colors.Grey100, background = Screen.colors.Red}, - cr = {bold = true, foreground = Screen.colors.SeaGreen4}, - }) - command("set display-=msgsep") + screen:set_default_attr_ids { + [0] = {bold=true, foreground=255}; + [1] = {bold = true, reverse = true}; + E = {foreground = Screen.colors.Grey100, background = Screen.colors.Red}; + cr = {bold = true, foreground = Screen.colors.SeaGreen4}; + } end) + it('works', function() command([[lua function Test(a) @@ -160,9 +161,8 @@ describe('debug.debug', function() end ]]) feed(':lua Test()\n') - screen:expect([[ - {0:~ }| - {0:~ }| + screen:expect{grid=[[ + | {0:~ }| {0:~ }| {0:~ }| @@ -173,11 +173,13 @@ describe('debug.debug', function() {0:~ }| {0:~ }| {0:~ }| + {1: }| nil | lua_debug> ^ | - ]]) + ]]} feed('print("TEST")\n') screen:expect([[ + | {0:~ }| {0:~ }| {0:~ }| @@ -186,8 +188,7 @@ describe('debug.debug', function() {0:~ }| {0:~ }| {0:~ }| - {0:~ }| - {0:~ }| + {1: }| nil | lua_debug> print("TEST") | TEST | @@ -195,10 +196,10 @@ describe('debug.debug', function() ]]) feed('<C-c>') screen:expect{grid=[[ + | {0:~ }| {0:~ }| - {0:~ }| - {0:~ }| + {1: }| nil | lua_debug> print("TEST") | TEST | @@ -212,6 +213,7 @@ describe('debug.debug', function() ]]} feed('<C-l>:lua Test()\n') screen:expect([[ + | {0:~ }| {0:~ }| {0:~ }| @@ -222,19 +224,18 @@ describe('debug.debug', function() {0:~ }| {0:~ }| {0:~ }| - {0:~ }| - {0:~ }| + {1: }| nil | lua_debug> ^ | ]]) feed('\n') screen:expect{grid=[[ + | {0:~ }| {0:~ }| {0:~ }| {0:~ }| - {0:~ }| - {0:~ }| + {1: }| nil | lua_debug> | {E:E5108: Error executing lua [string ":lua"]:5: attempt}| @@ -268,6 +269,7 @@ describe('debug.debug', function() feed("conttt<cr>") -- misspelled cont; invalid syntax screen:expect{grid=[[ + | {0:~ }| {0:~ }| {0:~ }| @@ -276,8 +278,7 @@ describe('debug.debug', function() {0:~ }| {0:~ }| {0:~ }| - {0:~ }| - {0:~ }| + {1: }| lua_debug> conttt | {E:E5115: Error while loading debug string: (debug comma}| {E:nd):1: '=' expected near '<eof>'} | @@ -286,14 +287,14 @@ describe('debug.debug', function() feed("cont<cr>") -- exactly "cont", exit now screen:expect{grid=[[ + | {0:~ }| {0:~ }| {0:~ }| {0:~ }| {0:~ }| {0:~ }| - {0:~ }| - {0:~ }| + {1: }| lua_debug> conttt | {E:E5115: Error while loading debug string: (debug comma}| {E:nd):1: '=' expected near '<eof>'} | diff --git a/test/functional/lua/vim_spec.lua b/test/functional/lua/vim_spec.lua index 2b249b7a69..f2fb661b70 100644 --- a/test/functional/lua/vim_spec.lua +++ b/test/functional/lua/vim_spec.lua @@ -2721,6 +2721,39 @@ describe('lua stdlib', function() ]] end) end) + + describe('vim.iconv', function() + it('can convert strings', function() + eq('hello', exec_lua[[ + return vim.iconv('hello', 'latin1', 'utf-8') + ]]) + end) + + it('can validate arguments', function() + eq({false, 'Expected at least 3 arguments'}, exec_lua[[ + return {pcall(vim.iconv, 'hello')} + ]]) + + eq({false, 'bad argument #3 to \'?\' (expected string)'}, exec_lua[[ + return {pcall(vim.iconv, 'hello', 'utf-8', true)} + ]]) + end) + + it('can handle bad encodings', function() + eq(NIL, exec_lua[[ + return vim.iconv('hello', 'foo', 'bar') + ]]) + end) + + it('can handle strings with NUL bytes', function() + eq(7, exec_lua[[ + local a = string.char(97, 98, 99, 0, 100, 101, 102) -- abc\0def + return string.len(vim.iconv(a, 'latin1', 'utf-8')) + ]]) + end) + + end) + end) describe('lua: builtin modules', function() diff --git a/test/functional/options/defaults_spec.lua b/test/functional/options/defaults_spec.lua index 9244ca0974..4e2f2ab63e 100644 --- a/test/functional/options/defaults_spec.lua +++ b/test/functional/options/defaults_spec.lua @@ -161,6 +161,36 @@ describe('startup defaults', function() ~ |~ | | ]]) + + -- change "vert" character to single-cell + funcs.setcellwidths({{0x2502, 0x2502, 1}}) + screen:expect([[ + 1 │1 | + ^+-- 2 lines: 2----------│+-- 2 lines: 2---------| + 4 │4 | + ~ │~ | + | + ]]) + + -- change "vert" character to double-cell + funcs.setcellwidths({{0x2502, 0x2502, 2}}) + screen:expect([[ + 1 |1 | + ^+-- 2 lines: 2----------|+-- 2 lines: 2---------| + 4 |4 | + ~ |~ | + | + ]]) + + -- "vert" character should still default to single-byte fillchars because of setcellwidths(). + command('set ambiwidth=single') + screen:expect([[ + 1 |1 | + ^+-- 2 lines: 2··········|+-- 2 lines: 2·········| + 4 |4 | + ~ |~ | + | + ]]) end) end) diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index cd7415de90..fa731f6faf 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -3181,4 +3181,32 @@ describe('LSP', function() } end) end) + + describe('cmd', function() + it('can connect to lsp server via rpc.connect', function() + local result = exec_lua [[ + local uv = vim.loop + local server = uv.new_tcp() + local init = nil + server:bind('127.0.0.1', 0) + server:listen(127, function(err) + assert(not err, err) + local socket = uv.new_tcp() + server:accept(socket) + socket:read_start(require('vim.lsp.rpc').create_read_loop(function(body) + init = body + socket:close() + end)) + end) + local port = server:getsockname().port + vim.lsp.start({ name = 'dummy', cmd = vim.lsp.rpc.connect('127.0.0.1', port) }) + vim.wait(1000, function() return init ~= nil end) + assert(init, "server must receive `initialize` request") + server:close() + server:shutdown() + return vim.json.decode(init) + ]] + eq(result.method, "initialize") + end) + end) end) diff --git a/test/functional/plugin/man_spec.lua b/test/functional/plugin/man_spec.lua index c8da5a711f..9304aa6da9 100644 --- a/test/functional/plugin/man_spec.lua +++ b/test/functional/plugin/man_spec.lua @@ -6,6 +6,12 @@ local funcs = helpers.funcs local nvim_prog = helpers.nvim_prog local matches = helpers.matches +clear() +if funcs.executable('man') == 0 then + pending('missing "man" command', function() end) + return +end + describe(':Man', function() before_each(function() clear() diff --git a/test/functional/provider/clipboard_spec.lua b/test/functional/provider/clipboard_spec.lua index 5bdfec574e..fbaef3ae00 100644 --- a/test/functional/provider/clipboard_spec.lua +++ b/test/functional/provider/clipboard_spec.lua @@ -96,9 +96,9 @@ describe('clipboard', function() [0] = {bold = true, foreground = Screen.colors.Blue}, [1] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red}, [2] = {bold = true, foreground = Screen.colors.SeaGreen4}, + [3] = {bold = true, reverse = true}; }) screen:attach() - command("set display-=msgsep") end) it('unnamed register works without provider', function() @@ -123,10 +123,10 @@ describe('clipboard', function() command("let g:clipboard = 'bogus'") feed_command('redir @+> | bogus_cmd | redir END') screen:expect{grid=[[ - {0:~ }| - clipboard: No provider. Try ":checkhealth" or ":h clipboard". | - {1:E492: Not an editor command: bogus_cmd | redir END} | - {2:Press ENTER or type command to continue}^ | + {3: }| + clipboard: No provider. Try ":checkhealth" or ":h clipboard". | + {1:E492: Not an editor command: bogus_cmd | redir END} | + {2:Press ENTER or type command to continue}^ | ]]} end) diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index eee759d2be..4f444316c3 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -297,6 +297,199 @@ describe('TUI', function() ]], attrs) end) + it('accepts mouse wheel events #19992', function() + child_session:request('nvim_command', [[ + set number nostartofline nowrap mousescroll=hor:1,ver:1 + call setline(1, repeat([join(range(10), '----')], 10)) + vsplit + ]]) + screen:expect([[ + {11: 1 }{1:0}----1----2----3----4│{11: 1 }0----1----2----3----| + {11: 2 }0----1----2----3----4│{11: 2 }0----1----2----3----| + {11: 3 }0----1----2----3----4│{11: 3 }0----1----2----3----| + {11: 4 }0----1----2----3----4│{11: 4 }0----1----2----3----| + {5:[No Name] [+] }{1:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]]) + -- <ScrollWheelDown> in active window + feed_data('\027[<65;8;1M') + screen:expect([[ + {11: 2 }{1:0}----1----2----3----4│{11: 1 }0----1----2----3----| + {11: 3 }0----1----2----3----4│{11: 2 }0----1----2----3----| + {11: 4 }0----1----2----3----4│{11: 3 }0----1----2----3----| + {11: 5 }0----1----2----3----4│{11: 4 }0----1----2----3----| + {5:[No Name] [+] }{1:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]]) + -- <ScrollWheelDown> in inactive window + feed_data('\027[<65;48;1M') + screen:expect([[ + {11: 2 }{1:0}----1----2----3----4│{11: 2 }0----1----2----3----| + {11: 3 }0----1----2----3----4│{11: 3 }0----1----2----3----| + {11: 4 }0----1----2----3----4│{11: 4 }0----1----2----3----| + {11: 5 }0----1----2----3----4│{11: 5 }0----1----2----3----| + {5:[No Name] [+] }{1:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]]) + -- <ScrollWheelRight> in active window + feed_data('\027[<67;8;1M') + screen:expect([[ + {11: 2 }{1:-}---1----2----3----4-│{11: 2 }0----1----2----3----| + {11: 3 }----1----2----3----4-│{11: 3 }0----1----2----3----| + {11: 4 }----1----2----3----4-│{11: 4 }0----1----2----3----| + {11: 5 }----1----2----3----4-│{11: 5 }0----1----2----3----| + {5:[No Name] [+] }{1:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]]) + -- <ScrollWheelRight> in inactive window + feed_data('\027[<67;48;1M') + screen:expect([[ + {11: 2 }{1:-}---1----2----3----4-│{11: 2 }----1----2----3----4| + {11: 3 }----1----2----3----4-│{11: 3 }----1----2----3----4| + {11: 4 }----1----2----3----4-│{11: 4 }----1----2----3----4| + {11: 5 }----1----2----3----4-│{11: 5 }----1----2----3----4| + {5:[No Name] [+] }{1:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]]) + -- <S-ScrollWheelDown> in active window + feed_data('\027[<69;8;1M') + screen:expect([[ + {11: 5 }{1:-}---1----2----3----4-│{11: 2 }----1----2----3----4| + {11: 6 }----1----2----3----4-│{11: 3 }----1----2----3----4| + {11: 7 }----1----2----3----4-│{11: 4 }----1----2----3----4| + {11: 8 }----1----2----3----4-│{11: 5 }----1----2----3----4| + {5:[No Name] [+] }{1:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]]) + -- <S-ScrollWheelDown> in inactive window + feed_data('\027[<69;48;1M') + screen:expect([[ + {11: 5 }{1:-}---1----2----3----4-│{11: 5 }----1----2----3----4| + {11: 6 }----1----2----3----4-│{11: 6 }----1----2----3----4| + {11: 7 }----1----2----3----4-│{11: 7 }----1----2----3----4| + {11: 8 }----1----2----3----4-│{11: 8 }----1----2----3----4| + {5:[No Name] [+] }{1:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]]) + -- <S-ScrollWheelRight> in active window + feed_data('\027[<71;8;1M') + screen:expect([[ + {11: 5 }{1:-}---6----7----8----9 │{11: 5 }----1----2----3----4| + {11: 6 }----6----7----8----9 │{11: 6 }----1----2----3----4| + {11: 7 }----6----7----8----9 │{11: 7 }----1----2----3----4| + {11: 8 }----6----7----8----9 │{11: 8 }----1----2----3----4| + {5:[No Name] [+] }{1:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]]) + -- <S-ScrollWheelRight> in inactive window + feed_data('\027[<71;48;1M') + screen:expect([[ + {11: 5 }{1:-}---6----7----8----9 │{11: 5 }5----6----7----8----| + {11: 6 }----6----7----8----9 │{11: 6 }5----6----7----8----| + {11: 7 }----6----7----8----9 │{11: 7 }5----6----7----8----| + {11: 8 }----6----7----8----9 │{11: 8 }5----6----7----8----| + {5:[No Name] [+] }{1:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]]) + -- <ScrollWheelUp> in active window + feed_data('\027[<64;8;1M') + screen:expect([[ + {11: 4 }----6----7----8----9 │{11: 5 }5----6----7----8----| + {11: 5 }{1:-}---6----7----8----9 │{11: 6 }5----6----7----8----| + {11: 6 }----6----7----8----9 │{11: 7 }5----6----7----8----| + {11: 7 }----6----7----8----9 │{11: 8 }5----6----7----8----| + {5:[No Name] [+] }{1:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]]) + -- <ScrollWheelUp> in inactive window + feed_data('\027[<64;48;1M') + screen:expect([[ + {11: 4 }----6----7----8----9 │{11: 4 }5----6----7----8----| + {11: 5 }{1:-}---6----7----8----9 │{11: 5 }5----6----7----8----| + {11: 6 }----6----7----8----9 │{11: 6 }5----6----7----8----| + {11: 7 }----6----7----8----9 │{11: 7 }5----6----7----8----| + {5:[No Name] [+] }{1:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]]) + -- <ScrollWheelLeft> in active window + feed_data('\027[<66;8;1M') + screen:expect([[ + {11: 4 }5----6----7----8----9│{11: 4 }5----6----7----8----| + {11: 5 }5{1:-}---6----7----8----9│{11: 5 }5----6----7----8----| + {11: 6 }5----6----7----8----9│{11: 6 }5----6----7----8----| + {11: 7 }5----6----7----8----9│{11: 7 }5----6----7----8----| + {5:[No Name] [+] }{1:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]]) + -- <ScrollWheelLeft> in inactive window + feed_data('\027[<66;48;1M') + screen:expect([[ + {11: 4 }5----6----7----8----9│{11: 4 }-5----6----7----8---| + {11: 5 }5{1:-}---6----7----8----9│{11: 5 }-5----6----7----8---| + {11: 6 }5----6----7----8----9│{11: 6 }-5----6----7----8---| + {11: 7 }5----6----7----8----9│{11: 7 }-5----6----7----8---| + {5:[No Name] [+] }{1:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]]) + -- <S-ScrollWheelUp> in active window + feed_data('\027[<68;8;1M') + screen:expect([[ + {11: 1 }5----6----7----8----9│{11: 4 }-5----6----7----8---| + {11: 2 }5----6----7----8----9│{11: 5 }-5----6----7----8---| + {11: 3 }5----6----7----8----9│{11: 6 }-5----6----7----8---| + {11: 4 }5{1:-}---6----7----8----9│{11: 7 }-5----6----7----8---| + {5:[No Name] [+] }{1:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]]) + -- <S-ScrollWheelUp> in inactive window + feed_data('\027[<68;48;1M') + screen:expect([[ + {11: 1 }5----6----7----8----9│{11: 1 }-5----6----7----8---| + {11: 2 }5----6----7----8----9│{11: 2 }-5----6----7----8---| + {11: 3 }5----6----7----8----9│{11: 3 }-5----6----7----8---| + {11: 4 }5{1:-}---6----7----8----9│{11: 4 }-5----6----7----8---| + {5:[No Name] [+] }{1:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]]) + -- <S-ScrollWheelLeft> in active window + feed_data('\027[<70;8;1M') + screen:expect([[ + {11: 1 }0----1----2----3----4│{11: 1 }-5----6----7----8---| + {11: 2 }0----1----2----3----4│{11: 2 }-5----6----7----8---| + {11: 3 }0----1----2----3----4│{11: 3 }-5----6----7----8---| + {11: 4 }0----1----2----3----{1:4}│{11: 4 }-5----6----7----8---| + {5:[No Name] [+] }{1:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]]) + -- <S-ScrollWheelLeft> in inactive window + feed_data('\027[<70;48;1M') + screen:expect([[ + {11: 1 }0----1----2----3----4│{11: 1 }0----1----2----3----| + {11: 2 }0----1----2----3----4│{11: 2 }0----1----2----3----| + {11: 3 }0----1----2----3----4│{11: 3 }0----1----2----3----| + {11: 4 }0----1----2----3----{1:4}│{11: 4 }0----1----2----3----| + {5:[No Name] [+] }{1:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]]) + end) + it('accepts keypad keys from kitty keyboard protocol #19180', function() feed_data('i') feed_data(funcs.nr2char(57399)) -- KP_0 @@ -1145,6 +1338,47 @@ describe('TUI', function() {3:-- TERMINAL --} | ]=]) 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_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: }| + ℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃| + ℃℃℃℃℃℃℃℃℃℃{4:$} | + {5:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]] + -- 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: }| + ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ >{4:@@@}| + {5:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]] + screen:expect(singlewidth_screen, attrs) + child_session:request('nvim_set_option', 'ambiwidth', 'double') + screen:expect(doublewidth_screen, attrs) + child_session:request('nvim_set_option', 'ambiwidth', 'single') + screen:expect(singlewidth_screen, attrs) + child_session:request('nvim_call_function', 'setcellwidths', {{{0x2103, 0x2103, 2}}}) + screen:expect(doublewidth_screen, attrs) + child_session:request('nvim_call_function', 'setcellwidths', {{{0x2103, 0x2103, 1}}}) + screen:expect(singlewidth_screen, attrs) + end) end) describe('TUI', function() diff --git a/test/functional/treesitter/highlight_spec.lua b/test/functional/treesitter/highlight_spec.lua index 5ec0a8a060..4b0bd1eb50 100644 --- a/test/functional/treesitter/highlight_spec.lua +++ b/test/functional/treesitter/highlight_spec.lua @@ -6,11 +6,14 @@ local insert = helpers.insert local exec_lua = helpers.exec_lua local feed = helpers.feed local pending_c_parser = helpers.pending_c_parser +local command = helpers.command +local meths = helpers.meths +local eq = helpers.eq before_each(clear) local hl_query = [[ - (ERROR) @ErrorMsg + (ERROR) @error "if" @keyword "else" @keyword @@ -23,23 +26,24 @@ local hl_query = [[ "enum" @type "extern" @type - (string_literal) @string.nonexistent-specializer-for-string.should-fallback-to-string + ; nonexistent specializer for string should fallback to string + (string_literal) @string.nonexistent_specializer (number_literal) @number (char_literal) @string (type_identifier) @type - ((type_identifier) @Special (#eq? @Special "LuaRef")) + ((type_identifier) @constant.builtin (#eq? @constant.builtin "LuaRef")) (primitive_type) @type (sized_type_specifier) @type ; Use lua regexes - ((identifier) @Identifier (#contains? @Identifier "lua_")) + ((identifier) @function (#contains? @function "lua_")) ((identifier) @Constant (#lua-match? @Constant "^[A-Z_]+$")) - ((identifier) @Normal (#vim-match? @Constant "^lstate$")) + ((identifier) @Normal (#vim-match? @Normal "^lstate$")) - ((binary_expression left: (identifier) @WarningMsg.left right: (identifier) @WarningMsg.right) (#eq? @WarningMsg.left @WarningMsg.right)) + ((binary_expression left: (identifier) @warning.left right: (identifier) @warning.right) (#eq? @warning.left @warning.right)) (comment) @comment ]] @@ -103,6 +107,7 @@ describe('treesitter highlighting', function() } exec_lua([[ hl_query = ... ]], hl_query) + command [[ hi link @warning WarningMsg ]] end) it('is updated with edits', function() @@ -547,7 +552,7 @@ describe('treesitter highlighting', function() -- This will change ONLY the literal strings to look like comments -- The only literal string is the "vim.schedule: expected function" in this test. - exec_lua [[vim.cmd("highlight link cString comment")]] + exec_lua [[vim.cmd("highlight link @string.nonexistent_specializer comment")]] screen:expect{grid=[[ {2:/// Schedule Lua callback on main loop's event queue} | {3:static} {3:int} {11:nlua_schedule}({3:lua_State} *{3:const} lstate) | @@ -612,6 +617,11 @@ describe('treesitter highlighting', function() -- bold will not be overwritten at the moment [12] = {background = Screen.colors.Red, bold = true, foreground = Screen.colors.Grey100}; }} + + eq({ + {capture='Error', priority='101'}; + {capture='type'}; + }, exec_lua [[ return vim.treesitter.get_captures_at_position(0, 0, 2) ]]) end) it("allows to use captures with dots (don't use fallback when specialization of foo exists)", function() @@ -642,11 +652,13 @@ describe('treesitter highlighting', function() | ]]} + command [[ + hi link @foo.bar Type + hi link @foo String + ]] exec_lua [[ local parser = vim.treesitter.get_parser(0, "c", {}) local highlighter = vim.treesitter.highlighter - highlighter.hl_map['foo.bar'] = 'Type' - highlighter.hl_map['foo'] = 'String' test_hl = highlighter.new(parser, {queries = {c = "(primitive_type) @foo.bar (string_literal) @foo"}}) ]] @@ -670,6 +682,29 @@ describe('treesitter highlighting', function() {1:~ }| | ]]} + + -- clearing specialization reactivates fallback + command [[ hi clear @foo.bar ]] + screen:expect{grid=[[ + {5:char}* x = {5:"Will somebody ever read this?"}; | + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]} end) it("supports conceal attribute", function() @@ -712,32 +747,26 @@ describe('treesitter highlighting', function() ]]} end) - it("hl_map has the correct fallback behavior", function() - exec_lua [[ - local hl_map = vim.treesitter.highlighter.hl_map - hl_map["foo"] = 1 - hl_map["foo.bar"] = 2 - hl_map["foo.bar.baz"] = 3 - - assert(hl_map["foo"] == 1) - assert(hl_map["foo.a.b.c.d"] == 1) - assert(hl_map["foo.bar"] == 2) - assert(hl_map["foo.bar.a.b.c.d"] == 2) - assert(hl_map["foo.bar.baz"] == 3) - assert(hl_map["foo.bar.baz.d"] == 3) - - hl_map["FOO"] = 1 - hl_map["FOO.BAR"] = 2 - assert(hl_map["FOO.BAR.BAZ"] == 2) - - hl_map["foo.missing.exists"] = 3 - assert(hl_map["foo.missing"] == 1) - assert(hl_map["foo.missing.exists"] == 3) - assert(hl_map["foo.missing.exists.bar"] == 3) - assert(hl_map["total.nonsense.but.a.lot.of.dots"] == nil) - -- It will not perform a second look up of this variable but return a sentinel value - assert(hl_map["total.nonsense.but.a.lot.of.dots"] == "__notfound") - ]] - + it("@foo.bar groups has the correct fallback behavior", function() + local get_hl = function(name) return meths.get_hl_by_name(name,1).foreground end + meths.set_hl(0, "@foo", {fg = 1}) + meths.set_hl(0, "@foo.bar", {fg = 2}) + meths.set_hl(0, "@foo.bar.baz", {fg = 3}) + + eq(1, get_hl"@foo") + eq(1, get_hl"@foo.a.b.c.d") + eq(2, get_hl"@foo.bar") + eq(2, get_hl"@foo.bar.a.b.c.d") + eq(3, get_hl"@foo.bar.baz") + eq(3, get_hl"@foo.bar.baz.d") + + -- lookup is case insensitive + eq(2, get_hl"@FOO.BAR.SPAM") + + meths.set_hl(0, "@foo.missing.exists", {fg = 3}) + eq(1, get_hl"@foo.missing") + eq(3, get_hl"@foo.missing.exists") + eq(3, get_hl"@foo.missing.exists.bar") + eq(nil, get_hl"@total.nonsense.but.a.lot.of.dots") end) end) diff --git a/test/functional/treesitter/language_spec.lua b/test/functional/treesitter/language_spec.lua index 30585be328..8e9941d797 100644 --- a/test/functional/treesitter/language_spec.lua +++ b/test/functional/treesitter/language_spec.lua @@ -7,10 +7,11 @@ local exec_lua = helpers.exec_lua local pcall_err = helpers.pcall_err local matches = helpers.matches local pending_c_parser = helpers.pending_c_parser +local insert = helpers.insert before_each(clear) -describe('treesitter API', function() +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", @@ -26,6 +27,11 @@ describe('treesitter API', function() eq("Error executing lua: .../language.lua:0: no parser for 'borklang' language, see :help treesitter-parsers", pcall_err(exec_lua, "parser = vim.treesitter.inspect_language('borklang')")) + + if not pending_c_parser(pending) then + matches("Error executing lua: Failed to load parser: uv_dlsym: .+", + pcall_err(exec_lua, 'vim.treesitter.require_language("c", nil, false, "borklang")')) + end end) it('inspects language', function() @@ -79,5 +85,35 @@ describe('treesitter API', function() eq("Error executing lua: .../language.lua:0: no parser for 'borklang' language, see :help treesitter-parsers", pcall_err(exec_lua, "new_parser = vim.treesitter.get_parser(0)")) end) + + it('retrieve the tree given a range', function () + if pending_c_parser(pending) then return end + insert([[ + int main() { + int x = 3; + }]]) + + exec_lua([[ + langtree = vim.treesitter.get_parser(0, "c") + tree = langtree:tree_for_range({1, 3, 1, 3}) + ]]) + + eq('<node translation_unit>', exec_lua('return tostring(tree:root())')) + end) + + it('retrieve the node given a range', function () + if pending_c_parser(pending) then return end + insert([[ + int main() { + int x = 3; + }]]) + + exec_lua([[ + langtree = vim.treesitter.get_parser(0, "c") + node = langtree:named_node_for_range({1, 3, 1, 3}) + ]]) + + eq('<node primitive_type>', exec_lua('return tostring(node)')) + end) end) diff --git a/test/functional/treesitter/node_spec.lua b/test/functional/treesitter/node_spec.lua index 21c287644e..87ce1b973c 100644 --- a/test/functional/treesitter/node_spec.lua +++ b/test/functional/treesitter/node_spec.lua @@ -59,4 +59,53 @@ describe('treesitter node API', function() exec_lua 'node = node:prev_named_sibling()' eq('int x', lua_eval('node_text(node)')) end) + + it('can retrieve the children of a node', function() + insert([[ + int main() { + int x = 3; + }]]) + + local len = exec_lua([[ + tree = vim.treesitter.get_parser(0, "c"):parse()[1] + node = tree:root():child(0) + children = node:named_children() + + return #children + ]]) + + eq(3, len) + eq('<node compound_statement>', lua_eval('tostring(children[3])')) + end) + + it('can retrieve the tree root given a node', function() + insert([[ + int main() { + int x = 3; + }]]) + + exec_lua([[ + tree = vim.treesitter.get_parser(0, "c"):parse()[1] + root = tree:root() + node = root:child(0):child(2) + ]]) + + eq(lua_eval('tostring(root)'), lua_eval('tostring(node:root())')) + end) + + it('can compute the byte length of a node', function() + insert([[ + int main() { + int x = 3; + }]]) + + exec_lua([[ + tree = vim.treesitter.get_parser(0, "c"):parse()[1] + root = tree:root() + child = root:child(0):child(0) + ]]) + + eq(28, lua_eval('root:byte_length()')) + eq(3, lua_eval('child:byte_length()')) + end) end) diff --git a/test/functional/treesitter/utils_spec.lua b/test/functional/treesitter/utils_spec.lua new file mode 100644 index 0000000000..4f4c18a748 --- /dev/null +++ b/test/functional/treesitter/utils_spec.lua @@ -0,0 +1,33 @@ +local helpers = require('test.functional.helpers')(after_each) + +local clear = helpers.clear +local insert = helpers.insert +local eq = helpers.eq +local exec_lua = helpers.exec_lua +local pending_c_parser = helpers.pending_c_parser + +before_each(clear) + +describe('treesitter utils', function() + before_each(clear) + + it('can find an ancestor', function() + if pending_c_parser(pending) then return end + + insert([[ + int main() { + int x = 3; + }]]) + + exec_lua([[ + parser = vim.treesitter.get_parser(0, "c") + tree = parser:parse()[1] + root = tree:root() + ancestor = root:child(0) + child = ancestor:child(0) + ]]) + + eq(true, exec_lua('return vim.treesitter.is_ancestor(ancestor, child)')) + eq(false, exec_lua('return vim.treesitter.is_ancestor(child, ancestor)')) + end) +end) diff --git a/test/functional/ui/cmdline_highlight_spec.lua b/test/functional/ui/cmdline_highlight_spec.lua index 384761ab17..fa5771a8b3 100644 --- a/test/functional/ui/cmdline_highlight_spec.lua +++ b/test/functional/ui/cmdline_highlight_spec.lua @@ -24,7 +24,6 @@ before_each(function() clear() screen = Screen.new(40, 8) screen:attach() - command("set display-=msgsep") source([[ highlight RBP1 guibg=Red highlight RBP2 guibg=Yellow @@ -152,6 +151,7 @@ before_each(function() SB={foreground = Screen.colors.Blue4}, E={foreground = Screen.colors.Red, background = Screen.colors.Blue}, M={bold = true}, + MSEP={bold = true, reverse = true}; }) end) @@ -298,22 +298,22 @@ describe('Command-line coloring', function() function() set_color_cb('SplittedMultibyteStart') start_prompt('echo "«') - screen:expect([[ - {EOB:~ }| - {EOB:~ }| + screen:expect{grid=[[ + | {EOB:~ }| {EOB:~ }| + {MSEP: }| :echo " | {ERR:E5405: Chunk 0 start 7 splits multibyte }| {ERR:character} | :echo "«^ | - ]]) + ]]} feed('»') screen:expect([[ + | {EOB:~ }| {EOB:~ }| - {EOB:~ }| - {EOB:~ }| + {MSEP: }| :echo " | {ERR:E5405: Chunk 0 start 7 splits multibyte }| {ERR:character} | @@ -325,10 +325,10 @@ describe('Command-line coloring', function() set_color_cb('SplittedMultibyteEnd') start_prompt('echo "«') screen:expect([[ + | {EOB:~ }| {EOB:~ }| - {EOB:~ }| - {EOB:~ }| + {MSEP: }| :echo " | {ERR:E5406: Chunk 0 end 7 splits multibyte ch}| {ERR:aracter} | @@ -339,10 +339,10 @@ describe('Command-line coloring', function() set_color_cb('Echoerring') start_prompt('e') screen:expect([[ + | {EOB:~ }| {EOB:~ }| - {EOB:~ }| - {EOB:~ }| + {MSEP: }| : | {ERR:E5407: Callback has thrown an exception:}| {ERR: Vim(echoerr):HERE} | @@ -398,10 +398,10 @@ describe('Command-line coloring', function() set_color_cb('Throwing') start_prompt('e') screen:expect([[ + | {EOB:~ }| {EOB:~ }| - {EOB:~ }| - {EOB:~ }| + {MSEP: }| : | {ERR:E5407: Callback has thrown an exception:}| {ERR: ABC} | @@ -412,10 +412,10 @@ describe('Command-line coloring', function() set_color_cb('SplittedMultibyteStart') start_prompt('let x = "«»«»«»«»«»"') screen:expect([[ + | {EOB:~ }| {EOB:~ }| - {EOB:~ }| - {EOB:~ }| + {MSEP: }| :let x = " | {ERR:E5405: Chunk 0 start 10 splits multibyte}| {ERR: character} | @@ -453,10 +453,10 @@ describe('Command-line coloring', function() screen:sleep(500) feed('<C-c>') screen:expect([[ + | {EOB:~ }| {EOB:~ }| - {EOB:~ }| - {EOB:~ }| + {MSEP: }| : | {ERR:E5407: Callback has thrown an exception:}| {ERR: Keyboard interrupt} | @@ -517,11 +517,11 @@ describe('Command-line coloring', function() set_color_cb('ReturningGlobal', '') start_prompt('#') screen:expect([[ + | {EOB:~ }| {EOB:~ }| {EOB:~ }| - {EOB:~ }| - {EOB:~ }| + {MSEP: }| : | {ERR:E5400: Callback should return list} | :#^ | @@ -531,11 +531,11 @@ describe('Command-line coloring', function() set_color_cb('ReturningGlobal', {{0, 1, 'Normal'}, 42}) start_prompt('#') screen:expect([[ + | {EOB:~ }| {EOB:~ }| {EOB:~ }| - {EOB:~ }| - {EOB:~ }| + {MSEP: }| : | {ERR:E5401: List item 1 is not a List} | :#^ | @@ -545,10 +545,10 @@ describe('Command-line coloring', function() set_color_cb('ReturningGlobal2', {{0, 1, 'Normal'}, {1}}) start_prompt('+') screen:expect([[ + | {EOB:~ }| {EOB:~ }| - {EOB:~ }| - {EOB:~ }| + {MSEP: }| :+ | {ERR:E5402: List item 1 has incorrect length:}| {ERR: 1 /= 3} | @@ -559,10 +559,10 @@ describe('Command-line coloring', function() set_color_cb('ReturningGlobal2', {{0, 1, 'Normal'}, {2, 3, 'Normal'}}) start_prompt('+') screen:expect([[ + | {EOB:~ }| {EOB:~ }| - {EOB:~ }| - {EOB:~ }| + {MSEP: }| :+ | {ERR:E5403: Chunk 1 start 2 not in range [1, }| {ERR:2)} | @@ -573,10 +573,10 @@ describe('Command-line coloring', function() set_color_cb('ReturningGlobal2', {{0, 1, 'Normal'}, {1, 3, 'Normal'}}) start_prompt('+') screen:expect([[ + | {EOB:~ }| {EOB:~ }| - {EOB:~ }| - {EOB:~ }| + {MSEP: }| :+ | {ERR:E5404: Chunk 1 end 3 not in range (1, 2]}| | @@ -800,10 +800,10 @@ describe('Ex commands coloring', function() it('does not crash when using `n` in debug mode', function() feed(':debug execute "echo 1"\n') screen:expect([[ + | {EOB:~ }| {EOB:~ }| - {EOB:~ }| - {EOB:~ }| + {MSEP: }| Entering Debug mode. Type "cont" to con| tinue. | cmd: execute "echo 1" | @@ -811,8 +811,8 @@ describe('Ex commands coloring', function() ]]) feed('n\n') screen:expect([[ - {EOB:~ }| - {EOB:~ }| + | + {MSEP: }| Entering Debug mode. Type "cont" to con| tinue. | cmd: execute "echo 1" | @@ -836,10 +836,10 @@ describe('Ex commands coloring', function() command("cnoremap <expr> x execute('throw 42')[-1]") feed(':#x') screen:expect([[ + | {EOB:~ }| {EOB:~ }| - {EOB:~ }| - {EOB:~ }| + {MSEP: }| :# | {ERR:Error detected while processing :} | {ERR:E605: Exception not caught: 42} | @@ -847,9 +847,9 @@ describe('Ex commands coloring', function() ]]) feed('<CR>') screen:expect([[ + | {EOB:~ }| - {EOB:~ }| - {EOB:~ }| + {MSEP: }| :# | {ERR:Error detected while processing :} | {ERR:E605: Exception not caught: 42} | @@ -864,9 +864,9 @@ describe('Ex commands coloring', function() meths.set_var('Nvim_color_cmdline', 42) feed(':#') screen:expect([[ + | {EOB:~ }| - {EOB:~ }| - {EOB:~ }| + {MSEP: }| : | {ERR:E5408: Unable to get g:Nvim_color_cmdlin}| {ERR:e callback: Vim:E6000: Argument is not a}| diff --git a/test/functional/ui/decorations_spec.lua b/test/functional/ui/decorations_spec.lua index 9af5d386db..789f1c6487 100644 --- a/test/functional/ui/decorations_spec.lua +++ b/test/functional/ui/decorations_spec.lua @@ -193,7 +193,7 @@ describe('decorations providers', function() | ]]} - meths._set_hl_ns(ns1) + meths.set_hl_ns(ns1) screen:expect{grid=[[ {10: 1 }{11:// just to see if there was an accid}| {10: }{11:ent} | @@ -219,7 +219,7 @@ describe('decorations providers', function() local ns2 = a.nvim_create_namespace 'ns2' a.nvim_set_decoration_provider (ns2, { on_win = function (_, win, buf) - a.nvim__set_hl_ns(win == thewin and _G.ns1 or ns2) + a.nvim_set_hl_ns_fast(win == thewin and _G.ns1 or ns2) end; }) ]] @@ -266,7 +266,7 @@ describe('decorations providers', function() ]]} meths.set_hl(ns1, 'LinkGroup', {fg = 'Blue'}) - meths._set_hl_ns(ns1) + meths.set_hl_ns(ns1) screen:expect{grid=[[ // just to see if there was an accident | @@ -302,7 +302,7 @@ describe('decorations providers', function() ]]} meths.set_hl(ns1, 'LinkGroup', {fg = 'Blue', default=true}) - meths._set_hl_ns(ns1) + meths.set_hl_ns(ns1) feed 'k' screen:expect{grid=[[ @@ -1681,7 +1681,7 @@ l5 screen:expect{grid=[[ S4S1^l1 | - S2x l2 | + x S2l2 | S5{1: }l3 | {1: }l4 | {1: }l5 | @@ -1779,6 +1779,34 @@ l5 ]]} end) + it('works with priority #19716', function() + screen:try_resize(20, 3) + insert(example_text) + feed 'gg' + + helpers.command('sign define Oldsign text=O3') + helpers.command([[exe 'sign place 42 line=1 name=Oldsign priority=10 buffer=' . bufnr('')]]) + + meths.buf_set_extmark(0, ns, 0, -1, {sign_text='S4', priority=100}) + meths.buf_set_extmark(0, ns, 0, -1, {sign_text='S2', priority=5}) + meths.buf_set_extmark(0, ns, 0, -1, {sign_text='S5', priority=200}) + meths.buf_set_extmark(0, ns, 0, -1, {sign_text='S1', priority=1}) + + screen:expect{grid=[[ + S1S2O3S4S5^l1 | + {1: }l2 | + | + ]]} + + -- Check truncation works too + meths.win_set_option(0, 'signcolumn', 'auto') + + screen:expect{grid=[[ + S5^l1 | + {1: }l2 | + | + ]]} + end) end) describe('decorations: virt_text', function() diff --git a/test/functional/ui/fold_spec.lua b/test/functional/ui/fold_spec.lua index c79fc2989c..6bb8bb81c6 100644 --- a/test/functional/ui/fold_spec.lua +++ b/test/functional/ui/fold_spec.lua @@ -1904,4 +1904,11 @@ describe("folded lines", function() describe('without ext_multigrid', function() with_ext_multigrid(false) end) + + it('no folds remains if :delete makes buffer empty #19671', function() + funcs.setline(1, {'foo', 'bar', 'baz'}) + command('2,3fold') + command('%delete') + eq(0, funcs.foldlevel(1)) + end) end) diff --git a/test/functional/ui/highlight_spec.lua b/test/functional/ui/highlight_spec.lua index e065a727f3..5ffe9ddaad 100644 --- a/test/functional/ui/highlight_spec.lua +++ b/test/functional/ui/highlight_spec.lua @@ -3,9 +3,10 @@ local Screen = require('test.functional.ui.screen') local os = require('os') local clear, feed, insert = helpers.clear, helpers.feed, helpers.insert local command, exec = helpers.command, helpers.exec -local eval, exc_exec = helpers.eval, helpers.exc_exec +local eval = helpers.eval local feed_command, eq = helpers.feed_command, helpers.eq local curbufmeths = helpers.curbufmeths +local meths = helpers.meths describe('colorscheme compatibility', function() before_each(function() @@ -92,16 +93,22 @@ describe('highlight defaults', function() before_each(function() clear() screen = Screen.new() + screen:set_default_attr_ids { + [0] = {bold=true, foreground=Screen.colors.Blue}; + [1] = {reverse = true, bold = true}; + [2] = {reverse = true}; + [3] = {bold = true}; + [4] = {bold = true, foreground = Screen.colors.SeaGreen}; + [5] = {foreground = Screen.colors.Red1, background = Screen.colors.WebGreen}; + [6] = {background = Screen.colors.Red1, foreground = Screen.colors.Grey100}; + [7] = {foreground = Screen.colors.Red}; + [8] = {foreground = Screen.colors.Blue}; + [9] = {italic = true}; + } screen:attach() - command("set display-=msgsep") end) it('window status bar', function() - screen:set_default_attr_ids({ - [0] = {bold=true, foreground=Screen.colors.Blue}, - [1] = {reverse = true, bold = true}, -- StatusLine - [2] = {reverse = true} -- StatusLineNC - }) feed_command('sp', 'vsp', 'vsp') screen:expect([[ ^ │ │ | @@ -200,31 +207,29 @@ describe('highlight defaults', function() ^ | {0:~ }| {0:~ }| - {1:-- INSERT --} | - ]], {[0] = {bold=true, foreground=Screen.colors.Blue}, - [1] = {bold = true}}) + {3:-- INSERT --} | + ]]) end) it('end of file markers', function() screen:try_resize(53, 4) screen:expect([[ ^ | - {1:~ }| - {1:~ }| + {0:~ }| + {0:~ }| | - ]], {[1] = {bold = true, foreground = Screen.colors.Blue}}) + ]]) end) it('"wait return" text', function() screen:try_resize(53, 4) feed(':ls<cr>') screen:expect([[ - {0:~ }| + {1: }| :ls | 1 %a "[No Name]" line 1 | - {1:Press ENTER or type command to continue}^ | - ]], {[0] = {bold=true, foreground=Screen.colors.Blue}, - [1] = {bold = true, foreground = Screen.colors.SeaGreen}}) + {4:Press ENTER or type command to continue}^ | + ]]) feed('<cr>') -- skip the "Press ENTER..." state or tests will hang end) @@ -237,8 +242,7 @@ describe('highlight defaults', function() {0:~ }| {0:~ }| -- INSERT -- | - ]], {[0] = {bold=true, foreground=Screen.colors.Blue}, - [1] = {bold=true}}) + ]]) feed('<esc>') feed_command('highlight CustomHLGroup guifg=red guibg=green') feed_command('highlight link ModeMsg CustomHLGroup') @@ -247,9 +251,8 @@ describe('highlight defaults', function() ^ | {0:~ }| {0:~ }| - {1:-- INSERT --} | - ]], {[0] = {bold=true, foreground=Screen.colors.Blue}, - [1] = {foreground = Screen.colors.Red, background = Screen.colors.Green}}) + {5:-- INSERT --} | + ]]) end) it('can be cleared by assigning NONE', function() @@ -258,14 +261,11 @@ describe('highlight defaults', function() feed_command('hi link TmpKeyword ErrorMsg') insert('neovim') screen:expect([[ - {1:neovi^m} | + {6:neovi^m} | {0:~ }| {0:~ }| | - ]], { - [0] = {bold=true, foreground=Screen.colors.Blue}, - [1] = {foreground = Screen.colors.White, background = Screen.colors.Red} - }) + ]]) feed_command("hi ErrorMsg term=NONE cterm=NONE ctermfg=NONE ctermbg=NONE" .. " gui=NONE guifg=NONE guibg=NONE guisp=NONE") screen:expect([[ @@ -273,7 +273,7 @@ describe('highlight defaults', function() {0:~ }| {0:~ }| | - ]], {[0] = {bold=true, foreground=Screen.colors.Blue}}) + ]]) end) it('linking updates window highlight immediately #16552', function() @@ -283,7 +283,7 @@ describe('highlight defaults', function() {0:~ }| {0:~ }| | - ]], {[0] = {bold=true, foreground=Screen.colors.Blue}}) + ]]) feed_command("hi NonTextAlt guifg=Red") feed_command("hi! link NonText NonTextAlt") screen:expect([[ @@ -305,56 +305,44 @@ describe('highlight defaults', function() feed_command('set listchars=space:.,tab:>-,trail:*,eol:¬ list') insert(' ne \t o\tv im ') screen:expect([[ - ne{0:.>----.}o{0:>-----}v{0:..}im{0:*^*¬} | - {0:~ }| - {0:~ }| + ne{7:.>----.}o{7:>-----}v{7:..}im{7:*^*¬} | + {7:~ }| + {7:~ }| | - ]], { - [0] = {foreground=Screen.colors.Red}, - [1] = {foreground=Screen.colors.Blue}, - }) + ]]) feed_command('highlight Whitespace gui=NONE guifg=#0000FF') screen:expect([[ - ne{1:.>----.}o{1:>-----}v{1:..}im{1:*^*}{0:¬} | - {0:~ }| - {0:~ }| + ne{8:.>----.}o{8:>-----}v{8:..}im{8:*^*}{7:¬} | + {7:~ }| + {7:~ }| :highlight Whitespace gui=NONE guifg=#0000FF | - ]], { - [0] = {foreground=Screen.colors.Red}, - [1] = {foreground=Screen.colors.Blue}, - }) + ]]) end) it('are sent to UIs', function() screen:try_resize(53, 4) - screen:set_default_attr_ids({ - [0] = {}, - [1] = {bold = true, foreground = Screen.colors.Blue1}, - [2] = {bold = true, reverse = true}, - [3] = {italic=true} - }) screen:expect{grid=[[ ^ | - {1:~ }| - {1:~ }| + {0:~ }| + {0:~ }| | - ]], hl_groups={EndOfBuffer=1, MsgSeparator=2}} + ]], hl_groups={EndOfBuffer=0, MsgSeparator=1}} command('highlight EndOfBuffer gui=italic') screen:expect{grid=[[ ^ | - {3:~ }| - {3:~ }| + {9:~ }| + {9:~ }| | - ]], hl_groups={EndOfBuffer=3, MsgSeparator=2}} + ]], hl_groups={EndOfBuffer=9, MsgSeparator=1}} command('highlight clear EndOfBuffer') screen:expect{grid=[[ ^ | - {1:~ }| - {1:~ }| + {0:~ }| + {0:~ }| | - ]], hl_groups={EndOfBuffer=1, MsgSeparator=2}} + ]], hl_groups={EndOfBuffer=0, MsgSeparator=1}} end) end) @@ -1787,6 +1775,7 @@ describe("'winhighlight' highlight", function() [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}; }) command("hi Background1 guibg=DarkBlue") command("hi Background2 guibg=DarkGreen") @@ -1820,7 +1809,7 @@ describe("'winhighlight' highlight", function() ]]) end) - it('handles invalid values', function() + it('handles undefined groups', function() command("set winhl=Normal:Background1") screen:expect([[ {1:^ }| @@ -1833,19 +1822,44 @@ describe("'winhighlight' highlight", function() | ]]) - eq('Vim(set):E474: Invalid argument: winhl=xxx:yyy', - exc_exec("set winhl=xxx:yyy")) - eq('Normal:Background1', eval('&winhl')) + command("set winhl=xxx:yyy") + eq('xxx:yyy', eval('&winhl')) screen:expect{grid=[[ - {1:^ }| - {2:~ }| - {2:~ }| - {2:~ }| - {2:~ }| - {2:~ }| - {2:~ }| + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]} + end) + + it('can be changed to define different groups', function() + command("set winhl=EndOfBuffer:Background1") + screen:expect{grid=[[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]} + + command("set winhl=Normal:ErrorMsg") + screen:expect{grid=[[ + {15:^ }| + {29:~ }| + {29:~ }| + {29:~ }| + {29:~ }| + {29:~ }| + {29:~ }| | - ]], unchanged=true} + ]]} end) it('works local to the window', function() @@ -2270,4 +2284,236 @@ describe("'winhighlight' highlight", function() | ]]) end) + + + it("can override syntax groups", function() + command('syntax on') + command('syntax keyword Foobar foobar') + command('syntax keyword Article the') + command('hi Foobar guibg=#FF0000') + command('hi Article guifg=#00FF00 gui=bold') + insert('the foobar was foobar') + screen:expect([[ + {25:the} {26:foobar} was {26:fooba}| + {26:^r} | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + + command('split') + command('set winhl=Foobar:Background1,Article:ErrorMsg') + screen:expect{grid=[[ + {15:the} {1:foobar} was {1:fooba}| + {1:^r} | + {0:~ }| + {3:[No Name] [+] }| + {25:the} {26:foobar} was {26:fooba}| + {26:r} | + {4:[No Name] [+] }| + | + ]]} + end) + + it('can be disabled in newly opened window #19823', function() + command('split | set winhl=Normal:ErrorMsg | set winhl=') + screen:expect{grid=[[ + ^ | + {0:~ }| + {0:~ }| + {3:[No Name] }| + | + {0:~ }| + {4:[No Name] }| + | + ]]} + + helpers.assert_alive() + end) + + it('can redraw statusline on cursor movement', function() + screen:try_resize(40, 8) + exec [[ + set statusline=%f%=%#Background1#%l,%c%V\ %P + split + ]] + insert [[ + some text + more text]] + screen:expect{grid=[[ + some text | + more tex^t | + {0:~ }| + {3:[No Name] }{1:2,9 All}| + some text | + more text | + {4:[No Name] }{1:1,1 All}| + | + ]]} + + command 'set winhl=Background1:Background2' + screen:expect{grid=[[ + some text | + more tex^t | + {0:~ }| + {3:[No Name] }{5:2,9 All}| + some text | + more text | + {4:[No Name] }{1:1,1 All}| + | + ]]} + + feed 'k' + screen:expect{grid=[[ + some tex^t | + more text | + {0:~ }| + {3:[No Name] }{5:1,9 All}| + some text | + more text | + {4:[No Name] }{1:1,1 All}| + | + ]]} + end) +end) + +describe('highlight namespaces', function() + local screen + local ns1, ns2 + + before_each(function() + clear() + screen = Screen.new(25,10) + screen:attach() + screen:set_default_attr_ids { + [1] = {foreground = Screen.colors.Blue, bold = true}; + [2] = {background = Screen.colors.DarkGrey}; + [3] = {italic = true, foreground = Screen.colors.DarkCyan, background = Screen.colors.DarkOrange4}; + [4] = {background = Screen.colors.Magenta4}; + [5] = {background = Screen.colors.Magenta4, foreground = Screen.colors.Crimson}; + [6] = {bold = true, reverse = true}; + [7] = {reverse = true}; + [8] = {foreground = Screen.colors.Gray20}; + } + + ns1 = meths.create_namespace 'grungy' + ns2 = meths.create_namespace 'ultrared' + + meths.set_hl(ns1, 'Normal', {bg='DarkGrey'}) + meths.set_hl(ns1, 'NonText', {bg='DarkOrange4', fg='DarkCyan', italic=true}) + meths.set_hl(ns2, 'Normal', {bg='DarkMagenta'}) + meths.set_hl(ns2, 'NonText', {fg='Crimson'}) + end) + + it('can be used globally', function() + screen:expect{grid=[[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]} + + meths.set_hl_ns(ns1) + screen:expect{grid=[[ + {2:^ }| + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + | + ]]} + + meths.set_hl_ns(ns2) + screen:expect{grid=[[ + {4:^ }| + {5:~ }| + {5:~ }| + {5:~ }| + {5:~ }| + {5:~ }| + {5:~ }| + {5:~ }| + {5:~ }| + | + ]]} + + meths.set_hl_ns(0) + screen:expect{grid=[[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]} + end) + + it('can be used per window', function() + local win1 = meths.get_current_win() + command 'split' + local win2 = meths.get_current_win() + command 'split' + + meths.win_set_hl_ns(win1, ns1) + meths.win_set_hl_ns(win2, ns2) + + screen:expect{grid=[[ + ^ | + {1:~ }| + {6:[No Name] }| + {4: }| + {5:~ }| + {7:[No Name] }| + {2: }| + {3:~ }| + {7:[No Name] }| + | + ]]} + end) + + it('redraws correctly when ns=0', function() + screen:expect{grid=[[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]} + + meths.set_hl(0, 'EndOfBuffer', {fg='#333333'}) + screen:expect{grid=[[ + ^ | + {8:~ }| + {8:~ }| + {8:~ }| + {8:~ }| + {8:~ }| + {8:~ }| + {8:~ }| + {8:~ }| + | + ]]} + end) end) diff --git a/test/functional/ui/hlstate_spec.lua b/test/functional/ui/hlstate_spec.lua index df7f34aa7f..dc74d6d401 100644 --- a/test/functional/ui/hlstate_spec.lua +++ b/test/functional/ui/hlstate_spec.lua @@ -119,13 +119,15 @@ describe('ext_hlstate detailed highlights', function() [3] = {{bold = true, reverse = true}, {{hi_name = "StatusLine", ui_name = "StatusLine", kind = "ui"}}}, [4] = {{reverse = true}, {{hi_name = "StatusLineNC", ui_name = "StatusLineNC", kind = "ui"}}}, [5] = {{background = Screen.colors.Red, foreground = Screen.colors.Grey100}, {{hi_name = "ErrorMsg", ui_name = "LineNr", kind = "ui"}}}, - [6] = {{bold = true, reverse = true}, {{hi_name = "MsgSeparator", ui_name = "Normal", kind = "ui"}}}, + [6] = {{bold = true, reverse = true}, {{hi_name = "Normal", ui_name = "Normal", kind = "ui"}}}, [7] = {{foreground = Screen.colors.Brown, bold = true, reverse = true}, {6, 1}}, - [8] = {{foreground = Screen.colors.Blue1, bold = true, reverse = true}, {6, 2}}, - [9] = {{bold = true, foreground = Screen.colors.Brown}, {{hi_name = "Statement", ui_name = "NormalNC", kind = "ui"}}}, + [8] = {{foreground = Screen.colors.Blue1, bold = true, reverse = true}, {6, 14}}, + [9] = {{bold = true, foreground = Screen.colors.Brown}, {{hi_name = "NormalNC", ui_name = "NormalNC", kind = "ui"}}}, [10] = {{bold = true, foreground = Screen.colors.Brown}, {9, 1}}, - [11] = {{bold = true, foreground = Screen.colors.Blue1}, {9, 2}}, + [11] = {{bold = true, foreground = Screen.colors.Blue1}, {9, 14}}, [12] = {{}, {{hi_name = "MsgArea", ui_name = "MsgArea", kind = "ui"}}}, + [13] = {{background = Screen.colors.Red1, foreground = Screen.colors.Gray100}, {{ui_name = "LineNr", kind = "ui", hi_name = "LineNr"}}}; + [14] = {{bold = true, foreground = Screen.colors.Blue}, {{ui_name = "EndOfBuffer", kind = "ui", hi_name = "EndOfBuffer"}}}; }) command("set number") @@ -143,16 +145,16 @@ describe('ext_hlstate detailed highlights', function() ]]) command("set winhl=LineNr:ErrorMsg") - screen:expect([[ - {5: 1 }^ | - {2:~ }| - {2:~ }| + screen:expect{grid=[[ + {13: 1 }^ | + {14:~ }| + {14:~ }| {3:[No Name] }| {1: 1 } | {2:~ }| {4:[No Name] }| {12: }| - ]]) + ]]} command("set winhl=Normal:MsgSeparator,NormalNC:Statement") screen:expect([[ diff --git a/test/functional/ui/inccommand_spec.lua b/test/functional/ui/inccommand_spec.lua index d8dd546a8d..9ca4673efe 100644 --- a/test/functional/ui/inccommand_spec.lua +++ b/test/functional/ui/inccommand_spec.lua @@ -66,7 +66,6 @@ local function common_setup(screen, inccommand, text) command("syntax on") command("set nohlsearch") command("hi Substitute guifg=red guibg=yellow") - command("set display-=msgsep") screen:attach() screen:set_default_attr_ids({ [1] = {foreground = Screen.colors.Fuchsia}, @@ -142,11 +141,11 @@ describe(":substitute, 'inccommand' preserves", function() feed_command("ls") screen:expect([[ + BAC | {15:~ }| {15:~ }| {15:~ }| - {15:~ }| - {15:~ }| + {11: }| :ls | 1 %a + "[No Name]" | line 1 | @@ -1469,14 +1468,14 @@ describe("inccommand=nosplit", function() -- non-modifier prefix feed(':silent tabedit %s/tw/to') screen:expect([[ + Inc substitution on | two lines | Inc substitution on | two lines | | {15:~ }| {15:~ }| - {15:~ }| - {15:~ }| + {11: }| :silent tabedit %s/t| w/to^ | ]]) @@ -2656,6 +2655,7 @@ describe(":substitute", function() feed("\\rѫ ab \\rXXXX") screen:expect([[ + 7 8 9 | K L M | {12:JLKR £} | {12:ѫ ab } | @@ -2667,8 +2667,7 @@ describe(":substitute", function() {12:ѫ ab } | {11:[No Name] [+] }| | 7| {12:JLKR £} | - | 8|{12: ѫ ab } | - {10:[Preview] }| + {11: }| :%s/[a-z]/JLKR £\rѫ ab \rXXX| X^ | ]]) @@ -3001,8 +3000,8 @@ it('long :%s/ with inccommand does not collapse cmdline', function() feed(':%s/AAAAAAA', 'A', 'A', 'A', 'A', 'A', 'A', 'A', 'A', 'A', 'A', 'A', 'A', 'A', 'A', 'A', 'A', 'A', 'A', 'A', 'A') screen:expect([[ - {15:~ }| - {15:~ }| + | + {11: }| :%s/AAAAAAAA| AAAAAAAAAAAA| AAAAAAA^ | diff --git a/test/functional/ui/inccommand_user_spec.lua b/test/functional/ui/inccommand_user_spec.lua index b5816f6fe6..0b25d4f8d2 100644 --- a/test/functional/ui/inccommand_user_spec.lua +++ b/test/functional/ui/inccommand_user_spec.lua @@ -7,61 +7,82 @@ local feed = helpers.feed local command = helpers.command local assert_alive = helpers.assert_alive --- Implements a :Replace command that works like :substitute. +-- Implements a :Replace command that works like :substitute and has multibuffer support. local setup_replace_cmd = [[ - local function show_replace_preview(buf, use_preview_win, preview_ns, preview_buf, matches) + local function show_replace_preview(use_preview_win, preview_ns, preview_buf, matches) -- Find the width taken by the largest line number, used for padding the line numbers local highest_lnum = math.max(matches[#matches][1], 1) local highest_lnum_width = math.floor(math.log10(highest_lnum)) local preview_buf_line = 0 - - vim.g.prevns = preview_ns - vim.g.prevbuf = preview_buf + local multibuffer = #matches > 1 for _, match in ipairs(matches) do - local lnum = match[1] - local line_matches = match[2] - local prefix - - if use_preview_win then - prefix = string.format( - '|%s%d| ', - string.rep(' ', highest_lnum_width - math.floor(math.log10(lnum))), - lnum - ) + local buf = match[1] + local buf_matches = match[2] + + if multibuffer and #buf_matches > 0 and use_preview_win then + local bufname = vim.api.nvim_buf_get_name(buf) + + if bufname == "" then + bufname = string.format("Buffer #%d", buf) + end vim.api.nvim_buf_set_lines( preview_buf, preview_buf_line, preview_buf_line, 0, - { prefix .. vim.api.nvim_buf_get_lines(buf, lnum - 1, lnum, false)[1] } + { bufname .. ':' } ) + + preview_buf_line = preview_buf_line + 1 end - for _, line_match in ipairs(line_matches) do - vim.api.nvim_buf_add_highlight( - buf, - preview_ns, - 'Substitute', - lnum - 1, - line_match[1], - line_match[2] - ) + for _, buf_match in ipairs(buf_matches) do + local lnum = buf_match[1] + local line_matches = buf_match[2] + local prefix if use_preview_win then - vim.api.nvim_buf_add_highlight( + prefix = string.format( + '|%s%d| ', + string.rep(' ', highest_lnum_width - math.floor(math.log10(lnum))), + lnum + ) + + vim.api.nvim_buf_set_lines( preview_buf, + preview_buf_line, + preview_buf_line, + 0, + { prefix .. vim.api.nvim_buf_get_lines(buf, lnum - 1, lnum, false)[1] } + ) + end + + for _, line_match in ipairs(line_matches) do + vim.api.nvim_buf_add_highlight( + buf, preview_ns, 'Substitute', - preview_buf_line, - #prefix + line_match[1], - #prefix + line_match[2] + lnum - 1, + line_match[1], + line_match[2] ) + + if use_preview_win then + vim.api.nvim_buf_add_highlight( + preview_buf, + preview_ns, + 'Substitute', + preview_buf_line, + #prefix + line_match[1], + #prefix + line_match[2] + ) + end end - end - preview_buf_line = preview_buf_line + 1 + preview_buf_line = preview_buf_line + 1 + end end if use_preview_win then @@ -72,94 +93,121 @@ local setup_replace_cmd = [[ end local function do_replace(opts, preview, preview_ns, preview_buf) - local pat1 = opts.fargs[1] or '' + local pat1 = opts.fargs[1] + + if not pat1 then return end + local pat2 = opts.fargs[2] or '' local line1 = opts.line1 local line2 = opts.line2 - - local buf = vim.api.nvim_get_current_buf() - local lines = vim.api.nvim_buf_get_lines(buf, line1 - 1, line2, 0) local matches = {} - for i, line in ipairs(lines) do - local startidx, endidx = 0, 0 - local line_matches = {} - local num = 1 + -- Get list of valid and listed buffers + local buffers = vim.tbl_filter( + function(buf) + if not (vim.api.nvim_buf_is_valid(buf) and vim.bo[buf].buflisted and buf ~= preview_buf) + then + return false + end - while startidx ~= -1 do - local match = vim.fn.matchstrpos(line, pat1, 0, num) - startidx, endidx = match[2], match[3] + -- Check if there's at least one window using the buffer + for _, win in ipairs(vim.api.nvim_tabpage_list_wins(0)) do + if vim.api.nvim_win_get_buf(win) == buf then + return true + end + end - if startidx ~= -1 then - line_matches[#line_matches+1] = { startidx, endidx } - end + return false + end, + vim.api.nvim_list_bufs() + ) - num = num + 1 - end + for _, buf in ipairs(buffers) do + local lines = vim.api.nvim_buf_get_lines(buf, line1 - 1, line2, false) + local buf_matches = {} - if #line_matches > 0 then - matches[#matches+1] = { line1 + i - 1, line_matches } - end - end + for i, line in ipairs(lines) do + local startidx, endidx = 0, 0 + local line_matches = {} + local num = 1 - local new_lines = {} + while startidx ~= -1 do + local match = vim.fn.matchstrpos(line, pat1, 0, num) + startidx, endidx = match[2], match[3] - for _, match in ipairs(matches) do - local lnum = match[1] - local line_matches = match[2] - local line = lines[lnum - line1 + 1] - local pat_width_differences = {} - - -- If previewing, only replace the text in current buffer if pat2 isn't empty - -- Otherwise, always replace the text - if pat2 ~= '' or not preview then - if preview then - for _, line_match in ipairs(line_matches) do - local startidx, endidx = unpack(line_match) - local pat_match = line:sub(startidx + 1, endidx) - - pat_width_differences[#pat_width_differences+1] = - #vim.fn.substitute(pat_match, pat1, pat2, 'g') - #pat_match + if startidx ~= -1 then + line_matches[#line_matches+1] = { startidx, endidx } end + + num = num + 1 end - new_lines[lnum] = vim.fn.substitute(line, pat1, pat2, 'g') + if #line_matches > 0 then + buf_matches[#buf_matches+1] = { line1 + i - 1, line_matches } + end end - -- Highlight the matches if previewing - if preview then - local idx_offset = 0 - for i, line_match in ipairs(line_matches) do - local startidx, endidx = unpack(line_match) - -- Starting index of replacement text - local repl_startidx = startidx + idx_offset - -- Ending index of the replacement text (if pat2 isn't empty) - local repl_endidx - - if pat2 ~= '' then - repl_endidx = endidx + idx_offset + pat_width_differences[i] - else - repl_endidx = endidx + idx_offset + local new_lines = {} + + for _, buf_match in ipairs(buf_matches) do + local lnum = buf_match[1] + local line_matches = buf_match[2] + local line = lines[lnum - line1 + 1] + local pat_width_differences = {} + + -- If previewing, only replace the text in current buffer if pat2 isn't empty + -- Otherwise, always replace the text + if pat2 ~= '' or not preview then + if preview then + for _, line_match in ipairs(line_matches) do + local startidx, endidx = unpack(line_match) + local pat_match = line:sub(startidx + 1, endidx) + + pat_width_differences[#pat_width_differences+1] = + #vim.fn.substitute(pat_match, pat1, pat2, 'g') - #pat_match + end end - if pat2 ~= '' then - idx_offset = idx_offset + pat_width_differences[i] - end + new_lines[lnum] = vim.fn.substitute(line, pat1, pat2, 'g') + end - line_matches[i] = { repl_startidx, repl_endidx } + -- Highlight the matches if previewing + if preview then + local idx_offset = 0 + for i, line_match in ipairs(line_matches) do + local startidx, endidx = unpack(line_match) + -- Starting index of replacement text + local repl_startidx = startidx + idx_offset + -- Ending index of the replacement text (if pat2 isn't empty) + local repl_endidx + + if pat2 ~= '' then + repl_endidx = endidx + idx_offset + pat_width_differences[i] + else + repl_endidx = endidx + idx_offset + end + + if pat2 ~= '' then + idx_offset = idx_offset + pat_width_differences[i] + end + + line_matches[i] = { repl_startidx, repl_endidx } + end end end - end - for lnum, line in pairs(new_lines) do - vim.api.nvim_buf_set_lines(buf, lnum - 1, lnum, false, { line }) + for lnum, line in pairs(new_lines) do + vim.api.nvim_buf_set_lines(buf, lnum - 1, lnum, false, { line }) + end + + matches[#matches+1] = { buf, buf_matches } end if preview then local lnum = vim.api.nvim_win_get_cursor(0)[1] -- Use preview window only if preview buffer is provided and range isn't just the current line local use_preview_win = (preview_buf ~= nil) and (line1 ~= lnum or line2 ~= lnum) - return show_replace_preview(buf, use_preview_win, preview_ns, preview_buf, matches) + return show_replace_preview(use_preview_win, preview_ns, preview_buf, matches) end end @@ -354,3 +402,120 @@ describe("'inccommand' for user commands", function() assert_alive() end) end) + +describe("'inccommand' with multiple buffers", function() + local screen + + before_each(function() + clear() + screen = Screen.new(40, 17) + screen:set_default_attr_ids({ + [1] = {background = Screen.colors.Yellow1}, + [2] = {foreground = Screen.colors.Blue1, bold = true}, + [3] = {reverse = true}, + [4] = {reverse = true, bold = true} + }) + screen:attach() + exec_lua(setup_replace_cmd) + command('set cmdwinheight=10') + insert[[ + foo bar baz + bar baz foo + baz foo bar + ]] + command('vsplit | enew') + insert[[ + bar baz foo + baz foo bar + foo bar baz + ]] + end) + + it('works', function() + command('set inccommand=nosplit') + feed(':Replace foo bar') + screen:expect([[ + bar baz {1:bar} │ {1:bar} bar baz | + baz {1:bar} bar │ bar baz {1:bar} | + {1:bar} bar baz │ baz {1:bar} bar | + │ | + {2:~ }│{2:~ }| + {2:~ }│{2:~ }| + {2:~ }│{2:~ }| + {2:~ }│{2:~ }| + {2:~ }│{2:~ }| + {2:~ }│{2:~ }| + {2:~ }│{2:~ }| + {2:~ }│{2:~ }| + {2:~ }│{2:~ }| + {2:~ }│{2:~ }| + {2:~ }│{2:~ }| + {4:[No Name] [+] }{3:[No Name] [+] }| + :Replace foo bar^ | + ]]) + feed('<CR>') + screen:expect([[ + bar baz bar │ bar bar baz | + baz bar bar │ bar baz bar | + bar bar baz │ baz bar bar | + ^ │ | + {2:~ }│{2:~ }| + {2:~ }│{2:~ }| + {2:~ }│{2:~ }| + {2:~ }│{2:~ }| + {2:~ }│{2:~ }| + {2:~ }│{2:~ }| + {2:~ }│{2:~ }| + {2:~ }│{2:~ }| + {2:~ }│{2:~ }| + {2:~ }│{2:~ }| + {2:~ }│{2:~ }| + {4:[No Name] [+] }{3:[No Name] [+] }| + :Replace foo bar | + ]]) + end) + + it('works with inccommand=split', function() + command('set inccommand=split') + feed(':Replace foo bar') + screen:expect([[ + bar baz {1:bar} │ {1:bar} bar baz | + baz {1:bar} bar │ bar baz {1:bar} | + {1:bar} bar baz │ baz {1:bar} bar | + │ | + {4:[No Name] [+] }{3:[No Name] [+] }| + Buffer #1: | + |1| {1:bar} bar baz | + |2| bar baz {1:bar} | + |3| baz {1:bar} bar | + Buffer #2: | + |1| bar baz {1:bar} | + |2| baz {1:bar} bar | + |3| {1:bar} bar baz | + | + {2:~ }| + {3:[Preview] }| + :Replace foo bar^ | + ]]) + feed('<CR>') + screen:expect([[ + bar baz bar │ bar bar baz | + baz bar bar │ bar baz bar | + bar bar baz │ baz bar bar | + ^ │ | + {2:~ }│{2:~ }| + {2:~ }│{2:~ }| + {2:~ }│{2:~ }| + {2:~ }│{2:~ }| + {2:~ }│{2:~ }| + {2:~ }│{2:~ }| + {2:~ }│{2:~ }| + {2:~ }│{2:~ }| + {2:~ }│{2:~ }| + {2:~ }│{2:~ }| + {2:~ }│{2:~ }| + {4:[No Name] [+] }{3:[No Name] [+] }| + :Replace foo bar | + ]]) + end) +end) diff --git a/test/functional/ui/input_spec.lua b/test/functional/ui/input_spec.lua index 0f4e97088c..05d55b94fb 100644 --- a/test/functional/ui/input_spec.lua +++ b/test/functional/ui/input_spec.lua @@ -321,13 +321,14 @@ describe('input non-printable chars', function() it("doesn't crash when echoing them back", function() write_file("Xtest-overwrite", [[foobar]]) local screen = Screen.new(60,8) - screen:set_default_attr_ids({ - [1] = {bold = true, foreground = Screen.colors.Blue1}, - [2] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red}, - [3] = {bold = true, foreground = Screen.colors.SeaGreen4} - }) + screen:set_default_attr_ids { + [1] = {bold = true, foreground = Screen.colors.Blue1}; + [2] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red}; + [3] = {bold = true, foreground = Screen.colors.SeaGreen4}; + [4] = {bold = true, reverse = true}; + } screen:attach() - command("set display-=msgsep shortmess-=F") + command("set shortmess-=F") feed_command("e Xtest-overwrite") screen:expect([[ @@ -346,11 +347,11 @@ describe('input non-printable chars', function() write_file("Xtest-overwrite", [[smurf]]) feed_command("w") screen:expect([[ + foobar | {1:~ }| {1:~ }| {1:~ }| - {1:~ }| - {1:~ }| + {4: }| "Xtest-overwrite" | {2:WARNING: The file has been changed since reading it!!!} | {3:Do you really want to write to it (y/n)?}^ | @@ -358,10 +359,10 @@ describe('input non-printable chars', function() feed("u") screen:expect([[ + foobar | {1:~ }| {1:~ }| - {1:~ }| - {1:~ }| + {4: }| "Xtest-overwrite" | {2:WARNING: The file has been changed since reading it!!!} | {3:Do you really want to write to it (y/n)?}u | @@ -370,9 +371,9 @@ describe('input non-printable chars', function() feed("\005") screen:expect([[ + foobar | {1:~ }| - {1:~ }| - {1:~ }| + {4: }| "Xtest-overwrite" | {2:WARNING: The file has been changed since reading it!!!} | {3:Do you really want to write to it (y/n)?}u | @@ -382,8 +383,8 @@ describe('input non-printable chars', function() feed("n") screen:expect([[ - {1:~ }| - {1:~ }| + foobar | + {4: }| "Xtest-overwrite" | {2:WARNING: The file has been changed since reading it!!!} | {3:Do you really want to write to it (y/n)?}u | diff --git a/test/functional/ui/messages_spec.lua b/test/functional/ui/messages_spec.lua index e7eaedba2d..522c9ccba2 100644 --- a/test/functional/ui/messages_spec.lua +++ b/test/functional/ui/messages_spec.lua @@ -1077,10 +1077,10 @@ vimComment xxx match /\s"[^\-:.%#=*].*$/ms=s+1,lc=1 excludenl contains=@vim ]]} end) - it('redraws NOT_VALID correctly after message', function() - -- edge case: only one window was set NOT_VALID. Original report + it('redraws UPD_NOT_VALID correctly after message', function() + -- edge case: only one window was set UPD_NOT_VALID. Original report -- used :make, but fake it using one command to set the current - -- window NOT_VALID and another to show a long message. + -- window UPD_NOT_VALID and another to show a long message. command("set more") feed(':new<cr><c-w><c-w>') screen:expect{grid=[[ @@ -1243,6 +1243,19 @@ vimComment xxx match /\s"[^\-:.%#=*].*$/ms=s+1,lc=1 excludenl contains=@vim | ]]) end) + + it('echo messages are shown correctly when getchar() immediately follows', function() + feed([[:echo 'foo' | echo 'bar' | call getchar()<CR>]]) + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }| + {3: }| + foo | + bar^ | + ]]) + end) end) describe('ui/ext_messages', function() diff --git a/test/functional/ui/mouse_spec.lua b/test/functional/ui/mouse_spec.lua index e389b7ab89..9896b11218 100644 --- a/test/functional/ui/mouse_spec.lua +++ b/test/functional/ui/mouse_spec.lua @@ -32,7 +32,7 @@ describe('ui/mouse/input', function() [6] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red}, [7] = {bold = true, foreground = Screen.colors.SeaGreen4}, }) - command("set display-=msgsep mousemodel=extend") + command("set mousemodel=extend") feed('itesting<cr>mouse<cr>support and selection<esc>') screen:expect([[ testing | diff --git a/test/functional/ui/multigrid_spec.lua b/test/functional/ui/multigrid_spec.lua index b30aa67fd3..78a1e8c677 100644 --- a/test/functional/ui/multigrid_spec.lua +++ b/test/functional/ui/multigrid_spec.lua @@ -3,7 +3,9 @@ local Screen = require('test.functional.ui.screen') local clear = helpers.clear local feed, command, insert = helpers.feed, helpers.command, helpers.insert local eq = helpers.eq +local funcs = helpers.funcs local meths = helpers.meths +local curwin = helpers.curwin local poke_eventloop = helpers.poke_eventloop @@ -871,6 +873,15 @@ describe('ext_multigrid', function() before_each(function() screen:try_resize_grid(2, 60, 20) end) + + it('winwidth() winheight() getwininfo() return inner width and height #19743', function() + eq(60, funcs.winwidth(0)) + eq(20, funcs.winheight(0)) + local win_info = funcs.getwininfo(curwin().id)[1] + eq(60, win_info.width) + eq(20, win_info.height) + end) + it('gets written till grid width', function() insert(('a'):rep(60).."\n") diff --git a/test/functional/ui/output_spec.lua b/test/functional/ui/output_spec.lua index 71c6410013..9bb067ed8e 100644 --- a/test/functional/ui/output_spec.lua +++ b/test/functional/ui/output_spec.lua @@ -100,45 +100,52 @@ describe("shell command :!", function() pending('missing printf') end local screen = Screen.new(50, 4) + screen:set_default_attr_ids { + [1] = {bold = true, reverse = true}; + [2] = {bold = true, foreground = Screen.colors.SeaGreen}; + [3] = {foreground = Screen.colors.Blue}; + } screen:attach() - command("set display-=msgsep") -- Print TAB chars. #2958 feed([[:!printf '1\t2\t3'<CR>]]) - screen:expect([[ - ~ | + screen:expect{grid=[[ + {1: }| :!printf '1\t2\t3' | 1 2 3 | - Press ENTER or type command to continue^ | - ]]) + {2:Press ENTER or type command to continue}^ | + ]]} feed([[<CR>]]) + -- Print BELL control code. #4338 screen.bell = false feed([[:!printf '\007\007\007\007text'<CR>]]) screen:expect{grid=[[ - ~ | + {1: }| :!printf '\007\007\007\007text' | text | - Press ENTER or type command to continue^ | + {2:Press ENTER or type command to continue}^ | ]], condition=function() eq(true, screen.bell) end} feed([[<CR>]]) + -- Print BS control code. feed([[:echo system('printf ''\010\n''')<CR>]]) screen:expect([[ - ~ | - ^H | + {1: }| + {3:^H} | | - Press ENTER or type command to continue^ | + {2:Press ENTER or type command to continue}^ | ]]) feed([[<CR>]]) + -- Print LF control code. feed([[:!printf '\n'<CR>]]) screen:expect([[ :!printf '\n' | | | - Press ENTER or type command to continue^ | + {2:Press ENTER or type command to continue}^ | ]]) feed([[<CR>]]) end) diff --git a/test/functional/ui/popupmenu_spec.lua b/test/functional/ui/popupmenu_spec.lua index 7b0005bcf1..dcd4ad3d9a 100644 --- a/test/functional/ui/popupmenu_spec.lua +++ b/test/functional/ui/popupmenu_spec.lua @@ -7,7 +7,6 @@ local insert = helpers.insert local meths = helpers.meths local command = helpers.command local funcs = helpers.funcs -local get_pathsep = helpers.get_pathsep local eq = helpers.eq local pcall_err = helpers.pcall_err local exec_lua = helpers.exec_lua @@ -1785,6 +1784,8 @@ describe('builtin popupmenu', function() screen:try_resize(32,10) command('set wildmenu') command('set wildoptions=pum') + command('set shellslash') + command("cd test/functional/fixtures/wildpum") feed(':sign ') screen:expect([[ @@ -1800,7 +1801,7 @@ describe('builtin popupmenu', function() :sign ^ | ]]) - feed('<tab>') + feed('<Tab>') screen:expect([[ | {1:~ }| @@ -1814,21 +1815,215 @@ describe('builtin popupmenu', function() :sign define^ | ]]) - feed('<left>') + feed('<Right><Right>') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }{n: define }{1: }| + {1:~ }{n: jump }{1: }| + {1:~ }{s: list }{1: }| + {1:~ }{n: place }{1: }| + {1:~ }{n: undefine }{1: }| + {1:~ }{n: unplace }{1: }| + :sign list^ | + ]]) + + feed('<C-N>') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }{n: define }{1: }| + {1:~ }{n: jump }{1: }| + {1:~ }{n: list }{1: }| + {1:~ }{s: place }{1: }| + {1:~ }{n: undefine }{1: }| + {1:~ }{n: unplace }{1: }| + :sign place^ | + ]]) + + feed('<C-P>') screen:expect([[ | {1:~ }| {1:~ }| {1:~ }{n: define }{1: }| {1:~ }{n: jump }{1: }| + {1:~ }{s: list }{1: }| + {1:~ }{n: place }{1: }| + {1:~ }{n: undefine }{1: }| + {1:~ }{n: unplace }{1: }| + :sign list^ | + ]]) + + feed('<Left>') + 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^ | + ]]) + + -- pressing <C-E> should end completion and go back to the original match + feed('<C-E>') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| :sign ^ | ]]) - feed('<left>') + -- pressing <C-Y> should select the current match and end completion + feed('<Tab><C-P><C-P><C-Y>') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + :sign unplace^ | + ]]) + + -- showing popup menu in different columns in the cmdline + feed('<C-U>sign define <Tab>') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }{s: culhl= }{1: }| + {1:~ }{n: icon= }{1: }| + {1:~ }{n: linehl= }{1: }| + {1:~ }{n: numhl= }{1: }| + {1:~ }{n: text= }{1: }| + {1:~ }{n: texthl= }{1: }| + :sign define culhl=^ | + ]]) + + feed('<Space><Tab>') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }{s: culhl= }{1: }| + {1:~ }{n: icon= }{1: }| + {1:~ }{n: linehl= }{1: }| + {1:~ }{n: numhl= }{1: }| + {1:~ }{n: text= }{1: }| + {1:~ }{n: texthl= }{1: }| + :sign define culhl= culhl=^ | + ]]) + + feed('<C-U>e Xdi<Tab><Tab>') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }{s: XdirA/ }{1: }| + {1:~ }{n: XfileA }{1: }| + :e Xdir/XdirA/^ | + ]]) + + -- Pressing <Down> on a directory name should go into that directory + feed('<Down>') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }{s: XdirB/ }{1: }| + {1:~ }{n: XfileB }{1: }| + :e Xdir/XdirA/XdirB/^ | + ]]) + + -- Pressing <Up> on a directory name should go to the parent directory + feed('<Up>') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }{s: XdirA/ }{1: }| + {1:~ }{n: XfileA }{1: }| + :e Xdir/XdirA/^ | + ]]) + + -- Pressing <C-A> when the popup menu is displayed should list all the + -- matches and remove the popup menu + feed(':<C-U>sign <Tab><C-A>') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {4: }| + :sign define jump list place und| + efine unplace^ | + ]]) + + -- Pressing <Left> after that should move the cursor + feed('<Left>') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {4: }| + :sign define jump list place und| + efine unplac^e | + ]]) + feed('<End>') + + -- Pressing <C-D> when the popup menu is displayed should remove the popup + -- menu + feed('<C-U>sign <Tab><C-D>') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {4: }| + :sign define | + define | + :sign define^ | + ]]) + + -- Pressing <S-Tab> should open the popup menu with the last entry selected + feed('<C-U><CR>:sign <S-Tab><C-P>') screen:expect([[ | {1:~ }| @@ -1837,23 +2032,195 @@ describe('builtin popupmenu', function() {1:~ }{n: jump }{1: }| {1:~ }{n: list }{1: }| {1:~ }{n: place }{1: }| + {1:~ }{s: undefine }{1: }| + {1:~ }{n: unplace }{1: }| + :sign undefine^ | + ]]) + + -- Pressing <Esc> should close the popup menu and cancel the cmd line + feed('<C-U><CR>:sign <Tab><Esc>') + screen:expect([[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + + -- Typing a character when the popup is open, should close the popup + feed(':sign <Tab>x') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + :sign definex^ | + ]]) + + -- When the popup is open, entering the cmdline window should close the popup + feed('<C-U>sign <Tab><C-F>') + screen:expect([[ + | + {3:[No Name] }| + {1::}sign define | + {1::}sign define^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {4:[Command Line] }| + :sign define | + ]]) + feed(':q<CR>') + + -- After the last popup menu item, <C-N> should show the original string + feed(':sign u<Tab><C-N><C-N>') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| {1:~ }{n: undefine }{1: }| - {1:~ }{s: unplace }{1: }| - :sign unplace^ | + {1:~ }{n: unplace }{1: }| + :sign u^ | ]]) - feed('x') + -- Use the popup menu for the command name + feed('<C-U>bu<Tab>') screen:expect([[ | {1:~ }| {1:~ }| {1:~ }| {1:~ }| + {s: bufdo }{1: }| + {n: buffer }{1: }| + {n: buffers }{1: }| + {n: bunload }{1: }| + :bufdo^ | + ]]) + + -- Pressing <BS> should remove the popup menu and erase the last character + feed('<C-E><C-U>sign <Tab><BS>') + screen:expect([[ + | + {1:~ }| {1:~ }| {1:~ }| {1:~ }| {1:~ }| - :sign unplacex^ | + {1:~ }| + {1:~ }| + {1:~ }| + :sign defin^ | + ]]) + + -- Pressing <C-W> should remove the popup menu and erase the previous word + feed('<C-E><C-U>sign <Tab><C-W>') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + :sign ^ | + ]]) + + -- Pressing <C-U> should remove the popup menu and erase the entire line + feed('<C-E><C-U>sign <Tab><C-U>') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + :^ | + ]]) + + -- Using <C-E> to cancel the popup menu and then pressing <Up> should recall + -- the cmdline from history + feed('sign xyz<Esc>:sign <Tab><C-E><Up>') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + :sign xyz^ | + ]]) + + feed('<esc>') + + -- Check "list" still works + command('set wildmode=longest,list') + feed(':cn<Tab>') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }| + {4: }| + :cn | + cnewer cnoreabbrev | + cnext cnoremap | + cnfile cnoremenu | + :cn^ | + ]]) + feed('s') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }| + {4: }| + :cn | + cnewer cnoreabbrev | + cnext cnoremap | + cnfile cnoremenu | + :cns^ | + ]]) + + feed('<esc>') + command('set wildmode=full') + + -- Tests a directory name contained full-width characters. + feed(':e あいう/<Tab>') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }{s: 123 }{1: }| + {1:~ }{n: abc }{1: }| + {1:~ }{n: xyz }{1: }| + :e あいう/123^ | ]]) feed('<esc>') @@ -1927,12 +2294,12 @@ describe('builtin popupmenu', function() :b långfile^ | ]]) - -- special case: when patterns ends with "/", show menu items aligned - -- after the "/" feed('<esc>') command("close") command('set wildmode=full') - command("cd test/functional/fixtures/") + + -- special case: when patterns ends with "/", show menu items aligned + -- after the "/" feed(':e compdir/<tab>') screen:expect([[ | @@ -1949,7 +2316,7 @@ describe('builtin popupmenu', function() {1:~ }| {1:~ }{s: file1 }{1: }| {1:~ }{n: file2 }{1: }| - :e compdir]]..get_pathsep()..[[file1^ | + :e compdir/file1^ | ]]) end) @@ -2007,7 +2374,9 @@ describe('builtin popupmenu', function() command('set wildoptions=pum') command('set wildmode=longest,full') - feed(':sign u<tab>') + -- With 'wildmode' set to 'longest,full', completing a match should display + -- the longest match, the wildmenu should not be displayed. + feed(':sign u<Tab>') screen:expect{grid=[[ | {1:~ }| @@ -2020,7 +2389,8 @@ describe('builtin popupmenu', function() ]]} eq(0, funcs.wildmenumode()) - feed('<tab>') + -- pressing <Tab> should display the wildmenu + feed('<Tab>') screen:expect{grid=[[ | {1:~ }| @@ -2032,6 +2402,19 @@ describe('builtin popupmenu', function() :sign undefine^ | ]]} eq(1, funcs.wildmenumode()) + + -- pressing <Tab> second time should select the next entry in the menu + feed('<Tab>') + screen:expect{grid=[[ + | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }{n: undefine }{1: }| + {1:~ }{s: unplace }{1: }| + :sign unplace^ | + ]]} end) it("'pumblend' RGB-color", function() diff --git a/test/functional/ui/screen.lua b/test/functional/ui/screen.lua index ea98705394..6ee9e7b393 100644 --- a/test/functional/ui/screen.lua +++ b/test/functional/ui/screen.lua @@ -546,7 +546,7 @@ function Screen:_wait(check, flags) return true end - run_session(self._session, flags.request_cb, notification_cb, nil, minimal_timeout) + local eof = run_session(self._session, flags.request_cb, notification_cb, nil, minimal_timeout) if not did_flush then err = "no flush received" elseif not checked then @@ -557,9 +557,9 @@ function Screen:_wait(check, flags) end end - if not success_seen then + if not success_seen and not eof then did_miminal_timeout = true - run_session(self._session, flags.request_cb, notification_cb, nil, timeout-minimal_timeout) + eof = run_session(self._session, flags.request_cb, notification_cb, nil, timeout-minimal_timeout) end local did_warn = false @@ -600,8 +600,10 @@ between asynchronous (feed(), nvim_input()) and synchronous API calls. if err then + if eof then err = err..'\n\n'..eof[2] end busted.fail(err, 3) elseif did_warn then + if eof then print(eof[2]) end local tb = debug.traceback() local index = string.find(tb, '\n%s*%[C]') print(string.sub(tb,1,index)) diff --git a/test/functional/ui/statusline_spec.lua b/test/functional/ui/statusline_spec.lua index f3735c8e4c..add5144e1b 100644 --- a/test/functional/ui/statusline_spec.lua +++ b/test/functional/ui/statusline_spec.lua @@ -394,7 +394,7 @@ describe('global statusline', function() meths.input_mouse('left', 'drag', '', 0, 14, 10) eq(1, meths.get_option('cmdheight')) meths.input_mouse('left', 'drag', '', 0, 15, 10) - eq(0, meths.get_option('cmdheight')) + eq(1, meths.get_option('cmdheight')) meths.input_mouse('left', 'drag', '', 0, 14, 10) eq(1, meths.get_option('cmdheight')) end) diff --git a/test/functional/ui/wildmode_spec.lua b/test/functional/ui/wildmode_spec.lua index 98398bc7a1..58ffa3bda8 100644 --- a/test/functional/ui/wildmode_spec.lua +++ b/test/functional/ui/wildmode_spec.lua @@ -461,20 +461,20 @@ end) describe('command line completion', function() local screen before_each(function() + clear() screen = Screen.new(40, 5) screen:set_default_attr_ids({ [1] = {bold = true, foreground = Screen.colors.Blue1}, [2] = {foreground = Screen.colors.Grey0, background = Screen.colors.Yellow}, [3] = {bold = true, reverse = true}, }) + screen:attach() end) after_each(function() os.remove('Xtest-functional-viml-compl-dir') end) it('lists directories with empty PATH', function() - clear() - screen:attach() local tmp = funcs.tempname() command('e '.. tmp) command('cd %:h') @@ -491,8 +491,6 @@ describe('command line completion', function() end) it('completes env var names #9681', function() - clear() - screen:attach() command('let $XTEST_1 = "foo" | let $XTEST_2 = "bar"') command('set wildmenu wildmode=full') feed(':!echo $XTEST_<tab>') @@ -521,6 +519,58 @@ describe('command line completion', function() :!echo $XTEST_1AaあB^ | ]]) end) + + it('does not leak memory with <S-Tab> with wildmenu and only one match #19874', function() + meths.set_option('wildmenu', true) + meths.set_option('wildmode', 'full') + meths.set_option('wildoptions', 'pum') + + feed(':sign unpla<S-Tab>') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }| + :sign unplace^ | + ]]) + + feed('<Space>buff<Tab>') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }| + :sign unplace buffer=^ | + ]]) + end) + + it('does not show matches with <S-Tab> without wildmenu with wildmode=full', function() + meths.set_option('wildmenu', false) + meths.set_option('wildmode', 'full') + + feed(':sign <S-Tab>') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }| + :sign unplace^ | + ]]) + end) + + it('shows matches with <S-Tab> without wildmenu with wildmode=list', function() + meths.set_option('wildmenu', false) + meths.set_option('wildmode', 'list') + + feed(':sign <S-Tab>') + screen:expect([[ + {3: }| + :sign define | + define list undefine | + jump place unplace | + :sign unplace^ | + ]]) + end) end) describe('ui/ext_wildmenu', function() diff --git a/test/functional/ui/winbar_spec.lua b/test/functional/ui/winbar_spec.lua index 60fa10da87..ece27ec3ff 100644 --- a/test/functional/ui/winbar_spec.lua +++ b/test/functional/ui/winbar_spec.lua @@ -7,6 +7,8 @@ local meths = helpers.meths local eq = helpers.eq local poke_eventloop = helpers.poke_eventloop local feed = helpers.feed +local funcs = helpers.funcs +local curwin = helpers.curwin local pcall_err = helpers.pcall_err describe('winbar', function() @@ -48,6 +50,11 @@ describe('winbar', function() {3:~ }| | ]]) + -- winbar is excluded from the heights returned by winheight() and getwininfo() + eq(11, funcs.winheight(0)) + local win_info = funcs.getwininfo(curwin().id)[1] + eq(11, win_info.height) + eq(1, win_info.winbar) end) it('works with custom \'fillchars\' value', function() @@ -579,47 +586,95 @@ describe('winbar', function() end) end) -it('local winbar works with tabs', function() - clear() - local screen = Screen.new(60, 13) - screen:attach() - screen:set_default_attr_ids({ - [1] = {bold = true}, - [2] = {reverse = true}, - [3] = {bold = true, foreground = Screen.colors.Blue}, - [4] = {underline = true, background = Screen.colors.LightGray} - }) - meths.set_option_value('winbar', 'foo', { scope = 'local', win = 0 }) - command('tabnew') - screen:expect([[ - {4: [No Name] }{1: [No Name] }{2: }{4:X}| - ^ | - {3:~ }| - {3:~ }| - {3:~ }| - {3:~ }| - {3:~ }| - {3:~ }| - {3:~ }| - {3:~ }| - {3:~ }| - {3:~ }| - | - ]]) - command('tabnext') - screen:expect{grid=[[ - {1: [No Name] }{4: [No Name] }{2: }{4:X}| - {1:foo }| - ^ | - {3:~ }| - {3:~ }| - {3:~ }| - {3:~ }| - {3:~ }| - {3:~ }| - {3:~ }| - {3:~ }| - {3:~ }| - | - ]]} +describe('local winbar with tabs', function() + local screen + before_each(function() + clear() + screen = Screen.new(60, 10) + screen:attach() + screen:set_default_attr_ids({ + [1] = {bold = true}, + [2] = {reverse = true}, + [3] = {bold = true, foreground = Screen.colors.Blue}, + [4] = {underline = true, background = Screen.colors.LightGray} + }) + meths.set_option_value('winbar', 'foo', { scope = 'local', win = 0 }) + end) + + it('works', function() + command('tabnew') + screen:expect([[ + {4: [No Name] }{1: [No Name] }{2: }{4:X}| + ^ | + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + | + ]]) + command('tabnext') + screen:expect{grid=[[ + {1: [No Name] }{4: [No Name] }{2: }{4:X}| + {1:foo }| + ^ | + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + | + ]]} + end) + + it('can edit new empty buffer #19458', function() + insert [[ + some + goofy + text]] + screen:expect{grid=[[ + {1:foo }| + some | + goofy | + tex^t | + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + | + ]]} + + -- this used to throw an E315 ml_get error + command 'tabedit' + screen:expect{grid=[[ + {4: + [No Name] }{1: [No Name] }{2: }{4:X}| + ^ | + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + | + ]]} + + command 'tabprev' + screen:expect{grid=[[ + {1: + [No Name] }{4: [No Name] }{2: }{4:X}| + {1:foo }| + some | + goofy | + tex^t | + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + | + ]]} + end) end) diff --git a/test/functional/vimscript/eval_spec.lua b/test/functional/vimscript/eval_spec.lua index 0c2ca8de78..d4fa7afe89 100644 --- a/test/functional/vimscript/eval_spec.lua +++ b/test/functional/vimscript/eval_spec.lua @@ -10,11 +10,13 @@ -- test/functional/vimscript/functions_spec.lua local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') local lfs = require('lfs') local clear = helpers.clear local eq = helpers.eq local exc_exec = helpers.exc_exec +local exec = helpers.exec local eval = helpers.eval local command = helpers.command local write_file = helpers.write_file @@ -144,3 +146,104 @@ describe('List support code', function() end 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() + os.remove('throw1.vim') + os.remove('throw2.vim') + os.remove('throw3.vim') + end) + + it('is not forgotten #13490', function() + command('autocmd BufWinEnter * throw "i am error"') + eq('i am error', exc_exec('try | new | endtry')) + + -- Like Vim, throwing here aborts the processing of the script, but does not stop :runtime! + -- from processing the others. + -- Only the first thrown exception should be rethrown from the :try below, though. + for i = 1, 3 do + write_file('throw' .. i .. '.vim', ([[ + let result ..= '%d' + throw 'throw%d' + let result ..= 'X' + ]]):format(i, i)) + end + command('set runtimepath+=. | let result = ""') + eq('throw1', exc_exec('try | runtime! throw*.vim | endtry')) + eq('123', eval('result')) + end) +end) diff --git a/test/functional/vimscript/input_spec.lua b/test/functional/vimscript/input_spec.lua index 554d15e550..f50b39c2c5 100644 --- a/test/functional/vimscript/input_spec.lua +++ b/test/functional/vimscript/input_spec.lua @@ -8,7 +8,8 @@ local clear = helpers.clear local source = helpers.source local command = helpers.command local exc_exec = helpers.exc_exec -local nvim_async = helpers.nvim_async +local pcall_err = helpers.pcall_err +local async_meths = helpers.async_meths local NIL = helpers.NIL local screen @@ -449,6 +450,78 @@ describe('inputdialog()', function() end) describe('confirm()', function() + -- oldtest: Test_confirm() + it('works', function() + meths.set_option('more', false) -- Avoid hit-enter prompt + meths.set_option('laststatus', 2) + -- screen:expect() calls are needed to avoid feeding input too early + screen:expect({any = '%[No Name%]'}) + + async_meths.command([[let a = confirm('Press O to proceed')]]) + screen:expect({any = '{CONFIRM:.+: }'}) + feed('o') + screen:expect({any = '%[No Name%]'}) + eq(1, meths.get_var('a')) + + async_meths.command([[let a = 'Are you sure?'->confirm("&Yes\n&No")]]) + screen:expect({any = '{CONFIRM:.+: }'}) + feed('y') + screen:expect({any = '%[No Name%]'}) + eq(1, meths.get_var('a')) + + async_meths.command([[let a = confirm('Are you sure?', "&Yes\n&No")]]) + screen:expect({any = '{CONFIRM:.+: }'}) + feed('n') + screen:expect({any = '%[No Name%]'}) + eq(2, meths.get_var('a')) + + -- Not possible to match Vim's CTRL-C test here as CTRL-C always sets got_int in Nvim. + + -- confirm() should return 0 when pressing ESC. + async_meths.command([[let a = confirm('Are you sure?', "&Yes\n&No")]]) + screen:expect({any = '{CONFIRM:.+: }'}) + feed('<Esc>') + screen:expect({any = '%[No Name%]'}) + eq(0, meths.get_var('a')) + + -- Default choice is returned when pressing <CR>. + async_meths.command([[let a = confirm('Are you sure?', "&Yes\n&No")]]) + screen:expect({any = '{CONFIRM:.+: }'}) + feed('<CR>') + screen:expect({any = '%[No Name%]'}) + eq(1, meths.get_var('a')) + + async_meths.command([[let a = confirm('Are you sure?', "&Yes\n&No", 2)]]) + screen:expect({any = '{CONFIRM:.+: }'}) + feed('<CR>') + screen:expect({any = '%[No Name%]'}) + eq(2, meths.get_var('a')) + + async_meths.command([[let a = confirm('Are you sure?', "&Yes\n&No", 0)]]) + screen:expect({any = '{CONFIRM:.+: }'}) + feed('<CR>') + screen:expect({any = '%[No Name%]'}) + eq(0, meths.get_var('a')) + + -- Test with the {type} 4th argument + for _, type in ipairs({'Error', 'Question', 'Info', 'Warning', 'Generic'}) do + async_meths.command(([[let a = confirm('Are you sure?', "&Yes\n&No", 1, '%s')]]):format(type)) + screen:expect({any = '{CONFIRM:.+: }'}) + feed('y') + screen:expect({any = '%[No Name%]'}) + eq(1, meths.get_var('a')) + end + + eq('Vim(call):E730: using List as a String', + pcall_err(command, 'call confirm([])')) + eq('Vim(call):E730: using List as a String', + pcall_err(command, 'call confirm("Are you sure?", [])')) + eq('Vim(call):E745: Using a List as a Number', + pcall_err(command, 'call confirm("Are you sure?", "&Yes\n&No\n", [])')) + eq('Vim(call):E730: using List as a String', + pcall_err(command, 'call confirm("Are you sure?", "&Yes\n&No\n", 0, [])')) + end) + it("shows dialog even if :silent #8788", function() command("autocmd BufNewFile * call confirm('test')") @@ -483,7 +556,7 @@ describe('confirm()', function() feed(':call nvim_command("edit x")<cr>') check_and_clear(':call nvim_command("edit |\n') - nvim_async('command', 'edit x') + async_meths.command('edit x') check_and_clear(' |\n') end) end) diff --git a/test/functional/vimscript/null_spec.lua b/test/functional/vimscript/null_spec.lua index f23f00bcc5..2451da983e 100644 --- a/test/functional/vimscript/null_spec.lua +++ b/test/functional/vimscript/null_spec.lua @@ -135,7 +135,7 @@ describe('NULL', function() null_test('does not make Neovim crash when v:oldfiles gets assigned to that', ':let v:oldfiles = L|oldfiles', 0) null_expr_test('does not make complete() crash or error out', 'execute(":normal i\\<C-r>=complete(1, L)[-1]\\n")', - '', '\n', function() + 0, '', function() eq({''}, curbufmeths.get_lines(0, -1, false)) end) null_expr_test('is accepted by setmatches()', 'setmatches(L)', 0, 0) diff --git a/test/functional/vimscript/special_vars_spec.lua b/test/functional/vimscript/special_vars_spec.lua index 97a12d490d..14ccbc3827 100644 --- a/test/functional/vimscript/special_vars_spec.lua +++ b/test/functional/vimscript/special_vars_spec.lua @@ -125,9 +125,9 @@ describe('Special values', function() end) it('work with . (concat) properly', function() - eq("true", eval('"" . v:true')) - eq("null", eval('"" . v:null')) - eq("false", eval('"" . v:false')) + eq("v:true", eval('"" . v:true')) + eq("v:null", eval('"" . v:null')) + eq("v:false", eval('"" . v:false')) end) it('work with type()', function() diff --git a/test/unit/buffer_spec.lua b/test/unit/buffer_spec.lua index 204d713fb7..5dccc2f5a2 100644 --- a/test/unit/buffer_spec.lua +++ b/test/unit/buffer_spec.lua @@ -9,6 +9,7 @@ local NULL = helpers.NULL local globals = helpers.cimport("./src/nvim/globals.h") local buffer = helpers.cimport("./src/nvim/buffer.h") +local stl = helpers.cimport("./src/nvim/statusline.h") describe('buffer functions', function() @@ -228,7 +229,7 @@ describe('buffer functions', function() local fillchar = arg.fillchar or (' '):byte() local maximum_cell_count = arg.maximum_cell_count or buffer_byte_size - return buffer.build_stl_str_hl(globals.curwin, + return stl.build_stl_str_hl(globals.curwin, output_buffer, buffer_byte_size, to_cstr(pat), diff --git a/test/unit/eval/typval_spec.lua b/test/unit/eval/typval_spec.lua index e61b568f3a..6387f89fe4 100644 --- a/test/unit/eval/typval_spec.lua +++ b/test/unit/eval/typval_spec.lua @@ -2992,9 +2992,9 @@ describe('typval.c', function() {lib.VAR_FUNC, {v_string=NULL}, 'E729: using Funcref as a String', ''}, {lib.VAR_LIST, {v_list=NULL}, 'E730: using List as a String', ''}, {lib.VAR_DICT, {v_dict=NULL}, 'E731: using Dictionary as a String', ''}, - {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarNull}, nil, 'null'}, - {lib.VAR_BOOL, {v_bool=lib.kBoolVarTrue}, nil, 'true'}, - {lib.VAR_BOOL, {v_bool=lib.kBoolVarFalse}, nil, 'false'}, + {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarNull}, nil, 'v:null'}, + {lib.VAR_BOOL, {v_bool=lib.kBoolVarTrue}, nil, 'v:true'}, + {lib.VAR_BOOL, {v_bool=lib.kBoolVarFalse}, nil, 'v:false'}, {lib.VAR_UNKNOWN, nil, 'E908: using an invalid value as a String', ''}, }) do -- Using to_cstr in place of Neovim allocated string, cannot @@ -3036,9 +3036,9 @@ describe('typval.c', function() {lib.VAR_FUNC, {v_string=NULL}, 'E729: using Funcref as a String', nil}, {lib.VAR_LIST, {v_list=NULL}, 'E730: using List as a String', nil}, {lib.VAR_DICT, {v_dict=NULL}, 'E731: using Dictionary as a String', nil}, - {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarNull}, nil, 'null'}, - {lib.VAR_BOOL, {v_bool=lib.kBoolVarTrue}, nil, 'true'}, - {lib.VAR_BOOL, {v_bool=lib.kBoolVarFalse}, nil, 'false'}, + {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarNull}, nil, 'v:null'}, + {lib.VAR_BOOL, {v_bool=lib.kBoolVarTrue}, nil, 'v:true'}, + {lib.VAR_BOOL, {v_bool=lib.kBoolVarFalse}, nil, 'v:false'}, {lib.VAR_UNKNOWN, nil, 'E908: using an invalid value as a String', nil}, }) do -- Using to_cstr, cannot free with tv_clear @@ -3078,9 +3078,9 @@ describe('typval.c', function() {lib.VAR_FUNC, {v_string=NULL}, 'E729: using Funcref as a String', ''}, {lib.VAR_LIST, {v_list=NULL}, 'E730: using List as a String', ''}, {lib.VAR_DICT, {v_dict=NULL}, 'E731: using Dictionary as a String', ''}, - {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarNull}, nil, 'null'}, - {lib.VAR_BOOL, {v_bool=lib.kBoolVarTrue}, nil, 'true'}, - {lib.VAR_BOOL, {v_bool=lib.kBoolVarFalse}, nil, 'false'}, + {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarNull}, nil, 'v:null'}, + {lib.VAR_BOOL, {v_bool=lib.kBoolVarTrue}, nil, 'v:true'}, + {lib.VAR_BOOL, {v_bool=lib.kBoolVarFalse}, nil, 'v:false'}, {lib.VAR_UNKNOWN, nil, 'E908: using an invalid value as a String', ''}, }) do -- Using to_cstr, cannot free with tv_clear @@ -3121,9 +3121,9 @@ describe('typval.c', function() {lib.VAR_FUNC, {v_string=NULL}, 'E729: using Funcref as a String', nil}, {lib.VAR_LIST, {v_list=NULL}, 'E730: using List as a String', nil}, {lib.VAR_DICT, {v_dict=NULL}, 'E731: using Dictionary as a String', nil}, - {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarNull}, nil, 'null'}, - {lib.VAR_BOOL, {v_bool=lib.kBoolVarTrue}, nil, 'true'}, - {lib.VAR_BOOL, {v_bool=lib.kBoolVarFalse}, nil, 'false'}, + {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarNull}, nil, 'v:null'}, + {lib.VAR_BOOL, {v_bool=lib.kBoolVarTrue}, nil, 'v:true'}, + {lib.VAR_BOOL, {v_bool=lib.kBoolVarFalse}, nil, 'v:false'}, {lib.VAR_UNKNOWN, nil, 'E908: using an invalid value as a String', nil}, }) do -- Using to_cstr, cannot free with tv_clear diff --git a/test/unit/indent_spec.lua b/test/unit/indent_spec.lua new file mode 100644 index 0000000000..ec86822b55 --- /dev/null +++ b/test/unit/indent_spec.lua @@ -0,0 +1,30 @@ +local helpers = require("test.unit.helpers")(after_each) +local itp = helpers.gen_itp(it) + +local eq = helpers.eq + +local indent = helpers.cimport("./src/nvim/indent.h") +local globals = helpers.cimport("./src/nvim/globals.h") + +describe('get_sts_value', function() + itp([[returns 'softtabstop' when it is non-negative]], function() + globals.curbuf.b_p_sts = 5 + eq(5, indent.get_sts_value()) + + globals.curbuf.b_p_sts = 0 + eq(0, indent.get_sts_value()) + end) + + itp([[returns "effective shiftwidth" when 'softtabstop' is negative]], function() + local shiftwidth = 2 + globals.curbuf.b_p_sw = shiftwidth + local tabstop = 5 + globals.curbuf.b_p_ts = tabstop + globals.curbuf.b_p_sts = -2 + eq(shiftwidth, indent.get_sts_value()) + + shiftwidth = 0 + globals.curbuf.b_p_sw = shiftwidth + eq(tabstop, indent.get_sts_value()) + end) +end) diff --git a/test/unit/option_spec.lua b/test/unit/option_spec.lua deleted file mode 100644 index b8b8a435bc..0000000000 --- a/test/unit/option_spec.lua +++ /dev/null @@ -1,52 +0,0 @@ -local helpers = require("test.unit.helpers")(after_each) -local itp = helpers.gen_itp(it) - -local to_cstr = helpers.to_cstr -local eq = helpers.eq - -local option = helpers.cimport("./src/nvim/option.h") -local globals = helpers.cimport("./src/nvim/globals.h") - -local check_ff_value = function(ff) - return option.check_ff_value(to_cstr(ff)) -end - -describe('check_ff_value', function() - - itp('views empty string as valid', function() - eq(1, check_ff_value("")) - end) - - itp('views "unix", "dos" and "mac" as valid', function() - eq(1, check_ff_value("unix")) - eq(1, check_ff_value("dos")) - eq(1, check_ff_value("mac")) - end) - - itp('views "foo" as invalid', function() - eq(0, check_ff_value("foo")) - end) -end) - -describe('get_sts_value', function() - itp([[returns 'softtabstop' when it is non-negative]], function() - globals.curbuf.b_p_sts = 5 - eq(5, option.get_sts_value()) - - globals.curbuf.b_p_sts = 0 - eq(0, option.get_sts_value()) - end) - - itp([[returns "effective shiftwidth" when 'softtabstop' is negative]], function() - local shiftwidth = 2 - globals.curbuf.b_p_sw = shiftwidth - local tabstop = 5 - globals.curbuf.b_p_ts = tabstop - globals.curbuf.b_p_sts = -2 - eq(shiftwidth, option.get_sts_value()) - - shiftwidth = 0 - globals.curbuf.b_p_sw = shiftwidth - eq(tabstop, option.get_sts_value()) - end) -end) diff --git a/test/unit/optionstr_spec.lua b/test/unit/optionstr_spec.lua new file mode 100644 index 0000000000..f8122f4fe0 --- /dev/null +++ b/test/unit/optionstr_spec.lua @@ -0,0 +1,28 @@ +local helpers = require("test.unit.helpers")(after_each) +local itp = helpers.gen_itp(it) + +local to_cstr = helpers.to_cstr +local eq = helpers.eq + +local option = helpers.cimport("./src/nvim/optionstr.h") + +local check_ff_value = function(ff) + return option.check_ff_value(to_cstr(ff)) +end + +describe('check_ff_value', function() + + itp('views empty string as valid', function() + eq(1, check_ff_value("")) + end) + + itp('views "unix", "dos" and "mac" as valid', function() + eq(1, check_ff_value("unix")) + eq(1, check_ff_value("dos")) + eq(1, check_ff_value("mac")) + end) + + itp('views "foo" as invalid', function() + eq(0, check_ff_value("foo")) + end) +end) diff --git a/test/unit/path_spec.lua b/test/unit/path_spec.lua index fb476397e6..eb23a3cff1 100644 --- a/test/unit/path_spec.lua +++ b/test/unit/path_spec.lua @@ -640,6 +640,10 @@ describe('path.c', function() eq(2, path_with_url([[test-abc:\\xyz\foo\b3]])) eq(0, path_with_url([[-test://xyz/foo/b4]])) eq(0, path_with_url([[test-://xyz/foo/b5]])) + eq(1, path_with_url([[test-C:/xyz/foo/b5]])) + eq(1, path_with_url([[test-custom:/xyz/foo/b5]])) + eq(0, path_with_url([[c:/xyz/foo/b5]])) + eq(0, path_with_url([[C:/xyz/foo/b5]])) end) end) end) |