diff options
author | Josh Rahm <rahm@google.com> | 2022-08-19 12:26:08 -0600 |
---|---|---|
committer | Josh Rahm <rahm@google.com> | 2022-08-19 13:06:41 -0600 |
commit | a7237662f96933efe29eed8212464571e3778cd0 (patch) | |
tree | 27930202726b4251437c8cfa53069f65b4db90dc | |
parent | 02292344929069ea63c0bb872cc22d552d86b67f (diff) | |
parent | b2f979b30beac67906b2dd717fcb6a34f46f5e54 (diff) | |
download | rneovim-tmp.tar.gz rneovim-tmp.tar.bz2 rneovim-tmp.zip |
Merge branch 'master' of https://github.com/neovim/neovim into rahmtmp
313 files changed, 39210 insertions, 26031 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/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..4b220708cf 100644 --- a/runtime/autoload/python.vim +++ b/runtime/autoload/python.vim @@ -3,13 +3,28 @@ let s:keepcpo= &cpo set cpo&vim +" searchpair() can be slow, limit the time to 150 msec or what is put in +" g:pyindent_searchpair_timeout +let s: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 +let s:disable_parentheses_indenting = get(g:, 'pyindent_disable_parentheses_indenting', v:false) + +let s:maxoff = 50 " maximum number of lines to look backwards for () + +function s:SearchBracket(fromlnum, flags) + return searchpairpos('[[({]', '', '[])}]', a:flags, + \ {-> synID('.', col('.'), v:true)->synIDattr('name') + \ =~ '\%(Comment\|Todo\|String\)$'}, + \ [0, a:fromlnum - s:maxoff]->max(), s: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. @@ -39,30 +54,30 @@ 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 s: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 && parcol != col([parlnum, '$']) - 1 + return parcol + 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 +100,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,11 +120,7 @@ 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()) endif @@ -136,12 +143,12 @@ 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 synIDattr(synID(plnum, pline_len, 1), "name") =~ "\\(Comment\\|Todo\\)" 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 synIDattr(synID(plnum, col, 1), "name") =~ "\\(Comment\\|Todo\\)" let max = col else let min = col + 1 diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index be42b7c14e..a388592981 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..0fc8a30c20 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,6 +397,7 @@ 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} setcmdpos({pos}) Number set cursor position in command-line @@ -586,7 +588,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 > @@ -1063,7 +1065,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. @@ -1135,8 +1149,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. @@ -1988,6 +2002,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 +2010,7 @@ expand({string} [, {nosuf} [, {list}]]) *expand()* a function <SID> "<SNR>123_" where "123" is the current script ID |<SID>| + <stack> call stack <cword> word under the cursor <cWORD> WORD under the cursor <client> the {clientid} of the last received @@ -2980,10 +2996,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 +3134,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 +3521,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 +4743,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. @@ -5903,7 +5919,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 @@ -6653,7 +6669,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 +6833,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. @@ -7665,15 +7708,15 @@ stdpath({what}) *stdpath()* *E6100* 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..29eff75bfa 100644 --- a/runtime/doc/cmdline.txt +++ b/runtime/doc/cmdline.txt @@ -875,16 +875,22 @@ 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> 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* . *:<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/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..7fc0daa0ca 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|. @@ -160,7 +163,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 +204,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 +291,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 +357,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 +403,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 +415,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 +424,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 +464,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 lens. *lsp-highlight-signature* @@ -473,8 +476,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 +516,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 - - Return: ~ - true if any client returns true; false otherwise + Send a notification to a 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] (optional): The number of the buffer + {method} [string]: Name of the request method + {params} [string]: Arguments to send to the server - 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 +787,208 @@ 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) 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} (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"; } < - {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 +996,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 +1429,544 @@ 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* 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): 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) 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()| + + Members: + • {pid} (number) The LSP server's PID. + • {handle} A handle for low-level interaction with the LSP server + process |vim.loop|. ============================================================================== @@ -2146,23 +1974,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 +1995,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..42f3a5e432 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,114 @@ 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.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 +901,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 +952,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 +1003,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,35 +1044,35 @@ 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-vim-options* @@ -1248,877 +1237,848 @@ 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) + 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. + Get or set an |option|. Invalid key is an error. - This is a wrapper around |nvim_set_option()| and |nvim_get_option()|. + This is a wrapper around |nvim_set_option_value()| and + |nvim_get_option_value()|. - 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. + 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 + Example: > + vim.go.cmdheight = 4 + print(vim.go.columns) + print(vim.go.bar) -- error: invalid key < -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.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 *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) +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 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 +2094,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..2b2bfec6c7 100644 --- a/runtime/doc/map.txt +++ b/runtime/doc/map.txt @@ -1452,6 +1452,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 +1466,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 +1480,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 dfb6b746d6..cf099af391 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -591,7 +591,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 @@ -1321,7 +1325,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) @@ -2141,7 +2147,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' @@ -3735,8 +3742,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) @@ -4716,8 +4723,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'* @@ -4838,7 +4845,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'* @@ -5874,6 +5881,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. @@ -6189,7 +6201,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). @@ -7139,10 +7151,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..b74611633f 100644 --- a/runtime/doc/syntax.txt +++ b/runtime/doc/syntax.txt @@ -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..52531a1525 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 @@ -340,7 +336,7 @@ identical identifiers, highlighting both as |hl-WarningMsg|: > < 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 @@ -357,153 +353,146 @@ attribute: > Lua module: vim.treesitter *lua-treesitter-core* 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 ============================================================================== 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 + 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} The language the parser should parse + {path} Optional path the parser is located at + {silent} Don't throw an error if language not found ============================================================================== 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 + 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} the node + {source} The buffer or string from which the node is extracted get_query({lang}, {query_name}) *get_query()* - Returns the runtime query {query_name} for {lang}. + 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 +504,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 +540,220 @@ 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: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} + Parameters: ~ + {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..0c907bfb68 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 diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index 27c953a460..76beaf9830 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -209,7 +209,7 @@ Commands: |:Man| is available by default, with many improvements such as completion |:sign-define| accepts a `numhl` argument, to highlight the line number |:match| can be invoked before highlight group is defined - |:source| works with Lua and anonymous (no file) scripts + |:source| works with Lua User commands can support |:command-preview| to show results as you type Events: @@ -271,7 +271,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 +372,7 @@ Lua interface (|lua.txt|): Commands: |:doautocmd| does not warn about "No matching autocommands". + |:wincmd| accepts a count. Functions: |input()| and |inputdialog()| support for each other’s features (return on diff --git a/runtime/doc/windows.txt b/runtime/doc/windows.txt index 8062b9e28f..7355cec522 100644 --- a/runtime/doc/windows.txt +++ b/runtime/doc/windows.txt @@ -372,7 +372,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 +442,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..52a20d5c10 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 @@ -1161,6 +1164,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 +1536,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 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/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/python.in b/runtime/indent/testdir/python.in new file mode 100644 index 0000000000..868a63622b --- /dev/null +++ b/runtime/indent/testdir/python.in @@ -0,0 +1,68 @@ +" vim: set ft=python sw=4 et: + +" 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..c0c08af4b9 --- /dev/null +++ b/runtime/indent/testdir/python.ok @@ -0,0 +1,68 @@ +" vim: set ft=python sw=4 et: + +" 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/filetype.lua b/runtime/lua/vim/filetype.lua index 1b209e6a9d..99c98764dd 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', @@ -668,6 +669,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 +792,7 @@ local extension = { ptl = 'python', ql = 'ql', qll = 'ql', + qmd = 'quarto', R = function(path, bufnr) return require('vim.filetype.detect').r(bufnr) end, @@ -2472,7 +2489,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/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..fd64c1a495 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -338,54 +338,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) + --- + --- 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: + --- + --- None: One group for all clients + --- Full: One group for all clients + --- Incremental: One group per `offset_encoding` --- - --- state - --- use_incremental_sync: bool - --- buffers: bufnr -> buffer_state + --- 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. --- - --- 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 + --- @class CTGroup + --- @field sync_kind number TextDocumentSyncKind, considers config.flags.allow_incremental_sync + --- @field offset_encoding "utf-8"|"utf-16"|"utf-32" --- - --- timer?: uv_timer - --- lines: table - local state_by_client = {} + --- @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 +507,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 +543,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 +561,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 +598,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 +691,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 @@ -871,7 +985,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 +1082,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 +1108,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 +1144,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) @@ -1403,9 +1533,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 +1809,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 +1845,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 +2111,32 @@ function lsp.formatexpr(opts) return 1 end - local start_line = vim.v.lnum - local end_line = start_line + vim.v.count - 1 + local start_lnum = vim.v.lnum + local end_lnum = start_lnum + 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) - - -- 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..0926912066 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') @@ -248,13 +246,13 @@ end --- ---@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 +---@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, optional) Additional context for the LSP +---@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 @@ -342,6 +340,9 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params) 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 @@ -434,7 +435,7 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params) end end - stderr:read_start(function(_err, chunk) + stderr:read_start(function(_, chunk) if chunk then local _ = log.error() and log.error('rpc', cmd, 'stderr', chunk) end @@ -520,7 +521,7 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params) -- 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) + 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_callback = notify_reply_callbacks and notify_reply_callbacks[result_id] 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/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/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/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/shared/typescriptcommon.vim b/runtime/syntax/shared/typescriptcommon.vim new file mode 100644 index 0000000000..ef362fc721 --- /dev/null +++ b/runtime/syntax/shared/typescriptcommon.vim @@ -0,0 +1,2099 @@ +" Vim syntax file +" Language: TypeScript and TypeScriptReact +" Maintainer: Bram Moolenaar, Herrington Darkholme +" Last Change: 2021 Sep 22 +" Based On: Herrington Darkholme's yats.vim +" Changes: See https:github.com/HerringtonDarkholme/yats.vim +" Credits: See yats.vim on github + +if &cpo =~ 'C' + let s:cpo_save = &cpo + set cpo&vim +endif + + +" NOTE: this results in accurate highlighting, but can be slow. +syntax sync fromstart + +"Dollar sign is permitted anywhere in an identifier +setlocal iskeyword-=$ +if main_syntax == 'typescript' || main_syntax == 'typescriptreact' + setlocal iskeyword+=$ + " syntax cluster htmlJavaScript contains=TOP +endif +" For private field added from TypeScript 3.8 +setlocal iskeyword+=# + +" lowest priority on least used feature +syntax match typescriptLabel /[a-zA-Z_$]\k*:/he=e-1 contains=typescriptReserved nextgroup=@typescriptStatement skipwhite skipempty + +" other keywords like return,case,yield uses containedin +syntax region typescriptBlock matchgroup=typescriptBraces start=/{/ end=/}/ contains=@typescriptStatement,@typescriptComments fold +syntax cluster afterIdentifier contains= + \ typescriptDotNotation, + \ typescriptFuncCallArg, + \ typescriptTemplate, + \ typescriptIndexExpr, + \ @typescriptSymbols, + \ typescriptTypeArguments + +syntax match typescriptIdentifierName /\<\K\k*/ + \ nextgroup=@afterIdentifier + \ transparent + \ contains=@_semantic + \ skipnl skipwhite + +syntax match typescriptProp contained /\K\k*!\?/ + \ transparent + \ contains=@props + \ nextgroup=@afterIdentifier + \ skipwhite skipempty + +syntax region typescriptIndexExpr contained matchgroup=typescriptProperty start=/\[/rs=s+1 end=/]/he=e-1 contains=@typescriptValue nextgroup=@typescriptSymbols,typescriptDotNotation,typescriptFuncCallArg skipwhite skipempty + +syntax match typescriptDotNotation /\.\|?\.\|!\./ nextgroup=typescriptProp skipnl +syntax match typescriptDotStyleNotation /\.style\./ nextgroup=typescriptDOMStyle transparent +" syntax match typescriptFuncCall contained /[a-zA-Z]\k*\ze(/ nextgroup=typescriptFuncCallArg +syntax region typescriptParenExp matchgroup=typescriptParens start=/(/ end=/)/ contains=@typescriptComments,@typescriptValue,typescriptCastKeyword nextgroup=@typescriptSymbols skipwhite skipempty +syntax region typescriptFuncCallArg contained matchgroup=typescriptParens start=/(/ end=/)/ contains=@typescriptValue,@typescriptComments nextgroup=@typescriptSymbols,typescriptDotNotation skipwhite skipempty skipnl +syntax region typescriptEventFuncCallArg contained matchgroup=typescriptParens start=/(/ end=/)/ contains=@typescriptEventExpression +syntax region typescriptEventString contained start=/\z(["']\)/ skip=/\\\\\|\\\z1\|\\\n/ end=/\z1\|$/ contains=typescriptASCII,@events + +syntax region typescriptDestructureString + \ start=/\z(["']\)/ skip=/\\\\\|\\\z1\|\\\n/ end=/\z1\|$/ + \ contains=typescriptASCII + \ nextgroup=typescriptDestructureAs + \ contained skipwhite skipempty + +syntax cluster typescriptVariableDeclarations + \ contains=typescriptVariableDeclaration,@typescriptDestructures + +syntax match typescriptVariableDeclaration /[A-Za-z_$]\k*/ + \ nextgroup=typescriptTypeAnnotation,typescriptAssign + \ contained skipwhite skipempty + +syntax cluster typescriptDestructureVariables contains= + \ typescriptRestOrSpread, + \ typescriptDestructureComma, + \ typescriptDestructureLabel, + \ typescriptDestructureVariable, + \ @typescriptDestructures + +syntax match typescriptDestructureVariable /[A-Za-z_$]\k*/ contained + \ nextgroup=typescriptDefaultParam + \ contained skipwhite skipempty + +syntax match typescriptDestructureLabel /[A-Za-z_$]\k*\ze\_s*:/ + \ nextgroup=typescriptDestructureAs + \ contained skipwhite skipempty + +syntax match typescriptDestructureAs /:/ + \ nextgroup=typescriptDestructureVariable,@typescriptDestructures + \ contained skipwhite skipempty + +syntax match typescriptDestructureComma /,/ contained + +syntax cluster typescriptDestructures contains= + \ typescriptArrayDestructure, + \ typescriptObjectDestructure + +syntax region typescriptArrayDestructure matchgroup=typescriptBraces + \ start=/\[/ end=/]/ + \ contains=@typescriptDestructureVariables,@typescriptComments + \ nextgroup=typescriptTypeAnnotation,typescriptAssign + \ transparent contained skipwhite skipempty fold + +syntax region typescriptObjectDestructure matchgroup=typescriptBraces + \ start=/{/ end=/}/ + \ contains=typescriptDestructureString,@typescriptDestructureVariables,@typescriptComments + \ nextgroup=typescriptTypeAnnotation,typescriptAssign + \ transparent contained skipwhite skipempty fold + +"Syntax in the JavaScript code + +" String +syntax match typescriptASCII contained /\\\d\d\d/ + +syntax region typescriptTemplateSubstitution matchgroup=typescriptTemplateSB + \ start=/\${/ end=/}/ + \ contains=@typescriptValue + \ contained + + +syntax region typescriptString + \ start=+\z(["']\)+ skip=+\\\%(\z1\|$\)+ end=+\z1+ end=+$+ + \ contains=typescriptSpecial,@Spell + \ extend + +syntax match typescriptSpecial contained "\v\\%(x\x\x|u%(\x{4}|\{\x{1,6}})|c\u|.)" + +" From vim runtime +" <https://github.com/vim/vim/blob/master/runtime/syntax/javascript.vim#L48> +syntax region typescriptRegexpString start=+/[^/*]+me=e-1 skip=+\\\\\|\\/+ end=+/[gimuy]\{0,5\}\s*$+ end=+/[gimuy]\{0,5\}\s*[;.,)\]}:]+me=e-1 nextgroup=typescriptDotNotation oneline + +syntax region typescriptTemplate + \ start=/`/ skip=/\\\\\|\\`\|\n/ end=/`\|$/ + \ contains=typescriptTemplateSubstitution,typescriptSpecial,@Spell + \ nextgroup=@typescriptSymbols + \ skipwhite skipempty + +"Array +syntax region typescriptArray matchgroup=typescriptBraces + \ start=/\[/ end=/]/ + \ contains=@typescriptValue,@typescriptComments + \ nextgroup=@typescriptSymbols,typescriptDotNotation + \ skipwhite skipempty fold + +" Number +syntax match typescriptNumber /\<0[bB][01][01_]*\>/ nextgroup=@typescriptSymbols skipwhite skipempty +syntax match typescriptNumber /\<0[oO][0-7][0-7_]*\>/ nextgroup=@typescriptSymbols skipwhite skipempty +syntax match typescriptNumber /\<0[xX][0-9a-fA-F][0-9a-fA-F_]*\>/ nextgroup=@typescriptSymbols skipwhite skipempty +syntax match typescriptNumber /\<\%(\d[0-9_]*\%(\.\d[0-9_]*\)\=\|\.\d[0-9_]*\)\%([eE][+-]\=\d[0-9_]*\)\=\>/ + \ nextgroup=typescriptSymbols skipwhite skipempty + +syntax region typescriptObjectLiteral matchgroup=typescriptBraces + \ start=/{/ end=/}/ + \ contains=@typescriptComments,typescriptObjectLabel,typescriptStringProperty,typescriptComputedPropertyName,typescriptObjectAsyncKeyword + \ fold contained + +syntax keyword typescriptObjectAsyncKeyword async contained + +syntax match typescriptObjectLabel contained /\k\+\_s*/ + \ nextgroup=typescriptObjectColon,@typescriptCallImpl + \ skipwhite skipempty + +syntax region typescriptStringProperty contained + \ start=/\z(["']\)/ skip=/\\\\\|\\\z1\|\\\n/ end=/\z1/ + \ nextgroup=typescriptObjectColon,@typescriptCallImpl + \ skipwhite skipempty + +" syntax region typescriptPropertyName contained start=/\z(["']\)/ skip=/\\\\\|\\\z1\|\\\n/ end=/\z1(/me=e-1 nextgroup=@typescriptCallSignature skipwhite skipempty oneline +syntax region typescriptComputedPropertyName contained matchgroup=typescriptBraces + \ start=/\[/rs=s+1 end=/]/ + \ contains=@typescriptValue + \ nextgroup=typescriptObjectColon,@typescriptCallImpl + \ skipwhite skipempty + +" syntax region typescriptComputedPropertyName contained matchgroup=typescriptPropertyName start=/\[/rs=s+1 end=/]\_s*:/he=e-1 contains=@typescriptValue nextgroup=@typescriptValue skipwhite skipempty +" syntax region typescriptComputedPropertyName contained matchgroup=typescriptPropertyName start=/\[/rs=s+1 end=/]\_s*(/me=e-1 contains=@typescriptValue nextgroup=@typescriptCallSignature skipwhite skipempty +" Value for object, statement for label statement +syntax match typescriptRestOrSpread /\.\.\./ contained +syntax match typescriptObjectSpread /\.\.\./ contained containedin=typescriptObjectLiteral,typescriptArray nextgroup=@typescriptValue + +syntax match typescriptObjectColon contained /:/ nextgroup=@typescriptValue skipwhite skipempty + +" + - ^ ~ +syntax match typescriptUnaryOp /[+\-~!]/ + \ nextgroup=@typescriptValue + \ skipwhite + +syntax region typescriptTernary matchgroup=typescriptTernaryOp start=/?[.?]\@!/ end=/:/ contained contains=@typescriptValue,@typescriptComments nextgroup=@typescriptValue skipwhite skipempty + +syntax match typescriptAssign /=/ nextgroup=@typescriptValue + \ skipwhite skipempty + +" 2: ==, === +syntax match typescriptBinaryOp contained /===\?/ nextgroup=@typescriptValue skipwhite skipempty +" 6: >>>=, >>>, >>=, >>, >=, > +syntax match typescriptBinaryOp contained />\(>>=\|>>\|>=\|>\|=\)\?/ nextgroup=@typescriptValue skipwhite skipempty +" 4: <<=, <<, <=, < +syntax match typescriptBinaryOp contained /<\(<=\|<\|=\)\?/ nextgroup=@typescriptValue skipwhite skipempty +" 3: ||, |=, |, ||= +syntax match typescriptBinaryOp contained /||\?=\?/ nextgroup=@typescriptValue skipwhite skipempty +" 4: &&, &=, &, &&= +syntax match typescriptBinaryOp contained /&&\?=\?/ nextgroup=@typescriptValue skipwhite skipempty +" 2: ??, ??= +syntax match typescriptBinaryOp contained /??=\?/ nextgroup=@typescriptValue skipwhite skipempty +" 2: *=, * +syntax match typescriptBinaryOp contained /\*=\?/ nextgroup=@typescriptValue skipwhite skipempty +" 2: %=, % +syntax match typescriptBinaryOp contained /%=\?/ nextgroup=@typescriptValue skipwhite skipempty +" 2: /=, / +syntax match typescriptBinaryOp contained +/\(=\|[^\*/]\@=\)+ nextgroup=@typescriptValue skipwhite skipempty +syntax match typescriptBinaryOp contained /!==\?/ nextgroup=@typescriptValue skipwhite skipempty +" 2: !=, !== +syntax match typescriptBinaryOp contained /+\(+\|=\)\?/ nextgroup=@typescriptValue skipwhite skipempty +" 3: +, ++, += +syntax match typescriptBinaryOp contained /-\(-\|=\)\?/ nextgroup=@typescriptValue skipwhite skipempty +" 3: -, --, -= + +" exponentiation operator +" 2: **, **= +syntax match typescriptBinaryOp contained /\*\*=\?/ nextgroup=@typescriptValue + +syntax cluster typescriptSymbols contains=typescriptBinaryOp,typescriptKeywordOp,typescriptTernary,typescriptAssign,typescriptCastKeyword + +" runtime syntax/basic/reserved.vim +"Import +syntax keyword typescriptImport from as +syntax keyword typescriptImport import + \ nextgroup=typescriptImportType + \ skipwhite +syntax keyword typescriptImportType type + \ contained +syntax keyword typescriptExport export + \ nextgroup=typescriptExportType + \ skipwhite +syntax match typescriptExportType /\<type\s*{\@=/ + \ contained skipwhite skipempty skipnl +syntax keyword typescriptModule namespace module + +"this + +"JavaScript Prototype +syntax keyword typescriptPrototype prototype + \ nextgroup=@afterIdentifier + +syntax keyword typescriptCastKeyword as + \ nextgroup=@typescriptType + \ skipwhite + +"Program Keywords +syntax keyword typescriptIdentifier arguments this super + \ nextgroup=@afterIdentifier + +syntax keyword typescriptVariable let var + \ nextgroup=@typescriptVariableDeclarations + \ skipwhite skipempty + +syntax keyword typescriptVariable const + \ nextgroup=typescriptEnum,@typescriptVariableDeclarations + \ skipwhite skipempty + +syntax region typescriptEnum matchgroup=typescriptEnumKeyword start=/enum / end=/\ze{/ + \ nextgroup=typescriptBlock + \ skipwhite + +syntax keyword typescriptKeywordOp + \ contained in instanceof nextgroup=@typescriptValue +syntax keyword typescriptOperator delete new typeof void + \ nextgroup=@typescriptValue + \ skipwhite skipempty + +syntax keyword typescriptForOperator contained in of +syntax keyword typescriptBoolean true false nextgroup=@typescriptSymbols skipwhite skipempty +syntax keyword typescriptNull null undefined nextgroup=@typescriptSymbols skipwhite skipempty +syntax keyword typescriptMessage alert confirm prompt status + \ nextgroup=typescriptDotNotation,typescriptFuncCallArg +syntax keyword typescriptGlobal self top parent + \ nextgroup=@afterIdentifier + +"Statement Keywords +syntax keyword typescriptConditional if else switch + \ nextgroup=typescriptConditionalParen + \ skipwhite skipempty skipnl +syntax keyword typescriptConditionalElse else +syntax keyword typescriptRepeat do while for nextgroup=typescriptLoopParen skipwhite skipempty +syntax keyword typescriptRepeat for nextgroup=typescriptLoopParen,typescriptAsyncFor skipwhite skipempty +syntax keyword typescriptBranch break continue containedin=typescriptBlock +syntax keyword typescriptCase case nextgroup=@typescriptPrimitive skipwhite containedin=typescriptBlock +syntax keyword typescriptDefault default containedin=typescriptBlock nextgroup=@typescriptValue,typescriptClassKeyword,typescriptInterfaceKeyword skipwhite oneline +syntax keyword typescriptStatementKeyword with +syntax keyword typescriptStatementKeyword yield skipwhite nextgroup=@typescriptValue containedin=typescriptBlock +syntax keyword typescriptStatementKeyword return skipwhite contained nextgroup=@typescriptValue containedin=typescriptBlock + +syntax keyword typescriptTry try +syntax keyword typescriptExceptions catch throw finally +syntax keyword typescriptDebugger debugger + +syntax keyword typescriptAsyncFor await nextgroup=typescriptLoopParen skipwhite skipempty contained + +syntax region typescriptLoopParen contained matchgroup=typescriptParens + \ start=/(/ end=/)/ + \ contains=typescriptVariable,typescriptForOperator,typescriptEndColons,@typescriptValue,@typescriptComments + \ nextgroup=typescriptBlock + \ skipwhite skipempty +syntax region typescriptConditionalParen contained matchgroup=typescriptParens + \ start=/(/ end=/)/ + \ contains=@typescriptValue,@typescriptComments + \ nextgroup=typescriptBlock + \ skipwhite skipempty +syntax match typescriptEndColons /[;,]/ contained + +syntax keyword typescriptAmbientDeclaration declare nextgroup=@typescriptAmbients + \ skipwhite skipempty + +syntax cluster typescriptAmbients contains= + \ typescriptVariable, + \ typescriptFuncKeyword, + \ typescriptClassKeyword, + \ typescriptAbstract, + \ typescriptEnumKeyword,typescriptEnum, + \ typescriptModule + +"Syntax coloring for Node.js shebang line +syntax match shellbang "^#!.*node\>" +syntax match shellbang "^#!.*iojs\>" + + +"JavaScript comments +syntax keyword typescriptCommentTodo TODO FIXME XXX TBD +syntax match typescriptMagicComment "@ts-\%(ignore\|expect-error\)\>" +syntax match typescriptLineComment "//.*" + \ contains=@Spell,typescriptCommentTodo,typescriptRef,typescriptMagicComment +syntax region typescriptComment + \ start="/\*" end="\*/" + \ contains=@Spell,typescriptCommentTodo extend +syntax cluster typescriptComments + \ contains=typescriptDocComment,typescriptComment,typescriptLineComment + +syntax match typescriptRef +///\s*<reference\s\+.*\/>$+ + \ contains=typescriptString +syntax match typescriptRef +///\s*<amd-dependency\s\+.*\/>$+ + \ contains=typescriptString +syntax match typescriptRef +///\s*<amd-module\s\+.*\/>$+ + \ contains=typescriptString + +"JSDoc +syntax case ignore + +syntax region typescriptDocComment matchgroup=typescriptComment + \ start="/\*\*" end="\*/" + \ contains=typescriptDocNotation,typescriptCommentTodo,@Spell + \ fold keepend +syntax match typescriptDocNotation contained /@/ nextgroup=typescriptDocTags + +syntax keyword typescriptDocTags contained constant constructor constructs function ignore inner private public readonly static +syntax keyword typescriptDocTags contained const dict expose inheritDoc interface nosideeffects override protected struct internal +syntax keyword typescriptDocTags contained example global +syntax keyword typescriptDocTags contained alpha beta defaultValue eventProperty experimental label +syntax keyword typescriptDocTags contained packageDocumentation privateRemarks remarks sealed typeParam + +" syntax keyword typescriptDocTags contained ngdoc nextgroup=typescriptDocNGDirective +syntax keyword typescriptDocTags contained ngdoc scope priority animations +syntax keyword typescriptDocTags contained ngdoc restrict methodOf propertyOf eventOf eventType nextgroup=typescriptDocParam skipwhite +syntax keyword typescriptDocNGDirective contained overview service object function method property event directive filter inputType error + +syntax keyword typescriptDocTags contained abstract virtual access augments + +syntax keyword typescriptDocTags contained arguments callback lends memberOf name type kind link mixes mixin tutorial nextgroup=typescriptDocParam skipwhite +syntax keyword typescriptDocTags contained variation nextgroup=typescriptDocNumParam skipwhite + +syntax keyword typescriptDocTags contained author class classdesc copyright default defaultvalue nextgroup=typescriptDocDesc skipwhite +syntax keyword typescriptDocTags contained deprecated description external host nextgroup=typescriptDocDesc skipwhite +syntax keyword typescriptDocTags contained file fileOverview overview namespace requires since version nextgroup=typescriptDocDesc skipwhite +syntax keyword typescriptDocTags contained summary todo license preserve nextgroup=typescriptDocDesc skipwhite + +syntax keyword typescriptDocTags contained borrows exports nextgroup=typescriptDocA skipwhite +syntax keyword typescriptDocTags contained param arg argument property prop module nextgroup=typescriptDocNamedParamType,typescriptDocParamName skipwhite +syntax keyword typescriptDocTags contained define enum extends implements this typedef nextgroup=typescriptDocParamType skipwhite +syntax keyword typescriptDocTags contained return returns throws exception nextgroup=typescriptDocParamType,typescriptDocParamName skipwhite +syntax keyword typescriptDocTags contained see nextgroup=typescriptDocRef skipwhite + +syntax keyword typescriptDocTags contained function func method nextgroup=typescriptDocName skipwhite +syntax match typescriptDocName contained /\h\w*/ + +syntax keyword typescriptDocTags contained fires event nextgroup=typescriptDocEventRef skipwhite +syntax match typescriptDocEventRef contained /\h\w*#\(\h\w*\:\)\?\h\w*/ + +syntax match typescriptDocNamedParamType contained /{.\+}/ nextgroup=typescriptDocParamName skipwhite +syntax match typescriptDocParamName contained /\[\?0-9a-zA-Z_\.]\+\]\?/ nextgroup=typescriptDocDesc skipwhite +syntax match typescriptDocParamType contained /{.\+}/ nextgroup=typescriptDocDesc skipwhite +syntax match typescriptDocA contained /\%(#\|\w\|\.\|:\|\/\)\+/ nextgroup=typescriptDocAs skipwhite +syntax match typescriptDocAs contained /\s*as\s*/ nextgroup=typescriptDocB skipwhite +syntax match typescriptDocB contained /\%(#\|\w\|\.\|:\|\/\)\+/ +syntax match typescriptDocParam contained /\%(#\|\w\|\.\|:\|\/\|-\)\+/ +syntax match typescriptDocNumParam contained /\d\+/ +syntax match typescriptDocRef contained /\%(#\|\w\|\.\|:\|\/\)\+/ +syntax region typescriptDocLinkTag contained matchgroup=typescriptDocLinkTag start=/{/ end=/}/ contains=typescriptDocTags + +syntax cluster typescriptDocs contains=typescriptDocParamType,typescriptDocNamedParamType,typescriptDocParam + +if exists("main_syntax") && main_syntax == "typescript" + syntax sync clear + syntax sync ccomment typescriptComment minlines=200 +endif + +syntax case match + +" Types +syntax match typescriptOptionalMark /?/ contained + +syntax cluster typescriptTypeParameterCluster contains= + \ typescriptTypeParameter, + \ typescriptGenericDefault + +syntax region typescriptTypeParameters matchgroup=typescriptTypeBrackets + \ start=/</ end=/>/ + \ contains=@typescriptTypeParameterCluster + \ contained + +syntax match typescriptTypeParameter /\K\k*/ + \ nextgroup=typescriptConstraint + \ contained skipwhite skipnl + +syntax keyword typescriptConstraint extends + \ nextgroup=@typescriptType + \ contained skipwhite skipnl + +syntax match typescriptGenericDefault /=/ + \ nextgroup=@typescriptType + \ contained skipwhite + +">< +" class A extend B<T> {} // ClassBlock +" func<T>() // FuncCallArg +syntax region typescriptTypeArguments matchgroup=typescriptTypeBrackets + \ start=/\></ end=/>/ + \ contains=@typescriptType + \ nextgroup=typescriptFuncCallArg,@typescriptTypeOperator + \ contained skipwhite + + +syntax cluster typescriptType contains= + \ @typescriptPrimaryType, + \ typescriptUnion, + \ @typescriptFunctionType, + \ typescriptConstructorType + +" array type: A[] +" type indexing A['key'] +syntax region typescriptTypeBracket contained + \ start=/\[/ end=/\]/ + \ contains=typescriptString,typescriptNumber + \ nextgroup=@typescriptTypeOperator + \ skipwhite skipempty + +syntax cluster typescriptPrimaryType contains= + \ typescriptParenthesizedType, + \ typescriptPredefinedType, + \ typescriptTypeReference, + \ typescriptObjectType, + \ typescriptTupleType, + \ typescriptTypeQuery, + \ typescriptStringLiteralType, + \ typescriptTemplateLiteralType, + \ typescriptReadonlyArrayKeyword, + \ typescriptAssertType + +syntax region typescriptStringLiteralType contained + \ start=/\z(["']\)/ skip=/\\\\\|\\\z1\|\\\n/ end=/\z1\|$/ + \ nextgroup=typescriptUnion + \ skipwhite skipempty + +syntax region typescriptTemplateLiteralType contained + \ start=/`/ skip=/\\\\\|\\`\|\n/ end=/`\|$/ + \ contains=typescriptTemplateSubstitutionType + \ nextgroup=typescriptTypeOperator + \ skipwhite skipempty + +syntax region typescriptTemplateSubstitutionType matchgroup=typescriptTemplateSB + \ start=/\${/ end=/}/ + \ contains=@typescriptType + \ contained + +syntax region typescriptParenthesizedType matchgroup=typescriptParens + \ start=/(/ end=/)/ + \ contains=@typescriptType + \ nextgroup=@typescriptTypeOperator + \ contained skipwhite skipempty fold + +syntax match typescriptTypeReference /\K\k*\(\.\K\k*\)*/ + \ nextgroup=typescriptTypeArguments,@typescriptTypeOperator,typescriptUserDefinedType + \ skipwhite contained skipempty + +syntax keyword typescriptPredefinedType any number boolean string void never undefined null object unknown + \ nextgroup=@typescriptTypeOperator + \ contained skipwhite skipempty + +syntax match typescriptPredefinedType /unique symbol/ + \ nextgroup=@typescriptTypeOperator + \ contained skipwhite skipempty + +syntax region typescriptObjectType matchgroup=typescriptBraces + \ start=/{/ end=/}/ + \ contains=@typescriptTypeMember,typescriptEndColons,@typescriptComments,typescriptAccessibilityModifier,typescriptReadonlyModifier + \ nextgroup=@typescriptTypeOperator + \ contained skipwhite skipnl fold + +syntax cluster typescriptTypeMember contains= + \ @typescriptCallSignature, + \ typescriptConstructSignature, + \ typescriptIndexSignature, + \ @typescriptMembers + +syntax match typescriptTupleLable /\K\k*?\?:/ + \ contained + +syntax region typescriptTupleType matchgroup=typescriptBraces + \ start=/\[/ end=/\]/ + \ contains=@typescriptType,@typescriptComments,typescriptRestOrSpread,typescriptTupleLable + \ contained skipwhite + +syntax cluster typescriptTypeOperator + \ contains=typescriptUnion,typescriptTypeBracket,typescriptConstraint,typescriptConditionalType + +syntax match typescriptUnion /|\|&/ contained nextgroup=@typescriptPrimaryType skipwhite skipempty + +syntax match typescriptConditionalType /?\|:/ contained nextgroup=@typescriptPrimaryType skipwhite skipempty + +syntax cluster typescriptFunctionType contains=typescriptGenericFunc,typescriptFuncType +syntax region typescriptGenericFunc matchgroup=typescriptTypeBrackets + \ start=/</ end=/>/ + \ contains=typescriptTypeParameter + \ nextgroup=typescriptFuncType + \ containedin=typescriptFunctionType + \ contained skipwhite skipnl + +syntax region typescriptFuncType matchgroup=typescriptParens + \ start=/(/ end=/)\s*=>/me=e-2 + \ contains=@typescriptParameterList + \ nextgroup=typescriptFuncTypeArrow + \ contained skipwhite skipnl oneline + +syntax match typescriptFuncTypeArrow /=>/ + \ nextgroup=@typescriptType + \ containedin=typescriptFuncType + \ contained skipwhite skipnl + + +syntax keyword typescriptConstructorType new + \ nextgroup=@typescriptFunctionType + \ contained skipwhite skipnl + +syntax keyword typescriptUserDefinedType is + \ contained nextgroup=@typescriptType skipwhite skipempty + +syntax keyword typescriptTypeQuery typeof keyof + \ nextgroup=typescriptTypeReference + \ contained skipwhite skipnl + +syntax keyword typescriptAssertType asserts + \ nextgroup=typescriptTypeReference + \ contained skipwhite skipnl + +syntax cluster typescriptCallSignature contains=typescriptGenericCall,typescriptCall +syntax region typescriptGenericCall matchgroup=typescriptTypeBrackets + \ start=/</ end=/>/ + \ contains=typescriptTypeParameter + \ nextgroup=typescriptCall + \ contained skipwhite skipnl +syntax region typescriptCall matchgroup=typescriptParens + \ start=/(/ end=/)/ + \ contains=typescriptDecorator,@typescriptParameterList,@typescriptComments + \ nextgroup=typescriptTypeAnnotation,typescriptBlock + \ contained skipwhite skipnl + +syntax match typescriptTypeAnnotation /:/ + \ nextgroup=@typescriptType + \ contained skipwhite skipnl + +syntax cluster typescriptParameterList contains= + \ typescriptTypeAnnotation, + \ typescriptAccessibilityModifier, + \ typescriptReadonlyModifier, + \ typescriptOptionalMark, + \ typescriptRestOrSpread, + \ typescriptFuncComma, + \ typescriptDefaultParam + +syntax match typescriptFuncComma /,/ contained + +syntax match typescriptDefaultParam /=/ + \ nextgroup=@typescriptValue + \ contained skipwhite + +syntax keyword typescriptConstructSignature new + \ nextgroup=@typescriptCallSignature + \ contained skipwhite + +syntax region typescriptIndexSignature matchgroup=typescriptBraces + \ start=/\[/ end=/\]/ + \ contains=typescriptPredefinedType,typescriptMappedIn,typescriptString + \ nextgroup=typescriptTypeAnnotation + \ contained skipwhite oneline + +syntax keyword typescriptMappedIn in + \ nextgroup=@typescriptType + \ contained skipwhite skipnl skipempty + +syntax keyword typescriptAliasKeyword type + \ nextgroup=typescriptAliasDeclaration + \ skipwhite skipnl skipempty + +syntax region typescriptAliasDeclaration matchgroup=typescriptUnion + \ start=/ / end=/=/ + \ nextgroup=@typescriptType + \ contains=typescriptConstraint,typescriptTypeParameters + \ contained skipwhite skipempty + +syntax keyword typescriptReadonlyArrayKeyword readonly + \ nextgroup=@typescriptPrimaryType + \ skipwhite + + +" extension +if get(g:, 'yats_host_keyword', 1) + syntax keyword typescriptGlobal containedin=typescriptIdentifierName Function Boolean + " use of nextgroup Suggested by Doug Kearns + syntax keyword typescriptGlobal containedin=typescriptIdentifierName Error EvalError nextgroup=typescriptFuncCallArg + syntax keyword typescriptGlobal containedin=typescriptIdentifierName InternalError + syntax keyword typescriptGlobal containedin=typescriptIdentifierName RangeError ReferenceError + syntax keyword typescriptGlobal containedin=typescriptIdentifierName StopIteration + syntax keyword typescriptGlobal containedin=typescriptIdentifierName SyntaxError TypeError + syntax keyword typescriptGlobal containedin=typescriptIdentifierName URIError Date + syntax keyword typescriptGlobal containedin=typescriptIdentifierName Float32Array + syntax keyword typescriptGlobal containedin=typescriptIdentifierName Float64Array + syntax keyword typescriptGlobal containedin=typescriptIdentifierName Int16Array Int32Array + syntax keyword typescriptGlobal containedin=typescriptIdentifierName Int8Array Uint16Array + syntax keyword typescriptGlobal containedin=typescriptIdentifierName Uint32Array Uint8Array + syntax keyword typescriptGlobal containedin=typescriptIdentifierName Uint8ClampedArray + syntax keyword typescriptGlobal containedin=typescriptIdentifierName ParallelArray + syntax keyword typescriptGlobal containedin=typescriptIdentifierName ArrayBuffer DataView + syntax keyword typescriptGlobal containedin=typescriptIdentifierName Iterator Generator + syntax keyword typescriptGlobal containedin=typescriptIdentifierName Reflect Proxy + syntax keyword typescriptGlobal containedin=typescriptIdentifierName arguments + hi def link typescriptGlobal Structure + syntax keyword typescriptGlobalMethod containedin=typescriptIdentifierName eval uneval nextgroup=typescriptFuncCallArg + syntax keyword typescriptGlobalMethod containedin=typescriptIdentifierName isFinite nextgroup=typescriptFuncCallArg + syntax keyword typescriptGlobalMethod containedin=typescriptIdentifierName isNaN parseFloat nextgroup=typescriptFuncCallArg + syntax keyword typescriptGlobalMethod containedin=typescriptIdentifierName parseInt nextgroup=typescriptFuncCallArg + syntax keyword typescriptGlobalMethod containedin=typescriptIdentifierName decodeURI nextgroup=typescriptFuncCallArg + syntax keyword typescriptGlobalMethod containedin=typescriptIdentifierName decodeURIComponent nextgroup=typescriptFuncCallArg + syntax keyword typescriptGlobalMethod containedin=typescriptIdentifierName encodeURI nextgroup=typescriptFuncCallArg + syntax keyword typescriptGlobalMethod containedin=typescriptIdentifierName encodeURIComponent nextgroup=typescriptFuncCallArg + syntax cluster props add=typescriptGlobalMethod + hi def link typescriptGlobalMethod Structure + + syntax keyword typescriptGlobal containedin=typescriptIdentifierName Number nextgroup=typescriptGlobalNumberDot,typescriptFuncCallArg + syntax match typescriptGlobalNumberDot /\./ contained nextgroup=typescriptNumberStaticProp,typescriptNumberStaticMethod,typescriptProp + syntax keyword typescriptNumberStaticProp contained EPSILON MAX_SAFE_INTEGER MAX_VALUE + syntax keyword typescriptNumberStaticProp contained MIN_SAFE_INTEGER MIN_VALUE NEGATIVE_INFINITY + syntax keyword typescriptNumberStaticProp contained NaN POSITIVE_INFINITY + hi def link typescriptNumberStaticProp Keyword + syntax keyword typescriptNumberStaticMethod contained isFinite isInteger isNaN isSafeInteger nextgroup=typescriptFuncCallArg + syntax keyword typescriptNumberStaticMethod contained parseFloat parseInt nextgroup=typescriptFuncCallArg + hi def link typescriptNumberStaticMethod Keyword + syntax keyword typescriptNumberMethod contained toExponential toFixed toLocaleString nextgroup=typescriptFuncCallArg + syntax keyword typescriptNumberMethod contained toPrecision toSource toString valueOf nextgroup=typescriptFuncCallArg + syntax cluster props add=typescriptNumberMethod + hi def link typescriptNumberMethod Keyword + + syntax keyword typescriptGlobal containedin=typescriptIdentifierName String nextgroup=typescriptGlobalStringDot,typescriptFuncCallArg + syntax match typescriptGlobalStringDot /\./ contained nextgroup=typescriptStringStaticMethod,typescriptProp + syntax keyword typescriptStringStaticMethod contained fromCharCode fromCodePoint raw nextgroup=typescriptFuncCallArg + hi def link typescriptStringStaticMethod Keyword + syntax keyword typescriptStringMethod contained anchor charAt charCodeAt codePointAt nextgroup=typescriptFuncCallArg + syntax keyword typescriptStringMethod contained concat endsWith includes indexOf lastIndexOf nextgroup=typescriptFuncCallArg + syntax keyword typescriptStringMethod contained link localeCompare match normalize nextgroup=typescriptFuncCallArg + syntax keyword typescriptStringMethod contained padStart padEnd repeat replace search nextgroup=typescriptFuncCallArg + syntax keyword typescriptStringMethod contained slice split startsWith substr substring nextgroup=typescriptFuncCallArg + syntax keyword typescriptStringMethod contained toLocaleLowerCase toLocaleUpperCase nextgroup=typescriptFuncCallArg + syntax keyword typescriptStringMethod contained toLowerCase toString toUpperCase trim nextgroup=typescriptFuncCallArg + syntax keyword typescriptStringMethod contained valueOf nextgroup=typescriptFuncCallArg + syntax cluster props add=typescriptStringMethod + hi def link typescriptStringMethod Keyword + + syntax keyword typescriptGlobal containedin=typescriptIdentifierName Array nextgroup=typescriptGlobalArrayDot,typescriptFuncCallArg + syntax match typescriptGlobalArrayDot /\./ contained nextgroup=typescriptArrayStaticMethod,typescriptProp + syntax keyword typescriptArrayStaticMethod contained from isArray of nextgroup=typescriptFuncCallArg + hi def link typescriptArrayStaticMethod Keyword + syntax keyword typescriptArrayMethod contained concat copyWithin entries every fill nextgroup=typescriptFuncCallArg + syntax keyword typescriptArrayMethod contained filter find findIndex forEach indexOf nextgroup=typescriptFuncCallArg + syntax keyword typescriptArrayMethod contained includes join keys lastIndexOf map nextgroup=typescriptFuncCallArg + syntax keyword typescriptArrayMethod contained pop push reduce reduceRight reverse nextgroup=typescriptFuncCallArg + syntax keyword typescriptArrayMethod contained shift slice some sort splice toLocaleString nextgroup=typescriptFuncCallArg + syntax keyword typescriptArrayMethod contained toSource toString unshift nextgroup=typescriptFuncCallArg + syntax cluster props add=typescriptArrayMethod + hi def link typescriptArrayMethod Keyword + + syntax keyword typescriptGlobal containedin=typescriptIdentifierName Object nextgroup=typescriptGlobalObjectDot,typescriptFuncCallArg + syntax match typescriptGlobalObjectDot /\./ contained nextgroup=typescriptObjectStaticMethod,typescriptProp + syntax keyword typescriptObjectStaticMethod contained create defineProperties defineProperty nextgroup=typescriptFuncCallArg + syntax keyword typescriptObjectStaticMethod contained entries freeze getOwnPropertyDescriptors nextgroup=typescriptFuncCallArg + syntax keyword typescriptObjectStaticMethod contained getOwnPropertyDescriptor getOwnPropertyNames nextgroup=typescriptFuncCallArg + syntax keyword typescriptObjectStaticMethod contained getOwnPropertySymbols getPrototypeOf nextgroup=typescriptFuncCallArg + syntax keyword typescriptObjectStaticMethod contained is isExtensible isFrozen isSealed nextgroup=typescriptFuncCallArg + syntax keyword typescriptObjectStaticMethod contained keys preventExtensions values nextgroup=typescriptFuncCallArg + hi def link typescriptObjectStaticMethod Keyword + syntax keyword typescriptObjectMethod contained getOwnPropertyDescriptors hasOwnProperty nextgroup=typescriptFuncCallArg + syntax keyword typescriptObjectMethod contained isPrototypeOf propertyIsEnumerable nextgroup=typescriptFuncCallArg + syntax keyword typescriptObjectMethod contained toLocaleString toString valueOf seal nextgroup=typescriptFuncCallArg + syntax keyword typescriptObjectMethod contained setPrototypeOf nextgroup=typescriptFuncCallArg + syntax cluster props add=typescriptObjectMethod + hi def link typescriptObjectMethod Keyword + + syntax keyword typescriptGlobal containedin=typescriptIdentifierName Symbol nextgroup=typescriptGlobalSymbolDot,typescriptFuncCallArg + syntax match typescriptGlobalSymbolDot /\./ contained nextgroup=typescriptSymbolStaticProp,typescriptSymbolStaticMethod,typescriptProp + syntax keyword typescriptSymbolStaticProp contained length iterator match replace + syntax keyword typescriptSymbolStaticProp contained search split hasInstance isConcatSpreadable + syntax keyword typescriptSymbolStaticProp contained unscopables species toPrimitive + syntax keyword typescriptSymbolStaticProp contained toStringTag + hi def link typescriptSymbolStaticProp Keyword + syntax keyword typescriptSymbolStaticMethod contained for keyFor nextgroup=typescriptFuncCallArg + hi def link typescriptSymbolStaticMethod Keyword + + syntax keyword typescriptGlobal containedin=typescriptIdentifierName Function + syntax keyword typescriptFunctionMethod contained apply bind call nextgroup=typescriptFuncCallArg + syntax cluster props add=typescriptFunctionMethod + hi def link typescriptFunctionMethod Keyword + + syntax keyword typescriptGlobal containedin=typescriptIdentifierName Math nextgroup=typescriptGlobalMathDot,typescriptFuncCallArg + syntax match typescriptGlobalMathDot /\./ contained nextgroup=typescriptMathStaticProp,typescriptMathStaticMethod,typescriptProp + syntax keyword typescriptMathStaticProp contained E LN10 LN2 LOG10E LOG2E PI SQRT1_2 + syntax keyword typescriptMathStaticProp contained SQRT2 + hi def link typescriptMathStaticProp Keyword + syntax keyword typescriptMathStaticMethod contained abs acos acosh asin asinh atan nextgroup=typescriptFuncCallArg + syntax keyword typescriptMathStaticMethod contained atan2 atanh cbrt ceil clz32 cos nextgroup=typescriptFuncCallArg + syntax keyword typescriptMathStaticMethod contained cosh exp expm1 floor fround hypot nextgroup=typescriptFuncCallArg + syntax keyword typescriptMathStaticMethod contained imul log log10 log1p log2 max nextgroup=typescriptFuncCallArg + syntax keyword typescriptMathStaticMethod contained min pow random round sign sin nextgroup=typescriptFuncCallArg + syntax keyword typescriptMathStaticMethod contained sinh sqrt tan tanh trunc nextgroup=typescriptFuncCallArg + hi def link typescriptMathStaticMethod Keyword + + syntax keyword typescriptGlobal containedin=typescriptIdentifierName Date nextgroup=typescriptGlobalDateDot,typescriptFuncCallArg + syntax match typescriptGlobalDateDot /\./ contained nextgroup=typescriptDateStaticMethod,typescriptProp + syntax keyword typescriptDateStaticMethod contained UTC now parse nextgroup=typescriptFuncCallArg + hi def link typescriptDateStaticMethod Keyword + syntax keyword typescriptDateMethod contained getDate getDay getFullYear getHours nextgroup=typescriptFuncCallArg + syntax keyword typescriptDateMethod contained getMilliseconds getMinutes getMonth nextgroup=typescriptFuncCallArg + syntax keyword typescriptDateMethod contained getSeconds getTime getTimezoneOffset nextgroup=typescriptFuncCallArg + syntax keyword typescriptDateMethod contained getUTCDate getUTCDay getUTCFullYear nextgroup=typescriptFuncCallArg + syntax keyword typescriptDateMethod contained getUTCHours getUTCMilliseconds getUTCMinutes nextgroup=typescriptFuncCallArg + syntax keyword typescriptDateMethod contained getUTCMonth getUTCSeconds setDate setFullYear nextgroup=typescriptFuncCallArg + syntax keyword typescriptDateMethod contained setHours setMilliseconds setMinutes nextgroup=typescriptFuncCallArg + syntax keyword typescriptDateMethod contained setMonth setSeconds setTime setUTCDate nextgroup=typescriptFuncCallArg + syntax keyword typescriptDateMethod contained setUTCFullYear setUTCHours setUTCMilliseconds nextgroup=typescriptFuncCallArg + syntax keyword typescriptDateMethod contained setUTCMinutes setUTCMonth setUTCSeconds nextgroup=typescriptFuncCallArg + syntax keyword typescriptDateMethod contained toDateString toISOString toJSON toLocaleDateString nextgroup=typescriptFuncCallArg + syntax keyword typescriptDateMethod contained toLocaleFormat toLocaleString toLocaleTimeString nextgroup=typescriptFuncCallArg + syntax keyword typescriptDateMethod contained toSource toString toTimeString toUTCString nextgroup=typescriptFuncCallArg + syntax keyword typescriptDateMethod contained valueOf nextgroup=typescriptFuncCallArg + syntax cluster props add=typescriptDateMethod + hi def link typescriptDateMethod Keyword + + syntax keyword typescriptGlobal containedin=typescriptIdentifierName JSON nextgroup=typescriptGlobalJSONDot,typescriptFuncCallArg + syntax match typescriptGlobalJSONDot /\./ contained nextgroup=typescriptJSONStaticMethod,typescriptProp + syntax keyword typescriptJSONStaticMethod contained parse stringify nextgroup=typescriptFuncCallArg + hi def link typescriptJSONStaticMethod Keyword + + syntax keyword typescriptGlobal containedin=typescriptIdentifierName RegExp nextgroup=typescriptGlobalRegExpDot,typescriptFuncCallArg + syntax match typescriptGlobalRegExpDot /\./ contained nextgroup=typescriptRegExpStaticProp,typescriptProp + syntax keyword typescriptRegExpStaticProp contained lastIndex + hi def link typescriptRegExpStaticProp Keyword + syntax keyword typescriptRegExpProp contained global ignoreCase multiline source sticky + syntax cluster props add=typescriptRegExpProp + hi def link typescriptRegExpProp Keyword + syntax keyword typescriptRegExpMethod contained exec test nextgroup=typescriptFuncCallArg + syntax cluster props add=typescriptRegExpMethod + hi def link typescriptRegExpMethod Keyword + + syntax keyword typescriptGlobal containedin=typescriptIdentifierName Map WeakMap + syntax keyword typescriptES6MapProp contained size + syntax cluster props add=typescriptES6MapProp + hi def link typescriptES6MapProp Keyword + syntax keyword typescriptES6MapMethod contained clear delete entries forEach get has nextgroup=typescriptFuncCallArg + syntax keyword typescriptES6MapMethod contained keys set values nextgroup=typescriptFuncCallArg + syntax cluster props add=typescriptES6MapMethod + hi def link typescriptES6MapMethod Keyword + + syntax keyword typescriptGlobal containedin=typescriptIdentifierName Set WeakSet + syntax keyword typescriptES6SetProp contained size + syntax cluster props add=typescriptES6SetProp + hi def link typescriptES6SetProp Keyword + syntax keyword typescriptES6SetMethod contained add clear delete entries forEach has nextgroup=typescriptFuncCallArg + syntax keyword typescriptES6SetMethod contained values nextgroup=typescriptFuncCallArg + syntax cluster props add=typescriptES6SetMethod + hi def link typescriptES6SetMethod Keyword + + syntax keyword typescriptGlobal containedin=typescriptIdentifierName Proxy + syntax keyword typescriptProxyAPI contained getOwnPropertyDescriptor getOwnPropertyNames + syntax keyword typescriptProxyAPI contained defineProperty deleteProperty freeze seal + syntax keyword typescriptProxyAPI contained preventExtensions has hasOwn get set enumerate + syntax keyword typescriptProxyAPI contained iterate ownKeys apply construct + hi def link typescriptProxyAPI Keyword + + syntax keyword typescriptGlobal containedin=typescriptIdentifierName Promise nextgroup=typescriptGlobalPromiseDot,typescriptFuncCallArg + syntax match typescriptGlobalPromiseDot /\./ contained nextgroup=typescriptPromiseStaticMethod,typescriptProp + syntax keyword typescriptPromiseStaticMethod contained resolve reject all race nextgroup=typescriptFuncCallArg + hi def link typescriptPromiseStaticMethod Keyword + syntax keyword typescriptPromiseMethod contained then catch finally nextgroup=typescriptFuncCallArg + syntax cluster props add=typescriptPromiseMethod + hi def link typescriptPromiseMethod Keyword + + syntax keyword typescriptGlobal containedin=typescriptIdentifierName Reflect + syntax keyword typescriptReflectMethod contained apply construct defineProperty deleteProperty nextgroup=typescriptFuncCallArg + syntax keyword typescriptReflectMethod contained enumerate get getOwnPropertyDescriptor nextgroup=typescriptFuncCallArg + syntax keyword typescriptReflectMethod contained getPrototypeOf has isExtensible ownKeys nextgroup=typescriptFuncCallArg + syntax keyword typescriptReflectMethod contained preventExtensions set setPrototypeOf nextgroup=typescriptFuncCallArg + syntax cluster props add=typescriptReflectMethod + hi def link typescriptReflectMethod Keyword + + syntax keyword typescriptGlobal containedin=typescriptIdentifierName Intl + syntax keyword typescriptIntlMethod contained Collator DateTimeFormat NumberFormat nextgroup=typescriptFuncCallArg + syntax keyword typescriptIntlMethod contained PluralRules nextgroup=typescriptFuncCallArg + syntax cluster props add=typescriptIntlMethod + hi def link typescriptIntlMethod Keyword + + syntax keyword typescriptNodeGlobal containedin=typescriptIdentifierName global process + syntax keyword typescriptNodeGlobal containedin=typescriptIdentifierName console Buffer + syntax keyword typescriptNodeGlobal containedin=typescriptIdentifierName module exports + syntax keyword typescriptNodeGlobal containedin=typescriptIdentifierName setTimeout + syntax keyword typescriptNodeGlobal containedin=typescriptIdentifierName clearTimeout + syntax keyword typescriptNodeGlobal containedin=typescriptIdentifierName setInterval + syntax keyword typescriptNodeGlobal containedin=typescriptIdentifierName clearInterval + hi def link typescriptNodeGlobal Structure + + syntax keyword typescriptTestGlobal containedin=typescriptIdentifierName describe + syntax keyword typescriptTestGlobal containedin=typescriptIdentifierName it test before + syntax keyword typescriptTestGlobal containedin=typescriptIdentifierName after beforeEach + syntax keyword typescriptTestGlobal containedin=typescriptIdentifierName afterEach + syntax keyword typescriptTestGlobal containedin=typescriptIdentifierName beforeAll + syntax keyword typescriptTestGlobal containedin=typescriptIdentifierName afterAll + syntax keyword typescriptTestGlobal containedin=typescriptIdentifierName expect assert + + syntax keyword typescriptBOM containedin=typescriptIdentifierName AbortController + syntax keyword typescriptBOM containedin=typescriptIdentifierName AbstractWorker AnalyserNode + syntax keyword typescriptBOM containedin=typescriptIdentifierName App Apps ArrayBuffer + syntax keyword typescriptBOM containedin=typescriptIdentifierName ArrayBufferView + syntax keyword typescriptBOM containedin=typescriptIdentifierName Attr AudioBuffer + syntax keyword typescriptBOM containedin=typescriptIdentifierName AudioBufferSourceNode + syntax keyword typescriptBOM containedin=typescriptIdentifierName AudioContext AudioDestinationNode + syntax keyword typescriptBOM containedin=typescriptIdentifierName AudioListener AudioNode + syntax keyword typescriptBOM containedin=typescriptIdentifierName AudioParam BatteryManager + syntax keyword typescriptBOM containedin=typescriptIdentifierName BiquadFilterNode + syntax keyword typescriptBOM containedin=typescriptIdentifierName BlobEvent BluetoothAdapter + syntax keyword typescriptBOM containedin=typescriptIdentifierName BluetoothDevice + syntax keyword typescriptBOM containedin=typescriptIdentifierName BluetoothManager + syntax keyword typescriptBOM containedin=typescriptIdentifierName CameraCapabilities + syntax keyword typescriptBOM containedin=typescriptIdentifierName CameraControl CameraManager + syntax keyword typescriptBOM containedin=typescriptIdentifierName CanvasGradient CanvasImageSource + syntax keyword typescriptBOM containedin=typescriptIdentifierName CanvasPattern CanvasRenderingContext2D + syntax keyword typescriptBOM containedin=typescriptIdentifierName CaretPosition CDATASection + syntax keyword typescriptBOM containedin=typescriptIdentifierName ChannelMergerNode + syntax keyword typescriptBOM containedin=typescriptIdentifierName ChannelSplitterNode + syntax keyword typescriptBOM containedin=typescriptIdentifierName CharacterData ChildNode + syntax keyword typescriptBOM containedin=typescriptIdentifierName ChromeWorker Comment + syntax keyword typescriptBOM containedin=typescriptIdentifierName Connection Console + syntax keyword typescriptBOM containedin=typescriptIdentifierName ContactManager Contacts + syntax keyword typescriptBOM containedin=typescriptIdentifierName ConvolverNode Coordinates + syntax keyword typescriptBOM containedin=typescriptIdentifierName CSS CSSConditionRule + syntax keyword typescriptBOM containedin=typescriptIdentifierName CSSGroupingRule + syntax keyword typescriptBOM containedin=typescriptIdentifierName CSSKeyframeRule + syntax keyword typescriptBOM containedin=typescriptIdentifierName CSSKeyframesRule + syntax keyword typescriptBOM containedin=typescriptIdentifierName CSSMediaRule CSSNamespaceRule + syntax keyword typescriptBOM containedin=typescriptIdentifierName CSSPageRule CSSRule + syntax keyword typescriptBOM containedin=typescriptIdentifierName CSSRuleList CSSStyleDeclaration + syntax keyword typescriptBOM containedin=typescriptIdentifierName CSSStyleRule CSSStyleSheet + syntax keyword typescriptBOM containedin=typescriptIdentifierName CSSSupportsRule + syntax keyword typescriptBOM containedin=typescriptIdentifierName DataTransfer DataView + syntax keyword typescriptBOM containedin=typescriptIdentifierName DedicatedWorkerGlobalScope + syntax keyword typescriptBOM containedin=typescriptIdentifierName DelayNode DeviceAcceleration + syntax keyword typescriptBOM containedin=typescriptIdentifierName DeviceRotationRate + syntax keyword typescriptBOM containedin=typescriptIdentifierName DeviceStorage DirectoryEntry + syntax keyword typescriptBOM containedin=typescriptIdentifierName DirectoryEntrySync + syntax keyword typescriptBOM containedin=typescriptIdentifierName DirectoryReader + syntax keyword typescriptBOM containedin=typescriptIdentifierName DirectoryReaderSync + syntax keyword typescriptBOM containedin=typescriptIdentifierName Document DocumentFragment + syntax keyword typescriptBOM containedin=typescriptIdentifierName DocumentTouch DocumentType + syntax keyword typescriptBOM containedin=typescriptIdentifierName DOMCursor DOMError + syntax keyword typescriptBOM containedin=typescriptIdentifierName DOMException DOMHighResTimeStamp + syntax keyword typescriptBOM containedin=typescriptIdentifierName DOMImplementation + syntax keyword typescriptBOM containedin=typescriptIdentifierName DOMImplementationRegistry + syntax keyword typescriptBOM containedin=typescriptIdentifierName DOMParser DOMRequest + syntax keyword typescriptBOM containedin=typescriptIdentifierName DOMString DOMStringList + syntax keyword typescriptBOM containedin=typescriptIdentifierName DOMStringMap DOMTimeStamp + syntax keyword typescriptBOM containedin=typescriptIdentifierName DOMTokenList DynamicsCompressorNode + syntax keyword typescriptBOM containedin=typescriptIdentifierName Element Entry EntrySync + syntax keyword typescriptBOM containedin=typescriptIdentifierName Extensions FileException + syntax keyword typescriptBOM containedin=typescriptIdentifierName Float32Array Float64Array + syntax keyword typescriptBOM containedin=typescriptIdentifierName FMRadio FormData + syntax keyword typescriptBOM containedin=typescriptIdentifierName GainNode Gamepad + syntax keyword typescriptBOM containedin=typescriptIdentifierName GamepadButton Geolocation + syntax keyword typescriptBOM containedin=typescriptIdentifierName History HTMLAnchorElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName HTMLAreaElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName HTMLAudioElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName HTMLBaseElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName HTMLBodyElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName HTMLBRElement HTMLButtonElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName HTMLCanvasElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName HTMLCollection HTMLDataElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName HTMLDataListElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName HTMLDivElement HTMLDListElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName HTMLDocument HTMLElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName HTMLEmbedElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName HTMLFieldSetElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName HTMLFormControlsCollection + syntax keyword typescriptBOM containedin=typescriptIdentifierName HTMLFormElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName HTMLHeadElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName HTMLHeadingElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName HTMLHRElement HTMLHtmlElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName HTMLIFrameElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName HTMLImageElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName HTMLInputElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName HTMLKeygenElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName HTMLLabelElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName HTMLLegendElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName HTMLLIElement HTMLLinkElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName HTMLMapElement HTMLMediaElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName HTMLMetaElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName HTMLMeterElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName HTMLModElement HTMLObjectElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName HTMLOListElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName HTMLOptGroupElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName HTMLOptionElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName HTMLOptionsCollection + syntax keyword typescriptBOM containedin=typescriptIdentifierName HTMLOutputElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName HTMLParagraphElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName HTMLParamElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName HTMLPreElement HTMLProgressElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName HTMLQuoteElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName HTMLScriptElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName HTMLSelectElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName HTMLSourceElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName HTMLSpanElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName HTMLStyleElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName HTMLTableCaptionElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName HTMLTableCellElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName HTMLTableColElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName HTMLTableDataCellElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName HTMLTableElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName HTMLTableHeaderCellElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName HTMLTableRowElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName HTMLTableSectionElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName HTMLTextAreaElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName HTMLTimeElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName HTMLTitleElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName HTMLTrackElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName HTMLUListElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName HTMLUnknownElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName HTMLVideoElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName IDBCursor IDBCursorSync + syntax keyword typescriptBOM containedin=typescriptIdentifierName IDBCursorWithValue + syntax keyword typescriptBOM containedin=typescriptIdentifierName IDBDatabase IDBDatabaseSync + syntax keyword typescriptBOM containedin=typescriptIdentifierName IDBEnvironment IDBEnvironmentSync + syntax keyword typescriptBOM containedin=typescriptIdentifierName IDBFactory IDBFactorySync + syntax keyword typescriptBOM containedin=typescriptIdentifierName IDBIndex IDBIndexSync + syntax keyword typescriptBOM containedin=typescriptIdentifierName IDBKeyRange IDBObjectStore + syntax keyword typescriptBOM containedin=typescriptIdentifierName IDBObjectStoreSync + syntax keyword typescriptBOM containedin=typescriptIdentifierName IDBOpenDBRequest + syntax keyword typescriptBOM containedin=typescriptIdentifierName IDBRequest IDBTransaction + syntax keyword typescriptBOM containedin=typescriptIdentifierName IDBTransactionSync + syntax keyword typescriptBOM containedin=typescriptIdentifierName IDBVersionChangeEvent + syntax keyword typescriptBOM containedin=typescriptIdentifierName ImageData IndexedDB + syntax keyword typescriptBOM containedin=typescriptIdentifierName Int16Array Int32Array + syntax keyword typescriptBOM containedin=typescriptIdentifierName Int8Array L10n LinkStyle + syntax keyword typescriptBOM containedin=typescriptIdentifierName LocalFileSystem + syntax keyword typescriptBOM containedin=typescriptIdentifierName LocalFileSystemSync + syntax keyword typescriptBOM containedin=typescriptIdentifierName Location LockedFile + syntax keyword typescriptBOM containedin=typescriptIdentifierName MediaQueryList MediaQueryListListener + syntax keyword typescriptBOM containedin=typescriptIdentifierName MediaRecorder MediaSource + syntax keyword typescriptBOM containedin=typescriptIdentifierName MediaStream MediaStreamTrack + syntax keyword typescriptBOM containedin=typescriptIdentifierName MutationObserver + syntax keyword typescriptBOM containedin=typescriptIdentifierName Navigator NavigatorGeolocation + syntax keyword typescriptBOM containedin=typescriptIdentifierName NavigatorID NavigatorLanguage + syntax keyword typescriptBOM containedin=typescriptIdentifierName NavigatorOnLine + syntax keyword typescriptBOM containedin=typescriptIdentifierName NavigatorPlugins + syntax keyword typescriptBOM containedin=typescriptIdentifierName Node NodeFilter + syntax keyword typescriptBOM containedin=typescriptIdentifierName NodeIterator NodeList + syntax keyword typescriptBOM containedin=typescriptIdentifierName Notification OfflineAudioContext + syntax keyword typescriptBOM containedin=typescriptIdentifierName OscillatorNode PannerNode + syntax keyword typescriptBOM containedin=typescriptIdentifierName ParentNode Performance + syntax keyword typescriptBOM containedin=typescriptIdentifierName PerformanceNavigation + syntax keyword typescriptBOM containedin=typescriptIdentifierName PerformanceTiming + syntax keyword typescriptBOM containedin=typescriptIdentifierName Permissions PermissionSettings + syntax keyword typescriptBOM containedin=typescriptIdentifierName Plugin PluginArray + syntax keyword typescriptBOM containedin=typescriptIdentifierName Position PositionError + syntax keyword typescriptBOM containedin=typescriptIdentifierName PositionOptions + syntax keyword typescriptBOM containedin=typescriptIdentifierName PowerManager ProcessingInstruction + syntax keyword typescriptBOM containedin=typescriptIdentifierName PromiseResolver + syntax keyword typescriptBOM containedin=typescriptIdentifierName PushManager Range + syntax keyword typescriptBOM containedin=typescriptIdentifierName RTCConfiguration + syntax keyword typescriptBOM containedin=typescriptIdentifierName RTCPeerConnection + syntax keyword typescriptBOM containedin=typescriptIdentifierName RTCPeerConnectionErrorCallback + syntax keyword typescriptBOM containedin=typescriptIdentifierName RTCSessionDescription + syntax keyword typescriptBOM containedin=typescriptIdentifierName RTCSessionDescriptionCallback + syntax keyword typescriptBOM containedin=typescriptIdentifierName ScriptProcessorNode + syntax keyword typescriptBOM containedin=typescriptIdentifierName Selection SettingsLock + syntax keyword typescriptBOM containedin=typescriptIdentifierName SettingsManager + syntax keyword typescriptBOM containedin=typescriptIdentifierName SharedWorker StyleSheet + syntax keyword typescriptBOM containedin=typescriptIdentifierName StyleSheetList SVGAElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName SVGAngle SVGAnimateColorElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName SVGAnimatedAngle + syntax keyword typescriptBOM containedin=typescriptIdentifierName SVGAnimatedBoolean + syntax keyword typescriptBOM containedin=typescriptIdentifierName SVGAnimatedEnumeration + syntax keyword typescriptBOM containedin=typescriptIdentifierName SVGAnimatedInteger + syntax keyword typescriptBOM containedin=typescriptIdentifierName SVGAnimatedLength + syntax keyword typescriptBOM containedin=typescriptIdentifierName SVGAnimatedLengthList + syntax keyword typescriptBOM containedin=typescriptIdentifierName SVGAnimatedNumber + syntax keyword typescriptBOM containedin=typescriptIdentifierName SVGAnimatedNumberList + syntax keyword typescriptBOM containedin=typescriptIdentifierName SVGAnimatedPoints + syntax keyword typescriptBOM containedin=typescriptIdentifierName SVGAnimatedPreserveAspectRatio + syntax keyword typescriptBOM containedin=typescriptIdentifierName SVGAnimatedRect + syntax keyword typescriptBOM containedin=typescriptIdentifierName SVGAnimatedString + syntax keyword typescriptBOM containedin=typescriptIdentifierName SVGAnimatedTransformList + syntax keyword typescriptBOM containedin=typescriptIdentifierName SVGAnimateElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName SVGAnimateMotionElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName SVGAnimateTransformElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName SVGAnimationElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName SVGCircleElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName SVGClipPathElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName SVGCursorElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName SVGDefsElement SVGDescElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName SVGElement SVGEllipseElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName SVGFilterElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName SVGFontElement SVGFontFaceElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName SVGFontFaceFormatElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName SVGFontFaceNameElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName SVGFontFaceSrcElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName SVGFontFaceUriElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName SVGForeignObjectElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName SVGGElement SVGGlyphElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName SVGGradientElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName SVGHKernElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName SVGImageElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName SVGLength SVGLengthList + syntax keyword typescriptBOM containedin=typescriptIdentifierName SVGLinearGradientElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName SVGLineElement SVGMaskElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName SVGMatrix SVGMissingGlyphElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName SVGMPathElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName SVGNumber SVGNumberList + syntax keyword typescriptBOM containedin=typescriptIdentifierName SVGPathElement SVGPatternElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName SVGPoint SVGPolygonElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName SVGPolylineElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName SVGPreserveAspectRatio + syntax keyword typescriptBOM containedin=typescriptIdentifierName SVGRadialGradientElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName SVGRect SVGRectElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName SVGScriptElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName SVGSetElement SVGStopElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName SVGStringList SVGStylable + syntax keyword typescriptBOM containedin=typescriptIdentifierName SVGStyleElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName SVGSVGElement SVGSwitchElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName SVGSymbolElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName SVGTests SVGTextElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName SVGTextPositioningElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName SVGTitleElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName SVGTransform SVGTransformable + syntax keyword typescriptBOM containedin=typescriptIdentifierName SVGTransformList + syntax keyword typescriptBOM containedin=typescriptIdentifierName SVGTRefElement SVGTSpanElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName SVGUseElement SVGViewElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName SVGVKernElement + syntax keyword typescriptBOM containedin=typescriptIdentifierName TCPServerSocket + syntax keyword typescriptBOM containedin=typescriptIdentifierName TCPSocket Telephony + syntax keyword typescriptBOM containedin=typescriptIdentifierName TelephonyCall Text + syntax keyword typescriptBOM containedin=typescriptIdentifierName TextDecoder TextEncoder + syntax keyword typescriptBOM containedin=typescriptIdentifierName TextMetrics TimeRanges + syntax keyword typescriptBOM containedin=typescriptIdentifierName Touch TouchList + syntax keyword typescriptBOM containedin=typescriptIdentifierName Transferable TreeWalker + syntax keyword typescriptBOM containedin=typescriptIdentifierName Uint16Array Uint32Array + syntax keyword typescriptBOM containedin=typescriptIdentifierName Uint8Array Uint8ClampedArray + syntax keyword typescriptBOM containedin=typescriptIdentifierName URLSearchParams + syntax keyword typescriptBOM containedin=typescriptIdentifierName URLUtilsReadOnly + syntax keyword typescriptBOM containedin=typescriptIdentifierName UserProximityEvent + syntax keyword typescriptBOM containedin=typescriptIdentifierName ValidityState VideoPlaybackQuality + syntax keyword typescriptBOM containedin=typescriptIdentifierName WaveShaperNode WebBluetooth + syntax keyword typescriptBOM containedin=typescriptIdentifierName WebGLRenderingContext + syntax keyword typescriptBOM containedin=typescriptIdentifierName WebSMS WebSocket + syntax keyword typescriptBOM containedin=typescriptIdentifierName WebVTT WifiManager + syntax keyword typescriptBOM containedin=typescriptIdentifierName Window Worker WorkerConsole + syntax keyword typescriptBOM containedin=typescriptIdentifierName WorkerLocation WorkerNavigator + syntax keyword typescriptBOM containedin=typescriptIdentifierName XDomainRequest XMLDocument + syntax keyword typescriptBOM containedin=typescriptIdentifierName XMLHttpRequestEventTarget + hi def link typescriptBOM Structure + + syntax keyword typescriptBOMWindowProp containedin=typescriptIdentifierName applicationCache + syntax keyword typescriptBOMWindowProp containedin=typescriptIdentifierName closed + syntax keyword typescriptBOMWindowProp containedin=typescriptIdentifierName Components + syntax keyword typescriptBOMWindowProp containedin=typescriptIdentifierName controllers + syntax keyword typescriptBOMWindowProp containedin=typescriptIdentifierName dialogArguments + syntax keyword typescriptBOMWindowProp containedin=typescriptIdentifierName document + syntax keyword typescriptBOMWindowProp containedin=typescriptIdentifierName frameElement + syntax keyword typescriptBOMWindowProp containedin=typescriptIdentifierName frames + syntax keyword typescriptBOMWindowProp containedin=typescriptIdentifierName fullScreen + syntax keyword typescriptBOMWindowProp containedin=typescriptIdentifierName history + syntax keyword typescriptBOMWindowProp containedin=typescriptIdentifierName innerHeight + syntax keyword typescriptBOMWindowProp containedin=typescriptIdentifierName innerWidth + syntax keyword typescriptBOMWindowProp containedin=typescriptIdentifierName length + syntax keyword typescriptBOMWindowProp containedin=typescriptIdentifierName location + syntax keyword typescriptBOMWindowProp containedin=typescriptIdentifierName locationbar + syntax keyword typescriptBOMWindowProp containedin=typescriptIdentifierName menubar + syntax keyword typescriptBOMWindowProp containedin=typescriptIdentifierName messageManager + syntax keyword typescriptBOMWindowProp containedin=typescriptIdentifierName name navigator + syntax keyword typescriptBOMWindowProp containedin=typescriptIdentifierName opener + syntax keyword typescriptBOMWindowProp containedin=typescriptIdentifierName outerHeight + syntax keyword typescriptBOMWindowProp containedin=typescriptIdentifierName outerWidth + syntax keyword typescriptBOMWindowProp containedin=typescriptIdentifierName pageXOffset + syntax keyword typescriptBOMWindowProp containedin=typescriptIdentifierName pageYOffset + syntax keyword typescriptBOMWindowProp containedin=typescriptIdentifierName parent + syntax keyword typescriptBOMWindowProp containedin=typescriptIdentifierName performance + syntax keyword typescriptBOMWindowProp containedin=typescriptIdentifierName personalbar + syntax keyword typescriptBOMWindowProp containedin=typescriptIdentifierName returnValue + syntax keyword typescriptBOMWindowProp containedin=typescriptIdentifierName screen + syntax keyword typescriptBOMWindowProp containedin=typescriptIdentifierName screenX + syntax keyword typescriptBOMWindowProp containedin=typescriptIdentifierName screenY + syntax keyword typescriptBOMWindowProp containedin=typescriptIdentifierName scrollbars + syntax keyword typescriptBOMWindowProp containedin=typescriptIdentifierName scrollMaxX + syntax keyword typescriptBOMWindowProp containedin=typescriptIdentifierName scrollMaxY + syntax keyword typescriptBOMWindowProp containedin=typescriptIdentifierName scrollX + syntax keyword typescriptBOMWindowProp containedin=typescriptIdentifierName scrollY + syntax keyword typescriptBOMWindowProp containedin=typescriptIdentifierName self sidebar + syntax keyword typescriptBOMWindowProp containedin=typescriptIdentifierName status + syntax keyword typescriptBOMWindowProp containedin=typescriptIdentifierName statusbar + syntax keyword typescriptBOMWindowProp containedin=typescriptIdentifierName toolbar + syntax keyword typescriptBOMWindowProp containedin=typescriptIdentifierName top visualViewport + syntax keyword typescriptBOMWindowProp containedin=typescriptIdentifierName window + syntax cluster props add=typescriptBOMWindowProp + hi def link typescriptBOMWindowProp Structure + syntax keyword typescriptBOMWindowMethod containedin=typescriptIdentifierName alert nextgroup=typescriptFuncCallArg + syntax keyword typescriptBOMWindowMethod containedin=typescriptIdentifierName atob nextgroup=typescriptFuncCallArg + syntax keyword typescriptBOMWindowMethod containedin=typescriptIdentifierName blur nextgroup=typescriptFuncCallArg + syntax keyword typescriptBOMWindowMethod containedin=typescriptIdentifierName btoa nextgroup=typescriptFuncCallArg + syntax keyword typescriptBOMWindowMethod containedin=typescriptIdentifierName clearImmediate nextgroup=typescriptFuncCallArg + syntax keyword typescriptBOMWindowMethod containedin=typescriptIdentifierName clearInterval nextgroup=typescriptFuncCallArg + syntax keyword typescriptBOMWindowMethod containedin=typescriptIdentifierName clearTimeout nextgroup=typescriptFuncCallArg + syntax keyword typescriptBOMWindowMethod containedin=typescriptIdentifierName close nextgroup=typescriptFuncCallArg + syntax keyword typescriptBOMWindowMethod containedin=typescriptIdentifierName confirm nextgroup=typescriptFuncCallArg + syntax keyword typescriptBOMWindowMethod containedin=typescriptIdentifierName dispatchEvent nextgroup=typescriptFuncCallArg + syntax keyword typescriptBOMWindowMethod containedin=typescriptIdentifierName find nextgroup=typescriptFuncCallArg + syntax keyword typescriptBOMWindowMethod containedin=typescriptIdentifierName focus nextgroup=typescriptFuncCallArg + syntax keyword typescriptBOMWindowMethod containedin=typescriptIdentifierName getAttention nextgroup=typescriptFuncCallArg + syntax keyword typescriptBOMWindowMethod containedin=typescriptIdentifierName getAttentionWithCycleCount nextgroup=typescriptFuncCallArg + syntax keyword typescriptBOMWindowMethod containedin=typescriptIdentifierName getComputedStyle nextgroup=typescriptFuncCallArg + syntax keyword typescriptBOMWindowMethod containedin=typescriptIdentifierName getDefaulComputedStyle nextgroup=typescriptFuncCallArg + syntax keyword typescriptBOMWindowMethod containedin=typescriptIdentifierName getSelection nextgroup=typescriptFuncCallArg + syntax keyword typescriptBOMWindowMethod containedin=typescriptIdentifierName matchMedia nextgroup=typescriptFuncCallArg + syntax keyword typescriptBOMWindowMethod containedin=typescriptIdentifierName maximize nextgroup=typescriptFuncCallArg + syntax keyword typescriptBOMWindowMethod containedin=typescriptIdentifierName moveBy nextgroup=typescriptFuncCallArg + syntax keyword typescriptBOMWindowMethod containedin=typescriptIdentifierName moveTo nextgroup=typescriptFuncCallArg + syntax keyword typescriptBOMWindowMethod containedin=typescriptIdentifierName open nextgroup=typescriptFuncCallArg + syntax keyword typescriptBOMWindowMethod containedin=typescriptIdentifierName openDialog nextgroup=typescriptFuncCallArg + syntax keyword typescriptBOMWindowMethod containedin=typescriptIdentifierName postMessage nextgroup=typescriptFuncCallArg + syntax keyword typescriptBOMWindowMethod containedin=typescriptIdentifierName print nextgroup=typescriptFuncCallArg + syntax keyword typescriptBOMWindowMethod containedin=typescriptIdentifierName prompt nextgroup=typescriptFuncCallArg + syntax keyword typescriptBOMWindowMethod containedin=typescriptIdentifierName removeEventListener nextgroup=typescriptFuncCallArg + syntax keyword typescriptBOMWindowMethod containedin=typescriptIdentifierName resizeBy nextgroup=typescriptFuncCallArg + syntax keyword typescriptBOMWindowMethod containedin=typescriptIdentifierName resizeTo nextgroup=typescriptFuncCallArg + syntax keyword typescriptBOMWindowMethod containedin=typescriptIdentifierName restore nextgroup=typescriptFuncCallArg + syntax keyword typescriptBOMWindowMethod containedin=typescriptIdentifierName scroll nextgroup=typescriptFuncCallArg + syntax keyword typescriptBOMWindowMethod containedin=typescriptIdentifierName scrollBy nextgroup=typescriptFuncCallArg + syntax keyword typescriptBOMWindowMethod containedin=typescriptIdentifierName scrollByLines nextgroup=typescriptFuncCallArg + syntax keyword typescriptBOMWindowMethod containedin=typescriptIdentifierName scrollByPages nextgroup=typescriptFuncCallArg + syntax keyword typescriptBOMWindowMethod containedin=typescriptIdentifierName scrollTo nextgroup=typescriptFuncCallArg + syntax keyword typescriptBOMWindowMethod containedin=typescriptIdentifierName setCursor nextgroup=typescriptFuncCallArg + syntax keyword typescriptBOMWindowMethod containedin=typescriptIdentifierName setImmediate nextgroup=typescriptFuncCallArg + syntax keyword typescriptBOMWindowMethod containedin=typescriptIdentifierName setInterval nextgroup=typescriptFuncCallArg + syntax keyword typescriptBOMWindowMethod containedin=typescriptIdentifierName setResizable nextgroup=typescriptFuncCallArg + syntax keyword typescriptBOMWindowMethod containedin=typescriptIdentifierName setTimeout nextgroup=typescriptFuncCallArg + syntax keyword typescriptBOMWindowMethod containedin=typescriptIdentifierName showModalDialog nextgroup=typescriptFuncCallArg + syntax keyword typescriptBOMWindowMethod containedin=typescriptIdentifierName sizeToContent nextgroup=typescriptFuncCallArg + syntax keyword typescriptBOMWindowMethod containedin=typescriptIdentifierName stop nextgroup=typescriptFuncCallArg + syntax keyword typescriptBOMWindowMethod containedin=typescriptIdentifierName updateCommands nextgroup=typescriptFuncCallArg + syntax cluster props add=typescriptBOMWindowMethod + hi def link typescriptBOMWindowMethod Structure + syntax keyword typescriptBOMWindowEvent contained onabort onbeforeunload onblur onchange + syntax keyword typescriptBOMWindowEvent contained onclick onclose oncontextmenu ondevicelight + syntax keyword typescriptBOMWindowEvent contained ondevicemotion ondeviceorientation + syntax keyword typescriptBOMWindowEvent contained ondeviceproximity ondragdrop onerror + syntax keyword typescriptBOMWindowEvent contained onfocus onhashchange onkeydown onkeypress + syntax keyword typescriptBOMWindowEvent contained onkeyup onload onmousedown onmousemove + syntax keyword typescriptBOMWindowEvent contained onmouseout onmouseover onmouseup + syntax keyword typescriptBOMWindowEvent contained onmozbeforepaint onpaint onpopstate + syntax keyword typescriptBOMWindowEvent contained onreset onresize onscroll onselect + syntax keyword typescriptBOMWindowEvent contained onsubmit onunload onuserproximity + syntax keyword typescriptBOMWindowEvent contained onpageshow onpagehide + hi def link typescriptBOMWindowEvent Keyword + syntax keyword typescriptBOMWindowCons containedin=typescriptIdentifierName DOMParser + syntax keyword typescriptBOMWindowCons containedin=typescriptIdentifierName QueryInterface + syntax keyword typescriptBOMWindowCons containedin=typescriptIdentifierName XMLSerializer + hi def link typescriptBOMWindowCons Structure + + syntax keyword typescriptBOMNavigatorProp contained battery buildID connection cookieEnabled + syntax keyword typescriptBOMNavigatorProp contained doNotTrack maxTouchPoints oscpu + syntax keyword typescriptBOMNavigatorProp contained productSub push serviceWorker + syntax keyword typescriptBOMNavigatorProp contained vendor vendorSub + syntax cluster props add=typescriptBOMNavigatorProp + hi def link typescriptBOMNavigatorProp Keyword + syntax keyword typescriptBOMNavigatorMethod contained addIdleObserver geolocation nextgroup=typescriptFuncCallArg + syntax keyword typescriptBOMNavigatorMethod contained getDeviceStorage getDeviceStorages nextgroup=typescriptFuncCallArg + syntax keyword typescriptBOMNavigatorMethod contained getGamepads getUserMedia registerContentHandler nextgroup=typescriptFuncCallArg + syntax keyword typescriptBOMNavigatorMethod contained removeIdleObserver requestWakeLock nextgroup=typescriptFuncCallArg + syntax keyword typescriptBOMNavigatorMethod contained share vibrate watch registerProtocolHandler nextgroup=typescriptFuncCallArg + syntax keyword typescriptBOMNavigatorMethod contained sendBeacon nextgroup=typescriptFuncCallArg + syntax cluster props add=typescriptBOMNavigatorMethod + hi def link typescriptBOMNavigatorMethod Keyword + syntax keyword typescriptServiceWorkerMethod contained register nextgroup=typescriptFuncCallArg + syntax cluster props add=typescriptServiceWorkerMethod + hi def link typescriptServiceWorkerMethod Keyword + + syntax keyword typescriptBOMLocationProp contained href protocol host hostname port + syntax keyword typescriptBOMLocationProp contained pathname search hash username password + syntax keyword typescriptBOMLocationProp contained origin + syntax cluster props add=typescriptBOMLocationProp + hi def link typescriptBOMLocationProp Keyword + syntax keyword typescriptBOMLocationMethod contained assign reload replace toString nextgroup=typescriptFuncCallArg + syntax cluster props add=typescriptBOMLocationMethod + hi def link typescriptBOMLocationMethod Keyword + + syntax keyword typescriptBOMHistoryProp contained length current next previous state + syntax keyword typescriptBOMHistoryProp contained scrollRestoration + syntax cluster props add=typescriptBOMHistoryProp + hi def link typescriptBOMHistoryProp Keyword + syntax keyword typescriptBOMHistoryMethod contained back forward go pushState replaceState nextgroup=typescriptFuncCallArg + syntax cluster props add=typescriptBOMHistoryMethod + hi def link typescriptBOMHistoryMethod Keyword + + syntax keyword typescriptGlobal containedin=typescriptIdentifierName console + syntax keyword typescriptConsoleMethod contained count dir error group groupCollapsed nextgroup=typescriptFuncCallArg + syntax keyword typescriptConsoleMethod contained groupEnd info log time timeEnd trace nextgroup=typescriptFuncCallArg + syntax keyword typescriptConsoleMethod contained warn nextgroup=typescriptFuncCallArg + syntax cluster props add=typescriptConsoleMethod + hi def link typescriptConsoleMethod Keyword + + syntax keyword typescriptXHRGlobal containedin=typescriptIdentifierName XMLHttpRequest + hi def link typescriptXHRGlobal Structure + syntax keyword typescriptXHRProp contained onreadystatechange readyState response + syntax keyword typescriptXHRProp contained responseText responseType responseXML status + syntax keyword typescriptXHRProp contained statusText timeout ontimeout upload withCredentials + syntax cluster props add=typescriptXHRProp + hi def link typescriptXHRProp Keyword + syntax keyword typescriptXHRMethod contained abort getAllResponseHeaders getResponseHeader nextgroup=typescriptFuncCallArg + syntax keyword typescriptXHRMethod contained open overrideMimeType send setRequestHeader nextgroup=typescriptFuncCallArg + syntax cluster props add=typescriptXHRMethod + hi def link typescriptXHRMethod Keyword + + syntax keyword typescriptGlobal containedin=typescriptIdentifierName Blob BlobBuilder + syntax keyword typescriptGlobal containedin=typescriptIdentifierName File FileReader + syntax keyword typescriptGlobal containedin=typescriptIdentifierName FileReaderSync + syntax keyword typescriptGlobal containedin=typescriptIdentifierName URL nextgroup=typescriptGlobalURLDot,typescriptFuncCallArg + syntax match typescriptGlobalURLDot /\./ contained nextgroup=typescriptURLStaticMethod,typescriptProp + syntax keyword typescriptGlobal containedin=typescriptIdentifierName URLUtils + syntax keyword typescriptFileMethod contained readAsArrayBuffer readAsBinaryString nextgroup=typescriptFuncCallArg + syntax keyword typescriptFileMethod contained readAsDataURL readAsText nextgroup=typescriptFuncCallArg + syntax cluster props add=typescriptFileMethod + hi def link typescriptFileMethod Keyword + syntax keyword typescriptFileReaderProp contained error readyState result + syntax cluster props add=typescriptFileReaderProp + hi def link typescriptFileReaderProp Keyword + syntax keyword typescriptFileReaderMethod contained abort readAsArrayBuffer readAsBinaryString nextgroup=typescriptFuncCallArg + syntax keyword typescriptFileReaderMethod contained readAsDataURL readAsText nextgroup=typescriptFuncCallArg + syntax cluster props add=typescriptFileReaderMethod + hi def link typescriptFileReaderMethod Keyword + syntax keyword typescriptFileListMethod contained item nextgroup=typescriptFuncCallArg + syntax cluster props add=typescriptFileListMethod + hi def link typescriptFileListMethod Keyword + syntax keyword typescriptBlobMethod contained append getBlob getFile nextgroup=typescriptFuncCallArg + syntax cluster props add=typescriptBlobMethod + hi def link typescriptBlobMethod Keyword + syntax keyword typescriptURLUtilsProp contained hash host hostname href origin password + syntax keyword typescriptURLUtilsProp contained pathname port protocol search searchParams + syntax keyword typescriptURLUtilsProp contained username + syntax cluster props add=typescriptURLUtilsProp + hi def link typescriptURLUtilsProp Keyword + syntax keyword typescriptURLStaticMethod contained createObjectURL revokeObjectURL nextgroup=typescriptFuncCallArg + hi def link typescriptURLStaticMethod Keyword + + syntax keyword typescriptCryptoGlobal containedin=typescriptIdentifierName crypto + hi def link typescriptCryptoGlobal Structure + syntax keyword typescriptSubtleCryptoMethod contained encrypt decrypt sign verify nextgroup=typescriptFuncCallArg + syntax keyword typescriptSubtleCryptoMethod contained digest nextgroup=typescriptFuncCallArg + syntax cluster props add=typescriptSubtleCryptoMethod + hi def link typescriptSubtleCryptoMethod Keyword + syntax keyword typescriptCryptoProp contained subtle + syntax cluster props add=typescriptCryptoProp + hi def link typescriptCryptoProp Keyword + syntax keyword typescriptCryptoMethod contained getRandomValues nextgroup=typescriptFuncCallArg + syntax cluster props add=typescriptCryptoMethod + hi def link typescriptCryptoMethod Keyword + + syntax keyword typescriptGlobal containedin=typescriptIdentifierName Headers Request + syntax keyword typescriptGlobal containedin=typescriptIdentifierName Response + syntax keyword typescriptGlobalMethod containedin=typescriptIdentifierName fetch nextgroup=typescriptFuncCallArg + syntax cluster props add=typescriptGlobalMethod + hi def link typescriptGlobalMethod Structure + syntax keyword typescriptHeadersMethod contained append delete get getAll has set nextgroup=typescriptFuncCallArg + syntax cluster props add=typescriptHeadersMethod + hi def link typescriptHeadersMethod Keyword + syntax keyword typescriptRequestProp contained method url headers context referrer + syntax keyword typescriptRequestProp contained mode credentials cache + syntax cluster props add=typescriptRequestProp + hi def link typescriptRequestProp Keyword + syntax keyword typescriptRequestMethod contained clone nextgroup=typescriptFuncCallArg + syntax cluster props add=typescriptRequestMethod + hi def link typescriptRequestMethod Keyword + syntax keyword typescriptResponseProp contained type url status statusText headers + syntax keyword typescriptResponseProp contained redirected + syntax cluster props add=typescriptResponseProp + hi def link typescriptResponseProp Keyword + syntax keyword typescriptResponseMethod contained clone nextgroup=typescriptFuncCallArg + syntax cluster props add=typescriptResponseMethod + hi def link typescriptResponseMethod Keyword + + syntax keyword typescriptServiceWorkerProp contained controller ready + syntax cluster props add=typescriptServiceWorkerProp + hi def link typescriptServiceWorkerProp Keyword + syntax keyword typescriptServiceWorkerMethod contained register getRegistration nextgroup=typescriptFuncCallArg + syntax cluster props add=typescriptServiceWorkerMethod + hi def link typescriptServiceWorkerMethod Keyword + syntax keyword typescriptGlobal containedin=typescriptIdentifierName Cache + syntax keyword typescriptCacheMethod contained match matchAll add addAll put delete nextgroup=typescriptFuncCallArg + syntax keyword typescriptCacheMethod contained keys nextgroup=typescriptFuncCallArg + syntax cluster props add=typescriptCacheMethod + hi def link typescriptCacheMethod Keyword + + syntax keyword typescriptEncodingGlobal containedin=typescriptIdentifierName TextEncoder + syntax keyword typescriptEncodingGlobal containedin=typescriptIdentifierName TextDecoder + hi def link typescriptEncodingGlobal Structure + syntax keyword typescriptEncodingProp contained encoding fatal ignoreBOM + syntax cluster props add=typescriptEncodingProp + hi def link typescriptEncodingProp Keyword + syntax keyword typescriptEncodingMethod contained encode decode nextgroup=typescriptFuncCallArg + syntax cluster props add=typescriptEncodingMethod + hi def link typescriptEncodingMethod Keyword + + syntax keyword typescriptGlobal containedin=typescriptIdentifierName Geolocation + syntax keyword typescriptGeolocationMethod contained getCurrentPosition watchPosition nextgroup=typescriptFuncCallArg + syntax keyword typescriptGeolocationMethod contained clearWatch nextgroup=typescriptFuncCallArg + syntax cluster props add=typescriptGeolocationMethod + hi def link typescriptGeolocationMethod Keyword + + syntax keyword typescriptGlobal containedin=typescriptIdentifierName NetworkInformation + syntax keyword typescriptBOMNetworkProp contained downlink downlinkMax effectiveType + syntax keyword typescriptBOMNetworkProp contained rtt type + syntax cluster props add=typescriptBOMNetworkProp + hi def link typescriptBOMNetworkProp Keyword + + syntax keyword typescriptGlobal containedin=typescriptIdentifierName PaymentRequest + syntax keyword typescriptPaymentMethod contained show abort canMakePayment nextgroup=typescriptFuncCallArg + syntax cluster props add=typescriptPaymentMethod + hi def link typescriptPaymentMethod Keyword + syntax keyword typescriptPaymentProp contained shippingAddress shippingOption result + syntax cluster props add=typescriptPaymentProp + hi def link typescriptPaymentProp Keyword + syntax keyword typescriptPaymentEvent contained onshippingaddresschange onshippingoptionchange + hi def link typescriptPaymentEvent Keyword + syntax keyword typescriptPaymentResponseMethod contained complete nextgroup=typescriptFuncCallArg + syntax cluster props add=typescriptPaymentResponseMethod + hi def link typescriptPaymentResponseMethod Keyword + syntax keyword typescriptPaymentResponseProp contained details methodName payerEmail + syntax keyword typescriptPaymentResponseProp contained payerPhone shippingAddress + syntax keyword typescriptPaymentResponseProp contained shippingOption + syntax cluster props add=typescriptPaymentResponseProp + hi def link typescriptPaymentResponseProp Keyword + syntax keyword typescriptPaymentAddressProp contained addressLine careOf city country + syntax keyword typescriptPaymentAddressProp contained country dependentLocality languageCode + syntax keyword typescriptPaymentAddressProp contained organization phone postalCode + syntax keyword typescriptPaymentAddressProp contained recipient region sortingCode + syntax cluster props add=typescriptPaymentAddressProp + hi def link typescriptPaymentAddressProp Keyword + syntax keyword typescriptPaymentShippingOptionProp contained id label amount selected + syntax cluster props add=typescriptPaymentShippingOptionProp + hi def link typescriptPaymentShippingOptionProp Keyword + + syntax keyword typescriptDOMNodeProp contained attributes baseURI baseURIObject childNodes + syntax keyword typescriptDOMNodeProp contained firstChild lastChild localName namespaceURI + syntax keyword typescriptDOMNodeProp contained nextSibling nodeName nodePrincipal + syntax keyword typescriptDOMNodeProp contained nodeType nodeValue ownerDocument parentElement + syntax keyword typescriptDOMNodeProp contained parentNode prefix previousSibling textContent + syntax cluster props add=typescriptDOMNodeProp + hi def link typescriptDOMNodeProp Keyword + syntax keyword typescriptDOMNodeMethod contained appendChild cloneNode compareDocumentPosition nextgroup=typescriptFuncCallArg + syntax keyword typescriptDOMNodeMethod contained getUserData hasAttributes hasChildNodes nextgroup=typescriptFuncCallArg + syntax keyword typescriptDOMNodeMethod contained insertBefore isDefaultNamespace isEqualNode nextgroup=typescriptFuncCallArg + syntax keyword typescriptDOMNodeMethod contained isSameNode isSupported lookupNamespaceURI nextgroup=typescriptFuncCallArg + syntax keyword typescriptDOMNodeMethod contained lookupPrefix normalize removeChild nextgroup=typescriptFuncCallArg + syntax keyword typescriptDOMNodeMethod contained replaceChild setUserData nextgroup=typescriptFuncCallArg + syntax match typescriptDOMNodeMethod contained /contains/ + syntax cluster props add=typescriptDOMNodeMethod + hi def link typescriptDOMNodeMethod Keyword + syntax keyword typescriptDOMNodeType contained ELEMENT_NODE ATTRIBUTE_NODE TEXT_NODE + syntax keyword typescriptDOMNodeType contained CDATA_SECTION_NODEN_NODE ENTITY_REFERENCE_NODE + syntax keyword typescriptDOMNodeType contained ENTITY_NODE PROCESSING_INSTRUCTION_NODEN_NODE + syntax keyword typescriptDOMNodeType contained COMMENT_NODE DOCUMENT_NODE DOCUMENT_TYPE_NODE + syntax keyword typescriptDOMNodeType contained DOCUMENT_FRAGMENT_NODE NOTATION_NODE + hi def link typescriptDOMNodeType Keyword + + syntax keyword typescriptDOMElemAttrs contained accessKey clientHeight clientLeft + syntax keyword typescriptDOMElemAttrs contained clientTop clientWidth id innerHTML + syntax keyword typescriptDOMElemAttrs contained length onafterscriptexecute onbeforescriptexecute + syntax keyword typescriptDOMElemAttrs contained oncopy oncut onpaste onwheel scrollHeight + syntax keyword typescriptDOMElemAttrs contained scrollLeft scrollTop scrollWidth tagName + syntax keyword typescriptDOMElemAttrs contained classList className name outerHTML + syntax keyword typescriptDOMElemAttrs contained style + hi def link typescriptDOMElemAttrs Keyword + syntax keyword typescriptDOMElemFuncs contained getAttributeNS getAttributeNode getAttributeNodeNS + syntax keyword typescriptDOMElemFuncs contained getBoundingClientRect getClientRects + syntax keyword typescriptDOMElemFuncs contained getElementsByClassName getElementsByTagName + syntax keyword typescriptDOMElemFuncs contained getElementsByTagNameNS hasAttribute + syntax keyword typescriptDOMElemFuncs contained hasAttributeNS insertAdjacentHTML + syntax keyword typescriptDOMElemFuncs contained matches querySelector querySelectorAll + syntax keyword typescriptDOMElemFuncs contained removeAttribute removeAttributeNS + syntax keyword typescriptDOMElemFuncs contained removeAttributeNode requestFullscreen + syntax keyword typescriptDOMElemFuncs contained requestPointerLock scrollIntoView + syntax keyword typescriptDOMElemFuncs contained setAttribute setAttributeNS setAttributeNode + syntax keyword typescriptDOMElemFuncs contained setAttributeNodeNS setCapture supports + syntax keyword typescriptDOMElemFuncs contained getAttribute + hi def link typescriptDOMElemFuncs Keyword + + syntax keyword typescriptDOMDocProp contained activeElement body cookie defaultView + syntax keyword typescriptDOMDocProp contained designMode dir domain embeds forms head + syntax keyword typescriptDOMDocProp contained images lastModified links location plugins + syntax keyword typescriptDOMDocProp contained postMessage readyState referrer registerElement + syntax keyword typescriptDOMDocProp contained scripts styleSheets title vlinkColor + syntax keyword typescriptDOMDocProp contained xmlEncoding characterSet compatMode + syntax keyword typescriptDOMDocProp contained contentType currentScript doctype documentElement + syntax keyword typescriptDOMDocProp contained documentURI documentURIObject firstChild + syntax keyword typescriptDOMDocProp contained implementation lastStyleSheetSet namespaceURI + syntax keyword typescriptDOMDocProp contained nodePrincipal ononline pointerLockElement + syntax keyword typescriptDOMDocProp contained popupNode preferredStyleSheetSet selectedStyleSheetSet + syntax keyword typescriptDOMDocProp contained styleSheetSets textContent tooltipNode + syntax cluster props add=typescriptDOMDocProp + hi def link typescriptDOMDocProp Keyword + syntax keyword typescriptDOMDocMethod contained caretPositionFromPoint close createNodeIterator nextgroup=typescriptFuncCallArg + syntax keyword typescriptDOMDocMethod contained createRange createTreeWalker elementFromPoint nextgroup=typescriptFuncCallArg + syntax keyword typescriptDOMDocMethod contained getElementsByName adoptNode createAttribute nextgroup=typescriptFuncCallArg + syntax keyword typescriptDOMDocMethod contained createCDATASection createComment createDocumentFragment nextgroup=typescriptFuncCallArg + syntax keyword typescriptDOMDocMethod contained createElement createElementNS createEvent nextgroup=typescriptFuncCallArg + syntax keyword typescriptDOMDocMethod contained createExpression createNSResolver nextgroup=typescriptFuncCallArg + syntax keyword typescriptDOMDocMethod contained createProcessingInstruction createTextNode nextgroup=typescriptFuncCallArg + syntax keyword typescriptDOMDocMethod contained enableStyleSheetsForSet evaluate execCommand nextgroup=typescriptFuncCallArg + syntax keyword typescriptDOMDocMethod contained exitPointerLock getBoxObjectFor getElementById nextgroup=typescriptFuncCallArg + syntax keyword typescriptDOMDocMethod contained getElementsByClassName getElementsByTagName nextgroup=typescriptFuncCallArg + syntax keyword typescriptDOMDocMethod contained getElementsByTagNameNS getSelection nextgroup=typescriptFuncCallArg + syntax keyword typescriptDOMDocMethod contained hasFocus importNode loadOverlay open nextgroup=typescriptFuncCallArg + syntax keyword typescriptDOMDocMethod contained queryCommandSupported querySelector nextgroup=typescriptFuncCallArg + syntax keyword typescriptDOMDocMethod contained querySelectorAll write writeln nextgroup=typescriptFuncCallArg + syntax cluster props add=typescriptDOMDocMethod + hi def link typescriptDOMDocMethod Keyword + + syntax keyword typescriptDOMEventTargetMethod contained addEventListener removeEventListener nextgroup=typescriptEventFuncCallArg + syntax keyword typescriptDOMEventTargetMethod contained dispatchEvent waitUntil nextgroup=typescriptEventFuncCallArg + syntax cluster props add=typescriptDOMEventTargetMethod + hi def link typescriptDOMEventTargetMethod Keyword + syntax keyword typescriptDOMEventCons containedin=typescriptIdentifierName AnimationEvent + syntax keyword typescriptDOMEventCons containedin=typescriptIdentifierName AudioProcessingEvent + syntax keyword typescriptDOMEventCons containedin=typescriptIdentifierName BeforeInputEvent + syntax keyword typescriptDOMEventCons containedin=typescriptIdentifierName BeforeUnloadEvent + syntax keyword typescriptDOMEventCons containedin=typescriptIdentifierName BlobEvent + syntax keyword typescriptDOMEventCons containedin=typescriptIdentifierName ClipboardEvent + syntax keyword typescriptDOMEventCons containedin=typescriptIdentifierName CloseEvent + syntax keyword typescriptDOMEventCons containedin=typescriptIdentifierName CompositionEvent + syntax keyword typescriptDOMEventCons containedin=typescriptIdentifierName CSSFontFaceLoadEvent + syntax keyword typescriptDOMEventCons containedin=typescriptIdentifierName CustomEvent + syntax keyword typescriptDOMEventCons containedin=typescriptIdentifierName DeviceLightEvent + syntax keyword typescriptDOMEventCons containedin=typescriptIdentifierName DeviceMotionEvent + syntax keyword typescriptDOMEventCons containedin=typescriptIdentifierName DeviceOrientationEvent + syntax keyword typescriptDOMEventCons containedin=typescriptIdentifierName DeviceProximityEvent + syntax keyword typescriptDOMEventCons containedin=typescriptIdentifierName DOMTransactionEvent + syntax keyword typescriptDOMEventCons containedin=typescriptIdentifierName DragEvent + syntax keyword typescriptDOMEventCons containedin=typescriptIdentifierName EditingBeforeInputEvent + syntax keyword typescriptDOMEventCons containedin=typescriptIdentifierName ErrorEvent + syntax keyword typescriptDOMEventCons containedin=typescriptIdentifierName FocusEvent + syntax keyword typescriptDOMEventCons containedin=typescriptIdentifierName GamepadEvent + syntax keyword typescriptDOMEventCons containedin=typescriptIdentifierName HashChangeEvent + syntax keyword typescriptDOMEventCons containedin=typescriptIdentifierName IDBVersionChangeEvent + syntax keyword typescriptDOMEventCons containedin=typescriptIdentifierName KeyboardEvent + syntax keyword typescriptDOMEventCons containedin=typescriptIdentifierName MediaStreamEvent + syntax keyword typescriptDOMEventCons containedin=typescriptIdentifierName MessageEvent + syntax keyword typescriptDOMEventCons containedin=typescriptIdentifierName MouseEvent + syntax keyword typescriptDOMEventCons containedin=typescriptIdentifierName MutationEvent + syntax keyword typescriptDOMEventCons containedin=typescriptIdentifierName OfflineAudioCompletionEvent + syntax keyword typescriptDOMEventCons containedin=typescriptIdentifierName PageTransitionEvent + syntax keyword typescriptDOMEventCons containedin=typescriptIdentifierName PointerEvent + syntax keyword typescriptDOMEventCons containedin=typescriptIdentifierName PopStateEvent + syntax keyword typescriptDOMEventCons containedin=typescriptIdentifierName ProgressEvent + syntax keyword typescriptDOMEventCons containedin=typescriptIdentifierName RelatedEvent + syntax keyword typescriptDOMEventCons containedin=typescriptIdentifierName RTCPeerConnectionIceEvent + syntax keyword typescriptDOMEventCons containedin=typescriptIdentifierName SensorEvent + syntax keyword typescriptDOMEventCons containedin=typescriptIdentifierName StorageEvent + syntax keyword typescriptDOMEventCons containedin=typescriptIdentifierName SVGEvent + syntax keyword typescriptDOMEventCons containedin=typescriptIdentifierName SVGZoomEvent + syntax keyword typescriptDOMEventCons containedin=typescriptIdentifierName TimeEvent + syntax keyword typescriptDOMEventCons containedin=typescriptIdentifierName TouchEvent + syntax keyword typescriptDOMEventCons containedin=typescriptIdentifierName TrackEvent + syntax keyword typescriptDOMEventCons containedin=typescriptIdentifierName TransitionEvent + syntax keyword typescriptDOMEventCons containedin=typescriptIdentifierName UIEvent + syntax keyword typescriptDOMEventCons containedin=typescriptIdentifierName UserProximityEvent + syntax keyword typescriptDOMEventCons containedin=typescriptIdentifierName WheelEvent + hi def link typescriptDOMEventCons Structure + syntax keyword typescriptDOMEventProp contained bubbles cancelable currentTarget defaultPrevented + syntax keyword typescriptDOMEventProp contained eventPhase target timeStamp type isTrusted + syntax keyword typescriptDOMEventProp contained isReload + syntax cluster props add=typescriptDOMEventProp + hi def link typescriptDOMEventProp Keyword + syntax keyword typescriptDOMEventMethod contained initEvent preventDefault stopImmediatePropagation nextgroup=typescriptEventFuncCallArg + syntax keyword typescriptDOMEventMethod contained stopPropagation respondWith default nextgroup=typescriptEventFuncCallArg + syntax cluster props add=typescriptDOMEventMethod + hi def link typescriptDOMEventMethod Keyword + + syntax keyword typescriptDOMStorage contained sessionStorage localStorage + hi def link typescriptDOMStorage Keyword + syntax keyword typescriptDOMStorageProp contained length + syntax cluster props add=typescriptDOMStorageProp + hi def link typescriptDOMStorageProp Keyword + syntax keyword typescriptDOMStorageMethod contained getItem key setItem removeItem nextgroup=typescriptFuncCallArg + syntax keyword typescriptDOMStorageMethod contained clear nextgroup=typescriptFuncCallArg + syntax cluster props add=typescriptDOMStorageMethod + hi def link typescriptDOMStorageMethod Keyword + + syntax keyword typescriptDOMFormProp contained acceptCharset action elements encoding + syntax keyword typescriptDOMFormProp contained enctype length method name target + syntax cluster props add=typescriptDOMFormProp + hi def link typescriptDOMFormProp Keyword + syntax keyword typescriptDOMFormMethod contained reportValidity reset submit nextgroup=typescriptFuncCallArg + syntax cluster props add=typescriptDOMFormMethod + hi def link typescriptDOMFormMethod Keyword + + syntax keyword typescriptDOMStyle contained alignContent alignItems alignSelf animation + syntax keyword typescriptDOMStyle contained animationDelay animationDirection animationDuration + syntax keyword typescriptDOMStyle contained animationFillMode animationIterationCount + syntax keyword typescriptDOMStyle contained animationName animationPlayState animationTimingFunction + syntax keyword typescriptDOMStyle contained appearance backfaceVisibility background + syntax keyword typescriptDOMStyle contained backgroundAttachment backgroundBlendMode + syntax keyword typescriptDOMStyle contained backgroundClip backgroundColor backgroundImage + syntax keyword typescriptDOMStyle contained backgroundOrigin backgroundPosition backgroundRepeat + syntax keyword typescriptDOMStyle contained backgroundSize border borderBottom borderBottomColor + syntax keyword typescriptDOMStyle contained borderBottomLeftRadius borderBottomRightRadius + syntax keyword typescriptDOMStyle contained borderBottomStyle borderBottomWidth borderCollapse + syntax keyword typescriptDOMStyle contained borderColor borderImage borderImageOutset + syntax keyword typescriptDOMStyle contained borderImageRepeat borderImageSlice borderImageSource + syntax keyword typescriptDOMStyle contained borderImageWidth borderLeft borderLeftColor + syntax keyword typescriptDOMStyle contained borderLeftStyle borderLeftWidth borderRadius + syntax keyword typescriptDOMStyle contained borderRight borderRightColor borderRightStyle + syntax keyword typescriptDOMStyle contained borderRightWidth borderSpacing borderStyle + syntax keyword typescriptDOMStyle contained borderTop borderTopColor borderTopLeftRadius + syntax keyword typescriptDOMStyle contained borderTopRightRadius borderTopStyle borderTopWidth + syntax keyword typescriptDOMStyle contained borderWidth bottom boxDecorationBreak + syntax keyword typescriptDOMStyle contained boxShadow boxSizing breakAfter breakBefore + syntax keyword typescriptDOMStyle contained breakInside captionSide caretColor caretShape + syntax keyword typescriptDOMStyle contained caret clear clip clipPath color columns + syntax keyword typescriptDOMStyle contained columnCount columnFill columnGap columnRule + syntax keyword typescriptDOMStyle contained columnRuleColor columnRuleStyle columnRuleWidth + syntax keyword typescriptDOMStyle contained columnSpan columnWidth content counterIncrement + syntax keyword typescriptDOMStyle contained counterReset cursor direction display + syntax keyword typescriptDOMStyle contained emptyCells flex flexBasis flexDirection + syntax keyword typescriptDOMStyle contained flexFlow flexGrow flexShrink flexWrap + syntax keyword typescriptDOMStyle contained float font fontFamily fontFeatureSettings + syntax keyword typescriptDOMStyle contained fontKerning fontLanguageOverride fontSize + syntax keyword typescriptDOMStyle contained fontSizeAdjust fontStretch fontStyle fontSynthesis + syntax keyword typescriptDOMStyle contained fontVariant fontVariantAlternates fontVariantCaps + syntax keyword typescriptDOMStyle contained fontVariantEastAsian fontVariantLigatures + syntax keyword typescriptDOMStyle contained fontVariantNumeric fontVariantPosition + syntax keyword typescriptDOMStyle contained fontWeight grad grid gridArea gridAutoColumns + syntax keyword typescriptDOMStyle contained gridAutoFlow gridAutoPosition gridAutoRows + syntax keyword typescriptDOMStyle contained gridColumn gridColumnStart gridColumnEnd + syntax keyword typescriptDOMStyle contained gridRow gridRowStart gridRowEnd gridTemplate + syntax keyword typescriptDOMStyle contained gridTemplateAreas gridTemplateRows gridTemplateColumns + syntax keyword typescriptDOMStyle contained height hyphens imageRendering imageResolution + syntax keyword typescriptDOMStyle contained imageOrientation imeMode inherit justifyContent + syntax keyword typescriptDOMStyle contained left letterSpacing lineBreak lineHeight + syntax keyword typescriptDOMStyle contained listStyle listStyleImage listStylePosition + syntax keyword typescriptDOMStyle contained listStyleType margin marginBottom marginLeft + syntax keyword typescriptDOMStyle contained marginRight marginTop marks mask maskType + syntax keyword typescriptDOMStyle contained maxHeight maxWidth minHeight minWidth + syntax keyword typescriptDOMStyle contained mixBlendMode objectFit objectPosition + syntax keyword typescriptDOMStyle contained opacity order orphans outline outlineColor + syntax keyword typescriptDOMStyle contained outlineOffset outlineStyle outlineWidth + syntax keyword typescriptDOMStyle contained overflow overflowWrap overflowX overflowY + syntax keyword typescriptDOMStyle contained overflowClipBox padding paddingBottom + syntax keyword typescriptDOMStyle contained paddingLeft paddingRight paddingTop pageBreakAfter + syntax keyword typescriptDOMStyle contained pageBreakBefore pageBreakInside perspective + syntax keyword typescriptDOMStyle contained perspectiveOrigin pointerEvents position + syntax keyword typescriptDOMStyle contained quotes resize right shapeImageThreshold + syntax keyword typescriptDOMStyle contained shapeMargin shapeOutside tableLayout tabSize + syntax keyword typescriptDOMStyle contained textAlign textAlignLast textCombineHorizontal + syntax keyword typescriptDOMStyle contained textDecoration textDecorationColor textDecorationLine + syntax keyword typescriptDOMStyle contained textDecorationStyle textIndent textOrientation + syntax keyword typescriptDOMStyle contained textOverflow textRendering textShadow + syntax keyword typescriptDOMStyle contained textTransform textUnderlinePosition top + syntax keyword typescriptDOMStyle contained touchAction transform transformOrigin + syntax keyword typescriptDOMStyle contained transformStyle transition transitionDelay + syntax keyword typescriptDOMStyle contained transitionDuration transitionProperty + syntax keyword typescriptDOMStyle contained transitionTimingFunction unicodeBidi unicodeRange + syntax keyword typescriptDOMStyle contained userSelect userZoom verticalAlign visibility + syntax keyword typescriptDOMStyle contained whiteSpace width willChange wordBreak + syntax keyword typescriptDOMStyle contained wordSpacing wordWrap writingMode zIndex + hi def link typescriptDOMStyle Keyword + + + + let typescript_props = 1 + syntax keyword typescriptAnimationEvent contained animationend animationiteration + syntax keyword typescriptAnimationEvent contained animationstart beginEvent endEvent + syntax keyword typescriptAnimationEvent contained repeatEvent + syntax cluster events add=typescriptAnimationEvent + hi def link typescriptAnimationEvent Title + syntax keyword typescriptCSSEvent contained CssRuleViewRefreshed CssRuleViewChanged + syntax keyword typescriptCSSEvent contained CssRuleViewCSSLinkClicked transitionend + syntax cluster events add=typescriptCSSEvent + hi def link typescriptCSSEvent Title + syntax keyword typescriptDatabaseEvent contained blocked complete error success upgradeneeded + syntax keyword typescriptDatabaseEvent contained versionchange + syntax cluster events add=typescriptDatabaseEvent + hi def link typescriptDatabaseEvent Title + syntax keyword typescriptDocumentEvent contained DOMLinkAdded DOMLinkRemoved DOMMetaAdded + syntax keyword typescriptDocumentEvent contained DOMMetaRemoved DOMWillOpenModalDialog + syntax keyword typescriptDocumentEvent contained DOMModalDialogClosed unload + syntax cluster events add=typescriptDocumentEvent + hi def link typescriptDocumentEvent Title + syntax keyword typescriptDOMMutationEvent contained DOMAttributeNameChanged DOMAttrModified + syntax keyword typescriptDOMMutationEvent contained DOMCharacterDataModified DOMContentLoaded + syntax keyword typescriptDOMMutationEvent contained DOMElementNameChanged DOMNodeInserted + syntax keyword typescriptDOMMutationEvent contained DOMNodeInsertedIntoDocument DOMNodeRemoved + syntax keyword typescriptDOMMutationEvent contained DOMNodeRemovedFromDocument DOMSubtreeModified + syntax cluster events add=typescriptDOMMutationEvent + hi def link typescriptDOMMutationEvent Title + syntax keyword typescriptDragEvent contained drag dragdrop dragend dragenter dragexit + syntax keyword typescriptDragEvent contained draggesture dragleave dragover dragstart + syntax keyword typescriptDragEvent contained drop + syntax cluster events add=typescriptDragEvent + hi def link typescriptDragEvent Title + syntax keyword typescriptElementEvent contained invalid overflow underflow DOMAutoComplete + syntax keyword typescriptElementEvent contained command commandupdate + syntax cluster events add=typescriptElementEvent + hi def link typescriptElementEvent Title + syntax keyword typescriptFocusEvent contained blur change DOMFocusIn DOMFocusOut focus + syntax keyword typescriptFocusEvent contained focusin focusout + syntax cluster events add=typescriptFocusEvent + hi def link typescriptFocusEvent Title + syntax keyword typescriptFormEvent contained reset submit + syntax cluster events add=typescriptFormEvent + hi def link typescriptFormEvent Title + syntax keyword typescriptFrameEvent contained DOMFrameContentLoaded + syntax cluster events add=typescriptFrameEvent + hi def link typescriptFrameEvent Title + syntax keyword typescriptInputDeviceEvent contained click contextmenu DOMMouseScroll + syntax keyword typescriptInputDeviceEvent contained dblclick gamepadconnected gamepaddisconnected + syntax keyword typescriptInputDeviceEvent contained keydown keypress keyup MozGamepadButtonDown + syntax keyword typescriptInputDeviceEvent contained MozGamepadButtonUp mousedown mouseenter + syntax keyword typescriptInputDeviceEvent contained mouseleave mousemove mouseout + syntax keyword typescriptInputDeviceEvent contained mouseover mouseup mousewheel MozMousePixelScroll + syntax keyword typescriptInputDeviceEvent contained pointerlockchange pointerlockerror + syntax keyword typescriptInputDeviceEvent contained wheel + syntax cluster events add=typescriptInputDeviceEvent + hi def link typescriptInputDeviceEvent Title + syntax keyword typescriptMediaEvent contained audioprocess canplay canplaythrough + syntax keyword typescriptMediaEvent contained durationchange emptied ended ended loadeddata + syntax keyword typescriptMediaEvent contained loadedmetadata MozAudioAvailable pause + syntax keyword typescriptMediaEvent contained play playing ratechange seeked seeking + syntax keyword typescriptMediaEvent contained stalled suspend timeupdate volumechange + syntax keyword typescriptMediaEvent contained waiting complete + syntax cluster events add=typescriptMediaEvent + hi def link typescriptMediaEvent Title + syntax keyword typescriptMenuEvent contained DOMMenuItemActive DOMMenuItemInactive + syntax cluster events add=typescriptMenuEvent + hi def link typescriptMenuEvent Title + syntax keyword typescriptNetworkEvent contained datachange dataerror disabled enabled + syntax keyword typescriptNetworkEvent contained offline online statuschange connectionInfoUpdate + syntax cluster events add=typescriptNetworkEvent + hi def link typescriptNetworkEvent Title + syntax keyword typescriptProgressEvent contained abort error load loadend loadstart + syntax keyword typescriptProgressEvent contained progress timeout uploadprogress + syntax cluster events add=typescriptProgressEvent + hi def link typescriptProgressEvent Title + syntax keyword typescriptResourceEvent contained cached error load + syntax cluster events add=typescriptResourceEvent + hi def link typescriptResourceEvent Title + syntax keyword typescriptScriptEvent contained afterscriptexecute beforescriptexecute + syntax cluster events add=typescriptScriptEvent + hi def link typescriptScriptEvent Title + syntax keyword typescriptSensorEvent contained compassneedscalibration devicelight + syntax keyword typescriptSensorEvent contained devicemotion deviceorientation deviceproximity + syntax keyword typescriptSensorEvent contained orientationchange userproximity + syntax cluster events add=typescriptSensorEvent + hi def link typescriptSensorEvent Title + syntax keyword typescriptSessionHistoryEvent contained pagehide pageshow popstate + syntax cluster events add=typescriptSessionHistoryEvent + hi def link typescriptSessionHistoryEvent Title + syntax keyword typescriptStorageEvent contained change storage + syntax cluster events add=typescriptStorageEvent + hi def link typescriptStorageEvent Title + syntax keyword typescriptSVGEvent contained SVGAbort SVGError SVGLoad SVGResize SVGScroll + syntax keyword typescriptSVGEvent contained SVGUnload SVGZoom + syntax cluster events add=typescriptSVGEvent + hi def link typescriptSVGEvent Title + syntax keyword typescriptTabEvent contained visibilitychange + syntax cluster events add=typescriptTabEvent + hi def link typescriptTabEvent Title + syntax keyword typescriptTextEvent contained compositionend compositionstart compositionupdate + syntax keyword typescriptTextEvent contained copy cut paste select text + syntax cluster events add=typescriptTextEvent + hi def link typescriptTextEvent Title + syntax keyword typescriptTouchEvent contained touchcancel touchend touchenter touchleave + syntax keyword typescriptTouchEvent contained touchmove touchstart + syntax cluster events add=typescriptTouchEvent + hi def link typescriptTouchEvent Title + syntax keyword typescriptUpdateEvent contained checking downloading error noupdate + syntax keyword typescriptUpdateEvent contained obsolete updateready + syntax cluster events add=typescriptUpdateEvent + hi def link typescriptUpdateEvent Title + syntax keyword typescriptValueChangeEvent contained hashchange input readystatechange + syntax cluster events add=typescriptValueChangeEvent + hi def link typescriptValueChangeEvent Title + syntax keyword typescriptViewEvent contained fullscreen fullscreenchange fullscreenerror + syntax keyword typescriptViewEvent contained resize scroll + syntax cluster events add=typescriptViewEvent + hi def link typescriptViewEvent Title + syntax keyword typescriptWebsocketEvent contained close error message open + syntax cluster events add=typescriptWebsocketEvent + hi def link typescriptWebsocketEvent Title + syntax keyword typescriptWindowEvent contained DOMWindowCreated DOMWindowClose DOMTitleChanged + syntax cluster events add=typescriptWindowEvent + hi def link typescriptWindowEvent Title + syntax keyword typescriptUncategorizedEvent contained beforeunload message open show + syntax cluster events add=typescriptUncategorizedEvent + hi def link typescriptUncategorizedEvent Title + syntax keyword typescriptServiceWorkerEvent contained install activate fetch + syntax cluster events add=typescriptServiceWorkerEvent + hi def link typescriptServiceWorkerEvent Title + + +endif + +" patch +" patch for generated code +syntax keyword typescriptGlobal Promise + \ nextgroup=typescriptGlobalPromiseDot,typescriptFuncCallArg,typescriptTypeArguments oneline +syntax keyword typescriptGlobal Map WeakMap + \ nextgroup=typescriptGlobalPromiseDot,typescriptFuncCallArg,typescriptTypeArguments oneline + +syntax keyword typescriptConstructor contained constructor + \ nextgroup=@typescriptCallSignature + \ skipwhite skipempty + + +syntax cluster memberNextGroup contains=typescriptMemberOptionality,typescriptTypeAnnotation,@typescriptCallSignature + +syntax match typescriptMember /#\?\K\k*/ + \ nextgroup=@memberNextGroup + \ contained skipwhite + +syntax match typescriptMethodAccessor contained /\v(get|set)\s\K/me=e-1 + \ nextgroup=@typescriptMembers + +syntax cluster typescriptPropertyMemberDeclaration contains= + \ typescriptClassStatic, + \ typescriptAccessibilityModifier, + \ typescriptReadonlyModifier, + \ typescriptMethodAccessor, + \ @typescriptMembers + " \ typescriptMemberVariableDeclaration + +syntax match typescriptMemberOptionality /?\|!/ contained + \ nextgroup=typescriptTypeAnnotation,@typescriptCallSignature + \ skipwhite skipempty + +syntax cluster typescriptMembers contains=typescriptMember,typescriptStringMember,typescriptComputedMember + +syntax keyword typescriptClassStatic static + \ nextgroup=@typescriptMembers,typescriptAsyncFuncKeyword,typescriptReadonlyModifier + \ skipwhite contained + +syntax keyword typescriptAccessibilityModifier public private protected contained + +syntax keyword typescriptReadonlyModifier readonly contained + +syntax region typescriptStringMember contained + \ start=/\z(["']\)/ skip=/\\\\\|\\\z1\|\\\n/ end=/\z1/ + \ nextgroup=@memberNextGroup + \ skipwhite skipempty + +syntax region typescriptComputedMember contained matchgroup=typescriptProperty + \ start=/\[/rs=s+1 end=/]/ + \ contains=@typescriptValue,typescriptMember,typescriptMappedIn + \ nextgroup=@memberNextGroup + \ skipwhite skipempty + +"don't add typescriptMembers to nextgroup, let outer scope match it +" so we won't match abstract method outside abstract class +syntax keyword typescriptAbstract abstract + \ nextgroup=typescriptClassKeyword + \ skipwhite skipnl +syntax keyword typescriptClassKeyword class + \ nextgroup=typescriptClassName,typescriptClassExtends,typescriptClassBlock + \ skipwhite + +syntax match typescriptClassName contained /\K\k*/ + \ nextgroup=typescriptClassBlock,typescriptClassExtends,typescriptClassTypeParameter + \ skipwhite skipnl + +syntax region typescriptClassTypeParameter + \ start=/</ end=/>/ + \ contains=@typescriptTypeParameterCluster + \ nextgroup=typescriptClassBlock,typescriptClassExtends + \ contained skipwhite skipnl + +syntax keyword typescriptClassExtends contained extends implements nextgroup=typescriptClassHeritage skipwhite skipnl + +syntax match typescriptClassHeritage contained /\v(\k|\.|\(|\))+/ + \ nextgroup=typescriptClassBlock,typescriptClassExtends,typescriptMixinComma,typescriptClassTypeArguments + \ contains=@typescriptValue + \ skipwhite skipnl + \ contained + +syntax region typescriptClassTypeArguments matchgroup=typescriptTypeBrackets + \ start=/</ end=/>/ + \ contains=@typescriptType + \ nextgroup=typescriptClassExtends,typescriptClassBlock,typescriptMixinComma + \ contained skipwhite skipnl + +syntax match typescriptMixinComma /,/ contained nextgroup=typescriptClassHeritage skipwhite skipnl + +" we need add arrowFunc to class block for high order arrow func +" see test case +syntax region typescriptClassBlock matchgroup=typescriptBraces start=/{/ end=/}/ + \ contains=@typescriptPropertyMemberDeclaration,typescriptAbstract,@typescriptComments,typescriptBlock,typescriptAssign,typescriptDecorator,typescriptAsyncFuncKeyword,typescriptArrowFunc + \ contained fold + +syntax keyword typescriptInterfaceKeyword interface nextgroup=typescriptInterfaceName skipwhite +syntax match typescriptInterfaceName contained /\k\+/ + \ nextgroup=typescriptObjectType,typescriptInterfaceExtends,typescriptInterfaceTypeParameter + \ skipwhite skipnl +syntax region typescriptInterfaceTypeParameter + \ start=/</ end=/>/ + \ contains=@typescriptTypeParameterCluster + \ nextgroup=typescriptObjectType,typescriptInterfaceExtends + \ contained + \ skipwhite skipnl + +syntax keyword typescriptInterfaceExtends contained extends nextgroup=typescriptInterfaceHeritage skipwhite skipnl + +syntax match typescriptInterfaceHeritage contained /\v(\k|\.)+/ + \ nextgroup=typescriptObjectType,typescriptInterfaceComma,typescriptInterfaceTypeArguments + \ skipwhite + +syntax region typescriptInterfaceTypeArguments matchgroup=typescriptTypeBrackets + \ start=/</ end=/>/ skip=/\s*,\s*/ + \ contains=@typescriptType + \ nextgroup=typescriptObjectType,typescriptInterfaceComma + \ contained skipwhite + +syntax match typescriptInterfaceComma /,/ contained nextgroup=typescriptInterfaceHeritage skipwhite skipnl + +"Block VariableStatement EmptyStatement ExpressionStatement IfStatement IterationStatement ContinueStatement BreakStatement ReturnStatement WithStatement LabelledStatement SwitchStatement ThrowStatement TryStatement DebuggerStatement +syntax cluster typescriptStatement + \ contains=typescriptBlock,typescriptVariable, + \ @typescriptTopExpression,typescriptAssign, + \ typescriptConditional,typescriptRepeat,typescriptBranch, + \ typescriptLabel,typescriptStatementKeyword, + \ typescriptFuncKeyword, + \ typescriptTry,typescriptExceptions,typescriptDebugger, + \ typescriptExport,typescriptInterfaceKeyword,typescriptEnum, + \ typescriptModule,typescriptAliasKeyword,typescriptImport + +syntax cluster typescriptPrimitive contains=typescriptString,typescriptTemplate,typescriptRegexpString,typescriptNumber,typescriptBoolean,typescriptNull,typescriptArray + +syntax cluster typescriptEventTypes contains=typescriptEventString,typescriptTemplate,typescriptNumber,typescriptBoolean,typescriptNull + +" top level expression: no arrow func +" also no func keyword. funcKeyword is contained in statement +" funcKeyword allows overloading (func without body) +" funcImpl requires body +syntax cluster typescriptTopExpression + \ contains=@typescriptPrimitive, + \ typescriptIdentifier,typescriptIdentifierName, + \ typescriptOperator,typescriptUnaryOp, + \ typescriptParenExp,typescriptRegexpString, + \ typescriptGlobal,typescriptAsyncFuncKeyword, + \ typescriptClassKeyword,typescriptTypeCast + +" no object literal, used in type cast and arrow func +" TODO: change func keyword to funcImpl +syntax cluster typescriptExpression + \ contains=@typescriptTopExpression, + \ typescriptArrowFuncDef, + \ typescriptFuncImpl + +syntax cluster typescriptValue + \ contains=@typescriptExpression,typescriptObjectLiteral + +syntax cluster typescriptEventExpression contains=typescriptArrowFuncDef,typescriptParenExp,@typescriptValue,typescriptRegexpString,@typescriptEventTypes,typescriptOperator,typescriptGlobal,jsxRegion + +syntax keyword typescriptAsyncFuncKeyword async + \ nextgroup=typescriptFuncKeyword,typescriptArrowFuncDef + \ skipwhite + +syntax keyword typescriptAsyncFuncKeyword await + \ nextgroup=@typescriptValue + \ skipwhite + +syntax keyword typescriptFuncKeyword function + \ nextgroup=typescriptAsyncFunc,typescriptFuncName,@typescriptCallSignature + \ skipwhite skipempty + +syntax match typescriptAsyncFunc contained /*/ + \ nextgroup=typescriptFuncName,@typescriptCallSignature + \ skipwhite skipempty + +syntax match typescriptFuncName contained /\K\k*/ + \ nextgroup=@typescriptCallSignature + \ skipwhite + +" destructuring ({ a: ee }) => +syntax match typescriptArrowFuncDef contained /(\(\s*\({\_[^}]*}\|\k\+\)\(:\_[^)]\)\?,\?\)\+)\s*=>/ + \ contains=typescriptArrowFuncArg,typescriptArrowFunc + \ nextgroup=@typescriptExpression,typescriptBlock + \ skipwhite skipempty + +" matches `(a) =>` or `([a]) =>` or +" `( +" a) =>` +syntax match typescriptArrowFuncDef contained /(\(\_s*[a-zA-Z\$_\[.]\_[^)]*\)*)\s*=>/ + \ contains=typescriptArrowFuncArg,typescriptArrowFunc + \ nextgroup=@typescriptExpression,typescriptBlock + \ skipwhite skipempty + +syntax match typescriptArrowFuncDef contained /\K\k*\s*=>/ + \ contains=typescriptArrowFuncArg,typescriptArrowFunc + \ nextgroup=@typescriptExpression,typescriptBlock + \ skipwhite skipempty + +" TODO: optimize this pattern +syntax region typescriptArrowFuncDef contained start=/(\_[^(^)]*):/ end=/=>/ + \ contains=typescriptArrowFuncArg,typescriptArrowFunc,typescriptTypeAnnotation + \ nextgroup=@typescriptExpression,typescriptBlock + \ skipwhite skipempty keepend + +syntax match typescriptArrowFunc /=>/ +syntax match typescriptArrowFuncArg contained /\K\k*/ +syntax region typescriptArrowFuncArg contained start=/<\|(/ end=/\ze=>/ contains=@typescriptCallSignature + +syntax region typescriptReturnAnnotation contained start=/:/ end=/{/me=e-1 contains=@typescriptType nextgroup=typescriptBlock + + +syntax region typescriptFuncImpl contained start=/function\>/ end=/{/me=e-1 + \ contains=typescriptFuncKeyword + \ nextgroup=typescriptBlock + +syntax cluster typescriptCallImpl contains=typescriptGenericImpl,typescriptParamImpl +syntax region typescriptGenericImpl matchgroup=typescriptTypeBrackets + \ start=/</ end=/>/ skip=/\s*,\s*/ + \ contains=typescriptTypeParameter + \ nextgroup=typescriptParamImpl + \ contained skipwhite +syntax region typescriptParamImpl matchgroup=typescriptParens + \ start=/(/ end=/)/ + \ contains=typescriptDecorator,@typescriptParameterList,@typescriptComments + \ nextgroup=typescriptReturnAnnotation,typescriptBlock + \ contained skipwhite skipnl + +syntax match typescriptDecorator /@\([_$a-zA-Z][_$a-zA-Z0-9]*\.\)*[_$a-zA-Z][_$a-zA-Z0-9]*\>/ + \ nextgroup=typescriptFuncCallArg,typescriptTypeArguments + \ contains=@_semantic,typescriptDotNotation + +" Define the default highlighting. +hi def link typescriptReserved Error + +hi def link typescriptEndColons Exception +hi def link typescriptSymbols Normal +hi def link typescriptBraces Function +hi def link typescriptParens Normal +hi def link typescriptComment Comment +hi def link typescriptLineComment Comment +hi def link typescriptDocComment Comment +hi def link typescriptCommentTodo Todo +hi def link typescriptMagicComment SpecialComment +hi def link typescriptRef Include +hi def link typescriptDocNotation SpecialComment +hi def link typescriptDocTags SpecialComment +hi def link typescriptDocNGParam typescriptDocParam +hi def link typescriptDocParam Function +hi def link typescriptDocNumParam Function +hi def link typescriptDocEventRef Function +hi def link typescriptDocNamedParamType Type +hi def link typescriptDocParamName Type +hi def link typescriptDocParamType Type +hi def link typescriptString String +hi def link typescriptSpecial Special +hi def link typescriptStringLiteralType String +hi def link typescriptTemplateLiteralType String +hi def link typescriptStringMember String +hi def link typescriptTemplate String +hi def link typescriptEventString String +hi def link typescriptDestructureString String +hi def link typescriptASCII Special +hi def link typescriptTemplateSB Label +hi def link typescriptRegexpString String +hi def link typescriptGlobal Constant +hi def link typescriptTestGlobal Function +hi def link typescriptPrototype Type +hi def link typescriptConditional Conditional +hi def link typescriptConditionalElse Conditional +hi def link typescriptCase Conditional +hi def link typescriptDefault typescriptCase +hi def link typescriptBranch Conditional +hi def link typescriptIdentifier Structure +hi def link typescriptVariable Identifier +hi def link typescriptDestructureVariable PreProc +hi def link typescriptEnumKeyword Identifier +hi def link typescriptRepeat Repeat +hi def link typescriptForOperator Repeat +hi def link typescriptStatementKeyword Statement +hi def link typescriptMessage Keyword +hi def link typescriptOperator Identifier +hi def link typescriptKeywordOp Identifier +hi def link typescriptCastKeyword Special +hi def link typescriptType Type +hi def link typescriptNull Boolean +hi def link typescriptNumber Number +hi def link typescriptBoolean Boolean +hi def link typescriptObjectLabel typescriptLabel +hi def link typescriptDestructureLabel Function +hi def link typescriptLabel Label +hi def link typescriptTupleLable Label +hi def link typescriptStringProperty String +hi def link typescriptImport Special +hi def link typescriptImportType Special +hi def link typescriptAmbientDeclaration Special +hi def link typescriptExport Special +hi def link typescriptExportType Special +hi def link typescriptModule Special +hi def link typescriptTry Special +hi def link typescriptExceptions Special + +hi def link typescriptMember Function +hi def link typescriptMethodAccessor Operator + +hi def link typescriptAsyncFuncKeyword Keyword +hi def link typescriptObjectAsyncKeyword Keyword +hi def link typescriptAsyncFor Keyword +hi def link typescriptFuncKeyword Keyword +hi def link typescriptAsyncFunc Keyword +hi def link typescriptArrowFunc Type +hi def link typescriptFuncName Function +hi def link typescriptFuncArg PreProc +hi def link typescriptArrowFuncArg PreProc +hi def link typescriptFuncComma Operator + +hi def link typescriptClassKeyword Keyword +hi def link typescriptClassExtends Keyword +" hi def link typescriptClassName Function +hi def link typescriptAbstract Special +" hi def link typescriptClassHeritage Function +" hi def link typescriptInterfaceHeritage Function +hi def link typescriptClassStatic StorageClass +hi def link typescriptReadonlyModifier Keyword +hi def link typescriptInterfaceKeyword Keyword +hi def link typescriptInterfaceExtends Keyword +hi def link typescriptInterfaceName Function + +hi def link shellbang Comment + +hi def link typescriptTypeParameter Identifier +hi def link typescriptConstraint Keyword +hi def link typescriptPredefinedType Type +hi def link typescriptReadonlyArrayKeyword Keyword +hi def link typescriptUnion Operator +hi def link typescriptFuncTypeArrow Function +hi def link typescriptConstructorType Function +hi def link typescriptTypeQuery Keyword +hi def link typescriptAccessibilityModifier Keyword +hi def link typescriptOptionalMark PreProc +hi def link typescriptFuncType Special +hi def link typescriptMappedIn Special +hi def link typescriptCall PreProc +hi def link typescriptParamImpl PreProc +hi def link typescriptConstructSignature Identifier +hi def link typescriptAliasDeclaration Identifier +hi def link typescriptAliasKeyword Keyword +hi def link typescriptUserDefinedType Keyword +hi def link typescriptTypeReference Identifier +hi def link typescriptConstructor Keyword +hi def link typescriptDecorator Special +hi def link typescriptAssertType Keyword + +hi link typeScript NONE + +if exists('s:cpo_save') + let &cpo = s:cpo_save + unlet s:cpo_save +endif 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/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/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/gen_vimdoc.py b/scripts/gen_vimdoc.py index c17742ddaf..bca3dd816f 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. @@ -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..79ae7994f7 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 diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index d3895d31cf..5e90e40dd3 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" 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..933aa85530 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" @@ -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. @@ -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 a764fb069b..32104ef6dc 100644 --- a/src/nvim/api/keysets.lua +++ b/src/nvim/api/keysets.lua @@ -106,7 +106,6 @@ return { "reverse"; "nocombine"; "default"; - "global"; "cterm"; "foreground"; "fg"; "background"; "bg"; @@ -114,9 +113,9 @@ return { "ctermbg"; "special"; "sp"; "link"; + "global_link"; "fallback"; "blend"; - "temp"; }; highlight_cterm = { "bold"; diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index fad75d55be..c466fc53e1 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,7 +54,7 @@ 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, @@ -89,7 +89,7 @@ bool try_leave(const TryState *const tstate, Error *const err) 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; diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h index 1441da853c..4608554448 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,8 +130,8 @@ 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; int need_rethrow; @@ -144,8 +144,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..6f7bfa244a 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); diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index e2f58dba62..e4dc219e9a 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" @@ -52,8 +54,8 @@ #include "nvim/option.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/types.h" #include "nvim/ui.h" @@ -91,7 +93,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 +181,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(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 +482,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`. @@ -1833,11 +1837,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 +1880,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) { diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c index 478e146781..a28bfd2ab9 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; diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c index d36c5bfb95..0c89726d71 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; } @@ -209,7 +209,7 @@ void nvim_win_set_config(Window window, Dict(float_config) *config, Error *err) } 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..580dfd8639 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" @@ -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, NOT_VALID); +} diff --git a/src/nvim/arglist.c b/src/nvim/arglist.c new file mode 100644 index 0000000000..7d8917cc73 --- /dev/null +++ b/src/nvim/arglist.c @@ -0,0 +1,1157 @@ +// 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_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 = (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 + +/// 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; +} + +/// 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 = (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; + } +} + +/// @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; +} + +/// 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_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(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); + } +} + +/// ":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 (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" +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, 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 +void f_argidx(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = curwin->w_arg_idx; +} + +/// "arglistid()" function +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; + } +} + +/// 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, 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); + } +} 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..b5b2a73be1 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/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" @@ -1141,7 +1146,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; @@ -1769,10 +1774,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; @@ -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; @@ -1981,8 +1984,9 @@ void auto_next_pat(AutoPatCmd *apc, int stop_at_last) AutoPat *ap; AutoCmd *cp; char *s; + char **const sourcing_namep = &SOURCING_NAME; - XFREE_CLEAR(sourcing_name); + XFREE_CLEAR(*sourcing_namep); for (ap = apc->curpat; ap != NULL && !got_int; ap = ap->next) { apc->curpat = NULL; @@ -2007,11 +2011,11 @@ 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); + *sourcing_namep = xmalloc(sourcing_name_len); + snprintf(*sourcing_namep, sourcing_name_len, s, name, ap->pat); if (p_verbose >= 8) { verbose_enter(); - smsg(_("Executing %s"), sourcing_name); + smsg(_("Executing %s"), *sourcing_namep); verbose_leave(); } diff --git a/src/nvim/autocmd.h b/src/nvim/autocmd.h index a085a03455..d559d8c3d2 100644 --- a/src/nvim/autocmd.h +++ b/src/nvim/autocmd.h @@ -22,7 +22,8 @@ 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 @@ -30,11 +31,12 @@ typedef struct AutoCmd { int64_t id; // ID used for uniquely tracking an autocmd. sctx_T script_ctx; // script context where 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 @@ -58,8 +61,8 @@ typedef struct AutoPatCmd { event_T event; // current event 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..f9bce2476f 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -25,8 +25,10 @@ #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" @@ -36,6 +38,7 @@ #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 +53,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" @@ -69,7 +73,7 @@ #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/strings.h" @@ -793,8 +797,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 { @@ -1532,6 +1536,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; @@ -2595,7 +2608,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 +2616,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. @@ -4628,279 +4641,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 +4879,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 +4923,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 +4964,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 +4979,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; diff --git a/src/nvim/buffer.h b/src/nvim/buffer.h index b452eb227e..7627b6a596 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 { diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index b7f66e6dba..769788a63e 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) @@ -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 @@ -1150,43 +1148,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. @@ -1198,11 +1159,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 @@ -1525,11 +1489,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..5184dc0689 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,7 +24,6 @@ #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/ui.h" @@ -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); } 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..6238d85b3a 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) { diff --git a/src/nvim/cmdhist.c b/src/nvim/cmdhist.c new file mode 100644 index 0000000000..5725a6655d --- /dev/null +++ b/src/nvim/cmdhist.c @@ -0,0 +1,743 @@ +// 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_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) +{ + 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 +{ + 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, 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 +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 +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 +void f_histnr(typval_T *argvars, typval_T *rettv, FunPtr 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..e3ae9355bf 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); diff --git a/src/nvim/cursor.c b/src/nvim/cursor.c index 1446257f7e..d4670dedc7 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" diff --git a/src/nvim/debugger.c b/src/nvim/debugger.c index 0eaff06833..a061bd961b 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" @@ -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; @@ -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); } diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index e7c76fe38e..a93fb599c4 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: diff --git a/src/nvim/decoration_provider.c b/src/nvim/decoration_provider.c index 04d875c4e3..95e13b4240 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(); } } } 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..c1fdbc1b9a 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" @@ -35,7 +37,6 @@ #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" @@ -956,13 +957,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 +1076,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; @@ -2143,14 +2144,14 @@ int diffopt_changed(void) long diff_algorithm_new = 0; long diff_indent_heuristic = 0; - char_u *p = p_dip; + char *p = (char *)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 +2175,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; diff --git a/src/nvim/digraph.c b/src/nvim/digraph.c index 733b3d3d5d..0f511bd37c 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" diff --git a/src/nvim/drawline.c b/src/nvim/drawline.c new file mode 100644 index 0000000000..95026ff8ed --- /dev/null +++ b/src/nvim/drawline.c @@ -0,0 +1,2706 @@ +// 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(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; +} + +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; + } + } +} + +/// 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(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, &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 = 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; + 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! + 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(neovim): 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, &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; + 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, 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..75fe0565c2 --- /dev/null +++ b/src/nvim/drawscreen.c @@ -0,0 +1,2373 @@ +// 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, 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/buffer.h" +#include "nvim/charset.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/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 = 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(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; + } +} + +/// 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(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(); + } + } + } + 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 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 < 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; + screen_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; + } + + win_check_ns_hl(wp); + + // 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) { + 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; + + char* title = wp->w_float_config.title; + size_t n_title = wp->w_float_config.n_title; + stl_hlrec_t* title_hl = wp->w_float_config.title_hl; + + int m8[MAX_MCO + 1]; + int cc; + int len; + int t_attr = title_hl != NULL && title_hl->userhl + ? syn_id2attr(title_hl->userhl) + : 0; + t_attr = hl_combine_attr(attrs[1], t_attr); + + int title_pos = 2; + switch (wp->w_float_config.title_pos) { + case kTitleLeft: + title_pos = 2; + break; + case kTitleRight: + title_pos = icol - 2 - vim_strsize(title); + break; + case kTitleCenter: + title_pos = (icol - vim_strsize(title)) / 2 - 1; + break; + } + title_pos = title_pos < 2 ? 2 : title_pos; + + 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++) { + schar_T ch; + int attr; + // Draw the title if in the correct position. + if (i > title_pos && n_title > 0 && i < icol - 2) { + cc = utfc_ptr2char((char_u*) title, m8); + len = utfc_ptr2len(title); + n_title -= len; + title += len; + + while (title_hl != NULL && + (title_hl + 1)->start != NULL && + (title_hl + 1)->start < title) { + ++ title_hl; + t_attr = hl_combine_attr(attrs[1], syn_id2attr(-title_hl->userhl)); + } + + schar_from_cc(ch, cc, m8); + attr = t_attr; + } else { + memcpy(ch, chars[1], sizeof(schar_T)); + attr = attrs[1]; + } + grid_put_schar(grid, 0, i + adj[3], ch, attr); + } + 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); + } +} + +/// 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 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 = 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; +} + +/// 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(); + } +} + +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. +/// 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, &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 = 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 (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 == 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); + + 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, (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, &provider_err); + } + } 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; + } +} + +/// 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); + } + } +} + +/// 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; + } + } + } +} + +/// 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(); + } +} + +/// 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); + } +} diff --git a/src/nvim/drawscreen.h b/src/nvim/drawscreen.h new file mode 100644 index 0000000000..3eac1caaa1 --- /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 { + VALID = 10, ///< buffer not changed, or changes marked with b_mod_* + INVERTED = 20, ///< redisplay inverted part that changed + INVERTED_ALL = 25, ///< redisplay whole inverted part + REDRAW_TOP = 30, ///< display first w_upd_rows screen lines + SOME_VALID = 35, ///< like NOT_VALID but may scroll + NOT_VALID = 40, ///< buffer needs complete redraw + 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 969e0af9f5..3a47731715 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,9 +48,8 @@ #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" @@ -1747,7 +1748,7 @@ void change_indent(int type, int amount, int round, int replaced, int call_chang replace_push(replaced); replaced = NUL; } - ++start_col; + start_col++; } } @@ -1913,7 +1914,7 @@ int get_literal(bool no_simplify) cc = cc * 10 + nc - '0'; } - ++i; + i++; } if (cc > 255 @@ -1948,7 +1949,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. @@ -2022,7 +2023,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 +2082,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 +2098,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; @@ -3253,7 +3254,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; @@ -3433,7 +3434,7 @@ void replace_push(int c) memmove(p + 1, p, (size_t)replace_offset); } *p = (char_u)c; - ++replace_stack_nr; + replace_stack_nr++; } /* @@ -3468,7 +3469,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; @@ -3794,12 +3795,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++; @@ -4253,7 +4251,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 @@ -5221,8 +5219,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. @@ -5254,8 +5252,8 @@ static bool ins_tab(void) } } } - ++fpos.col; - ++ptr; + fpos.col++; + ptr++; vcol += i; } @@ -5266,8 +5264,8 @@ static bool ins_tab(void) // Skip over the spaces we need. while (vcol < want_vcol && *ptr == ' ') { vcol += lbr_chartabsize(line, ptr, vcol); - ++ptr; - ++repl_off; + ptr++; + repl_off++; } if (vcol > want_vcol) { // Must have a char with 'showbreak' just before it. diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 7cb8bdee19..53f1074033 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -1,9 +1,7 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com -/* - * eval.c: Expression evaluation. - */ +// eval.c: Expression evaluation. #include <math.h> #include <stdlib.h> @@ -15,10 +13,12 @@ #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" @@ -29,9 +29,10 @@ #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/ex_session.h" -#include "nvim/fileio.h" #include "nvim/getchar.h" #include "nvim/highlight_group.h" #include "nvim/lua/executor.h" @@ -43,8 +44,10 @@ #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 +74,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,9 +97,7 @@ 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_varcount; // nr of variables in the list @@ -356,8 +353,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 +360,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 +442,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 +466,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 +511,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 +542,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 +555,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 +575,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 +595,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 @@ -935,7 +923,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 +931,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 +988,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 +1026,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 +1038,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 +1120,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 +1176,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 +1184,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 +1209,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 +1247,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 +1299,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 +1316,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 +1560,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 +1713,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); @@ -1888,7 +1834,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 +1876,7 @@ void *eval_for_line(const char *arg, bool *errp, char **nextcmdp, int skip) } } if (skip) { - --emsg_skip; + emsg_skip--; } return fi; @@ -2009,7 +1955,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 +1981,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 +1994,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 +2019,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; @@ -2123,11 +2069,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 +2115,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 +2132,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 +2146,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 +2160,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); } @@ -2291,7 +2235,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,11 +2261,9 @@ 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. @@ -2372,18 +2314,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 +2336,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 +2351,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 +2379,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 +2401,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 +2438,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 +2460,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 +2511,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 +2560,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 +2611,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 +2638,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 +2649,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]; @@ -2876,16 +2778,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 +2806,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 +2831,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 +2945,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 +3041,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,15 +3058,12 @@ static int eval7(char **arg, typval_T *rettv, int evaluate, int want_string) // Register contents: @r. case '@': - ++*arg; + (*arg)++; int regname = mb_cptr2char_adv((const char_u**) arg); if (evaluate) { rettv->v_type = VAR_STRING; rettv->vval.v_string = get_reg_contents(regname, kGRegExprSrc); } - // if (**arg != NUL) { - // ++*arg; - // } break; // nested expression: (expression). @@ -3180,7 +3071,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); @@ -3326,7 +3217,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). @@ -3354,7 +3245,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 != '(') { @@ -3504,9 +3395,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) { @@ -3514,11 +3403,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; @@ -3530,9 +3417,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); @@ -3773,11 +3658,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. @@ -3794,10 +3675,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) { @@ -3838,9 +3723,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++; @@ -3864,10 +3747,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; @@ -3906,7 +3787,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++; @@ -3936,7 +3817,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>" @@ -3958,11 +3839,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; @@ -3980,19 +3861,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++; } } @@ -4007,10 +3885,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; @@ -4019,9 +3895,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; @@ -4120,16 +3996,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; } @@ -4142,8 +4016,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; @@ -4153,8 +4027,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; } @@ -4183,25 +4057,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. /// @@ -4220,6 +4092,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(); @@ -4233,7 +4122,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); } @@ -4692,21 +4581,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; @@ -4716,9 +4600,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; @@ -4751,7 +4637,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); @@ -4806,8 +4692,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; @@ -4821,6 +4705,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); } @@ -4834,23 +4719,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); @@ -4868,18 +4748,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) { @@ -4909,17 +4777,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") @@ -4950,18 +4811,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; @@ -4970,14 +4833,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))) { @@ -5126,7 +4989,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) { @@ -5330,29 +5193,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) @@ -5689,11 +5529,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; @@ -5715,6 +5551,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; @@ -5724,6 +5561,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); @@ -5807,7 +5647,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; @@ -5816,6 +5655,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; @@ -5925,9 +5765,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; @@ -6499,12 +6339,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; } @@ -6521,6 +6358,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 { @@ -6528,7 +6366,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) { @@ -6628,8 +6466,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. @@ -6640,6 +6476,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) { @@ -6692,15 +6530,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; } @@ -6748,8 +6584,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 @@ -6758,7 +6592,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; @@ -6776,10 +6610,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; @@ -6811,10 +6643,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; @@ -6825,6 +6653,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) @@ -6842,7 +6674,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) { @@ -6860,9 +6692,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--; } } @@ -6898,20 +6730,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); @@ -7354,7 +7185,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; @@ -7431,8 +7262,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) { @@ -7456,7 +7285,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 @@ -7491,7 +7320,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; @@ -7507,7 +7335,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; } @@ -7596,28 +7424,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++; } } @@ -7641,8 +7467,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); } @@ -7674,7 +7500,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: @@ -7727,7 +7553,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); } @@ -7739,7 +7565,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; } @@ -7756,7 +7582,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 @@ -7836,12 +7662,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); @@ -7885,7 +7710,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) { @@ -7899,7 +7724,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); @@ -7915,7 +7740,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; @@ -7941,174 +7766,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. /// @@ -8183,61 +7840,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 { @@ -8325,11 +7927,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; @@ -8414,10 +8014,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; @@ -8479,6 +8077,8 @@ repeat: } } + int c; + // ":." - path relative to the current directory // ":~" - path relative to the home directory // ":8" - shortname path - postponed till after @@ -8544,7 +8144,7 @@ repeat: } } - tail = path_tail(*fnamep); + char *tail = path_tail(*fnamep); *fnamelen = STRLEN(*fnamep); // ":h" - head, remove "/file_name", can be repeated @@ -8585,10 +8185,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; @@ -8639,18 +8238,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); @@ -8668,7 +8265,7 @@ repeat: *fnamelen = STRLEN(s); xfree(*bufp); *bufp = s; - didit = TRUE; + didit = true; xfree(sub); xfree(str); } @@ -8709,26 +8306,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; + char *save_cpo = p_cpo; p_cpo = (char *)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]) { @@ -8869,8 +8462,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, @@ -8969,8 +8561,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, "?"); } @@ -9012,8 +8604,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 @@ -9025,8 +8615,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..e4e9b34ec6 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -72,6 +72,7 @@ return { 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}, @@ -327,6 +328,7 @@ 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}, 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/funcs.c b/src/nvim/eval/funcs.c index f24285063e..3b9dc92137 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -7,12 +7,14 @@ #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/cmdhist.h" #include "nvim/context.h" #include "nvim/cursor.h" #include "nvim/diff.h" @@ -27,10 +29,12 @@ #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/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,6 +49,7 @@ #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" @@ -52,18 +57,20 @@ #include "nvim/ops.h" #include "nvim/option.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 +124,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 +160,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; } @@ -294,10 +299,9 @@ static void f_abs(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (argvars[0].v_type == VAR_FLOAT) { float_op_wrapper(argvars, rettv, (FunPtr)&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) { @@ -371,77 +375,6 @@ 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) { @@ -479,8 +412,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)) @@ -632,17 +565,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 +582,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; + int save_magic = p_magic; + p_magic = true; + char *save_cpo = p_cpo; p_cpo = ""; - 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 +617,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)); @@ -735,13 +664,13 @@ 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) { - byteidx(argvars, rettv, FALSE); + byteidx(argvars, rettv, false); } /// "byteidxcomp()" function static void f_byteidxcomp(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - byteidx(argvars, rettv, TRUE); + byteidx(argvars, rettv, true); } /// "call(func, arglist [, dict])" function @@ -758,7 +687,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 +704,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)); @@ -898,10 +827,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 @@ -984,9 +912,6 @@ 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) { - char_u *cwd; - CdScope scope = kCdScopeGlobal; - rettv->v_type = VAR_STRING; rettv->vval.v_string = NULL; @@ -997,7 +922,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 +931,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) { @@ -1021,11 +947,8 @@ 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) { - 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(); @@ -1060,14 +983,12 @@ static void f_confirm(typval_T *argvars, typval_T *rettv, FunPtr 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 +1000,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 { @@ -1152,15 +1073,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 +1099,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)) { @@ -1414,10 +1331,8 @@ static void f_cursor(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "debugbreak()" function static void f_debugbreak(typval_T *argvars, typval_T *rettv, FunPtr 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 { @@ -1564,10 +1479,6 @@ 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) { - 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 +1487,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 +1501,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; @@ -1660,8 +1574,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 +1582,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 +1604,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 { @@ -2042,12 +1954,9 @@ 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) { - 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; @@ -2066,7 +1975,8 @@ 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); + size_t len; + char_u *result = eval_vars((char_u *)s, (char_u *)s, &len, NULL, &errormsg, NULL); emsg_off--; if (rettv->v_type == VAR_LIST) { tv_list_alloc_ret(rettv, (result != NULL)); @@ -2085,6 +1995,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) { @@ -2151,8 +2062,6 @@ static void f_expandcmd(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "flatten(list[, {maxdepth}])" function static void f_flatten(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - list_T *list; - long maxdepth; bool error = false; if (argvars[0].v_type != VAR_LIST) { @@ -2160,6 +2069,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 +2083,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"), @@ -2190,7 +2100,6 @@ static void f_extend(typval_T *argvars, typval_T *rettv, FunPtr 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 +2107,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. } @@ -2360,7 +2269,7 @@ 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) { - filter_map(argvars, rettv, FALSE); + filter_map(argvars, rettv, false); } /// "finddir({fname}[, {path}[, {count}]])" function @@ -2440,130 +2349,6 @@ 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) {} @@ -2593,10 +2378,6 @@ 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) { - listitem_T *li; - list_T *l; - dictitem_T *di; - dict_T *d; typval_T *tv = NULL; bool what_is_dict = false; @@ -2617,17 +2398,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 +2422,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; } @@ -2851,147 +2634,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; @@ -3203,15 +2845,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; @@ -3455,13 +3097,6 @@ 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) { @@ -3483,8 +3118,6 @@ 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) { - dict_T *d; - win_T *wp; int row = mouse_row; int col = mouse_col; int grid = mouse_grid; @@ -3495,12 +3128,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 @@ -3546,12 +3179,6 @@ static void f_getpos(typval_T *argvars, typval_T *rettv, FunPtr 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. @@ -3788,7 +3415,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 +3428,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 @@ -3826,12 +3453,8 @@ 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) { - 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 +3464,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; @@ -3890,8 +3515,8 @@ static void f_glob(typval_T *argvars, typval_T *rettv, FunPtr fptr) 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)) { @@ -3964,7 +3589,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_u *)file, &ga, flags); if (rettv->v_type == VAR_STRING) { rettv->vval.v_string = ga_concat_strings_sep(&ga, "\n"); @@ -4306,87 +3931,6 @@ 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) { @@ -4526,21 +4070,18 @@ static bool inputsecret_flag = false; /// Also handles inputsecret() when inputsecret is set. static void f_input(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - get_user_input(argvars, rettv, FALSE, inputsecret_flag); + get_user_input(argvars, rettv, false, inputsecret_flag); } /// "inputdialog()" function static void f_inputdialog(typval_T *argvars, typval_T *rettv, FunPtr 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) { - int selected; - int mouse_used; - if (argvars[0].v_type != VAR_LIST) { semsg(_(e_listarg), "inputlist()"); return; @@ -4558,7 +4099,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; } @@ -4695,7 +4237,6 @@ static void f_isdirectory(typval_T *argvars, typval_T *rettv, FunPtr fptr) static void f_islocked(typval_T *argvars, typval_T *rettv, FunPtr 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]), @@ -4708,7 +4249,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 @@ -5016,7 +4557,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((const char_u *)cwd)) { semsg(_(e_invarg2), "expected valid directory"); shell_free_argv(argv); return; @@ -5410,7 +4951,7 @@ 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) { - filter_map(argvars, rettv, TRUE); + filter_map(argvars, rettv, true); } static void find_some_match(typval_T *const argvars, typval_T *const rettv, @@ -5420,18 +4961,16 @@ 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; + char *save_cpo = p_cpo; p_cpo = ""; rettv->vval.v_number = -1; @@ -5458,6 +4997,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; @@ -5566,10 +5106,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; } @@ -5710,13 +5248,13 @@ 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) { - max_min(argvars, rettv, TRUE); + max_min(argvars, rettv, true); } /// "min()" function static void f_min(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - max_min(argvars, rettv, FALSE); + max_min(argvars, rettv, false); } /// "mkdir()" function @@ -6047,14 +5585,13 @@ static void f_printf(typval_T *argvars, typval_T *rettv, FunPtr 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; @@ -6067,13 +5604,12 @@ 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) { - 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; } @@ -6091,13 +5627,12 @@ 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) { - 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; } @@ -6342,13 +5877,11 @@ static void f_rubyeval(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "range()" function static void f_range(typval_T *argvars, typval_T *rettv, FunPtr 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; @@ -6368,7 +5901,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); } } @@ -6379,8 +5912,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; @@ -6389,11 +5920,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; } @@ -6435,12 +5968,11 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr 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) { @@ -6482,7 +6014,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: @@ -6514,9 +6046,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; @@ -6590,10 +6122,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 { @@ -7142,7 +6674,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]; @@ -7150,6 +6681,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': @@ -7199,26 +6731,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; } @@ -7245,25 +6770,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 (;;) { @@ -7398,8 +6925,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; @@ -7407,16 +6933,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; @@ -7433,8 +6957,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; @@ -7568,14 +7091,13 @@ 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) { - 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 { @@ -7587,14 +7109,13 @@ 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) { - 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 { @@ -7606,10 +7127,10 @@ 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) { - 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) { @@ -7640,10 +7161,6 @@ static void f_screencol(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "screenpos({winid}, {lnum}, {col})" function static void f_screenpos(typval_T *argvars, typval_T *rettv, FunPtr 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; @@ -7652,9 +7169,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); @@ -7722,7 +7243,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; @@ -7740,7 +7260,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; } @@ -7834,33 +7354,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; + char *save_cpo = p_cpo; p_cpo = (char *)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); @@ -7876,19 +7387,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; @@ -7914,7 +7427,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); @@ -8103,13 +7616,13 @@ static void f_setbufline(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// 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; @@ -8143,15 +7656,13 @@ static void f_setcharpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) static void f_setcharsearch(typval_T *argvars, typval_T *rettv, FunPtr 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]; @@ -8159,7 +7670,7 @@ static void f_setcharsearch(typval_T *argvars, typval_T *rettv, FunPtr fptr) 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); } @@ -8240,117 +7751,17 @@ static void f_setline(typval_T *argvars, typval_T *rettv, FunPtr fptr) 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) { 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 @@ -8372,7 +7783,7 @@ 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; } @@ -8380,11 +7791,9 @@ static int get_yank_type(char_u **const pp, MotionType *const yank_type, long *c static void f_setreg(typval_T *argvars, typval_T *rettv, FunPtr 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. @@ -8404,7 +7813,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; } @@ -8416,7 +7825,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"); @@ -8459,7 +7868,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); } } } @@ -8493,7 +7902,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) { @@ -8523,14 +7932,12 @@ free_lstval: static void f_settagstack(typval_T *argvars, typval_T *rettv, FunPtr fptr) { static char *e_invact2 = N_("E962: Invalid action: '%s'"); - win_T *wp; - dict_T *d; 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; } @@ -8540,7 +7947,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; } @@ -8600,9 +8007,7 @@ static void f_shiftwidth(typval_T *argvars, typval_T *rettv, FunPtr 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 } @@ -8681,10 +8086,9 @@ static void f_stdioopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) 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; @@ -8732,9 +8136,6 @@ 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) { - 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) { @@ -8748,6 +8149,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); @@ -8779,22 +8183,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) { - 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) { @@ -8808,8 +8206,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; @@ -8838,14 +8239,12 @@ f_spellsuggest_return: static void f_split(typval_T *argvars, typval_T *rettv, FunPtr 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; + char *save_cpo = p_cpo; p_cpo = ""; const char *str = tv_get_string(&argvars[0]); @@ -8878,6 +8277,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 { @@ -8978,7 +8378,6 @@ static void f_str2list(typval_T *argvars, typval_T *rettv, FunPtr fptr) static void f_str2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr) { int base = 10; - varnumber_T n; int what = 0; if (argvars[1].v_type != VAR_UNKNOWN) { @@ -9008,6 +8407,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) { @@ -9583,7 +8983,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) { @@ -9694,11 +9094,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); @@ -9713,20 +9111,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; @@ -9743,14 +9141,14 @@ 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; @@ -9772,13 +9170,11 @@ 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) { - char *fname; - tagname_T tn; - tv_list_alloc_ret(rettv, kListLenUnknown); - fname = xmalloc(MAXPATHL); + char *fname = xmalloc(MAXPATHL); bool first = true; + tagname_T tn; while (get_tagfname(&tn, first, (char_u *)fname) == OK) { tv_list_append_string(rettv->vval.v_list, fname, -1); first = false; @@ -9857,7 +9253,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((const char_u *)cwd)) { semsg(_(e_invarg2), "expected valid directory"); shell_free_argv(argv); return; @@ -9978,7 +9374,6 @@ static void f_timer_pause(typval_T *argvars, typval_T *unused, FunPtr fptr) static void f_timer_start(typval_T *argvars, typval_T *rettv, FunPtr fptr) { int repeat = 1; - dict_T *dict; rettv->vval.v_number = -1; if (check_secure()) { @@ -9986,8 +9381,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; } @@ -10128,10 +9523,8 @@ static void f_trim(typval_T *argvars, typval_T *rettv, FunPtr fptr) 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; @@ -10156,6 +9549,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) { @@ -10178,7 +9572,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) { @@ -10277,10 +9671,9 @@ static void f_undotree(typval_T *argvars, typval_T *rettv, FunPtr fptr) static void f_virtcol(typval_T *argvars, typval_T *rettv, FunPtr fptr) { colnr_T vcol = 0; - pos_T *fp; int fnum = curbuf->b_fnum; - fp = var2fpos(&argvars[0], false, &fnum, 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. @@ -10293,7 +9686,7 @@ 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; @@ -10384,17 +9777,14 @@ static void f_win_id2win(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "win_move_separator()" function static void f_win_move_separator(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - win_T *wp; - int offset; - rettv->vval.v_number = false; - wp = find_win_by_nr_or_id(&argvars[0]); + 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; } @@ -10475,18 +9865,15 @@ static void f_winline(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "winnr()" function static void f_winnr(typval_T *argvars, typval_T *rettv, FunPtr 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) { - 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. @@ -10511,10 +9898,9 @@ 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) { - 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; @@ -10562,10 +9948,8 @@ 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) { - 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); diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index ff1808ed91..8822bb0491 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"); } diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h index c02351947b..c4bc9f603b 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" @@ -356,6 +355,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..c46cb6ba5d 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; @@ -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,7 +845,7 @@ 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()) { @@ -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; @@ -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,73 +1009,49 @@ 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; @@ -1097,12 +1088,12 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett 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,7 +1878,7 @@ 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); @@ -1941,7 +1929,7 @@ void ex_function(exarg_T *eap) todo = (int)func_hashtab.ht_used; 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)) { continue; @@ -1974,7 +1962,7 @@ void ex_function(exarg_T *eap) todo = (int)func_hashtab.ht_used; 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 +1974,7 @@ void ex_function(exarg_T *eap) } } if (*p == '/') { - ++p; + p++; } eap->nextcmd = (char *)check_nextcmd(p); return; @@ -2007,7 +1995,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) { /* @@ -2064,7 +2052,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 +2096,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 +2109,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 +2179,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 +2221,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,7 +2302,7 @@ 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; @@ -2400,12 +2387,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 +2441,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 +2485,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 +2525,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 +2617,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 +2644,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 +2681,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) { @@ -2867,7 +2855,7 @@ void ex_return(exarg_T *eap) } if (eap->skip) { - ++emsg_skip; + emsg_skip++; } eap->nextcmd = NULL; @@ -2899,7 +2887,7 @@ void ex_return(exarg_T *eap) } if (eap->skip) { - --emsg_skip; + emsg_skip--; } } @@ -2932,7 +2920,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 +2975,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; } @@ -3145,8 +3133,7 @@ char *get_func_line(int c, void *cookie, int indent, bool do_concat) // 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, fp->uf_name, SOURCING_LNUM); fcp->dbg_tick = debug_tick; } if (do_profiling == PROF_YES) { @@ -3160,14 +3147,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 = (char_u *)xstrdup(((char **)(gap->ga_data))[fcp->linenr++]); + SOURCING_LNUM = fcp->linenr; if (do_profiling == PROF_YES) { func_line_start(cookie); } @@ -3175,11 +3162,10 @@ 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(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, fp->uf_name, SOURCING_LNUM); fcp->dbg_tick = debug_tick; } 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 ea1c3a8c4e..51123e1a85 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" diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 9d1ac185d4..bc8e823797 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" @@ -59,9 +63,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" @@ -300,7 +304,7 @@ void ex_align(exarg_T *eap) new_indent--; break; } - --new_indent; + new_indent--; } } } @@ -1091,12 +1095,12 @@ 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; } @@ -1198,7 +1202,7 @@ void do_bang(int addr_count, exarg_T *eap, bool forceit, bool do_in, bool do_out break; } } - ++p; + p++; } } while (trailarg != NULL); @@ -1442,7 +1446,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 +1464,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: @@ -2111,7 +2115,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) { @@ -2246,7 +2250,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(); @@ -2959,7 +2963,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 @@ -3015,7 +3019,7 @@ void ex_append(exarg_T *eap) vcol = 0; for (p = theline; indent > vcol; ++p) { if (*p == ' ') { - ++vcol; + vcol++; } else if (*p == TAB) { vcol += 8 - vcol % 8; } else { @@ -3044,7 +3048,7 @@ void ex_append(exarg_T *eap) } xfree(theline); - ++lnum; + lnum++; if (empty) { ml_delete(2L, false); @@ -3136,10 +3140,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 +3200,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 +3347,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 +3546,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 = (char *)skip_regexp((char_u *)cmd, delimiter, p_magic, &eap->arg); if (cmd[0] == delimiter) { // end delimiter found *cmd++ = NUL; // replace it with a NUL has_second_delim = true; @@ -4296,7 +4301,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 +4631,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 = (char *)skip_regexp((char_u *)cmd, delim, p_magic, &eap->arg); if (cmd[0] == delim) { // end delimiter found *cmd++ = NUL; // replace it with a NUL } @@ -4775,1124 +4780,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. /// 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..54315a6417 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -11,30 +11,27 @@ #include <stdbool.h> #include <string.h> +#include "nvim/arglist.h" #include "nvim/ascii.h" +#include "nvim/autocmd.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/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/mark.h" #include "nvim/mbyte.h" -#include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" #include "nvim/move.h" @@ -42,101 +39,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/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 +97,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! /// @@ -796,483 +444,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) { @@ -1524,51 +695,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 +758,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) { @@ -3007,7 +1152,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..e454a30028 100644 --- a/src/nvim/ex_cmds2.h +++ b/src/nvim/ex_cmds2.h @@ -3,8 +3,7 @@ #include <stdbool.h> -#include "nvim/ex_docmd.h" -#include "nvim/runtime.h" +#include "nvim/ex_cmds_defs.h" // // flags for check_changed() @@ -15,27 +14,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_docmd.c b/src/nvim/ex_docmd.c index 4ac9847e53..afa8a276c8 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -9,14 +9,17 @@ #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/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,6 +41,7 @@ #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" @@ -62,10 +66,10 @@ #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" @@ -100,16 +104,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; }; @@ -136,9 +138,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 +148,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); @@ -179,11 +179,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 +189,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 +204,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 +227,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 @@ -301,26 +296,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 +356,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 +366,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 +383,13 @@ 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_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 +401,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 +418,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 +453,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); + (char_u *)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((char_u *)fname, SOURCING_LNUM); // Find next breakpoint. *breakpoint = dbg_find_breakpoint(getline_equal(fgetline, cookie, getsourceline), - (char_u *)fname, sourcing_lnum); + (char_u *)fname, SOURCING_LNUM); *dbg_tick = debug_tick; } if (do_profiling == PROF_YES) { @@ -509,10 +497,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; } @@ -532,9 +518,7 @@ 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) { @@ -549,13 +533,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 +545,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 +579,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 +599,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); @@ -667,34 +642,27 @@ 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 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. if (cstack.cs_lflags & CSL_HAD_FINA) { cstack.cs_lflags &= ~CSL_HAD_FINA; report_make_pending((cstack.cs_pending[cstack.cs_idx] @@ -720,38 +688,34 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) // 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) || 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))); 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 a sourced file or executed function ran to its end, report the + // unclosed conditional. if (!got_int && !current_exception && ((getline_equal(fgetline, cookie, getsourceline) && !source_finished(fgetline, cookie)) @@ -768,18 +732,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); @@ -799,17 +761,13 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) // commands are executed. if (current_exception) { 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 +783,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 +803,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 @@ -880,25 +834,21 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) } 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")); + ? (char_u *)_("End of sourced file") + : (char_u *)_("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 +864,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 +898,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 +912,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 +920,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 +930,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 +957,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 +975,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 +1021,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 +1034,7 @@ static int current_tab_nr(tabpage_T *tab) int nr = 0; FOR_ALL_TABS(tp) { - ++nr; + nr++; if (tp == tab) { break; } @@ -1385,12 +1321,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,21 +1358,20 @@ 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; // 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 @@ -1449,13 +1383,13 @@ bool parse_cmdline(char *cmdline, exarg_T *eap, CmdParseInfo *cmdinfo, char **er 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; @@ -1719,7 +1653,7 @@ 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 @@ -1855,7 +1789,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 +1811,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 +1824,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 @@ -1941,7 +1875,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 +1949,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 +2128,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 +2228,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 +2266,7 @@ doend: ea.nextcmd = NULL; } - --ex_nesting_level; + ex_nesting_level--; return ea.nextcmd; } @@ -2368,8 +2302,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 +2333,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': @@ -2840,12 +2772,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 +2790,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 +2818,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 +2827,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 +2855,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 +2871,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 +2919,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 +2930,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 +3002,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 +3017,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; } @@ -3113,7 +3038,6 @@ int cmd_exists(const char *const name) /// "fullcommand" function void f_fullcommand(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - exarg_T ea; char *name = argvars[0].vval.v_string; rettv->v_type = VAR_STRING; @@ -3127,6 +3051,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; @@ -3175,14 +3100,10 @@ const char *set_one_cmd_context(expand_T *xp, const char *buff) return NULL; } - /* - * 3. parse a range specifier of the form: addr [,addr] [;addr] .. - */ + // 3. parse a range specifier of the form: addr [,addr] [;addr] .. cmd = (const char *)skip_range(cmd, &xp->xp_context); - /* - * 4. parse command - */ + // 4. parse command xp->xp_pattern = (char *)cmd; if (*cmd == NUL) { return NULL; @@ -3195,13 +3116,11 @@ const char *set_one_cmd_context(expand_T *xp, const char *buff) 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' - */ + // 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; @@ -3281,9 +3200,7 @@ const char *set_one_cmd_context(expand_T *xp, const char *buff) p++; } - /* - * 5. parse arguments - */ + // 5. parse arguments if (!IS_USER_CMDIDX(ea.cmdidx)) { ea.argt = cmdnames[(int)ea.cmdidx].cmd_argt; } @@ -3341,10 +3258,8 @@ const char *set_one_cmd_context(expand_T *xp, const char *buff) arg = (const char *)skipwhite(arg); } - /* - * Check for '|' to separate commands and '"' to start comments. - * Don't do this for ":read !cmd" and ":write !cmd". - */ + // 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 @@ -3392,18 +3307,15 @@ const char *set_one_cmd_context(expand_T *xp, const char *buff) } 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. - */ + // 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); + int c = utf_ptr2char(p); if (c == '\\' && p[1] != NUL) { p++; } else if (c == '`') { @@ -3412,13 +3324,9 @@ const char *set_one_cmd_context(expand_T *xp, const char *buff) 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)) { + // 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); @@ -3438,10 +3346,8 @@ const char *set_one_cmd_context(expand_T *xp, const char *buff) MB_PTR_ADV(p); } - /* - * If we are still inside the quotes, and we passed a space, just - * expand from there. - */ + // 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; } @@ -3450,7 +3356,7 @@ const char *set_one_cmd_context(expand_T *xp, const char *buff) // 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; + xp->xp_shell = true; #endif // When still after the command name expand executables. if (xp->xp_pattern == skipwhite(arg)) { @@ -3483,14 +3389,12 @@ const char *set_one_cmd_context(expand_T *xp, const char *buff) 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; + xp->xp_pattern++; } } } - /* - * 6. switch on command name - */ + // 6. switch on command name switch (ea.cmdidx) { case CMD_find: case CMD_sfind: @@ -3514,8 +3418,8 @@ const char *set_one_cmd_context(expand_T *xp, const char *buff) xp->xp_pattern = (char *)arg; break; - /* Command modifiers: return the argument. - * Also for commands with an argument that is a command. */ + // Command modifiers: return the argument. + // Also for commands with an argument that is a command. case CMD_aboveleft: case CMD_argdo: case CMD_belowright: @@ -3573,9 +3477,7 @@ const char *set_one_cmd_context(expand_T *xp, const char *buff) } return (const char *)find_nextcmd((char_u *)arg); - /* - * All completion for the +cmdline_compl feature goes here. - */ + // All completion for the +cmdline_compl feature goes here. case CMD_command: return set_context_in_user_cmd(xp, arg); @@ -3785,7 +3687,7 @@ const char *set_one_cmd_context(expand_T *xp, const char *buff) } 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, + return (const char *)set_context_in_map_cmd(xp, "map", (char_u *)arg, forceit, false, false, CMD_map); } @@ -3823,7 +3725,7 @@ const char *set_one_cmd_context(expand_T *xp, const char *buff) 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, + return (const char *)set_context_in_map_cmd(xp, (char *)cmd, (char_u *)arg, forceit, false, false, ea.cmdidx); case CMD_unmap: case CMD_nunmap: @@ -3834,7 +3736,7 @@ const char *set_one_cmd_context(expand_T *xp, const char *buff) 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, + return (const char *)set_context_in_map_cmd(xp, (char *)cmd, (char_u *)arg, forceit, false, true, ea.cmdidx); case CMD_mapclear: case CMD_nmapclear: @@ -3855,12 +3757,12 @@ const char *set_one_cmd_context(expand_T *xp, const char *buff) 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, + return (const char *)set_context_in_map_cmd(xp, (char *)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, + return (const char *)set_context_in_map_cmd(xp, (char *)cmd, (char_u *)arg, forceit, true, true, ea.cmdidx); case CMD_menu: case CMD_noremenu: @@ -3992,8 +3894,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 +3906,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) { @@ -4017,7 +3917,7 @@ char *skip_range(const char *cmd, int *ctx) } } if (*cmd != NUL) { - ++cmd; + cmd++; } } @@ -4055,17 +3955,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 +3999,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: @@ -4162,7 +4060,7 @@ static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, int goto error; } if (skip) { - ++cmd; + cmd++; } else { // Only accept a mark in another file when it is // used by itself: ":'M". @@ -4194,7 +4092,7 @@ static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, int if (skip) { // skip "/pat/" cmd = (char *)skip_regexp((char_u *)cmd, c, p_magic, NULL); if (*cmd == c) { - ++cmd; + cmd++; } } else { int flags; @@ -4234,7 +4132,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 +4163,7 @@ static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, int goto error; } } - ++cmd; + cmd++; break; default: @@ -4560,45 +4458,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); if (*errormsgp != NULL) { // error detected return FAIL; } @@ -4668,20 +4556,16 @@ 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); @@ -4695,15 +4579,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((char_u *)eap->arg); + } +#else backslash_halve((char_u *)eap->arg); +#endif if (has_wildcards) { expand_T xpc; @@ -4733,11 +4618,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 +4629,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 +4728,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,7 +4747,7 @@ static char *getargcmd(char **argp) /// Find end of "+command" argument. Skip over "\ " and "\\". /// -/// @param rembs TRUE to halve the number of backslashes +/// @param rembs true to halve the number of backslashes static char *skip_cmd_arg(char *p, int rembs) { while (*p && !ascii_isspace(*p)) { @@ -4874,7 +4755,7 @@ static char *skip_cmd_arg(char *p, int rembs) if (rembs) { STRMOVE(p, p + 1); } else { - ++p; + p++; } } MB_PTR_ADV(p); @@ -4905,7 +4786,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) { @@ -4964,7 +4844,7 @@ static int getargopt(exarg_T *eap) 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 +4869,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 +4880,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) { @@ -5223,9 +5102,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 +5145,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 +5351,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 +5387,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 +5394,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 +5466,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 +5473,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 +5493,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 +5509,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 +5624,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 +5692,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 +5726,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 +5796,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 @@ -6147,15 +5851,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 +5879,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 +5910,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; @@ -6286,12 +5983,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 +6017,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 +6064,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 +6082,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); cursor_correct(); - curwin->w_redr_status = TRUE; + curwin->w_redr_status = true; } } curwin = save_curwin; @@ -6425,9 +6116,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 +6124,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 +6147,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 { @@ -6685,17 +6376,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 +6503,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 +6516,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 +6534,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 +6596,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 +6625,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 +6716,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 +6739,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 +6758,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 +6768,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 +6797,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 +6829,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_cmdline = true; } update_screen(eap->forceit ? NOT_VALID - : VIsual_active ? INVERTED : 0); + : VIsual_active ? INVERTED : 0); if (need_maketitle) { maketitle(); } @@ -7180,7 +6864,7 @@ 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 { @@ -7245,8 +6929,6 @@ 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)) { @@ -7259,6 +6941,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 +6952,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 +7038,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 +7056,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 +7083,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 +7202,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 +7223,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 +7231,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 = (char *)skip_regexp((char_u *)eap->arg, '/', p_magic, NULL); if (*p) { *p++ = NUL; p = skipwhite(p); @@ -7686,7 +7365,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] = "#", @@ -7705,8 +7383,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); @@ -7725,6 +7403,7 @@ ssize_t find_cmdline_var(const char_u *src, size_t *usedlen) /// '<cexpr>' to C-expression under the cursor /// '<cfile>' to path name under the cursor /// '<sfile>' to sourced file name +/// '<stack>' to call stack /// '<slnum>' to sourced file line number /// '<afile>' to file name for autocommand /// '<abuf>' to buffer number for autocommand @@ -7746,12 +7425,9 @@ ssize_t find_cmdline_var(const char_u *src, size_t *usedlen) char_u *eval_vars(char_u *src, char_u *srcstart, size_t *usedlen, linenr_T *lnump, char **errormsg, int *escaped) { - 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 +7435,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 +7492,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 +7523,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 +7588,33 @@ 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; + case SPEC_STACK: // call stack + result = estack_sfile(spec_idx == SPEC_SFILE ? ESTACK_SFILE : ESTACK_STACK); if (result == NULL) { - *errormsg = _("E498: no :source file name to substitute for \"<sfile>\""); + *errormsg = spec_idx == SPEC_SFILE + ? _("E498: no :source file name to substitute for \"<sfile>\"") + : _("E489: no call stack to substitute for \"<stack>\""); 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,6 +7640,7 @@ 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)++; + char *s; if ((s = (char *)STRRCHR(result, '.')) != NULL && s >= path_tail(result)) { resultlen = (size_t)(s - result); @@ -7995,88 +7670,21 @@ 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; - - result = xstrdup(arg); + char *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); if (errormsg != NULL) { if (*errormsg) { emsg(errormsg); @@ -8088,8 +7696,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,9 +7715,7 @@ 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 = (char *)p_shada; if (*p_shada == NUL) { p_shada = (char_u *)"'100"; } @@ -8193,10 +7799,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 +7808,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) { @@ -8342,7 +7948,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..69d509abb7 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" @@ -151,8 +151,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 @@ -254,7 +254,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 +275,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; } @@ -389,7 +395,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 +475,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,8 +484,18 @@ 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; @@ -489,7 +505,7 @@ static int throw_exception(void *value, except_type_T type, char *cmdname) } 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 } @@ -499,7 +515,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 { @@ -542,7 +558,7 @@ static void discard_exception(except_T *excp, bool was_finished) } 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 } @@ -610,7 +626,7 @@ static void catch_exception(except_T *excp) } 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 } @@ -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 { @@ -732,12 +748,12 @@ static void report_pending(int action, int pending, void *value) if (debug_break_level > 0) { 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; } @@ -2038,7 +2054,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) { 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..c15d85967d 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -15,13 +15,16 @@ #include "nvim/api/private/helpers.h" #include "nvim/api/vim.h" #include "nvim/arabic.h" +#include "nvim/arglist.h" #include "nvim/ascii.h" #include "nvim/assert.h" #include "nvim/buffer.h" #include "nvim/charset.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" @@ -37,6 +40,8 @@ #include "nvim/garray.h" #include "nvim/getchar.h" #include "nvim/globals.h" +#include "nvim/grid.h" +#include "nvim/help.h" #include "nvim/highlight.h" #include "nvim/highlight_defs.h" #include "nvim/highlight_group.h" @@ -63,9 +68,9 @@ #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" @@ -143,7 +148,7 @@ struct cmdline_info { /// 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; @@ -195,6 +200,32 @@ typedef struct command_line_state { long *b_im_ptr; } CommandLineState; +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; + typedef struct cmdline_info CmdlineInfo; /// The current cmdline_info. It is initialized in getcmdline() and after that @@ -214,12 +245,6 @@ static Array cmdline_block = ARRAY_DICT_INIT; */ 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 @@ -243,26 +268,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,8 +299,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); + save_viewstate(curwin, &s->init_viewstate); + save_viewstate(curwin, &s->old_viewstate); } /// Completion for |:checkhealth| command. @@ -316,7 +341,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 +365,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); @@ -458,7 +483,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; @@ -521,8 +545,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 +580,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); @@ -665,7 +690,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 @@ -736,7 +761,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 +894,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 @@ -1664,7 +1689,7 @@ 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); + save_viewstate(curwin, &s->old_viewstate); update_screen(NOT_VALID); highlight_match = false; redrawcmdline(); @@ -1682,12 +1707,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 +1721,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 +1739,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; } @@ -2103,7 +2128,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 +2153,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 +2184,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 +2421,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 +2585,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 +2604,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,7 +2630,7 @@ 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; @@ -2512,44 +2642,13 @@ 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 + // Restore state. + cmdpreview_restore_state(&cpinfo); - 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); - } - - 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); @@ -2682,7 +2781,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; @@ -3592,7 +3691,7 @@ 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--; } } } @@ -3645,7 +3744,7 @@ void put_on_cmdline(char_u *str, int len, int redraw) static void save_cmdline(struct cmdline_info *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 } @@ -3669,7 +3768,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 +3801,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; @@ -4138,9 +4237,9 @@ char_u *ExpandOne(expand_T *xp, char_u *str, char_u *orig, int options, int mode if (findex == -1) { findex = xp->xp_numfiles; } - --findex; + findex--; } else { // mode == WILD_NEXT - ++findex; + findex++; } /* @@ -4880,7 +4979,7 @@ char_u *addstar(char_u *fname, size_t len, int context) && vim_strchr((char *)retval, '`') == NULL) { retval[len++] = '*'; } else if (len > 0 && retval[len - 1] == '$') { - --len; + len--; } retval[len] = NUL; } @@ -5028,58 +5127,6 @@ int expand_cmdline(expand_T *xp, char_u *str, int col, int *matchcount, char *** 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". @@ -5289,8 +5336,8 @@ static int ExpandFromContext(expand_T *xp, char_u *pat, int *num_file, char ***f { 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_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 }, @@ -5521,7 +5568,7 @@ static void expand_shellcmd(char_u *filepat, int *num_file, char ***file, int fl } } if (*e != NUL) { - ++e; + e++; } } *file = ga.ga_data; @@ -5674,163 +5721,9 @@ static int ExpandUserLua(expand_T *xp, int *num_file, char ***file) return OK; } -/// 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[]) -{ - *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 (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} -static int ExpandPackAddDir(char_u *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_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; - } - - // 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) +void globpath(char *path, char_u *file, garray_T *ga, int expand_options) { expand_T xpc; ExpandInit(&xpc); @@ -5841,7 +5734,7 @@ void globpath(char_u *path, char_u *file, garray_T *ga, int expand_options) // 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, ","); + copy_option_part(&path, (char *)buf, MAXPATHL, ","); if (STRLEN(buf) + STRLEN(file) + 2 < MAXPATHL) { add_pathsep((char *)buf); STRCAT(buf, file); // NOLINT @@ -5868,309 +5761,6 @@ void globpath(char_u *path, char_u *file, garray_T *ga, int expand_options) 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 -{ - 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 -{ - memset(hisptr, 0, sizeof(*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 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; -} - -/// 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 -{ - // 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; -} - -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) -{ - 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. - * "histype" may be one of the HIST_ values. - */ -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; -} - /// 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) @@ -6292,168 +5882,10 @@ int get_cmdline_type(void) 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; - } - - 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. - * "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 *)""; - } -} - -/// 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. - * "histype" may be one of the HIST_ values. - */ -int del_history_entry(int histype, char_u *str) +/// Return the first character of the current command line. +int get_cmdline_firstc(void) { - 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. - * "histype" may be one of the HIST_ values. - */ -int del_history_idx(int histype, int idx) -{ - 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 +5896,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 +5925,9 @@ int get_list_range(char_u **str, int *num1, int *num2) return OK; } -/* - * :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_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; - } - } - } - } -} - -/// Translate a history type number to the associated character -int hist_type2char(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; -} - void cmdline_init(void) { - memset(&ccline, 0, sizeof(struct cmdline_info)); + CLEAR_FIELD(ccline); } /// Open a window on the current command line and history. Allow editing in @@ -6688,18 +6011,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)); } } @@ -6904,90 +6227,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..2f6929924d 100644 --- a/src/nvim/ex_getln.h +++ b/src/nvim/ex_getln.h @@ -3,8 +3,6 @@ #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" // Values for nextwild() and ExpandOne(). See ExpandOne() for meaning. @@ -39,30 +37,8 @@ #define VSE_SHELL 1 ///< escape for a shell command #define VSE_BUFFER 2 ///< escape for a ":buffer" command -/// 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) - 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; - #ifdef INCLUDE_GENERATED_DECLARATIONS # include "ex_getln.h.generated.h" #endif diff --git a/src/nvim/ex_session.c b/src/nvim/ex_session.c index 1d670afa6d..9495ae1c4b 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" diff --git a/src/nvim/file_search.c b/src/nvim/file_search.c index ebefd1157c..2d09e7aa71 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" @@ -306,7 +307,7 @@ void *vim_findfile_init(char_u *path, char_u *filename, char_u *stopdirs, int le 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 @@ -583,7 +584,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) { @@ -824,9 +825,9 @@ 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; + suf = (char *)curbuf->b_p_sua; } for (;;) { // if file exists and we didn't already find it @@ -891,7 +892,7 @@ 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 { @@ -1401,11 +1402,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)) { @@ -1482,7 +1483,7 @@ 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) @@ -1496,7 +1497,7 @@ char_u *find_file_in_path_option(char_u *ptr, size_t len, int options, int first break; } assert(MAXPATHL >= l); - copy_option_part((char **)&buf, (char *)NameBuff + l, MAXPATHL - l, ","); + copy_option_part(&buf, (char *)NameBuff + l, MAXPATHL - l, ","); } } } @@ -1509,8 +1510,8 @@ char_u *find_file_in_path_option(char_u *ptr, size_t len, int options, int first if (first == TRUE) { // vim_findfile_free_visited can handle a possible NULL pointer vim_findfile_free_visited(fdip_search_ctx); - dir = path_option; - did_findfile_init = FALSE; + dir = (char *)path_option; + did_findfile_init = false; } for (;;) { @@ -1536,13 +1537,13 @@ 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; } diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index b98984017b..d29a0f0a08 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" @@ -48,7 +50,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" @@ -631,7 +632,7 @@ 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 return FAIL; @@ -1196,11 +1197,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 +1239,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 +1557,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 +1578,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 +1634,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; } @@ -1999,7 +2000,7 @@ static linenr_T readfile_linenr(linenr_T linecnt, char_u *p, char_u *endp) lnum = curbuf->b_ml.ml_line_count - linecnt + 1; for (s = p; s < endp; ++s) { if (*s == '\n') { - ++lnum; + lnum++; } } return lnum; @@ -2477,7 +2478,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; @@ -4819,8 +4820,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"); @@ -5122,7 +5123,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); } @@ -5580,14 +5581,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; @@ -5680,7 +5681,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 '.': @@ -5768,7 +5769,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..650977deac 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" diff --git a/src/nvim/fold.c b/src/nvim/fold.c index 8f26e03a94..8ce24fd378 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. @@ -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; @@ -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); 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 @@ -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, FunPtr fptr) +{ + foldclosed_both(argvars, rettv, false); +} + +/// "foldclosedend()" function +void f_foldclosedend(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + foldclosed_both(argvars, rettv, true); +} + +/// "foldlevel()" function +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 +void f_foldtext(typval_T *argvars, typval_T *rettv, FunPtr 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, FunPtr 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/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..a3af7dc372 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -15,7 +15,10 @@ #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_docmd.h" #include "nvim/ex_getln.h" @@ -40,7 +43,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" @@ -1336,7 +1338,7 @@ static void closescript(void) file_free(scriptin[curscript], false); scriptin[curscript] = NULL; if (curscript > 0) { - --curscript; + curscript--; } } @@ -1695,6 +1697,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(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 +void f_getchar(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + getchar_common(argvars, rettv); +} + +/// "getcharstr()" function +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 +void f_getcharmod(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = mod_mask; +} + typedef enum { map_result_fail, // failed, break loop map_result_get, // get a character from typeahead @@ -2251,7 +2396,7 @@ static int vgetorpeek(bool advance) return NUL; } - ++vgetc_busy; + vgetc_busy++; if (advance) { KeyStuffed = FALSE; @@ -2638,7 +2783,7 @@ static int vgetorpeek(bool advance) gotchars(nop_buf, 3); } - --vgetc_busy; + vgetc_busy--; return c; } diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 317423ffa0..954b62883e 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 @@ -248,9 +248,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 @@ -296,7 +293,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 +345,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; diff --git a/src/nvim/grid.c b/src/nvim/grid.c index 72e85c425d..f95ef3e705 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" @@ -780,3 +789,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..6a1bbcb089 100644 --- a/src/nvim/hardcopy.c +++ b/src/nvim/hardcopy.c @@ -17,12 +17,13 @@ #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,7 +32,7 @@ #include "nvim/os/input.h" #include "nvim/os/os.h" #include "nvim/path.h" -#include "nvim/screen.h" +#include "nvim/runtime.h" #include "nvim/strings.h" #include "nvim/syntax.h" #include "nvim/ui.h" @@ -292,8 +293,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,9 +316,9 @@ 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); @@ -333,8 +334,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 +343,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++; } } @@ -632,8 +633,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; @@ -734,7 +735,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; @@ -837,7 +838,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. @@ -900,7 +901,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 { @@ -1718,7 +1719,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), diff --git a/src/nvim/hashtab.c b/src/nvim/hashtab.c index 95ae7a152c..951e72ea52 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; } @@ -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 diff --git a/src/nvim/help.c b/src/nvim/help.c new file mode 100644 index 0000000000..172d389e74 --- /dev/null +++ b/src/nvim/help.c @@ -0,0 +1,1178 @@ +// 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/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" +#include "nvim/help.h" +#include "nvim/memory.h" +#include "nvim/option.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((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); +} + +/// ":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(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("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]", // 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 = (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); + } +} 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..c26b00df79 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,46 @@ 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) { 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)))); @@ -215,44 +221,76 @@ 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.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 +303,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 +314,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 +336,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 +356,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 +374,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 < (int)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 +481,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 +515,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, @@ -852,7 +930,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 +972,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..77424de3b8 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 /// @{ @@ -689,14 +691,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 +735,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 +753,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(NOT_VALID); + } + need_highlight_changed = true; } /// Handle ":highlight" command @@ -861,7 +869,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 +879,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,7 +890,7 @@ void do_highlight(const char *line, const bool forceit, const bool init) } hlgroup->sg_link = to_id; hlgroup->sg_script_ctx = current_sctx; - hlgroup->sg_script_ctx.sc_lnum += sourcing_lnum; + hlgroup->sg_script_ctx.sc_lnum += SOURCING_LNUM; nlua_set_sctx(&hlgroup->sg_script_ctx); hlgroup->sg_cleared = false; redraw_all_later(SOME_VALID); @@ -1272,7 +1280,7 @@ void do_highlight(const char *line, const bool forceit, const bool init) 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 @@ -1765,7 +1773,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; @@ -1788,11 +1796,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 +1816,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 +1833,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) { @@ -1863,7 +1884,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 +1934,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 +1957,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 +1972,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++) { 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..689d1fce0d 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" @@ -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); 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; } @@ -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++; } } diff --git a/src/nvim/indent.c b/src/nvim/indent.c index 271498d41a..f18a6d7b32 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" @@ -30,6 +31,313 @@ # 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_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 *end; + + if (strtol((char *)cp, &end, 10) <= 0) { + if (cp != (char_u *)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 "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) { @@ -769,10 +1077,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 ? 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..34a3de4f78 100644 --- a/src/nvim/indent_c.c +++ b/src/nvim/indent_c.c @@ -223,8 +223,8 @@ bool cin_is_cinword(const char_u *line) char_u *cinw_buf = xmalloc(cinw_len); line = (char_u *)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 = (char *)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; @@ -317,17 +317,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); @@ -519,8 +519,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 = (char *)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] != ':') { @@ -1601,8 +1601,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); @@ -1740,16 +1740,16 @@ void parse_cino(buf_T *buf) // Handle C #pragma directives buf->b_ind_pragma = 0; - for (p = buf->b_p_cino; *p;) { + for (p = (char *)buf->b_p_cino; *p;) { l = p++; 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 +1768,7 @@ void parse_cino(buf_T *buf) n += (sw * fraction + divider / 2) / divider; } } - ++p; + p++; } if (l[1] == '-') { n = -n; @@ -2052,7 +2052,7 @@ 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; @@ -2063,7 +2063,7 @@ int get_c_indent(void) *lead_start = NUL; *lead_middle = NUL; - p = curbuf->b_p_com; + p = (char *)curbuf->b_p_com; while (*p != NUL) { int align = 0; int off = 0; @@ -2071,11 +2071,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 +2084,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); @@ -3334,7 +3334,7 @@ int get_c_indent(void) amount += curbuf->b_ind_open_extra; } } - ++whilelevel; + whilelevel++; } /* * We are after a "normal" statement. @@ -3848,7 +3848,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/insexpand.c b/src/nvim/insexpand.c index a64d8e8f00..c525a49bc3 100644 --- a/src/nvim/insexpand.c +++ b/src/nvim/insexpand.c @@ -13,10 +13,12 @@ #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" #include "nvim/ex_docmd.h" +#include "nvim/ex_eval.h" #include "nvim/ex_getln.h" #include "nvim/fileio.h" #include "nvim/getchar.h" @@ -33,9 +35,8 @@ #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" @@ -2139,7 +2140,7 @@ 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++) { @@ -2473,7 +2474,7 @@ 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 char *e_cpt = ""; // 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 @@ -2502,8 +2503,7 @@ static int ins_compl_get_exp(pos_T *ini) } found_all = false; ins_buf = curbuf; - e_cpt = (compl_cont_status & CONT_LOCAL) - ? (char_u *)"." : curbuf->b_p_cpt; + e_cpt = (compl_cont_status & CONT_LOCAL) ? "." : (char *)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. @@ -2583,7 +2583,7 @@ static int ins_compl_get_exp(pos_T *ini) type = CTRL_X_THESAURUS; } if (*++e_cpt != ',' && *e_cpt != NUL) { - dict = e_cpt; + dict = (char_u *)e_cpt; dict_f = DICT_FIRST; } } else if (*e_cpt == 'i') { @@ -2600,7 +2600,7 @@ static int ins_compl_get_exp(pos_T *ini) } // in any case e_cpt is advanced to the next entry - (void)copy_option_part((char **)&e_cpt, (char *)IObuff, IOSIZE, ","); + (void)copy_option_part(&e_cpt, (char *)IObuff, IOSIZE, ","); found_all = true; if (type == -1) { 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..5d97f90bb1 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -15,12 +15,13 @@ #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_getln.h" #include "nvim/extmark.h" #include "nvim/func_attr.h" @@ -37,7 +38,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 +448,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) { @@ -1313,12 +1314,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 +1331,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); @@ -1906,7 +1906,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; 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..5a82ae30b5 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" 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/main.c b/src/nvim/main.c index 494ff0b4af..fd31ba6c66 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" @@ -57,10 +59,10 @@ #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" @@ -159,6 +161,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"); @@ -1242,8 +1245,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]; } @@ -1418,7 +1421,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; @@ -1613,9 +1616,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) { @@ -1677,8 +1680,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 +1698,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) { @@ -1782,7 +1785,7 @@ static void edit_buffers(mparm_T *parmp, char_u *cwd) 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 +1799,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 +1817,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"); } @@ -1841,7 +1844,7 @@ static void exe_commands(mparm_T *parmp) 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,7 +1853,7 @@ 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; @@ -2059,17 +2062,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..64a798a27b 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" @@ -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. @@ -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; @@ -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) \ @@ -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) { @@ -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) { @@ -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 1fe3327b29..593275d489 100644 --- a/src/nvim/mark.c +++ b/src/nvim/mark.c @@ -19,7 +19,6 @@ #include "nvim/eval.h" #include "nvim/ex_cmds.h" #include "nvim/extmark.h" -#include "nvim/fileio.h" #include "nvim/fold.h" #include "nvim/mark.h" #include "nvim/mbyte.h" @@ -86,7 +85,7 @@ void clear_fmark(fmark_T *fm) FUNC_ATTR_NONNULL_ALL { free_fmark(*fm); - memset(fm, 0, sizeof(*fm)); + CLEAR_POINTER(fm); } /* @@ -1741,7 +1740,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..1c34c9f004 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" @@ -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; diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c index 223b4d6845..af9e214d92 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)) { @@ -872,9 +890,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 +1006,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 +1183,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 +1200,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; } @@ -1587,7 +1606,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; @@ -1620,7 +1639,7 @@ int utf_head_off(const char_u *base, const char_u *p) // 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 +1660,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 +1819,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; @@ -2678,3 +2697,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, FunPtr 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(NOT_VALID); +} + +void f_charclass(typval_T *argvars, typval_T *rettv, FunPtr 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..216ee26620 100644 --- a/src/nvim/memfile.c +++ b/src/nvim/memfile.c @@ -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..23bc5d59c8 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. - */ - dirp = p_dir; + // Try all directories in the 'directory' option. + dirp = (char *)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; @@ -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 @@ -491,10 +489,8 @@ void ml_open_file(buf_T *buf) return; } - /* - * Try all directories in 'directory' option. - */ - dirp = p_dir; + // Try all directories in 'directory' option. + dirp = (char *)p_dir; bool found_existing_dir = false; for (;;) { if (*dirp == NUL) { @@ -503,8 +499,7 @@ 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 } @@ -1141,7 +1136,7 @@ void ml_recover(bool checkext) 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; } @@ -1210,10 +1205,10 @@ void ml_recover(bool checkext) 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 { @@ -1272,7 +1267,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 @@ -1299,12 +1294,12 @@ int recover_names(char_u *fname, int list, int nr, char_u **fname_out) // Do the loop for every directory in 'directory'. // First allocate some memory to put the directory name in. dir_name = xmalloc(STRLEN(p_dir) + 1); - dirp = p_dir; + dirp = (char *)p_dir; while (*dirp) { // 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) { @@ -1395,7 +1390,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) { @@ -1660,12 +1655,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; @@ -2184,7 +2179,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 +2219,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 @@ -2998,9 +2993,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; @@ -3304,7 +3299,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 +3334,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. diff --git a/src/nvim/memory.c b/src/nvim/memory.c index 5ae7f7bbe3..fb36d4ccf4 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" @@ -617,8 +618,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 +629,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 +643,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" diff --git a/src/nvim/menu.c b/src/nvim/menu.c index 16802a4e50..c3cf4457fc 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" @@ -952,7 +952,7 @@ 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; @@ -1659,26 +1659,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 +1807,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; } 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..684cf7207c 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" @@ -318,7 +321,7 @@ bool msg_attr_keep(const char *s, int attr, bool keep, bool multiline) if (entered >= 3) { return TRUE; } - ++entered; + entered++; // Add message to history (unless it's a repeated kept message or a // truncated message) @@ -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; } @@ -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,19 +619,19 @@ 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: @@ -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,7 +742,6 @@ 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 @@ -733,9 +752,8 @@ static bool emsg_multiline(const char *s, bool multiline) 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. @@ -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; } @@ -1964,13 +1995,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 +2154,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; @@ -2208,7 +2239,7 @@ 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(); @@ -2223,7 +2254,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) { @@ -2255,7 +2286,7 @@ static void msg_puts_display(const char_u *str, int maxlen, int attr, int recurs 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 +2303,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 { @@ -2306,7 +2337,7 @@ static void msg_puts_display(const char_u *str, int maxlen, int attr, int recurs s += l - 1; } } - ++s; + s++; } // Output any postponed text. @@ -2314,7 +2345,7 @@ static void msg_puts_display(const char_u *str, int maxlen, int attr, int recurs t_puts(&t_col, t_s, 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(); @@ -2457,7 +2488,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,7 +2499,7 @@ 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); @@ -2497,7 +2528,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; @@ -2654,7 +2685,7 @@ 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); if (mp->sb_eol || mp->sb_next == NULL) { @@ -2683,7 +2714,7 @@ static void t_puts(int *t_col, const char_u *t_s, const char_u *s, int attr) } if (msg_col >= Columns) { msg_col = 0; - ++msg_row; + msg_row++; } } @@ -2938,7 +2969,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 +3068,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++; } } } @@ -3339,7 +3370,7 @@ int redirecting(void) void verbose_enter(void) { if (*p_vfile != NUL) { - ++msg_silent; + msg_silent++; } } @@ -3358,7 +3389,7 @@ 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; @@ -3520,7 +3551,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 +3600,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 +3754,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; diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c index a4a521fa80..a8d0b3b584 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" @@ -386,7 +387,7 @@ retnomove: 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); } @@ -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) { diff --git a/src/nvim/move.c b/src/nvim/move.c index 6d4eb8ef49..1ed7acd012 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" @@ -114,7 +116,7 @@ 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); @@ -1056,7 +1058,7 @@ bool scrolldown(long line_count, int byfold) // 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; } @@ -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 { @@ -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; @@ -1726,7 +1728,7 @@ void scroll_cursor_halfway(int atend) } else { ++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) { diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 2d752eade7..c87c0cbb6e 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,11 +55,12 @@ #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" @@ -465,7 +471,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" @@ -1276,10 +1282,10 @@ static void normal_redraw(NormalState *s) if (VIsual_active) { redraw_curbuf_later(INVERTED); // update inverted part - update_screen(INVERTED); + 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(); } @@ -1832,7 +1838,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 ? INVERTED : VALID); + update_screen(0); setcursor(); ui_flush(); // Update before showing popup menu } @@ -2383,7 +2390,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 +2401,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 +2477,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; } @@ -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) { @@ -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); @@ -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; @@ -4272,7 +4279,7 @@ static void nv_ident(cmdarg_T *cap) 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) { + if (kp_help && *skipwhite(ptr) == NUL) { emsg(_(e_noident)); // found white space only return; } @@ -4288,9 +4295,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 +4334,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 +4371,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,12 +4383,14 @@ 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); } else { @@ -4406,7 +4415,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 +4427,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 +4442,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(); @@ -4842,7 +4851,7 @@ 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)); + CLEAR_FIELD(sia); i = do_search(cap->oap, dir, dir, pat, cap->count1, opt | SEARCH_OPT | SEARCH_ECHO | SEARCH_MSG, &sia); if (wrapped != NULL) { @@ -5044,15 +5053,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 @@ -6923,6 +6932,10 @@ static void nv_esc(cmdarg_T *cap) } } + if (restart_edit != 0) { + redraw_mode = true; // remove "-- (insert) --" + } + restart_edit = 0; if (cmdwin_type != 0) { diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 142a19e6d3..e121118fe9 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" @@ -25,7 +26,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" @@ -48,7 +48,6 @@ #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" @@ -1177,14 +1176,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); @@ -1459,7 +1458,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)) { @@ -1524,15 +1523,6 @@ static void stuffescaped(const char *arg, bool literally) } } -/// If "regname" is a special register, return true and store a pointer to its -/// value in "argp". -/// -/// @param allocated return: true when value was allocated -/// @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); - /// Converts a yankreg to a dict which can be used as an argument to the // userregfunc. static dict_T* yankreg_to_dict(yankreg_T* yankreg) { @@ -1606,14 +1596,14 @@ static int eval_yank_userreg(const char_u *ufn, int regname, yankreg_T *reg) return ret; } -// If "regname" is a special register, return true and store a pointer to its -// value in "argp". -bool get_spec_reg( - int regname, - char_u **argp, - bool *allocated, // return: true when value was allocated - bool errmsg // give error message when failing -) +/// If "regname" is a special register, return true and store a pointer to its +/// value in "argp". +/// +/// @param allocated return: true when value was allocated +/// @param errmsg give error message when failing +/// +/// @return true if "regname" is a special register, +bool get_spec_reg(int regname, char **argp, bool *allocated, bool errmsg) { size_t cnt; @@ -1624,15 +1614,15 @@ bool get_spec_reg( 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; @@ -1640,18 +1630,18 @@ bool get_spec_reg( if (last_cmdline == NULL && errmsg) { emsg(_(e_nolastcmd)); } - *argp = last_cmdline; + *argp = (char *)last_cmdline; return true; case '/': // last search-pattern 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)); @@ -1663,8 +1653,9 @@ bool get_spec_reg( 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; @@ -1676,7 +1667,7 @@ bool get_spec_reg( 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; @@ -1685,11 +1676,11 @@ bool get_spec_reg( 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; default: /* User-defined registers. */ @@ -2202,8 +2193,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; } @@ -3259,7 +3250,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; @@ -3268,7 +3259,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; @@ -3391,10 +3382,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'); @@ -3402,7 +3393,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; @@ -3413,7 +3404,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 @@ -3434,7 +3425,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) { @@ -3643,7 +3634,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) // block spaces = y_width + 1; for (int j = 0; j < yanklen; j++) { - spaces -= lbr_chartabsize(NULL, &y_array[i][j], 0); + spaces -= lbr_chartabsize(NULL, (char_u *)(&y_array[i][j]), 0); } if (spaces < 0) { spaces = 0; @@ -3744,12 +3735,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; @@ -3882,13 +3870,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; @@ -3972,8 +3960,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; @@ -4264,9 +4252,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) { @@ -4288,7 +4276,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; @@ -4303,7 +4291,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 @@ -4587,7 +4575,7 @@ static int same_leader(linenr_T lnum, int leader1_len, char_u *leader1_flags, in } } else { while (ascii_iswhite(line1[idx1])) { - ++idx1; + idx1++; } } } @@ -5000,7 +4988,7 @@ static int fmt_check_par(linenr_T lnum, int *leader_len, char_u **leader_flags, */ flags = *leader_flags; while (*flags && *flags != ':' && *flags != COM_END) { - ++flags; + flags++; } } @@ -5759,16 +5747,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); @@ -5863,11 +5851,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) { @@ -5889,7 +5877,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); } @@ -5977,7 +5965,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); } @@ -5991,7 +5979,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 { @@ -6033,9 +6021,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. @@ -6053,7 +6040,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); @@ -6076,7 +6063,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); @@ -6095,7 +6082,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. diff --git a/src/nvim/option.c b/src/nvim/option.c index ba77d12a3d..9fe007a453 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -27,13 +27,16 @@ #include <stdlib.h> #include <string.h> +#include "nvim/arglist.h" #include "nvim/ascii.h" #include "nvim/buffer.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" @@ -49,6 +52,7 @@ #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" @@ -66,12 +70,13 @@ #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/spell.h" #include "nvim/spellfile.h" +#include "nvim/spellsuggest.h" #include "nvim/strings.h" #include "nvim/syntax.h" #include "nvim/ui.h" @@ -82,7 +87,9 @@ #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" @@ -978,7 +985,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" @@ -1106,7 +1113,7 @@ int do_set(char *arg, int opt_flags) } flags = options[opt_idx].flags; - varp = get_varp_scope(&(options[opt_idx]), opt_flags); + varp = (char *)get_varp_scope(&(options[opt_idx]), opt_flags); } else { flags = P_STRING; } @@ -1188,7 +1195,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[ @@ -1250,8 +1257,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) { @@ -1310,7 +1316,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. @@ -1333,7 +1339,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 @@ -1382,23 +1388,16 @@ 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: @@ -1425,14 +1424,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) { @@ -1452,14 +1447,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++; } @@ -1993,7 +1985,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. @@ -2070,6 +2062,7 @@ void check_buf_options(buf_T *buf) 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_urf); check_string_option(&buf->b_p_keymap); check_string_option(&buf->b_p_gp); check_string_option(&buf->b_p_mp); @@ -2324,7 +2317,7 @@ static char *set_string_option(const int opt_idx, const char *const value, const /// 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_u *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++) { @@ -2344,25 +2337,6 @@ static bool valid_filetype(const char_u *val) 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) @@ -2441,7 +2415,7 @@ static char *did_set_string_option(int opt_idx, char_u **varp, char_u *oldval, c size_t errbuflen, int opt_flags, int *value_checked) { char *errmsg = NULL; - char_u *s, *p; + char *s, *p; int did_chartab = false; char_u **gvarp; bool free_oldval = (options[opt_idx].flags & P_ALLOCED); @@ -2530,7 +2504,7 @@ static char *did_set_string_option(int opt_idx, char_u **varp, char_u *oldval, c errmsg = check_colorcolumn(curwin); } else if (varp == &p_hlg) { // 'helplang' // Check for "", "ab", "ab,cd", etc. - for (s = p_hlg; *s != NUL; s += 3) { + 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; @@ -2578,18 +2552,7 @@ static char *did_set_string_option(int opt_idx, char_u **varp, char_u *oldval, c 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 + errmsg = check_chars_options(); } } else if (varp == &p_bg) { // 'background' if (check_opt_strings(p_bg, p_bg_values, false) == OK) { @@ -2647,9 +2610,9 @@ ambw_end: if (errmsg == NULL) { // canonize the value, so that STRCMP() can be used on it - p = enc_canonize(*varp); + p = (char *)enc_canonize(*varp); xfree(*varp); - *varp = p; + *varp = (char_u *)p; if (varp == &p_enc) { // only encoding=utf-8 allowed if (STRCMP(p_enc, "utf-8") != 0) { @@ -2661,9 +2624,9 @@ ambw_end: } } else if (varp == &p_penc) { // Canonize printencoding if VIM standard one - p = enc_canonize(p_penc); + p = (char *)enc_canonize(p_penc); xfree(p_penc); - p_penc = p; + p_penc = (char_u *)p; } else if (varp == &curbuf->b_p_keymap) { if (!valid_filetype(*varp)) { errmsg = e_invarg; @@ -2726,17 +2689,17 @@ ambw_end: errmsg = e_invarg; } } else if (gvarp == &p_mps) { // 'matchpairs' - for (p = *varp; *p != NUL; p++) { + for (p = (char *)(*varp); *p != NUL; p++) { int x2 = -1; int x3 = -1; - p += utfc_ptr2len((char *)p); + p += utfc_ptr2len(p); if (*p != NUL) { - x2 = *p++; + x2 = (unsigned char)(*p++); } if (*p != NUL) { - x3 = utf_ptr2char((char *)p); - p += utfc_ptr2len((char *)p); + x3 = utf_ptr2char(p); + p += utfc_ptr2len(p); } if (x2 != ':' || x3 == -1 || (*p != NUL && *p != ',')) { errmsg = e_invarg; @@ -2747,7 +2710,7 @@ ambw_end: } } } else if (gvarp == &p_com) { // 'comments' - for (s = *varp; *s;) { + for (s = (char *)(*varp); *s;) { while (*s && *s != ':') { if (vim_strchr(COM_ALL, *s) == NULL && !ascii_isdigit(*s) && *s != '-') { @@ -2770,7 +2733,7 @@ ambw_end: } s++; } - s = skip_to_option_part(s); + s = (char *)skip_to_option_part((char_u *)s); } } else if (varp == &p_lcs) { // global 'listchars' errmsg = set_chars_option(curwin, varp, false); @@ -2826,7 +2789,7 @@ ambw_end: // 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;) { + for (s = (char *)p_shada; *s;) { // Check it's a valid character if (vim_strchr("!\"%'/:<@cfhnrs", *s) == NULL) { errmsg = illegal_char(errbuf, errbuflen, *s); @@ -2871,8 +2834,8 @@ ambw_end: errmsg = N_("E528: Must specify a ' value"); } } else if (gvarp == &p_sbr) { // 'showbreak' - for (s = *varp; *s;) { - if (ptr2cells((char *)s) != 1) { + for (s = (char *)(*varp); *s;) { + if (ptr2cells(s) != 1) { errmsg = N_("E595: 'showbreak' contains unprintable or wide character"); } MB_PTR_ADV(s); @@ -2996,13 +2959,13 @@ ambw_end: if (varp == &p_ruf) { // reset ru_wid first ru_wid = 0; } - s = *varp; + s = (char *)(*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); + wid = getdigits_int(&s, true, 0); if (wid && *s == '(' && (errmsg = check_stl_option((char *)p_ruf)) == NULL) { ru_wid = wid; } else { @@ -3010,7 +2973,7 @@ ambw_end: } } 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); + errmsg = check_stl_option(s); } if (varp == &p_ruf && errmsg == NULL) { comp_col(); @@ -3021,7 +2984,7 @@ ambw_end: } } else if (gvarp == &p_cpt) { // check if it is a valid value for 'complete' -- Acevedo - for (s = *varp; *s;) { + for (s = (char *)(*varp); *s;) { while (*s == ',' || *s == ' ') { s++; } @@ -3090,11 +3053,11 @@ ambw_end: p = NULL; (void)replace_termcodes((char *)p_pt, STRLEN(p_pt), - (char **)&p, REPTERM_FROM_PART | REPTERM_DO_LT, NULL, + &p, REPTERM_FROM_PART | REPTERM_DO_LT, NULL, CPO_TO_CPO_FLAGS); if (p != NULL) { free_string_option(p_pt); - p_pt = p; + p_pt = (char_u *)p; } } } else if (varp == &p_bs) { // 'backspace' @@ -3113,10 +3076,10 @@ ambw_end: unsigned int *flags; if (opt_flags & OPT_LOCAL) { - p = curbuf->b_p_tc; + p = (char *)curbuf->b_p_tc; flags = &curbuf->b_tc_flags; } else { - p = p_tc; + p = (char *)p_tc; flags = &tc_flags; } @@ -3124,7 +3087,7 @@ ambw_end: // 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) { + || opt_strings_flags((char_u *)p, p_tc_values, flags, false) != OK) { errmsg = e_invarg; } } else if (varp == &p_cmp) { // 'casemap' @@ -3150,10 +3113,10 @@ ambw_end: foldUpdateAll(curwin); } } else if (gvarp == &curwin->w_allbuf_opt.wo_fmr) { // 'foldmarker' - p = (char_u *)vim_strchr((char *)(*varp), ','); + p = vim_strchr((char *)(*varp), ','); if (p == NULL) { errmsg = N_("E536: comma required"); - } else if (p == *varp || p[1] == NUL) { + } else if ((char_u *)p == *varp || p[1] == NUL) { errmsg = e_invarg; } else if (foldmethodIsMarker(curwin)) { foldUpdateAll(curwin); @@ -3198,7 +3161,7 @@ ambw_end: } } else if (varp == &p_csqf) { if (p_csqf != NULL) { - p = p_csqf; + p = (char *)p_csqf; while (*p != NUL) { if (vim_strchr(CSQF_CMDS, *p) == NULL || p[1] == NUL @@ -3310,22 +3273,22 @@ ambw_end: // Options that are a list of flags. p = NULL; if (varp == &p_ww) { // 'whichwrap' - p = (char_u *)WW_ALL; + p = WW_ALL; } if (varp == &p_shm) { // 'shortmess' - p = (char_u *)SHM_ALL; + p = (char *)SHM_ALL; } else if (varp == (char_u **)&(p_cpo)) { // 'cpoptions' - p = (char_u *)CPO_VI; + p = CPO_VI; } else if (varp == &(curbuf->b_p_fo)) { // 'formatoptions' - p = (char_u *)FO_ALL; + p = FO_ALL; } else if (varp == &curwin->w_p_cocu) { // 'concealcursor' - p = (char_u *)COCU_ALL; + p = COCU_ALL; } else if (varp == &p_mouse) { // 'mouse' - p = (char_u *)MOUSE_ALL; + p = MOUSE_ALL; } if (p != NULL) { - for (s = *varp; *s; s++) { - if (vim_strchr((char *)p, *s) == NULL) { + for (s = (char *)(*varp); *s; s++) { + if (vim_strchr(p, *s) == NULL) { errmsg = illegal_char(errbuf, errbuflen, *s); break; } @@ -3360,7 +3323,7 @@ ambw_end: && ((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); + p = (char *)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) { @@ -3423,14 +3386,14 @@ ambw_end: * Use the first name in 'spelllang' up to '_region' or * '.encoding'. */ - for (p = q; *p != NUL; p++) { + for (p = (char *)q; *p != NUL; p++) { if (!ASCII_ISALNUM(*p) && *p != '-') { break; } } - if (p > q) { + if (p > (char *)q) { vim_snprintf((char *)fname, sizeof(fname), "spell/%.*s.vim", - (int)(p - q), q); + (int)(p - (char *)q), q); source_runtime((char *)fname, DIP_ALL); } } @@ -3450,12 +3413,6 @@ ambw_end: 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 @@ -3486,356 +3443,12 @@ int check_signcolumn(char_u *val) 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.colorcol, "colorcol", NUL }, - { &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) @@ -3906,63 +3519,30 @@ char *check_stl_option(char *s) 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,27 +3553,15 @@ 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; } @@ -4009,7 +3577,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 }; @@ -5667,7 +5235,7 @@ static int put_setstring(FILE *fd, char *cmd, char *name, char_u **valuep, uint6 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; @@ -5703,14 +5271,14 @@ static int put_setstring(FILE *fd, char *cmd, char *name, char_u **valuep, uint6 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; } @@ -5771,47 +5339,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) { @@ -6157,6 +5684,7 @@ static char_u *get_varp(vimoption_T *p) return (char_u *)&(curwin->w_p_cocu); case PV_COLE: return (char_u *)&(curwin->w_p_cole); + case PV_AI: return (char_u *)&(curbuf->b_p_ai); case PV_BIN: @@ -6317,7 +5845,7 @@ void win_copy_options(win_T *wp_from, win_T *wp_to) { copy_winopt(&wp_from->w_onebuf_opt, &wp_to->w_onebuf_opt); copy_winopt(&wp_from->w_allbuf_opt, &wp_to->w_allbuf_opt); - didset_window_options(wp_to); + didset_window_options(wp_to, true); } /// Copy the options from one winopt_T to another. @@ -6442,7 +5970,7 @@ void clear_winopt(winopt_T *wop) clear_string_option((char_u **)&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); @@ -6451,7 +5979,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; } @@ -6514,7 +6042,7 @@ 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 @@ -7569,313 +7097,6 @@ 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) diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c index eaa56ffe63..98ef4bd0f6 100644 --- a/src/nvim/os/env.c +++ b/src/nvim/os/env.c @@ -10,7 +10,6 @@ #include "nvim/charset.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" @@ -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; } @@ -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])) { diff --git a/src/nvim/os/fs.c b/src/nvim/os/fs.c index 901a1bc5a6..0d62a5f5f9 100644 --- a/src/nvim/os/fs.c +++ b/src/nvim/os/fs.c @@ -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 @@ -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..dd44cb1ce7 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" 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..caea11debd 100644 --- a/src/nvim/path.c +++ b/src/nvim/path.c @@ -227,7 +227,7 @@ char_u *get_past_head(const char_u *path) #endif while (vim_ispathsep(*retval)) { - ++retval; + retval++; } return (char_u *)retval; @@ -666,8 +666,8 @@ static size_t do_path_expand(garray_T *gap, const char_u *path, size_t wildoff, for (p = buf + wildoff; p < s; ++p) { if (rem_backslash(p)) { STRMOVE(p, p + 1); - --e; - --s; + e--; + s--; } } @@ -695,11 +695,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); @@ -742,9 +742,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); @@ -1141,7 +1141,7 @@ static int expand_in_path(garray_T *const gap, char_u *const pattern, const int if (flags & EW_ADDSLASH) { glob_flags |= WILD_ADD_SLASH; } - globpath(paths, pattern, gap, glob_flags); + globpath((char *)paths, pattern, gap, glob_flags); xfree(paths); return gap->ga_len; @@ -1401,7 +1401,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 +1409,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++; } } @@ -1656,12 +1656,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 "../" diff --git a/src/nvim/popupmnu.c b/src/nvim/popupmenu.c index 4accddfce0..92db2db735 100644 --- a/src/nvim/popupmnu.c +++ b/src/nvim/popupmenu.c @@ -1,12 +1,10 @@ // 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) -#include "nvim/popupmnu.h" - #include <assert.h> #include <inttypes.h> #include <stdbool.h> @@ -15,9 +13,11 @@ #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" @@ -25,8 +25,7 @@ #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/syntax.h" @@ -59,7 +58,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 #define PUM_DEF_WIDTH 15 @@ -472,7 +471,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; @@ -492,7 +491,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; @@ -663,11 +662,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; @@ -800,12 +799,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; @@ -819,7 +818,7 @@ 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 @@ -928,7 +927,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) { @@ -1051,7 +1050,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..d4f3756f4d 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 = (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); + } +} + +/// 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; +} + +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 = (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); +} + +/// 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((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"); + } + } +} + +/// 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..1c416a872b 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" @@ -37,7 +40,6 @@ #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" @@ -1224,7 +1226,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; @@ -3119,7 +3121,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; } @@ -5299,7 +5301,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)); @@ -5779,7 +5781,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 +6151,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; @@ -7157,3 +7159,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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr fptr) +{ + set_qf_ll_list(NULL, argvars, rettv); +} diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c index fbbf904f8b..b7ec4bf94e 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,16 +481,14 @@ 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_u *skip_regexp(char_u *startp, int dirc, int magic, char **newp) { int mymagic; char_u *p = startp; @@ -516,8 +514,8 @@ 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); - p = *newp + (p - startp); + *newp = (char *)vim_strsave(startp); + p = (char_u *)(*newp) + (p - startp); } STRMOVE(p, p + 1); } else { @@ -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) { @@ -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..769d2ceeef 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; 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..554def5b8a 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++; } } diff --git a/src/nvim/runtime.c b/src/nvim/runtime.c index 5d28d624fe..edcaa27e2b 100644 --- a/src/nvim/runtime.c +++ b/src/nvim/runtime.c @@ -7,20 +7,155 @@ #include "nvim/api/private/helpers.h" #include "nvim/ascii.h" +#include "nvim/autocmd.h" #include "nvim/charset.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/ex_getln.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> and ESTACK_STACK for <stack>. +char *estack_sfile(estack_arg_T which) +{ + 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); + } + + // 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; @@ -90,10 +225,10 @@ int do_in_path(char_u *path, char *name, int flags, DoInRuntimepathCB callback, } // Loop over all entries in 'runtimepath'. - char_u *rtp = rtp_copy; + char *rtp = (char *)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. @@ -114,12 +249,11 @@ int do_in_path(char_u *path, char *name, int flags, DoInRuntimepathCB callback, tail = (char_u *)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 "); + copy_option_part(&np, (char *)tail, (size_t)(MAXPATHL - (tail - (char_u *)buf)), "\t "); if (p_verbose > 10) { verbose_enter(); @@ -248,11 +382,11 @@ int do_in_cached_path(char_u *name, int flags, DoInRuntimepathCB callback, void tail = buf + STRLEN(buf); // Loop over all patterns in "name" - char_u *np = name; + char *np = (char *)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, (char *)tail, (size_t)(MAXPATHL - (tail - buf)), "\t "); if (p_verbose > 10) { verbose_enter(); @@ -991,6 +1125,160 @@ void ex_packadd(exarg_T *eap) } } +/// Expand color scheme, compiler or filetype names. +/// Search from 'runtimepath': +/// 'runtimepath'/{dirnames}/{pat}.vim +/// When "flags" has DIP_START: search also from 'start' of 'packpath': +/// 'packpath'/pack/ * /start/ * /{dirnames}/{pat}.vim +/// When "flags" has DIP_OPT: search also from 'opt' of 'packpath': +/// 'packpath'/pack/ * /opt/ * /{dirnames}/{pat}.vim +/// When "flags" has DIP_LUA: search also performed for .lua files +/// "dirnames" is an array with one or more directory names. +int ExpandRTDir(char_u *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_u *s = xmalloc(size); + snprintf((char *)s, size, "%s/%s*.vim", dirnames[i], pat); + globpath((char *)p_rtp, s, &ga, 0); + if (flags & DIP_LUA) { + snprintf((char *)s, size, "%s/%s*.lua", dirnames[i], pat); + globpath((char *)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((char *)p_pp, s, &ga, 0); + if (flags & DIP_LUA) { + snprintf((char *)s, size, "pack/*/start/*/%s/%s*.lua", dirnames[i], pat); // NOLINT + globpath((char *)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((char *)p_pp, s, &ga, 0); + if (flags & DIP_LUA) { + snprintf((char *)s, size, "start/*/%s/%s*.lua", dirnames[i], pat); // NOLINT + globpath((char *)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((char *)p_pp, s, &ga, 0); + if (flags & DIP_LUA) { + snprintf((char *)s, size, "pack/*/opt/*/%s/%s*.lua", dirnames[i], pat); // NOLINT + globpath((char *)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((char *)p_pp, s, &ga, 0); + if (flags & DIP_LUA) { + snprintf((char *)s, size, "opt/*/%s/%s*.lua", dirnames[i], pat); // NOLINT + globpath((char *)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 (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_u *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_u *s = xmalloc(buflen); + snprintf((char *)s, buflen, "pack/*/opt/%s*", pat); // NOLINT + globpath((char *)p_pp, s, &ga, 0); + snprintf((char *)s, buflen, "opt/%s*", pat); // NOLINT + globpath((char *)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; + } + + // 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 @@ -1282,3 +1570,901 @@ 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((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); + } +} + +/// ":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_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; + +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]; + 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_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 *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; + + // 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, (char *)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, (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; + } + // 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((char *)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 = (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; +} + +/// ":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; +} diff --git a/src/nvim/runtime.h b/src/nvim/runtime.h index d83ec00185..a255c6c096 100644 --- a/src/nvim/runtime.h +++ b/src/nvim/runtime.h @@ -3,7 +3,76 @@ #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_arg_T; + +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; + +/// 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 6f4400e531..b343b167f8 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -1,629 +1,52 @@ // 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/cursor.h" -#include "nvim/cursor_shape.h" -#include "nvim/decoration.h" -#include "nvim/decoration_provider.h" -#include "nvim/diff.h" -#include "nvim/edit.h" #include "nvim/eval.h" -#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/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/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; - } - - // 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); - } +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'"); - 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 { @@ -646,20 +69,6 @@ bool conceal_cursor_line(const win_T *wp) 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); - } -} - /// Whether cursorline is drawn in a special way /// /// If true, both old and new cursorline will need to be redrawn when moving cursor within windows. @@ -669,1081 +78,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 +117,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 +142,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 +156,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 +170,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 +179,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,2634 +234,43 @@ 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; - // Draw the colorcolumn character. - c = wp->w_p_fcs_chars.colorcol; - schar_from_char(linebuf_char[off], c); - } - - 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) +/// 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) { - status_redraw_buf(curbuf); -} + int len = 0; -/// 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; + int emenu = (xp->xp_context == EXPAND_MENUS + || xp->xp_context == EXPAND_MENUNAMES); - 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); - } + // Check for menu separators - replace with '|'. + if (emenu && menu_is_separator((char *)s)) { + return 1; } -} -/* - * 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(); + while (*s != NUL) { + s += skip_status_match_char(xp, s); + len += ptr2cells((char *)s); + MB_PTR_ADV(s); } + + return len; } -/* - * Redraw all status lines at the bottom of frame "frp". - */ +/// Redraw all status lines at the bottom of frame "frp". void win_redraw_last_status(const frame_T *frp) FUNC_ATTR_NONNULL_ARG(1) { @@ -4615,131 +290,8 @@ void win_redraw_last_status(const frame_T *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) -{ - int len = 0; - - int emenu = (xp->xp_context == EXPAND_MENUS - || xp->xp_context == EXPAND_MENUNAMES); - - // Check for menu separators - replace with '|'. - if (emenu && menu_is_separator((char *)s)) { - return 1; - } - - while (*s != NUL) { - s += skip_status_match_char(xp, s); - len += ptr2cells((char *)s); - MB_PTR_ADV(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. - */ +/// 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) { if ((rem_backslash(s) && xp->xp_context != EXPAND_HELP) @@ -4864,7 +416,7 @@ void win_redr_status_matches(expand_T *xp, int num_matches, char **matches, int len += l; clen += l; } else { - for (; *s != NUL; ++s) { + for (; *s != NUL; s++) { s += skip_status_match_char(xp, s); clen += ptr2cells((char *)s); if ((l = utfc_ptr2len((char *)s)) > 1) { @@ -4891,7 +443,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; @@ -4944,190 +496,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 @@ -5152,77 +520,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 @@ -5266,7 +563,7 @@ bool get_keymap_str(win_T *wp, char *fmt, char *buf, int len) /// 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) +void win_redr_custom(win_T *wp, bool draw_winbar, bool draw_ruler) { static bool entered = false; int attr; @@ -5290,9 +587,9 @@ static void win_redr_custom(win_T *wp, bool draw_winbar, bool draw_ruler) 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. */ + // 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; } @@ -5389,14 +686,14 @@ static void win_redr_custom(win_T *wp, bool draw_winbar, bool draw_ruler) goto theend; } - /* Temporarily reset 'cursorbind', we don't want a side effect from moving - * the cursor away and back. */ + // 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; + 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. */ + // 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, @@ -5417,9 +714,7 @@ static void win_redr_custom(win_T *wp, bool draw_winbar, bool draw_ruler) } buf[len] = NUL; - /* - * Draw each snippet with the specified highlighting. - */ + // Draw each snippet with the specified highlighting. grid_puts_line_start(grid, row); curattr = attr; @@ -5483,134 +778,23 @@ 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; - - char* title = wp->w_float_config.title; - size_t n_title = wp->w_float_config.n_title; - stl_hlrec_t* title_hl = wp->w_float_config.title_hl; - - int m8[MAX_MCO + 1]; - int cc; - int len; - int t_attr = title_hl != NULL && title_hl->userhl - ? syn_id2attr(title_hl->userhl) - : 0; - t_attr = hl_combine_attr(attrs[1], t_attr); - - int title_pos = 2; - switch (wp->w_float_config.title_pos) { - case kTitleLeft: - title_pos = 2; - break; - case kTitleRight: - title_pos = icol - 2 - vim_strsize(title); - break; - case kTitleCenter: - title_pos = (icol - vim_strsize(title)) / 2 - 1; - break; - } - title_pos = title_pos < 2 ? 2 : title_pos; - - 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++) { - schar_T ch; - int attr; - // Draw the title if in the correct position. - if (i > title_pos && n_title > 0 && i < icol - 2) { - cc = utfc_ptr2char((char_u*) title, m8); - len = utfc_ptr2len(title); - n_title -= len; - title += len; - - while (title_hl != NULL && - (title_hl + 1)->start != NULL && - (title_hl + 1)->start < title) { - ++ title_hl; - t_attr = hl_combine_attr(attrs[1], syn_id2attr(-title_hl->userhl)); - } - - schar_from_cc(ch, cc, m8); - attr = t_attr; - } else { - memcpy(ch, chars[1], sizeof(schar_T)); - attr = attrs[1]; - } - grid_put_schar(grid, 0, i + adj[3], ch, attr); - } - 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; } } @@ -5630,105 +814,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. @@ -5745,67 +830,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) { @@ -5835,9 +859,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) { @@ -5859,126 +883,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; @@ -5999,12 +926,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; @@ -6128,7 +1051,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 @@ -6138,7 +1061,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 @@ -6150,6 +1073,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 @@ -6167,14 +1093,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; @@ -6216,19 +1141,13 @@ static void recording_mode(int attr) { msg_puts_attr(_("recording"), attr); if (!shortmess(SHM_RECORDING)) { - char s[10]; - int len = (*utf_char2len)(reg_recording); - utf_char2bytes(reg_recording, s + 2); - s[0] = ' '; - s[1] = '@'; - s[len + 2] = 0; + char s[4]; + snprintf(s, ARRAY_SIZE(s), " @%c", reg_recording); msg_puts_attr(s, attr); } } -/* - * Draw the tab pages line at the top of the Vim window. - */ +/// Draw the tab pages line at the top of the Vim window. void draw_tabline(void) { int tabcount = 0; @@ -6246,8 +1165,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; @@ -6281,7 +1199,7 @@ void draw_tabline(void) did_emsg |= saved_did_emsg; } else { FOR_ALL_TABS(tp) { - ++tabcount; + tabcount++; } if (tabcount > 0) { @@ -6403,7 +1321,7 @@ 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); @@ -6450,10 +1368,6 @@ void ui_ext_tabline_update(void) arena_mem_free(arena_finish(&arena), &ui_ext_fixblk); } -/* - * 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) { @@ -6464,9 +1378,7 @@ 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". - */ +/// Get the character to use in a status line. Get its attributes in "*attr". int fillchar_status(int *attr, win_T *wp) { int fill; @@ -6494,7 +1406,7 @@ int fillchar_status(int *attr, win_T *wp) /// 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; @@ -6502,59 +1414,26 @@ 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(); - } -} - -static void win_redr_ruler(win_T *wp, bool always) +void win_redr_ruler(win_T *wp, bool always) { bool is_stl_global = global_stl_height() > 0; static bool did_show_ext_ruler = false; @@ -6564,10 +1443,8 @@ static void win_redr_ruler(win_T *wp, bool always) return; } - /* - * Check if cursor.lnum is valid, since win_redr_ruler() may be called - * after deleting lines, before cursor.lnum is corrected. - */ + // 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; } @@ -6595,9 +1472,7 @@ static void win_redr_ruler(win_T *wp, bool always) empty_line = true; } - /* - * Only draw the ruler when something changed. - */ + // Only draw the ruler when something changed. validate_virtcol_win(wp); if (redraw_cmdline || always @@ -6651,23 +1526,19 @@ static void win_redr_ruler(win_T *wp, bool always) #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. - */ + // 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); + (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). - */ + // 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); @@ -6731,11 +1602,49 @@ static void win_redr_ruler(win_T *wp, bool always) } } -/* - * 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. - */ +#define COL_RULER 17 // columns needed by standard ruler + +/// 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) +{ + 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); +} + +/// 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; @@ -6757,7 +1666,7 @@ int number_width(win_T *wp) n = 0; do { lnum /= 10; - ++n; + n++; } while (lnum > 0); // 'numberwidth' gives the minimal width plus one @@ -6776,155 +1685,283 @@ 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(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); - - *left_col = 0; - *right_col = width1; + char_u *s = *p; - 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; + 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; } - // 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((char *)s); + int c = mb_cptr2char_adv((const char_u **)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 &curwin->w_p_lcs or &curwin->w_p_fcs +/// @return error message, NULL if it's OK. +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; + + // 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", '~' }, + { &wp->w_p_fcs_chars.colorcol, "colorcol", ' ' }, + }; + 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 (width < 0 || height < 0) { // just checking... - return; + 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 (State == MODE_HITRETURN || State == MODE_SETWSIZE) { - // postpone the resizing - State = MODE_SETWSIZE; - return; - } + // 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; - /* 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; - } + 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; + } - resizing_screen = 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; + } + } + } + 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; + } + } + } - 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; + 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++; + } + } } - height = Rows; - width = Columns; - p_lines = Rows; - p_columns = Columns; - ui_call_grid_resize(1, width, height); - - send_grid_resize = true; - /// 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(). + return NULL; // no error +} - if (State != MODE_ASKMORE && State != MODE_EXTERNCMD && State != MODE_CONFIRM) { - screenclear(); +/// 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 (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(); - } - } + 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. @@ -6945,13 +1982,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..c820817a71 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" @@ -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); @@ -431,7 +433,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); } } @@ -513,7 +515,7 @@ void last_pat_prog(regmmatch_T *regmatch) } ++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. @@ -1036,7 +1038,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; @@ -1123,13 +1125,13 @@ 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; + ps = (char_u *)strcopy; p = skip_regexp(pat, search_delim, p_magic, &strcopy); - if (strcopy != ps) { + 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 @@ -1160,9 +1162,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++; } } @@ -1426,7 +1428,7 @@ 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) { @@ -2136,10 +2138,10 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) } 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 @@ -2342,7 +2344,7 @@ int check_linecomment(const char_u *line) && !is_pos_in_string(line, (colnr_T)(p - line))) { break; } - ++p; + p++; } } @@ -2629,7 +2631,7 @@ bool findpar(bool *pincl, int dir, long count, int what, int both) } setpcmark(); if (both && *ml_get(curr) == '}') { // include line with '}' - ++curr; + curr++; } curwin->w_cursor.lnum = curr; if (curr == curbuf->b_ml.ml_line_count && what != '}') { @@ -2667,7 +2669,7 @@ static int inmacro(char_u *opt, char_u *s) && (s[0] == NUL || s[1] == NUL || s[1] == ' ')))) { break; } - ++macro; + macro++; if (macro[0] == NUL) { break; } @@ -3110,7 +3112,7 @@ int current_word(oparg_T *oap, long count, int include, int bigword) oap->start = start_pos; oap->motion_type = kMTCharWise; } - --count; + count--; } /* @@ -3160,7 +3162,7 @@ int current_word(oparg_T *oap, long count, int include, int bigword) } } } - --count; + count--; } if (include_white && (cls() != 0 @@ -3323,7 +3325,7 @@ extend: } else { ncount = count; if (start_blank) { - --ncount; + ncount--; } } if (ncount > 0) { @@ -3851,7 +3853,7 @@ extend: break; } } - --start_lnum; + start_lnum--; } /* @@ -3859,13 +3861,13 @@ extend: */ end_lnum = start_lnum; while (end_lnum <= curbuf->b_ml.ml_line_count && linewhite(end_lnum)) { - ++end_lnum; + end_lnum++; } end_lnum--; i = (int)count; if (!include && white_in_front) { - --i; + i--; } while (i--) { if (end_lnum == curbuf->b_ml.ml_line_count) { @@ -3877,14 +3879,12 @@ extend: } if (include || !do_white) { - ++end_lnum; - /* - * skip to end of paragraph - */ + 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; + end_lnum++; } } @@ -3898,7 +3898,7 @@ extend: if (include || do_white) { while (end_lnum < curbuf->b_ml.ml_line_count && linewhite(end_lnum + 1)) { - ++end_lnum; + end_lnum++; } } } @@ -3909,7 +3909,7 @@ extend: */ if (!white_in_front && !linewhite(end_lnum) && include) { while (start_lnum > 1 && linewhite(start_lnum - 1)) { - --start_lnum; + start_lnum--; } } @@ -3983,7 +3983,7 @@ static int find_prev_quote(char_u *line, int col_start, int quotechar, char_u *e if (escape != NULL) { while (col_start - n > 0 && vim_strchr((char *)escape, line[col_start - n - 1]) != NULL) { - ++n; + n++; } } if (n & 1) { @@ -4169,11 +4169,11 @@ bool current_quote(oparg_T *oap, long count, bool include, int quotechar) if (include) { if (ascii_iswhite(line[col_end + 1])) { while (ascii_iswhite(line[col_end + 1])) { - ++col_end; + col_end++; } } else { while (col_start > 0 && ascii_iswhite(line[col_start - 1])) { - --col_start; + col_start--; } } } @@ -4551,7 +4551,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; @@ -5467,7 +5467,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(" "); } @@ -5509,11 +5509,11 @@ 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]; @@ -5561,7 +5561,7 @@ 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; @@ -5860,7 +5860,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 +5948,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; } @@ -5996,7 +5996,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 +6012,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 cd3b967a9f..0927473be0 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" @@ -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; @@ -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; } diff --git a/src/nvim/sign.c b/src/nvim/sign.c index 7b6b55fede..f1ddbfd147 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" @@ -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'. 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..1e44d328b3 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -56,18 +56,6 @@ // 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> @@ -84,15 +72,12 @@ #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/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" @@ -108,20 +93,14 @@ #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/spellsuggest.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) - // Result values. Lower number is accepted over higher one. #define SP_BANNED (-1) #define SP_RARE 0 @@ -136,104 +115,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 +170,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 +184,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. @@ -374,7 +227,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 @@ -639,13 +492,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 +536,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 +549,8 @@ static void find_word(matchinf_T *mip, int mode) if (ptr[wlen] != ' ' && ptr[wlen] != TAB) { break; } - ++wlen; - --flen; + wlen++; + flen--; } } } @@ -708,7 +561,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]; @@ -1050,7 +903,7 @@ 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; @@ -1072,7 +925,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,37 +954,11 @@ 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; @@ -1154,7 +981,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 +993,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,8 +1015,8 @@ 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; @@ -1282,8 +1109,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; @@ -1332,8 +1159,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--; } } @@ -1368,7 +1195,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 +1204,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)) { @@ -1581,7 +1408,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 +1436,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) { @@ -1903,38 +1730,6 @@ 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) @@ -2016,7 +1811,7 @@ static int count_syllables(slang_T *slang, const char_u *word) } } if (len != 0) { // found a match, count syllable - ++cnt; + cnt++; skip = false; } else { // No recognized syllable item, at least a syllable char then? @@ -2038,7 +1833,7 @@ static int count_syllables(slang_T *slang, const char_u *word) 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 +1845,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; @@ -2080,9 +1875,9 @@ char *did_set_spelllang(win_T *wp) 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); @@ -2204,8 +1999,8 @@ char *did_set_spelllang(win_T *wp) // round 1: load first name in 'spellfile'. // round 2: load second name in 'spellfile. // etc. - spf = curwin->w_s->b_p_spf; - for (round = 0; round == 0 || *spf != NUL; ++round) { + spf = (char *)curwin->w_s->b_p_spf; + for (round = 0; round == 0 || *spf != NUL; round++) { if (round == 0) { // Internal wordlist, if there is one. if (int_wordlist == NULL) { @@ -2214,7 +2009,7 @@ 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. @@ -2338,7 +2133,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); } @@ -2444,51 +2239,6 @@ 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) { @@ -2547,36 +2297,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. @@ -2610,9 +2330,9 @@ void clear_spell_chartab(spelltab_T *sp) { int i; - // Init everything to false. - memset(sp->st_isw, false, sizeof(sp->st_isw)); - memset(sp->st_isu, false, sizeof(sp->st_isu)); + // Init everything to false (zero). + CLEAR_FIELD(sp->st_isw); + CLEAR_FIELD(sp->st_isu); for (i = 0; i < 256; i++) { sp->st_fold[i] = (char_u)i; @@ -2665,7 +2385,7 @@ 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; @@ -2783,298 +2503,9 @@ 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; @@ -3176,10 +2607,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 +2626,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 +2648,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 +2672,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 +2688,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 +2703,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. @@ -6128,12 +2901,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 @@ -6203,12 +2976,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; @@ -6339,499 +3112,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) { @@ -7005,7 +3285,7 @@ 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 { @@ -7038,7 +3318,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 +3343,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--; } } } @@ -7201,7 +3481,7 @@ static linenr_T dump_prefixes(slang_T *slang, char_u *word, char_u *pat, Directi 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. @@ -7224,7 +3504,7 @@ static linenr_T dump_prefixes(slang_T *slang, char_u *word, char_u *pat, Directi (c & WF_RAREPFX) ? (flags | WF_RARE) : flags, lnum); if (lnum != 0) { - ++lnum; + lnum++; } } @@ -7240,7 +3520,7 @@ static linenr_T dump_prefixes(slang_T *slang, char_u *word, char_u *pat, Directi (c & WF_RAREPFX) ? (flags | WF_RARE) : flags, lnum); if (lnum != 0) { - ++lnum; + lnum++; } } } @@ -7327,3 +3607,71 @@ 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_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. +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; +} + +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; + char_u *re; + + 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 + 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; + } + } + + vim_regfree(rp); + return NULL; +} diff --git a/src/nvim/spell_defs.h b/src/nvim/spell_defs.h index 222d103f5d..61f722b9ee 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 @@ -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..be1373f617 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" @@ -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) { @@ -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,7 +840,7 @@ 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. @@ -853,12 +855,12 @@ static void tree_count_words(char_u *byts, idx_T *idxs) // 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; @@ -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; @@ -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 == '#') { @@ -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) { @@ -2300,18 +2301,15 @@ 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++] = (char *)getroom_save(spin, items[1]); + ((char **)(gap->ga_data))[gap->ga_len++] = (char *)getroom_save(spin, items[2]); } } else if (is_aff_rule(items, itemcnt, "SYLLABLE", 2) && syllable == NULL) { @@ -2387,7 +2385,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; @@ -2809,7 +2807,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 +2943,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 +2966,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 +2975,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 +2988,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) { @@ -3072,7 +3070,7 @@ static void spell_free_aff(afffile_T *aff) todo = (int)ht->ht_used; 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); @@ -3142,7 +3140,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 +3148,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 @@ -3186,7 +3184,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; } @@ -3227,7 +3225,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 +3360,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 +3390,7 @@ static void get_compflags(afffile_T *affile, char_u *afflist, char_u *store_affl } } if (affile->af_flagtype == AFT_NUM && *p == ',') { - ++p; + p++; } } @@ -3438,7 +3436,7 @@ static int store_aff_word(spellinfo_T *spin, char_u *word, char_u *afflist, afff todo = (int)ht->ht_used; 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 @@ -3686,7 +3684,7 @@ static int spell_read_wordfile(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 == '#') { @@ -3696,7 +3694,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 @@ -3719,7 +3717,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"), @@ -3802,13 +3800,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; } @@ -4146,8 +4144,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,7 +4171,7 @@ 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 } @@ -4248,7 +4246,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); @@ -4581,7 +4579,7 @@ static int write_vim_spell(spellinfo_T *spin, char_u *fname) if (round == 2) { // <word> fwv &= fwrite(hi->hi_key, l, 1, fd); } - --todo; + todo--; } } if (round == 1) { @@ -4644,8 +4642,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 +4653,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> @@ -4782,7 +4780,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. @@ -5012,7 +5010,7 @@ 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. @@ -5033,8 +5031,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. @@ -5282,7 +5280,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; @@ -5815,7 +5813,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) { diff --git a/src/nvim/spellsuggest.c b/src/nvim/spellsuggest.c new file mode 100644 index 0000000000..b4a9bed437 --- /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 = (char *)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(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 = 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]; + 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(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(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(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", // 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(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); +} + +/// 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/strings.c b/src/nvim/strings.c index 22effaade0..78312c738c 100644 --- a/src/nvim/strings.c +++ b/src/nvim/strings.c @@ -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; @@ -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 } diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index 4ec4a57d68..47b5647a08 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" @@ -41,8 +42,8 @@ #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 +379,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(); } @@ -723,7 +724,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 +732,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; @@ -1029,7 +1030,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; } @@ -1500,7 +1501,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(); } } @@ -2095,9 +2096,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) { @@ -2506,7 +2507,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; } @@ -2582,7 +2583,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 +2591,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; } @@ -3653,7 +3654,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); @@ -3927,7 +3928,7 @@ static void syn_clear_keyword(int id, hashtab_T *ht) if (HASHITEM_EMPTY(hi)) { continue; } - --todo; + todo--; kp_prev = NULL; for (kp = HI2KE(hi); kp != NULL;) { if (kp->k_syn.id == id) { @@ -3967,7 +3968,7 @@ static void clear_keywtab(hashtab_T *ht) todo = (int)ht->ht_used; 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); @@ -4257,7 +4258,7 @@ static void syn_cmd_include(exarg_T *eap, int syncing) } if (arg[0] == '@') { - ++arg; + arg++; rest = get_group_name(arg, &group_name_end); if (rest == NULL) { emsg(_("E397: Filename required")); @@ -4453,7 +4454,7 @@ static void syn_cmd_match(exarg_T *eap, int syncing) // get the pattern. init_syn_patterns(); - memset(&item, 0, sizeof(item)); + CLEAR_FIELD(item); rest = get_syn_pattern(rest, &item); if (vim_regcomp_had_eol() && !(syn_opt_arg.flags & HL_EXCLUDENL)) { syn_opt_arg.flags |= HL_HAS_EOL; @@ -4583,7 +4584,7 @@ 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)); @@ -4708,8 +4709,8 @@ static void syn_cmd_region(exarg_T *eap, int syncing) 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; } @@ -4945,7 +4946,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; @@ -5048,7 +5049,7 @@ static void init_syn_patterns(void) */ static char_u *get_syn_pattern(char_u *arg, synpat_T *ci) { - char_u *end; + char *end; int *p; int idx; char *cpo_save; @@ -5058,13 +5059,13 @@ 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 = (char *)skip_regexp(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; @@ -5081,7 +5082,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 +5107,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 +5118,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 +5136,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 (char_u *)skipwhite(end); } /* @@ -5144,7 +5145,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; @@ -5157,21 +5158,21 @@ static void syn_cmd_sync(exarg_T *eap, int syncing) } while (!ends_excmd(*arg_start)) { - arg_end = skiptowhite(arg_start); - next_arg = (char_u *)skipwhite((char *)arg_end); + arg_end = (char *)skiptowhite(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 = (char *)skiptowhite(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 +5181,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; 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; @@ -5215,16 +5216,16 @@ static void syn_cmd_sync(exarg_T *eap, int syncing) 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 = (char *)skip_regexp(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); + 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 @@ -5241,7 +5242,7 @@ 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) { @@ -5401,7 +5402,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 != ',') { @@ -5477,7 +5478,7 @@ static int in_id_list(stateitem_T *cur_si, int16_t *list, struct sp_syn *ssp, in // 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, @@ -5541,9 +5542,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 +5615,7 @@ void ex_syntax(exarg_T *eap) } xfree(subcmd_name); if (eap->skip) { - --emsg_skip; + emsg_skip--; } } @@ -5624,8 +5625,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) diff --git a/src/nvim/tag.c b/src/nvim/tag.c index 5b799be381..f212aefbfc 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -14,16 +14,17 @@ #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" -#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" @@ -40,7 +41,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" @@ -465,7 +466,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; } @@ -653,13 +654,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; } @@ -1076,9 +1077,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 } @@ -1320,7 +1321,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; }); @@ -1411,7 +1412,7 @@ int find_tags(char_u *pat, int *num_matches, char ***matchesp, int flags, int mi 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; @@ -1476,7 +1477,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]); } @@ -1520,7 +1521,7 @@ 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; @@ -1612,7 +1613,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++; } } } @@ -1860,7 +1861,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) { @@ -2088,9 +2089,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 +2123,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 +2141,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 +2167,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 @@ -2258,29 +2258,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; } } @@ -2342,7 +2342,7 @@ int get_tagfname(tagname_T *tnp, int first, char_u *buf) char_u *r_ptr; if (first) { - memset(tnp, 0, sizeof(tagname_T)); + CLEAR_POINTER(tnp); } if (curbuf->b_help) { @@ -2353,7 +2353,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); } @@ -2373,13 +2373,12 @@ int get_tagfname(tagname_T *tnp, int first, char_u *buf) simplify_filename(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,9 +2386,8 @@ 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) ? curbuf->b_p_tags : p_tags); + tnp->tn_np = (char *)tnp->tn_tags; } /* @@ -2420,7 +2418,7 @@ 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, (char *)buf, MAXPATHL - 1, " ,"); r_ptr = vim_findfile_stopdir(buf); // move the filename one char forward and truncate the @@ -2478,7 +2476,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 +2487,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; @@ -2720,7 +2718,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 @@ -3171,7 +3169,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); @@ -3250,13 +3248,13 @@ 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) { @@ -3266,7 +3264,7 @@ int get_tags(list_T *list, char_u *pat, char_u *buf_fname) } else { // Skip field without colon. while (*p != NUL && *p >= ' ') { - ++p; + p++; } } if (*p == NUL) { 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..844a79b33d 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" @@ -69,7 +70,6 @@ #include "nvim/move.h" #include "nvim/option.h" #include "nvim/os/input.h" -#include "nvim/screen.h" #include "nvim/state.h" #include "nvim/terminal.h" #include "nvim/ui.h" @@ -681,7 +681,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 +702,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); diff --git a/src/nvim/testdir/runtest.vim b/src/nvim/testdir/runtest.vim index 6b16e888a9..fcd3d5724c 100644 --- a/src/nvim/testdir/runtest.vim +++ b/src/nvim/testdir/runtest.vim @@ -257,6 +257,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 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..716511210d 100644 --- a/src/nvim/testdir/test_autocmd.vim +++ b/src/nvim/testdir/test_autocmd.vim @@ -2614,6 +2614,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() @@ -2715,6 +2737,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_bufline.vim b/src/nvim/testdir/test_bufline.vim index ffb8e3facd..579d3a5eb5 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, setbufline(bufnr('$') + 1, 1, ['x'])) call assert_equal(0, setbufline(b, 4, ['d', 'e'])) call assert_equal(['c'], b->getbufline(3)) call assert_equal(['d'], getbufline(b, 4)) diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim index 7aac731709..b9f027afb2 100644 --- a/src/nvim/testdir/test_cmdline.vim +++ b/src/nvim/testdir/test_cmdline.vim @@ -126,6 +126,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 @@ -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') @@ -1392,14 +1440,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 +1452,22 @@ func Test_cmdwin_tabpage() tabclose! 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 diff --git a/src/nvim/testdir/test_diffmode.vim b/src/nvim/testdir/test_diffmode.vim index 1dbbe578c5..ea453b7174 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 diff --git a/src/nvim/testdir/test_edit.vim b/src/nvim/testdir/test_edit.vim index a09346a595..e26bbdc5be 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 @@ -1620,6 +1604,7 @@ func Test_edit_InsertLeave_undo() bwipe! au! InsertLeave call delete('XtestUndo') + call delete(undofile('XtestUndo')) set undofile& endfunc @@ -1687,11 +1672,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 +1686,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 +1760,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_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_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_func.vim b/src/nvim/testdir/test_expand_func.vim index b48c2e8a19..df01d84f19 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() diff --git a/src/nvim/testdir/test_filechanged.vim b/src/nvim/testdir/test_filechanged.vim index c6e781a1ef..b77f02afd1 100644 --- a/src/nvim/testdir/test_filechanged.vim +++ b/src/nvim/testdir/test_filechanged.vim @@ -242,6 +242,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..e3a8370661 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'], @@ -360,7 +361,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 +442,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'], diff --git a/src/nvim/testdir/test_functions.vim b/src/nvim/testdir/test_functions.vim index c11e7b4fea..44b6f0373e 100644 --- a/src/nvim/testdir/test_functions.vim +++ b/src/nvim/testdir/test_functions.vim @@ -1769,6 +1769,15 @@ func Test_char2nr() call assert_equal(12354, char2nr('あ', 1)) 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() call assert_equal(0, eventhandler()) endfunc 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_listdict.vim b/src/nvim/testdir/test_listdict.vim index aa66d86af1..2f4e1db4a1 100644 --- a/src/nvim/testdir/test_listdict.vim +++ b/src/nvim/testdir/test_listdict.vim @@ -649,6 +649,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_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..347404a579 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() @@ -491,6 +467,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 +642,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 +662,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 +747,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 +853,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 +884,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 +944,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 +1270,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 @@ -1420,6 +1484,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 +1940,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 +1984,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 +2138,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 +2989,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 +3011,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 +3083,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 +3305,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 +3399,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..fdfc1c0f89 100644 --- a/src/nvim/testdir/test_options.vim +++ b/src/nvim/testdir/test_options.vim @@ -368,9 +368,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 @@ -812,11 +820,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_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_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_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_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_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_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_vimscript.vim b/src/nvim/testdir/test_vimscript.vim index de4629451b..0f204cdd0c 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 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..e70e9f2cbd 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. 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..477102276c 100644 --- a/src/nvim/types.h +++ b/src/nvim/types.h @@ -28,6 +28,8 @@ 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..da671a3ad1 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" @@ -663,6 +663,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_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..75a09b244c 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,7 +680,7 @@ 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; } @@ -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; } @@ -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); } } @@ -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 + 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 { @@ -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 @@ -3080,10 +2963,8 @@ void u_saveline(linenr_T lnum) curbuf->b_u_line_ptr = 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,7 +2991,7 @@ void u_undoline(void) return; } - oldp = u_save_line(curbuf->b_u_line_lnum); + char_u *oldp = u_save_line(curbuf->b_u_line_lnum); ml_replace(curbuf->b_u_line_lnum, (char *)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), @@ -3123,7 +2999,7 @@ void u_undoline(void) xfree(curbuf->b_u_line_ptr); curbuf->b_u_line_ptr = 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..59b8d10200 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" @@ -167,7 +169,7 @@ char *find_ucmd(exarg_T *eap, char *p, int *full, expand_T *xp, int *complp) xp->xp_luaref = uc->uc_compl_luaref; xp->xp_arg = (char *)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. @@ -889,7 +891,7 @@ int uc_add_command(char *name, size_t name_len, const char *rep, uint32_t 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_luaref = compl_luaref; @@ -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) { diff --git a/src/nvim/version.c b/src/nvim/version.c index 3ffae6592c..0667243bc3 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" @@ -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; diff --git a/src/nvim/vim.h b/src/nvim/vim.h index 31ac5a67ff..09b949bb20 100644 --- a/src/nvim/vim.h +++ b/src/nvim/vim.h @@ -199,6 +199,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 @@ -274,8 +275,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/window.c b/src/nvim/window.c index 39346faa14..2d995af00d 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" @@ -43,7 +48,6 @@ #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 +126,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; @@ -483,14 +487,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 +522,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; @@ -698,14 +702,14 @@ 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); return wp; @@ -728,13 +732,15 @@ void win_set_minimal_style(win_T *wp) : 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); - } + + // TODO(bfredl): this could use a highlight namespace directly, + // and avoid pecularities around window options + 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); + parse_winhl_opt(wp); // signcolumn: use 'auto' if (wp->w_p_scl[0] != 'a' || STRLEN(wp->w_p_scl) >= 8) { @@ -789,7 +795,7 @@ 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); + win_set_inner_size(wp, true); must_redraw = MAX(must_redraw, VALID); wp->w_pos_changed = true; @@ -1270,7 +1276,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); } /* @@ -1670,7 +1676,7 @@ int win_count(void) int count = 0; FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - ++count; + count++; } return count; } @@ -2459,7 +2465,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 +2502,7 @@ void close_windows(buf_T *buf, bool keep_curwin) } } - --RedrawingDisabled; + RedrawingDisabled--; redraw_tabline = true; if (h != tabline_height()) { @@ -3825,7 +3831,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) { @@ -4098,7 +4104,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; @@ -4244,7 +4250,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; } @@ -4259,7 +4265,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; } @@ -4521,7 +4527,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 @@ -4868,10 +4874,17 @@ static void win_enter_ext(win_T *const wp, const int flags) redraw_later(curwin, 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, NOT_VALID); + } + if (prevwin) { + if (prevwin->w_hl_attr_normal != prevwin->w_hl_attr_normalnc) { + redraw_later(prevwin, NOT_VALID); + } } // set window height to desired minimal value @@ -5040,6 +5053,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; @@ -5180,7 +5195,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); } } @@ -5556,7 +5571,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 @@ -5919,6 +5934,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; @@ -5969,6 +5991,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; @@ -6024,7 +6048,7 @@ 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); showmode(); @@ -6167,7 +6191,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) @@ -6230,7 +6254,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--; @@ -6276,7 +6300,7 @@ void scroll_to_fraction(win_T *wp, int prev_height) 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) { @@ -6290,7 +6314,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. @@ -6309,7 +6333,7 @@ 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?? @@ -6318,11 +6342,13 @@ void win_set_inner_size(win_T *wp) 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); } @@ -6351,7 +6377,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; @@ -6386,6 +6412,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) { @@ -6470,17 +6509,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); } @@ -6566,7 +6605,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)); } @@ -6577,7 +6616,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) { @@ -6743,9 +6782,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) @@ -6761,7 +6802,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) { @@ -6782,7 +6823,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; } } @@ -7100,7 +7141,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; @@ -7233,6 +7274,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 = (char *)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) { 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..72a03c409a 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) @@ -3829,5 +3841,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/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/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/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..163ded43f9 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,18 @@ 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>') + ]]) + 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')) + os.remove(test_file) end) @@ -153,13 +205,15 @@ 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>') ]]) command('edit '..test_file) @@ -167,6 +221,9 @@ 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')) + os.remove(test_file) 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/helpers.lua b/test/functional/helpers.lua index 8c5a60657a..981cfc306e 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() 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/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/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/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/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..99f69ef556 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -1145,6 +1145,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/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 946129b082..c5f882a831 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) @@ -1788,6 +1776,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") @@ -1821,7 +1810,7 @@ describe("'winhighlight' highlight", function() ]]) end) - it('handles invalid values', function() + it('handles undefined groups', function() command("set winhl=Normal:Background1") screen:expect([[ {1:^ }| @@ -1834,19 +1823,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:~ }| | - ]], unchanged=true} + ]]} + 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:~ }| + | + ]]} end) it('works local to the window', function() @@ -2271,4 +2285,191 @@ 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) +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..2cff7c1cf4 100644 --- a/test/functional/ui/messages_spec.lua +++ b/test/functional/ui/messages_spec.lua @@ -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/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/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/winbar_spec.lua b/test/functional/ui/winbar_spec.lua index 60fa10da87..8976c4371f 100644 --- a/test/functional/ui/winbar_spec.lua +++ b/test/functional/ui/winbar_spec.lua @@ -579,47 +579,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/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/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 index b8b8a435bc..b3c3718035 100644 --- a/test/unit/option_spec.lua +++ b/test/unit/option_spec.lua @@ -5,7 +5,6 @@ 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)) @@ -27,26 +26,3 @@ describe('check_ff_value', 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) |