diff options
447 files changed, 29663 insertions, 26177 deletions
diff --git a/.builds/freebsd.yml b/.builds/freebsd.yml index 36f44f236b..bb7fbeb78b 100644 --- a/.builds/freebsd.yml +++ b/.builds/freebsd.yml @@ -20,7 +20,7 @@ sources: environment: SOURCEHUT: 1 LANG: en_US.UTF-8 - CMAKE_EXTRA_FLAGS: -DTRAVIS_CI_BUILD=ON -DMIN_LOG_LEVEL=3 + CMAKE_EXTRA_FLAGS: -DCI_BUILD=ON -DMIN_LOG_LEVEL=3 tasks: - build-deps: | diff --git a/.builds/openbsd.yml b/.builds/openbsd.yml index 5fa6556066..2f0f970dcb 100644 --- a/.builds/openbsd.yml +++ b/.builds/openbsd.yml @@ -19,7 +19,7 @@ sources: environment: SOURCEHUT: 1 LC_CTYPE: en_US.UTF-8 - CMAKE_EXTRA_FLAGS: -DTRAVIS_CI_BUILD=ON -DMIN_LOG_LEVEL=3 + CMAKE_EXTRA_FLAGS: -DCI_BUILD=ON -DMIN_LOG_LEVEL=3 tasks: - build-deps: | diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 50ef4e6897..90ba2a6d7a 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1 +1,2 @@ -custom: https://salt.bountysource.com/teams/neovim +github: neovim +open_collective: neovim diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml new file mode 100644 index 0000000000..f72500efe7 --- /dev/null +++ b/.github/workflows/linux.yml @@ -0,0 +1,141 @@ +name: Linux CI +on: [push, pull_request] +env: + # Set "false" to force rebuild of third-party dependencies. + CACHE_ENABLE: true + DEPS_CMAKE_FLAGS: "-DUSE_BUNDLED_GPERF=OFF" + # default target name for functional tests + FUNCTIONALTEST: functionaltest + CI_TARGET: tests + # Environment variables for ccache + CCACHE_COMPRESS: 1 + CCACHE_SLOPPINESS: "time_macros,file_macro" + # Default since 3.3; required with newer gcc/clang. + CCACHE_CPP2: 1 + +jobs: + ASAN: + runs-on: ubuntu-latest + env: + CC: clang-11 + steps: + - uses: actions/checkout@v2 + + - name: Setup commom environment variables + run: | + echo "$HOME/.local/bin" >> $GITHUB_PATH + + echo "CI_BUILD_DIR=$GITHUB_WORKSPACE" >> $GITHUB_ENV + echo "BUILD_DIR=$GITHUB_WORKSPACE/build" >> $GITHUB_ENV + echo "DEPS_BUILD_DIR=$HOME/nvim-deps" >> $GITHUB_ENV + echo "INSTALL_PREFIX=$HOME/nvim-install" >> $GITHUB_ENV + echo "LOG_DIR=$GITHUB_WORKSPACE/build/log" >> $GITHUB_ENV + echo "NVIM_LOG_FILE=$GITHUB_WORKSPACE/build/.nvimlog" >> $GITHUB_ENV + echo "CMAKE_FLAGS=-DCI_BUILD=ON -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX:PATH=$HOME/nvim-install -DBUSTED_OUTPUT_TYPE=nvim -DDEPS_PREFIX=$HOME/nvim-deps/usr -DMIN_LOG_LEVEL=3" >> $GITHUB_ENV + echo "ASAN_OPTIONS=detect_leaks=1:check_initialization_order=1:log_path=$GITHUB_WORKSPACE/build/log/asan" >> $GITHUB_ENV + echo "TSAN_OPTIONS=log_path=$GITHUB_WORKSPACE/build/log/tsan" >> $GITHUB_ENV + echo "UBSAN_OPTIONS=print_stacktrace=1 log_path=$GITHUB_WORKSPACE/build/log/ubsan" >> $GITHUB_ENV + echo "VALGRIND_LOG=$GITHUB_WORKSPACE/build/log/valgrind-%p.log" >> $GITHUB_ENV + echo "CACHE_NVIM_DEPS_DIR=$HOME/.cache/nvim-deps" >> $GITHUB_ENV + echo "CACHE_MARKER=$HOME/.cache/nvim-deps/.ci_cache_marker" >> $GITHUB_ENV + echo "CCACHE_BASEDIR=$GITHUB_WORKSPACE" >> $GITHUB_ENV + + - name: Setup clang repository + run: | + wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key|sudo apt-key add - + sudo add-apt-repository 'deb http://apt.llvm.org/bionic/ llvm-toolchain-bionic-11 main' + + - name: Install apt packages + run: | + sudo apt-get update + sudo apt-get install -y autoconf automake build-essential ccache cmake cpanminus cscope gcc-multilib gdb gettext gperf language-pack-tr libtool-bin locales ninja-build pkg-config python python-pip python-setuptools python3 python3-pip python3-setuptools unzip valgrind xclip + + - name: Install new clang + run: sudo apt-get install -y clang-11 + + - name: Set ASAN env vars + run: | + echo "CLANG_SANITIZER=ASAN_UBSAN" >> $GITHUB_ENV + echo "CMAKE_FLAGS=-DCI_BUILD=ON -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX:PATH=$HOME/nvim-install -DBUSTED_OUTPUT_TYPE=nvim -DDEPS_PREFIX=$HOME/nvim-deps/usr -DMIN_LOG_LEVEL=3 -DPREFER_LUA=ON" >> $GITHUB_ENV + echo "SYMBOLIZER=asan_symbolize-11" >> $GITHUB_ENV + + - name: Setup interpreter packages + run: | + ./ci/before_install.sh + ./ci/install.sh + + - name: Cache dependencies + uses: actions/cache@v2 + env: + cache-name: asan-deps + with: + path: | + ${{ env.CACHE_NVIM_DEPS_DIR }} + ~/.ccache + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('cmake/*', 'third-party/**', '**/CMakeLists.txt') }}-${{ github.base_ref }} + + - name: Build third-party + run: ./ci/before_script.sh + + - name: Build and test + run: ./ci/script.sh + + - name: Cache dependencies + if: ${{ success() }} + run: ./ci/before_cache.sh + + lint: + runs-on: ubuntu-latest + env: + CI_TARGET: lint + steps: + - uses: actions/checkout@v2 + + - name: Setup commom environment variables + run: | + echo "$HOME/.local/bin" >> $GITHUB_PATH + + echo "CI_BUILD_DIR=$GITHUB_WORKSPACE" >> $GITHUB_ENV + echo "BUILD_DIR=$GITHUB_WORKSPACE/build" >> $GITHUB_ENV + echo "DEPS_BUILD_DIR=$HOME/nvim-deps" >> $GITHUB_ENV + echo "INSTALL_PREFIX=$HOME/nvim-install" >> $GITHUB_ENV + echo "LOG_DIR=$GITHUB_WORKSPACE/build/log" >> $GITHUB_ENV + echo "NVIM_LOG_FILE=$GITHUB_WORKSPACE/build/.nvimlog" >> $GITHUB_ENV + echo "CMAKE_FLAGS=-DCI_BUILD=ON -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX:PATH=$HOME/nvim-install -DBUSTED_OUTPUT_TYPE=nvim -DDEPS_PREFIX=$HOME/nvim-deps/usr -DMIN_LOG_LEVEL=3" >> $GITHUB_ENV + echo "ASAN_OPTIONS=detect_leaks=1:check_initialization_order=1:log_path=$GITHUB_WORKSPACE/build/log/asan" >> $GITHUB_ENV + echo "TSAN_OPTIONS=log_path=$GITHUB_WORKSPACE/build/log/tsan" >> $GITHUB_ENV + echo "UBSAN_OPTIONS=print_stacktrace=1 log_path=$GITHUB_WORKSPACE/build/log/ubsan" >> $GITHUB_ENV + echo "VALGRIND_LOG=$GITHUB_WORKSPACE/build/log/valgrind-%p.log" >> $GITHUB_ENV + echo "CACHE_NVIM_DEPS_DIR=$HOME/.cache/nvim-deps" >> $GITHUB_ENV + echo "CACHE_MARKER=$HOME/.cache/nvim-deps/.ci_cache_marker" >> $GITHUB_ENV + echo "CCACHE_BASEDIR=$GITHUB_WORKSPACE" >> $GITHUB_ENV + + - name: Install apt packages + run: | + sudo apt-get update + sudo apt-get install -y autoconf automake build-essential ccache cmake gcc-multilib gettext gperf libtool-bin locales ninja-build pkg-config python3 python3-pip python3-setuptools unzip + + - name: Setup interpreter packages + run: | + ./ci/before_install.sh + ./ci/install.sh + + - name: Cache dependencies + uses: actions/cache@v2 + env: + cache-name: lint-deps + with: + path: | + ${{ env.CACHE_NVIM_DEPS_DIR }} + ~/.ccache + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('cmake/*', 'third-party/**', '**/CMakeLists.txt') }}-${{ github.base_ref }} + + - name: Build third-party + run: ./ci/before_script.sh + + - name: Build and test + run: ./ci/script.sh + + - name: Cache dependencies + if: ${{ success() }} + run: ./ci/before_cache.sh diff --git a/.gitignore b/.gitignore index 699d493b59..ab301bd336 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,14 @@ # Tools -.ropeproject/ +/venv/ compile_commands.json -# Visual Studio + +# IDEs /.vs/ +/.vscode/ +/.idea/ # Build/deps dir /build/ -/cmake-build-debug/ -/dist/ /.deps/ /tmp/ /.clangd/ @@ -20,8 +21,6 @@ compile_commands.json *.o *.so -tags - /src/nvim/po/vim.pot /src/nvim/po/*.ck @@ -57,12 +56,12 @@ tags # local make targets local.mk -# runtime/doc +# Generated from :help docs +tags /runtime/doc/*.html /runtime/doc/tags.ref /runtime/doc/errors.log -# Don't include the mpack files. -/runtime/doc/*.mpack -# CLion -/.idea/ +# Generated by gen_vimdoc.py: +/runtime/doc/*.mpack +/tmp-*-doc diff --git a/.luacheckrc b/.luacheckrc index 9c8bddb88e..a628daed80 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -1,5 +1,10 @@ -- vim: ft=lua tw=80 +stds.nvim = { + read_globals = { "jit" } +} +std = "lua51+nvim" + -- Ignore W211 (unused variable) with preload files. files["**/preload.lua"] = {ignore = { "211" }} -- Allow vim module to modify itself, but only here. diff --git a/.travis.yml b/.travis.yml index b920f70f45..715109f71f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,4 @@ -dist: xenial +dist: bionic language: c @@ -30,7 +30,7 @@ env: # Nvim log file. - NVIM_LOG_FILE="$BUILD_DIR/.nvimlog" # Default CMake flags. - - CMAKE_FLAGS="-DTRAVIS_CI_BUILD=ON + - CMAKE_FLAGS="-DCI_BUILD=ON -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX:PATH=$INSTALL_PREFIX -DBUSTED_OUTPUT_TYPE=nvim @@ -45,7 +45,7 @@ env: - VALGRIND_LOG="$LOG_DIR/valgrind-%p.log" - CACHE_NVIM_DEPS_DIR="$HOME/.cache/nvim-deps" # If this file exists, the cache is valid (compile was successful). - - CACHE_MARKER="$CACHE_NVIM_DEPS_DIR/.travis_cache_marker" + - CACHE_MARKER="$CACHE_NVIM_DEPS_DIR/.ci_cache_marker" # default target name for functional tests - FUNCTIONALTEST=functionaltest - CI_TARGET=tests @@ -99,12 +99,21 @@ jobs: - stage: baseline name: clang-asan os: linux - compiler: clang + compiler: clang-11 # Use Lua so that ASAN can test our embedded Lua support. 8fec4d53d0f6 env: - CLANG_SANITIZER=ASAN_UBSAN - CMAKE_FLAGS="$CMAKE_FLAGS -DPREFER_LUA=ON" + - SYMBOLIZER=asan_symbolize-11 - *common-job-env + addons: + apt: + sources: + - sourceline: 'deb http://apt.llvm.org/bionic/ llvm-toolchain-bionic-11 main' + key_url: 'https://apt.llvm.org/llvm-snapshot.gpg.key' + packages: + - *common-apt-packages + - clang-11 - name: gcc-coverage (gcc 9) os: linux compiler: gcc-9 @@ -116,15 +125,15 @@ jobs: - BUSTED_ARGS="--coverage" - *common-job-env addons: + snaps: + - name: powershell + confinement: classic apt: sources: - sourceline: 'ppa:ubuntu-toolchain-r/test' - - sourceline: 'deb [arch=amd64] https://packages.microsoft.com/ubuntu/16.04/prod xenial main' - key_url: 'https://packages.microsoft.com/keys/microsoft.asc' packages: - *common-apt-packages - gcc-9 - - powershell - if: branch = master AND commit_message !~ /\[skip.lint\]/ name: lint os: linux @@ -193,6 +202,7 @@ jobs: - LANG: C.UTF-8 - SNAPCRAFT_ENABLE_SILENT_REPORT: y - SNAPCRAFT_ENABLE_DEVELOPER_DEBUG: y + - SNAPCRAFT_BUILD_ENVIRONMENT: lxd addons: snaps: - name: snapcraft @@ -221,6 +231,7 @@ jobs: - LANG: C.UTF-8 - SNAPCRAFT_ENABLE_SILENT_REPORT: y - SNAPCRAFT_ENABLE_DEVELOPER_DEBUG: y + - SNAPCRAFT_BUILD_ENVIRONMENT: lxd fast_finish: true before_install: ci/before_install.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index a4e49ccfc5..78588fb3da 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -267,8 +267,8 @@ else() -Wstrict-prototypes -std=gnu99 -Wshadow -Wconversion -Wmissing-prototypes) - check_c_compiler_flag(-Wimplicit-fallthrough HAS_WIMPLICIT_FALLTHROUGH_FLAG) - if(HAS_WIMPLICIT_FALLTHROUGH_FLAG) + check_c_compiler_flag(-Wimplicit-fallthrough HAVE_WIMPLICIT_FALLTHROUGH_FLAG) + if(HAVE_WIMPLICIT_FALLTHROUGH_FLAG) add_compile_options(-Wimplicit-fallthrough) endif() @@ -322,10 +322,10 @@ if(HAS_DIAG_COLOR_FLAG) endif() endif() -option(TRAVIS_CI_BUILD "Travis/sourcehut CI, extra flags will be set" OFF) +option(CI_BUILD "CI, extra flags will be set" OFF) -if(TRAVIS_CI_BUILD) - message(STATUS "Travis/sourcehut CI build enabled") +if(CI_BUILD) + message(STATUS "CI build enabled") add_compile_options(-Werror) if(DEFINED ENV{BUILD_32BIT}) # Get some test coverage for unsigned char @@ -374,6 +374,9 @@ include_directories(SYSTEM ${MSGPACK_INCLUDE_DIRS}) find_package(LibLUV 1.30.0 REQUIRED) include_directories(SYSTEM ${LIBLUV_INCLUDE_DIRS}) +find_package(TreeSitter REQUIRED) +include_directories(SYSTEM ${TreeSitter_INCLUDE_DIRS}) + # Note: The test lib requires LuaJIT; it will be skipped if LuaJIT is missing. option(PREFER_LUA "Prefer Lua over LuaJIT in the nvim executable." OFF) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c7d8398bf0..c179db0c46 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -110,7 +110,7 @@ and [AppVeyor]. # To get a full backtrace: # 1. Rebuild with debug info. rm -rf nvim.core build - gmake CMAKE_BUILD_TYPE=RelWithDebInfo CMAKE_EXTRA_FLAGS="-DTRAVIS_CI_BUILD=ON -DMIN_LOG_LEVEL=3" nvim + gmake CMAKE_BUILD_TYPE=RelWithDebInfo CMAKE_EXTRA_FLAGS="-DCI_BUILD=ON -DMIN_LOG_LEVEL=3" nvim # 2. Run the failing test to generate a new core file. TEST_FILE=test/functional/foo.lua gmake functionaltest lldb build/bin/nvim -c nvim.core @@ -4,7 +4,7 @@ [Chat](https://gitter.im/neovim/neovim) | [Twitter](https://twitter.com/Neovim) -[](https://travis-ci.org/neovim/neovim) +[](https://github.com/neovim/neovim/actions?query=workflow%3A%22Linux+CI%22) [](https://ci.appveyor.com/project/neovim/neovim/branch/master) [](https://codecov.io/gh/neovim/neovim) [](https://scan.coverity.com/projects/2227) diff --git a/ci/before_install.sh b/ci/before_install.sh index 1cf60edf73..c3fd8bdbde 100755 --- a/ci/before_install.sh +++ b/ci/before_install.sh @@ -22,7 +22,7 @@ if [[ "${TRAVIS_OS_NAME}" != osx ]] && command -v pyenv; then echo 'Setting Python versions via pyenv' # Prefer Python 2 over 3 (more conservative). - pyenv global 2.7.15:3.7.1 + pyenv global 2.7:3.8 echo 'Updated Python info:' ( diff --git a/ci/before_script.sh b/ci/before_script.sh index 1759dbe942..8bab1c4e17 100755 --- a/ci/before_script.sh +++ b/ci/before_script.sh @@ -3,10 +3,6 @@ set -e set -o pipefail -if [[ "${CI_TARGET}" == lint ]]; then - exit -fi - CI_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "${CI_DIR}/common/build.sh" diff --git a/ci/build.ps1 b/ci/build.ps1 index 36570be7ae..dbc43aecf3 100644 --- a/ci/build.ps1 +++ b/ci/build.ps1 @@ -91,7 +91,14 @@ if ($compiler -eq 'MINGW') { & C:\msys64\usr\bin\mkdir -p /var/cache/pacman/pkg # Build third-party dependencies - C:\msys64\usr\bin\bash -lc "pacman --verbose --noconfirm -Su" ; exitIfFailed + C:\msys64\usr\bin\bash -lc "curl -O http://repo.msys2.org/msys/x86_64/msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz" ; exitIfFailed + C:\msys64\usr\bin\bash -lc "curl -O http://repo.msys2.org/msys/x86_64/msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz.sig" ; exitIfFailed + C:\msys64\usr\bin\bash -lc "pacman-key --verify msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz.sig" ; exitIfFailed + C:\msys64\usr\bin\bash -lc "pacman --verbose --noconfirm -U msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz" ; exitIfFailed + # If there are still processes using msys-2.0.dll, after the base system update is finished, it will wait for input from the user. + # To prevent this, we will terminate all processes that use msys-2.0.dll. + Get-Process | Where-Object { $_.path -like 'C:\msys64*' } | Stop-Process + C:\msys64\usr\bin\bash -lc "pacman --verbose --noconfirm -Syu" ; exitIfFailed C:\msys64\usr\bin\bash -lc "pacman --verbose --noconfirm --needed -S $mingwPackages" ; exitIfFailed } elseif ($compiler -eq 'MSVC') { @@ -116,7 +123,7 @@ if (-not $NoTests) { python3 -c "import pynvim; print(str(pynvim))" ; exitIfFailed $env:PATH = "C:\Ruby24\bin;$env:PATH" - gem.cmd install neovim + gem.cmd install --pre neovim Get-Command -CommandType Application neovim-ruby-host.bat npm.cmd install -g neovim diff --git a/ci/common/build.sh b/ci/common/build.sh index 0024f2cbd5..f0bdec0a0e 100644 --- a/ci/common/build.sh +++ b/ci/common/build.sh @@ -29,7 +29,7 @@ build_deps() { if test "${CACHE_ENABLE}" = "false" ; then export CCACHE_RECACHE=1 elif test -f "${CACHE_MARKER}" ; then - echo "Using third-party dependencies from Travis cache (last update: $(_stat "${CACHE_MARKER}"))." + echo "Using third-party dependencies from cache (last update: $(_stat "${CACHE_MARKER}"))." cp -a "${CACHE_NVIM_DEPS_DIR}"/. "${DEPS_BUILD_DIR}" fi @@ -37,13 +37,13 @@ build_deps() { # update CMake configuration and update to newer deps versions. cd "${DEPS_BUILD_DIR}" echo "Configuring with '${DEPS_CMAKE_FLAGS}'." - CC= cmake -G Ninja ${DEPS_CMAKE_FLAGS} "${TRAVIS_BUILD_DIR}/third-party/" + CC= cmake -G Ninja ${DEPS_CMAKE_FLAGS} "${CI_BUILD_DIR}/third-party/" if ! top_make; then exit 1 fi - cd "${TRAVIS_BUILD_DIR}" + cd "${CI_BUILD_DIR}" } prepare_build() { @@ -54,7 +54,7 @@ prepare_build() { mkdir -p "${BUILD_DIR}" cd "${BUILD_DIR}" echo "Configuring with '${CMAKE_FLAGS} $@'." - cmake -G Ninja ${CMAKE_FLAGS} "$@" "${TRAVIS_BUILD_DIR}" + cmake -G Ninja ${CMAKE_FLAGS} "$@" "${CI_BUILD_DIR}" } build_nvim() { @@ -84,5 +84,5 @@ build_nvim() { fi check_sanitizer "${LOG_DIR}" - cd "${TRAVIS_BUILD_DIR}" + cd "${CI_BUILD_DIR}" } diff --git a/ci/common/suite.sh b/ci/common/suite.sh index 4c42f06c60..038b116c5a 100644 --- a/ci/common/suite.sh +++ b/ci/common/suite.sh @@ -13,16 +13,33 @@ FAIL_SUMMARY_FILE="$BUILD_DIR/.test_errors" ANSI_CLEAR="\033[0K" -travis_fold() { - local action="$1" - local name="$2" - name="$(echo -n "$name" | tr '\n\0' '--' | sed 's/[^A-Za-z0-9]\{1,\}/-/g')" - name="$(echo -n "$name" | sed 's/-$//')" - echo -en "travis_fold:${action}:${name}\r${ANSI_CLEAR}" -} - -if test "$TRAVIS" != "true" ; then - travis_fold() { +if test "$TRAVIS" = "true"; then + ci_fold() { + local action="$1" + local name="$2" + name="$(echo -n "$name" | tr '\n\0' '--' | sed 's/[^A-Za-z0-9]\{1,\}/-/g')" + name="$(echo -n "$name" | sed 's/-$//')" + echo -en "travis_fold:${action}:${name}\r${ANSI_CLEAR}" + } +elif test "$GITHUB_ACTIONS" = "true"; then + ci_fold() { + local action="$1" + local name="$2" + name="$(echo -n "$name" | tr '\n\0' '--' | sed 's/[^A-Za-z0-9]\{1,\}/-/g')" + name="$(echo -n "$name" | sed 's/-$//')" + case "$action" in + start) + echo "::group::${name}" + ;; + end) + echo "::endgroup::" + ;; + *) + :;; + esac + } +else + ci_fold() { return 0 } fi @@ -33,7 +50,7 @@ enter_suite() { rm -f "${END_MARKER}" local suite_name="$1" export NVIM_TEST_CURRENT_SUITE="${NVIM_TEST_CURRENT_SUITE}/$suite_name" - travis_fold start "${NVIM_TEST_CURRENT_SUITE}" + ci_fold start "${NVIM_TEST_CURRENT_SUITE}" set -x } @@ -43,7 +60,7 @@ exit_suite() { echo "Suite ${NVIM_TEST_CURRENT_SUITE} failed, summary:" echo "${FAIL_SUMMARY}" else - travis_fold end "${NVIM_TEST_CURRENT_SUITE}" + ci_fold end "${NVIM_TEST_CURRENT_SUITE}" fi export NVIM_TEST_CURRENT_SUITE="${NVIM_TEST_CURRENT_SUITE%/*}" if test "$1" != "--continue" ; then diff --git a/ci/common/test.sh b/ci/common/test.sh index b2fbeaf2da..4ef6260339 100644 --- a/ci/common/test.sh +++ b/ci/common/test.sh @@ -82,7 +82,7 @@ valgrind_check() { check_sanitizer() { if test -n "${CLANG_SANITIZER}"; then - check_logs "${1}" "*san.*" + check_logs "${1}" "*san.*" | ${SYMBOLIZER:-cat} fi } diff --git a/ci/install.sh b/ci/install.sh index a4dfc87a1b..cd0d744361 100755 --- a/ci/install.sh +++ b/ci/install.sh @@ -19,7 +19,7 @@ echo "Install neovim module for Python 2." CC=cc python2 -m pip -q install --user --upgrade pynvim echo "Install neovim RubyGem." -gem install --no-document --version ">= 0.8.0" neovim +gem install --no-document --user-install --pre neovim echo "Install neovim npm package" source ~/.nvm/nvm.sh diff --git a/ci/snap/.snapcraft_payload b/ci/snap/.snapcraft_payload new file mode 100644 index 0000000000..29f895fad6 --- /dev/null +++ b/ci/snap/.snapcraft_payload @@ -0,0 +1,194 @@ +{ + "ref": "refs/heads/master", + "before": "66b136c43c12df3dcf8f19ff48f206ad2e4f43fc", + "after": "1bf69c32217cc455603ce8aa2b5415d9717f0fa2", + "repository": { + "id": 292861950, + "node_id": "MDEwOlJlcG9zaXRvcnkyOTI4NjE5NTA=", + "name": "neovim-snap", + "full_name": "hurricanehrndz/neovim-snap", + "private": false, + "owner": { + "name": "hurricanehrndz", + "email": "hurricanehrndz@users.noreply.github.com", + "login": "hurricanehrndz", + "id": 5804237, + "node_id": "MDQ6VXNlcjU4MDQyMzc=", + "avatar_url": "https://avatars0.githubusercontent.com/u/5804237?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/hurricanehrndz", + "html_url": "https://github.com/hurricanehrndz", + "followers_url": "https://api.github.com/users/hurricanehrndz/followers", + "following_url": "https://api.github.com/users/hurricanehrndz/following{/other_user}", + "gists_url": "https://api.github.com/users/hurricanehrndz/gists{/gist_id}", + "starred_url": "https://api.github.com/users/hurricanehrndz/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/hurricanehrndz/subscriptions", + "organizations_url": "https://api.github.com/users/hurricanehrndz/orgs", + "repos_url": "https://api.github.com/users/hurricanehrndz/repos", + "events_url": "https://api.github.com/users/hurricanehrndz/events{/privacy}", + "received_events_url": "https://api.github.com/users/hurricanehrndz/received_events", + "type": "User", + "site_admin": false + }, + "html_url": "https://github.com/hurricanehrndz/neovim-snap", + "description": "snap build for neovim", + "fork": false, + "url": "https://github.com/hurricanehrndz/neovim-snap", + "forks_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/forks", + "keys_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/teams", + "hooks_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/hooks", + "issue_events_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/issues/events{/number}", + "events_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/events", + "assignees_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/assignees{/user}", + "branches_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/branches{/branch}", + "tags_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/tags", + "blobs_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/statuses/{sha}", + "languages_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/languages", + "stargazers_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/stargazers", + "contributors_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/contributors", + "subscribers_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/subscribers", + "subscription_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/subscription", + "commits_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/contents/{+path}", + "compare_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/merges", + "archive_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/downloads", + "issues_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/issues{/number}", + "pulls_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/pulls{/number}", + "milestones_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/milestones{/number}", + "notifications_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/labels{/name}", + "releases_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/releases{/id}", + "deployments_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/deployments", + "created_at": 1599227980, + "updated_at": "2020-09-04T14:02:38Z", + "pushed_at": 1599228352, + "git_url": "git://github.com/hurricanehrndz/neovim-snap.git", + "ssh_url": "git@github.com:hurricanehrndz/neovim-snap.git", + "clone_url": "https://github.com/hurricanehrndz/neovim-snap.git", + "svn_url": "https://github.com/hurricanehrndz/neovim-snap", + "homepage": null, + "size": 0, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "forks_count": 0, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 0, + "license": { + "key": "mit", + "name": "MIT License", + "spdx_id": "MIT", + "url": "https://api.github.com/licenses/mit", + "node_id": "MDc6TGljZW5zZTEz" + }, + "forks": 0, + "open_issues": 0, + "watchers": 0, + "default_branch": "master", + "stargazers": 0, + "master_branch": "master" + }, + "pusher": { + "name": "hurricanehrndz", + "email": "hurricanehrndz@users.noreply.github.com" + }, + "sender": { + "login": "hurricanehrndz", + "id": 5804237, + "node_id": "MDQ6VXNlcjU4MDQyMzc=", + "avatar_url": "https://avatars0.githubusercontent.com/u/5804237?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/hurricanehrndz", + "html_url": "https://github.com/hurricanehrndz", + "followers_url": "https://api.github.com/users/hurricanehrndz/followers", + "following_url": "https://api.github.com/users/hurricanehrndz/following{/other_user}", + "gists_url": "https://api.github.com/users/hurricanehrndz/gists{/gist_id}", + "starred_url": "https://api.github.com/users/hurricanehrndz/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/hurricanehrndz/subscriptions", + "organizations_url": "https://api.github.com/users/hurricanehrndz/orgs", + "repos_url": "https://api.github.com/users/hurricanehrndz/repos", + "events_url": "https://api.github.com/users/hurricanehrndz/events{/privacy}", + "received_events_url": "https://api.github.com/users/hurricanehrndz/received_events", + "type": "User", + "site_admin": false + }, + "created": false, + "deleted": false, + "forced": false, + "base_ref": null, + "compare": "https://github.com/hurricanehrndz/neovim-snap/compare/66b136c43c12...1bf69c32217c", + "commits": [ + { + "id": "1bf69c32217cc455603ce8aa2b5415d9717f0fa2", + "tree_id": "62ea83a2349be8c930c45fdc199f71b08bf5927e", + "distinct": true, + "message": "Build of latest tag", + "timestamp": "2020-09-04T14:05:40Z", + "url": "https://github.com/hurricanehrndz/neovim-snap/commit/1bf69c32217cc455603ce8aa2b5415d9717f0fa2", + "author": { + "name": "Carlos Hernandez", + "email": "carlos@techbyte.ca", + "username": "hurricanehrndz" + }, + "committer": { + "name": "Carlos Hernandez", + "email": "carlos@techbyte.ca", + "username": "hurricanehrndz" + }, + "added": [ + + ], + "removed": [ + + ], + "modified": [ + "snap/snapcraft.yaml" + ] + } + ], + "head_commit": { + "id": "1bf69c32217cc455603ce8aa2b5415d9717f0fa2", + "tree_id": "62ea83a2349be8c930c45fdc199f71b08bf5927e", + "distinct": true, + "message": "Build of latest tag", + "timestamp": "2020-09-04T14:05:40Z", + "url": "https://github.com/hurricanehrndz/neovim-snap/commit/1bf69c32217cc455603ce8aa2b5415d9717f0fa2", + "author": { + "name": "Carlos Hernandez", + "email": "carlos@techbyte.ca", + "username": "hurricanehrndz" + }, + "committer": { + "name": "Carlos Hernandez", + "email": "carlos@techbyte.ca", + "username": "hurricanehrndz" + }, + "added": [ + + ], + "removed": [ + + ], + "modified": [ + "snap/snapcraft.yaml" + ] + } +} diff --git a/ci/snap/deploy.sh b/ci/snap/deploy.sh index 5fbd52d775..579c48e933 100755 --- a/ci/snap/deploy.sh +++ b/ci/snap/deploy.sh @@ -3,19 +3,37 @@ set -e set -o pipefail -# not a tagged release, abort -# [[ "$TRAVIS_TAG" != "$TRAVIS_BRANCH" ]] && exit 0 +SNAP_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +WEBHOOK_PAYLOAD="$(cat "${SNAP_DIR}/.snapcraft_payload")" +PAYLOAD_SIG="${SECRET_SNAP_SIG}" -mkdir -p .snapcraft -# shellcheck disable=SC2154 -openssl aes-256-cbc -K "$encrypted_ece1c4844832_key" -iv "$encrypted_ece1c4844832_iv" \ - -in ci/snap/travis_snapcraft.cfg -out .snapcraft/snapcraft.cfg -d -SNAP=$(find ./ -name "*.snap") +snap_realease_needed() { + last_committed_tag="$(git tag -l --sort=refname|head -1)" + last_snap_release="$(snap info nvim | awk '$1 == "latest/edge:" { print $2 }' | perl -lpe 's/v\d.\d.\d-//g')" + git fetch -f --tags + git checkout "${last_committed_tag}" 2> /dev/null + last_git_release="$(git describe --first-parent 2> /dev/null | perl -lpe 's/v\d.\d.\d-//g')" -# TODO(justinmk): This always does `edge` until we enable tagged builds. -if [[ "$SNAP" =~ "dirty" || "$SNAP" =~ "nightly" ]]; then - snapcraft push "$SNAP" --release edge -else - snapcraft push "$SNAP" --release candidate + if [[ -z "$(echo $last_snap_release | perl -ne "print if /${last_git_release}.*/")" ]]; then + return 0 + fi + return 1 +} + + +trigger_snapcraft_webhook() { + [[ -n "${PAYLOAD_SIG}" ]] || exit + echo "Triggering new snap relase via webhook..." + curl -X POST \ + -H "Content-Type: application/json" \ + -H "X-Hub-Signature: sha1=${PAYLOAD_SIG}" \ + --data "${WEBHOOK_PAYLOAD}" \ + https://snapcraft.io/nvim/webhook/notify +} + + +if $(snap_realease_needed); then + echo "New snap release required" + trigger_snapcraft_webhook fi diff --git a/ci/snap/install.sh b/ci/snap/install.sh index 23e0bc5eb8..0ceb6f0422 100755 --- a/ci/snap/install.sh +++ b/ci/snap/install.sh @@ -4,6 +4,7 @@ set -e set -o pipefail sudo apt update +sudo usermod -aG lxd $USER sudo /snap/bin/lxd.migrate -yes sudo /snap/bin/lxd waitready sudo /snap/bin/lxd init --auto diff --git a/ci/snap/script.sh b/ci/snap/script.sh index 647cda4874..21d3421044 100755 --- a/ci/snap/script.sh +++ b/ci/snap/script.sh @@ -3,6 +3,6 @@ set -e set -o pipefail -mkdir -p "$TRAVIS_BUILD_DIR/snaps-cache" -sudo snapcraft --use-lxd +mkdir -p "$CI_BUILD_DIR/snaps-cache" +sg lxd -c snapcraft diff --git a/cmake/FindLua.cmake b/cmake/FindLua.cmake index b669a49f29..7ba13e1f56 100644 --- a/cmake/FindLua.cmake +++ b/cmake/FindLua.cmake @@ -42,7 +42,7 @@ unset(_lua_append_versions) # this is a function only to have all the variables inside go away automatically function(_lua_set_version_vars) - set(LUA_VERSIONS5 5.3 5.2 5.1 5.0) + set(LUA_VERSIONS5 5.4 5.3 5.2 5.1 5.0) if (Lua_FIND_VERSION_EXACT) if (Lua_FIND_VERSION_COUNT GREATER 1) diff --git a/cmake/FindTreeSitter.cmake b/cmake/FindTreeSitter.cmake new file mode 100644 index 0000000000..ae0928e9f7 --- /dev/null +++ b/cmake/FindTreeSitter.cmake @@ -0,0 +1,11 @@ +# - Try to find tree-sitter +# Once done, this will define +# +# TreeSitter_FOUND - system has tree-sitter +# TreeSitter_INCLUDE_DIRS - the tree-sitter include directories +# TreeSitter_LIBRARIES - link these to use tree-sitter + +include(LibFindMacros) + +libfind_pkg_detect(TreeSitter tree-sitter FIND_PATH tree_sitter/api.h FIND_LIBRARY tree-sitter) +libfind_process(TreeSitter) diff --git a/codecov.yml b/codecov.yml index 0f867db668..a83fd916ee 100644 --- a/codecov.yml +++ b/codecov.yml @@ -25,6 +25,3 @@ coverage: changes: no comment: off - -ignore: - - "src/tree_sitter" diff --git a/config/CMakeLists.txt b/config/CMakeLists.txt index 6c9e06d59d..8a70d864c4 100644 --- a/config/CMakeLists.txt +++ b/config/CMakeLists.txt @@ -32,6 +32,7 @@ endif() check_include_files(sys/utsname.h HAVE_SYS_UTSNAME_H) check_include_files(termios.h HAVE_TERMIOS_H) check_include_files(sys/uio.h HAVE_SYS_UIO_H) +check_include_files(sys/sdt.h HAVE_SYS_SDT_H) # Functions check_function_exists(fseeko HAVE_FSEEKO) diff --git a/config/config.h.in b/config/config.h.in index 0cb87c6b4d..95e2c872a3 100644 --- a/config/config.h.in +++ b/config/config.h.in @@ -33,6 +33,7 @@ #cmakedefine HAVE_STRCASECMP #cmakedefine HAVE_STRINGS_H #cmakedefine HAVE_STRNCASECMP +#cmakedefine HAVE_SYS_SDT_H #cmakedefine HAVE_SYS_UTSNAME_H #cmakedefine HAVE_SYS_WAIT_H #cmakedefine HAVE_TERMIOS_H @@ -60,5 +61,6 @@ #cmakedefine HAVE_EXECINFO_BACKTRACE #cmakedefine HAVE_BUILTIN_ADD_OVERFLOW +#cmakedefine HAVE_WIMPLICIT_FALLTHROUGH_FLAG #endif // AUTO_CONFIG_H diff --git a/runtime/autoload/dist/ft.vim b/runtime/autoload/dist/ft.vim index e85ffc763b..b6297472c3 100644 --- a/runtime/autoload/dist/ft.vim +++ b/runtime/autoload/dist/ft.vim @@ -575,7 +575,7 @@ endfunc let s:ft_rules_udev_rules_pattern = '^\s*\cudev_rules\s*=\s*"\([^"]\{-1,}\)/*".*' func dist#ft#FTRules() let path = expand('<amatch>:p') - if path =~ '^/\(etc/udev/\%(rules\.d/\)\=.*\.rules\|lib/udev/\%(rules\.d/\)\=.*\.rules\)$' + if path =~ '/\(etc/udev/\%(rules\.d/\)\=.*\.rules\|\%(usr/\)\=lib/udev/\%(rules\.d/\)\=.*\.rules\)$' setf udevrules return endif diff --git a/runtime/autoload/health/provider.vim b/runtime/autoload/health/provider.vim index 0482cb7f3c..94fd7cf505 100644 --- a/runtime/autoload/health/provider.vim +++ b/runtime/autoload/health/provider.vim @@ -573,7 +573,7 @@ function! s:check_ruby() abort endif call health#report_info('Ruby: '. s:system('ruby -v')) - let host = provider#ruby#Detect() + let [host, err] = provider#ruby#Detect() if empty(host) call health#report_warn('`neovim-ruby-host` not found.', \ ['Run `gem install neovim` to ensure the neovim RubyGem is installed.', @@ -636,7 +636,7 @@ function! s:check_node() abort call health#report_warn('node.js on this system does not support --inspect-brk so $NVIM_NODE_HOST_DEBUG is ignored.') endif - let host = provider#node#Detect() + let [host, err] = provider#node#Detect() if empty(host) call health#report_warn('Missing "neovim" npm (or yarn) package.', \ ['Run in shell: npm install -g neovim', @@ -689,29 +689,31 @@ function! s:check_perl() abort return endif - if !executable('perl') || !executable('cpanm') - call health#report_warn( - \ '`perl` and `cpanm` must be in $PATH.', - \ ['Install Perl and cpanminus and verify that `perl` and `cpanm` commands work.']) - return + let [perl_exec, perl_errors] = provider#perl#Detect() + if empty(perl_exec) + if !empty(perl_errors) + call health#report_error('perl provider error:', perl_errors) + else + call health#report_warn('No usable perl executable found') + endif + return endif - let perl_v = get(split(s:system(['perl', '-W', '-e', 'print $^V']), "\n"), 0, '') - call health#report_info('Perl: '. perl_v) + + call health#report_info('perl executable: '. perl_exec) + + " we cannot use cpanm that is on the path, as it may not be for the perl + " set with g:perl_host_prog + call s:system([perl_exec, '-W', '-MApp::cpanminus', '-e', '']) if s:shell_error - call health#report_warn('Nvim perl host does not support '.perl_v) - " Skip further checks, they are nonsense if perl is too old. - return + return [perl_exec, '"App::cpanminus" module is not installed'] endif - let host = provider#perl#Detect() - if empty(host) - call health#report_warn('Missing "Neovim::Ext" cpan module.', - \ ['Run in shell: cpanm Neovim::Ext']) - return - endif - call health#report_info('Nvim perl host: '. host) + let latest_cpan_cmd = [perl_exec, + \ '-MApp::cpanminus::fatscript', '-e', + \ 'my $app = App::cpanminus::script->new; + \ $app->parse_options ("--info", "-q", "Neovim::Ext"); + \ exit $app->doit'] - let latest_cpan_cmd = 'cpanm --info -q Neovim::Ext' let latest_cpan = s:system(latest_cpan_cmd) if s:shell_error || empty(latest_cpan) call health#report_error('Failed to run: '. latest_cpan_cmd, @@ -735,7 +737,7 @@ function! s:check_perl() abort return endif - let current_cpan_cmd = [host, '-W', '-MNeovim::Ext', '-e', 'print $Neovim::Ext::VERSION'] + let current_cpan_cmd = [perl_exec, '-W', '-MNeovim::Ext', '-e', 'print $Neovim::Ext::VERSION'] let current_cpan = s:system(current_cpan_cmd) if s:shell_error call health#report_error('Failed to run: '. string(current_cpan_cmd), @@ -747,7 +749,7 @@ function! s:check_perl() abort call health#report_warn( \ printf('Module "Neovim::Ext" is out-of-date. Installed: %s, latest: %s', \ current_cpan, latest_cpan), - \ ['Run in shell: cpanm Neovim::Ext']) + \ ['Run in shell: cpanm -n Neovim::Ext']) else call health#report_ok('Latest "Neovim::Ext" cpan module is installed: '. current_cpan) endif diff --git a/runtime/autoload/provider/clipboard.vim b/runtime/autoload/provider/clipboard.vim index a96a0a61b7..c2195fa02d 100644 --- a/runtime/autoload/provider/clipboard.vim +++ b/runtime/autoload/provider/clipboard.vim @@ -35,8 +35,7 @@ endfunction let s:selections = { '*': s:selection, '+': copy(s:selection) } function! s:try_cmd(cmd, ...) abort - let argv = split(a:cmd, " ") - let out = systemlist(argv, (a:0 ? a:1 : ['']), 1) + let out = systemlist(a:cmd, (a:0 ? a:1 : ['']), 1) if v:shell_error if !exists('s:did_error_try_cmd') echohl WarningMsg @@ -55,6 +54,10 @@ function! s:cmd_ok(cmd) abort return v:shell_error == 0 endfunction +function! s:split_cmd(cmd) abort + return (type(a:cmd) == v:t_string) ? split(a:cmd, " ") : a:cmd +endfunction + let s:cache_enabled = 1 let s:err = '' @@ -71,44 +74,50 @@ function! provider#clipboard#Executable() abort return '' endif - let s:copy = get(g:clipboard, 'copy', { '+': v:null, '*': v:null }) - let s:paste = get(g:clipboard, 'paste', { '+': v:null, '*': v:null }) + let s:copy = {} + let s:copy['+'] = s:split_cmd(get(g:clipboard.copy, '+', v:null)) + let s:copy['*'] = s:split_cmd(get(g:clipboard.copy, '*', v:null)) + + let s:paste = {} + let s:paste['+'] = s:split_cmd(get(g:clipboard.paste, '+', v:null)) + let s:paste['*'] = s:split_cmd(get(g:clipboard.paste, '*', v:null)) + let s:cache_enabled = get(g:clipboard, 'cache_enabled', 0) return get(g:clipboard, 'name', 'g:clipboard') elseif has('mac') - let s:copy['+'] = 'pbcopy' - let s:paste['+'] = 'pbpaste' + let s:copy['+'] = ['pbcopy'] + let s:paste['+'] = ['pbpaste'] let s:copy['*'] = s:copy['+'] let s:paste['*'] = s:paste['+'] let s:cache_enabled = 0 return 'pbcopy' - elseif exists('$WAYLAND_DISPLAY') && executable('wl-copy') && executable('wl-paste') - let s:copy['+'] = 'wl-copy --foreground --type text/plain' - let s:paste['+'] = 'wl-paste --no-newline' - let s:copy['*'] = 'wl-copy --foreground --primary --type text/plain' - let s:paste['*'] = 'wl-paste --no-newline --primary' + elseif !empty($WAYLAND_DISPLAY) && executable('wl-copy') && executable('wl-paste') + let s:copy['+'] = ['wl-copy', '--foreground', '--type', 'text/plain'] + let s:paste['+'] = ['wl-paste', '--no-newline'] + let s:copy['*'] = ['wl-copy', '--foreground', '--primary', '--type', 'text/plain'] + let s:paste['*'] = ['wl-paste', '--no-newline', '--primary'] return 'wl-copy' - elseif exists('$DISPLAY') && executable('xclip') - let s:copy['+'] = 'xclip -quiet -i -selection clipboard' - let s:paste['+'] = 'xclip -o -selection clipboard' - let s:copy['*'] = 'xclip -quiet -i -selection primary' - let s:paste['*'] = 'xclip -o -selection primary' + elseif !empty($DISPLAY) && executable('xclip') + let s:copy['+'] = ['xclip', '-quiet', '-i', '-selection', 'clipboard'] + let s:paste['+'] = ['xclip', '-o', '-selection', 'clipboard'] + let s:copy['*'] = ['xclip', '-quiet', '-i', '-selection', 'primary'] + let s:paste['*'] = ['xclip', '-o', '-selection', 'primary'] return 'xclip' - elseif exists('$DISPLAY') && executable('xsel') && s:cmd_ok('xsel -o -b') - let s:copy['+'] = 'xsel --nodetach -i -b' - let s:paste['+'] = 'xsel -o -b' - let s:copy['*'] = 'xsel --nodetach -i -p' - let s:paste['*'] = 'xsel -o -p' + elseif !empty($DISPLAY) && executable('xsel') && s:cmd_ok('xsel -o -b') + let s:copy['+'] = ['xsel', '--nodetach', '-i', '-b'] + let s:paste['+'] = ['xsel', '-o', '-b'] + let s:copy['*'] = ['xsel', '--nodetach', '-i', '-p'] + let s:paste['*'] = ['xsel', '-o', '-p'] return 'xsel' elseif executable('lemonade') - let s:copy['+'] = 'lemonade copy' - let s:paste['+'] = 'lemonade paste' - let s:copy['*'] = 'lemonade copy' - let s:paste['*'] = 'lemonade paste' + let s:copy['+'] = ['lemonade', 'copy'] + let s:paste['+'] = ['lemonade', 'paste'] + let s:copy['*'] = ['lemonade', 'copy'] + let s:paste['*'] = ['lemonade', 'paste'] return 'lemonade' elseif executable('doitclient') - let s:copy['+'] = 'doitclient wclip' - let s:paste['+'] = 'doitclient wclip -r' + let s:copy['+'] = ['doitclient', 'wclip'] + let s:paste['+'] = ['doitclient', 'wclip', '-r'] let s:copy['*'] = s:copy['+'] let s:paste['*'] = s:paste['+'] return 'doitclient' @@ -118,14 +127,14 @@ function! provider#clipboard#Executable() abort else let win32yank = 'win32yank.exe' endif - let s:copy['+'] = win32yank.' -i --crlf' - let s:paste['+'] = win32yank.' -o --lf' + let s:copy['+'] = [win32yank, '-i', '--crlf'] + let s:paste['+'] = [win32yank, '-o', '--lf'] let s:copy['*'] = s:copy['+'] let s:paste['*'] = s:paste['+'] return 'win32yank' - elseif exists('$TMUX') && executable('tmux') - let s:copy['+'] = 'tmux load-buffer -' - let s:paste['+'] = 'tmux save-buffer -' + elseif !empty($TMUX) && executable('tmux') + let s:copy['+'] = ['tmux', 'load-buffer', '-'] + let s:paste['+'] = ['tmux', 'save-buffer', '-'] let s:copy['*'] = s:copy['+'] let s:paste['*'] = s:paste['+'] return 'tmux' @@ -169,16 +178,15 @@ function! s:clipboard.set(lines, regtype, reg) abort let s:selections[a:reg] = copy(s:selection) let selection = s:selections[a:reg] let selection.data = [a:lines, a:regtype] - let argv = split(s:copy[a:reg], " ") - let selection.argv = argv + let selection.argv = s:copy[a:reg] let selection.detach = s:cache_enabled let selection.cwd = "/" - let jobid = jobstart(argv, selection) + let jobid = jobstart(selection.argv, selection) if jobid > 0 call jobsend(jobid, a:lines) call jobclose(jobid, 'stdin') " xclip does not close stdout when receiving input via stdin - if argv[0] ==# 'xclip' + if selection.argv[0] ==# 'xclip' call jobclose(jobid, 'stdout') endif let selection.owner = jobid diff --git a/runtime/autoload/provider/node.vim b/runtime/autoload/provider/node.vim index c5d5e87729..17b6137816 100644 --- a/runtime/autoload/provider/node.vim +++ b/runtime/autoload/provider/node.vim @@ -48,14 +48,15 @@ function! provider#node#can_inspect() abort endfunction function! provider#node#Detect() abort + let minver = [6, 0] if exists('g:node_host_prog') - return expand(g:node_host_prog) + return [expand(g:node_host_prog), ''] endif if !executable('node') - return '' + return ['', 'node not found (or not executable)'] endif - if !s:is_minimum_version(v:null, 6, 0) - return '' + if !s:is_minimum_version(v:null, minver[0], minver[1]) + return ['', printf('node version %s.%s not found', minver[0], minver[1])] endif let npm_opts = {} @@ -75,7 +76,7 @@ function! provider#node#Detect() abort if has('unix') let yarn_default_path = $HOME . '/.config/yarn/global/' . yarn_opts.entry_point if filereadable(yarn_default_path) - return yarn_default_path + return [yarn_default_path, ''] endif endif let yarn_opts.job_id = jobstart('yarn global dir', yarn_opts) @@ -85,18 +86,18 @@ function! provider#node#Detect() abort if !empty(npm_opts) let result = jobwait([npm_opts.job_id]) if result[0] == 0 && npm_opts.result != '' - return npm_opts.result + return [npm_opts.result, ''] endif endif if !empty(yarn_opts) let result = jobwait([yarn_opts.job_id]) if result[0] == 0 && yarn_opts.result != '' - return yarn_opts.result + return [yarn_opts.result, ''] endif endif - return '' + return ['', 'failed to detect node'] endfunction function! provider#node#Prog() abort @@ -142,7 +143,7 @@ endfunction let s:err = '' -let s:prog = provider#node#Detect() +let [s:prog, s:_] = provider#node#Detect() let g:loaded_node_provider = empty(s:prog) ? 1 : 2 if g:loaded_node_provider != 2 diff --git a/runtime/autoload/provider/perl.vim b/runtime/autoload/provider/perl.vim index 36ca2bbf14..24f2b018bb 100644 --- a/runtime/autoload/provider/perl.vim +++ b/runtime/autoload/provider/perl.vim @@ -5,15 +5,25 @@ endif let s:loaded_perl_provider = 1 function! provider#perl#Detect() abort - " use g:perl_host_prof if set or check if perl is on the path + " use g:perl_host_prog if set or check if perl is on the path let prog = exepath(get(g:, 'perl_host_prog', 'perl')) if empty(prog) - return '' + return ['', ''] + endif + + " if perl is available, make sure we have 5.22+ + call system([prog, '-e', 'use v5.22']) + if v:shell_error + return ['', 'Perl version is too old, 5.22+ required'] endif " if perl is available, make sure the required module is available call system([prog, '-W', '-MNeovim::Ext', '-e', '']) - return v:shell_error ? '' : prog + if v:shell_error + return ['', '"Neovim::Ext" cpan module is not installed'] + endif + + return [prog, ''] endfunction function! provider#perl#Prog() abort @@ -46,7 +56,7 @@ function! provider#perl#Call(method, args) abort if !exists('s:host') try - let s:host = remote#host#Require('perl') + let s:host = remote#host#Require('legacy-perl-provider') catch let s:err = v:exception echohl WarningMsg @@ -58,12 +68,16 @@ function! provider#perl#Call(method, args) abort return call('rpcrequest', insert(insert(a:args, 'perl_'.a:method), s:host)) endfunction -let s:err = '' -let s:prog = provider#perl#Detect() +let [s:prog, s:err] = provider#perl#Detect() let g:loaded_perl_provider = empty(s:prog) ? 1 : 2 if g:loaded_perl_provider != 2 let s:err = 'Cannot find perl or the required perl module' endif -call remote#host#RegisterPlugin('perl-provider', 'perl', []) + +" The perl provider plugin will run in a separate instance of the perl +" host. +call remote#host#RegisterClone('legacy-perl-provider', 'perl') +call remote#host#RegisterPlugin('legacy-perl-provider', 'ScriptHost.pm', []) + diff --git a/runtime/autoload/provider/pythonx.vim b/runtime/autoload/provider/pythonx.vim index e89d519790..550931d8aa 100644 --- a/runtime/autoload/provider/pythonx.vim +++ b/runtime/autoload/provider/pythonx.vim @@ -29,8 +29,8 @@ endfunction function! s:get_python_candidates(major_version) abort return { \ 2: ['python2', 'python2.7', 'python2.6', 'python'], - \ 3: ['python3', 'python3.9', 'python3.8', 'python3.7', 'python3.6', 'python3.5', - \ 'python3.4', 'python3.3', 'python'] + \ 3: ['python3', 'python3.10', 'python3.9', 'python3.8', 'python3.7', + \ 'python3.6', 'python'] \ }[a:major_version] endfunction diff --git a/runtime/autoload/provider/ruby.vim b/runtime/autoload/provider/ruby.vim index f843050df9..1f49c623ac 100644 --- a/runtime/autoload/provider/ruby.vim +++ b/runtime/autoload/provider/ruby.vim @@ -5,7 +5,8 @@ endif let g:loaded_ruby_provider = 1 function! provider#ruby#Detect() abort - return s:prog + let e = empty(s:prog) ? 'missing ruby or ruby-host' : '' + return [s:prog, e] endfunction function! provider#ruby#Prog() abort diff --git a/runtime/autoload/spellfile.vim b/runtime/autoload/spellfile.vim index d098902305..e36e2f936b 100644 --- a/runtime/autoload/spellfile.vim +++ b/runtime/autoload/spellfile.vim @@ -1,13 +1,9 @@ " Vim script to download a missing spell file if !exists('g:spellfile_URL') - " Prefer using http:// when netrw should be able to use it, since - " more firewalls let this through. - if executable("curl") || executable("wget") || executable("fetch") - let g:spellfile_URL = 'http://ftp.vim.org/pub/vim/runtime/spell' - else - let g:spellfile_URL = 'ftp://ftp.vim.org/pub/vim/runtime/spell' - endif + " Always use https:// because it's secure. The certificate is for nluug.nl, + " thus we can't use the alias ftp.vim.org here. + let g:spellfile_URL = 'https://ftp.nluug.nl/pub/vim/runtime/spell' endif let s:spellfile_URL = '' " Start with nothing so that s:donedict is reset. diff --git a/runtime/compiler/ts-node.vim b/runtime/compiler/ts-node.vim new file mode 100644 index 0000000000..14f0ea790c --- /dev/null +++ b/runtime/compiler/ts-node.vim @@ -0,0 +1,29 @@ +" Vim compiler file +" Compiler: TypeScript Runner +" Maintainer: Doug Kearns <dougkearns@gmail.com> +" Last Change: 2020 Feb 10 + +if exists("current_compiler") + finish +endif +let current_compiler = "node" + +if exists(":CompilerSet") != 2 " older Vim always used :setlocal + command -nargs=* CompilerSet setlocal <args> +endif + +let s:cpo_save = &cpo +set cpo&vim + +" CompilerSet makeprg=npx\ ts-node + +CompilerSet makeprg=ts-node +CompilerSet errorformat=%f\ %#(%l\\,%c):\ %trror\ TS%n:\ %m, + \%E%f:%l, + \%+Z%\\w%\\+Error:\ %.%#, + \%C%p^%\\+, + \%C%.%#, + \%-G%.%# + +let &cpo = s:cpo_save +unlet s:cpo_save diff --git a/runtime/compiler/tsc.vim b/runtime/compiler/tsc.vim new file mode 100644 index 0000000000..a246fc7751 --- /dev/null +++ b/runtime/compiler/tsc.vim @@ -0,0 +1,26 @@ +" Vim compiler file +" Compiler: TypeScript Compiler +" Maintainer: Doug Kearns <dougkearns@gmail.com> +" Last Change: 2020 Feb 10 + +if exists("current_compiler") + finish +endif +let current_compiler = "tsc" + +if exists(":CompilerSet") != 2 " older Vim always used :setlocal + command -nargs=* CompilerSet setlocal <args> +endif + +let s:cpo_save = &cpo +set cpo&vim + +" CompilerSet makeprg=npx\ tsc + +CompilerSet makeprg=tsc +CompilerSet errorformat=%f\ %#(%l\\,%c):\ %trror\ TS%n:\ %m, + \%trror\ TS%n:\ %m, + \%-G%.%# + +let &cpo = s:cpo_save +unlet s:cpo_save diff --git a/runtime/compiler/xo.vim b/runtime/compiler/xo.vim new file mode 100644 index 0000000000..525657d4bb --- /dev/null +++ b/runtime/compiler/xo.vim @@ -0,0 +1,26 @@ +" Vim compiler file +" Compiler: XO +" Maintainer: Doug Kearns <dougkearns@gmail.com> +" Last Change: 2019 Jul 10 + +if exists("current_compiler") + finish +endif +let current_compiler = "xo" + +if exists(":CompilerSet") != 2 " older Vim always used :setlocal + command -nargs=* CompilerSet setlocal <args> +endif + +let s:cpo_save = &cpo +set cpo&vim + +" CompilerSet makeprg=npx\ xo\ --reporter\ compact + +CompilerSet makeprg=xo\ --reporter\ compact +CompilerSet errorformat=%f:\ line\ %l\\,\ col\ %c\\,\ %trror\ %m, + \%f:\ line\ %l\\,\ col\ %c\\,\ %tarning\ %m, + \%-G%.%# + +let &cpo = s:cpo_save +unlet s:cpo_save diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index ea3a8242ae..58633455c3 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -70,7 +70,7 @@ Nvim instance: nvim = MessagePack::RPC::Client.new(MessagePack::RPC::UNIXTransport.new, ENV['NVIM_LISTEN_ADDRESS']) result = nvim.call(:nvim_command, 'echo "hello world!"') < -A better way is to use the Python REPL with the `neovim` package, where API +A better way is to use the Python REPL with the "pynvim" package, where API functions can be called interactively: > >>> from pynvim import attach @@ -336,7 +336,7 @@ callbacks. These callbacks are called frequently in various contexts; |textlock| prevents changing buffer contents and window layout (use |vim.schedule| to defer such operations to the main loop instead). -|nvim_buf_attach| will take keyword args for the callbacks. "on_lines" will +|nvim_buf_attach()| will take keyword args for the callbacks. "on_lines" will receive parameters ("lines", {buf}, {changedtick}, {firstline}, {lastline}, {new_lastline}, {old_byte_size}[, {old_utf32_size}, {old_utf16_size}]). Unlike remote channel events the text contents are not passed. The new text can @@ -355,7 +355,7 @@ was changed. The parameters recieved are ("changedtick", {buf}, {changedtick}). *api-lua-detach* In-process Lua callbacks can detach by returning `true`. This will detach all -callbacks attached with the same |nvim_buf_attach| call. +callbacks attached with the same |nvim_buf_attach()| call. ============================================================================== @@ -475,6 +475,9 @@ created for extmark changes. ============================================================================== Global Functions *api-global* +nvim__get_hl_defs({ns_id}) *nvim__get_hl_defs()* + TODO: Documentation + nvim__get_lib_dir() *nvim__get_lib_dir()* TODO: Documentation @@ -529,13 +532,11 @@ nvim__id_float({flt}) *nvim__id_float()* nvim__inspect_cell({grid}, {row}, {col}) *nvim__inspect_cell()* TODO: Documentation - *nvim__put_attr()* -nvim__put_attr({id}, {start_row}, {start_col}, {end_row}, {end_col}) - Set attrs in nvim__buf_set_lua_hl callbacks +nvim__screenshot({path}) *nvim__screenshot()* + TODO: Documentation - TODO(bfredl): This is rather pedestrian. The final interface - should probably be derived from a reformed bufhl/virttext - interface with full support for multi-line ranges etc + Attributes: ~ + {fast} nvim__stats() *nvim__stats()* Gets internal stats. @@ -734,13 +735,14 @@ nvim_feedkeys({keys}, {mode}, {escape_csi}) *nvim_feedkeys()* On execution error: does not fail, but updates v:errmsg. - If you need to input sequences like <C-o> use nvim_replace_termcodes - to replace the termcodes and then pass the resulting string to - nvim_feedkeys. You'll also want to enable escape_csi. + If you need to input sequences like <C-o> use + |nvim_replace_termcodes| to replace the termcodes and then + pass the resulting string to nvim_feedkeys. You'll also want + to enable escape_csi. Example: > - :let key = nvim_replace_termcodes("<C-o>", v:true, v:false, v:true) - :call nvim_feedkeys(key, 'n', v:true) + :let key = nvim_replace_termcodes("<C-o>", v:true, v:false, v:true) + :call nvim_feedkeys(key, 'n', v:true) < Parameters: ~ @@ -953,6 +955,9 @@ nvim_get_runtime_file({name}, {all}) *nvim_get_runtime_file()* It is not an error to not find any files. An empty array is returned then. + Attributes: ~ + {fast} + Parameters: ~ {name} pattern of files to search for {all} whether to return all matches or only the first @@ -988,6 +993,7 @@ nvim_input({keys}) *nvim_input()* 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 @@ -1379,8 +1385,7 @@ nvim_select_popupmenu_item({item}, {insert}, {finish}, {opts}) {opts} Optional parameters. Reserved for future use. *nvim_set_client_info()* -nvim_set_client_info({name}, {version}, {type}, {methods}, - {attributes}) +nvim_set_client_info({name}, {version}, {type}, {methods}, {attributes}) Self-identifies the client. The client/plugin/application should call this after @@ -1473,6 +1478,80 @@ nvim_set_current_win({window}) *nvim_set_current_win()* Parameters: ~ {window} Window handle + *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. Similarily, 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. + + 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 interation with fold lines is + subject to change) ["win", winid, bufnr, row] + โข on_end: called at the end of a redraw cycle + ["end", tick] + +nvim_set_hl({ns_id}, {name}, {val}) *nvim_set_hl()* + Set a highlight group. + + TODO: ns_id = 0, should modify :highlight namespace TODO val + should take update vs reset flag + + Parameters: ~ + {ns_id} number of namespace for this highlight + {name} highlight group name, like ErrorMsg + {val} highlight definiton map, like + |nvim_get_hl_by_name|. + +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: ~ + {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. @@ -1564,34 +1643,16 @@ affected. You can use |nvim_buf_is_loaded()| or |nvim_buf_line_count()| to check whether a buffer is loaded. - *nvim__buf_add_decoration()* -nvim__buf_add_decoration({buffer}, {ns_id}, {hl_group}, {start_row}, - {start_col}, {end_row}, {end_col}, - {virt_text}) - TODO: Documentation - *nvim__buf_redraw_range()* nvim__buf_redraw_range({buffer}, {first}, {last}) TODO: Documentation -nvim__buf_set_luahl({buffer}, {opts}) *nvim__buf_set_luahl()* - Unstabilized interface for defining syntax hl in lua. - - This is not yet safe for general use, lua callbacks will need - to be restricted, like textlock and probably other stuff. - - The API on_line/nvim__put_attr is quite raw and not intended - to be the final shape. Ideally this should operate on chunks - larger than a single line to reduce interpreter overhead, and - generate annotation objects (bufhl/virttext) on the fly but - using the same representation. - nvim__buf_stats({buffer}) *nvim__buf_stats()* TODO: Documentation *nvim_buf_add_highlight()* -nvim_buf_add_highlight({buffer}, {src_id}, {hl_group}, {line}, - {col_start}, {col_end}) +nvim_buf_add_highlight({buffer}, {src_id}, {hl_group}, {line}, {col_start}, + {col_end}) Adds a highlight to buffer. Useful for plugins that dynamically generate highlights to a @@ -1602,19 +1663,20 @@ nvim_buf_add_highlight({buffer}, {src_id}, {hl_group}, {line}, 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` . + 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. + |nvim_create_namespace()| to create a new empty namespace. Parameters: ~ {buffer} Buffer handle, or 0 for current buffer @@ -1688,6 +1750,29 @@ nvim_buf_attach({buffer}, {send_buffer}, {opts}) *nvim_buf_attach()* |nvim_buf_detach()| |api-buffer-updates-lua| +nvim_buf_call({buffer}, {fun}) *nvim_buf_call()* + 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 (calleed 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()|. + + 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. + *nvim_buf_clear_namespace()* nvim_buf_clear_namespace({buffer}, {ns_id}, {line_start}, {line_end}) Clears namespaced objects (highlights, extmarks, virtual text) @@ -1731,6 +1816,17 @@ nvim_buf_del_var({buffer}, {name}) *nvim_buf_del_var()* {buffer} Buffer handle, or 0 for current buffer {name} Variable name +nvim_buf_delete({buffer}, {opts}) *nvim_buf_delete()* + Deletes the buffer. See |:bwipeout| + + 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. @@ -1765,13 +1861,16 @@ nvim_buf_get_commands({buffer}, {opts}) *nvim_buf_get_commands()* Map of maps describing commands. *nvim_buf_get_extmark_by_id()* -nvim_buf_get_extmark_by_id({buffer}, {ns_id}, {id}) +nvim_buf_get_extmark_by_id({buffer}, {ns_id}, {id}, {opts}) Returns position for a given extmark id Parameters: ~ {buffer} Buffer handle, or 0 for current buffer {ns_id} Namespace id from |nvim_create_namespace()| {id} Extmark id + {opts} Optional parameters. Keys: + โข limit: Maximum number of marks to return + โข details Whether to include the details dict Return: ~ (row, col) tuple or empty list () if extmark id was absent @@ -1820,6 +1919,7 @@ nvim_buf_get_extmarks({buffer}, {ns_id}, {start}, {end}, {opts}) extmark id (whose position defines the bound) {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 @@ -1918,28 +2018,6 @@ nvim_buf_get_var({buffer}, {name}) *nvim_buf_get_var()* Return: ~ Variable value - *nvim_buf_get_virtual_text()* -nvim_buf_get_virtual_text({buffer}, {line}) - Get the virtual text (annotation) for a buffer line. - - The virtual text is returned as list of lists, whereas the - inner lists have either one or two elements. The first element - is the actual text, the optional second element is the - highlight group. - - The format is exactly the same as given to - nvim_buf_set_virtual_text(). - - If there is no virtual text associated with the given line, an - empty list is returned. - - Parameters: ~ - {buffer} Buffer handle, or 0 for current buffer - {line} Line to get the virtual text from (zero-indexed) - - Return: ~ - List of virtual text chunks - 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. @@ -1973,22 +2051,40 @@ nvim_buf_line_count({buffer}) *nvim_buf_line_count()* Line count, or 0 for unloaded buffer. |api-buffer| *nvim_buf_set_extmark()* -nvim_buf_set_extmark({buffer}, {ns_id}, {id}, {line}, {col}, {opts}) +nvim_buf_set_extmark({buffer}, {ns_id}, {line}, {col}, {opts}) Creates or updates an extmark. To create a new extmark, pass id=0. The extmark id will be - returned. It is also allowed to create a new mark by passing - in a previously unused id, but the caller must then keep track - of existing and unused ids itself. (Useful over RPC, to avoid + returned. To move an existing mark, pass its id. + + It is also allowed to create a new mark by passing in a + previously unused id, but 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()| - {id} Extmark id, or 0 to create new {line} Line number where to place the mark {col} Column where to place the mark - {opts} Optional parameters. Currently not used. + {opts} Optional parameters. + โข id : id of the extmark to edit. + โข end_line : ending line of the mark, 0-based + inclusive. + โข end_col : ending col of the mark, 0-based + inclusive. + โข hl_group : name of the highlight group used to + highlight this mark. + โข virt_text : virtual text to link to this mark. + โข 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. Return: ~ Id of the created/updated extmark @@ -2004,8 +2100,7 @@ nvim_buf_set_keymap({buffer}, {mode}, {lhs}, {rhs}, {opts}) |nvim_set_keymap()| *nvim_buf_set_lines()* -nvim_buf_set_lines({buffer}, {start}, {end}, {strict_indexing}, - {replacement}) +nvim_buf_set_lines({buffer}, {start}, {end}, {strict_indexing}, {replacement}) Sets (replaces) a line-range in the buffer. Indexing is zero-based, end-exclusive. Negative indices are @@ -2053,8 +2148,7 @@ nvim_buf_set_var({buffer}, {name}, {value}) *nvim_buf_set_var()* {value} Variable value *nvim_buf_set_virtual_text()* -nvim_buf_set_virtual_text({buffer}, {src_id}, {line}, {chunks}, - {opts}) +nvim_buf_set_virtual_text({buffer}, {src_id}, {line}, {chunks}, {opts}) Set the virtual text (annotation) for a buffer line. By default (and currently the only option) the text will be @@ -2065,12 +2159,12 @@ nvim_buf_set_virtual_text({buffer}, {src_id}, {line}, {chunks}, Namespaces are used to support batch deletion/updating of virtual text. To create a namespace, use - |nvim_create_namespace|. Virtual text is cleared using - |nvim_buf_clear_namespace|. The same `ns_id` can be used for + |nvim_create_namespace()|. Virtual text is cleared using + |nvim_buf_clear_namespace()|. The same `ns_id` can be used for both virtual text and highlights added by - |nvim_buf_add_highlight|, both can then be cleared with a - single call to |nvim_buf_clear_namespace|. If the virtual text - never will be cleared by an API call, pass `ns_id = -1` . + |nvim_buf_add_highlight()|, both can then be cleared with a + single call to |nvim_buf_clear_namespace()|. If the virtual + text never will be cleared by an API call, pass `ns_id = -1` . As a shorthand, `ns_id = 0` can be used to create a new namespace for the virtual text, the allocated id is then @@ -2386,8 +2480,8 @@ nvim_ui_pum_set_bounds({width}, {height}, {row}, {col}) 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 decorations such as - boarders and sliders. Floats need not use the same font size, + 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. diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt index f1753b75cc..01b21aa085 100644 --- a/runtime/doc/autocmd.txt +++ b/runtime/doc/autocmd.txt @@ -206,169 +206,9 @@ autocommands, this doesn't happen. You can use the 'eventignore' option to ignore a number of events or all events. - *autocommand-events* *{event}* -Vim recognizes the following events. Vim ignores the case of event names -(e.g., you can use "BUFread" or "bufread" instead of "BufRead"). - -First an overview by function with a short explanation. Then the list -alphabetically with full explanations |autocmd-events-abc|. - -Name triggered by ~ - - Reading -|BufNewFile| starting to edit a file that doesn't exist -|BufReadPre| starting to edit a new buffer, before reading the file -|BufRead| starting to edit a new buffer, after reading the file -|BufReadPost| starting to edit a new buffer, after reading the file -|BufReadCmd| before starting to edit a new buffer |Cmd-event| - -|FileReadPre| before reading a file with a ":read" command -|FileReadPost| after reading a file with a ":read" command -|FileReadCmd| before reading a file with a ":read" command |Cmd-event| - -|FilterReadPre| before reading a file from a filter command -|FilterReadPost| after reading a file from a filter command - -|StdinReadPre| before reading from stdin into the buffer -|StdinReadPost| After reading from the stdin into the buffer - - Writing -|BufWrite| starting to write the whole buffer to a file -|BufWritePre| starting to write the whole buffer to a file -|BufWritePost| after writing the whole buffer to a file -|BufWriteCmd| before writing the whole buffer to a file |Cmd-event| - -|FileWritePre| starting to write part of a buffer to a file -|FileWritePost| after writing part of a buffer to a file -|FileWriteCmd| before writing part of a buffer to a file |Cmd-event| - -|FileAppendPre| starting to append to a file -|FileAppendPost| after appending to a file -|FileAppendCmd| before appending to a file |Cmd-event| - -|FilterWritePre| starting to write a file for a filter command or diff -|FilterWritePost| after writing a file for a filter command or diff - - Buffers -|BufAdd| just after adding a buffer to the buffer list -|BufDelete| before deleting a buffer from the buffer list -|BufWipeout| before completely deleting a buffer - -|BufFilePre| before changing the name of the current buffer -|BufFilePost| after changing the name of the current buffer - -|BufEnter| after entering a buffer -|BufLeave| before leaving to another buffer -|BufWinEnter| after a buffer is displayed in a window -|BufWinLeave| before a buffer is removed from a window - -|BufUnload| before unloading a buffer -|BufHidden| just after a buffer has become hidden -|BufNew| just after creating a new buffer - -|SwapExists| detected an existing swap file -|TermOpen| starting a terminal job -|TermEnter| entering Terminal-mode -|TermLeave| leaving Terminal-mode -|TermClose| stopping a terminal job -|ChanOpen| after a channel opened -|ChanInfo| after a channel has its state changed - - Options -|FileType| when the 'filetype' option has been set -|Syntax| when the 'syntax' option has been set -|OptionSet| after setting any option - - Startup and exit -|VimEnter| after doing all the startup stuff -|UIEnter| after a UI attaches -|UILeave| after a UI detaches -|TermResponse| after the terminal response to t_RV is received -|QuitPre| when using `:quit`, before deciding whether to exit -|ExitPre| when using a command that may make Vim exit -|VimLeavePre| before exiting Nvim, before writing the shada file -|VimLeave| before exiting Nvim, after writing the shada file -|VimResume| after Nvim is resumed -|VimSuspend| before Nvim is suspended - - Various -|DiffUpdated| after diffs have been updated -|DirChanged| after the |current-directory| was changed - -|FileChangedShell| Vim notices that a file changed since editing started -|FileChangedShellPost| after handling a file changed since editing started -|FileChangedRO| before making the first change to a read-only file - -|ShellCmdPost| after executing a shell command -|ShellFilterPost| after filtering with a shell command - -|CmdUndefined| a user command is used but it isn't defined -|FuncUndefined| a user function is used but it isn't defined -|SpellFileMissing| a spell file is used but it can't be found -|SourcePre| before sourcing a Vim script -|SourcePost| after sourcing a Vim script -|SourceCmd| before sourcing a Vim script |Cmd-event| - -|VimResized| after the Vim window size changed -|FocusGained| Nvim got focus -|FocusLost| Nvim lost focus -|CursorHold| the user doesn't press a key for a while -|CursorHoldI| the user doesn't press a key for a while in Insert mode -|CursorMoved| the cursor was moved in Normal mode -|CursorMovedI| the cursor was moved in Insert mode - -|WinClosed| after closing a window -|WinNew| after creating a new window -|WinEnter| after entering another window -|WinLeave| before leaving a window -|TabEnter| after entering another tab page -|TabLeave| before leaving a tab page -|TabNew| when creating a new tab page -|TabNewEntered| after entering a new tab page -|TabClosed| after closing a tab page -|CmdlineChanged| after a change was made to the command-line text -|CmdlineEnter| after entering cmdline mode -|CmdlineLeave| before leaving cmdline mode -|CmdwinEnter| after entering the command-line window -|CmdwinLeave| before leaving the command-line window - -|InsertEnter| starting Insert mode -|InsertChange| when typing <Insert> while in Insert or Replace mode -|InsertLeave| when leaving Insert mode -|InsertCharPre| when a character was typed in Insert mode, before - inserting it - -|TextYankPost| when some text is yanked or deleted - -|TextChanged| after a change was made to the text in Normal mode -|TextChangedI| after a change was made to the text in Insert mode - when popup menu is not visible -|TextChangedP| after a change was made to the text in Insert mode - when popup menu visible - -|ColorSchemePre| before loading a color scheme -|ColorScheme| after loading a color scheme - -|RemoteReply| a reply from a server Vim was received - -|QuickFixCmdPre| before a quickfix command is run -|QuickFixCmdPost| after a quickfix command is run - -|SessionLoadPost| after loading a session file - -|MenuPopup| just before showing the popup menu -|CompleteChanged| after popup menu changed, not fired on popup menu hide -|CompleteDonePre| after Insert mode completion is done, before clearing - info -|CompleteDone| after Insert mode completion is done, after clearing - info - -|User| to be used in combination with ":doautocmd" -|Signal| after Nvim receives a signal - - - -The alphabetical list of autocommand events: *autocmd-events-abc* + + *events* *{event}* +Nvim recognizes the following events. Names are case-insensitive. *BufAdd* BufAdd Just after creating a new buffer which is @@ -413,6 +253,9 @@ BufLeave Before leaving to another buffer. Also when new current window is not for the same buffer. Not used for ":qa" or ":q" when exiting Vim. + *BufModifiedSet* +BufModifiedSet After the `'modified'` value of a buffer has + been changed. *BufNew* BufNew Just after creating a new buffer. Also used just after a buffer has been renamed. When @@ -642,7 +485,7 @@ CursorHold When the user doesn't press a key for the time Hint: to force an update of the status lines use: > :let &ro = &ro - +< *CursorHoldI* CursorHoldI Like CursorHold, but in Insert mode. Not triggered when waiting for another key, e.g. @@ -675,6 +518,8 @@ DirChanged After the |current-directory| was changed. Sets these |v:event| keys: cwd: current working directory scope: "global", "tab", "window" + changed_window: v:true if we fired the event + switching window (or tab) Non-recursive (event cannot trigger itself). *FileAppendCmd* FileAppendCmd Before appending to a file. Should do the @@ -860,9 +705,14 @@ InsertEnter Just before starting Insert mode. Also for The cursor is restored afterwards. If you do not want that set |v:char| to a non-empty string. + *InsertLeavePre* +InsertLeavePre Just before leaving Insert mode. Also when + using CTRL-O |i_CTRL-O|. Be caseful not to + change mode or use `:normal`, it will likely + cause trouble. *InsertLeave* -InsertLeave When leaving Insert mode. Also when using - CTRL-O |i_CTRL-O|. But not for |i_CTRL-C|. +InsertLeave Just after leaving Insert mode. Also when + using CTRL-O |i_CTRL-O|. But not for |i_CTRL-C|. *MenuPopup* MenuPopup Just before showing the popup menu (under the right mouse button). Useful for adjusting the @@ -1111,13 +961,13 @@ VimEnter After doing all the startup stuff, including if v:vim_did_enter call s:init() else - au VimEnter * call s:init() + au VimEnter * call s:init() endif < *VimLeave* VimLeave Before exiting Vim, just after writing the .shada file. Executed only once, like VimLeavePre. -< Use |v:dying| to detect an abnormal exit. + Use |v:dying| to detect an abnormal exit. Use |v:exiting| to get the exit code. Not triggered if |v:dying| is 2 or more. *VimLeavePre* @@ -1126,7 +976,7 @@ VimLeavePre Before exiting Vim, just before writing the if there is a match with the name of what happens to be the current buffer when exiting. Mostly useful with a "*" pattern. > - :autocmd VimLeavePre * call CleanupStuff() + :autocmd VimLeavePre * call CleanupStuff() < Use |v:dying| to detect an abnormal exit. Use |v:exiting| to get the exit code. Not triggered if |v:dying| is 2 or more. @@ -1166,6 +1016,10 @@ WinLeave Before leaving a window. If the window to be WinNew When a new window was created. Not done for the first window, when Vim has just started. Before WinEnter. + *WinScrolled* +WinScrolled After scrolling the viewport of the current + window. + ============================================================================== 6. Patterns *autocmd-pattern* *{pat}* diff --git a/runtime/doc/change.txt b/runtime/doc/change.txt index dcebbc524c..5c67359002 100644 --- a/runtime/doc/change.txt +++ b/runtime/doc/change.txt @@ -1615,6 +1615,10 @@ B When joining lines, don't insert a space between two multi-byte characters. Overruled by the 'M' flag. 1 Don't break a line after a one-letter word. It's broken before it instead (if possible). +] Respect textwidth rigorously. With this flag set, no line can be + longer than textwidth, unless line-break-prohibition rules make this + impossible. Mainly for CJK scripts and works only if 'encoding' is + "utf-8". j Where it makes sense, remove a comment leader when joining lines. For example, joining: int i; // the index ~ diff --git a/runtime/doc/cmdline.txt b/runtime/doc/cmdline.txt index b31177ce0e..562a1f23ac 100644 --- a/runtime/doc/cmdline.txt +++ b/runtime/doc/cmdline.txt @@ -224,6 +224,10 @@ CTRL-[ *c_CTRL-[* *c_<Esc>* *c_Esc* present in 'cpoptions', start entered command. Note: If your <Esc> key is hard to hit on your keyboard, train yourself to use CTRL-[. + *c_META* *c_ALT* + ALT (|META|) acts like <Esc> if the chord is not mapped. + For example <A-x> acts like <Esc>x if <A-x> does not have a + command-line mode mapping. *c_CTRL-C* CTRL-C quit command-line without executing @@ -556,6 +560,7 @@ followed by another Vim command: :lfdo :make :normal + :perlfile :promptfind :promptrepl :pyfile @@ -968,7 +973,7 @@ name). This is included for backwards compatibility with version 3.0, the Note: Where a file name is expected wildcards expansion is done. On Unix the shell is used for this, unless it can be done internally (for speed). -Unless in |restricted-mode|, backticks work also, like in > +Backticks work also, like in > :n `echo *.c` But expansion is only done if there are any wildcards before expanding the '%', '#', etc.. This avoids expanding wildcards inside a file name. If you diff --git a/runtime/doc/deprecated.txt b/runtime/doc/deprecated.txt index ce075e1bee..3b5287ee44 100644 --- a/runtime/doc/deprecated.txt +++ b/runtime/doc/deprecated.txt @@ -77,7 +77,9 @@ Options ~ *'fe'* 'fenc'+'enc' before Vim 6.0; no longer used. *'highlight'* *'hl'* Names of builtin |highlight-groups| cannot be changed. *'langnoremap'* Deprecated alias to 'nolangremap'. +'sessionoptions' Flags "unix", "slash" are ignored and always enabled. *'vi'* +'viewoptions' Flags "unix", "slash" are ignored and always enabled. *'viminfo'* Deprecated alias to 'shada' option. *'viminfofile'* Deprecated alias to 'shadafile' option. diff --git a/runtime/doc/develop.txt b/runtime/doc/develop.txt index 09c5b7c4ad..aec0178da2 100644 --- a/runtime/doc/develop.txt +++ b/runtime/doc/develop.txt @@ -103,8 +103,10 @@ Examples: The provider framework invokes VimL from C. It is composed of two functions in eval.c: -- eval_call_provider(name, method, arguments): calls provider#{name}#Call - with the method and arguments. +- eval_call_provider(name, method, arguments, discard): calls + provider#{name}#Call with the method and arguments. If discard is true, any + value returned by the provider will be discarded and and empty value be + returned. - eval_has_provider(name): Checks the `g:loaded_{name}_provider` variable which must be set to 2 by the provider script to indicate that it is "enabled and working". Called by |has()| to check if features are available. diff --git a/runtime/doc/diff.txt b/runtime/doc/diff.txt index dcc3035d41..20eaa47b26 100644 --- a/runtime/doc/diff.txt +++ b/runtime/doc/diff.txt @@ -20,8 +20,8 @@ additionally sets up for viewing the differences between the arguments. > nvim -d file1 file2 [file3 [file4]] -In addition to the |-d| argument, |-Z| and |-R| may be used for restricted -mode and readonly mode respectively. +In addition to the |-d| argument, |-R| may be used for readonly mode +respectively. The second and following arguments may also be a directory name. Vim will then append the file name of the first argument to the directory name to find diff --git a/runtime/doc/digraph.txt b/runtime/doc/digraph.txt index 7f807b5eee..271b8c2597 100644 --- a/runtime/doc/digraph.txt +++ b/runtime/doc/digraph.txt @@ -913,6 +913,7 @@ char digraph hex dec official name ~ โ 9" 201F 8223 DOUBLE HIGH-REVERSED-9 QUOTATION MARK โ /- 2020 8224 DAGGER โก /= 2021 8225 DOUBLE DAGGER +โข oo 2022 8226 BULLET โฅ .. 2025 8229 TWO DOT LEADER โฆ ,. 2026 8230 HORIZONTAL ELLIPSIS โฐ %0 2030 8240 PER MILLE SIGN diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index efb6272e58..7c6013f1b2 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -38,7 +38,9 @@ List An ordered sequence of items |List|. Dictionary An associative, unordered array: Each entry has a key and a value. |Dictionary| - Example: {'blue': "#0000ff", 'red': "#ff0000"} + Examples: + {'blue': "#0000ff", 'red': "#ff0000"} + #{blue: "#0000ff", red: "#ff0000"} The Number and String types are converted automatically, depending on how they are used. @@ -436,8 +438,14 @@ only appear once. Examples: > A key is always a String. You can use a Number, it will be converted to a String automatically. Thus the String '4' and the number 4 will find the same entry. Note that the String '04' and the Number 04 are different, since the -Number will be converted to the String '4'. The empty string can be used as a -key. +Number will be converted to the String '4'. The empty string can also be used +as a key. + *literal-Dict* +To avoid having to put quotes around every key the #{} form can be used. This +does require the key to consist only of ASCII letters, digits, '-' and '_'. +Example: > + let mydict = #{zero: 0, one_key: 1, two-key: 2, 333: 3} +Note that 333 here is the string "333". Empty keys are not possible with #{}. A value can be any expression. Using a Dictionary for a value creates a nested Dictionary: > @@ -1606,6 +1614,8 @@ v:event Dictionary of event data for the current |autocommand|. Valid |CompleteChanged|. scrollbar Is |v:true| if popup menu have scrollbar, or |v:false| if not. + changed_window Is |v:true| if the the event fired + while changing window (or tab) on |DirChanged|. *v:exception* *exception-variable* v:exception The value of the exception most recently caught and not @@ -2224,6 +2234,7 @@ inputsecret({prompt} [, {text}]) String like input() but hiding the text insert({list}, {item} [, {idx}]) List insert {item} in {list} [before {idx}] +interrupt() none interrupt script execution invert({expr}) Number bitwise invert isdirectory({directory}) Number |TRUE| if {directory} is a directory isinf({expr}) Number determine if {expr} is infinity value @@ -2287,6 +2298,7 @@ nr2char({expr}[, {utf8}]) String single char with ASCII/UTF8 value {expr} nvim_...({args}...) any call nvim |api| functions or({expr}, {expr}) Number bitwise OR pathshorten({expr}) String shorten directory names in a path +perleval({expr}) any evaluate |perl| expression pow({x}, {y}) Float {x} to the power of {y} prevnonblank({lnum}) Number line nr of non-blank line <= {lnum} printf({fmt}, {expr1}...) String format text @@ -2326,6 +2338,7 @@ repeat({expr}, {count}) String repeat {expr} {count} times resolve({filename}) String get filename a shortcut points to reverse({list}) List reverse {list} in-place round({expr}) Float round off {expr} +rubyeval({expr}) any evaluate |Ruby| expression rpcnotify({channel}, {event}[, {args}...]) Sends an |RPC| notification to {channel} rpcrequest({channel}, {method}[, {args}...]) @@ -2408,7 +2421,8 @@ str2list({expr} [, {utf8}]) List convert each character of {expr} to str2nr({expr} [, {base}]) Number convert String to Number strchars({expr} [, {skipcc}]) Number character length of the String {expr} strcharpart({str}, {start} [, {len}]) - String {len} characters of {str} at {start} + String {len} characters of {str} at + character {start} strdisplaywidth({expr} [, {col}]) Number display length of the String {expr} strftime({format} [, {time}]) String time in specified format strgetchar({str}, {index}) Number get char {index} from {str} @@ -2416,8 +2430,9 @@ stridx({haystack}, {needle} [, {start}]) Number index of {needle} in {haystack} string({expr}) String String representation of {expr} value strlen({expr}) Number length of the String {expr} -strpart({str}, {start} [, {len}]) - String {len} characters of {str} at {start} +strpart({str}, {start} [, {len} [, {chars}]]) + String {len} bytes/chars of {str} at + byte {start} strridx({haystack}, {needle} [, {start}]) Number last index of {needle} in {haystack} strtrans({expr}) String translate string to make it printable @@ -2456,7 +2471,8 @@ tolower({expr}) String the String {expr} switched to lowercase toupper({expr}) String the String {expr} switched to uppercase tr({src}, {fromstr}, {tostr}) String translate chars of {src} in {fromstr} to chars in {tostr} -trim({text} [, {mask}]) String trim characters in {mask} from {text} +trim({text} [, {mask} [, {dir}]]) + String trim characters in {mask} from {text} trunc({expr}) Float truncate Float {expr} type({name}) Number type of variable {name} undofile({name}) String undo file name for {name} @@ -2905,7 +2921,8 @@ byte2line({byte}) *byte2line()* byteidx({expr}, {nr}) *byteidx()* Return byte index of the {nr}'th character in the string - {expr}. Use zero for the first character, it returns zero. + {expr}. Use zero for the first character, it then returns + zero. This function is only useful when there are multibyte characters, otherwise the returned value is equal to {nr}. Composing characters are not counted separately, their byte @@ -3826,7 +3843,7 @@ feedkeys({string} [, {mode}]) *feedkeys()* stuck, waiting for a character to be typed before the script continues. Note that if you manage to call feedkeys() while - executing commands, thus calling it recursively, the + executing commands, thus calling it recursively, then all typehead will be consumed by the last call. '!' When used with 'x' will not end Insert mode. Can be used in a test when a timer is set to exit Insert mode @@ -4629,7 +4646,7 @@ getloclist({nr},[, {what}]) *getloclist()* If {what} contains 'filewinid', then returns the id of the window used to display files from the location list. This field is applicable only when called from a location list - window. + window. See |location-list-file-window| for more details. getmatches([{win}]) *getmatches()* Returns a |List| with all matches previously defined for the @@ -4720,7 +4737,9 @@ getqflist([{what}]) *getqflist()* id get information for the quickfix list with |quickfix-ID|; zero means the id for the current list or the list specified by "nr" - idx index of the current entry in the list + idx index of the current entry in the quickfix + list specified by 'id' or 'nr'. + See |quickfix-index| items quickfix list entries lines parse a list of lines using 'efm' and return the resulting entries. Only a |List| type is @@ -4913,6 +4932,19 @@ getwinpos([{timeout}]) *getwinpos()* {timeout} can be used to specify how long to wait in msec for a response from the terminal. When omitted 100 msec is used. + Use a longer time for a remote terminal. + When using a value less than 10 and no response is received + within that time, a previously reported position is returned, + if available. This can be used to poll for the position and + do some work in the meantime: > + while 1 + let res = getwinpos(1) + if res[0] >= 0 + break + endif + " Do some work here + endwhile +< *getwinposx()* getwinposx() The result is a Number, which is the X coordinate in pixels of the left hand side of the GUI Vim window. The result will be @@ -5412,6 +5444,19 @@ insert({list}, {item} [, {idx}]) *insert()* Note that when {item} is a |List| it is inserted as a single item. Use |extend()| to concatenate |Lists|. +interrupt() *interrupt()* + Interrupt script execution. It works more or less like the + user typing CTRL-C, most commands won't execute and control + returns to the user. This is useful to abort execution + from lower down, e.g. in an autocommand. Example: > + :function s:check_typoname(file) + : if fnamemodify(a:file, ':t') == '[' + : echomsg 'Maybe typo' + : call interrupt() + : endif + :endfunction + :au BufWritePre * call s:check_typoname(expand('<amatch>')) + invert({expr}) *invert()* Bitwise invert. The argument is converted to a number. A List, Dict or Float argument causes an error. Example: > @@ -5653,7 +5698,6 @@ libcall({libname}, {funcname}, {argument}) If {argument} is a number, it is passed to the function as an int; if {argument} is a string, it is passed as a null-terminated string. - This function will fail in |restricted-mode|. libcall() allows you to write your own 'plug-in' extensions to Vim without having to recompile the program. It is NOT a @@ -6237,6 +6281,7 @@ mode([expr]) Return a string that indicates the current mode. nov Operator-pending (forced charwise |o_v|) noV Operator-pending (forced linewise |o_V|) noCTRL-V Operator-pending (forced blockwise |o_CTRL-V|) + CTRL-V is one character niI Normal using |i_CTRL-O| in |Insert-mode| niR Normal using |i_CTRL-O| in |Replace-mode| niV Normal using |i_CTRL-O| in |Virtual-Replace-mode| @@ -6406,6 +6451,21 @@ pathshorten({expr}) *pathshorten()* < ~/.c/n/a/file1.vim ~ It doesn't matter if the path exists or not. +perleval({expr}) *perleval()* + Evaluate |perl| expression {expr} and return its result + converted to Vim data structures. + Numbers and strings are returned as they are (strings are + copied though). + Lists are represented as Vim |List| type. + Dictionaries are represented as Vim |Dictionary| type, + non-string keys result in error. + + Note: If you want an array or hash, {expr} must return a + reference to it. + Example: > + :echo perleval('[1 .. 4]') +< [1, 2, 3, 4] + pow({x}, {y}) *pow()* Return the power of {x} to the exponent {y} as a |Float|. {x} and {y} must evaluate to a |Float| or a |Number|. @@ -7012,6 +7072,17 @@ rpcstart({prog}[, {argv}]) *rpcstart()* < with > :let id = jobstart(['prog', 'arg1', 'arg2'], {'rpc': v:true}) +rubyeval({expr}) *rubyeval()* + Evaluate Ruby expression {expr} and return its result + converted to Vim data structures. + Numbers, floats and strings are returned as they are (strings + are copied though). + Arrays are represented as Vim |List| type. + Hashes are represented as Vim |Dictionary| type. + Other objects are represented as strings resulted from their + "Object#to_s" method. + {only available when compiled with the |+ruby| feature} + screenattr({row}, {col}) *screenattr()* Like |screenchar()|, but return the attribute. This is a rather arbitrary number that can only be used to compare to the @@ -7581,16 +7652,22 @@ setqflist({list} [, {action}[, {what}]]) *setqflist()* efm errorformat to use when parsing text from "lines". If this is not present, then the 'errorformat' option value is used. + See |quickfix-parse| id quickfix list identifier |quickfix-ID| + idx index of the current entry in the quickfix + list specified by 'id' or 'nr'. If set to '$', + then the last entry in the list is set as the + current entry. See |quickfix-index| items list of quickfix entries. Same as the {list} argument. lines use 'errorformat' to parse a list of lines and add the resulting entries to the quickfix list {nr} or {id}. Only a |List| value is supported. + See |quickfix-parse| nr list number in the quickfix stack; zero means the current quickfix list and "$" means - the last quickfix list - title quickfix list title text + the last quickfix list. + title quickfix list title text. See |quickfix-title| Unsupported keys in {what} are ignored. If the "nr" item is not present, then the current quickfix list is modified. When creating a new quickfix list, "nr" can be @@ -7737,26 +7814,23 @@ sha256({string}) *sha256()* shellescape({string} [, {special}]) *shellescape()* Escape {string} for use as a shell command argument. - On Windows when 'shellslash' is not set, it - will enclose {string} in double quotes and double all double - quotes within {string}. - Otherwise, it will enclose {string} in single quotes and - replace all "'" with "'\''". - - When the {special} argument is present and it's a non-zero - Number or a non-empty String (|non-zero-arg|), then special - items such as "!", "%", "#" and "<cword>" will be preceded by - a backslash. This backslash will be removed again by the |:!| - command. - The "!" character will be escaped (again with a |non-zero-arg| - {special}) when 'shell' contains "csh" in the tail. That is - because for csh and tcsh "!" is used for history replacement - even when inside single quotes. + On Windows when 'shellslash' is not set, encloses {string} in + double-quotes and doubles all double-quotes within {string}. + Otherwise encloses {string} in single-quotes and replaces all + "'" with "'\''". - With a |non-zero-arg| {special} the <NL> character is also - escaped. When 'shell' containing "csh" in the tail it's - escaped a second time. + If {special} is a ||non-zero-arg|: + - Special items such as "!", "%", "#" and "<cword>" will be + preceded by a backslash. The backslash will be removed again + by the |:!| command. + - The <NL> character is escaped. + + If 'shell' contains "csh" in the tail: + - The "!" character will be escaped. This is because csh and + tcsh use "!" for history replacement even in single-quotes. + - The <NL> character is escaped (twice if {special} is + a ||non-zero-arg|). Example of use with a |:!| command: > :exe '!dir ' . shellescape(expand('<cfile>'), 1) @@ -8229,15 +8303,13 @@ sqrt({expr}) *sqrt()* stdioopen({opts}) *stdioopen()* - In a nvim launched with the |--headless| option, this opens - stdin and stdout as a |channel|. This function can only be - invoked once per instance. See |channel-stdio| for more - information and examples. Note that stderr is not handled by - this function, see |v:stderr|. + With |--headless| this opens stdin and stdout as a |channel|. + May be called only once. See |channel-stdio|. stderr is not + handled by this function, see |v:stderr|. - Returns a |channel| ID. Close the stdio descriptors with |chanclose()|. - Use |chansend()| to send data to stdout, and - |rpcrequest()| and |rpcnotify()| to communicate over RPC. + Close the stdio handles with |chanclose()|. Use |chansend()| + to send data to stdout, and |rpcrequest()| and |rpcnotify()| + to communicate over RPC. {opts} is a dictionary with these keys: |on_stdin| : callback invoked when stdin is written to. @@ -8245,7 +8317,7 @@ stdioopen({opts}) *stdioopen()* rpc : If set, |msgpack-rpc| will be used to communicate over stdio Returns: - - The channel ID on success (this is always 1) + - |channel-id| on success (value is always 1) - 0 on invalid arguments @@ -8424,14 +8496,19 @@ strlen({expr}) The result is a Number, which is the length of the String {expr} in bytes. If the argument is a Number it is first converted to a String. For other types an error is given. - If you want to count the number of multi-byte characters use + If you want to count the number of multibyte characters use |strchars()|. Also see |len()|, |strdisplaywidth()| and |strwidth()|. -strpart({src}, {start} [, {len}]) *strpart()* +strpart({src}, {start} [, {len} [, {chars}]]) *strpart()* The result is a String, which is part of {src}, starting from byte {start}, with the byte length {len}. - To count characters instead of bytes use |strcharpart()|. + When {chars} is present and TRUE then {len} is the number of + characters positions (composing characters are not counted + separately, thus "1" means one base character and any + following composing characters). + To count {start} as characters instead of bytes use + |strcharpart()|. When bytes are selected which do not exist, this doesn't result in an error, the bytes are simply omitted. @@ -8443,8 +8520,8 @@ strpart({src}, {start} [, {len}]) *strpart()* strpart("abcdefg", 3) == "defg" < Note: To get the first character, {start} must be 0. For - example, to get three bytes under and after the cursor: > - strpart(getline("."), col(".") - 1, 3) + example, to get the character under the cursor: > + strpart(getline("."), col(".") - 1, 1, v:true) < strridx({haystack}, {needle} [, {start}]) *strridx()* The result is a Number, which gives the byte index in @@ -8726,7 +8803,6 @@ system({cmd} [, {input}]) *system()* *E677* {cmd} is a string: 'shell' 'shellcmdflag' {cmd} The resulting error code can be found in |v:shell_error|. - This function will fail in |restricted-mode|. Note that any wrong value in the options mentioned above may make the function fail. It has also been reported to fail @@ -8974,21 +9050,28 @@ tr({src}, {fromstr}, {tostr}) *tr()* echo tr("<blob>", "<>", "{}") < returns "{blob}" -trim({text} [, {mask}]) *trim()* +trim({text} [, {mask} [, {dir}]]) *trim()* Return {text} as a String where any character in {mask} is - removed from the beginning and end of {text}. + removed from the beginning and/or end of {text}. If {mask} is not given, {mask} is all characters up to 0x20, which includes Tab, space, NL and CR, plus the non-breaking space character 0xa0. - This code deals with multibyte characters properly. - + The optional {dir} argument specifies where to remove the + characters: + 0 remove from the beginning and end of {text} + 1 remove only at the beginning of {text} + 2 remove only at the end of {text} + When omitted both ends are trimmed. + This function deals with multibyte characters properly. Examples: > echo trim(" some text ") < returns "some text" > echo trim(" \r\t\t\r RESERVE \t\n\x0B\xA0") . "_TAIL" < returns "RESERVE_TAIL" > echo trim("rm<Xrm<>X>rrm", "rm<>") -< returns "Xrm<>X" (characters in the middle are not removed) +< returns "Xrm<>X" (characters in the middle are not removed) > + echo trim(" vim ", " ", 2) +< returns " vim" trunc({expr}) *trunc()* Return the largest integral value with magnitude less than or diff --git a/runtime/doc/help.txt b/runtime/doc/help.txt index a384b5f876..203699435b 100644 --- a/runtime/doc/help.txt +++ b/runtime/doc/help.txt @@ -137,7 +137,7 @@ Special issues ~ Programming language support ~ |indent.txt| automatic indenting for C and other languages -|lsp.txt| Language Server Protocol (LSP) +|lsp.txt| Language Server Protocol (LSP) |syntax.txt| syntax highlighting |filetype.txt| settings done specifically for a type of file |quickfix.txt| commands for a quick edit-compile-fix cycle @@ -168,9 +168,9 @@ Versions ~ |vi_diff.txt| Main differences between Vim and Vi *standard-plugin-list* Standard plugins ~ +|matchit.txt| Extended |%| matching |pi_gzip.txt| Reading and writing compressed files |pi_health.txt| Healthcheck framework -|pi_matchit.txt| Extended |%| matching |pi_msgpack.txt| msgpack utilities |pi_netrw.txt| Reading and writing files over a network |pi_paren.txt| Highlight matching parens diff --git a/runtime/doc/if_perl.txt b/runtime/doc/if_perl.txt new file mode 100644 index 0000000000..f1d07ddb20 --- /dev/null +++ b/runtime/doc/if_perl.txt @@ -0,0 +1,268 @@ +*if_perl.txt* Nvim + + + VIM REFERENCE MANUAL by Jacques Germishuys + +The perl Interface to Vim *if_perl* *perl* + +See |provider-perl| for more information. + + Type |gO| to see the table of contents. + +============================================================================== +1. Commands *perl-commands* + + *:perl* +:[range]perl {stmt} + Execute perl statement {stmt}. The current package is + "main". A simple check if the `:perl` command is + working: > + :perl print "Hello" + +:[range]perl << [endmarker] +{script} +{endmarker} + Execute perl script {script}. Useful for including + perl code in Vim scripts. Requires perl, see + |script-here|. + +The {endmarker} below the {script} must NOT be preceded by any white space. + +If [endmarker] is omitted from after the "<<", a dot '.' must be used after +{script}, like for the |:append| and |:insert| commands. + +Example: > + function! MyVimMethod() + perl << EOF + sub my_vim_method + { + print "Hello World!\n"; + } + EOF + endfunction + +To see what version of perl you have: > + + :perl print $^V +< + *:perldo* +:[range]perldo {cmd} Execute perl command {cmd} for each line in the[range], + with $_ being set to the test of each line in turn, + without a trailing <EOL>. In addition to $_, $line and + $linenr is also set to the line content and line number + respectively. Setting $_ will change the text, but note + that it is not possible to add or delete lines using + this command. + The default for [range] is the whole file: "1,$". + +Examples: +> + :perldo $_ = reverse($_); + :perldo $_ = "".$linenr." => $line"; + +One can use `:perldo` in conjunction with `:perl` to filter a range using +perl. For example: > + + :perl << EOF + sub perl_vim_string_replace + { + my $line = shift; + my $needle = $vim->eval('@a'); + my $replacement = $vim->eval('@b'); + $line =~ s/$needle/$replacement/g; + return $line; + } + EOF + :let @a='somevalue' + :let @b='newvalue' + :'<,'>perldo $_ = perl_vim_string_replace($_) +< + *:perlfile* +:[range]perlfile {file} + Execute the perl script in {file}. The whole + argument is used as a single file name. + +Both of these commands do essentially the same thing - they execute a piece of +perl code, with the "current range" set to the given line range. + +In the case of :perl, the code to execute is in the command-line. +In the case of :perlfile, the code to execute is the contents of the given file. + +perl commands cannot be used in the |sandbox|. + +To pass arguments you need to set @ARGV explicitly. Example: > + + :perl @ARGV = ("foo", "bar"); + :perlfile myscript.pl + +Here are some examples *perl-examples* > + + :perl print "Hello" + :perl $current->line (uc ($current->line)) + :perl my $str = $current->buffer->[42]; print "Set \$str to: $str" + +Note that changes (such as the "use" statements) persist from one command +to the next. + +============================================================================== +2. The VIM module *perl-vim* + +Perl code gets all of its access to Neovim via the "VIM" module. + +Overview > + print "Hello" # displays a message + VIM::Msg("Hello") # displays a message + VIM::SetOption("ai") # sets a vim option + $nbuf = VIM::Buffers() # returns the number of buffers + @buflist = VIM::Buffers() # returns array of all buffers + $mybuf = (VIM::Buffers('a.c'))[0] # returns buffer object for 'a.c' + @winlist = VIM::Windows() # returns array of all windows + $nwin = VIM::Windows() # returns the number of windows + ($success, $v) = VIM::Eval('&path') # $v: option 'path', $success: 1 + ($success, $v) = VIM::Eval('&xyz') # $v: '' and $success: 0 + $v = VIM::Eval('expand("<cfile>")') # expands <cfile> + $curwin->SetHeight(10) # sets the window height + @pos = $curwin->Cursor() # returns (row, col) array + @pos = (10, 10) + $curwin->Cursor(@pos) # sets cursor to @pos + $curwin->Cursor(10,10) # sets cursor to row 10 col 10 + $mybuf = $curwin->Buffer() # returns the buffer object for window + $curbuf->Name() # returns buffer name + $curbuf->Number() # returns buffer number + $curbuf->Count() # returns the number of lines + $l = $curbuf->Get(10) # returns line 10 + @l = $curbuf->Get(1 .. 5) # returns lines 1 through 5 + $curbuf->Delete(10) # deletes line 10 + $curbuf->Delete(10, 20) # delete lines 10 through 20 + $curbuf->Append(10, "Line") # appends a line + $curbuf->Append(10, "L1", "L2", "L3") # appends 3 lines + @l = ("L1", "L2", "L3") + $curbuf->Append(10, @l) # appends L1, L2 and L3 + $curbuf->Set(10, "Line") # replaces line 10 + $curbuf->Set(10, "Line1", "Line2") # replaces lines 10 and 11 + $curbuf->Set(10, @l) # replaces 3 lines + +Module Functions: + + *perl-Msg* +VIM::Msg({msg}) + Displays the message {msg}. + + *perl-SetOption* +VIM::SetOption({arg}) Sets a vim option. {arg} can be any argument that the + ":set" command accepts. Note that this means that no + spaces are allowed in the argument! See |:set|. + + *perl-Buffers* +VIM::Buffers([{bn}...]) With no arguments, returns a list of all the buffers + in an array context or returns the number of buffers + in a scalar context. For a list of buffer names or + numbers {bn}, returns a list of the buffers matching + {bn}, using the same rules as Vim's internal + |bufname()| function. + WARNING: the list becomes invalid when |:bwipe| is + used. + + *perl-Windows* +VIM::Windows([{wn}...]) With no arguments, returns a list of all the windows + in an array context or returns the number of windows + in a scalar context. For a list of window numbers + {wn}, returns a list of the windows with those + numbers. + WARNING: the list becomes invalid when a window is + closed. + + *perl-DoCommand* +VIM::DoCommand({cmd}) Executes Ex command {cmd}. + + *perl-Eval* +VIM::Eval({expr}) Evaluates {expr} and returns (success, value) in list + context or just value in scalar context. + success=1 indicates that val contains the value of + {expr}; success=0 indicates a failure to evaluate + the expression. '@x' returns the contents of register + x, '&x' returns the value of option x, 'x' returns the + value of internal |variables| x, and '$x' is equivalent + to perl's $ENV{x}. All |functions| accessible from + the command-line are valid for {expr}. + A |List| is turned into a string by joining the items + and inserting line breaks. + +============================================================================== +3. VIM::Buffer objects *perl-buffer* + +Methods: + + *perl-Buffer-Name* +Name() Returns the filename for the Buffer. + + *perl-Buffer-Number* +Number() Returns the number of the Buffer. + + *perl-Buffer-Count* +Count() Returns the number of lines in the Buffer. + + *perl-Buffer-Get* +Get({lnum}, {lnum}?, ...) + Returns a text string of line {lnum} in the Buffer + for each {lnum} specified. An array can be passed + with a list of {lnum}'s specified. + + *perl-Buffer-Delete* +Delete({lnum}, {lnum}?) + Deletes line {lnum} in the Buffer. With the second + {lnum}, deletes the range of lines from the first + {lnum} to the second {lnum}. + + *perl-Buffer-Append* +Append({lnum}, {line}, {line}?, ...) + Appends each {line} string after Buffer line {lnum}. + The list of {line}s can be an array. + + *perl-Buffer-Set* +Set({lnum}, {line}, {line}?, ...) + Replaces one or more Buffer lines with specified + {lines}s, starting at Buffer line {lnum}. The list of + {line}s can be an array. If the arguments are + invalid, replacement does not occur. + +============================================================================== +4. VIM::Window objects *perl-window* + +Methods: + *perl-Window-SetHeight* +SetHeight({height}) + Sets the Window height to {height}, within screen + limits. + + *perl-Window-GetCursor* +Cursor({row}?, {col}?) + With no arguments, returns a (row, col) array for the + current cursor position in the Window. With {row} and + {col} arguments, sets the Window's cursor position to + {row} and {col}. Note that {col} is numbered from 0, + Perl-fashion, and thus is one less than the value in + Vim's ruler. + +Buffer() *perl-Window-Buffer* + Returns the Buffer object corresponding to the given + Window. + +============================================================================== +5. Lexical variables *perl-globals* + +There are multiple lexical variables. + +$curwin The current Window object. +$curbuf The current Buffer object. +$vim A Neovim::Ext object. +$nvim The same as $nvim. +$current A Neovim::Ext::Current object. + +These are also available via the "main" package: + +$main::curwin The current Window object. +$main::curbuf The current Buffer object. + +============================================================================== + vim:tw=78:ts=8:noet:ft=help:norl: diff --git a/runtime/doc/if_ruby.txt b/runtime/doc/if_ruby.txt index 6468e4c81e..c8d2409549 100644 --- a/runtime/doc/if_ruby.txt +++ b/runtime/doc/if_ruby.txt @@ -136,7 +136,7 @@ self[{n}] Returns the buffer object for the number {n}. The first number Methods: -name Returns the name of the buffer. +name Returns the full name of the buffer. number Returns the number of the buffer. count Returns the number of lines. length Returns the number of lines. @@ -172,6 +172,7 @@ height = {n} Sets the window height to {n}. width Returns the width of the window. width = {n} Sets the window width to {n}. cursor Returns a [row, col] array for the cursor position. + First line number is 1 and first column number is 0. cursor = [{row}, {col}] Sets the cursor position to {row} and {col}. @@ -184,4 +185,13 @@ $curwin The current window object. $curbuf The current buffer object. ============================================================================== +6. rubyeval() Vim function *ruby-rubyeval* + +To facilitate bi-directional interface, you can use |rubyeval()| function to +evaluate Ruby expressions and pass their values to Vim script. + +The Ruby value "true", "false" and "nil" are converted to v:true, v:false and +v:null, respectively. + +============================================================================== vim:tw=78:ts=8:noet:ft=help:norl: diff --git a/runtime/doc/indent.txt b/runtime/doc/indent.txt index 1df0331239..f2278f8453 100644 --- a/runtime/doc/indent.txt +++ b/runtime/doc/indent.txt @@ -566,9 +566,15 @@ The examples below assume a 'shiftwidth' of 4. with "#" does not work. + PN When N is non-zero recognize C pragmas, and indent them like any + other code; does not concern other preprocessor directives. + When N is zero (default): don't recognize C pragmas, treating + them like every other preprocessor directive. + + The defaults, spelled out in full, are: cinoptions=>s,e0,n0,f0,{0,}0,^0,L-1,:s,=s,l0,b0,gs,hs,N0,E0,ps,ts,is,+s, - c3,C0,/0,(2s,us,U0,w0,W0,k0,m0,j0,J0,)20,*70,#0 + c3,C0,/0,(2s,us,U0,w0,W0,k0,m0,j0,J0,)20,*70,#0,P0 Vim puts a line in column 1 if: - It starts with '#' (preprocessor directives), if 'cinkeys' contains '#0'. diff --git a/runtime/doc/index.txt b/runtime/doc/index.txt index bdab10c0e4..afcacad460 100644 --- a/runtime/doc/index.txt +++ b/runtime/doc/index.txt @@ -1441,6 +1441,9 @@ tag command action ~ |:packloadall| :packl[oadall] load all packages under 'packpath' |:pclose| :pc[lose] close preview window |:pedit| :ped[it] edit file in the preview window +|:perl| :perl execute perl command +|:perldo| :perldo execute perl command for each line +|:perfile| :perlfile execute perl script file |:print| :p[rint] print lines |:profdel| :profd[el] stop profiling a function or script |:profile| :prof[ile] profiling functions and scripts diff --git a/runtime/doc/insert.txt b/runtime/doc/insert.txt index e53af5074b..c4b93a2a27 100644 --- a/runtime/doc/insert.txt +++ b/runtime/doc/insert.txt @@ -42,9 +42,9 @@ char action ~ abbreviation. Note: If your <Esc> key is hard to hit, try CTRL-[ instead. *i_META* *i_ALT* - ALT (|META|) acts like <Esc> if the chord is not mapped. + ALT (|META|) acts like <Esc> if the chord is not mapped. For example <A-x> acts like <Esc>x if <A-x> does not have an - insert-mode mapping. + insert-mode mapping. *i_CTRL-C* CTRL-C Quit insert mode, go back to Normal mode. Do not check for abbreviations. Does not trigger the |InsertLeave| autocommand diff --git a/runtime/doc/intro.txt b/runtime/doc/intro.txt index 3c3753df78..d858985e3f 100644 --- a/runtime/doc/intro.txt +++ b/runtime/doc/intro.txt @@ -4,21 +4,15 @@ NVIM REFERENCE MANUAL -Introduction to Vim *ref* *reference* +Nvim *ref* *reference* Type |gO| to see the table of contents. ============================================================================== Introduction *intro* -Vim stands for Vi IMproved. It used to be Vi IMitation, but there are so many -improvements that a name change was appropriate. Vim is a text editor which -includes almost all the commands from the Unix program "Vi" and a lot of new -ones. It is very useful for editing programs and other plain text. - All commands are given with the keyboard. This has the advantage that you -can keep your fingers on the keyboard and your eyes on the screen. For those -who want it, there is mouse support and a GUI version with scrollbars and -menus (see |gui.txt|). +Vim is a text editor which includes most commands from the Unix program "Vi" +and many new ones. An overview of this manual can be found in the file "help.txt", |help.txt|. It can be accessed from within Vim with the <Help> or <F1> key and with the @@ -28,16 +22,15 @@ is not located in the default place. You can jump to subjects like with tags: Use CTRL-] to jump to a subject under the cursor, use CTRL-T to jump back. *pronounce* -Vim is pronounced as one word, like Jim. Nvim is pronounced as N-vim, or, -continuing with the Jim simile, N-Jim, which sounds like Ninja. +Vim is pronounced as one word, like Jim. So Nvim is N-Jim, which sounds like +"Ninja". Starting Nvim is like performing a roundhouse kick. -This manual is a reference for all the Vim commands and options. This is not -an introduction to the use of Vi or Vim, it gets a bit complicated here and -there. For beginners, there is a hands-on |tutor|. To learn using Vim, read -the user manual |usr_toc.txt|. +This manual is a reference for all Nvim editor and API features. It is not an +introduction; instead for beginners, there is a hands-on |tutor| and a user +manual |usr_toc.txt|. *book* -There are many books on Vi and Vim. We recommend these books: +There are many books on Vi and Vim. We recommend: "Practical Vim" by Drew Neil "Modern Vim" by Drew Neil @@ -48,7 +41,7 @@ tasks with Vim. "Modern Vim" explores new features in Nvim and Vim 8. "Vim - Vi Improved" by Steve Oualline -This is the first book dedicated to Vim. Parts of it were included in the +This was the first book dedicated to Vim. Parts of it were included in the user manual. |frombook| ISBN: 0735710015 For more information try one of these: https://iccf-holland.org/click5.html @@ -63,11 +56,9 @@ Nvim on the interwebs *internet* Nvim FAQ: https://github.com/neovim/neovim/wiki/FAQ Downloads: https://github.com/neovim/neovim/releases Vim FAQ: https://vimhelp.appspot.com/vim_faq.txt.html - Vim home page: https://www.vim.org/ - *bugs* *bug-report* *bugreport.vim* *feature-request* - + *bugs* *bug-report* Report bugs and request features here: https://github.com/neovim/neovim/issues @@ -97,7 +88,7 @@ Neovim development is funded separately from Vim: https://neovim.io/#sponsor ============================================================================== -Credits *credits* *author* *Bram* *Moolenaar* +Credits *credits* Most of Vim was written by Bram Moolenaar <Bram@vim.org>. @@ -185,25 +176,21 @@ the ideas from all these people: They keep Vim alive! *love* *peace* *friendship* *gross-national-happiness* -In this documentation there are several references to other versions of Vi: +Documentation may refer to other versions of Vi: *Vi* *vi* Vi "the original". Without further remarks this is the version of Vi that appeared in Sun OS 4.x. ":version" returns - "Version 3.7, 6/7/85". Sometimes other versions are referred - to. Only runs under Unix. Source code only available with a - license. + "Version 3.7, 6/7/85". Source code only available with a license. *Nvi* Nvi The "New" Vi. The version of Vi that comes with BSD 4.4 and FreeBSD. Very good compatibility with the original Vi, with a few extensions. The version used is 1.79. ":version" returns "Version 1.79 - (10/23/96)". There has been no release the last few years, although - there is a development version 1.81. - Source code is freely available. + (10/23/96)". Source code is freely available. *Elvis* Elvis Another Vi clone, made by Steve Kirkendall. Very compact but isn't - as flexible as Vim. - The version used is 2.1. It is still being developed. Source code is - freely available. + as flexible as Vim. Source code is freely available. + +Vim Nvim is based on Vim. https://www.vim.org/ ============================================================================== Notation *notation* @@ -387,37 +374,26 @@ notation meaning equivalent decimal value(s) ~ <D-โฆ> command-key or "super" key *<D-* ----------------------------------------------------------------------- -Note: The shifted cursor keys, the help key, and the undo key are only -available on a few terminals. - -Note: There are two codes for the delete key. 127 is the decimal ASCII value -for the delete key, which is always recognized. Some delete keys send another -value, in which case this value is obtained from the |terminfo| entry "key_dc". -Both values have the same effect. +Note: -Note: The keypad keys are used in the same way as the corresponding "normal" -keys. For example, <kHome> has the same effect as <Home>. If a keypad key -sends the same raw key code as its non-keypad equivalent, it will be -recognized as the non-keypad code. For example, when <kHome> sends the same -code as <Home>, when pressing <kHome> Vim will think <Home> was pressed. -Mapping <kHome> will not work then. - -Note: If numlock is on, the |TUI| receives plain ASCII values, so -mappings to <k0> - <k9> and <kPoint> will not work. - -Note: Nvim supports mapping multibyte chars with modifiers such as `<M-รค>`. -Which combinations actually are usable depends on the terminal emulator or GUI. +- Availability of some keys (<Help>, <S-Right>, โฆ) depends on the UI or host + terminal. +- If numlock is on the |TUI| receives plain ASCII values, so mapping <k0>, + <k1>, ..., <k9> and <kPoint> will not work. +- Nvim supports mapping multibyte chars with modifiers such as `<M-รค>`. Which + combinations actually work depends on the the UI or host terminal. +- When a key is pressed using a meta or alt modifier and no mapping exists + for that keypress, Nvim behaves as though <Esc> was pressed before the key. *<>* Examples are often given in the <> notation. Sometimes this is just to make clear what you need to type, but often it can be typed literally, e.g., with the ":map" command. The rules are: - 1. Any printable characters are typed directly, except backslash and '<' - 2. A backslash is represented with "\\", double backslash, or "<Bslash>". - 3. A real '<' is represented with "\<" or "<lt>". When there is no - confusion possible, a '<' can be used directly. - 4. "<key>" means the special key typed. This is the notation explained in - the table above. A few examples: + 1. Printable characters are typed directly, except backslash and "<" + 2. Backslash is represented with "\\", double backslash, or "<Bslash>". + 3. Literal "<" is represented with "\<" or "<lt>". When there is no + confusion possible, "<" can be used directly. + 4. "<key>" means the special key typed (see the table above). Examples: <Esc> Escape key <C-G> CTRL-G <Up> cursor up key @@ -437,11 +413,6 @@ one always works. To get a literal "<lt>" in a mapping: > :map <C-L> <lt>lt> -For mapping, abbreviation and menu commands you can then copy-paste the -examples and use them directly. Or type them literally, including the '<' and -'>' characters. This does NOT work for other commands, like ":set" and -":autocmd"! - ============================================================================== Modes, introduction *vim-modes-intro* *vim-modes* @@ -599,7 +570,7 @@ Q or gQ Switch to Ex mode. This is like typing ":" commands Use the ":vi" command |:visual| to exit this mode. ============================================================================== -The window contents *window-contents* +Window contents *window-contents* In Normal mode and Insert/Replace mode the screen window will show the current contents of the buffer: What You See Is What You Get. There are two diff --git a/runtime/doc/lsp-extension.txt b/runtime/doc/lsp-extension.txt new file mode 100644 index 0000000000..d13303ada6 --- /dev/null +++ b/runtime/doc/lsp-extension.txt @@ -0,0 +1,129 @@ +*lsp-extension.txt* LSP Extension + + NVIM REFERENCE MANUAL + + +The `vim.lsp` Lua module is a framework for building LSP plugins. + + 1. Start with |vim.lsp.start_client()| and |vim.lsp.buf_attach_client()|. + 2. Peek at the API: > + :lua print(vim.inspect(vim.lsp)) +< 3. See |lsp-extension-example| for a full example. + +================================================================================ +LSP EXAMPLE *lsp-extension-example* + +This example is for plugin authors or users who want a lot of control. If you +are just getting started see |lsp-quickstart|. + +For more advanced configurations where just filtering by filetype isn't +sufficient, you can use the `vim.lsp.start_client()` and +`vim.lsp.buf_attach_client()` commands to easily customize the configuration +however you please. For example, if you want to do your own filtering, or +start a new LSP client based on the root directory for working with multiple +projects in a single session. To illustrate, the following is a fully working +Lua example. + +The example will: +1. Check for each new buffer whether or not we want to start an LSP client. +2. Try to find a root directory by ascending from the buffer's path. +3. Create a new LSP for that root directory if one doesn't exist. +4. Attach the buffer to the client for that root directory. + +> + -- Some path manipulation utilities + local function is_dir(filename) + local stat = vim.loop.fs_stat(filename) + return stat and stat.type == 'directory' or false + end + + local path_sep = vim.loop.os_uname().sysname == "Windows" and "\\" or "/" + -- Assumes filepath is a file. + local function dirname(filepath) + local is_changed = false + local result = filepath:gsub(path_sep.."([^"..path_sep.."]+)$", function() + is_changed = true + return "" + end) + return result, is_changed + end + + local function path_join(...) + return table.concat(vim.tbl_flatten {...}, path_sep) + end + + -- Ascend the buffer's path until we find the rootdir. + -- is_root_path is a function which returns bool + local function buffer_find_root_dir(bufnr, is_root_path) + local bufname = vim.api.nvim_buf_get_name(bufnr) + if vim.fn.filereadable(bufname) == 0 then + return nil + end + local dir = bufname + -- Just in case our algo is buggy, don't infinite loop. + for _ = 1, 100 do + local did_change + dir, did_change = dirname(dir) + if is_root_path(dir, bufname) then + return dir, bufname + end + -- If we can't ascend further, then stop looking. + if not did_change then + return nil + end + end + end + + -- A table to store our root_dir to client_id lookup. We want one LSP per + -- root directory, and this is how we assert that. + local javascript_lsps = {} + -- Which filetypes we want to consider. + local javascript_filetypes = { + ["javascript.jsx"] = true; + ["javascript"] = true; + ["typescript"] = true; + ["typescript.jsx"] = true; + } + + -- Create a template configuration for a server to start, minus the root_dir + -- which we will specify later. + local javascript_lsp_config = { + name = "javascript"; + cmd = { path_join(os.getenv("JAVASCRIPT_LANGUAGE_SERVER_DIRECTORY"), "lib", "language-server-stdio.js") }; + } + + -- This needs to be global so that we can call it from the autocmd. + function check_start_javascript_lsp() + local bufnr = vim.api.nvim_get_current_buf() + -- Filter which files we are considering. + if not javascript_filetypes[vim.api.nvim_buf_get_option(bufnr, 'filetype')] then + return + end + -- Try to find our root directory. We will define this as a directory which contains + -- node_modules. Another choice would be to check for `package.json`, or for `.git`. + local root_dir = buffer_find_root_dir(bufnr, function(dir) + return is_dir(path_join(dir, 'node_modules')) + -- return vim.fn.filereadable(path_join(dir, 'package.json')) == 1 + -- return is_dir(path_join(dir, '.git')) + end) + -- We couldn't find a root directory, so ignore this file. + if not root_dir then return end + + -- Check if we have a client already or start and store it. + local client_id = javascript_lsps[root_dir] + if not client_id then + local new_config = vim.tbl_extend("error", javascript_lsp_config, { + root_dir = root_dir; + }) + client_id = vim.lsp.start_client(new_config) + javascript_lsps[root_dir] = client_id + end + -- Finally, attach to the buffer to track changes. This will do nothing if we + -- are already attached. + vim.lsp.buf_attach_client(bufnr, client_id) + end + + vim.api.nvim_command [[autocmd BufReadPost * lua check_start_javascript_lsp()]] +< + + vim:tw=78:ts=8:ft=help:norl: diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt index b934d2dfa0..f110782490 100644 --- a/runtime/doc/lsp.txt +++ b/runtime/doc/lsp.txt @@ -4,11 +4,12 @@ NVIM REFERENCE MANUAL -LSP client/framework *lsp* +LSP client/framework *lsp* *LSP* 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/ LSP facilitates features like go-to-definition, find-references, hover, @@ -20,49 +21,71 @@ analysis (unlike |ctags|). ============================================================================== QUICKSTART *lsp-quickstart* -Nvim provides a LSP client, but the servers are provided by third parties. +Nvim provides an LSP client, but the servers are provided by third parties. Follow these steps to get LSP features: - 1. Install the nvim-lsp plugin. It provides common configuration for + 1. Install the nvim-lspconfig plugin. It provides common configuration for various servers so you can get started quickly. - https://github.com/neovim/nvim-lsp + https://github.com/neovim/nvim-lspconfig 2. Install a language server. Try ":LspInstall <tab>" or use your system package manager to install the relevant language server: https://microsoft.github.io/language-server-protocol/implementors/servers/ - 3. Add `nvim_lsp.xx.setup{โฆ}` to your vimrc, where "xx" is the name of the - relevant config. See the nvim-lsp README for details. - -To check LSP clients attached to the current buffer: > + 3. Add `lua require('nvim_lsp').xx.setup{โฆ}` to your init.vim, where "xx" is + the name of the relevant config. See the nvim-lspconfig README for details. + NOTE: Make sure to restart nvim after installing and configuring. + 4. Check that an LSP client has attached to the current buffer: > - :lua print(vim.inspect(vim.lsp.buf_get_clients())) + :lua print(vim.inspect(vim.lsp.buf_get_clients())) < *lsp-config* Inline diagnostics are enabled automatically, e.g. syntax errors will be -annotated in the buffer. But you probably want to use other features like -go-to-definition, hover, etc. Example config: > - - nnoremap <silent> gd <cmd>lua vim.lsp.buf.declaration()<CR> - nnoremap <silent> <c-]> <cmd>lua vim.lsp.buf.definition()<CR> - nnoremap <silent> K <cmd>lua vim.lsp.buf.hover()<CR> - nnoremap <silent> gD <cmd>lua vim.lsp.buf.implementation()<CR> - nnoremap <silent> <c-k> <cmd>lua vim.lsp.buf.signature_help()<CR> - nnoremap <silent> 1gD <cmd>lua vim.lsp.buf.type_definition()<CR> - nnoremap <silent> gr <cmd>lua vim.lsp.buf.references()<CR> - nnoremap <silent> g0 <cmd>lua vim.lsp.buf.document_symbol()<CR> - nnoremap <silent> gW <cmd>lua vim.lsp.buf.workspace_symbol()<CR> - -Nvim provides the |vim.lsp.omnifunc| 'omnifunc' handler which allows -|i_CTRL-X_CTRL-O| to consume LSP completion. Example config (note the use of -|v:lua| to call Lua from Vimscript): > - - " Use LSP omni-completion in Python files. - autocmd Filetype python setlocal omnifunc=v:lua.vim.lsp.omnifunc +annotated in the buffer. But you probably also want to use other features +like go-to-definition, hover, etc. + +While Nvim does not provide an "auto-completion" framework by default, it is +still possible to get completions from the LSP server. To incorporate these +completions, it is recommended to use |vim.lsp.omnifunc|, which is an 'omnifunc' +handler. When 'omnifunc' is set to `v:lua.vim.lsp.omnifunc`, |i_CTRL-X_CTRL-O| +will provide completions from the language server. + +Example config (in init.vim): > + + lua << EOF + local custom_lsp_attach = function(client) + -- See `:help nvim_buf_set_keymap()` for more information + vim.api.nvim_buf_set_keymap(0, 'n', 'K', '<cmd>lua vim.lsp.buf.hover()<CR>', {noremap = true}) + vim.api.nvim_buf_set_keymap(0, 'n', '<c-]>', '<cmd>lua vim.lsp.buf.definition()<CR>', {noremap = true}) + -- ... and other keymappings for LSP + + -- Use LSP as the handler for omnifunc. + -- See `:help omnifunc` and `:help ins-completion` for more information. + vim.api.nvim_buf_set_option(0, 'omnifunc', 'v:lua.vim.lsp.omnifunc') + + -- For plugins with an `on_attach` callback, call them here. For example: + -- require('completion').on_attach(client) + end -If a function has a `*_sync` variant, it's primarily intended for being run -automatically on file save. E.g. code formatting: > + -- An example of configuring for `sumneko_lua`, + -- a language server for Lua. + -- First, you must run `:LspInstall sumneko_lua` for this to work. + require('nvim_lsp').sumneko_lua.setup({ + -- An example of settings for an LSP server. + -- For more options, see nvim-lspconfig + settings = { + Lua = { + diagnostics = { + enable = true, + globals = { "vim" }, + }, + } + }, + + on_attach = custom_lsp_attach + }) + EOF +< - " Auto-format *.rs files prior to saving them - autocmd BufWritePre *.rs lua vim.lsp.buf.formatting_sync(nil, 1000) +Full list of features provided by default can be found in |lsp-buf|. ================================================================================ FAQ *lsp-faq* @@ -70,28 +93,52 @@ FAQ *lsp-faq* - Q: How to force-reload LSP? A: Stop all clients, then reload the buffer. > - :lua vim.lsp.stop_client(vim.lsp.get_active_clients()) - :edit + :lua vim.lsp.stop_client(vim.lsp.get_active_clients()) + :edit - Q: Why isn't completion working? A: In the buffer where you want to use LSP, check that 'omnifunc' is set to - "v:lua.vim.lsp.omnifunc": > + "v:lua.vim.lsp.omnifunc": > - :verbose set omnifunc? + :verbose set omnifunc? -< Some other plugin may be overriding the option. To avoid that, you could - set the option in an |after-directory| ftplugin, e.g. - "after/ftplugin/python.vim". +< Some other plugin may be overriding the option. To avoid that, you could + set the option in an |after-directory| ftplugin, e.g. + "after/ftplugin/python.vim". -================================================================================ -LSP API *lsp-api* +- Q: How do I run a request synchronously (e.g. for formatting on file save)? + A: Use the `_sync` variant of the function provided by |lsp-buf|, if it + exists. + + E.g. code formatting: > -The `vim.lsp` Lua module is a framework for building LSP plugins. + " Auto-format *.rs (rust) files prior to saving them + autocmd BufWritePre *.rs lua vim.lsp.buf.formatting_sync(nil, 1000) - 1. Start with |vim.lsp.start_client()| and |vim.lsp.buf_attach_client()|. - 2. Peek at the API: > - :lua print(vim.inspect(vim.lsp)) -< 3. See |lsp-extension-example| for a full example. +< + *vim.lsp.callbacks* +- Q: What happened to `vim.lsp.callbacks`? + A: After better defining the interface of |lsp-hander|s, we thought it best + to remove the generic usage of `callbacks` and transform to `handlers`. + Due to this, `vim.lsp.callbacks` was renamed to |vim.lsp.handlers|. + + *lsp-vs-treesitter* +- Q: How do LSP and Treesitter compare? + A: LSP requires a client and language server. The language server uses + semantic analysis to understand code at a project level. This provides + language servers with the ability to rename across files, find + definitions in external libraries and more. + + Treesitter is a language parsing library that provides excellent tools + for incrementally parsing text and handling errors. This makes it a great + fit for editors to understand the contents of the current file for things + like syntax highlighting, simple goto-definitions, scope analysis and + more. + + LSP and Treesitter are both great tools for editing and inspecting code. + +================================================================================ +LSP API *lsp-api* LSP core API is described at |lsp-core|. Those are the core functions for creating and managing clients. @@ -99,58 +146,209 @@ creating and managing clients. The `vim.lsp.buf_โฆ` functions perform operations for all LSP clients attached to the given buffer. |lsp-buf| -LSP request/response handlers are implemented as Lua callbacks. -|lsp-callbacks| The `vim.lsp.callbacks` table defines default callbacks used +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.callbacks))) - -These LSP requests/notifications are defined by default: - - textDocument/publishDiagnostics - window/logMessage - window/showMessage + :lua print(vim.inspect(vim.tbl_keys(vim.lsp.handlers))) +< + *lsp-method* + +Methods are the names of requests and notifications as defined by the LSP +specification. These LSP requests/notifications are defined by default: + + callHierarchy/incomingCalls + callHierarchy/outgoingCalls + textDocument/codeAction + textDocument/completion + textDocument/declaration* + textDocument/definition + textDocument/documentHighlight + textDocument/documentSymbol + textDocument/formatting + textDocument/hover + textDocument/implementation* + textDocument/publishDiagnostics + textDocument/rangeFormatting + textDocument/references + textDocument/rename + textDocument/signatureHelp + textDocument/typeDefinition* + window/logMessage + window/showMessage + workspace/applyEdit + workspace/symbol + +* NOTE: These are sometimes not implemented by servers. + + *lsp-handler* + +lsp-handlers are functions with special signatures that are designed to handle +responses and notifications from LSP servers. + +For |lsp-request|, each |lsp-handler| has this signature: > + + function(err, method, result, client_id, bufnr, 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|. + {method} (string) + The |lsp-method| name. + {result} (Result | Params | nil) + When the language server is able to succesfully + complete a request, this contains the `result` key + of the response. See |lsp-response|. + {client_id} (number) + The ID of the |vim.lsp.client|. + {bufnr} (Buffer) + Buffer handle, or 0 for current. + {config} (table) + Configuration for the handler. + + Each handler can define it's 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, method, params, client_id, bufnr, config) +< + Parameters: ~ + {err} (nil) + This is always `nil`. + See |lsp-notification| + {method} (string) + The |lsp-method| name. + {params} (Params) + This contains the `params` key of the notification. + See |lsp-notification| + {client_id} (number) + The ID of the |vim.lsp.client| + {bufnr} (nil) + `nil`, as the server doesn't have an associated buffer. + {config} (table) + Configuration for the handler. + + Each handler can define it's own configuration + table that allows users to customize the behavior + of a particular handler. + + For an example, see: + |vim.lsp.diagnostics.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* + +To configure the behavior of a builtin |lsp-handler|, the conenvience method +|vim.lsp.with()| is provided for users. + + To configure the behavior of |vim.lsp.diagnostic.on_publish_diagnostics()|, + consider the following example, where a new |lsp-handler| is created using + |vim.lsp.with()| that no longer generates signs for the diagnostics: > + + vim.lsp.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, + } + ) +< + 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, + } + }, + } +< + or if using 'nvim-lspconfig', you can use the {handlers} key of `setup()`: > + + nvim_lsp.rust_analyzer.setup { + handlers = { + ["textDocument/publishDiagnostics"] = vim.lsp.with( + vim.lsp.diagnostic.on_publish_diagnostics, { + -- Disable virtual_text + virtual_text = false + } + ), + } + } +< + *lsp-handler-resolution* +Handlers can be set by: -You can check these via `vim.tbl_keys(vim.lsp.callbacks)`. +- Setting a field in |vim.lsp.handlers|. *vim.lsp.handlers* + |vim.lsp.handlers| is a global table that contains the default mapping of + |lsp-method| names to |lsp-handlers|. -These will be used preferentially in `vim.lsp.buf_โฆ` methods for handling -requests. They will also be used when responding to server requests and -notifications. + To override the handler for the `"textDocument/definition"` method: > -Use cases: -- Users can modify this to customize to their preferences. -- UI plugins can modify this by assigning to - `vim.lsp.callbacks[method]` so as to provide more specialized - handling, allowing you to leverage the UI capabilities available. UIs should - try to be conscientious of any existing changes the user may have set - already by checking for existing values. + 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. -Any callbacks passed directly to `request` methods on a server client will -have the highest precedence, followed by the `callbacks`. + For example: > -You can override the default handlers, -- globally: by modifying the `vim.lsp.callbacks` table -- per-client: by passing the {callbacks} table parameter to - |vim.lsp.start_client| + vim.lsp.start_client { + ..., -- Other configuration ommitted. + handlers = { + ["textDocument/definition"] = my_custom_server_definition + }, + } -Each handler has this signature: > +- The {handler} parameter for |vim.lsp.buf_request()|. + This will set the |lsp-handler| ONLY for the current request. - function(err, method, params, client_id) + For example: > -Callbacks are functions which are called in a variety of situations by the -client. Their signature is `function(err, method, params, client_id)` They can -be set by the {callbacks} parameter for |vim.lsp.start_client| or via the -|vim.lsp.callbacks|. + 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: -Handlers are called for: -- Notifications from the server (`err` is always `nil`). -- Requests initiated by the server (`err` is always `nil`). - The 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. -- Handling requests initiated by the client if the request doesn't explicitly - specify a callback (such as in |vim.lsp.buf_request|). +1. Handler passed to |vim.lsp.buf_request()|, if any. +2. Handler defined in |vim.lsp.start_client()|, if any. +3. Handler defined in |vim.lsp.handlers|, if any. VIM.LSP.PROTOCOL *vim.lsp.protocol* @@ -164,41 +362,21 @@ name: > vim.lsp.protocol.TextDocumentSyncKind.Full == 1 vim.lsp.protocol.TextDocumentSyncKind[1] == "Full" +< + + *lsp-response* +For the format of the response message, see: + https://microsoft.github.io/language-server-protocol/specifications/specification-current/#responseMessage + + *lsp-notification* +For the format of the notification message, see: + https://microsoft.github.io/language-server-protocol/specifications/specification-current/#notificationMessage ================================================================================ LSP HIGHLIGHT *lsp-highlight* - *hl-LspDiagnosticsError* -LspDiagnosticsError used for "Error" diagnostic virtual text - *hl-LspDiagnosticsErrorSign* -LspDiagnosticsErrorSign used for "Error" diagnostic signs in sign - column - *hl-LspDiagnosticsErrorFloating* -LspDiagnosticsErrorFloating used for "Error" diagnostic messages in the - diagnostics float - *hl-LspDiagnosticsWarning* -LspDiagnosticsWarning used for "Warning" diagnostic virtual text - *hl-LspDiagnosticsWarningSign* -LspDiagnosticsWarningSign used for "Warning" diagnostic signs in sign - column - *hl-LspDiagnosticsWarningFloating* -LspDiagnosticsWarningFloating used for "Warning" diagnostic messages in the - diagnostics float - *hl-LspDiagnosticsInformation* -LspDiagnosticsInformation used for "Information" diagnostic virtual text - *hl-LspDiagnosticsInformationSign* -LspDiagnosticsInformationSign used for "Information" signs in sign column - *hl-LspDiagnosticsInformationFloating* -LspDiagnosticsInformationFloating used for "Information" diagnostic messages in - the diagnostics float - *hl-LspDiagnosticsHint* -LspDiagnosticsHint used for "Hint" diagnostic virtual text - *hl-LspDiagnosticsHintSign* -LspDiagnosticsHintSign used for "Hint" diagnostic signs in sign - column - *hl-LspDiagnosticsHintFloating* -LspDiagnosticsHintFloating used for "Hint" diagnostic messages in the - diagnostics float +Reference Highlights: + *hl-LspReferenceText* LspReferenceText used for highlighting "text" references *hl-LspReferenceRead* @@ -207,122 +385,120 @@ LspReferenceRead used for highlighting "read" references LspReferenceWrite used for highlighting "write" references -================================================================================ -LSP EXAMPLE *lsp-extension-example* - -This example is for plugin authors or users who want a lot of control. If you -are just getting started see |lsp-quickstart|. - -For more advanced configurations where just filtering by filetype isn't -sufficient, you can use the `vim.lsp.start_client()` and -`vim.lsp.buf_attach_client()` commands to easily customize the configuration -however you please. For example, if you want to do your own filtering, or -start a new LSP client based on the root directory for if you plan to work -with multiple projects in a single session. Below is a fully working Lua -example which can do exactly that. + *lsp-highlight-diagnostics* +All highlights defined for diagnostics begin with `LspDiagnostics` followed by +the type of highlight (e.g., `Sign`, `Underline`, etc.) and then the Severity +of the highlight (e.g. `Error`, `Warning`, etc.) -The example will: -1. Check for each new buffer whether or not we want to start an LSP client. -2. Try to find a root directory by ascending from the buffer's path. -3. Create a new LSP for that root directory if one doesn't exist. -4. Attach the buffer to the client for that root directory. +Sign, underline and virtual text highlights (by default) are linked to their +corresponding LspDiagnosticsDefault highlight. -> - -- Some path manipulation utilities - local function is_dir(filename) - local stat = vim.loop.fs_stat(filename) - return stat and stat.type == 'directory' or false - end - - local path_sep = vim.loop.os_uname().sysname == "Windows" and "\\" or "/" - -- Asumes filepath is a file. - local function dirname(filepath) - local is_changed = false - local result = filepath:gsub(path_sep.."([^"..path_sep.."]+)$", function() - is_changed = true - return "" - end) - return result, is_changed - end - - local function path_join(...) - return table.concat(vim.tbl_flatten {...}, path_sep) - end - - -- Ascend the buffer's path until we find the rootdir. - -- is_root_path is a function which returns bool - local function buffer_find_root_dir(bufnr, is_root_path) - local bufname = vim.api.nvim_buf_get_name(bufnr) - if vim.fn.filereadable(bufname) == 0 then - return nil - end - local dir = bufname - -- Just in case our algo is buggy, don't infinite loop. - for _ = 1, 100 do - local did_change - dir, did_change = dirname(dir) - if is_root_path(dir, bufname) then - return dir, bufname - end - -- If we can't ascend further, then stop looking. - if not did_change then - return nil - end - end - end - - -- A table to store our root_dir to client_id lookup. We want one LSP per - -- root directory, and this is how we assert that. - local javascript_lsps = {} - -- Which filetypes we want to consider. - local javascript_filetypes = { - ["javascript.jsx"] = true; - ["javascript"] = true; - ["typescript"] = true; - ["typescript.jsx"] = true; - } - - -- Create a template configuration for a server to start, minus the root_dir - -- which we will specify later. - local javascript_lsp_config = { - name = "javascript"; - cmd = { path_join(os.getenv("JAVASCRIPT_LANGUAGE_SERVER_DIRECTORY"), "lib", "language-server-stdio.js") }; - } +For example, the default highlighting for |hl-LspDiagnosticsSignError| is +linked to |hl-LspDiagnosticsDefaultError|. To change the default (and +therefore the linked highlights), use the |:highlight| command: > - -- This needs to be global so that we can call it from the autocmd. - function check_start_javascript_lsp() - local bufnr = vim.api.nvim_get_current_buf() - -- Filter which files we are considering. - if not javascript_filetypes[vim.api.nvim_buf_get_option(bufnr, 'filetype')] then - return - end - -- Try to find our root directory. We will define this as a directory which contains - -- node_modules. Another choice would be to check for `package.json`, or for `.git`. - local root_dir = buffer_find_root_dir(bufnr, function(dir) - return is_dir(path_join(dir, 'node_modules')) - -- return vim.fn.filereadable(path_join(dir, 'package.json')) == 1 - -- return is_dir(path_join(dir, '.git')) - end) - -- We couldn't find a root directory, so ignore this file. - if not root_dir then return end - - -- Check if we have a client alredy or start and store it. - local client_id = javascript_lsps[root_dir] - if not client_id then - local new_config = vim.tbl_extend("error", javascript_lsp_config, { - root_dir = root_dir; - }) - client_id = vim.lsp.start_client(new_config) - javascript_lsps[root_dir] = client_id - end - -- Finally, attach to the buffer to track changes. This will do nothing if we - -- are already attached. - vim.lsp.buf_attach_client(bufnr, client_id) - end - - vim.api.nvim_command [[autocmd BufReadPost * lua check_start_javascript_lsp()]] + highlight LspDiagnosticsDefaultError guifg="BrightRed" < + *hl-LspDiagnosticsDefaultError* +LspDiagnosticsDefaultError + Used as the base highlight group. + Other LspDiagnostic highlights link to this by default (except Underline) + + *hl-LspDiagnosticsDefaultWarning* +LspDiagnosticsDefaultWarning + Used as the base highlight group. + Other LspDiagnostic highlights link to this by default (except Underline) + + *hl-LspDiagnosticsDefaultInformation* +LspDiagnosticsDefaultInformation + Used as the base highlight group. + Other LspDiagnostic highlights link to this by default (except Underline) + + *hl-LspDiagnosticsDefaultHint* +LspDiagnosticsDefaultHint + Used as the base highlight group. + Other LspDiagnostic highlights link to this by default (except Underline) + + *hl-LspDiagnosticsVirtualTextError* +LspDiagnosticsVirtualTextError + Used for "Error" diagnostic virtual text. + See |vim.lsp.diagnostic.set_virtual_text()| + + *hl-LspDiagnosticsVirtualTextWarning* +LspDiagnosticsVirtualTextWarning + Used for "Warning" diagnostic virtual text. + See |vim.lsp.diagnostic.set_virtual_text()| + + *hl-LspDiagnosticsVirtualTextInformation* +LspDiagnosticsVirtualTextInformation + Used for "Information" diagnostic virtual text. + See |vim.lsp.diagnostic.set_virtual_text()| + + *hl-LspDiagnosticsVirtualTextHint* +LspDiagnosticsVirtualTextHint + Used for "Hint" diagnostic virtual text. + See |vim.lsp.diagnostic.set_virtual_text()| + + *hl-LspDiagnosticsUnderlineError* +LspDiagnosticsUnderlineError + Used to underline "Error" diagnostics. + See |vim.lsp.diagnostic.set_underline()| + + *hl-LspDiagnosticsUnderlineWarning* +LspDiagnosticsUnderlineWarning + Used to underline "Warning" diagnostics. + See |vim.lsp.diagnostic.set_underline()| + + *hl-LspDiagnosticsUnderlineInformation* +LspDiagnosticsUnderlineInformation + Used to underline "Information" diagnostics. + See |vim.lsp.diagnostic.set_underline()| + + *hl-LspDiagnosticsUnderlineHint* +LspDiagnosticsUnderlineHint + Used to underline "Hint" diagnostics. + See |vim.lsp.diagnostic.set_underline()| + + *hl-LspDiagnosticsFloatingError* +LspDiagnosticsFloatingError + Used to color "Error" diagnostic messages in diagnostics float. + See |vim.lsp.diagnostic.show_line_diagnostics()| + + *hl-LspDiagnosticsFloatingWarning* +LspDiagnosticsFloatingWarning + Used to color "Warning" diagnostic messages in diagnostics float. + See |vim.lsp.diagnostic.show_line_diagnostics()| + + *hl-LspDiagnosticsFloatingInformation* +LspDiagnosticsFloatingInformation + Used to color "Information" diagnostic messages in diagnostics float. + See |vim.lsp.diagnostic.show_line_diagnostics()| + + *hl-LspDiagnosticsFloatingHint* +LspDiagnosticsFloatingHint + Used to color "Hint" diagnostic messages in diagnostics float. + See |vim.lsp.diagnostic.show_line_diagnostics()| + + *hl-LspDiagnosticsSignError* +LspDiagnosticsSignError + Used for "Error" signs in sign column. + See |vim.lsp.diagnostic.set_signs()| + + *hl-LspDiagnosticsSignWarning* +LspDiagnosticsSignWarning + Used for "Warning" signs in sign column. + See |vim.lsp.diagnostic.set_signs()| + + *hl-LspDiagnosticsSignInformation* +LspDiagnosticsSignInformation + Used for "Information" signs in sign column. + See |vim.lsp.diagnostic.set_signs()| + + *hl-LspDiagnosticsSignHint* +LspDiagnosticsSignHint + Used for "Hint" signs in sign column. + See |vim.lsp.diagnostic.set_signs()| ============================================================================== AUTOCOMMANDS *lsp-autocommands* @@ -353,9 +529,6 @@ buf_get_clients({bufnr}) *vim.lsp.buf_get_clients()* {bufnr} (optional, number): Buffer handle, or 0 for current -buf_get_full_text({bufnr}) *vim.lsp.buf_get_full_text()* - TODO: Documentation - buf_is_attached({bufnr}, {client_id}) *vim.lsp.buf_is_attached()* Checks if a buffer is attached for a particular client. @@ -375,16 +548,16 @@ buf_notify({bufnr}, {method}, {params}) *vim.lsp.buf_notify()* true if any client returns true; false otherwise *vim.lsp.buf_request()* -buf_request({bufnr}, {method}, {params}, {callback}) +buf_request({bufnr}, {method}, {params}, {handler}) Sends an async request for all active clients attached to the buffer. Parameters: ~ - {bufnr} (number) Buffer handle, or 0 for current. - {method} (string) LSP method name - {params} (optional, table) Parameters to send to the - server - {callback} (optional, functionnil) Handler + {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| Return: ~ 2-tuple: @@ -416,71 +589,68 @@ buf_request_sync({bufnr}, {method}, {params}, {timeout_ms}) error, returns `(nil, err)` where `err` is a string describing the failure reason. -cancel_request({id}) *vim.lsp.cancel_request()* - TODO: Documentation - client() *vim.lsp.client* - LSP client object. + 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, [callback]) Send a request to the - server. If callback is not specified, it will use - {client.callbacks} to try to find a callback. If one is - not found there, then an error will occur. This is a thin - wrapper around {client.rpc.request} with some additional - checking. Returns a boolean to indicate if the - notification was successful. If it is false, then it will - always be false (the client has shutdown). If it was - successful, then it will return the request id as the - second result. You can use this with `notify("$/cancel", { - id = request_id })` to cancel the request. This helper is - made automatically with |vim.lsp.buf_request()| Returns: - status, [client_id] - โข notify(method, params) This is just {client.rpc.notify}() - Returns a boolean to indicate if the notification was - successful. If it is false, then it will always be false - (the client has shutdown). Returns: status - โข cancel_request(id) This is just - {client.rpc.notify}("$/cancelRequest", { id = id }) - Returns the same as `notify()` . - โข stop([force]) Stop a client, optionally with force. By + โข 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. + โข 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() Returns true if the client is fully stopped. + โข is_stopped() Checks whether a client is stopped. Returns: + true if the client is fully stopped. + โข on_attach(bufnr) Runs the on_attach function from the + client's config if it was defined. โข Members - โข id (number): The id allocated to the client. - โข name (string): If a name is specified on creation, that + โข {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. - โข offset_encoding (string): The encoding used for + โข {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 - `on_init` method before text is sent to the server. - โข callbacks (table): The callbacks used by the client as - described in |lsp-callbacks|. - โข config (table): copy of the table that was passed by 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|. + โข {config} (table): copy of the table that was passed by the user to |vim.lsp.start_client()|. - โข server_capabilities (table): Response from the server sent - on `initialize` describing the server's capabilities. - โข resolved_capabilities (table): Normalized table of + โข {server_capabilities} (table): Response from the server + sent on `initialize` describing the server's capabilities. + โข {resolved_capabilities} (table): Normalized table of capabilities that we have detected based on the initialize response from the server in `server_capabilities` . client_is_stopped({client_id}) *vim.lsp.client_is_stopped()* - TODO: Documentation - - *vim.lsp.define_default_sign()* -define_default_sign({name}, {properties}) - TODO: Documentation + Checks whether a client is stopped. -err_message({...}) *vim.lsp.err_message()* - TODO: Documentation + Parameters: ~ + {client_id} (Number) - *vim.lsp.for_each_buffer_client()* -for_each_buffer_client({bufnr}, {callback}) - TODO: Documentation + Return: ~ + true if client is stopped, false otherwise. get_active_clients() *vim.lsp.get_active_clients()* Gets all active clients. @@ -489,8 +659,8 @@ get_active_clients() *vim.lsp.get_active_clients()* Table of |vim.lsp.client| objects get_client_by_id({client_id}) *vim.lsp.get_client_by_id()* - Gets an active client by id, or nil if the id is invalid or - the client is not yet 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} client id number @@ -499,25 +669,10 @@ get_client_by_id({client_id}) *vim.lsp.get_client_by_id()* |vim.lsp.client| object, or nil get_log_path() *vim.lsp.get_log_path()* - TODO: Documentation - -initialize() *vim.lsp.initialize()* - TODO: Documentation - -is_dir({filename}) *vim.lsp.is_dir()* - TODO: Documentation - -is_stopped() *vim.lsp.is_stopped()* - TODO: Documentation - -next_client_id() *vim.lsp.next_client_id()* - TODO: Documentation + Gets the path of the logfile used by the LSP client. -notification({method}, {params}) *vim.lsp.notification()* - TODO: Documentation - -notify({...}) *vim.lsp.notify()* - TODO: Documentation + Return: ~ + (String) Path to logfile. omnifunc({findstart}, {base}) *vim.lsp.omnifunc()* Implements 'omnifunc' compatible LSP completion. @@ -538,30 +693,6 @@ omnifunc({findstart}, {base}) *vim.lsp.omnifunc()* |complete-items| |CompleteDone| -on_error({code}, {err}) *vim.lsp.on_error()* - TODO: Documentation - -on_exit({code}, {signal}) *vim.lsp.on_exit()* - TODO: Documentation - -once({fn}) *vim.lsp.once()* - TODO: Documentation - -optional_validator({fn}) *vim.lsp.optional_validator()* - TODO: Documentation - -request({method}, {params}, {callback}, {bufnr}) *vim.lsp.request()* - TODO: Documentation - -resolve_bufnr({bufnr}) *vim.lsp.resolve_bufnr()* - TODO: Documentation - -resolve_callback({method}) *vim.lsp.resolve_callback()* - TODO: Documentation - -server_request({method}, {params}) *vim.lsp.server_request()* - TODO: Documentation - set_log_level({level}) *vim.lsp.set_log_level()* Sets the global log level for LSP logging. @@ -582,6 +713,9 @@ start_client({config}) *vim.lsp.start_client()* Parameters `cmd` and `root_dir` are required. + The following parameters describe fields in the {config} + table. + Parameters: ~ {root_dir} (required, string) Directory where the LSP server will base its rootUri on @@ -610,19 +744,8 @@ start_client({config}) *vim.lsp.start_client()* `{[vim.type_idx]=vim.types.dictionary}` , else it will be encoded as an array. - {callbacks} Map of language server method names to `function(err, method, params, - client_id)` handler. Invoked for: - โข Notifications from the server, where - `err` will always be `nil` . - โข Requests initiated by the server. For - these you can respond by returning - two values: `result, err` where err - must be shaped like a RPC error, i.e. - `{ code, message, data? }` . Use - |vim.lsp.rpc_response_error()| to - help with this. - โข Default callback for client requests - not explicitly specifying a callback. + {handlers} Map of language server method names to + |lsp-handler| {init_options} Values to pass in the initialization request as `initializationOptions` . See `initialize` in the LSP spec. @@ -647,8 +770,9 @@ start_client({config}) *vim.lsp.start_client()* where `params` contains the parameters being sent to the server and `config` is the config that was passed to - `start_client()` . You can use this to - modify parameters before they are sent. + |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` @@ -674,14 +798,9 @@ start_client({config}) *vim.lsp.start_client()* "off" Return: ~ - Client id. |vim.lsp.get_client_by_id()| Note: client is - only available after it has been initialized, which may - happen after a small delay (or never if there is an - error). Use `on_init` to do any actions once the client - has been initialized. - -stop({force}) *vim.lsp.stop()* - TODO: Documentation + 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). @@ -690,7 +809,7 @@ stop_client({client_id}, {force}) *vim.lsp.stop_client()* object. To stop all clients: > - vim.lsp.stop_client(lsp.get_active_clients()) + vim.lsp.stop_client(vim.lsp.get_active_clients()) < By default asks the server to shutdown, unless stop was @@ -702,67 +821,51 @@ stop_client({client_id}, {force}) *vim.lsp.stop_client()* thereof {force} boolean (optional) shutdown forcefully - *vim.lsp.text_document_did_open_handler()* -text_document_did_open_handler({bufnr}, {client}) - TODO: Documentation - -unsupported_method({method}) *vim.lsp.unsupported_method()* - TODO: Documentation - -validate_client_config({config}) *vim.lsp.validate_client_config()* - TODO: Documentation - -validate_encoding({encoding}) *vim.lsp.validate_encoding()* - TODO: Documentation - - -============================================================================== -Lua module: vim.lsp.protocol *lsp-protocol* - -ifnil({a}, {b}) *vim.lsp.protocol.ifnil()* - TODO: Documentation - - *vim.lsp.protocol.make_client_capabilities()* -make_client_capabilities() - Gets a new ClientCapabilities object describing the LSP client - capabilities. - - *vim.lsp.protocol.resolve_capabilities()* -resolve_capabilities({server_capabilities}) - `*` to match one or more characters in a path segment `?` to - match on one character in a path segment `**` to match any - number of path segments, including none `{}` to group - conditions (e.g. `**โ/*.{ts,js}` matches all TypeScript and - JavaScript files) `[]` to declare a range of characters to - match in a path segment (e.g., `example.[0-9]` to match on - `example.0` , `example.1` , โฆ) `[!...]` to negate a range of - characters to match in a path segment (e.g., `example.[!0-9]` - to match on `example.a` , `example.b` , but not `example.0` ) - - *vim.lsp.protocol.transform_schema_comments()* -transform_schema_comments() - TODO: Documentation +with({handler}, {override_config}) *vim.lsp.with()* + Function to manage overriding defaults for LSP handlers. - *vim.lsp.protocol.transform_schema_to_table()* -transform_schema_to_table() - TODO: Documentation + Parameters: ~ + {handler} (function) See |lsp-handler| + {override_config} (table) Table containing the keys to + override behavior of the {handler} ============================================================================== Lua module: vim.lsp.buf *lsp-buf* clear_references() *vim.lsp.buf.clear_references()* - TODO: Documentation + Removes document highlights from current buffer. code_action({context}) *vim.lsp.buf.code_action()* - TODO: Documentation + Selects a code action from the input list that is available at + the current cursor position. + + Parameters: ~ + {context} (table, optional) Valid `CodeActionContext` + object + + 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. + 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| + declaration() *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. definition() *vim.lsp.buf.definition()* Jumps to the definition of the symbol under the cursor. @@ -782,22 +885,41 @@ document_symbol() *vim.lsp.buf.document_symbol()* window. execute_command({command}) *vim.lsp.buf.execute_command()* - TODO: Documentation + Executes an LSP server command. + + Parameters: ~ + {command} A valid `ExecuteCommandParams` object + + See also: ~ + https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_executeCommand formatting({options}) *vim.lsp.buf.formatting()* Formats the current buffer. - The optional {options} table can be used to specify - FormattingOptions, a list of which is available at https://microsoft.github.io/language-server-protocol/specification#textDocument_formatting . Some unspecified options will be automatically derived from - the current Neovim options. + Parameters: ~ + {options} (optional, table) 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 *vim.lsp.buf.formatting_sync()* formatting_sync({options}, {timeout_ms}) - Perform |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()|. + |vim.lsp.buf_request_sync()|. Example: +> + + vim.api.nvim_command[[autocmd BufWritePre <buffer> lua vim.lsp.buf.formatting_sync()]] +< + + Parameters: ~ + {options} Table with valid `FormattingOptions` entries + {timeout_ms} (number) Request timeout hover() *vim.lsp.buf.hover()* Displays hover information about the symbol under the cursor @@ -808,29 +930,61 @@ implementation() *vim.lsp.buf.implementation()* Lists all the implementations for the symbol under the cursor in the quickfix window. -npcall({fn}, {...}) *vim.lsp.buf.npcall()* - TODO: Documentation +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|. -ok_or_nil({status}, {...}) *vim.lsp.buf.ok_or_nil()* - TODO: Documentation +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|. + + *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, optional) Valid `CodeActionContext` + object + {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}) - TODO: Documentation + Formats a given range. + + Parameters: ~ + {options} Table with valid `FormattingOptions` entries. + {start_pos} ({number, number}, optional) mark-indexed + position. Defaults to the end of the last + visual selection. references({context}) *vim.lsp.buf.references()* Lists all the references to the symbol under the cursor in the quickfix window. + Parameters: ~ + {context} (table) Context for the request + + See also: ~ + https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_references + rename({new_name}) *vim.lsp.buf.rename()* - Renames all references to the symbol under the cursor. If - {new_name} is not provided, the user will be prompted for a - new name using |input()|. + Renames all references to the symbol under the cursor. -request({method}, {params}, {callback}) *vim.lsp.buf.request()* - TODO: Documentation + Parameters: ~ + {new_name} (string) If not provided, the user will be + prompted for a new name using |input()|. server_ready() *vim.lsp.buf.server_ready()* + Checks whether the language servers attached to the current + buffer are ready. + Return: ~ `true` if server responds. @@ -846,289 +1000,534 @@ workspace_symbol({query}) *vim.lsp.buf.workspace_symbol()* Lists all symbols in the current workspace in the quickfix window. - The list is filtered against the optional argument {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. -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|. - -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|. + Parameters: ~ + {query} (string, optional) ============================================================================== -Lua module: vim.lsp.callbacks *lsp-callbacks* +Lua module: vim.lsp.diagnostic *lsp-diagnostic* -err_message({...}) *vim.lsp.callbacks.err_message()* - TODO: Documentation + *vim.lsp.diagnostic.clear()* +clear({bufnr}, {client_id}, {diagnostic_ns}, {sign_ns}) + Clears the currently displayed diagnostics - *vim.lsp.callbacks.location_callback()* -location_callback({_}, {method}, {result}) - TODO: Documentation + Parameters: ~ + {bufnr} number The buffer number + {client_id} number the client id + {diagnostic_ns} number|nil Associated diagnostic + namespace + {sign_ns} number|nil Associated sign namespace +get({bufnr}, {client_id}) *vim.lsp.diagnostic.get()* + Return associated diagnostics for bufnr -============================================================================== -Lua module: vim.lsp.log *lsp-log* + Parameters: ~ + {bufnr} number + {client_id} number|nil If nil, then return all of the + diagnostics. Else, return just the + diagnostics associated with the client_id. -get_filename() *vim.lsp.log.get_filename()* - TODO: Documentation + *vim.lsp.diagnostic.get_count()* +get_count({bufnr}, {severity}, {client_id}) + Get the counts for a particular severity -path_join({...}) *vim.lsp.log.path_join()* - TODO: Documentation + Useful for showing diagnostic counts in statusline. eg: +> -set_level({level}) *vim.lsp.log.set_level()* - TODO: Documentation + function! LspStatus() abort + let sl = '' + if luaeval('not vim.tbl_isempty(vim.lsp.buf_get_clients(0))') + let sl.='%#MyStatuslineLSP#E:' + let sl.='%#MyStatuslineLSPErrors#%{luaeval("vim.lsp.diagnostic.get_count([[Error]])")}' + let sl.='%#MyStatuslineLSP# W:' + let sl.='%#MyStatuslineLSPWarnings#%{luaeval("vim.lsp.diagnostic.get_count([[Warning]])")}' + else + let sl.='%#MyStatuslineLSPErrors#off' + endif + return sl + endfunction + let &l:statusline = '%#MyStatuslineLSP#LSP '.LspStatus() +< -should_log({level}) *vim.lsp.log.should_log()* - TODO: Documentation + Parameters: ~ + {bufnr} number The buffer number + {severity} DiagnosticSeverity + {client_id} number the client id + *vim.lsp.diagnostic.get_line_diagnostics()* +get_line_diagnostics({bufnr}, {line_nr}, {opts}, {client_id}) + Get the diagnostics by line -============================================================================== -Lua module: vim.lsp.rpc *lsp-rpc* + Parameters: ~ + {bufnr} number The buffer number + {line_nr} number The line number + {opts} table|nil Configuration keys + โข severity: (DiagnosticSeverity, default nil) + โข Only return diagnostics with this + severity. Overrides severity_limit + + โข severity_limit: (DiagnosticSeverity, default nil) + โข Limit severity of diagnostics found. E.g. + "Warning" means { "Error", "Warning" } + will be valid. + {client_id} number the client id -convert_NIL({v}) *vim.lsp.rpc.convert_NIL()* - TODO: Documentation + Return: ~ + table Table with map of line number to list of + diagnostics. - *vim.lsp.rpc.create_and_start_client()* -create_and_start_client({cmd}, {cmd_args}, {handlers}, - {extra_spawn_params}) - TODO: Documentation +get_next({opts}) *vim.lsp.diagnostic.get_next()* + Get the previous diagnostic closest to the cursor_position -encode_and_send({payload}) *vim.lsp.rpc.encode_and_send()* - TODO: Documentation + Parameters: ~ + {opts} table See |vim.lsp.diagnostics.goto_next()| -env_merge({env}) *vim.lsp.rpc.env_merge()* - Merges current process env with the given env and returns the - result as a list of "k=v" strings. -> + Return: ~ + table Next diagnostic - Example: -< +get_next_pos({opts}) *vim.lsp.diagnostic.get_next_pos()* + Return the pos, {row, col}, for the next diagnostic in the + current buffer. - > in: { PRODUCTION="false", PATH="/usr/bin/", PORT=123, HOST="0.0.0.0", } - out: { "PRODUCTION=false", "PATH=/usr/bin/", "PORT=123", "HOST=0.0.0.0", } -< + Parameters: ~ + {opts} table See |vim.lsp.diagnostics.goto_next()| - *vim.lsp.rpc.format_message_with_content_length()* -format_message_with_content_length({encoded_message}) - TODO: Documentation + Return: ~ + table Next diagnostic position -format_rpc_error({err}) *vim.lsp.rpc.format_rpc_error()* - TODO: Documentation +get_prev({opts}) *vim.lsp.diagnostic.get_prev()* + Get the previous diagnostic closest to the cursor_position -handle_body({body}) *vim.lsp.rpc.handle_body()* - TODO: Documentation + Parameters: ~ + {opts} table See |vim.lsp.diagnostics.goto_next()| -is_dir({filename}) *vim.lsp.rpc.is_dir()* - TODO: Documentation + Return: ~ + table Previous diagnostic -json_decode({data}) *vim.lsp.rpc.json_decode()* - TODO: Documentation +get_prev_pos({opts}) *vim.lsp.diagnostic.get_prev_pos()* + Return the pos, {row, col}, for the prev diagnostic in the + current buffer. -json_encode({data}) *vim.lsp.rpc.json_encode()* - TODO: Documentation + Parameters: ~ + {opts} table See |vim.lsp.diagnostics.goto_next()| -notification({method}, {params}) *vim.lsp.rpc.notification()* - TODO: Documentation + Return: ~ + table Previous diagnostic position -on_error({errkind}, {...}) *vim.lsp.rpc.on_error()* - TODO: Documentation + *vim.lsp.diagnostic.get_virtual_text_chunks_for_line()* +get_virtual_text_chunks_for_line({bufnr}, {line}, {line_diags}, {opts}) + Default function to get text chunks to display using `nvim_buf_set_virtual_text` . -on_exit({code}, {signal}) *vim.lsp.rpc.on_exit()* - TODO: Documentation + Parameters: ~ + {bufnr} number The buffer to display the virtual + text in + {line} number The line number to display the + virtual text on + {line_diags} Diagnostic [] The diagnostics associated with the line + {opts} table See {opts} from + |vim.lsp.diagnostic.set_virtual_text()| -onexit({code}, {signal}) *vim.lsp.rpc.onexit()* - TODO: Documentation + Return: ~ + table chunks, as defined by |nvim_buf_set_virtual_text()| -parse_headers({header}) *vim.lsp.rpc.parse_headers()* - TODO: Documentation +goto_next({opts}) *vim.lsp.diagnostic.goto_next()* + Move to the next diagnostic - *vim.lsp.rpc.pcall_handler()* -pcall_handler({errkind}, {status}, {head}, {...}) - TODO: Documentation + Parameters: ~ + {opts} table|nil Configuration table. Keys: + โข {client_id}: (number) + โข If nil, will consider all clients attached to + buffer. -request_parser_loop() *vim.lsp.rpc.request_parser_loop()* - TODO: Documentation + โข {cursor_position}: (Position, default current + position) + โข See |nvim_win_get_cursor()| - *vim.lsp.rpc.rpc_response_error()* -rpc_response_error({code}, {message}, {data}) - Creates an RPC response object/table. + โข {wrap}: (boolean, default true) + โข Whether to loop around file or not. Similar to + 'wrapscan' + + โข {severity}: (DiagnosticSeverity) + โข Exclusive severity to consider. Overrides + {severity_limit} + + โข {severity_limit}: (DiagnosticSeverity) + โข Limit severity of diagnostics found. E.g. + "Warning" means { "Error", "Warning" } will be + valid. + + โข {enable_popup}: (boolean, default true) + โข Call + |vim.lsp.diagnostic.show_line_diagnostics()| + on jump + + โข {popup_opts}: (table) + โข Table to pass as {opts} parameter to + |vim.lsp.diagnostic.show_line_diagnostics()| + + โข {win_id}: (number, default 0) + โข Window ID + +goto_prev({opts}) *vim.lsp.diagnostic.goto_prev()* + Move to the previous diagnostic 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 + {opts} table See |vim.lsp.diagnostics.goto_next()| + + *vim.lsp.diagnostic.on_publish_diagnostics()* +on_publish_diagnostics({_}, {_}, {params}, {client_id}, {_}, {config}) + |lsp-handler| for the method "textDocument/publishDiagnostics" + + Note: + Each of the configuration options accepts: + โข `false` : Disable this feature + โข `true` : Enable this feature, use default settings. + โข `table` : Enable this feature, use overrides. + โข `function`: Function with signature (bufnr, client_id) that + returns any of the above.> + + 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(bufnr, client_id) + return vim.bo[bufnr].show_signs == false + end, + -- Disable a feature + update_in_insert = false, + } + ) +< -send_notification({method}, {params}) *vim.lsp.rpc.send_notification()* - TODO: Documentation + Parameters: ~ + {config} table Configuration table. + โข underline: (default=true) + โข Apply underlines to diagnostics. + โข See |vim.lsp.diagnostic.set_underline()| - *vim.lsp.rpc.send_request()* -send_request({method}, {params}, {callback}) - TODO: Documentation + โข virtual_text: (default=true) + โข Apply virtual text to line endings. + โข See |vim.lsp.diagnostic.set_virtual_text()| - *vim.lsp.rpc.send_response()* -send_response({request_id}, {err}, {result}) - TODO: Documentation + โข signs: (default=true) + โข Apply signs for diagnostics. + โข See |vim.lsp.diagnostic.set_signs()| -server_request({method}, {params}) *vim.lsp.rpc.server_request()* - TODO: Documentation + โข update_in_insert: (default=false) + โข Update diagnostics in InsertMode or wait + until InsertLeave -try_call({errkind}, {fn}, {...}) *vim.lsp.rpc.try_call()* - TODO: Documentation +save({diagnostics}, {bufnr}, {client_id}) *vim.lsp.diagnostic.save()* + Save diagnostics to the current buffer. + Handles saving diagnostics from multiple clients in the same + buffer. -============================================================================== -Lua module: vim.lsp.util *lsp-util* + Parameters: ~ + {diagnostics} Diagnostic [] + {bufnr} number + {client_id} number - *vim.lsp.util.apply_syntax_to_region()* -apply_syntax_to_region({ft}, {start}, {finish}) - TODO: Documentation +set_loclist({opts}) *vim.lsp.diagnostic.set_loclist()* + Sets the location list - *vim.lsp.util.apply_text_document_edit()* -apply_text_document_edit({text_document_edit}) - TODO: Documentation + Parameters: ~ + {opts} table|nil Configuration table. Keys: + โข {open_loclist}: (boolean, default true) + โข Open loclist after set - *vim.lsp.util.apply_text_edits()* -apply_text_edits({text_edits}, {bufnr}) - TODO: Documentation + โข {client_id}: (number) + โข If nil, will consider all clients attached to + buffer. - *vim.lsp.util.apply_workspace_edit()* -apply_workspace_edit({workspace_edit}) - TODO: Documentation + โข {severity}: (DiagnosticSeverity) + โข Exclusive severity to consider. Overrides + {severity_limit} -buf_clear_diagnostics({bufnr}) *vim.lsp.util.buf_clear_diagnostics()* - TODO: Documentation + โข {severity_limit}: (DiagnosticSeverity) + โข Limit severity of diagnostics found. E.g. + "Warning" means { "Error", "Warning" } will be + valid. -buf_clear_references({bufnr}) *vim.lsp.util.buf_clear_references()* - TODO: Documentation + *vim.lsp.diagnostic.set_signs()* +set_signs({diagnostics}, {bufnr}, {client_id}, {sign_ns}, {opts}) + Set signs for given diagnostics -buf_diagnostics_count({kind}) *vim.lsp.util.buf_diagnostics_count()* - Returns the number of diagnostics of given kind for current - buffer. + Sign characters can be customized with the following commands: +> - Useful for showing diagnostic counts in statusline. eg: + sign define LspDiagnosticsSignError text=E texthl=LspDiagnosticsSignError linehl= numhl= + sign define LspDiagnosticsSignWarning text=W texthl=LspDiagnosticsSignWarning linehl= numhl= + sign define LspDiagnosticsSignInformation text=I texthl=LspDiagnosticsSignInformation linehl= numhl= + sign define LspDiagnosticsSignHint text=H texthl=LspDiagnosticsSignHint linehl= numhl= +< + + Parameters: ~ + {diagnostics} Diagnostic [] + {bufnr} number The buffer number + {client_id} number the client id + {sign_ns} number|nil + {opts} table Configuration for signs. Keys: + โข priority: Set the priority of the signs. + + *vim.lsp.diagnostic.set_underline()* +set_underline({diagnostics}, {bufnr}, {client_id}, {diagnostic_ns}, {opts}) + Set underline for given diagnostics + + Underline highlights can be customized by changing the + following |:highlight| groups. > - function! LspStatus() abort - let sl = '' - if luaeval('not vim.tbl_isempty(vim.lsp.buf_get_clients(0))') - let sl.='%#MyStatuslineLSP#E:' - let sl.='%#MyStatuslineLSPErrors#%{luaeval("vim.lsp.util.buf_diagnostics_count([[Error]])")}' - let sl.='%#MyStatuslineLSP# W:' - let sl.='%#MyStatuslineLSPWarnings#%{luaeval("vim.lsp.util.buf_diagnostics_count([[Warning]])")}' - else - let sl.='%#MyStatuslineLSPErrors#off' - endif - return sl - endfunction - let &l:statusline = '%#MyStatuslineLSP#LSP '.LspStatus() + LspDiagnosticsUnderlineError + LspDiagnosticsUnderlineWarning + LspDiagnosticsUnderlineInformation + LspDiagnosticsUnderlineHint < Parameters: ~ - {kind} Diagnostic severity kind: See - |vim.lsp.protocol.DiagnosticSeverity| + {diagnostics} Diagnostic [] + {bufnr} number The buffer number + {client_id} number the client id + {diagnostic_ns} number|nil + {opts} table Currently unused. + + *vim.lsp.diagnostic.set_virtual_text()* +set_virtual_text({diagnostics}, {bufnr}, {client_id}, {diagnostic_ns}, {opts}) + Set virtual text given diagnostics + + Virtual text highlights can be customized by changing the + following |:highlight| groups. +> + + LspDiagnosticsVirtualTextError + LspDiagnosticsVirtualTextWarning + LspDiagnosticsVirtualTextInformation + LspDiagnosticsVirtualTextHint +< + + Parameters: ~ + {diagnostics} Diagnostic [] + {bufnr} number + {client_id} number + {diagnostic_ns} number + {opts} table Options on how to display virtual + text. Keys: + โข prefix (string): Prefix to display + before virtual text on line + โข spacing (number): Number of spaces to + insert before virtual text + + *vim.lsp.diagnostic.show_line_diagnostics()* +show_line_diagnostics({opts}, {bufnr}, {line_nr}, {client_id}) + Open a floating window with the diagnostics from {line_nr} + + The floating window can be customized with the following + highlight groups: > + + LspDiagnosticsFloatingError + LspDiagnosticsFloatingWarning + LspDiagnosticsFloatingInformation + LspDiagnosticsFloatingHint +< + + Parameters: ~ + {opts} table Configuration table + โข show_header (boolean, default true): Show + "Diagnostics:" header. + {bufnr} number The buffer number + {line_nr} number The line number + {client_id} number|nil the client id Return: ~ - Count of diagnostics + {popup_bufnr, win_id} + - *vim.lsp.util.buf_diagnostics_save_positions()* -buf_diagnostics_save_positions({bufnr}, {diagnostics}) - Saves the diagnostics (Diagnostic[]) into diagnostics_by_buf +============================================================================== +Lua module: vim.lsp.util *lsp-util* + *vim.lsp.util.apply_text_document_edit()* +apply_text_document_edit({text_document_edit}) Parameters: ~ - {bufnr} bufnr for which the diagnostics are for. - {diagnostics} Diagnostics[] received from the language - server. + {text_document_edit} (table) a `TextDocumentEdit` object - *vim.lsp.util.buf_diagnostics_signs()* -buf_diagnostics_signs({bufnr}, {diagnostics}) - Place signs for each diagnostic in the sign column. + See also: ~ + https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentEdit - Sign characters can be customized with the following commands: -> - sign define LspDiagnosticsErrorSign text=E texthl=LspDiagnosticsError linehl= numhl= - sign define LspDiagnosticsWarningSign text=W texthl=LspDiagnosticsWarning linehl= numhl= - sign define LspDiagnosticsInformationSign text=I texthl=LspDiagnosticsInformation linehl= numhl= - sign define LspDiagnosticsHintSign text=H texthl=LspDiagnosticsHint linehl= numhl= -< + *vim.lsp.util.apply_text_edits()* +apply_text_edits({text_edits}, {bufnr}) + Applies a list of text edits to a buffer. - *vim.lsp.util.buf_diagnostics_underline()* -buf_diagnostics_underline({bufnr}, {diagnostics}) - TODO: Documentation + Parameters: ~ + {text_edits} (table) list of `TextEdit` objects + {buf_nr} (number) Buffer id - *vim.lsp.util.buf_diagnostics_virtual_text()* -buf_diagnostics_virtual_text({bufnr}, {diagnostics}) - TODO: Documentation + *vim.lsp.util.apply_workspace_edit()* +apply_workspace_edit({workspace_edit}) + Applies a `WorkspaceEdit` . + + Parameters: ~ + {workspace_edit} (table) `WorkspaceEdit` + +buf_clear_references({bufnr}) *vim.lsp.util.buf_clear_references()* + Removes document highlights from a buffer. + + Parameters: ~ + {bufnr} buffer id *vim.lsp.util.buf_highlight_references()* buf_highlight_references({bufnr}, {references}) - TODO: Documentation + Shows a list of document highlights for a certain buffer. + + Parameters: ~ + {bufnr} buffer id + {references} List of `DocumentHighlight` objects to + highlight character_offset({buf}, {row}, {col}) *vim.lsp.util.character_offset()* - TODO: Documentation + Returns the UTF-32 and UTF-16 offsets for a position in a + certain buffer. + + Parameters: ~ + {buf} buffer id (0 for current) + {row} 0-indexed line + {col} 0-indexed byte offset in line + + Return: ~ + (number, number) UTF-32 and UTF-16 index of the character + in line {row} column {col} in buffer {buf} *vim.lsp.util.close_preview_autocmd()* close_preview_autocmd({events}, {winnr}) - TODO: Documentation + Creates autocommands to close a preview window when events + happen. + + Parameters: ~ + {events} (table) list of events + {winnr} (number) window id of preview window + + See also: ~ + |autocmd-events| *vim.lsp.util.convert_input_to_markdown_lines()* convert_input_to_markdown_lines({input}, {contents}) - TODO: Documentation + 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 + + Return: ~ + {contents}, extended with lines of converted markdown. + + 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}) - TODO: Documentation + Converts `textDocument/SignatureHelp` response to markdown + lines. - *vim.lsp.util.diagnostics_group_by_line()* -diagnostics_group_by_line({diagnostics}) - TODO: Documentation + Parameters: ~ + {signature_help} Response of `textDocument/SignatureHelp` + + Return: ~ + list of lines of converted markdown. + + See also: ~ + https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_signatureHelp *vim.lsp.util.extract_completion_items()* extract_completion_items({result}) - TODO: Documentation + 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 + + Return: ~ + (table) List of completion items + + See also: ~ + https://microsoft.github.io/language-server-protocol/specification#textDocument_completion *vim.lsp.util.fancy_floating_markdown()* fancy_floating_markdown({contents}, {opts}) - Convert markdown into syntax highlighted regions by stripping + 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. The result is shown - in a floating preview TODO: refactor to separate - stripping/converting and make use of open_floating_preview + in a floating preview. 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_left number of columns to pad contents + at left + โข pad_right number of columns to pad contents + at right + โข 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 -find_window_by_var({name}, {value}) *vim.lsp.util.find_window_by_var()* - TODO: Documentation - focusable_float({unique_name}, {fn}) *vim.lsp.util.focusable_float()* - TODO: Documentation + Parameters: ~ + {unique_name} (string) Window variable + {fn} (function) should return create a new + window and return a tuple of + ({focusable_buffer_id}, {window_id}). if + {focusable_buffer_id} is a valid buffer id, + the newly created window will be the new + focus associated with the current buffer + via the tag `unique_name` . + + Return: ~ + (pbufnr, pwinnr) if `fn()` has created a new window; nil + otherwise *vim.lsp.util.focusable_preview()* focusable_preview({unique_name}, {fn}) - TODO: Documentation - -get_completion_word({item}) *vim.lsp.util.get_completion_word()* - TODO: Documentation + Focuses/unfocuses the floating preview window associated with + the current buffer via the window variable `unique_name` . If + no such preview window exists, makes a new one. - *vim.lsp.util.get_current_line_to_cursor()* -get_current_line_to_cursor() - TODO: Documentation + Parameters: ~ + {unique_name} (string) Window variable + {fn} (function) The return values of this + function will be passed directly to + |vim.lsp.util.open_floating_preview()|, in + the case that a new floating window should + be created get_effective_tabstop({bufnr}) *vim.lsp.util.get_effective_tabstop()* - Get visual width of tabstop. + Returns visual width of tabstop. Parameters: ~ {bufnr} (optional, number): Buffer handle, defaults to @@ -1140,52 +1539,105 @@ get_effective_tabstop({bufnr}) *vim.lsp.util.get_effective_tabstop()* See also: ~ |softtabstop| - *vim.lsp.util.get_line_byte_from_position()* -get_line_byte_from_position({bufnr}, {position}) - TODO: Documentation - -get_line_diagnostics() *vim.lsp.util.get_line_diagnostics()* - TODO: Documentation +jump_to_location({location}) *vim.lsp.util.jump_to_location()* + Jumps to a location. - *vim.lsp.util.get_severity_highlight_name()* -get_severity_highlight_name({severity}) - TODO: Documentation + Parameters: ~ + {location} ( `Location` | `LocationLink` ) -jump_to_location({location}) *vim.lsp.util.jump_to_location()* - TODO: Documentation + Return: ~ + `true` if the jump succeeded locations_to_items({locations}) *vim.lsp.util.locations_to_items()* - TODO: Documentation + Returns the items with the byte position calculated correctly + and in sorted order, for display in quickfix and location + lists. + + Parameters: ~ + {locations} (table) list of `Location` s or + `LocationLink` s + + Return: ~ + (table) list of items *vim.lsp.util.make_floating_popup_options()* make_floating_popup_options({width}, {height}, {opts}) - TODO: Documentation + 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) + + Return: ~ + (table) Options *vim.lsp.util.make_formatting_params()* make_formatting_params({options}) - TODO: Documentation + Creates a `FormattingOptions` object for the current buffer + and cursor position. + + Parameters: ~ + {options} Table with valid `FormattingOptions` entries -make_position_param() *vim.lsp.util.make_position_param()* - TODO: Documentation + Return: ~ + `FormattingOptions object + + 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}) + Using the given range in the current buffer, creates an object + that is similar to |vim.lsp.util.make_range_params()|. + + Parameters: ~ + {start_pos} ({number, number}, optional) mark-indexed + position. Defaults to the start of the last + visual selection. + {end_pos} ({number, number}, optional) mark-indexed + position. Defaults to the end of the last + visual selection. + + Return: ~ + { textDocument = { uri = `current_file_uri` }, range = { + start = `start_position` , end = `end_position` } } make_position_params() *vim.lsp.util.make_position_params()* - TODO: Documentation + Creates a `TextDocumentPositionParams` object for the current + buffer and cursor position. + + Return: ~ + `TextDocumentPositionParams` object + + See also: ~ + https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentPositionParams make_range_params() *vim.lsp.util.make_range_params()* - TODO: Documentation + 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` . + + Return: ~ + { textDocument = { uri = `current_file_uri` }, range = { + start = `current_position` , end = `current_position` } } make_text_document_params() *vim.lsp.util.make_text_document_params()* - TODO: Documentation + Creates a `TextDocumentIdentifier` object for the current + buffer. -npcall({fn}, {...}) *vim.lsp.util.npcall()* - TODO: Documentation + Return: ~ + `TextDocumentIdentifier` -ok_or_nil({status}, {...}) *vim.lsp.util.ok_or_nil()* - TODO: Documentation + See also: ~ + https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentIdentifier *vim.lsp.util.open_floating_preview()* open_floating_preview({contents}, {filetype}, {opts}) - Show contents in a floating window + Shows contents in a floating window. Parameters: ~ {contents} table of lines to show in window @@ -1193,17 +1645,20 @@ open_floating_preview({contents}, {filetype}, {opts}) {opts} dictionary with optional fields Return: ~ - bufnr,winnr buffer and window number of floating window or - nil + bufnr,winnr buffer and window number of the newly created + floating preview window parse_snippet({input}) *vim.lsp.util.parse_snippet()* - TODO: Documentation + Parses snippets in a completion entry. + + Parameters: ~ + {input} (string) unparsed snippet -parse_snippet_rec({input}, {inner}) *vim.lsp.util.parse_snippet_rec()* - TODO: Documentation + Return: ~ + (string) parsed snippet preview_location({location}) *vim.lsp.util.preview_location()* - Preview a location in a floating windows + Previews a location in a floating window behavior depends on type of location: โข for Location, range is shown (e.g., function definition) @@ -1211,52 +1666,217 @@ preview_location({location}) *vim.lsp.util.preview_location()* function definition) Parameters: ~ - {location} a single Location or LocationLink + {location} a single `Location` or `LocationLink` Return: ~ - bufnr,winnr buffer and window number of floating window or - nil - - *vim.lsp.util.remove_unmatch_completion_items()* -remove_unmatch_completion_items({items}, {prefix}) - TODO: Documentation + (bufnr,winnr) buffer and window number of floating window + or nil set_lines({lines}, {A}, {B}, {new_lines}) *vim.lsp.util.set_lines()* - TODO: Documentation + Replaces text in a range with new text. -set_loclist({items}) *vim.lsp.util.set_loclist()* - TODO: Documentation + CAUTION: Changes in-place! -set_qflist({items}) *vim.lsp.util.set_qflist()* - TODO: Documentation + 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 -show_line_diagnostics() *vim.lsp.util.show_line_diagnostics()* - TODO: Documentation + Return: ~ + (table) The modified {lines} object -sort_by_key({fn}) *vim.lsp.util.sort_by_key()* - TODO: Documentation +set_loclist({items}) *vim.lsp.util.set_loclist()* + Fills current window's location list with given list of items. + Can be obtained with e.g. |vim.lsp.util.locations_to_items()|. -sort_completion_items({items}) *vim.lsp.util.sort_completion_items()* - TODO: Documentation + Parameters: ~ + {items} (table) list of items -split_lines({value}) *vim.lsp.util.split_lines()* - TODO: Documentation +set_qflist({items}) *vim.lsp.util.set_qflist()* + Fills quickfix list with given list of items. Can be obtained + with e.g. |vim.lsp.util.locations_to_items()|. + + Parameters: ~ + {items} (table) list of items symbols_to_items({symbols}, {bufnr}) *vim.lsp.util.symbols_to_items()* - Convert symbols to quickfix list items + Converts symbols to quickfix list items. Parameters: ~ {symbols} DocumentSymbol[] or SymbolInformation[] *vim.lsp.util.text_document_completion_list_to_complete_items()* text_document_completion_list_to_complete_items({result}, {prefix}) - TODO: Documentation + 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 + + Return: ~ + { matches = complete-items table, incomplete = bool } + + See also: ~ + |complete-items| trim_empty_lines({lines}) *vim.lsp.util.trim_empty_lines()* - TODO: Documentation + Removes empty lines from the beginning and end. + + Parameters: ~ + {lines} (table) list of lines to trim + + Return: ~ + (table) trimmed list of lines *vim.lsp.util.try_trim_markdown_code_blocks()* try_trim_markdown_code_blocks({lines}) - TODO: Documentation + 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! + + Parameters: ~ + {lines} (table) list of lines + + 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. + + Return: ~ + (string) log filename + +set_level({level}) *vim.lsp.log.set_level()* + Sets the current log level. + + 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. + + Parameters: ~ + {level} number log level + + 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. + + Parameters: ~ + {err} (table) The error object + + Return: ~ + (string) The formatted error message + +notify({method}, {params}) *vim.lsp.rpc.notify()* + Sends a notification to the LSP server. + + 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 + +request({method}, {params}, {callback}) *vim.lsp.rpc.request()* + 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 + + 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. + + 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 + + *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. + + 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|. + + +============================================================================== +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. + + *vim.lsp.protocol.resolve_capabilities()* +resolve_capabilities({server_capabilities}) + `*` to match one or more characters in a path segment `?` to + match on one character in a path segment `**` to match any + number of path segments, including none `{}` to group + conditions (e.g. `**โ/*.{ts,js}` matches all TypeScript and + JavaScript files) `[]` to declare a range of characters to + match in a path segment (e.g., `example.[0-9]` to match on + `example.0` , `example.1` , โฆ) `[!...]` to negate a range of + characters to match in a path segment (e.g., `example.[!0-9]` + to match on `example.a` , `example.b` , but not `example.0` ) vim:tw=78:ts=8:ft=help:norl: diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt index 60c7a60d25..a03de10a17 100644 --- a/runtime/doc/lua.txt +++ b/runtime/doc/lua.txt @@ -9,7 +9,7 @@ Lua engine *lua* *Lua* Type |gO| to see the table of contents. ============================================================================== -Introduction *lua-intro* +INTRODUCTION *lua-intro* The Lua 5.1 language is builtin and always available. Try this command to get an idea of what lurks beneath: > @@ -30,7 +30,7 @@ finds and loads Lua modules. The conventions are similar to VimL plugins, with some extra features. See |lua-require-example| for a walkthrough. ============================================================================== -Importing Lua modules *lua-require* +IMPORTING LUA MODULES *lua-require* *lua-package-path* Nvim automatically adjusts `package.path` and `package.cpath` according to @@ -233,7 +233,7 @@ lua/charblob.lua: > } ============================================================================== -Commands *lua-commands* +COMMANDS *lua-commands* These commands execute a Lua chunk from either the command line (:lua, :luado) or a file (:luafile) on the given line [range]. As always in Lua, each chunk @@ -456,7 +456,7 @@ management. Try this command to see available functions: > :lua print(vim.inspect(vim.loop)) -Reference: http://docs.libuv.org +Reference: https://github.com/luvit/luv/blob/master/docs.md Examples: https://github.com/luvit/luv/tree/master/examples *E5560* *lua-loop-callbacks* @@ -551,223 +551,6 @@ Example: TCP echo-server *tcp-server* print('TCP echo-server listening on port: '..server:getsockname().port) ------------------------------------------------------------------------------ -VIM.TREESITTER *lua-treesitter* - -Nvim integrates the tree-sitter library for incremental parsing of buffers. - -Currently Nvim does not provide the tree-sitter parsers, instead these must -be built separately, for instance using the tree-sitter utility. The only -exception is a C parser being included in official builds for testing -purposes. Parsers are searched for as `parser/{lang}.*` in any 'runtimepath' -directory. A parser can also be loaded manually using a full path: > - - vim.treesitter.require_language("python", "/path/to/python.so") - -<Create a parser for a buffer and a given language (if another plugin uses the -same buffer/language combination, it will be safely reused). Use > - - parser = vim.treesitter.get_parser(bufnr, lang) - -<`bufnr=0` can be used for current buffer. `lang` will default to 'filetype' (this -doesn't work yet for some filetypes like "cpp") Currently, the parser will be -retained for the lifetime of a buffer but this is subject to change. A plugin -should keep a reference to the parser object as long as it wants incremental -updates. - -Parser methods *lua-treesitter-parser* - -tsparser:parse() *tsparser:parse()* -Whenever you need to access the current syntax tree, parse the buffer: > - - tstree = parser:parse() - -<This will return an immutable tree that represents the current state of the -buffer. When the plugin wants to access the state after a (possible) edit -it should call `parse()` again. If the buffer wasn't edited, the same tree will -be returned again without extra work. If the buffer was parsed before, -incremental parsing will be done of the changed parts. - -NB: to use the parser directly inside a |nvim_buf_attach| Lua callback, you must -call `get_parser()` before you register your callback. But preferably parsing -shouldn't be done directly in the change callback anyway as they will be very -frequent. Rather a plugin that does any kind of analysis on a tree should use -a timer to throttle too frequent updates. - -tsparser:set_included_ranges(ranges) *tsparser:set_included_ranges()* - Changes the ranges the parser should consider. This is used for - language injection. `ranges` should be of the form (all zero-based): > - { - {start_node, end_node}, - ... - } -< - NOTE: `start_node` and `end_node` are both inclusive. - -Tree methods *lua-treesitter-tree* - -tstree:root() *tstree:root()* - Return the root node of this tree. - - -Node methods *lua-treesitter-node* - -tsnode:parent() *tsnode:parent()* - Get the node's immediate parent. - -tsnode:child_count() *tsnode:child_count()* - Get the node's number of children. - -tsnode:child(N) *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(N) *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: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: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: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: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:descendant_for_range(start_row, start_col, end_row, end_col) - *tsnode:descendant_for_range()* - Get the smallest node within this node that spans the given range of - (row, column) positions - -tsnode:named_descendant_for_range(start_row, start_col, end_row, end_col) - *tsnode:named_descendant_for_range()* - Get the smallest named node within this node that spans the given - range of (row, column) positions - -Query methods *lua-treesitter-query* - -Tree-sitter queries are supported, with some limitations. Currently, the only -supported match predicate is `eq?` (both comparing a capture against a string -and two captures against each other). - -vim.treesitter.parse_query(lang, query) - *vim.treesitter.parse_query(()* - Parse the query as a string. (If the query is in a file, the caller - should read the contents into a string before calling). - -query:iter_captures(node, bufnr, start_row, end_row) - *query:iter_captures()* - Iterate over all captures from all matches inside a `node`. - `bufnr` 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. `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) - - The iterator returns two values, a numeric id identifying the capture - and the captured node. The following example shows how to get captures - by name: -> - for id, node in query:iter_captures(tree:root(), bufnr, first, last) do - local name = query.captures[id] -- name of the capture in the query - -- typically useful info about the node: - local type = node:type() -- type of the captured node - local row1, col1, row2, col2 = node:range() -- range of the capture - ... use the info here ... - end -< -query:iter_matches(node, bufnr, start_row, end_row) - *query:iter_matches()* - 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, and a table mapping - capture indices to nodes. If the query has more than one pattern - the capture table might be sparse, and e.g. `pairs` should be used and not - `ipairs`. Here an example iterating over all captures in - every match: -> - for pattern, match in cquery:iter_matches(tree:root(), bufnr, first, last) do - for id,node in pairs(match) do - local name = query.captures[id] - -- `node` was captured by the `name` capture in the match - ... use the info here ... - end - end -> -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 indented -for those who want to experiment with this feature and contribute to -its development. - -Highlights are defined in the same query format as in the tree-sitter highlight -crate, which some limitations and additions. Set a highlight query for a -buffer with this code: > - - local query = [[ - "for" @keyword - "if" @keyword - "return" @keyword - - (string_literal) @string - (number_literal) @number - (comment) @comment - - (preproc_function_def name: (identifier) @function) - - ; ... more definitions - ]] - - highlighter = vim.treesitter.TSHighlighter.new(query, bufnr, lang) - -- alternatively, to use the current buffer and its filetype: - -- highlighter = vim.treesitter.TSHighlighter.new(query) - - -- Don't recreate the highlighter for the same buffer, instead - -- modify the query like this: - local query2 = [[ ... ]] - highlighter:set_query(query2) - -As mentioned above the supported predicate is currently only `eq?`. `match?` -predicates behave like matching always fails. As an addition a capture which -begin with an upper-case letter like `@WarningMsg` will map directly to this -highlight group, if defined. Also if the predicate begins with upper-case and -contains a dot only the part before the first will be interpreted as the -highlight group. As an example, this warns of a binary expression with two -identical identifiers, highlighting both as |hl-WarningMsg|: > - - ((binary_expression left: (identifier) @WarningMsg.left right: (identifier) @WarningMsg.right) - (eq? @WarningMsg.left @WarningMsg.right)) - ------------------------------------------------------------------------------- VIM.HIGHLIGHT *lua-highlight* Nvim includes a function for highlighting a selection on yank (see for example @@ -839,11 +622,6 @@ vim.api.{func}({...}) *vim.api* Example: call the "nvim_get_current_line()" API function: > print(tostring(vim.api.nvim_get_current_line())) -vim.call({func}, {...}) *vim.call()* - Invokes |vim-function| or |user-function| {func} with arguments {...}. - See also |vim.fn|. Equivalent to: > - vim.fn[func]({...}) - 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 @@ -876,6 +654,34 @@ vim.region({bufnr}, {pos1}, {pos2}, {type}, {inclusive}) *vim.region()* whether the selection is inclusive or not, into a zero-indexed table of linewise selections of the form `{linenr = {startcol, endcol}}` . + *vim.register_keystroke_callback()* +vim.register_keystroke_callback({fn}, {ns_id}) + Register a lua {fn} with an {ns_id} to be run after every keystroke. + + Parameters: ~ + {fn}: (function): Function to call on keystroke. + It should take one argument, which is a string. + The string will contain the literal keys typed. + See |i_CTRL-V| + + If {fn} is `nil`, it removes the callback for the + associated {ns_id}. + + {ns_id}: (number) Namespace ID. If not passed or 0, will generate + and return a new namespace ID from |nvim_create_namespace()| + + Return: ~ + (number) Namespace ID associated with {fn} + + NOTE: {fn} will be automatically removed if an error occurs while + calling. This is to prevent the annoying situation of every keystroke + erroring while trying to remove a broken callback. + + NOTE: {fn} will receive the keystrokes after mappings have been + evaluated + + NOTE: {fn} will *NOT* be cleared from |nvim_buf_clear_namespace()| + 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. @@ -931,13 +737,20 @@ vim.defer_fn({fn}, {timeout}) *vim.defer_fn* Returns: ~ |vim.loop|.new_timer() object -vim.wait({time}, {callback} [, {interval}]) *vim.wait()* +vim.wait({time} [, {callback}, {interval}, {fast_only}]) *vim.wait()* 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. + Parameters: ~ + {time} Number of milliseconds to wait + {callback} Optional callback. Waits until {callback} returns true + {interval} (Approximate) number of milliseconds to wait between polls + {fast_only} If true, only |api-fast| events will be processed. + If called from while in an |api-fast| event, will + automatically be set to `true`. Returns: ~ If {callback} returns `true` during the {time}: @@ -975,22 +788,6 @@ vim.wait({time}, {callback} [, {interval}]) *vim.wait()* end < -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']({...}) -< - 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: vim.fn keys are generated lazily, thus `pairs(vim.fn)` only - enumerates functions that were called at least once. - 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 @@ -1026,64 +823,103 @@ vim.types *vim.types* `vim.types.dictionary` will not change or that `vim.types` table will only contain values for these three types. -============================================================================== -Vim Internal Variables *lua-vim-internal-variables* - -Built-in Vim dictionaries can be accessed and set idiomatically in Lua by each -of the following tables. - -To set a value: > - - vim.g.my_global_variable = 5 -< +------------------------------------------------------------------------------ +LUA-VIMSCRIPT BRIDGE *lua-vimscript* -To read a value: > +Nvim Lua provides an interface to Vimscript variables and functions, and +editor commands and options. - print(vim.g.my_global_variable) -< +vim.call({func}, {...}) *vim.call()* + Invokes |vim-function| or |user-function| {func} with arguments {...}. + See also |vim.fn|. + Equivalent to: > + vim.fn[func]({...}) -To delete a value: > +vim.cmd({cmd}) *vim.cmd()* + Invokes an Ex command (the ":" commands, Vimscript statements). + See also |ex-cmd-index|. + Example: > + vim.cmd('echo 42') - vim.g.my_global_variable = nil +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']({...}) < + 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. -vim.g *vim.g* - Table with values from |g:| - Keys with no values set will result in `nil`. - -vim.b *vim.b* - Gets a buffer-scoped (b:) variable for the current buffer. - Keys with no values set will result in `nil`. - -vim.w *vim.w* - Gets a window-scoped (w:) variable for the current window. - Keys with no values set will result in `nil`. + Note: |v:null| values as part of the return value is represented as + |vim.NIL| special value -vim.t *vim.t* - Gets a tabpage-scoped (t:) variable for the current table. - Keys with no values set will result in `nil`. + Note: vim.fn keys are generated lazily, thus `pairs(vim.fn)` only + enumerates functions that were called at least once. -vim.v *vim.v* - Gets a v: variable. - Keys with no values set will result in `nil`. + *lua-vim-variables* +The Vim editor global dictionaries |g:| |w:| |b:| |t:| |v:| can be accessed +from Lua conveniently and idiomatically by referencing the `vim.*` Lua tables +described below. In this way you can easily read and modify global Vimscript +variables from Lua. -Vim Internal Options *lua-vim-internal-options* +Example: > -Read, set and clear vim |options| in Lua by each of the following tables. + vim.g.foo = 5 -- Set the g:foo Vimscript variable. + print(vim.g.foo) -- Get and print the g:foo Vimscript variable. + vim.g.foo = nil -- Delete (:unlet) the Vimscript variable. + +vim.g *vim.g* + 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`. + +vim.w *vim.w* + Window-scoped (|w:|) variables for the current window. + Invalid or unset key returns `nil`. + +vim.t *vim.t* + Tabpage-scoped (|t:|) variables for the current tabpage. + Invalid or unset key returns `nil`. + +vim.v *vim.v* + |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) +< + *lua-vim-options* +From Lua you can work with editor |options| by reading and setting items in +these Lua tables: -vim.o *vim.o* - Table with values from |options| - Invalid keys will result in an error. +vim.o *vim.o* + Get or set editor options, like |:set|. Invalid key is an error. + Example: > + vim.o.cmdheight = 4 + print(vim.o.columns) -vim.bo *vim.bo* - Gets a buffer-scoped option for the current buffer. - Invalid keys will result in an error. +vim.bo *vim.bo* + Get or set buffer-scoped |local-options|. Invalid key is an error. + Example: > + vim.bo.buflisted = true + print(vim.bo.comments) -vim.wo *vim.wo* - Gets a window-scoped option for the current window. - Invalid keys will result in an error. +vim.wo *vim.wo* + Get or set window-scoped |local-options|. Invalid key is an error. + Example: > + vim.wo.cursorcolumn = true + print(vim.wo.foldmarker) ============================================================================== @@ -1195,6 +1031,9 @@ is_callable({f}) *vim.is_callable()* Return: ~ true if `f` is callable, else false +is_valid({opt}) *vim.is_valid()* + TODO: Documentation + list_extend({dst}, {src}, {start}, {finish}) *vim.list_extend()* Extends a list-like table with the values of another list-like table. @@ -1229,7 +1068,7 @@ split({s}, {sep}, {plain}) *vim.split()* Splits a string at each instance of a separator. Examples: > - split(":aa::b:", ":") --> {'','aa','','bb',''} + split(":aa::b:", ":") --> {'','aa','','b',''} split("axaby", "ab?") --> {'','x','y'} split(x*yz*o, "*", true) --> {'x','yz','o'} < @@ -1339,17 +1178,21 @@ tbl_flatten({t}) *vim.tbl_flatten()* Fromhttps://github.com/premake/premake-core/blob/master/src/base/table.lua tbl_isempty({t}) *vim.tbl_isempty()* + Checks if a table is empty. + + Parameters: ~ + {t} Table to check + See also: ~ - Fromhttps://github.com/premake/premake-core/blob/master/src/base/table.lua@paramt Table to check + https://github.com/premake/premake-core/blob/master/src/base/table.lua tbl_islist({t}) *vim.tbl_islist()* - Determine whether a Lua table can be treated as an array. + Tests if a Lua table can be treated as an array. - An empty table `{}` will default to being treated as an array. - Use `vim.emtpy_dict()` to create a table treated as an empty - dict. Empty tables returned by `rpcrequest()` and `vim.fn` - functions can be checked using this function whether they - represent empty API arrays and vimL lists. + 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 @@ -1446,8 +1289,53 @@ validate({opt}) *vim.validate()* โข arg_value: argument value โข fn: any function accepting one argument, returns true if and only if the argument is - valid + 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 + + Parameters: ~ + {bufnr} (number): Buffer number + + Return: ~ + URI + +uri_from_fname({path}) *vim.uri_from_fname()* + Get a URI from a file path. + + Parameters: ~ + {path} (string): Path to file + + Return: ~ + URI + +uri_to_bufnr({uri}) *vim.uri_to_bufnr()* + Return or create a buffer for a uri. + + Parameters: ~ + {uri} (string): The URI + + Return: ~ + bufnr. + + Note: + Creates buffer but does not load it + +uri_to_fname({uri}) *vim.uri_to_fname()* + Get a filename from a URI + + Parameters: ~ + {uri} (string): The URI + + Return: ~ + Filename + vim:tw=78:ts=8:ft=help:norl: diff --git a/runtime/doc/map.txt b/runtime/doc/map.txt index ed31ecc42e..edec4a8de7 100644 --- a/runtime/doc/map.txt +++ b/runtime/doc/map.txt @@ -1078,6 +1078,10 @@ When executing an autocommand or a user command, it will run in the context of the script it was defined in. This makes it possible that the command calls a local function or uses a local mapping. +In case the value is used in a context where <SID> cannot be correctly +expanded, use the expand() function: > + let &includexpr = expand('<SID>') .. 'My_includeexpr()' + Otherwise, using "<SID>" outside of a script context is an error. If you need to get the script number to use in a complicated script, you can @@ -1132,9 +1136,10 @@ scripts. :com[mand] *:com* *:command* List all user-defined commands. When listing commands, - the characters in the first two columns are + the characters in the first columns are: ! Command has the -bang attribute " Command has the -register attribute + | Command has the -bar attribute b Command is local to current buffer (see below for details on attributes) The list can be filtered on command name with @@ -1162,6 +1167,10 @@ See |:verbose-cmd| for more information. attributes (see below) are {attr}. If the command already exists, an error is reported, unless a ! is specified, in which case the command is redefined. + There is one exception: When sourcing a script again, + a command that was previously defined in that script + will be silently replaced. + :delc[ommand] {cmd} *:delc* *:delcommand* *E184* Delete the user-defined command {cmd}. @@ -1169,7 +1178,8 @@ See |:verbose-cmd| for more information. :comc[lear] *:comc* *:comclear* Delete all user-defined commands. -Command attributes + +Command attributes ~ User-defined commands are treated by Vim just like any other Ex commands. They can have arguments, or have a range specified. Arguments are subject to @@ -1180,8 +1190,9 @@ There are a number of attributes, split into four categories: argument handling, completion behavior, range handling, and special cases. The attributes are described below, by category. -Argument handling *E175* *E176* *:command-nargs* +Argument handling ~ + *E175* *E176* *:command-nargs* By default, a user defined command will take no arguments (and an error is reported if any are supplied). However, it is possible to specify that the command can take arguments, using the -nargs attribute. Valid cases are: @@ -1257,9 +1268,9 @@ completion can be enabled: Note: That some completion methods might expand environment variables. -Custom completion *:command-completion-custom* - *:command-completion-customlist* - *E467* *E468* +Custom completion ~ + *:command-completion-custom* + *:command-completion-customlist* *E467* *E468* It is possible to define customized completion schemes via the "custom,{func}" or the "customlist,{func}" completion argument. The {func} part should be a function with the following signature: > @@ -1304,8 +1315,8 @@ the 'path' option: > This example does not work for file names with spaces! -Range handling *E177* *E178* *:command-range* - *:command-count* +Range handling ~ + *E177* *E178* *:command-range* *:command-count* By default, user-defined commands do not accept a line number range. However, it is possible to specify that the command does take a range (the -range attribute), or that it takes an arbitrary count value, either in the line @@ -1332,17 +1343,19 @@ It is possible that the special characters in the range like `.`, `$` or `%` which by default correspond to the current line, last line and the whole buffer, relate to arguments, (loaded) buffers, windows or tab pages. -Possible values are: - -addr=lines Range of lines (this is the default) - -addr=arguments Range for arguments - -addr=buffers Range for buffers (also not loaded buffers) - -addr=loaded_buffers Range for loaded buffers - -addr=windows Range for windows - -addr=tabs Range for tab pages - -addr=other other kind of range +Possible values are (second column is the short name used in listing): + -addr=lines Range of lines (this is the default) + -addr=arguments arg Range for arguments + -addr=buffers buf Range for buffers (also not loaded buffers) + -addr=loaded_buffers load Range for loaded buffers + -addr=windows win Range for windows + -addr=tabs tab Range for tab pages + -addr=quickfix qf Range for quickfix entries + -addr=other ? other kind of range -Special cases *:command-bang* *:command-bar* +Special cases ~ + *:command-bang* *:command-bar* *:command-register* *:command-buffer* There are some special cases as well: @@ -1360,7 +1373,8 @@ replacement text separately. Note that these arguments can be abbreviated, but that is a deprecated feature. Use the full name for new scripts. -Replacement text + +Replacement text ~ The replacement text for a user defined command is scanned for special escape sequences, using <...> notation. Escape sequences are replaced with values diff --git a/runtime/doc/mbyte.txt b/runtime/doc/mbyte.txt index 127c46c27d..a6410a09cb 100644 --- a/runtime/doc/mbyte.txt +++ b/runtime/doc/mbyte.txt @@ -71,24 +71,15 @@ If you are working in a terminal (emulator) you must make sure it accepts UTF-8, the encoding which Vim is working with. Otherwise only ASCII can be displayed and edited correctly. -For the GUI you must select fonts that work with UTF-8. This -is the difficult part. It depends on the system you are using, the locale and -a few other things. - -For X11 you can set the 'guifontset' option to a list of fonts that together -cover the characters that are used. Example for Korean: > - - :set guifontset=k12,r12 - -Alternatively, you can set 'guifont' and 'guifontwide'. 'guifont' is used for -the single-width characters, 'guifontwide' for the double-width characters. -Thus the 'guifontwide' font must be exactly twice as wide as 'guifont'. -Example for UTF-8: > +For the GUI you must select fonts that work with UTF-8. You can set 'guifont' +and 'guifontwide'. 'guifont' is used for the single-width characters, +'guifontwide' for the double-width characters. Thus the 'guifontwide' font +must be exactly twice as wide as 'guifont'. Example for UTF-8: > :set guifont=-misc-fixed-medium-r-normal-*-18-120-100-100-c-90-iso10646-1 :set guifontwide=-misc-fixed-medium-r-normal-*-18-120-100-100-c-180-iso10646-1 -You can also set 'guifont' alone, Vim will try to find a matching +You can also set 'guifont' alone, the Nvim GUI will try to find a matching 'guifontwide' for you. @@ -267,16 +258,16 @@ Recognized 'fileencoding' values include: *encoding-values* 1 cp1258 Vietnamese 1 cp{number} MS-Windows: any installed single-byte codepage 2 cp932 Japanese (Windows only) -2 euc-jp Japanese (Unix only) -2 sjis Japanese (Unix only) -2 cp949 Korean (Unix and Windows) -2 euc-kr Korean (Unix only) +2 euc-jp Japanese +2 sjis Japanese +2 cp949 Korean +2 euc-kr Korean 2 cp936 simplified Chinese (Windows only) -2 euc-cn simplified Chinese (Unix only) -2 cp950 traditional Chinese (on Unix alias for big5) -2 big5 traditional Chinese (on Windows alias for cp950) -2 euc-tw traditional Chinese (Unix only) -2 2byte-{name} Unix: any double-byte encoding (Vim specific name) +2 euc-cn simplified Chinese +2 cp950 traditional Chinese (alias for big5) +2 big5 traditional Chinese (alias for cp950) +2 euc-tw traditional Chinese +2 2byte-{name} any double-byte encoding (Vim-specific name) 2 cp{number} MS-Windows: any installed double-byte codepage u utf-8 32 bit UTF-8 encoded Unicode (ISO/IEC 10646-1) u ucs-2 16 bit UCS-2 encoded Unicode (ISO/IEC 10646-1) @@ -298,14 +289,14 @@ the same encoding is used and it's called latin1. 'isprint' can be used to display the characters 0x80 - 0xA0 or not. Several aliases can be used, they are translated to one of the names above. -An incomplete list: +Incomplete list: 1 ansi same as latin1 (obsolete, for backward compatibility) -2 japan Japanese: on Unix "euc-jp", on MS-Windows cp932 -2 korea Korean: on Unix "euc-kr", on MS-Windows cp949 -2 prc simplified Chinese: on Unix "euc-cn", on MS-Windows cp936 +2 japan Japanese: "euc-jp" +2 korea Korean: "euc-kr" +2 prc simplified Chinese: "euc-cn" 2 chinese same as "prc" -2 taiwan traditional Chinese: on Unix "euc-tw", on MS-Windows cp950 +2 taiwan traditional Chinese: "euc-tw" u utf8 same as utf-8 u unicode same as ucs-2 u ucs2be same as ucs-2 (big endian) @@ -394,148 +385,6 @@ conversion needs to be done. These conversions are supported: request a very large buffer, more than Vim is willing to provide). Try getting another iconv() implementation. - *iconv-dynamic* -On MS-Windows Vim can be compiled with the |+iconv/dyn| feature. This means -Vim will search for the "iconv.dll" and "libiconv.dll" libraries. When -neither of them can be found Vim will still work but some conversions won't be -possible. - -============================================================================== -Fonts on X11 *mbyte-fonts-X11* - -Unfortunately, using fonts in X11 is complicated. The name of a single-byte -font is a long string. For multi-byte fonts we need several of these... - -First of all, Vim only accepts fixed-width fonts for displaying text. You -cannot use proportionally spaced fonts. This excludes many of the available -(and nicer looking) fonts. However, for menus and tooltips any font can be -used. - -Note that Display and Input are independent. It is possible to see your -language even though you have no input method for it. - -You should get a default font for menus and tooltips that works, but it might -be ugly. Read the following to find out how to select a better font. - - -X LOGICAL FONT DESCRIPTION (XLFD) - *XLFD* -XLFD is the X font name and contains the information about the font size, -charset, etc. The name is in this format: - -FOUNDRY-FAMILY-WEIGHT-SLANT-WIDTH-STYLE-PIXEL-POINT-X-Y-SPACE-AVE-CR-CE - -Each field means: - -- FOUNDRY: FOUNDRY field. The company that created the font. -- FAMILY: FAMILY_NAME field. Basic font family name. (helvetica, gothic, - times, etc) -- WEIGHT: WEIGHT_NAME field. How thick the letters are. (light, medium, - bold, etc) -- SLANT: SLANT field. - r: Roman (no slant) - i: Italic - o: Oblique - ri: Reverse Italic - ro: Reverse Oblique - ot: Other - number: Scaled font -- WIDTH: SETWIDTH_NAME field. Width of characters. (normal, condensed, - narrow, double wide) -- STYLE: ADD_STYLE_NAME field. Extra info to describe font. (Serif, Sans - Serif, Informal, Decorated, etc) -- PIXEL: PIXEL_SIZE field. Height, in pixels, of characters. -- POINT: POINT_SIZE field. Ten times height of characters in points. -- X: RESOLUTION_X field. X resolution (dots per inch). -- Y: RESOLUTION_Y field. Y resolution (dots per inch). -- SPACE: SPACING field. - p: Proportional - m: Monospaced - c: CharCell -- AVE: AVERAGE_WIDTH field. Ten times average width in pixels. -- CR: CHARSET_REGISTRY field. The name of the charset group. -- CE: CHARSET_ENCODING field. The rest of the charset name. For some - charsets, such as JIS X 0208, if this field is 0, code points has - the same value as GL, and GR if 1. - -For example, in case of a 16 dots font corresponding to JIS X 0208, it is -written like: - -misc-fixed-medium-r-normal--16-110-100-100-c-160-jisx0208.1990-0 - - -X FONTSET - *fontset* *xfontset* -A single-byte charset is typically associated with one font. For multi-byte -charsets a combination of fonts is often used. This means that one group of -characters are used from one font and another group from another font (which -might be double wide). This collection of fonts is called a fontset. - -Which fonts are required in a fontset depends on the current locale. X -windows maintains a table of which groups of characters are required for a -locale. You have to specify all the fonts that a locale requires in the -'guifontset' option. - -NOTE: The fontset always uses the current locale, even though 'encoding' may -be set to use a different charset. In that situation you might want to use -'guifont' and 'guifontwide' instead of 'guifontset'. - -Example: - |charset| language "groups of characters" ~ - GB2312 Chinese (simplified) ISO-8859-1 and GB 2312 - Big5 Chinese (traditional) ISO-8859-1 and Big5 - CNS-11643 Chinese (traditional) ISO-8859-1, CNS 11643-1 and CNS 11643-2 - EUC-JP Japanese JIS X 0201 and JIS X 0208 - EUC-KR Korean ISO-8859-1 and KS C 5601 (KS X 1001) - -You can search for fonts using the xlsfonts command. For example, when you're -searching for a font for KS C 5601: > - xlsfonts | grep ksc5601 - -This is complicated and confusing. You might want to consult the X-Windows -documentation if there is something you don't understand. - - *base_font_name_list* -When you have found the names of the fonts you want to use, you need to set -the 'guifontset' option. You specify the list by concatenating the font names -and putting a comma in between them. - -For example, when you use the ja_JP.eucJP locale, this requires JIS X 0201 -and JIS X 0208. You could supply a list of fonts that explicitly specifies -the charsets, like: > - - :set guifontset=-misc-fixed-medium-r-normal--14-130-75-75-c-140-jisx0208.1983-0, - \-misc-fixed-medium-r-normal--14-130-75-75-c-70-jisx0201.1976-0 - -Alternatively, you can supply a base font name list that omits the charset -name, letting X-Windows select font characters required for the locale. For -example: > - - :set guifontset=-misc-fixed-medium-r-normal--14-130-75-75-c-140, - \-misc-fixed-medium-r-normal--14-130-75-75-c-70 - -Alternatively, you can supply a single base font name that allows X-Windows to -select from all available fonts. For example: > - - :set guifontset=-misc-fixed-medium-r-normal--14-* - -Alternatively, you can specify alias names. See the fonts.alias file in the -fonts directory (e.g., /usr/X11R6/lib/X11/fonts/). For example: > - - :set guifontset=k14,r14 -< - *E253* -Note that in East Asian fonts, the standard character cell is square. When -mixing a Latin font and an East Asian font, the East Asian font width should -be twice the Latin font width. - -If 'guifontset' is not empty, the "font" argument of the |:highlight| command -is also interpreted as a fontset. For example, you should use for -highlighting: > - :hi Comment font=english_font,your_font -If you use a wrong "font" argument you will get an error message. -Also make sure that you set 'guifontset' before setting fonts for highlight -groups. - ============================================================================== Input on X11 *mbyte-XIM* @@ -647,10 +496,6 @@ Note that Display and Input are independent. It is possible to see your language even though you have no input method for it. But when your Display method doesn't match your Input method, the text will be displayed wrong. - Note: You can not use IM unless you specify 'guifontset'. - Therefore, Latin users, you have to also use 'guifontset' - if you use IM. - To input your language you should run the |IM-server| which supports your language and |conversion-server| if needed. @@ -962,9 +807,9 @@ Vim has comprehensive UTF-8 support. It works well in: - MS-Windows GUI - several other platforms -Double-width characters are supported. This works best with 'guifontwide' or -'guifontset'. When using only 'guifont' the wide characters are drawn in the -normal width and a space to fill the gap. +Double-width characters are supported. Works best with 'guifontwide'. When +using only 'guifont' the wide characters are drawn in the normal width and +a space to fill the gap. *bom-bytes* When reading a file a BOM (Byte Order Mark) can be used to recognize the @@ -1031,7 +876,6 @@ this: 1. Set 'guifont' and let Vim find a matching 'guifontwide' 2. Set 'guifont' and 'guifontwide' -3. Set 'guifontset' See the documentation for each option for details. Example: > @@ -1077,10 +921,7 @@ not everybody is able to type a composing character. ============================================================================== Overview of options *mbyte-options* -These options are relevant for editing multi-byte files. Check the help in -options.txt for detailed information. - -'encoding' Internal text encoding, always "utf-8". +These options are relevant for editing multi-byte files. 'fileencoding' Encoding of a file. When it's different from "utf-8" conversion is done when reading or writing the file. @@ -1096,9 +937,6 @@ options.txt for detailed information. languages where a sequence of characters can be broken anywhere. -'guifontset' The list of font names used for a multi-byte encoding. When - this option is not empty, it replaces 'guifont'. - 'keymap' Specify the name of a keyboard mapping. ============================================================================== diff --git a/runtime/doc/message.txt b/runtime/doc/message.txt index 43b1eb5e0c..745160da8a 100644 --- a/runtime/doc/message.txt +++ b/runtime/doc/message.txt @@ -359,7 +359,7 @@ the other way around. It should be used like this: {foo,bar}. This matches ml_get: invalid lnum: {number} This is an internal Vim error. Please try to find out how it can be -reproduced, and submit a bug report |bugreport.vim|. +reproduced, and submit a |bug-report|. *E173* > {number} more files to edit diff --git a/runtime/doc/mlang.txt b/runtime/doc/mlang.txt index 2a10a7051d..5217b2c160 100644 --- a/runtime/doc/mlang.txt +++ b/runtime/doc/mlang.txt @@ -124,8 +124,7 @@ maintainer of the translation and ask him to update it. You can find the name and e-mail address of the translator in "$VIMRUNTIME/lang/menu_<lang>.vim". -To set the font (or fontset) to use for the menus, use the |:highlight| -command. Example: > +To set the font to use for the menus, use the |:highlight| command. Example: > :highlight Menu font=k12,r12 diff --git a/runtime/doc/nvim_terminal_emulator.txt b/runtime/doc/nvim_terminal_emulator.txt index a96d118667..bec2b362ea 100644 --- a/runtime/doc/nvim_terminal_emulator.txt +++ b/runtime/doc/nvim_terminal_emulator.txt @@ -92,7 +92,7 @@ Mouse input has the following behavior: the terminal wont lose focus and the hovered window will be scrolled. ============================================================================== -Configuration *terminal-configuration* +Configuration *terminal-config* Options: 'modified', 'scrollback' Events: |TermOpen|, |TermEnter|, |TermLeave|, |TermClose| diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index e1beea0fed..b83d2c4484 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -118,8 +118,7 @@ A few special texts: Option was set with command line argument |-c|, +, |-S| or |-q|. Last set from environment variable ~ - Option was set from an environment variable, $VIMINIT, - $GVIMINIT or $EXINIT. + Option was set from $VIMINIT. Last set from error handler ~ Option was cleared when evaluating it resulted in an error. @@ -1387,6 +1386,21 @@ A jump table for the options with a short description can be found at |Q_op|. This option cannot be set from a |modeline| or in the |sandbox|, for security reasons. + *'completeslash'* *'csl'* +'completeslash' 'csl' string (default: "") + local to buffer + {not in Vi} {only for MS-Windows} + When this option is set it overrules 'shellslash' for completion: + - When this option is set to "slash", a forward slash is used for path + completion in insert mode. This is useful when editing HTML tag, or + Makefile with 'noshellslash' on Windows. + - When this option is set to "backslash", backslash is used. This is + useful when editing a batch file with 'shellslash' set on Windows. + - When this option is empty, same character is used as for + 'shellslash'. + For Insert mode completion the buffer-local value is used. For + command line completion the global value is used. + *'completeopt'* *'cot'* 'completeopt' 'cot' string (default: "menu,preview") global @@ -2739,21 +2753,26 @@ A jump table for the options with a short description can be found at |Q_op|. hor{N} horizontal bar, {N} percent of the character height ver{N} vertical bar, {N} percent of the character width block block cursor, fills the whole character - [only one of the above three should be present] + - Only one of the above three should be present. + - Default is "block" for each mode. blinkwait{N} *cursor-blinking* blinkon{N} blinkoff{N} blink times for cursor: blinkwait is the delay before the cursor starts blinking, blinkon is the time that the cursor is shown and blinkoff is the time that the - cursor is not shown. The times are in msec. When one - of the numbers is zero, there is no blinking. E.g.: > + cursor is not shown. Times are in msec. When one of + the numbers is zero, there is no blinking. E.g.: > :set guicursor=n:blinkon0 -< {group-name} - Highlight group name that sets the color and font for - the cursor. |inverse|/reverse and no group-name are - interpreted as "the host terminal default cursor - colors" which usually invert bg and fg colors. +< - Default is "blinkon0" for each mode. + {group-name} + Highlight group that decides the color and font of the + cursor. + In the |TUI|: + - |inverse|/reverse and no group-name are interpreted + as "host-terminal default cursor colors" which + typically means "inverted bg and fg colors". + - |ctermfg| and |guifg| are ignored. {group-name}/{group-name} Two highlight group names, the first is used when no language mappings are used, the other when they @@ -2796,9 +2815,6 @@ A jump table for the options with a short description can be found at |Q_op|. font names a list can be specified, font names separated with commas. The first valid font is used. - On systems where 'guifontset' is supported (X11) and 'guifontset' is - not empty, then 'guifont' is not used. - Spaces after a comma are ignored. To include a comma in a font name precede it with a backslash. Setting an option requires an extra backslash before a space and a backslash. See also @@ -2850,45 +2866,16 @@ A jump table for the options with a short description can be found at |Q_op|. :set guifont=courier_new:h12:w5:b:cRUSSIAN :set guifont=Andale_Mono:h7.5:w4.5 < - - *'guifontset'* *'gfs'* - *E250* *E252* *E234* *E597* *E598* -'guifontset' 'gfs' string (default "") - global - When not empty, specifies two (or more) fonts to be used. The first - one for normal English, the second one for your special language. See - |xfontset|. - Setting this option also means that all font names will be handled as - a fontset name. Also the ones used for the "font" argument of the - |:highlight| command. - The fonts must match with the current locale. If fonts for the - character sets that the current locale uses are not included, setting - 'guifontset' will fail. - Note the difference between 'guifont' and 'guifontset': In 'guifont' - the comma-separated names are alternative names, one of which will be - used. In 'guifontset' the whole string is one fontset name, - including the commas. It is not possible to specify alternative - fontset names. - This example works on many X11 systems: > - :set guifontset=-*-*-medium-r-normal--16-*-*-*-c-*-*-* -< *'guifontwide'* *'gfw'* *E231* *E533* *E534* 'guifontwide' 'gfw' string (default "") global - When not empty, specifies a comma-separated list of fonts to be used - for double-width characters. The first font that can be loaded is - used. + Comma-separated list of fonts to be used for double-width characters. + The first font that can be loaded is used. Note: The size of these fonts must be exactly twice as wide as the one specified with 'guifont' and the same height. - 'guifontwide' is only used when 'guifontset' is empty or invalid. - When 'guifont' is set and a valid font is found in it and - 'guifontwide' is empty Vim will attempt to find a matching - double-width font and set 'guifontwide' to it. - - Windows +multibyte only: *guifontwide_win_mbyte* - - If set and valid, 'guifontwide' is used for IME instead of 'guifont'. + When 'guifont' has a valid font and 'guifontwide' is empty Vim will + attempt to set 'guifontwide' to a matching double-width font. *'guioptions'* *'go'* 'guioptions' 'go' string (default "egmrLT" (MS-Windows)) @@ -2931,6 +2918,8 @@ A jump table for the options with a short description can be found at |Q_op|. *'go-c'* 'c' Use console dialogs instead of popup dialogs for simple choices. + *'go-d'* + 'd' Use dark theme variant if available. *'go-e'* 'e' Add tab pages when indicated with 'showtabline'. 'guitablabel' can be used to change the text in the labels. @@ -4055,7 +4044,6 @@ A jump table for the options with a short description can be found at |Q_op|. *'mousefocus'* *'mousef'* *'nomousefocus'* *'nomousef'* 'mousefocus' 'mousef' boolean (default off) global - {only works in the GUI} The window that the mouse pointer is on is automatically activated. When changing the window layout or window focus in another way, the mouse pointer is moved to the window with keyboard focus. Off is the @@ -5028,24 +5016,18 @@ A jump table for the options with a short description can be found at |Q_op|. will become the current directory (useful with projects accessed over a network from different systems) - slash backslashes in file names replaced with forward - slashes tabpages all tab pages; without this only the current tab page is restored, so that you can make a session for each tab page separately terminal include terminal windows where the command can be restored - unix with Unix end-of-line format (single <NL>), even when - on Windows or DOS winpos position of the whole Vim window winsize window sizes + slash |deprecated| Always enabled. Uses "/" in filenames. + unix |deprecated| Always enabled. Uses "\n" line endings. - Don't include both "curdir" and "sesdir". - When neither "curdir" nor "sesdir" is included, file names are stored - with absolute paths. - "slash" and "unix" are useful on Windows when sharing session files - with Unix. The Unix version of Vim cannot source dos format scripts, - but the Windows version of Vim can source unix format scripts. + Don't include both "curdir" and "sesdir". When neither is included + filenames are stored as absolute paths. *'shada'* *'sd'* *E526* *E527* *E528* 'shada' 'sd' string (Vim default for @@ -5324,7 +5306,8 @@ A jump table for the options with a short description can be found at |Q_op|. 'shellslash' only works when a backslash can be used as a path separator. To test if this is so use: > if exists('+shellslash') -< +< Also see 'completeslash'. + *'shelltemp'* *'stmp'* *'noshelltemp'* *'nostmp'* 'shelltemp' 'stmp' boolean (Vim default on, Vi default off) global @@ -5553,6 +5536,8 @@ A jump table for the options with a short description can be found at |Q_op|. "yes" always "yes:[1-9]" always, with fixed space for signs up to the given number (maximum 9), e.g. "yes:3" + "number" display signs in the 'number' column. If the number + column is not present, then behaves like 'auto'. *'smartcase'* *'scs'* *'nosmartcase'* *'noscs'* @@ -5705,6 +5690,14 @@ A jump table for the options with a short description can be found at |Q_op|. up to the first character that is not an ASCII letter or number and not a dash. Also see |set-spc-auto|. + *'spelloptions'* *'spo'* +'spelloptions' 'spo' string (default "") + local to buffer + A comma separated list of options for spell checking: + camel When a word is CamelCased, assume "Cased" is a + separate word: every upper-case character in a word + that comes after a lower case character indicates the + start of a new word. *'spellsuggest'* *'sps'* 'spellsuggest' 'sps' string (default "best") @@ -5804,7 +5797,7 @@ A jump table for the options with a short description can be found at |Q_op|. normal text. Each status line item is of the form: %-0{minwid}.{maxwid}{item} All fields except the {item} are optional. A single percent sign can - be given as "%%". Up to 80 items can be specified. *E541* + be given as "%%". When the option starts with "%!" then it is used as an expression, evaluated and the result is used as the option value. Example: > @@ -6530,7 +6523,9 @@ A jump table for the options with a short description can be found at |Q_op|. >= 12 Every executed function. >= 13 When an exception is thrown, caught, finished, or discarded. >= 14 Anything pending in a ":finally" clause. - >= 15 Every executed Ex command (truncated at 200 characters). + >= 15 Every executed Ex command from a script (truncated at 200 + characters). + >= 16 Every executed Ex command This option can also be set with the "-V" argument. See |-V|. This option is also set by the |:verbose| command. @@ -6569,14 +6564,8 @@ A jump table for the options with a short description can be found at |Q_op|. options options and mappings local to a window or buffer (not global values for local options) localoptions same as "options" - slash backslashes in file names replaced with forward - slashes - unix with Unix end-of-line format (single <NL>), even when - on Windows or DOS - - "slash" and "unix" are useful on Windows when sharing view files - with Unix. The Unix version of Vim cannot source dos format scripts, - but the Windows version of Vim can source unix format scripts. + slash |deprecated| Always enabled. Uses "/" in filenames. + unix |deprecated| Always enabled. Uses "\n" line endings. *'virtualedit'* *'ve'* 'virtualedit' 've' string (default "") diff --git a/runtime/doc/pattern.txt b/runtime/doc/pattern.txt index adfab07758..7129c6cd58 100644 --- a/runtime/doc/pattern.txt +++ b/runtime/doc/pattern.txt @@ -1111,6 +1111,9 @@ x A single character, with no special meaning, matches itself *[:tab:]* [:tab:] the <Tab> character *[:escape:]* [:escape:] the <Esc> character *[:backspace:]* [:backspace:] the <BS> character +*[:ident:]* [:ident:] identifier character (same as "\i") +*[:keyword:]* [:keyword:] keyword character (same as "\k") +*[:fname:]* [:fname:] file name character (same as "\f") The brackets in character class expressions are additional to the brackets delimiting a collection. For example, the following is a plausible pattern for a Unix filename: "[-./[:alnum:]_~]\+" That is, diff --git a/runtime/doc/provider.txt b/runtime/doc/provider.txt index 0a6cdc60e8..f944689d0b 100644 --- a/runtime/doc/provider.txt +++ b/runtime/doc/provider.txt @@ -129,9 +129,13 @@ To use the RVM "system" Ruby installation: > ============================================================================== Perl integration *provider-perl* -Nvim supports Perl |remote-plugin|s. +Nvim supports Perl |remote-plugin|s on Unix platforms. Support for polling STDIN +on MS-Windows is currently lacking from all known event loop implementations. +The Vim legacy |perl-vim| interface is also supported (which is itself +implemented as a Nvim remote-plugin). https://github.com/jacquesg/p5-Neovim-Ext +Note: Only perl versions from 5.22 onward are supported. PERL QUICKSTART~ @@ -212,12 +216,12 @@ For example this configuration integrates the tmux clipboard: > let g:clipboard = { \ 'name': 'myClipboard', \ 'copy': { - \ '+': 'tmux load-buffer -', - \ '*': 'tmux load-buffer -', + \ '+': ['tmux', 'load-buffer', '-'], + \ '*': ['tmux', 'load-buffer', '-'], \ }, \ 'paste': { - \ '+': 'tmux save-buffer -', - \ '*': 'tmux save-buffer -', + \ '+': ['tmux', 'save-buffer', '-'], + \ '*': ['tmux', 'save-buffer', '-'], \ }, \ 'cache_enabled': 1, \ } diff --git a/runtime/doc/quickfix.txt b/runtime/doc/quickfix.txt index 61e090cc78..9da11a553d 100644 --- a/runtime/doc/quickfix.txt +++ b/runtime/doc/quickfix.txt @@ -43,6 +43,7 @@ A location list is a window-local quickfix list. You get one after commands like `:lvimgrep`, `:lgrep`, `:lhelpgrep`, `:lmake`, etc., which create a location list instead of a quickfix list as the corresponding `:vimgrep`, `:grep`, `:helpgrep`, `:make` do. + *location-list-file-window* A location list is associated with a window and each window can have a separate location list. A location list can be associated with only one window. The location list is independent of the quickfix list. @@ -501,6 +502,29 @@ EXECUTE A COMMAND IN ALL THE BUFFERS IN QUICKFIX OR LOCATION LIST: < Otherwise it works the same as `:ldo`. {not in Vi} +FILTERING A QUICKFIX OR LOCATION LIST: + *cfilter-plugin* *:Cfilter* *:Lfilter* +If you have too many entries in a quickfix list, you can use the cfilter +plugin to reduce the number of entries. Load the plugin with: > + + packadd cfilter + +Then you can use the following commands to filter a quickfix/location list: > + + :Cfilter[!] /{pat}/ + :Lfilter[!] /{pat}/ + +The |:Cfilter| command creates a new quickfix list from the entries matching +{pat} in the current quickfix list. {pat} is a Vim |regular-expression| +pattern. Both the file name and the text of the entries are matched against +{pat}. If the optional ! is supplied, then the entries not matching {pat} are +used. The pattern can be optionally enclosed using one of the following +characters: ', ", /. If the pattern is empty, then the last used search +pattern is used. + +The |:Lfilter| command does the same as |:Cfilter| but operates on the current +location list. + ============================================================================= 2. The error window *quickfix-window* @@ -695,6 +719,9 @@ using these functions are below: " get the location list window id of the third window :echo getloclist(3, {'winid' : 0}).winid + + " get the file window id of a location list window (winnr: 4) + :echo getloclist(4, {'filewinid' : 0}).filewinid < *setqflist-examples* The |setqflist()| and |setloclist()| functions can be used to set the various @@ -709,6 +736,9 @@ using these functions are below: " set the title of the current quickfix list :call setqflist([], 'a', {'title' : 'Mytitle'}) + " change the current entry in the list specified by an identifier + :call setqflist([], 'a', {'id' : qfid, 'idx' : 10}) + " set the context of a quickfix list specified by an identifier :call setqflist([], 'a', {'id' : qfid, 'context' : {'val' : 100}}) @@ -1563,22 +1593,6 @@ The backslashes before the pipe character are required to avoid it to be recognized as a command separator. The backslash before each space is required for the set command. - *cfilter-plugin* *:Cfilter* *:Lfilter* -If you have too many matching messages, you can use the cfilter plugin to -reduce the number of entries. Load the plugin with: > - packadd cfilter - -Then you can use these command: > - :Cfilter[!] /{pat}/ - :Lfilter[!] /{pat}/ - -:Cfilter creates a new quickfix list from entries matching {pat} in the -current quickfix list. Both the file name and the text of the entries are -matched against {pat}. If ! is supplied, then entries not matching {pat} are -used. - -:Lfilter does the same as :Cfilter but operates on the current location list. - ============================================================================= 8. The directory stack *quickfix-directory-stack* diff --git a/runtime/doc/quickref.txt b/runtime/doc/quickref.txt index 224f14a18b..4a47fd4b57 100644 --- a/runtime/doc/quickref.txt +++ b/runtime/doc/quickref.txt @@ -711,7 +711,6 @@ Short explanation of each option: *option-list* 'grepprg' 'gp' program to use for ":grep" 'guicursor' 'gcr' GUI: settings for cursor shape and blinking 'guifont' 'gfn' GUI: Name(s) of font(s) to be used -'guifontset' 'gfs' GUI: Names of multi-byte fonts to be used 'guifontwide' 'gfw' list of font names for double-wide characters 'guioptions' 'go' GUI: Which components and options are used 'guitablabel' 'gtl' GUI: custom label for a tab page @@ -1106,7 +1105,6 @@ Context-sensitive completion on the command-line: ------------------------------------------------------------------------------ *Q_st* Starting Vim -|-vim| vim [options] start editing with an empty buffer |-file| vim [options] {file} .. start editing one or more files |--| vim [options] - read file from stdin |-tag| vim [options] -t {tag} edit the file associated with {tag} diff --git a/runtime/doc/spell.txt b/runtime/doc/spell.txt index b88e26cdff..0eef976819 100644 --- a/runtime/doc/spell.txt +++ b/runtime/doc/spell.txt @@ -187,6 +187,9 @@ When there is a line break right after a sentence the highlighting of the next line may be postponed. Use |CTRL-L| when needed. Also see |set-spc-auto| for how it can be set automatically when 'spelllang' is set. +The 'spelloptions' option has a few more flags that influence the way spell +checking works. + Vim counts the number of times a good word is encountered. This is used to sort the suggestions: words that have been seen before get a small bonus, words that have been seen often get a bigger bonus. The COMMON item in the @@ -617,11 +620,12 @@ ask you where to write the file (there must be a writable directory in 'runtimepath' for this). The plugin has a default place where to look for spell files, on the Vim ftp -server. If you want to use another location or another protocol, set the -g:spellfile_URL variable to the directory that holds the spell files. The -|netrw| plugin is used for getting the file, look there for the specific -syntax of the URL. Example: > - let g:spellfile_URL = 'http://ftp.vim.org/vim/runtime/spell' +server. The protocol used is SSL (https://) for security. If you want to use +another location or another protocol, set the g:spellfile_URL variable to the +directory that holds the spell files. You can use http:// or ftp://, but you +are taking a security risk then. The |netrw| plugin is used for getting the +file, look there for the specific syntax of the URL. Example: > + let g:spellfile_URL = 'https://ftp.nluug.nl/vim/runtime/spell' You may need to escape special characters. The plugin will only ask about downloading a language once. If you want to diff --git a/runtime/doc/starting.txt b/runtime/doc/starting.txt index 0ded6a9060..f58b0d5030 100644 --- a/runtime/doc/starting.txt +++ b/runtime/doc/starting.txt @@ -9,20 +9,20 @@ Starting Vim *starting* Type |gO| to see the table of contents. ============================================================================== -1. Vim arguments *vim-arguments* +Nvim arguments *vim-arguments* -Most often, Vim is started to edit a single file with the command +Most often, Nvim is started to edit a single file with the command: > - nvim filename *-vim* + nvim filename -More generally, Vim is started with: +More generally, Nvim is started with: > nvim [option | filename] .. Option arguments and file name arguments can be mixed, and any number of them can be given. However, watch out for options that take an argument. -The following items may be used to choose how to start editing: +The following items decide how to start editing: *-file* *---* filename One or more file names. The first one will be the current @@ -185,18 +185,6 @@ argument. the 'modifiable' and 'write' options can be set to enable changes and writing. - *-Z* *restricted-mode* *E145* *E981* --Z Restricted mode. All commands that make use of an external - shell are disabled. This includes suspending with CTRL-Z, - ":sh", filtering, the system() function, backtick expansion - and libcall(). - Also disallowed are delete(), rename(), mkdir(), jobstart(), - etc. - Interfaces, such as Python, Ruby and Lua, are also disabled, - since they could be used to execute shell commands. - Note that the user may still find a loophole to execute a - shell command, it has only been made difficult. - -e *-e* *-E* -E Start Nvim in Ex mode |gQ|. @@ -231,9 +219,8 @@ argument. -b Binary mode. File I/O will only recognize <NL> to separate lines. The 'expandtab' option will be reset. The 'textwidth' option is set to 0. 'modeline' is reset. The 'binary' option - is set. This is done after reading the init.vim/exrc files - but before reading any file in the arglist. See also - |edit-binary|. + is set. This is done after reading the |vimrc| but before + reading any file in the arglist. See also |edit-binary|. *-l* -l Lisp mode. Sets the 'lisp' and 'showmatch' options on. @@ -398,7 +385,7 @@ argument. primary listen address |v:servername| to {addr}. |serverstart()| ============================================================================== -2. Initialization *initialization* *startup* +Initialization *initialization* *startup* At startup, Vim checks environment variables and files and sets values accordingly. Vim proceeds in this order: @@ -414,45 +401,47 @@ accordingly. Vim proceeds in this order: The |-V| argument can be used to display or log what happens next, useful for debugging the initializations. -3. Execute Ex commands, from environment variables and/or files - An environment variable (e.g. $VIMINIT) is read as one Ex command - line, where multiple commands must be separated with '|' or <NL>. +3. Wait for UI to connect. + Nvim started with |--embed| waits for the UI to connect before + proceeding to load user configuration. + +4. Load user config (execute Ex commands from files, environment, โฆ). + $VIMINIT environment variable is read as one Ex command line (separate + multiple commands with '|' or <NL>). *config* *init.vim* *vimrc* *exrc* - A file that contains initialization commands is generically called - a "vimrc" or config file. Each line in a vimrc file is executed as an - Ex command line. See also |vimrc-intro| and |base-directories|. + A file containing init commands is generically called a "vimrc" or + "config". Each line in such a file is executed as an Ex command. + |vimrc-intro| |base-directories| - The Nvim config file is named "init.vim", located at: + The Nvim config file is "init.vim", located at: Unix ~/.config/nvim/init.vim Windows ~/AppData/Local/nvim/init.vim - Or if |$XDG_CONFIG_HOME| is defined: + or if |$XDG_CONFIG_HOME| is defined: $XDG_CONFIG_HOME/nvim/init.vim - If Nvim was started with "-u filename", the file "filename" is used. - All following initializations until 4. are skipped. $MYVIMRC is not - set. + If Nvim was started with "-u {file}" then {file} is used as the config + and all initializations until 5. are skipped. $MYVIMRC is not set. "nvim -u NORC" can be used to skip these initializations without reading a file. "nvim -u NONE" also skips plugins and syntax highlighting. |-u| - If Nvim was started with |-es|, all following initializations until 4. - are skipped. + If Nvim was started with |-es| all initializations until 5. are + skipped. *system-vimrc* *sysinit.vim* a. The system vimrc file is read for initializations. If nvim/sysinit.vim file exists in one of $XDG_CONFIG_DIRS, it will be - used. Otherwise, the system vimrc file is used. The path of this file - is shown with the ":version" command. Mostly it's "$VIM/sysinit.vim". + used. Otherwise the system vimrc file is used. The path of this file + is given by the |:version| command. Usually it's "$VIM/sysinit.vim". *VIMINIT* *EXINIT* *$MYVIMRC* - b. Four places are searched for initializations. The first that exists - is used, the others are ignored. The $MYVIMRC environment variable is - set to the file that was first found, unless $MYVIMRC was already set - and when using VIMINIT. - - Environment variable $VIMINIT, used as an Ex command line. - - User |config| file: $XDG_CONFIG_HOME/nvim/init.vim. - - Other config file: {xdg_config_dir}/nvim/init.vim where - {xdg_config_dir} is one of the directories in $XDG_CONFIG_DIRS. - - Environment variable $EXINIT, used as an Ex command line. + b. Locations searched for initializations, in order of preference: + - $VIMINIT environment variable (Ex command line). + - User |config|: $XDG_CONFIG_HOME/nvim/init.vim. + - Other config: {dir}/nvim/init.vim where {dir} is any directory + in $XDG_CONFIG_DIRS. + - $EXINIT environment variable (Ex command line). + |$MYVIMRC| is set to the first valid location unless it was already + set or when using $VIMINIT. c. If the 'exrc' option is on (which is NOT the default), the current directory is searched for two files. The first that exists is used, @@ -460,7 +449,7 @@ accordingly. Vim proceeds in this order: - The file ".nvimrc" - The file ".exrc" -4. Enable filetype and indent plugins. +5. Enable filetype and indent plugins. This does the same as the commands: > :runtime! filetype.vim :runtime! ftplugin.vim @@ -468,13 +457,13 @@ accordingly. Vim proceeds in this order: < Skipped if ":filetype โฆ off" was called or if the "-u NONE" command line argument was given. -5. Enable syntax highlighting. +6. Enable syntax highlighting. This does the same as the command: > :runtime! syntax/syntax.vim < Skipped if ":syntax off" was called or if the "-u NONE" command line argument was given. -6. Load the plugin scripts. *load-plugins* +7. Load the plugin scripts. *load-plugins* This does the same as the command: > :runtime! plugin/**/*.vim < The result is that all directories in the 'runtimepath' option will be @@ -503,26 +492,26 @@ accordingly. Vim proceeds in this order: if packages have been found, but that should not add a directory ending in "after". -7. Set 'shellpipe' and 'shellredir' +8. Set 'shellpipe' and 'shellredir' The 'shellpipe' and 'shellredir' options are set according to the value of the 'shell' option, unless they have been set before. This means that Vim will figure out the values of 'shellpipe' and 'shellredir' for you, unless you have set them yourself. -8. Set 'updatecount' to zero, if "-n" command argument used +9. Set 'updatecount' to zero, if "-n" command argument used -9. Set binary options +10. Set binary options If the "-b" flag was given to Vim, the options for binary editing will be set now. See |-b|. -10. Read the ShaDa file +11. Read the ShaDa file See |shada-file|. -11. Read the quickfix file +12. Read the quickfix file If the "-q" flag was given to Vim, the quickfix file is read. If this fails, Vim exits. -12. Open all windows +13. Open all windows When the |-o| flag was given, windows will be opened (but not displayed yet). When the |-p| flag was given, tab pages will be created (but not @@ -531,7 +520,7 @@ accordingly. Vim proceeds in this order: If the "-q" flag was given to Vim, the first error is jumped to. Buffers for all windows will be loaded. -13. Execute startup commands +14. Execute startup commands If a "-t" flag was given to Vim, the tag is jumped to. The commands given with the |-c| and |+cmd| arguments are executed. If the 'insertmode' option is set, Insert mode is entered. @@ -540,29 +529,6 @@ accordingly. Vim proceeds in this order: The |VimEnter| autocommands are executed. -Some hints on using initializations ~ - -Standard setup: -Create a vimrc file to set the default settings and mappings for all your edit -sessions. Put it in a place so that it will be found by 3b: - ~/.config/nvim/init.vim (Unix) - ~/AppData/Local/nvim/init.vim (Win32) - -Local setup: -Put all commands that you need for editing a specific directory only into a -vimrc file and place it in that directory under the name ".nvimrc" ("_nvimrc" -for Windows). NOTE: To make Vim look for these special files you -have to turn on the option 'exrc'. See |trojan-horse| too. - -System setup: -This only applies if you are managing a Unix system with several users and -want to set the defaults for all users. Create a vimrc file with commands -for default settings and mappings and put it in the place that is given with -the ":version" command. NOTE: System vimrc file needs specific compilation -options (one needs to define SYS_VIMRC_FILE macros). If :version command does -not show anything like this, consider contacting the nvim package maintainer. - - Saving the current state of Vim to a file ~ Whenever you have changed values of options or when you have created a @@ -570,20 +536,6 @@ mapping, then you may want to save them in a vimrc file for later use. See |save-settings| about saving the current state of settings to a file. -Avoiding setup problems for Vi users ~ - -Vi uses the variable EXINIT and the file "~/.exrc". So if you do not want to -interfere with Vi, then use the variable VIMINIT and the file init.vim -instead. - - -MS-DOS line separators: ~ - -On Windows systems Vim assumes that all the vimrc files have <CR> <NL> pairs -as line separators. This will give problems if you have a file with only -<NL>s and have a line like ":map xx yy^M". The trailing ^M will be ignored. - - Avoiding trojan horses ~ *trojan-horse* While reading the "vimrc" or the "exrc" file in the current directory, some @@ -606,7 +558,7 @@ it possible for another user to create a nasty vimrc and make you the owner. Be careful! When using tag search commands, executing the search command (the last part of the line in the tags file) is always done in secure mode. This works -just like executing a command from a vimrc/exrc in the current directory. +just like executing a command from a vimrc in the current directory. If Vim startup is slow ~ @@ -620,27 +572,29 @@ moment (use the Vim argument "-i NONE", |-i|). Try reducing the number of lines stored in a register with ":set shada='20,<50,s10". |shada-file|. -Intro message ~ - *:intro* -When Vim starts without a file name, an introductory message is displayed (for -those who don't know what Vim is). It is removed as soon as the display is -redrawn in any way. To see the message again, use the ":intro" command (if -there is not enough room, you will see only part of it). - To avoid the intro message on startup, add the 'I' flag to 'shortmess'. +Troubleshooting broken configurations ~ + *bisect* +The extreme flexibility of editors like Vim and Emacs means that any plugin or +setting can affect the entire editor in ways that are not initially obvious. - *info-message* -The |--help| and |--version| arguments cause Nvim to print a message and then -exit. Normally the message is sent to stdout, thus can be redirected to a -file with: > +To find the cause of a problem in your config, you must "bisect" it: +1. Remove or disable half of your `init.vim`. +2. Restart Nvim. +3. If the problem still occurs, goto 1. +4. If the problem is gone, restore half of the removed lines. +5. Continue narrowing your config in this way, until you find the setting or + plugin causing the issue. - nvim --help >file -From inside Nvim: > - - :read !nvim --help +Intro message ~ + *:intro* +When Vim starts without a file name, an introductory message is displayed. It +is removed as soon as the display is redrawn. To see the message again, use +the ":intro" command. To avoid the intro message on startup, add the "I" flag +to 'shortmess'. ============================================================================== -3. $VIM and $VIMRUNTIME +$VIM and $VIMRUNTIME *$VIM* The environment variable "$VIM" is used to locate various user files for Nvim, such as the user startup script |init.vim|. This depends on the system, see @@ -683,9 +637,9 @@ greps in the help files) you might be able to use this: > VIMRUNTIME="$(nvim --clean --headless --cmd 'echo $VIMRUNTIME|q')" ============================================================================== -4. Suspending *suspend* +Suspending *suspend* - *iconize* *iconise* *CTRL-Z* *v_CTRL-Z* + *CTRL-Z* *v_CTRL-Z* CTRL-Z Suspend Nvim, like ":stop". Works in Normal and in Visual mode. In Insert and Command-line mode, the CTRL-Z is inserted as a normal @@ -706,7 +660,7 @@ CTRL-Z Suspend Nvim, like ":stop". In the GUI, suspending is implementation-defined. ============================================================================== -5. Exiting *exiting* +Exiting *exiting* There are several ways to exit Vim: - Close the last window with `:quit`. Only when there are no changes. @@ -719,7 +673,7 @@ When using `:cquit` or when there was an error message Vim exits with exit code 1. Errors can be avoided by using `:silent!` or with `:catch`. ============================================================================== -6. Saving settings *save-settings* +Saving settings *save-settings* Mostly you will edit your vimrc files manually. This gives you the greatest flexibility. There are a few commands to generate a vimrc file automatically. @@ -776,7 +730,7 @@ these steps: You need to escape special characters, esp. spaces. ============================================================================== -7. Views and Sessions *views-sessions* +Views and Sessions *views-sessions* This is introduced in sections |21.4| and |21.5| of the user manual. @@ -920,7 +874,7 @@ To automatically save and restore views for *.c files: > au BufWinEnter *.c silent! loadview ============================================================================== -8. The ShaDa file *shada* *shada-file* +Shada ("shared data") file *shada* *shada-file* If you exit Vim and later start it again, you would normally lose a lot of information. The ShaDa file can be used to remember that information, which @@ -1358,7 +1312,7 @@ file when reading and include: complete MessagePack object. ============================================================================== -9. Standard Paths *standard-path* +Standard Paths *standard-path* Nvim stores configuration and data in standard locations. Plugins are strongly encouraged to follow this pattern also. Use |stdpath()| to get the paths. @@ -1389,4 +1343,5 @@ debugging, plugins and RPC clients. > Usually the file is ~/.local/share/nvim/log unless that path is inaccessible or if $NVIM_LOG_FILE was set before |startup|. + vim:noet:tw=78:ts=8:ft=help:norl: diff --git a/runtime/doc/treesitter.txt b/runtime/doc/treesitter.txt new file mode 100644 index 0000000000..58cd535e98 --- /dev/null +++ b/runtime/doc/treesitter.txt @@ -0,0 +1,324 @@ +*treesitter.txt* Nvim + + + NVIM REFERENCE MANUAL + + +Tree-sitter integration *treesitter* + + Type |gO| to see the table of contents. + +------------------------------------------------------------------------------ +VIM.TREESITTER *lua-treesitter* + +Nvim integrates the tree-sitter library for incremental parsing of buffers. + +Currently Nvim does not provide the tree-sitter parsers, instead these must +be built separately, for instance using the tree-sitter utility. The only +exception is a C parser being included in official builds for testing +purposes. Parsers are searched for as `parser/{lang}.*` in any 'runtimepath' +directory. A parser can also be loaded manually using a full path: > + + vim.treesitter.require_language("python", "/path/to/python.so") + +<Create a parser for a buffer and a given language (if another plugin uses the +same buffer/language combination, it will be safely reused). Use > + + parser = vim.treesitter.get_parser(bufnr, lang) + +<`bufnr=0` can be used for current buffer. `lang` will default to 'filetype' (this +doesn't work yet for some filetypes like "cpp") Currently, the parser will be +retained for the lifetime of a buffer but this is subject to change. A plugin +should keep a reference to the parser object as long as it wants incremental +updates. + +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. + +For a parser to be available for a given language, there must be a file named +`{lang}.so` within the parser directory. + +Parser methods *lua-treesitter-parser* + +tsparser:parse() *tsparser:parse()* +Whenever you need to access the current syntax tree, parse the buffer: > + + tstree = parser:parse() + +<This will return an immutable tree that represents the current state of the +buffer. When the plugin wants to access the state after a (possible) edit +it should call `parse()` again. If the buffer wasn't edited, the same tree will +be returned again without extra work. If the buffer was parsed before, +incremental parsing will be done of the changed parts. + +NB: to use the parser directly inside a |nvim_buf_attach| Lua callback, you must +call `get_parser()` before you register your callback. But preferably parsing +shouldn't be done directly in the change callback anyway as they will be very +frequent. Rather a plugin that does any kind of analysis on a tree should use +a timer to throttle too frequent updates. + +tsparser:set_included_ranges({ranges}) *tsparser:set_included_ranges()* + Changes the ranges the parser should consider. This is used for + language injection. {ranges} should be of the form (all zero-based): > + { + {start_node, end_node}, + ... + } +< + NOTE: `start_node` and `end_node` are both inclusive. + +Tree methods *lua-treesitter-tree* + +tstree:root() *tstree:root()* + Return the root node of this tree. + +tstree:copy() *tstree:copy()* + Returns a copy of the `tstree`. + + +Node methods *lua-treesitter-node* + +tsnode:parent() *tsnode:parent()* + Get the node's immediate parent. + +tsnode:iter_children() *tsnode:iter_children()* + Iterates over all the direct children of {tsnode}, regardless of + wether 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: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: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: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: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: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: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:sexpr() *tsnode:sexpr()* + Get an S-expression representing the node as a string. + +tsnode:id() *tsnode:id()* + Get an unique identier for the node inside its own tree. + + No guarantees are made about this identifer's internal representation, + except for being a primitive lua type with value equality (so not a table). + Presently it is a (non-printable) string. + + NB: the id is not guaranteed to be unique for nodes from different trees. + +tsnode:descendant_for_range({start_row}, {start_col}, {end_row}, {end_col}) + *tsnode:descendant_for_range()* + Get the smallest node within this node that spans the given range of + (row, column) positions + +tsnode:named_descendant_for_range({start_row}, {start_col}, {end_row}, {end_col}) + *tsnode:named_descendant_for_range()* + Get the smallest named node within this node that spans the given + range of (row, column) positions + +Query methods *lua-treesitter-query* + +Tree-sitter queries are supported, with some limitations. Currently, the only +supported match predicate is `eq?` (both comparing a capture against a string +and two captures against each other). + +A `query` consists of one or more patterns. A `pattern` is defined over node +types in the syntax tree. A `match` corresponds to specific elements of the +syntax tree which match a pattern. Patterns may optionally define captures +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. + +vim.treesitter.parse_query({lang}, {query}) + *vim.treesitter.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). + + Returns a `Query` (see |lua-treesitter-query|) object which can be used to + search nodes in the syntax tree for the patterns defined in {query} + using `iter_*` methods below. Exposes `info` and `captures` with + additional information about the {query}. + - `captures` contains the list of unique capture names defined in + {query}. + -` info.captures` also points to `captures`. + - `info.patterns` contains information about predicates. + + +query:iter_captures({node}, {bufnr}, {start_row}, {end_row}) + *query:iter_captures()* + Iterate over all captures from all matches inside {node}. + {bufnr} 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. {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) + + The iterator returns two values, a numeric id identifying the capture + and the captured node. The following example shows how to get captures + by name: +> + for id, node in query:iter_captures(tree:root(), bufnr, first, last) do + local name = query.captures[id] -- name of the capture in the query + -- typically useful info about the node: + local type = node:type() -- type of the captured node + local row1, col1, row2, col2 = node:range() -- range of the capture + ... use the info here ... + end +< +query:iter_matches({node}, {bufnr}, {start_row}, {end_row}) + *query:iter_matches()* + 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, and a table mapping + capture indices to nodes. If the query has more than one pattern + the capture table might be sparse, and e.g. `pairs` should be used and not + `ipairs`. Here an example iterating over all captures in + every match: +> + for pattern, match in cquery:iter_matches(tree:root(), bufnr, first, last) do + for id,node in pairs(match) do + local name = query.captures[id] + -- `node` was captured by the `name` capture in the match + ... use the info here ... + end + end + +Treesitter Query Predicates *lua-treesitter-predicates* + +When writing queries for treesitter, one might use `predicates`, that is, +special scheme nodes that are evaluted to verify things on a captured node for +example, the |eq?| predicate : > + ((identifier) @foo (#eq? @foo "foo")) + +This will only match identifier corresponding to the `"foo"` text. +Here is a list of built-in predicates : + + `eq?` *ts-predicate-eq?* + This predicate will check text correspondance between nodes or + strings : > + ((identifier) @foo (#eq? @foo "foo")) + ((node1) @left (node2) @right (#eq? @left @right)) +< + `match?` *ts-predicate-match?* + `vim-match?` *ts-predicate-vim-match?* + This will match if the provived vim regex matches the text + corresponding to a node : > + ((idenfitier) @constant (#match? @constant "^[A-Z_]+$")) +< 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")) +< + *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({name}, {handler}) + +This adds a predicate with the name {name} to be used in queries. +{handler} should be a function whose signature will be : > + handler(match, pattern, bufnr, predicate) +< + *vim.treesitter.query.list_predicates()* +vim.treesitter.query.list_predicates() + +This lists the currently available predicates to use in queries. + +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 indented +for those who want to experiment with this feature and contribute to +its development. + +Highlights are defined in the same query format as in the tree-sitter highlight +crate, which some limitations and additions. Set a highlight query for a +buffer with this code: > + + local query = [[ + "for" @keyword + "if" @keyword + "return" @keyword + + (string_literal) @string + (number_literal) @number + (comment) @comment + + (preproc_function_def name: (identifier) @function) + + ; ... more definitions + ]] + + highlighter = vim.treesitter.TSHighlighter.new(query, bufnr, lang) + -- alternatively, to use the current buffer and its filetype: + -- highlighter = vim.treesitter.TSHighlighter.new(query) + + -- Don't recreate the highlighter for the same buffer, instead + -- modify the query like this: + local query2 = [[ ... ]] + highlighter:set_query(query2) + +As mentioned above the supported predicate is currently only `eq?`. `match?` +predicates behave like matching always fails. As an addition a capture which +begin with an upper-case letter like `@WarningMsg` will map directly to this +highlight group, if defined. Also if the predicate begins with upper-case and +contains a dot only the part before the first will be interpreted as the +highlight group. As an example, this warns of a binary expression with two +identical identifiers, highlighting both as |hl-WarningMsg|: > + + ((binary_expression left: (identifier) @WarningMsg.left right: (identifier) @WarningMsg.right) + (eq? @WarningMsg.left @WarningMsg.right)) + + vim:tw=78:ts=8:ft=help:norl: diff --git a/runtime/doc/ui.txt b/runtime/doc/ui.txt index 2817c1015b..e5c6b9b1b7 100644 --- a/runtime/doc/ui.txt +++ b/runtime/doc/ui.txt @@ -183,9 +183,9 @@ the editor. 'ambiwidth' 'emoji' 'guifont' - 'guifontset' 'guifontwide' 'linespace' + 'mousefocus' 'pumblend' 'showtabline' 'termguicolors' @@ -719,7 +719,7 @@ events, which the UI must handle. kind Name indicating the message kind: - "" (empty) Unknown, report a |feature-request| + "" (empty) Unknown (consider a feature-request: |bugs|) "confirm" |confirm()| or |:confirm| dialog "confirm_sub" |:substitute| confirm dialog |:s_c| "emsg" Error (|errors|, internal error, |:throw|, โฆ) diff --git a/runtime/doc/usr_02.txt b/runtime/doc/usr_02.txt index 9ebbd11ac7..c8fd7c3e35 100644 --- a/runtime/doc/usr_02.txt +++ b/runtime/doc/usr_02.txt @@ -652,7 +652,7 @@ Summary: *help-summary* > 22) Autocommand events can be found by their name: > :help BufWinLeave < To see all possible events: > - :help autocommand-events + :help events 23) Command-line switches always start with "-". So for the help of the -f command switch of Vim use: > diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index 24b562543e..ac24f22bc6 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -13,7 +13,7 @@ the differences. Type |gO| to see the table of contents. ============================================================================== -1. Configuration *nvim-configuration* +1. Configuration *nvim-config* - Use `$XDG_CONFIG_HOME/nvim/init.vim` instead of `.vimrc` for configuration. - Use `$XDG_CONFIG_HOME/nvim` instead of `.vim` to store configuration files. @@ -50,7 +50,7 @@ the differences. - 'listchars' defaults to "tab:> ,trail:-,nbsp:+" - 'nrformats' defaults to "bin,hex" - 'ruler' is enabled -- 'sessionoptions' excludes "options" +- 'sessionoptions' includes "unix,slash", excludes "options" - 'shortmess' includes "F", excludes "S" - 'showcmd' is enabled - 'sidescroll' defaults to 1 @@ -60,13 +60,14 @@ the differences. - 'tags' defaults to "./tags;,tags" - 'ttimeoutlen' defaults to 50 - 'ttyfast' is always set +- 'viewoptions' includes "unix,slash" - 'undodir' defaults to ~/.local/share/nvim/undo (|xdg|), auto-created - 'viminfo' includes "!" - 'wildmenu' is enabled - 'wildoptions' defaults to "pum,tagfile" -- The |man.vim| plugin is enabled, to provide the |:Man| command. -- The |matchit| plugin is enabled. To disable it in your config: > +- |man.vim| plugin is enabled, so |:Man| is available by default. +- |matchit| plugin is enabled. To disable it in your config: > :let loaded_matchit = 1 ============================================================================== @@ -112,7 +113,6 @@ Working intuitively and consistently is a major goal of Nvim. Usability details have been improved where the benefit outweighs any backwards-compatibility cost. Some examples: -- |K| in help documents can be used like |CTRL-]|. - Directories for 'directory' and 'undodir' are auto-created. - Terminal features such as 'guicursor' are enabled where possible. @@ -146,19 +146,15 @@ Command-line highlighting: Commands: |:autocmd| accepts the `++once` flag |:checkhealth| - |:cquit| can use [count] to set the exit code |:drop| is always available |:Man| is available by default, with many improvements such as completion |:sign-define| accepts a `numhl` argument, to highlight the line number - |:tchdir| tab-local |current-directory| Events: - |DirChanged| |Signal| |TabNewEntered| |TermClose| |TermOpen| - |TextYankPost| |UIEnter| |UILeave| |VimResume| @@ -180,24 +176,22 @@ Highlight groups: |hl-NormalNC| highlights non-current windows |hl-MsgArea| highlights messages/cmdline area |hl-MsgSeparator| highlights separator for scrolled messages - |hl-QuickFixLine| |hl-Substitute| |hl-TermCursor| |hl-TermCursorNC| |hl-Whitespace| highlights 'listchars' whitespace -Input: +Input/Mappings: + |<Cmd>| pseudokey + ALT (|META|) chords always work (even in the |TUI|). Map |<M-| with any key: <M-1>, <M-BS>, <M-Del>, <M-Ins>, <M-/>, <M-\>, <M-Space>, <M-Enter>, etc. Case-sensitive: <M-a> and <M-A> are two different keycodes. - ALT in insert-mode behaves like <Esc> if not mapped. |i_ALT| - -Mappings: - |<Cmd>| pseudokey + ALT behaves like <Esc> if not mapped. |i_ALT| |v_ALT| |c_ALT| Normal commands: - "Outline": Type |gO| in |:Man| and |:help| pages to see a document outline. + |gO| shows a filetype-defined "outline" of the current buffer. Options: 'cpoptions' flags: |cpo-_| @@ -221,7 +215,6 @@ Signs: Signs are removed if the associated line is deleted. Variables: - |v:event| |v:exiting| |v:progpath| is always absolute ("full") |v:windowid| is always available (for use by external UIs) @@ -406,7 +399,6 @@ Some legacy Vim features are not implemented: - |if_lua|: Nvim Lua API is not compatible with Vim's "if_lua" - *if_mzscheme* -- *if_perl* - |if_py|: *python-bindeval* *python-Function* are not supported - *if_tcl* @@ -449,6 +441,9 @@ Eval: *js_decode()* *v:none* (used by Vim to represent JavaScript "undefined"); use |v:null| instead. +Events: + *SigUSR1* Use |Signal| to detect `SIGUSR1` signal instead. + Highlight groups: *hl-StatusLineTerm* *hl-StatusLineTermNC* are unnecessary because Nvim supports 'winhighlight' window-local highlights. @@ -471,6 +466,7 @@ Options: 'encoding' ("utf-8" is always used) 'esckeys' 'guioptions' "t" flag was removed + *'guifontset'* *'gfs'* (Use 'guifont' instead.) *'guipty'* (Nvim uses pipes and PTYs consistently on all platforms.) 'highlight' (Names of builtin |highlight-groups| cannot be changed.) *'imactivatefunc'* *'imaf'* @@ -506,7 +502,6 @@ Test functions: test_alloc_fail() test_autochdir() test_disable_char_avail() - test_garbagecollect_now() test_null_channel() test_null_dict() test_null_job() diff --git a/runtime/doc/visual.txt b/runtime/doc/visual.txt index 0052382044..fd3d93ed98 100644 --- a/runtime/doc/visual.txt +++ b/runtime/doc/visual.txt @@ -159,7 +159,10 @@ If you want to highlight exactly the same area as the last time, you can use *v_<Esc>* <Esc> In Visual mode: Stop Visual mode. - + *v_META* *v_ALT* + ALT (|META|) acts like <Esc> if the chord is not mapped. + For example <A-x> acts like <Esc>x if <A-x> does not have a + visual-mode mapping. *v_CTRL-C* CTRL-C In Visual mode: Stop Visual mode. When insert mode is pending (the mode message shows diff --git a/runtime/filetype.vim b/runtime/filetype.vim index 6807bef3eb..8eb26046da 100644 --- a/runtime/filetype.vim +++ b/runtime/filetype.vim @@ -84,6 +84,9 @@ au BufNewFile,BufRead *.gpr setf ada " AHDL au BufNewFile,BufRead *.tdf setf ahdl +" AIDL +au BufNewFile,BufRead *.aidl setf aidl + " AMPL au BufNewFile,BufRead *.run setf ampl @@ -177,7 +180,7 @@ au BufNewFile,BufRead *.at setf m4 au BufNewFile,BufRead *.ave setf ave " Awk -au BufNewFile,BufRead *.awk setf awk +au BufNewFile,BufRead *.awk,*.gawk setf awk " B au BufNewFile,BufRead *.mch,*.ref,*.imp setf b @@ -233,10 +236,10 @@ au BufNewFile,BufRead */etc/blkid.tab,*/etc/blkid.tab.old setf xml au BufNewFile,BufRead *bsd,*.bsdl setf bsdl " Bazel (http://bazel.io) -autocmd BufRead,BufNewFile *.bzl,WORKSPACE,BUILD.bazel setf bzl +autocmd BufRead,BufNewFile *.bzl,*.bazel,WORKSPACE setf bzl if has("fname_case") " There is another check for BUILD further below. - autocmd BufRead,BufNewFile BUILD setf bzl + autocmd BufRead,BufNewFile *.BUILD,BUILD setf bzl endif " C or lpc @@ -310,7 +313,7 @@ au BufNewFile,BufRead *.css setf css au BufNewFile,BufRead *.con setf cterm " Changelog -au BufNewFile,BufRead changelog.Debian,changelog.dch,NEWS.Debian,NEWS.dch +au BufNewFile,BufRead changelog.Debian,changelog.dch,NEWS.Debian,NEWS.dch,*/debian/changelog \ setf debchangelog au BufNewFile,BufRead [cC]hange[lL]og @@ -1086,6 +1089,9 @@ au BufNewFile,BufRead .netrc setf netrc " Ninja file au BufNewFile,BufRead *.ninja setf ninja +" NPM RC file +au BufNewFile,BufRead npmrc,.npmrc setf dosini + " Novell netware batch files au BufNewFile,BufRead *.ncf setf ncf @@ -1152,10 +1158,10 @@ au BufNewFile,BufRead *.papp,*.pxml,*.pxsl setf papp au BufNewFile,BufRead */etc/passwd,*/etc/passwd-,*/etc/passwd.edit,*/etc/shadow,*/etc/shadow-,*/etc/shadow.edit,*/var/backups/passwd.bak,*/var/backups/shadow.bak setf passwd " Pascal (also *.p) -au BufNewFile,BufRead *.pas setf pascal +au BufNewFile,BufRead *.pas,*.pp setf pascal -" Delphi project file -au BufNewFile,BufRead *.dpr setf pascal +" Delphi or Lazarus program file +au BufNewFile,BufRead *.dpr,*.lpr setf pascal " PDF au BufNewFile,BufRead *.pdf setf pdf @@ -1192,6 +1198,9 @@ au BufNewFile,BufRead *.pod6 setf pod6 " Also .ctp for Cake template file au BufNewFile,BufRead *.php,*.php\d,*.phtml,*.ctp setf php +" PHP config +au BufNewFile,BufRead php.ini,php.ini-* setf dosini + " Pike and Cmod au BufNewFile,BufRead *.pike,*.pmod setf pike au BufNewFile,BufRead *.cmod setf cmod @@ -1714,7 +1723,7 @@ au BufNewFile,BufRead *.latex,*.sty,*.dtx,*.ltx,*.bbl setf tex au BufNewFile,BufRead *.tex call dist#ft#FTtex() " ConTeXt -au BufNewFile,BufRead *.mkii,*.mkiv,*.mkvi setf context +au BufNewFile,BufRead *.mkii,*.mkiv,*.mkvi,*.mkxl,*.mklx setf context " Texinfo au BufNewFile,BufRead *.texinfo,*.texi,*.txi setf texinfo @@ -1723,7 +1732,7 @@ au BufNewFile,BufRead *.texinfo,*.texi,*.txi setf texinfo au BufNewFile,BufRead texmf.cnf setf texmf " Tidy config -au BufNewFile,BufRead .tidyrc,tidyrc setf tidy +au BufNewFile,BufRead .tidyrc,tidyrc,tidy.conf setf tidy " TF mud client au BufNewFile,BufRead *.tf,.tfrc,tfrc setf tf @@ -2032,7 +2041,7 @@ au BufNewFile,BufRead bzr_log.* setf bzr " Bazel build file if !has("fname_case") - au BufNewFile,BufRead BUILD setf bzl + au BufNewFile,BufRead *.BUILD,BUILD setf bzl endif " BIND zone diff --git a/runtime/ftplugin/man.vim b/runtime/ftplugin/man.vim index 0416e41368..74225a558c 100644 --- a/runtime/ftplugin/man.vim +++ b/runtime/ftplugin/man.vim @@ -16,6 +16,7 @@ setlocal noswapfile buftype=nofile bufhidden=hide setlocal nomodified readonly nomodifiable setlocal noexpandtab tabstop=8 softtabstop=8 shiftwidth=8 setlocal wrap breakindent linebreak +setlocal iskeyword+=- setlocal nonumber norelativenumber setlocal foldcolumn=0 colorcolumn=0 nolist nofoldenable diff --git a/runtime/ftplugin/markdown.vim b/runtime/ftplugin/markdown.vim index 277ba94e8b..fc1d9e068b 100644 --- a/runtime/ftplugin/markdown.vim +++ b/runtime/ftplugin/markdown.vim @@ -1,7 +1,7 @@ " Vim filetype plugin " Language: Markdown " Maintainer: Tim Pope <vimNOSPAM@tpope.org> -" Last Change: 2016 Aug 29 +" Last Change: 2019 Dec 05 if exists("b:did_ftplugin") finish @@ -9,7 +9,7 @@ endif runtime! ftplugin/html.vim ftplugin/html_*.vim ftplugin/html/*.vim -setlocal comments=fb:*,fb:-,fb:+,n:> commentstring=>\ %s +setlocal comments=fb:*,fb:-,fb:+,n:> commentstring=<!--%s--> setlocal formatoptions+=tcqln formatoptions-=r formatoptions-=o setlocal formatlistpat=^\\s*\\d\\+\\.\\s\\+\\\|^[-*+]\\s\\+\\\|^\\[^\\ze[^\\]]\\+\\]: @@ -19,32 +19,56 @@ else let b:undo_ftplugin = "setl cms< com< fo< flp<" endif -function! MarkdownFold() +function! s:NotCodeBlock(lnum) abort + return synIDattr(synID(v:lnum, 1, 1), 'name') !=# 'markdownCode' +endfunction + +function! MarkdownFold() abort let line = getline(v:lnum) - " Regular headers - let depth = match(line, '\(^#\+\)\@<=\( .*$\)\@=') - if depth > 0 - return ">" . depth + if line =~# '^#\+ ' && s:NotCodeBlock(v:lnum) + return ">" . match(line, ' ') endif - " Setext style headings let nextline = getline(v:lnum + 1) - if (line =~ '^.\+$') && (nextline =~ '^=\+$') + if (line =~ '^.\+$') && (nextline =~ '^=\+$') && s:NotCodeBlock(v:lnum + 1) return ">1" endif - if (line =~ '^.\+$') && (nextline =~ '^-\+$') + if (line =~ '^.\+$') && (nextline =~ '^-\+$') && s:NotCodeBlock(v:lnum + 1) return ">2" endif return "=" endfunction +function! s:HashIndent(lnum) abort + let hash_header = matchstr(getline(a:lnum), '^#\{1,6}') + if len(hash_header) + return hash_header + else + let nextline = getline(a:lnum + 1) + if nextline =~# '^=\+\s*$' + return '#' + elseif nextline =~# '^-\+\s*$' + return '##' + endif + endif +endfunction + +function! MarkdownFoldText() abort + let hash_indent = s:HashIndent(v:foldstart) + let title = substitute(getline(v:foldstart), '^#\+\s*', '', '') + let foldsize = (v:foldend - v:foldstart + 1) + let linecount = '['.foldsize.' lines]' + return hash_indent.' '.title.' '.linecount +endfunction + if has("folding") && exists("g:markdown_folding") setlocal foldexpr=MarkdownFold() setlocal foldmethod=expr - let b:undo_ftplugin .= " foldexpr< foldmethod<" + setlocal foldtext=MarkdownFoldText() + let b:undo_ftplugin .= " foldexpr< foldmethod< foldtext<" endif " vim:set sw=2: diff --git a/runtime/ftplugin/typescript.vim b/runtime/ftplugin/typescript.vim new file mode 100644 index 0000000000..f701ae96cd --- /dev/null +++ b/runtime/ftplugin/typescript.vim @@ -0,0 +1,39 @@ +" Vim filetype plugin file +" Language: TypeScript +" Maintainer: Doug Kearns <dougkearns@gmail.com> +" Last Change: 2019 Aug 30 + +if exists("b:did_ftplugin") + finish +endif +let b:did_ftplugin = 1 + +let s:cpo_save = &cpo +set cpo-=C + +" Set 'formatoptions' to break comment lines but not other lines, +" and insert the comment leader when hitting <CR> or using "o". +setlocal formatoptions-=t formatoptions+=croql + +" Set 'comments' to format dashed lists in comments. +setlocal comments=sO:*\ -,mO:*\ \ ,exO:*/,s1:/*,mb:*,ex:*/,:// + +setlocal commentstring=//%s + +setlocal suffixesadd+=.ts,.d.ts,.tsx,.js,.jsx,.cjs,.mjs + +" Change the :browse e filter to primarily show TypeScript-related files. +if (has("gui_win32") || has("gui_gtk")) && !exists("b:browsefilter") + let b:browsefilter="TypeScript Files (*.ts)\t*.ts\n" . + \ "TypeScript Declaration Files (*.d.ts)\t*.d.ts\n" . + \ "TSX Files (*.tsx)\t*.tsx\n" . + \ "JavaScript Files (*.js)\t*.js\n" . + \ "JavaScript Modules (*.es, *.cjs, *.mjs)\t*.es;*.cjs;*.mjs\n" . + \ "JSON Files (*.json)\t*.json\n" . + \ "All Files (*.*)\t*.*\n" +endif + +let b:undo_ftplugin = "setl fo< com< cms< sua< | unlet! b:browsefilter" + +let &cpo = s:cpo_save +unlet s:cpo_save diff --git a/runtime/ftplugin/typescriptreact.vim b/runtime/ftplugin/typescriptreact.vim new file mode 100644 index 0000000000..3bd6001a18 --- /dev/null +++ b/runtime/ftplugin/typescriptreact.vim @@ -0,0 +1,33 @@ +" Vim filetype plugin file +" Language: TypeScript React +" Maintainer: Doug Kearns <dougkearns@gmail.com> +" Last Change: 2020 Aug 09 + +let s:match_words = "" +let s:undo_ftplugin = "" + +runtime! ftplugin/typescript.vim + +let s:cpo_save = &cpo +set cpo-=C + +if exists("b:match_words") + let s:match_words = b:match_words +endif +if exists("b:undo_ftplugin") + let s:undo_ftplugin = b:undo_ftplugin +endif + +" Matchit configuration +if exists("loaded_matchit") + let b:match_ignorecase = 0 + let b:match_words = s:match_words . + \ '<:>,' . + \ '<\@<=\([^ \t>/]\+\)\%(\s\+[^>]*\%([^/]>\|$\)\|>\|$\):<\@<=/\1>,' . + \ '<\@<=\%([^ \t>/]\+\)\%(\s\+[^/>]*\|$\):/>' +endif + +let b:undo_ftplugin = "unlet! b:match_words b:match_ignorecase | " . s:undo_ftplugin + +let &cpo = s:cpo_save +unlet s:cpo_save diff --git a/runtime/indent/typescript.vim b/runtime/indent/typescript.vim index 69accaa054..b6b2cb5acf 100644 --- a/runtime/indent/typescript.vim +++ b/runtime/indent/typescript.vim @@ -1,7 +1,7 @@ " Vim indent file " Language: TypeScript " Maintainer: See https://github.com/HerringtonDarkholme/yats.vim -" Last Change: 2019 Jun 06 +" Last Change: 2019 Oct 18 " Acknowledgement: Based off of vim-ruby maintained by Nikolai Weibull http://vim-ruby.rubyforge.org " 0. Initialization {{{1 @@ -442,7 +442,7 @@ let &cpo = s:cpo_save unlet s:cpo_save function! Fixedgq(lnum, count) - let l:tw = &tw ? &tw : 80; + let l:tw = &tw ? &tw : 80 let l:count = a:count let l:first_char = indent(a:lnum) + 1 diff --git a/runtime/lua/vim/F.lua b/runtime/lua/vim/F.lua new file mode 100644 index 0000000000..5887e978b9 --- /dev/null +++ b/runtime/lua/vim/F.lua @@ -0,0 +1,24 @@ +local F = {} + +--- Returns {a} if it is not nil, otherwise returns {b}. +--- +--@param a +--@param b +function F.if_nil(a, b) + if a == nil then return b end + return a +end + +-- Use in combination with pcall +function F.ok_or_nil(status, ...) + if not status then return end + return ... +end + +-- Nil pcall. +function F.npcall(fn, ...) + return F.ok_or_nil(pcall(fn, ...)) +end + + +return F diff --git a/runtime/lua/vim/highlight.lua b/runtime/lua/vim/highlight.lua index ce0a3de520..0012dce081 100644 --- a/runtime/lua/vim/highlight.lua +++ b/runtime/lua/vim/highlight.lua @@ -2,6 +2,22 @@ local api = vim.api local highlight = {} +--@private +function highlight.create(higroup, hi_info, default) + local options = {} + -- TODO: Add validation + for k, v in pairs(hi_info) do + table.insert(options, string.format("%s=%s", k, v)) + end + vim.cmd(string.format([[highlight %s %s %s]], default and "default" or "", higroup, table.concat(options, " "))) +end + +--@private +function highlight.link(higroup, link_to, force) + vim.cmd(string.format([[highlight%s link %s %s]], force and "!" or " default", higroup, link_to)) +end + + --- Highlight range between two positions --- --@param bufnr number of buffer to apply highlighting to @@ -14,7 +30,7 @@ function highlight.range(bufnr, ns, higroup, start, finish, rtype, inclusive) inclusive = inclusive or false -- sanity check - if start[2] < 0 or finish[2] < start[2] then return end + if start[2] < 0 or finish[1] < start[1] then return end local region = vim.region(bufnr, start, finish, rtype, inclusive) for linenr, cols in pairs(region) do diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 6fe1d15b7e..dacdbcfa17 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -1,4 +1,4 @@ -local default_callbacks = require 'vim.lsp.callbacks' +local default_handlers = require 'vim.lsp.handlers' local log = require 'vim.lsp.log' local lsp_rpc = require 'vim.lsp.rpc' local protocol = require 'vim.lsp.protocol' @@ -13,25 +13,61 @@ local validate = vim.validate local lsp = { protocol = protocol; - callbacks = default_callbacks; + + -- TODO(tjdevries): Add in the warning that `callbacks` is no longer supported. + -- util.warn_once("vim.lsp.callbacks is deprecated. Use vim.lsp.handlers instead.") + handlers = default_handlers; + callbacks = default_handlers; + buf = require'vim.lsp.buf'; + diagnostic = require'vim.lsp.diagnostic'; util = util; + -- Allow raw RPC access. rpc = lsp_rpc; + -- Export these directly from rpc. rpc_response_error = lsp_rpc.rpc_response_error; - -- You probably won't need this directly, since __tostring is set for errors - -- by the RPC. - -- format_rpc_error = lsp_rpc.format_rpc_error; +} + +-- maps request name to the required resolved_capability in the client. +lsp._request_name_to_capability = { + ['textDocument/hover'] = 'hover'; + ['textDocument/signatureHelp'] = 'signature_help'; + ['textDocument/definition'] = 'goto_definition'; + ['textDocument/implementation'] = 'implementation'; + ['textDocument/declaration'] = 'declaration'; + ['textDocument/typeDefinition'] = 'type_definition'; + ['textDocument/documentSymbol'] = 'document_symbol'; + ['textDocument/workspaceSymbol'] = 'workspace_symbol'; + ['textDocument/prepareCallHierarchy'] = 'call_hierarchy'; + ['textDocument/rename'] = 'rename'; + ['textDocument/codeAction'] = 'code_action'; + ['workspace/executeCommand'] = 'execute_command'; + ['textDocument/references'] = 'find_references'; + ['textDocument/rangeFormatting'] = 'document_range_formatting'; + ['textDocument/formatting'] = 'document_formatting'; + ['textDocument/completion'] = 'completion'; + ['textDocument/documentHighlight'] = 'document_highlight'; } -- TODO improve handling of scratch buffers with LSP attached. +--@private +--- Concatenates and writes a list of strings to the Vim error buffer. +--- +--@param {...} (List of strings) List to write to the buffer local function err_message(...) nvim_err_writeln(table.concat(vim.tbl_flatten{...})) nvim_command("redraw") end +--@private +--- Returns the buffer number for the given {bufnr}. +--- +--@param bufnr (number) Buffer number to resolve. Defaults to the current +---buffer if not given. +--@returns bufnr (number) Number of requested buffer local function resolve_bufnr(bufnr) validate { bufnr = { bufnr, 'n', true } } if bufnr == nil or bufnr == 0 then @@ -40,6 +76,21 @@ local function resolve_bufnr(bufnr) return bufnr end +--@private +--- Called by the client when trying to call a method that's not +--- supported in any of the servers registered for the current buffer. +--@param method (string) name of the method +function lsp._unsupported_method(method) + local msg = string.format("method %s is not supported by any of the servers registered for the current buffer", method) + log.warn(msg) + return lsp.rpc_response_error(protocol.ErrorCodes.MethodNotFound, msg) +end + +--@private +--- Checks whether a given path is a directory. +--- +--@param filename (string) path to check +--@returns true if {filename} exists and is a directory, false otherwise local function is_dir(filename) validate{filename={filename,'s'}} local stat = uv.fs_stat(filename) @@ -55,6 +106,10 @@ local valid_encodings = { } local client_index = 0 +--@private +--- Returns a new, unused client id. +--- +--@returns (number) client id local function next_client_id() client_index = client_index + 1 return client_index @@ -64,9 +119,15 @@ local active_clients = {} local all_buffer_active_clients = {} local uninitialized_clients = {} -local function for_each_buffer_client(bufnr, callback) +--@private +--- Invokes a function for each LSP client attached to the buffer {bufnr}. +--- +--@param bufnr (Number) of buffer +--@param fn (function({client}, {client_id}, {bufnr}) Function to run on +---each client attached to that buffer. +local function for_each_buffer_client(bufnr, fn) validate { - callback = { callback, 'f' }; + fn = { fn, 'f' }; } bufnr = resolve_bufnr(bufnr) local client_ids = all_buffer_active_clients[bufnr] @@ -76,7 +137,7 @@ local function for_each_buffer_client(bufnr, callback) for client_id in pairs(client_ids) do local client = active_clients[client_id] if client then - callback(client, client_id, bufnr) + fn(client, client_id, bufnr) end end end @@ -88,6 +149,11 @@ lsp.client_errors = tbl_extend("error", lsp_rpc.client_errors, vim.tbl_add_rever ON_INIT_CALLBACK_ERROR = table.maxn(lsp_rpc.client_errors) + 1; }) +--@private +--- Normalizes {encoding} to valid LSP encoding names. +--- +--@param encoding (string) Encoding to normalize +--@returns (string) normalized encoding name local function validate_encoding(encoding) validate { encoding = { encoding, 's' }; @@ -96,6 +162,13 @@ local function validate_encoding(encoding) or error(string.format("Invalid offset encoding %q. Must be one of: 'utf-8', 'utf-16', 'utf-32'", encoding)) end +--@internal +--- Parses a command invocation into the command itself and its args. If there +--- are no arguments, an empty table is returned as the second argument. +--- +--@param input (List) +--@returns (string) the command +--@returns (list of strings) its arguments function lsp._cmd_parts(input) vim.validate{cmd={ input, @@ -114,19 +187,36 @@ function lsp._cmd_parts(input) return cmd, cmd_args end +--@private +--- Augments a validator function with support for optional (nil) values. +--- +--@param fn (function(v)) The original validator function; should return a +---bool. +--@returns (function(v)) The augmented function. Also returns true if {v} is +---`nil`. local function optional_validator(fn) return function(v) return v == nil or fn(v) end end +--@private +--- Validates a client configuration as given to |vim.lsp.start_client()|. +--- +--@param config (table) +--@returns (table) "Cleaned" config, containing only the command, its +---arguments, and a valid encoding. +--- +--@see |vim.lsp.start_client()| local function validate_client_config(config) validate { config = { config, 't' }; } validate { root_dir = { config.root_dir, is_dir, "directory" }; + -- TODO(remove-callbacks) callbacks = { config.callbacks, "t", true }; + handlers = { config.handlers, "t", true }; capabilities = { config.capabilities, "t", true }; cmd_cwd = { config.cmd_cwd, optional_validator(is_dir), "directory" }; cmd_env = { config.cmd_env, "t", true }; @@ -137,17 +227,32 @@ local function validate_client_config(config) before_init = { config.before_init, "f", true }; offset_encoding = { config.offset_encoding, "s", true }; } + + -- TODO(remove-callbacks) + if config.handlers and config.callbacks then + error(debug.traceback( + "Unable to configure LSP with both 'config.handlers' and 'config.callbacks'. Use 'config.handlers' exclusively." + )) + end + local cmd, cmd_args = lsp._cmd_parts(config.cmd) local offset_encoding = valid_encodings.UTF16 if config.offset_encoding then offset_encoding = validate_encoding(config.offset_encoding) end + return { - cmd = cmd; cmd_args = cmd_args; + cmd = cmd; + cmd_args = cmd_args; offset_encoding = offset_encoding; } end +--@private +--- Returns full text of buffer {bufnr} as a string. +--- +--@param bufnr (number) Buffer handle, or 0 for current. +--@returns Buffer text as string. local function buf_get_full_text(bufnr) local text = table.concat(nvim_buf_get_lines(bufnr, 0, -1, true), '\n') if nvim_buf_get_option(bufnr, 'eol') then @@ -156,6 +261,11 @@ local function buf_get_full_text(bufnr) return text end +--@private +--- Default handler for the 'textDocument/didOpen' LSP notification. +--- +--@param bufnr (Number) Number of the buffer, or 0 for current +--@param client Client object local function text_document_did_open_handler(bufnr, client) if not client.resolved_capabilities.text_document_open_close then return @@ -176,74 +286,88 @@ local function text_document_did_open_handler(bufnr, client) util.buf_versions[bufnr] = params.textDocument.version end ---- LSP client object. +-- FIXME: DOC: Shouldn't need to use a dummy function +-- +--- 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, [callback]) ---- Send a request to the server. If callback is not specified, it will use ---- {client.callbacks} to try to find a callback. If one is not found there, ---- then an error will occur. +--- - request(method, params, [handler], bufnr) +--- Sends a request to the server. --- This is a thin wrapper around {client.rpc.request} with some additional --- checking. ---- Returns a boolean to indicate if the notification was successful. If it ---- is false, then it will always be false (the client has shutdown). ---- If it was successful, then it will return the request id as the second ---- result. You can use this with `notify("$/cancel", { id = request_id })` ---- to cancel the request. This helper is made automatically with ---- |vim.lsp.buf_request()| ---- Returns: status, [client_id] +--- 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. --- --- - notify(method, params) ---- This is just {client.rpc.notify}() ---- Returns a boolean to indicate if the notification was successful. If it ---- is false, then it will always be false (the client has shutdown). ---- Returns: status +--- 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) ---- This is just {client.rpc.notify}("$/cancelRequest", { id = id }) ---- Returns the same as `notify()`. +--- Cancels a request with a given request id. +--- Returns: same as `notify()`. --- --- - stop([force]) ---- Stop a client, optionally with 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() ---- Returns true if the client is fully stopped. +--- Checks whether a client is stopped. +--- Returns: true if the client is fully stopped. +--- +--- - on_attach(bufnr) +--- Runs the on_attach function from the client's config if it was defined. --- --- - Members ---- - id (number): The id allocated to the client. +--- - {id} (number): The id allocated to the client. --- ---- - name (string): If a name is specified on creation, that will be +--- - {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. --- ---- - offset_encoding (string): The encoding used for communicating ---- with the server. You can modify this in the `on_init` method +--- - {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. --- ---- - callbacks (table): The callbacks used by the client as ---- described in |lsp-callbacks|. +--- - {handlers} (table): The handlers used by the client as described in |lsp-handler|. --- ---- - config (table): copy of the table that was passed by the user +--- - {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 +--- - {server_capabilities} (table): Response from the server sent on --- `initialize` describing the server's capabilities. --- ---- - resolved_capabilities (table): Normalized table of +--- - {resolved_capabilities} (table): Normalized table of --- capabilities that we have detected based on the initialize --- response from the server in `server_capabilities`. function lsp.client() error() end +-- FIXME: DOC: Currently all methods on the `vim.lsp.client` object are +-- documented twice: Here, and on the methods themselves (e.g. +-- `client.request()`). This is a workaround for the vimdoc generator script +-- not handling method names correctly. If you change the documentation on +-- either, please make sure to update the other as well. +-- --- Starts and initializes a client with the given configuration. --- --- Parameters `cmd` and `root_dir` are required. --- +--- The following parameters describe fields in the {config} table. +--- --@param root_dir: (required, string) Directory where the LSP server will base --- its rootUri on initialization. --- @@ -269,15 +393,7 @@ end --- `{[vim.type_idx]=vim.types.dictionary}`, else it will be encoded as an --- array. --- ---@param callbacks Map of language server method names to ---- `function(err, method, params, client_id)` handler. Invoked for: ---- - Notifications from the server, where `err` will always be `nil`. ---- - Requests initiated by the server. For these you can respond by returning ---- two values: `result, err` where err must be shaped like a RPC error, ---- i.e. `{ code, message, data? }`. Use |vim.lsp.rpc_response_error()| to ---- help with this. ---- - Default callback for client requests not explicitly specifying ---- a callback. +--@param handlers Map of language server method names to |lsp-handler| --- --@param init_options Values to pass in the initialization request --- as `initializationOptions`. See `initialize` in the LSP spec. @@ -297,7 +413,7 @@ end --@param before_init Callback with parameters (initialize_params, config) --- invoked before the LSP "initialize" phase, where `params` contains the --- parameters being sent to the server and `config` is the config that was ---- passed to `start_client()`. You can use this to modify parameters before +--- passed to |vim.lsp.start_client()|. You can use this to modify parameters before --- they are sent. --- --@param on_init Callback (client, initialize_result) invoked after LSP @@ -319,9 +435,8 @@ end --@param trace: "off" | "messages" | "verbose" | nil passed directly to the language --- server in the initialize request. Invalid/empty values will default to "off" --- ---@returns Client id. |vim.lsp.get_client_by_id()| Note: client is only ---- available after it has been initialized, which may happen after a small ---- delay (or never if there is an error). Use `on_init` to do any actions once +--@returns Client id. |vim.lsp.get_client_by_id()| Note: client may not be +--- fully initialized. Use `on_init` to do any actions once --- the client has been initialized. function lsp.start_client(config) local cleaned_config = validate_client_config(config) @@ -329,37 +444,62 @@ function lsp.start_client(config) local client_id = next_client_id() - local callbacks = config.callbacks or {} + -- TODO(remove-callbacks) + local handlers = config.handlers or config.callbacks or {} local name = config.name or tostring(client_id) local log_prefix = string.format("LSP[%s]", name) - local handlers = {} + local dispatch = {} - local function resolve_callback(method) - return callbacks[method] or default_callbacks[method] + --@private + --- Returns the handler associated with an LSP method. + --- Returns the default handler if the user hasn't set a custom one. + --- + --@param method (string) LSP method name + --@returns (fn) The handler for the given method, if defined, or the default from |vim.lsp.handlers| + local function resolve_handler(method) + return handlers[method] or default_handlers[method] end - function handlers.notification(method, params) + --@private + --- Handles a notification sent by an LSP server by invoking the + --- corresponding handler. + --- + --@param method (string) LSP method name + --@param params (table) The parameters for that method. + function dispatch.notification(method, params) local _ = log.debug() and log.debug('notification', method, params) - local callback = resolve_callback(method) - if callback then + local handler = resolve_handler(method) + if handler then -- Method name is provided here for convenience. - callback(nil, method, params, client_id) + handler(nil, method, params, client_id) end end - function handlers.server_request(method, params) + --@private + --- Handles a request from an LSP server by invoking the corresponding handler. + --- + --@param method (string) LSP method name + --@param params (table) The parameters for that method + function dispatch.server_request(method, params) local _ = log.debug() and log.debug('server_request', method, params) - local callback = resolve_callback(method) - if callback then - local _ = log.debug() and log.debug("server_request: found callback for", method) - return callback(nil, method, params, client_id) + local handler = resolve_handler(method) + if handler then + local _ = log.debug() and log.debug("server_request: found handler for", method) + return handler(nil, method, params, client_id) end - local _ = log.debug() and log.debug("server_request: no callback found for", method) + local _ = log.debug() and log.debug("server_request: no handler found for", method) return nil, lsp.rpc_response_error(protocol.ErrorCodes.MethodNotFound) end - function handlers.on_error(code, err) + --@private + --- Invoked when the client operation throws an error. + --- + --@param code (number) Error code + --@param err (...) Other arguments may be passed depending on the error kind + --@see |vim.lsp.client_errors| for possible errors. Use + ---`vim.lsp.client_errors[code]` to get a human-friendly name. + function dispatch.on_error(code, err) local _ = log.error() and log.error(log_prefix, "on_error", { code = lsp.client_errors[code], err = err }) err_message(log_prefix, ': Error ', lsp.client_errors[code], ': ', vim.inspect(err)) if config.on_error then @@ -371,7 +511,12 @@ function lsp.start_client(config) end end - function handlers.on_exit(code, signal) + --@private + --- Invoked on client exit. + --- + --@param code (number) exit code of the process + --@param signal (number) the signal used to terminate (if any) + function dispatch.on_exit(code, signal) active_clients[client_id] = nil uninitialized_clients[client_id] = nil local active_buffers = {} @@ -384,7 +529,7 @@ function lsp.start_client(config) -- Buffer level cleanup vim.schedule(function() for _, bufnr in ipairs(active_buffers) do - util.buf_clear_diagnostics(bufnr) + lsp.diagnostic.clear(bufnr) end end) if config.on_exit then @@ -393,7 +538,7 @@ function lsp.start_client(config) end -- Start the RPC client. - local rpc = lsp_rpc.start(cmd, cmd_args, handlers, { + local rpc = lsp_rpc.start(cmd, cmd_args, dispatch, { cwd = config.cmd_cwd; env = config.cmd_env; }) @@ -403,14 +548,17 @@ function lsp.start_client(config) name = name; rpc = rpc; offset_encoding = offset_encoding; - callbacks = callbacks; config = config; + + -- TODO(remove-callbacks) + callbacks = handlers; + handlers = handlers; } - -- Store the uninitialized_clients for cleanup in case we exit before - -- initialize finishes. + -- Store the uninitialized_clients for cleanup in case we exit before initialize finishes. uninitialized_clients[client_id] = client; + --@private local function initialize() local valid_traces = { off = 'off'; messages = 'messages'; verbose = 'verbose'; @@ -466,6 +614,15 @@ function lsp.start_client(config) -- These are the cleaned up capabilities we use for dynamically deciding -- when to send certain events to clients. client.resolved_capabilities = protocol.resolve_capabilities(client.server_capabilities) + client.supports_method = function(method) + local required_capability = lsp._request_name_to_capability[method] + -- if we don't know about the method, assume that the client supports it. + if not required_capability then + return true + end + + return client.resolved_capabilities[required_capability] + end if config.on_init then local status, err = pcall(config.on_init, client, result) if not status then @@ -488,43 +645,53 @@ function lsp.start_client(config) end) end - local function unsupported_method(method) - local msg = "server doesn't support "..method - local _ = log.warn() and log.warn(msg) - err_message(msg) - return lsp.rpc_response_error(protocol.ErrorCodes.MethodNotFound, msg) - end - - --- Checks capabilities before rpc.request-ing. - function client.request(method, params, callback, bufnr) - if not callback then - callback = resolve_callback(method) - or error(string.format("not found: %q request callback for client %q.", method, client.name)) - end - local _ = log.debug() and log.debug(log_prefix, "client.request", client_id, method, params, callback, bufnr) - -- TODO keep these checks or just let it go anyway? - if (not client.resolved_capabilities.hover and method == 'textDocument/hover') - or (not client.resolved_capabilities.signature_help and method == 'textDocument/signatureHelp') - or (not client.resolved_capabilities.goto_definition and method == 'textDocument/definition') - or (not client.resolved_capabilities.implementation and method == 'textDocument/implementation') - or (not client.resolved_capabilities.declaration and method == 'textDocument/declaration') - or (not client.resolved_capabilities.type_definition and method == 'textDocument/typeDefinition') - or (not client.resolved_capabilities.document_symbol and method == 'textDocument/documentSymbol') - or (not client.resolved_capabilities.workspace_symbol and method == 'textDocument/workspaceSymbol') - or (not client.resolved_capabilities.call_hierarchy and method == 'textDocument/prepareCallHierarchy') - then - callback(unsupported_method(method), method, nil, client_id, bufnr) - return + --@private + --- Sends a request to the server. + --- + --- This is a thin wrapper around {client.rpc.request} with some additional + --- checks for capabilities and handler availability. + --- + --@param method (string) LSP method name. + --@param params (table) LSP request params. + --@param handler (function, optional) Response |lsp-handler| for this method. + --@param bufnr (number) Buffer handle (0 for current). + --@returns ({status}, [request_id]): {status} is a bool indicating + ---whether the request was successful. If it is `false`, then it will + ---always be `false` (the client has shutdown). If it was + ---successful, then it will return {request_id} as the + ---second result. You can use this with `client.cancel_request(request_id)` + ---to cancel the-request. + --@see |vim.lsp.buf_request()| + function client.request(method, params, handler, bufnr) + if not handler then + handler = resolve_handler(method) + or error(string.format("not found: %q request handler for client %q.", method, client.name)) end + local _ = log.debug() and log.debug(log_prefix, "client.request", client_id, method, params, handler, bufnr) return rpc.request(method, params, function(err, result) - callback(err, method, result, client_id, bufnr) + handler(err, method, result, client_id, bufnr) end) end + --@private + --- Sends a notification to an LSP server. + --- + --@param method (string) LSP method name. + --@param params (optional, table) LSP request params. + --@param bufnr (number) Buffer handle, or 0 for current. + --@returns {status} (bool) true if the notification was successful. + ---If it is false, then it will always be false + ---(the client has shutdown). function client.notify(...) return rpc.notify(...) end + --@private + --- Cancels a request with a given request id. + --- + --@param id (number) id of request to cancel + --@returns true if any client returns true; false otherwise + --@see |vim.lsp.client.notify()| function client.cancel_request(id) validate{id = {id, 'n'}} return rpc.notify("$/cancelRequest", { id = id }) @@ -533,6 +700,14 @@ function lsp.start_client(config) -- Track this so that we can escalate automatically if we've alredy tried a -- graceful shutdown local tried_graceful_shutdown = false + --@private + --- 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. + --- + --@param force (bool, optional) function client.stop(force) local handle = rpc.handle if handle:is_closing() then @@ -554,10 +729,18 @@ function lsp.start_client(config) end) end + --@private + --- Checks whether a client is stopped. + --- + --@returns (bool) true if client is stopped or in the process of being + ---stopped; false otherwise function client.is_stopped() return rpc.handle:is_closing() end + --@private + --- Runs the on_attach function from the client's config if it was defined. + --@param bufnr (number) Buffer number function client._on_attach(bufnr) text_document_did_open_handler(bufnr, client) if config.on_attach then @@ -571,6 +754,12 @@ function lsp.start_client(config) return client_id end +--@private +--- Memoizes a function. On first run, the function return value is saved and +--- immediately returned on subsequent runs. +--- +--@param fn (function) Function to run +--@returns (function) Memoized function local function once(fn) local value return function(...) @@ -579,14 +768,21 @@ local function once(fn) end end +--@private +--@fn text_document_did_change_handler(_, bufnr, changedtick, firstline, lastline, new_lastline, old_byte_size, old_utf32_size, old_utf16_size) +--- Notify all attached clients that a buffer has changed. local text_document_did_change_handler do local encoding_index = { ["utf-8"] = 1; ["utf-16"] = 2; ["utf-32"] = 3; } text_document_did_change_handler = function(_, bufnr, changedtick, firstline, lastline, new_lastline, old_byte_size, old_utf32_size, old_utf16_size) - local _ = log.debug() and log.debug("on_lines", bufnr, changedtick, firstline, - lastline, new_lastline, old_byte_size, old_utf32_size, old_utf16_size, nvim_buf_get_lines(bufnr, firstline, new_lastline, true)) + + local _ = log.debug() and log.debug( + string.format("on_lines bufnr: %s, changedtick: %s, firstline: %s, lastline: %s, new_lastline: %s, old_byte_size: %s, old_utf32_size: %s, old_utf16_size: %s", + bufnr, changedtick, firstline, lastline, new_lastline, old_byte_size, old_utf32_size, old_utf16_size), + nvim_buf_get_lines(bufnr, firstline, new_lastline, true) + ) -- Don't do anything if there are no clients attached. if tbl_isempty(all_buffer_active_clients[bufnr] or {}) then @@ -730,14 +926,14 @@ function lsp.buf_is_attached(bufnr, client_id) return (all_buffer_active_clients[bufnr] or {})[client_id] == true end ---- Gets an active client by id, or nil if the id is invalid or the ---- client is not yet initialized. ---- +--- Gets a client by id, or nil if the id is invalid. +--- The returned client may not yet be fully initialized. +-- --@param client_id client id number --- ---@return |vim.lsp.client| object, or nil +--@returns |vim.lsp.client| object, or nil function lsp.get_client_by_id(client_id) - return active_clients[client_id] + return active_clients[client_id] or uninitialized_clients[client_id] end --- Stops a client(s). @@ -746,7 +942,7 @@ end --- To stop all clients: --- --- <pre> ---- vim.lsp.stop_client(lsp.get_active_clients()) +--- vim.lsp.stop_client(vim.lsp.get_active_clients()) --- </pre> --- --- By default asks the server to shutdown, unless stop was requested @@ -769,7 +965,7 @@ end --- Gets all active clients. --- ---@return Table of |vim.lsp.client| objects +--@returns Table of |vim.lsp.client| objects function lsp.get_active_clients() return vim.tbl_values(active_clients) end @@ -803,31 +999,46 @@ nvim_command("autocmd VimLeavePre * lua vim.lsp._vim_exit_handler()") --@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 callback (optional, functionnil) Handler --- `function(err, method, params, client_id)` for this request. Defaults --- to the client callback in `client.callbacks`. See |lsp-callbacks|. +--@param handler (optional, function) See |lsp-handler| +-- If nil, follows resolution strategy defined in |lsp-handler-configuration| -- --@returns 2-tuple: --- - Map of client-id:request-id pairs for all successful requests. --- - Function which can be used to cancel all the requests. You could instead --- iterate all clients and call their `cancel_request()` methods. -function lsp.buf_request(bufnr, method, params, callback) +function lsp.buf_request(bufnr, method, params, handler) validate { bufnr = { bufnr, 'n', true }; method = { method, 's' }; - callback = { callback, 'f', true }; + handler = { handler, 'f', true }; } local client_request_ids = {} - for_each_buffer_client(bufnr, function(client, client_id, resolved_bufnr) - local request_success, request_id = client.request(method, params, callback, resolved_bufnr) - -- This could only fail if the client shut down in the time since we looked - -- it up and we did the request, which should be rare. - if request_success then - client_request_ids[client_id] = request_id + local method_supported = false + for_each_buffer_client(bufnr, function(client, client_id, resolved_bufnr) + if client.supports_method(method) then + method_supported = true + local request_success, request_id = client.request(method, params, handler, resolved_bufnr) + + -- This could only fail if the client shut down in the time since we looked + -- it up and we did the request, which should be rare. + if request_success then + client_request_ids[client_id] = request_id + end end end) + -- if no clients support the given method, call the handler with the proper + -- error message. + if not method_supported then + local unsupported_err = lsp._unsupported_method(method) + handler = handler or lsp.handlers[method] + if handler then + handler(unsupported_err, method, bufnr) + end + return + end + local function _cancel_all_requests() for client_id, request_id in pairs(client_request_ids) do local client = active_clients[client_id] @@ -856,11 +1067,11 @@ end function lsp.buf_request_sync(bufnr, method, params, timeout_ms) local request_results = {} local result_count = 0 - local function _callback(err, _method, result, client_id) + local function _sync_handler(err, _, result, client_id) request_results[client_id] = { error = err, result = result } result_count = result_count + 1 end - local client_request_ids, cancel = lsp.buf_request(bufnr, method, params, _callback) + local client_request_ids, cancel = lsp.buf_request(bufnr, method, params, _sync_handler) local expected_result_count = 0 for _ in pairs(client_request_ids) do expected_result_count = expected_result_count + 1 @@ -904,7 +1115,7 @@ end --@param findstart 0 or 1, decides behavior --@param base If findstart=0, text to match against --- ---@return (number) Decided by `findstart`: +--@returns (number) Decided by `findstart`: --- - findstart=0: column where the completion starts, or -2 or -3 --- - findstart=1: list of matches (actually just calls |complete()|) function lsp.omnifunc(findstart, base) @@ -948,6 +1159,10 @@ function lsp.omnifunc(findstart, base) return -2 end +---Checks whether a client is stopped. +--- +--@param client_id (Number) +--@returns true if client is stopped, false otherwise. function lsp.client_is_stopped(client_id) return active_clients[client_id] == nil end @@ -992,22 +1207,58 @@ function lsp.set_log_level(level) end --- Gets the path of the logfile used by the LSP client. +--@returns (String) Path to logfile. function lsp.get_log_path() return log.get_filename() end --- Define the LspDiagnostics signs if they're not defined already. -do - local function define_default_sign(name, properties) - if vim.tbl_isempty(vim.fn.sign_getdefined(name)) then - vim.fn.sign_define(name, properties) +--- Call {fn} for every client attached to {bufnr} +function lsp.for_each_buffer_client(bufnr, fn) + return for_each_buffer_client(bufnr, fn) +end + +--- Function to manage overriding defaults for LSP handlers. +--@param handler (function) See |lsp-handler| +--@param override_config (table) Table containing the keys to override behavior of the {handler} +function lsp.with(handler, override_config) + return function(err, method, params, client_id, bufnr, config) + return handler(err, method, params, client_id, bufnr, vim.tbl_deep_extend("force", config or {}, override_config)) + end +end + +--- Helper function to use when implementing a handler. +--- This will check that all of the keys in the user configuration +--- are valid keys and make sense to include for this handler. +--- +--- Will error on invalid keys (i.e. keys that do not exist in the options) +function lsp._with_extend(name, options, user_config) + user_config = user_config or {} + + local resulting_config = {} + for k, v in pairs(user_config) do + if options[k] == nil then + error(debug.traceback(string.format( + "Invalid option for `%s`: %s. Valid options are:\n%s", + name, + k, + vim.inspect(vim.tbl_keys(options)) + ))) + end + + resulting_config[k] = v + end + + for k, v in pairs(options) do + if resulting_config[k] == nil then + resulting_config[k] = v end end - define_default_sign('LspDiagnosticsErrorSign', {text='E', texthl='LspDiagnosticsErrorSign', linehl='', numhl=''}) - define_default_sign('LspDiagnosticsWarningSign', {text='W', texthl='LspDiagnosticsWarningSign', linehl='', numhl=''}) - define_default_sign('LspDiagnosticsInformationSign', {text='I', texthl='LspDiagnosticsInformationSign', linehl='', numhl=''}) - define_default_sign('LspDiagnosticsHintSign', {text='H', texthl='LspDiagnosticsHintSign', linehl='', numhl=''}) + + return resulting_config end +-- Define the LspDiagnostics signs if they're not defined already. +require('vim.lsp.diagnostic')._define_default_signs_and_highlights() + return lsp -- vim:sw=2 ts=2 et diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index 2e27617997..fa62905c0a 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -1,31 +1,55 @@ local vim = vim local validate = vim.validate -local api = vim.api local vfn = vim.fn local util = require 'vim.lsp.util' -local list_extend = vim.list_extend local M = {} +--@private +--- Returns nil if {status} is false or nil, otherwise returns the rest of the +--- arguments. local function ok_or_nil(status, ...) if not status then return end return ... end + +--@private +--- Swallows errors. +--- +--@param fn Function to run +--@param ... Function arguments +--@returns Result of `fn(...)` if there are no errors, otherwise nil. +--- Returns nil if errors occur during {fn}, otherwise returns local function npcall(fn, ...) return ok_or_nil(pcall(fn, ...)) end -local function request(method, params, callback) +--@private +--- Sends an async request to all active clients attached to the current +--- buffer. +--- +--@param method (string) LSP method name +--@param params (optional, table) Parameters to send to the server +--@param handler (optional, functionnil) See |lsp-handler|. Follows |lsp-handler-resolution| +-- +--@returns 2-tuple: +--- - Map of client-id:request-id pairs for all successful requests. +--- - Function which can be used to cancel all the requests. You could instead +--- iterate all clients and call their `cancel_request()` methods. +--- +--@see |vim.lsp.buf_request()| +local function request(method, params, handler) validate { method = {method, 's'}; - callback = {callback, 'f', true}; + handler = {handler, 'f', true}; } - return vim.lsp.buf_request(0, method, params, callback) + return vim.lsp.buf_request(0, method, params, handler) end ---- Sends a notification through all clients associated with current buffer. --- ---@return `true` if server responds. +--- Checks whether the language servers attached to the current buffer are +--- ready. +--- +--@returns `true` if server responds. function M.server_ready() return not not vim.lsp.buf_notify(0, "window/progress", {}) end @@ -38,6 +62,7 @@ function M.hover() end --- 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. --- function M.declaration() local params = util.make_position_params() @@ -74,6 +99,12 @@ end --- Retrieves the completion items at the current cursor position. Can only be --- called in Insert mode. +--- +--@param 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 |vim.lsp.protocol.constants.CompletionTriggerKind| function M.completion(context) local params = util.make_position_params() params.context = context @@ -82,64 +113,60 @@ end --- Formats the current buffer. --- ---- The optional {options} table can be used to specify FormattingOptions, a ---- list of which is available at ---- https://microsoft.github.io/language-server-protocol/specification#textDocument_formatting. +--@param options (optional, table) 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/specification#textDocument_formatting function M.formatting(options) local params = util.make_formatting_params(options) return request('textDocument/formatting', params) end ---- Perform |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()|. +--- saved. {timeout_ms} is passed on to |vim.lsp.buf_request_sync()|. Example: +--- +--- <pre> +--- vim.api.nvim_command[[autocmd BufWritePre <buffer> lua vim.lsp.buf.formatting_sync()]] +--- </pre> +--- +--@param options Table with valid `FormattingOptions` entries +--@param timeout_ms (number) Request timeout function M.formatting_sync(options, timeout_ms) local params = util.make_formatting_params(options) local result = vim.lsp.buf_request_sync(0, "textDocument/formatting", params, timeout_ms) + if not result or vim.tbl_isempty(result) then return end + local _, formatting_result = next(result) + result = formatting_result.result if not result then return end - result = result[1].result vim.lsp.util.apply_text_edits(result) end +--- Formats a given range. +--- +--@param options Table with valid `FormattingOptions` entries. +--@param start_pos ({number, number}, optional) mark-indexed position. +---Defaults to the start of the last visual selection. +--@param start_pos ({number, number}, optional) mark-indexed position. +---Defaults to the end of the last visual selection. function M.range_formatting(options, start_pos, end_pos) - validate { - options = {options, 't', true}; - start_pos = {start_pos, 't', true}; - end_pos = {end_pos, 't', true}; - } + validate { options = {options, 't', true} } local sts = vim.bo.softtabstop; options = vim.tbl_extend('keep', options or {}, { tabSize = (sts > 0 and sts) or (sts < 0 and vim.bo.shiftwidth) or vim.bo.tabstop; insertSpaces = vim.bo.expandtab; }) - local A = list_extend({}, start_pos or api.nvim_buf_get_mark(0, '<')) - local B = list_extend({}, end_pos or api.nvim_buf_get_mark(0, '>')) - -- convert to 0-index - A[1] = A[1] - 1 - B[1] = B[1] - 1 - -- account for encoding. - if A[2] > 0 then - A = {A[1], util.character_offset(0, A[1], A[2])} - end - if B[2] > 0 then - B = {B[1], util.character_offset(0, B[1], B[2])} - end - local params = { - textDocument = { uri = vim.uri_from_bufnr(0) }; - range = { - start = { line = A[1]; character = A[2]; }; - ["end"] = { line = B[1]; character = B[2]; }; - }; - options = options; - } + local params = util.make_given_range_params(start_pos, end_pos) + params.options = options return request('textDocument/rangeFormatting', params) end ---- Renames all references to the symbol under the cursor. If {new_name} is not ---- provided, the user will be prompted for a new name using |input()|. +--- Renames all references to the symbol under the cursor. +--- +--@param new_name (string) If not provided, the user will be prompted for a new +---name using |input()|. function M.rename(new_name) -- TODO(ashkan) use prepareRename -- * result: [`Range`](#range) \| `{ range: Range, placeholder: string }` \| `null` describing the range of the string to rename and optionally a placeholder text of the string content to be renamed. If `null` is returned then it is deemed that a 'textDocument/rename' request is not valid at the given position. @@ -152,6 +179,8 @@ end --- Lists all the references to the symbol under the cursor in the quickfix window. --- +--@param context (table) Context for the request +--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_references function M.references(context) validate { context = { context, 't', true } } local params = util.make_position_params() @@ -169,6 +198,7 @@ function M.document_symbol() request('textDocument/documentSymbol', params) end +--@private local function pick_call_hierarchy_item(call_hierarchy_items) if not call_hierarchy_items then return end if #call_hierarchy_items == 1 then @@ -186,6 +216,9 @@ local function pick_call_hierarchy_item(call_hierarchy_items) return choice end +--- 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|. function M.incoming_calls() local params = util.make_position_params() request('textDocument/prepareCallHierarchy', params, function(_, _, result) @@ -194,6 +227,9 @@ function M.incoming_calls() end) end +--- 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|. function M.outgoing_calls() local params = util.make_position_params() request('textDocument/prepareCallHierarchy', params, function(_, _, result) @@ -204,9 +240,11 @@ end --- Lists all symbols in the current workspace in the quickfix window. --- ---- The list is filtered against the optional argument {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. +--- +--@param query (string, optional) function M.workspace_symbol(query) query = query or npcall(vfn.input, "Query: ") local params = {query = query} @@ -227,18 +265,44 @@ function M.document_highlight() request('textDocument/documentHighlight', params) end +--- Removes document highlights from current buffer. +--- function M.clear_references() util.buf_clear_references() end +--- Selects a code action from the input list that is available at the current +--- cursor position. +-- +--@param context: (table, optional) Valid `CodeActionContext` object +--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_codeAction function M.code_action(context) validate { context = { context, 't', true } } - context = context or { diagnostics = util.get_line_diagnostics() } + context = context or { diagnostics = vim.lsp.diagnostic.get_line_diagnostics() } local params = util.make_range_params() params.context = context request('textDocument/codeAction', params) end +--- Performs |vim.lsp.buf.code_action()| for a given range. +--- +--@param context: (table, optional) Valid `CodeActionContext` object +--@param start_pos ({number, number}, optional) mark-indexed position. +---Defaults to the start of the last visual selection. +--@param end_pos ({number, number}, optional) mark-indexed position. +---Defaults to the end of the last visual selection. +function M.range_code_action(context, start_pos, end_pos) + validate { context = { context, 't', true } } + context = context or { diagnostics = vim.lsp.diagnostic.get_line_diagnostics() } + local params = util.make_given_range_params(start_pos, end_pos) + params.context = context + request('textDocument/codeAction', params) +end + +--- Executes an LSP server command. +--- +--@param command A valid `ExecuteCommandParams` object +--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_executeCommand function M.execute_command(command) validate { command = { command.command, 's' }, diff --git a/runtime/lua/vim/lsp/callbacks.lua b/runtime/lua/vim/lsp/callbacks.lua index 1ed58995d0..1da92b900d 100644 --- a/runtime/lua/vim/lsp/callbacks.lua +++ b/runtime/lua/vim/lsp/callbacks.lua @@ -1,293 +1,4 @@ -local log = require 'vim.lsp.log' -local protocol = require 'vim.lsp.protocol' local util = require 'vim.lsp.util' -local vim = vim -local api = vim.api -local buf = require 'vim.lsp.buf' -local M = {} - -local function err_message(...) - api.nvim_err_writeln(table.concat(vim.tbl_flatten{...})) - api.nvim_command("redraw") -end - -M['workspace/executeCommand'] = function(err, _) - if err then - error("Could not execute code action: "..err.message) - end -end - -M['textDocument/codeAction'] = function(_, _, actions) - if actions == nil or vim.tbl_isempty(actions) then - print("No code actions available") - return - end - - local option_strings = {"Code Actions:"} - for i, action in ipairs(actions) do - local title = action.title:gsub('\r\n', '\\r\\n') - title = title:gsub('\n', '\\n') - table.insert(option_strings, string.format("%d. %s", i, title)) - end - - local choice = vim.fn.inputlist(option_strings) - if choice < 1 or choice > #actions then - return - end - local action_chosen = actions[choice] - -- textDocument/codeAction can return either Command[] or CodeAction[]. - -- If it is a CodeAction, it can have either an edit, a command or both. - -- Edits should be executed first - if action_chosen.edit or type(action_chosen.command) == "table" then - if action_chosen.edit then - util.apply_workspace_edit(action_chosen.edit) - end - if type(action_chosen.command) == "table" then - buf.execute_command(action_chosen.command) - end - else - buf.execute_command(action_chosen) - end -end - -M['workspace/applyEdit'] = function(_, _, workspace_edit) - if not workspace_edit then return end - -- TODO(ashkan) Do something more with label? - if workspace_edit.label then - print("Workspace edit", workspace_edit.label) - end - local status, result = pcall(util.apply_workspace_edit, workspace_edit.edit) - return { - applied = status; - failureReason = result; - } -end - -M['textDocument/publishDiagnostics'] = function(_, _, result) - if not result then return end - local uri = result.uri - local bufnr = vim.uri_to_bufnr(uri) - if not bufnr then - err_message("LSP.publishDiagnostics: Couldn't find buffer for ", uri) - return - end - - -- Unloaded buffers should not handle diagnostics. - -- When the buffer is loaded, we'll call on_attach, which sends textDocument/didOpen. - -- This should trigger another publish of the diagnostics. - -- - -- In particular, this stops a ton of spam when first starting a server for current - -- unloaded buffers. - if not api.nvim_buf_is_loaded(bufnr) then - return - end - - util.buf_clear_diagnostics(bufnr) - - -- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#diagnostic - -- The diagnostic's severity. Can be omitted. If omitted it is up to the - -- client to interpret diagnostics as error, warning, info or hint. - -- TODO: Replace this with server-specific heuristics to infer severity. - for _, diagnostic in ipairs(result.diagnostics) do - if diagnostic.severity == nil then - diagnostic.severity = protocol.DiagnosticSeverity.Error - end - end - - util.buf_diagnostics_save_positions(bufnr, result.diagnostics) - util.buf_diagnostics_underline(bufnr, result.diagnostics) - util.buf_diagnostics_virtual_text(bufnr, result.diagnostics) - util.buf_diagnostics_signs(bufnr, result.diagnostics) - vim.api.nvim_command("doautocmd User LspDiagnosticsChanged") -end - -M['textDocument/references'] = function(_, _, result) - if not result then return end - util.set_qflist(util.locations_to_items(result)) - api.nvim_command("copen") - api.nvim_command("wincmd p") -end - -local symbol_callback = function(_, _, result, _, bufnr) - if not result or vim.tbl_isempty(result) then return end - - util.set_qflist(util.symbols_to_items(result, bufnr)) - api.nvim_command("copen") - api.nvim_command("wincmd p") -end -M['textDocument/documentSymbol'] = symbol_callback -M['workspace/symbol'] = symbol_callback - -M['textDocument/rename'] = function(_, _, result) - if not result then return end - util.apply_workspace_edit(result) -end - -M['textDocument/rangeFormatting'] = function(_, _, result) - if not result then return end - util.apply_text_edits(result) -end - -M['textDocument/formatting'] = function(_, _, result) - if not result then return end - util.apply_text_edits(result) -end - -M['textDocument/completion'] = function(_, _, result) - if vim.tbl_isempty(result or {}) then return end - local row, col = unpack(api.nvim_win_get_cursor(0)) - local line = assert(api.nvim_buf_get_lines(0, row-1, row, false)[1]) - local line_to_cursor = line:sub(col+1) - local textMatch = vim.fn.match(line_to_cursor, '\\k*$') - local prefix = line_to_cursor:sub(textMatch+1) - - local matches = util.text_document_completion_list_to_complete_items(result, prefix) - vim.fn.complete(textMatch+1, matches) -end - -M['textDocument/hover'] = function(_, method, result) - util.focusable_float(method, function() - if not (result and result.contents) then - -- return { 'No information available' } - return - end - local markdown_lines = util.convert_input_to_markdown_lines(result.contents) - markdown_lines = util.trim_empty_lines(markdown_lines) - if vim.tbl_isempty(markdown_lines) then - -- return { 'No information available' } - return - end - local bufnr, winnr = util.fancy_floating_markdown(markdown_lines, { - pad_left = 1; pad_right = 1; - }) - util.close_preview_autocmd({"CursorMoved", "BufHidden", "InsertCharPre"}, winnr) - return bufnr, winnr - end) -end - -local function location_callback(_, method, result) - if result == nil or vim.tbl_isempty(result) then - local _ = log.info() and log.info(method, 'No location found') - return nil - end - - -- textDocument/definition can return Location or Location[] - -- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_definition - - if vim.tbl_islist(result) then - util.jump_to_location(result[1]) - - if #result > 1 then - util.set_qflist(util.locations_to_items(result)) - api.nvim_command("copen") - api.nvim_command("wincmd p") - end - else - util.jump_to_location(result) - end -end - -M['textDocument/declaration'] = location_callback -M['textDocument/definition'] = location_callback -M['textDocument/typeDefinition'] = location_callback -M['textDocument/implementation'] = location_callback - -M['textDocument/signatureHelp'] = function(_, method, result) - util.focusable_preview(method, function() - if not (result and result.signatures and result.signatures[1]) then - return { 'No signature available' } - end - -- TODO show popup when signatures is empty? - local lines = util.convert_signature_help_to_markdown_lines(result) - lines = util.trim_empty_lines(lines) - if vim.tbl_isempty(lines) then - return { 'No signature available' } - end - return lines, util.try_trim_markdown_code_blocks(lines) - end) -end - -M['textDocument/documentHighlight'] = function(_, _, result, _) - if not result then return end - local bufnr = api.nvim_get_current_buf() - util.buf_highlight_references(bufnr, result) -end - --- direction is "from" for incoming calls and "to" for outgoing calls -local make_call_hierarchy_callback = function(direction) - -- result is a CallHierarchy{Incoming,Outgoing}Call[] - return function(_, _, result) - if not result then return end - local items = {} - for _, call_hierarchy_call in pairs(result) do - local call_hierarchy_item = call_hierarchy_call[direction] - for _, range in pairs(call_hierarchy_call.fromRanges) do - table.insert(items, { - filename = assert(vim.uri_to_fname(call_hierarchy_item.uri)), - text = call_hierarchy_item.name, - lnum = range.start.line + 1, - col = range.start.character + 1, - }) - end - end - util.set_qflist(items) - api.nvim_command("copen") - api.nvim_command("wincmd p") - end -end - -M['callHierarchy/incomingCalls'] = make_call_hierarchy_callback('from') - -M['callHierarchy/outgoingCalls'] = make_call_hierarchy_callback('to') - -M['window/logMessage'] = function(_, _, result, client_id) - local message_type = result.type - local message = result.message - local client = vim.lsp.get_client_by_id(client_id) - local client_name = client and client.name or string.format("id=%d", client_id) - if not client then - err_message("LSP[", client_name, "] client has shut down after sending the message") - end - if message_type == protocol.MessageType.Error then - log.error(message) - elseif message_type == protocol.MessageType.Warning then - log.warn(message) - elseif message_type == protocol.MessageType.Info then - log.info(message) - else - log.debug(message) - end - return result -end - -M['window/showMessage'] = function(_, _, result, client_id) - local message_type = result.type - local message = result.message - local client = vim.lsp.get_client_by_id(client_id) - local client_name = client and client.name or string.format("id=%d", client_id) - if not client then - err_message("LSP[", client_name, "] client has shut down after sending the message") - end - if message_type == protocol.MessageType.Error then - err_message("LSP[", client_name, "] ", message) - else - local message_type_name = protocol.MessageType[message_type] - api.nvim_out_write(string.format("LSP[%s][%s] %s\n", client_name, message_type_name, message)) - end - return result -end - --- Add boilerplate error validation and logging for all of these. -for k, fn in pairs(M) do - M[k] = function(err, method, params, client_id, bufnr) - log.debug('default_callback', method, { params = params, client_id = client_id, err = err, bufnr = bufnr }) - if err then - error(tostring(err)) - end - return fn(err, method, params, client_id, bufnr) - end -end - -return M --- vim:sw=2 ts=2 et +util._warn_once("require('vim.lsp.callbacks') is deprecated. Use vim.lsp.handlers instead.") +return require('vim.lsp.handlers') diff --git a/runtime/lua/vim/lsp/diagnostic.lua b/runtime/lua/vim/lsp/diagnostic.lua new file mode 100644 index 0000000000..45e717d16c --- /dev/null +++ b/runtime/lua/vim/lsp/diagnostic.lua @@ -0,0 +1,1195 @@ +local api = vim.api +local validate = vim.validate + +local highlight = vim.highlight +local log = require('vim.lsp.log') +local protocol = require('vim.lsp.protocol') +local util = require('vim.lsp.util') + +local if_nil = vim.F.if_nil + +--@class DiagnosticSeverity +local DiagnosticSeverity = protocol.DiagnosticSeverity + +local to_severity = function(severity) + if not severity then return nil end + return type(severity) == 'string' and DiagnosticSeverity[severity] or severity +end + +local to_position = function(position, bufnr) + vim.validate { position = {position, 't'} } + + return { + position.line, + util._get_line_byte_from_position(bufnr, position) + } +end + + +---@brief lsp-diagnostic +--- +--@class Diagnostic +--@field range Range +--@field message string +--@field severity DiagnosticSeverity|nil +--@field code number | string +--@field source string +--@field tags DiagnosticTag[] +--@field relatedInformation DiagnosticRelatedInformation[] + +local M = {} + +-- Diagnostic Highlights {{{ + +-- TODO(tjdevries): Determine how to generate documentation for these +-- and how to configure them to be easy for users. +-- +-- For now, just use the following script. It should work pretty good. +--[[ +local levels = {"Error", "Warning", "Information", "Hint" } + +local all_info = { + { "Default", "Used as the base highlight group, other highlight groups link to", }, + { "VirtualText", 'Used for "%s" diagnostic virtual text.\n See |vim.lsp.diagnostic.set_virtual_text()|', }, + { "Underline", 'Used to underline "%s" diagnostics.\n See |vim.lsp.diagnostic.set_underline()|', }, + { "Floating", 'Used to color "%s" diagnostic messages in diagnostics float.\n See |vim.lsp.diagnostic.show_line_diagnostics()|', }, + { "Sign", 'Used for "%s" signs in sing column.\n See |vim.lsp.diagnostic.set_signs()|', }, +} + +local results = {} +for _, info in ipairs(all_info) do + for _, level in ipairs(levels) do + local name = info[1] + local description = info[2] + local fullname = string.format("Lsp%s%s", name, level) + table.insert(results, string.format( + "%78s", string.format("*hl-%s*", fullname)) + ) + + table.insert(results, fullname) + table.insert(results, string.format(" %s", description)) + table.insert(results, "") + end +end + +-- print(table.concat(results, '\n')) +vim.fn.setreg("*", table.concat(results, '\n')) +--]] + +local diagnostic_severities = { + [DiagnosticSeverity.Error] = { guifg = "Red" }; + [DiagnosticSeverity.Warning] = { guifg = "Orange" }; + [DiagnosticSeverity.Information] = { guifg = "LightBlue" }; + [DiagnosticSeverity.Hint] = { guifg = "LightGrey" }; +} + +-- Make a map from DiagnosticSeverity -> Highlight Name +local make_highlight_map = function(base_name) + local result = {} + for k, _ in pairs(diagnostic_severities) do + result[k] = "LspDiagnostics" .. base_name .. DiagnosticSeverity[k] + end + + return result +end + +local default_highlight_map = make_highlight_map("Default") +local virtual_text_highlight_map = make_highlight_map("VirtualText") +local underline_highlight_map = make_highlight_map("Underline") +local floating_highlight_map = make_highlight_map("Floating") +local sign_highlight_map = make_highlight_map("Sign") + +-- }}} +-- Diagnostic Namespaces {{{ +local DEFAULT_CLIENT_ID = -1 +local get_client_id = function(client_id) + if client_id == nil then + client_id = DEFAULT_CLIENT_ID + end + + return client_id +end + +local get_bufnr = function(bufnr) + if not bufnr then + return api.nvim_get_current_buf() + elseif bufnr == 0 then + return api.nvim_get_current_buf() + end + + return bufnr +end + + +--- Create a namespace table, used to track a client's buffer local items +local _make_namespace_table = function(namespace, api_namespace) + vim.validate { namespace = { namespace, 's' } } + + return setmetatable({ + [DEFAULT_CLIENT_ID] = api.nvim_create_namespace(namespace) + }, { + __index = function(t, client_id) + client_id = get_client_id(client_id) + + if rawget(t, client_id) == nil then + local value = string.format("%s:%s", namespace, client_id) + + if api_namespace then + value = api.nvim_create_namespace(value) + end + + rawset(t, client_id, value) + end + + return rawget(t, client_id) + end + }) +end + +local _diagnostic_namespaces = _make_namespace_table("vim_lsp_diagnostics", true) +local _sign_namespaces = _make_namespace_table("vim_lsp_signs", false) + +--@private +function M._get_diagnostic_namespace(client_id) + return _diagnostic_namespaces[client_id] +end + +--@private +function M._get_sign_namespace(client_id) + return _sign_namespaces[client_id] +end +-- }}} +-- Diagnostic Buffer & Client metatables {{{ +local bufnr_and_client_cacher_mt = { + __index = function(t, bufnr) + if bufnr == 0 or bufnr == nil then + bufnr = vim.api.nvim_get_current_buf() + end + + if rawget(t, bufnr) == nil then + rawset(t, bufnr, {}) + end + + return rawget(t, bufnr) + end, + + __newindex = function(t, bufnr, v) + if bufnr == 0 or bufnr == nil then + bufnr = vim.api.nvim_get_current_buf() + end + + rawset(t, bufnr, v) + end, +} +-- }}} +-- Diagnostic Saving & Caching {{{ +local _diagnostic_cleanup = setmetatable({}, bufnr_and_client_cacher_mt) +local diagnostic_cache = setmetatable({}, bufnr_and_client_cacher_mt) +local diagnostic_cache_lines = setmetatable({}, bufnr_and_client_cacher_mt) +local diagnostic_cache_counts = setmetatable({}, bufnr_and_client_cacher_mt) + +local _bufs_waiting_to_update = setmetatable({}, bufnr_and_client_cacher_mt) + +--- Store Diagnostic[] by line +--- +---@param diagnostics Diagnostic[] +---@return table<number, Diagnostic[]> +local _diagnostic_lines = function(diagnostics) + if not diagnostics then return end + + local diagnostics_by_line = {} + for _, diagnostic in ipairs(diagnostics) do + local start = diagnostic.range.start + local line_diagnostics = diagnostics_by_line[start.line] + if not line_diagnostics then + line_diagnostics = {} + diagnostics_by_line[start.line] = line_diagnostics + end + table.insert(line_diagnostics, diagnostic) + end + return diagnostics_by_line +end + +--- Get the count of M by Severity +--- +---@param diagnostics Diagnostic[] +---@return table<DiagnosticSeverity, number> +local _diagnostic_counts = function(diagnostics) + if not diagnostics then return end + + local counts = {} + for _, diagnostic in pairs(diagnostics) do + if diagnostic.severity then + local val = counts[diagnostic.severity] + if val == nil then + val = 0 + end + + counts[diagnostic.severity] = val + 1 + end + end + + return counts +end + +--@private +--- Set the different diagnostic cache after `textDocument/publishDiagnostics` +---@param diagnostics Diagnostic[] +---@param bufnr number +---@param client_id number +---@return nil +local function set_diagnostic_cache(diagnostics, bufnr, client_id) + client_id = get_client_id(client_id) + + -- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#diagnostic + -- + -- The diagnostic's severity. Can be omitted. If omitted it is up to the + -- client to interpret diagnostics as error, warning, info or hint. + -- TODO: Replace this with server-specific heuristics to infer severity. + for _, diagnostic in ipairs(diagnostics) do + if diagnostic.severity == nil then + diagnostic.severity = DiagnosticSeverity.Error + end + end + + diagnostic_cache[bufnr][client_id] = diagnostics + diagnostic_cache_lines[bufnr][client_id] = _diagnostic_lines(diagnostics) + diagnostic_cache_counts[bufnr][client_id] = _diagnostic_counts(diagnostics) +end + + +--@private +--- Clear the cached diagnostics +---@param bufnr number +---@param client_id number +local function clear_diagnostic_cache(bufnr, client_id) + client_id = get_client_id(client_id) + + diagnostic_cache[bufnr][client_id] = nil + diagnostic_cache_lines[bufnr][client_id] = nil + diagnostic_cache_counts[bufnr][client_id] = nil +end + +--- Save diagnostics to the current buffer. +--- +--- Handles saving diagnostics from multiple clients in the same buffer. +---@param diagnostics Diagnostic[] +---@param bufnr number +---@param client_id number +function M.save(diagnostics, bufnr, client_id) + validate { + diagnostics = {diagnostics, 't'}, + bufnr = {bufnr, 'n'}, + client_id = {client_id, 'n', true}, + } + + if not diagnostics then return end + + bufnr = get_bufnr(bufnr) + client_id = get_client_id(client_id) + + if not _diagnostic_cleanup[bufnr][client_id] then + _diagnostic_cleanup[bufnr][client_id] = true + + -- Clean up our data when the buffer unloads. + api.nvim_buf_attach(bufnr, false, { + on_detach = function(b) + clear_diagnostic_cache(b, client_id) + _diagnostic_cleanup[bufnr][client_id] = nil + end + }) + end + + set_diagnostic_cache(diagnostics, bufnr, client_id) +end +-- }}} +-- Diagnostic Retrieval {{{ + +--- Return associated diagnostics for bufnr +--- +---@param bufnr number +---@param client_id number|nil If nil, then return all of the diagnostics. +--- Else, return just the diagnostics associated with the client_id. +function M.get(bufnr, client_id) + if client_id == nil then + local all_diagnostics = {} + for iter_client_id, _ in pairs(diagnostic_cache[bufnr]) do + local iter_diagnostics = M.get(bufnr, iter_client_id) + + for _, diagnostic in ipairs(iter_diagnostics) do + table.insert(all_diagnostics, diagnostic) + end + end + + return all_diagnostics + end + + return diagnostic_cache[bufnr][client_id] or {} +end + +--- Get the diagnostics by line +--- +---@param bufnr number The buffer number +---@param line_nr number The line number +---@param opts table|nil Configuration keys +--- - severity: (DiagnosticSeverity, default nil) +--- - Only return diagnostics with this severity. Overrides severity_limit +--- - severity_limit: (DiagnosticSeverity, default nil) +--- - Limit severity of diagnostics found. E.g. "Warning" means { "Error", "Warning" } will be valid. +---@param client_id number the client id +---@return table Table with map of line number to list of diagnostics. +-- Structured: { [1] = {...}, [5] = {.... } } +function M.get_line_diagnostics(bufnr, line_nr, opts, client_id) + opts = opts or {} + + bufnr = bufnr or vim.api.nvim_get_current_buf() + line_nr = line_nr or vim.api.nvim_win_get_cursor(0)[1] - 1 + + local client_get_diags = function(iter_client_id) + return (diagnostic_cache_lines[bufnr][iter_client_id] or {})[line_nr] or {} + end + + local line_diagnostics + if client_id == nil then + line_diagnostics = {} + for iter_client_id, _ in pairs(diagnostic_cache_lines[bufnr]) do + for _, diagnostic in ipairs(client_get_diags(iter_client_id)) do + table.insert(line_diagnostics, diagnostic) + end + end + else + line_diagnostics = vim.deepcopy(client_get_diags(client_id)) + end + + if opts.severity then + local filter_level = to_severity(opts.severity) + line_diagnostics = vim.tbl_filter(function(t) return t.severity == filter_level end, line_diagnostics) + elseif opts.severity_limit then + local filter_level = to_severity(opts.severity_limit) + line_diagnostics = vim.tbl_filter(function(t) return t.severity <= filter_level end, line_diagnostics) + end + + if opts.severity_sort then + table.sort(line_diagnostics, function(a, b) return a.severity < b.severity end) + end + + return line_diagnostics +end + +--- Get the counts for a particular severity +--- +--- Useful for showing diagnostic counts in statusline. eg: +--- +--- <pre> +--- function! LspStatus() abort +--- let sl = '' +--- if luaeval('not vim.tbl_isempty(vim.lsp.buf_get_clients(0))') +--- let sl.='%#MyStatuslineLSP#E:' +--- let sl.='%#MyStatuslineLSPErrors#%{luaeval("vim.lsp.diagnostic.get_count([[Error]])")}' +--- let sl.='%#MyStatuslineLSP# W:' +--- let sl.='%#MyStatuslineLSPWarnings#%{luaeval("vim.lsp.diagnostic.get_count([[Warning]])")}' +--- else +--- let sl.='%#MyStatuslineLSPErrors#off' +--- endif +--- return sl +--- endfunction +--- let &l:statusline = '%#MyStatuslineLSP#LSP '.LspStatus() +--- </pre> +--- +---@param bufnr number The buffer number +---@param severity DiagnosticSeverity +---@param client_id number the client id +function M.get_count(bufnr, severity, client_id) + if client_id == nil then + local total = 0 + for iter_client_id, _ in pairs(diagnostic_cache_counts[bufnr]) do + total = total + M.get_count(bufnr, severity, iter_client_id) + end + + return total + end + + return (diagnostic_cache_counts[bufnr][client_id] or {})[DiagnosticSeverity[severity]] or 0 +end + + +-- }}} +-- Diagnostic Movements {{{ + +--- Helper function to iterate through all of the diagnostic lines +---@return table list of diagnostics +local _iter_diagnostic_lines = function(start, finish, step, bufnr, opts, client_id) + if bufnr == nil then + bufnr = vim.api.nvim_get_current_buf() + end + + local wrap = if_nil(opts.wrap, true) + + local search = function(search_start, search_finish, search_step) + for line_nr = search_start, search_finish, search_step do + local line_diagnostics = M.get_line_diagnostics(bufnr, line_nr, opts, client_id) + if line_diagnostics and not vim.tbl_isempty(line_diagnostics) then + return line_diagnostics + end + end + end + + local result = search(start, finish, step) + + if wrap then + local wrap_start, wrap_finish + if step == 1 then + wrap_start, wrap_finish = 1, start + else + wrap_start, wrap_finish = vim.api.nvim_buf_line_count(bufnr), start + end + + if not result then + result = search(wrap_start, wrap_finish, step) + end + end + + return result +end + +--@private +--- Helper function to ierate through diagnostic lines and return a position +--- +---@return table {row, col} +local function _iter_diagnostic_lines_pos(opts, line_diagnostics) + opts = opts or {} + + local win_id = opts.win_id or vim.api.nvim_get_current_win() + local bufnr = vim.api.nvim_win_get_buf(win_id) + + if line_diagnostics == nil or vim.tbl_isempty(line_diagnostics) then + return false + end + + local iter_diagnostic = line_diagnostics[1] + return to_position(iter_diagnostic.range.start, bufnr) +end + +--@private +-- Move to the diagnostic position +local function _iter_diagnostic_move_pos(name, opts, pos) + opts = opts or {} + + local enable_popup = if_nil(opts.enable_popup, true) + local win_id = opts.win_id or vim.api.nvim_get_current_win() + + if not pos then + print(string.format("%s: No more valid diagnostics to move to.", name)) + return + end + + vim.api.nvim_win_set_cursor(win_id, {pos[1] + 1, pos[2]}) + + if enable_popup then + -- This is a bit weird... I'm surprised that we need to wait til the next tick to do this. + vim.schedule(function() + M.show_line_diagnostics(opts.popup_opts, vim.api.nvim_win_get_buf(win_id)) + end) + end +end + +--- Get the previous diagnostic closest to the cursor_position +--- +---@param opts table See |vim.lsp.diagnostics.goto_next()| +---@return table Previous diagnostic +function M.get_prev(opts) + opts = opts or {} + + local win_id = opts.win_id or vim.api.nvim_get_current_win() + local bufnr = vim.api.nvim_win_get_buf(win_id) + local cursor_position = opts.cursor_position or vim.api.nvim_win_get_cursor(win_id) + + return _iter_diagnostic_lines(cursor_position[1] - 2, 0, -1, bufnr, opts, opts.client_id) +end + +--- Return the pos, {row, col}, for the prev diagnostic in the current buffer. +---@param opts table See |vim.lsp.diagnostics.goto_next()| +---@return table Previous diagnostic position +function M.get_prev_pos(opts) + return _iter_diagnostic_lines_pos( + opts, + M.get_prev(opts) + ) +end + +--- Move to the previous diagnostic +---@param opts table See |vim.lsp.diagnostics.goto_next()| +function M.goto_prev(opts) + return _iter_diagnostic_move_pos( + "DiagnosticPrevious", + opts, + M.get_prev_pos(opts) + ) +end + +--- Get the previous diagnostic closest to the cursor_position +---@param opts table See |vim.lsp.diagnostics.goto_next()| +---@return table Next diagnostic +function M.get_next(opts) + opts = opts or {} + + local win_id = opts.win_id or vim.api.nvim_get_current_win() + local bufnr = vim.api.nvim_win_get_buf(win_id) + local cursor_position = opts.cursor_position or vim.api.nvim_win_get_cursor(win_id) + + return _iter_diagnostic_lines(cursor_position[1], vim.api.nvim_buf_line_count(bufnr), 1, bufnr, opts, opts.client_id) +end + +--- Return the pos, {row, col}, for the next diagnostic in the current buffer. +---@param opts table See |vim.lsp.diagnostics.goto_next()| +---@return table Next diagnostic position +function M.get_next_pos(opts) + return _iter_diagnostic_lines_pos( + opts, + M.get_next(opts) + ) +end + +--- Move to the next diagnostic +---@param opts table|nil Configuration table. Keys: +--- - {client_id}: (number) +--- - If nil, will consider all clients attached to buffer. +--- - {cursor_position}: (Position, default current position) +--- - See |nvim_win_get_cursor()| +--- - {wrap}: (boolean, default true) +--- - Whether to loop around file or not. Similar to 'wrapscan' +--- - {severity}: (DiagnosticSeverity) +--- - Exclusive severity to consider. Overrides {severity_limit} +--- - {severity_limit}: (DiagnosticSeverity) +--- - Limit severity of diagnostics found. E.g. "Warning" means { "Error", "Warning" } will be valid. +--- - {enable_popup}: (boolean, default true) +--- - Call |vim.lsp.diagnostic.show_line_diagnostics()| on jump +--- - {popup_opts}: (table) +--- - Table to pass as {opts} parameter to |vim.lsp.diagnostic.show_line_diagnostics()| +--- - {win_id}: (number, default 0) +--- - Window ID +function M.goto_next(opts) + return _iter_diagnostic_move_pos( + "DiagnosticNext", + opts, + M.get_next_pos(opts) + ) +end +-- }}} +-- Diagnostic Setters {{{ + +--- Set signs for given diagnostics +--- +--- Sign characters can be customized with the following commands: +--- +--- <pre> +--- sign define LspDiagnosticsSignError text=E texthl=LspDiagnosticsSignError linehl= numhl= +--- sign define LspDiagnosticsSignWarning text=W texthl=LspDiagnosticsSignWarning linehl= numhl= +--- sign define LspDiagnosticsSignInformation text=I texthl=LspDiagnosticsSignInformation linehl= numhl= +--- sign define LspDiagnosticsSignHint text=H texthl=LspDiagnosticsSignHint linehl= numhl= +--- </pre> +---@param diagnostics Diagnostic[] +---@param bufnr number The buffer number +---@param client_id number the client id +---@param sign_ns number|nil +---@param opts table Configuration for signs. Keys: +--- - priority: Set the priority of the signs. +function M.set_signs(diagnostics, bufnr, client_id, sign_ns, opts) + opts = opts or {} + sign_ns = sign_ns or M._get_sign_namespace(client_id) + + if not diagnostics then + diagnostics = diagnostic_cache[bufnr][client_id] + end + + if not diagnostics then + return + end + + bufnr = get_bufnr(bufnr) + + local ok = true + for _, diagnostic in ipairs(diagnostics) do + ok = ok and pcall(vim.fn.sign_place, + 0, + sign_ns, + sign_highlight_map[diagnostic.severity], + bufnr, + { + priority = opts.priority, + lnum = diagnostic.range.start.line + 1 + } + ) + end + + if not ok then + log.debug("Failed to place signs:", diagnostics) + end +end + +--- Set underline for given diagnostics +--- +--- Underline highlights can be customized by changing the following |:highlight| groups. +--- +--- <pre> +--- LspDiagnosticsUnderlineError +--- LspDiagnosticsUnderlineWarning +--- LspDiagnosticsUnderlineInformation +--- LspDiagnosticsUnderlineHint +--- </pre> +--- +---@param diagnostics Diagnostic[] +---@param bufnr number The buffer number +---@param client_id number the client id +---@param diagnostic_ns number|nil +---@param opts table Currently unused. +function M.set_underline(diagnostics, bufnr, client_id, diagnostic_ns, opts) + opts = opts or {} + assert(opts) -- lint + + diagnostic_ns = diagnostic_ns or M._get_diagnostic_namespace(client_id) + + for _, diagnostic in ipairs(diagnostics) do + local start = diagnostic.range["start"] + local finish = diagnostic.range["end"] + local higroup = underline_highlight_map[diagnostic.severity] + + if higroup == nil then + -- Default to error if we don't have a highlight associated + higroup = underline_highlight_map[DiagnosticSeverity.Error] + end + + highlight.range( + bufnr, + diagnostic_ns, + higroup, + to_position(start, bufnr), + to_position(finish, bufnr) + ) + end +end + +-- Virtual Text {{{ +--- Set virtual text given diagnostics +--- +--- Virtual text highlights can be customized by changing the following |:highlight| groups. +--- +--- <pre> +--- LspDiagnosticsVirtualTextError +--- LspDiagnosticsVirtualTextWarning +--- LspDiagnosticsVirtualTextInformation +--- LspDiagnosticsVirtualTextHint +--- </pre> +--- +---@param diagnostics Diagnostic[] +---@param bufnr number +---@param client_id number +---@param diagnostic_ns number +---@param opts table Options on how to display virtual text. Keys: +--- - prefix (string): Prefix to display before virtual text on line +--- - spacing (number): Number of spaces to insert before virtual text +function M.set_virtual_text(diagnostics, bufnr, client_id, diagnostic_ns, opts) + opts = opts or {} + + client_id = get_client_id(client_id) + diagnostic_ns = diagnostic_ns or M._get_diagnostic_namespace(client_id) + + local buffer_line_diagnostics + if diagnostics then + buffer_line_diagnostics = _diagnostic_lines(diagnostics) + else + buffer_line_diagnostics = diagnostic_cache_lines[bufnr][client_id] + end + + if not buffer_line_diagnostics then + return nil + end + + for line, line_diagnostics in pairs(buffer_line_diagnostics) do + local virt_texts = M.get_virtual_text_chunks_for_line(bufnr, line, line_diagnostics, opts) + + if virt_texts then + api.nvim_buf_set_virtual_text(bufnr, diagnostic_ns, line, virt_texts, {}) + end + end +end + +--- Default function to get text chunks to display using `nvim_buf_set_virtual_text`. +---@param bufnr number The buffer to display the virtual text in +---@param line number The line number to display the virtual text on +---@param line_diags Diagnostic[] The diagnostics associated with the line +---@param opts table See {opts} from |vim.lsp.diagnostic.set_virtual_text()| +---@return table chunks, as defined by |nvim_buf_set_virtual_text()| +function M.get_virtual_text_chunks_for_line(bufnr, line, line_diags, opts) + assert(bufnr or line) + + if #line_diags == 0 then + return nil + end + + opts = opts or {} + local prefix = opts.prefix or "โ " + local spacing = opts.spacing or 4 + + -- Create a little more space between virtual text and contents + local virt_texts = {{string.rep(" ", spacing)}} + + for i = 1, #line_diags - 1 do + table.insert(virt_texts, {prefix, virtual_text_highlight_map[line_diags[i].severity]}) + end + local last = line_diags[#line_diags] + + -- TODO(tjdevries): Allow different servers to be shown first somehow? + -- TODO(tjdevries): Display server name associated with these? + if last.message then + table.insert( + virt_texts, + { + string.format("%s %s", prefix, last.message:gsub("\r", ""):gsub("\n", " ")), + virtual_text_highlight_map[last.severity] + } + ) + + return virt_texts + end +end +-- }}} +-- }}} +-- Diagnostic Clear {{{ +--- Clears the currently displayed diagnostics +---@param bufnr number The buffer number +---@param client_id number the client id +---@param diagnostic_ns number|nil Associated diagnostic namespace +---@param sign_ns number|nil Associated sign namespace +function M.clear(bufnr, client_id, diagnostic_ns, sign_ns) + validate { bufnr = { bufnr, 'n' } } + + bufnr = (bufnr == 0 and api.nvim_get_current_buf()) or bufnr + + if client_id == nil then + return vim.lsp.for_each_buffer_client(bufnr, function(_, iter_client_id, _) + return M.clear(bufnr, iter_client_id) + end) + end + + diagnostic_ns = diagnostic_ns or M._get_diagnostic_namespace(client_id) + sign_ns = sign_ns or M._get_sign_namespace(client_id) + + assert(bufnr, "bufnr is required") + assert(diagnostic_ns, "Need diagnostic_ns, got nil") + assert(sign_ns, string.format("Need sign_ns, got nil %s", sign_ns)) + + -- clear sign group + vim.fn.sign_unplace(sign_ns, {buffer=bufnr}) + + -- clear virtual text namespace + api.nvim_buf_clear_namespace(bufnr, diagnostic_ns, 0, -1) +end +-- }}} +-- Diagnostic Insert Leave Handler {{{ + +--- Callback scheduled for after leaving insert mode +--- +--- Used to handle +--@private +function M._execute_scheduled_display(bufnr, client_id) + local args = _bufs_waiting_to_update[bufnr][client_id] + if not args then + return + end + + -- Clear the args so we don't display unnecessarily. + _bufs_waiting_to_update[bufnr][client_id] = nil + + M.display(nil, bufnr, client_id, args) +end + +local registered = {} + +local make_augroup_key = function(bufnr, client_id) + return string.format("LspDiagnosticInsertLeave:%s:%s", bufnr, client_id) +end + +--- Table of autocmd events to fire the update for displaying new diagnostic information +M.insert_leave_auto_cmds = { "InsertLeave", "CursorHoldI" } + +--- Used to schedule diagnostic updates upon leaving insert mode. +--- +--- For parameter description, see |M.display()| +function M._schedule_display(bufnr, client_id, args) + _bufs_waiting_to_update[bufnr][client_id] = args + + local key = make_augroup_key(bufnr, client_id) + if not registered[key] then + vim.cmd(string.format("augroup %s", key)) + vim.cmd(" au!") + vim.cmd( + string.format( + [[autocmd %s <buffer=%s> :lua vim.lsp.diagnostic._execute_scheduled_display(%s, %s)]], + table.concat(M.insert_leave_auto_cmds, ","), + bufnr, + bufnr, + client_id + ) + ) + vim.cmd("augroup END") + + registered[key] = true + end +end + + +--- Used in tandem with +--- +--- For parameter description, see |M.display()| +function M._clear_scheduled_display(bufnr, client_id) + local key = make_augroup_key(bufnr, client_id) + + if registered[key] then + vim.cmd(string.format("augroup %s", key)) + vim.cmd(" au!") + vim.cmd("augroup END") + + registered[key] = nil + end +end +-- }}} + +-- Diagnostic Private Highlight Utilies {{{ +--- Get the severity highlight name +--@private +function M._get_severity_highlight_name(severity) + return virtual_text_highlight_map[severity] +end + +--- Get floating severity highlight name +--@private +function M._get_floating_severity_highlight_name(severity) + return floating_highlight_map[severity] +end + +--- This should be called to update the highlights for the LSP client. +function M._define_default_signs_and_highlights() + --@private + local function define_default_sign(name, properties) + if vim.tbl_isempty(vim.fn.sign_getdefined(name)) then + vim.fn.sign_define(name, properties) + end + end + + -- Initialize default diagnostic highlights + for severity, hi_info in pairs(diagnostic_severities) do + local default_highlight_name = default_highlight_map[severity] + highlight.create(default_highlight_name, hi_info, true) + + -- Default link all corresponding highlights to the default highlight + highlight.link(virtual_text_highlight_map[severity], default_highlight_name, false) + highlight.link(floating_highlight_map[severity], default_highlight_name, false) + highlight.link(sign_highlight_map[severity], default_highlight_name, false) + end + + -- Create all signs + for severity, sign_hl_name in pairs(sign_highlight_map) do + local severity_name = DiagnosticSeverity[severity] + + define_default_sign(sign_hl_name, { + text = (severity_name or 'U'):sub(1, 1), + texthl = sign_hl_name, + linehl = '', + numhl = '', + }) + end + + -- Initialize Underline highlights + for severity, underline_highlight_name in pairs(underline_highlight_map) do + highlight.create(underline_highlight_name, { + cterm = 'underline', + gui = 'underline', + guisp = diagnostic_severities[severity].guifg + }, true) + end +end +-- }}} +-- Diagnostic Display {{{ + +--- |lsp-handler| for the method "textDocument/publishDiagnostics" +--- +---@note Each of the configuration options accepts: +--- - `false`: Disable this feature +--- - `true`: Enable this feature, use default settings. +--- - `table`: Enable this feature, use overrides. +--- - `function`: Function with signature (bufnr, client_id) that returns any of the above. +--- <pre> +--- 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(bufnr, client_id) +--- return vim.bo[bufnr].show_signs == false +--- end, +--- -- Disable a feature +--- update_in_insert = false, +--- } +--- ) +--- </pre> +--- +---@param config table Configuration table. +--- - underline: (default=true) +--- - Apply underlines to diagnostics. +--- - See |vim.lsp.diagnostic.set_underline()| +--- - virtual_text: (default=true) +--- - Apply virtual text to line endings. +--- - See |vim.lsp.diagnostic.set_virtual_text()| +--- - signs: (default=true) +--- - Apply signs for diagnostics. +--- - See |vim.lsp.diagnostic.set_signs()| +--- - update_in_insert: (default=false) +--- - Update diagnostics in InsertMode or wait until InsertLeave +function M.on_publish_diagnostics(_, _, params, client_id, _, config) + local uri = params.uri + local bufnr = vim.uri_to_bufnr(uri) + + if not bufnr then + return + end + + local diagnostics = params.diagnostics + + -- Always save the diagnostics, even if the buf is not loaded. + -- Language servers may report compile or build errors via diagnostics + -- Users should be able to find these, even if they're in files which + -- are not loaded. + M.save(diagnostics, bufnr, client_id) + + -- Unloaded buffers should not handle diagnostics. + -- When the buffer is loaded, we'll call on_attach, which sends textDocument/didOpen. + -- This should trigger another publish of the diagnostics. + -- + -- In particular, this stops a ton of spam when first starting a server for current + -- unloaded buffers. + if not api.nvim_buf_is_loaded(bufnr) then + return + end + + M.display(diagnostics, bufnr, client_id, config) +end + +--@private +--- Display diagnostics for the buffer, given a configuration. +function M.display(diagnostics, bufnr, client_id, config) + config = vim.lsp._with_extend('vim.lsp.diagnostic.on_publish_diagnostics', { + signs = true, + underline = true, + virtual_text = true, + update_in_insert = false, + }, config) + + if diagnostics == nil then + diagnostics = M.get(bufnr, client_id) + end + + -- TODO(tjdevries): Consider how we can make this a "standardized" kind of thing for |lsp-handlers|. + -- It seems like we would probably want to do this more often as we expose more of them. + -- It provides a very nice functional interface for people to override configuration. + local resolve_optional_value = function(option) + local enabled_val = {} + + if not option then + return false + elseif option == true then + return enabled_val + elseif type(option) == 'function' then + local val = option(bufnr, client_id) + if val == true then + return enabled_val + else + return val + end + elseif type(option) == 'table' then + return option + else + error("Unexpected option type: " .. vim.inspect(option)) + end + end + + if resolve_optional_value(config.update_in_insert) then + M._clear_scheduled_display(bufnr, client_id) + else + local mode = vim.api.nvim_get_mode() + + if string.sub(mode.mode, 1, 1) == 'i' then + M._schedule_display(bufnr, client_id, config) + return + end + end + + M.clear(bufnr, client_id) + + diagnostics = diagnostics or diagnostic_cache[bufnr][client_id] + + if not diagnostics or vim.tbl_isempty(diagnostics) then + return + end + + local underline_opts = resolve_optional_value(config.underline) + if underline_opts then + M.set_underline(diagnostics, bufnr, client_id, nil, underline_opts) + end + + local virtual_text_opts = resolve_optional_value(config.virtual_text) + if virtual_text_opts then + M.set_virtual_text(diagnostics, bufnr, client_id, nil, virtual_text_opts) + end + + local signs_opts = resolve_optional_value(config.signs) + if signs_opts then + M.set_signs(diagnostics, bufnr, client_id, nil, signs_opts) + end + + vim.api.nvim_command("doautocmd User LspDiagnosticsChanged") +end +-- }}} +-- Diagnostic User Functions {{{ + +--- Open a floating window with the diagnostics from {line_nr} +--- +--- The floating window can be customized with the following highlight groups: +--- <pre> +--- LspDiagnosticsFloatingError +--- LspDiagnosticsFloatingWarning +--- LspDiagnosticsFloatingInformation +--- LspDiagnosticsFloatingHint +--- </pre> +---@param opts table Configuration table +--- - show_header (boolean, default true): Show "Diagnostics:" header. +---@param bufnr number The buffer number +---@param line_nr number The line number +---@param client_id number|nil the client id +---@return {popup_bufnr, win_id} +function M.show_line_diagnostics(opts, bufnr, line_nr, client_id) + opts = opts or {} + opts.severity_sort = if_nil(opts.severity_sort, true) + + local show_header = if_nil(opts.show_header, true) + + bufnr = bufnr or 0 + line_nr = line_nr or (vim.api.nvim_win_get_cursor(0)[1] - 1) + + local lines = {} + local highlights = {} + if show_header then + table.insert(lines, "Diagnostics:") + table.insert(highlights, {0, "Bold"}) + end + + local line_diagnostics = M.get_line_diagnostics(bufnr, line_nr, opts, client_id) + if vim.tbl_isempty(line_diagnostics) then return end + + for i, diagnostic in ipairs(line_diagnostics) do + local prefix = string.format("%d. ", i) + local hiname = M._get_floating_severity_highlight_name(diagnostic.severity) + assert(hiname, 'unknown severity: ' .. tostring(diagnostic.severity)) + + local message_lines = vim.split(diagnostic.message, '\n', true) + table.insert(lines, prefix..message_lines[1]) + table.insert(highlights, {#prefix + 1, hiname}) + for j = 2, #message_lines do + table.insert(lines, message_lines[j]) + table.insert(highlights, {0, hiname}) + end + end + + local popup_bufnr, winnr = util.open_floating_preview(lines, 'plaintext') + for i, hi in ipairs(highlights) do + local prefixlen, hiname = unpack(hi) + -- Start highlight after the prefix + api.nvim_buf_add_highlight(popup_bufnr, -1, hiname, i-1, prefixlen, -1) + end + + return popup_bufnr, winnr +end + +local loclist_type_map = { + [DiagnosticSeverity.Error] = 'E', + [DiagnosticSeverity.Warning] = 'W', + [DiagnosticSeverity.Information] = 'I', + [DiagnosticSeverity.Hint] = 'I', +} + +--- Sets the location list +---@param opts table|nil Configuration table. Keys: +--- - {open_loclist}: (boolean, default true) +--- - Open loclist after set +--- - {client_id}: (number) +--- - If nil, will consider all clients attached to buffer. +--- - {severity}: (DiagnosticSeverity) +--- - Exclusive severity to consider. Overrides {severity_limit} +--- - {severity_limit}: (DiagnosticSeverity) +--- - Limit severity of diagnostics found. E.g. "Warning" means { "Error", "Warning" } will be valid. +function M.set_loclist(opts) + opts = opts or {} + + local open_loclist = if_nil(opts.open_loclist, true) + + local bufnr = vim.api.nvim_get_current_buf() + local buffer_diags = M.get(bufnr, opts.client_id) + + local severity = to_severity(opts.severity) + local severity_limit = to_severity(opts.severity_limit) + + local items = {} + local insert_diag = function(diag) + if severity then + -- Handle missing severities + if not diag.severity then + return + end + + if severity ~= diag.severity then + return + end + elseif severity_limit then + if not diag.severity then + return + end + + if severity_limit < diag.severity then + return + end + end + + local pos = diag.range.start + local row = pos.line + local col = util.character_offset(bufnr, row, pos.character) + + local line = (api.nvim_buf_get_lines(bufnr, row, row + 1, false) or {""})[1] + + table.insert(items, { + bufnr = bufnr, + lnum = row + 1, + col = col + 1, + text = line .. " | " .. diag.message, + type = loclist_type_map[diag.severity or DiagnosticSeverity.Error] or 'E', + }) + end + + for _, diag in ipairs(buffer_diags) do + insert_diag(diag) + end + + table.sort(items, function(a, b) return a.lnum < b.lnum end) + + util.set_loclist(items) + if open_loclist then + vim.cmd [[lopen]] + end +end +-- }}} + +return M diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua new file mode 100644 index 0000000000..e034923afb --- /dev/null +++ b/runtime/lua/vim/lsp/handlers.lua @@ -0,0 +1,310 @@ +local log = require 'vim.lsp.log' +local protocol = require 'vim.lsp.protocol' +local util = require 'vim.lsp.util' +local vim = vim +local api = vim.api +local buf = require 'vim.lsp.buf' + +local M = {} + +-- FIXME: DOC: Expose in vimdocs + +--@private +--- Writes to error buffer. +--@param ... (table of strings) Will be concatenated before being written +local function err_message(...) + api.nvim_err_writeln(table.concat(vim.tbl_flatten{...})) + api.nvim_command("redraw") +end + +--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_executeCommand +M['workspace/executeCommand'] = function(err, _) + if err then + error("Could not execute code action: "..err.message) + end +end + +--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_codeAction +M['textDocument/codeAction'] = function(_, _, actions) + if actions == nil or vim.tbl_isempty(actions) then + print("No code actions available") + return + end + + local option_strings = {"Code Actions:"} + for i, action in ipairs(actions) do + local title = action.title:gsub('\r\n', '\\r\\n') + title = title:gsub('\n', '\\n') + table.insert(option_strings, string.format("%d. %s", i, title)) + end + + local choice = vim.fn.inputlist(option_strings) + if choice < 1 or choice > #actions then + return + end + local action_chosen = actions[choice] + -- textDocument/codeAction can return either Command[] or CodeAction[]. + -- If it is a CodeAction, it can have either an edit, a command or both. + -- Edits should be executed first + if action_chosen.edit or type(action_chosen.command) == "table" then + if action_chosen.edit then + util.apply_workspace_edit(action_chosen.edit) + end + if type(action_chosen.command) == "table" then + buf.execute_command(action_chosen.command) + end + else + buf.execute_command(action_chosen) + end +end + +--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_applyEdit +M['workspace/applyEdit'] = function(_, _, workspace_edit) + if not workspace_edit then return end + -- TODO(ashkan) Do something more with label? + if workspace_edit.label then + print("Workspace edit", workspace_edit.label) + end + local status, result = pcall(util.apply_workspace_edit, workspace_edit.edit) + return { + applied = status; + failureReason = result; + } +end + +M['textDocument/publishDiagnostics'] = function(...) + return require('vim.lsp.diagnostic').on_publish_diagnostics(...) +end + +--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_references +M['textDocument/references'] = function(_, _, result) + if not result then return end + util.set_qflist(util.locations_to_items(result)) + api.nvim_command("copen") + api.nvim_command("wincmd p") +end + +--@private +--- Prints given list of symbols to the quickfix list. +--@param _ (not used) +--@param _ (not used) +--@param result (list of Symbols) LSP method name +--@param result (table) result of LSP method; a location or a list of locations. +---(`textDocument/definition` can return `Location` or `Location[]` +local symbol_handler = function(_, _, result, _, bufnr) + if not result or vim.tbl_isempty(result) then return end + + util.set_qflist(util.symbols_to_items(result, bufnr)) + api.nvim_command("copen") + api.nvim_command("wincmd p") +end +--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_documentSymbol +M['textDocument/documentSymbol'] = symbol_handler +--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_symbol +M['workspace/symbol'] = symbol_handler + +--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_rename +M['textDocument/rename'] = function(_, _, result) + if not result then return end + util.apply_workspace_edit(result) +end + +--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_rangeFormatting +M['textDocument/rangeFormatting'] = function(_, _, result) + if not result then return end + util.apply_text_edits(result) +end + +--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_formatting +M['textDocument/formatting'] = function(_, _, result) + if not result then return end + util.apply_text_edits(result) +end + +--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion +M['textDocument/completion'] = function(_, _, result) + if vim.tbl_isempty(result or {}) then return end + local row, col = unpack(api.nvim_win_get_cursor(0)) + local line = assert(api.nvim_buf_get_lines(0, row-1, row, false)[1]) + local line_to_cursor = line:sub(col+1) + local textMatch = vim.fn.match(line_to_cursor, '\\k*$') + local prefix = line_to_cursor:sub(textMatch+1) + + local matches = util.text_document_completion_list_to_complete_items(result, prefix) + vim.fn.complete(textMatch+1, matches) +end + +--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_hover +M['textDocument/hover'] = function(_, method, result) + util.focusable_float(method, function() + if not (result and result.contents) then + -- return { 'No information available' } + return + end + local markdown_lines = util.convert_input_to_markdown_lines(result.contents) + markdown_lines = util.trim_empty_lines(markdown_lines) + if vim.tbl_isempty(markdown_lines) then + -- return { 'No information available' } + return + end + local bufnr, winnr = util.fancy_floating_markdown(markdown_lines, { + pad_left = 1; pad_right = 1; + }) + util.close_preview_autocmd({"CursorMoved", "BufHidden", "InsertCharPre"}, winnr) + return bufnr, winnr + end) +end + +--@private +--- Jumps to a location. Used as a handler for multiple LSP methods. +--@param _ (not used) +--@param method (string) LSP method name +--@param result (table) result of LSP method; a location or a list of locations. +---(`textDocument/definition` can return `Location` or `Location[]` +local function location_handler(_, method, result) + if result == nil or vim.tbl_isempty(result) then + local _ = log.info() and log.info(method, 'No location found') + return nil + end + + -- textDocument/definition can return Location or Location[] + -- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_definition + + if vim.tbl_islist(result) then + util.jump_to_location(result[1]) + + if #result > 1 then + util.set_qflist(util.locations_to_items(result)) + api.nvim_command("copen") + api.nvim_command("wincmd p") + end + else + util.jump_to_location(result) + end +end + +--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_declaration +M['textDocument/declaration'] = location_handler +--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_definition +M['textDocument/definition'] = location_handler +--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_typeDefinition +M['textDocument/typeDefinition'] = location_handler +--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_implementation +M['textDocument/implementation'] = location_handler + +--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_signatureHelp +M['textDocument/signatureHelp'] = function(_, method, result) + -- When use `autocmd CompleteDone <silent><buffer> lua vim.lsp.buf.signature_help()` to call signatureHelp handler + -- If the completion item doesn't have signatures It will make noise. Change to use `print` that can use `<silent>` to ignore + if not (result and result.signatures and result.signatures[1]) then + print('No signature help available') + return + end + local lines = util.convert_signature_help_to_markdown_lines(result) + lines = util.trim_empty_lines(lines) + if vim.tbl_isempty(lines) then + print('No signature help available') + return + end + util.focusable_preview(method, function() + return lines, util.try_trim_markdown_code_blocks(lines) + end) +end + +--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_documentHighlight +M['textDocument/documentHighlight'] = function(_, _, result, _) + if not result then return end + local bufnr = api.nvim_get_current_buf() + util.buf_highlight_references(bufnr, result) +end + +--@private +--- +--- Displays call hierarchy in the quickfix window. +--- +--@param direction `"from"` for incoming calls and `"to"` for outgoing calls +--@returns `CallHierarchyIncomingCall[]` if {direction} is `"from"`, +--@returns `CallHierarchyOutgoingCall[]` if {direction} is `"to"`, +local make_call_hierarchy_handler = function(direction) + return function(_, _, result) + if not result then return end + local items = {} + for _, call_hierarchy_call in pairs(result) do + local call_hierarchy_item = call_hierarchy_call[direction] + for _, range in pairs(call_hierarchy_call.fromRanges) do + table.insert(items, { + filename = assert(vim.uri_to_fname(call_hierarchy_item.uri)), + text = call_hierarchy_item.name, + lnum = range.start.line + 1, + col = range.start.character + 1, + }) + end + end + util.set_qflist(items) + api.nvim_command("copen") + api.nvim_command("wincmd p") + end +end + +--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#callHierarchy/incomingCalls +M['callHierarchy/incomingCalls'] = make_call_hierarchy_handler('from') + +--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#callHierarchy/outgoingCalls +M['callHierarchy/outgoingCalls'] = make_call_hierarchy_handler('to') + +--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window/logMessage +M['window/logMessage'] = function(_, _, result, client_id) + local message_type = result.type + local message = result.message + local client = vim.lsp.get_client_by_id(client_id) + local client_name = client and client.name or string.format("id=%d", client_id) + if not client then + err_message("LSP[", client_name, "] client has shut down after sending the message") + end + if message_type == protocol.MessageType.Error then + log.error(message) + elseif message_type == protocol.MessageType.Warning then + log.warn(message) + elseif message_type == protocol.MessageType.Info then + log.info(message) + else + log.debug(message) + end + return result +end + +--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window/showMessage +M['window/showMessage'] = function(_, _, result, client_id) + local message_type = result.type + local message = result.message + local client = vim.lsp.get_client_by_id(client_id) + local client_name = client and client.name or string.format("id=%d", client_id) + if not client then + err_message("LSP[", client_name, "] client has shut down after sending the message") + end + if message_type == protocol.MessageType.Error then + err_message("LSP[", client_name, "] ", message) + else + local message_type_name = protocol.MessageType[message_type] + api.nvim_out_write(string.format("LSP[%s][%s] %s\n", client_name, message_type_name, message)) + end + return result +end + +-- Add boilerplate error validation and logging for all of these. +for k, fn in pairs(M) do + M[k] = function(err, method, params, client_id, bufnr, config) + local _ = log.debug() and log.debug('default_handler', method, { + params = params, client_id = client_id, err = err, bufnr = bufnr, config = config + }) + + if err then + error(tostring(err)) + end + + return fn(err, method, params, client_id, bufnr, config) + end +end + +return M +-- vim:sw=2 ts=2 et diff --git a/runtime/lua/vim/lsp/log.lua b/runtime/lua/vim/lsp/log.lua index 696ce43a59..587a65cd96 100644 --- a/runtime/lua/vim/lsp/log.lua +++ b/runtime/lua/vim/lsp/log.lua @@ -2,6 +2,9 @@ local log = {} +-- FIXME: DOC +-- Should be exposed in the vim docs. +-- -- Log level dictionary with reverse lookup as well. -- -- Can be used to lookup the number from the name or the name from the number. @@ -21,12 +24,14 @@ local log_date_format = "%FT%H:%M:%S%z" do local path_sep = vim.loop.os_uname().sysname == "Windows" and "\\" or "/" + --@private local function path_join(...) return table.concat(vim.tbl_flatten{...}, path_sep) end local logfilename = path_join(vim.fn.stdpath('data'), 'lsp.log') - --- Return the log filename. + --- Returns the log filename. + --@returns (string) log filename function log.get_filename() return logfilename end @@ -36,6 +41,9 @@ do for level, levelnr in pairs(log.levels) do -- Also export the log level on the root object. log[level] = levelnr + -- FIXME: DOC + -- Should be exposed in the vim docs. + -- -- Set the lowercase name as the main use function. -- If called without arguments, it will check whether the log level is -- greater than or equal to this one. When called with arguments, it will @@ -74,6 +82,8 @@ end -- interfere with iterating the levels vim.tbl_add_reverse_lookup(log.levels) +--- Sets the current log level. +--@param level (string or number) One of `vim.lsp.log.levels` function log.set_level(level) if type(level) == 'string' then current_log_level = assert(log.levels[level:upper()], string.format("Invalid log level: %q", level)) @@ -84,8 +94,9 @@ function log.set_level(level) end end --- Return whether the level is sufficient for logging. --- @param level number log level +--- Checks whether the level is sufficient for logging. +--@param level number log level +--@returns (bool) true if would log, false if not function log.should_log(level) return level >= current_log_level end diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua index ef5e08680e..07b4e8b926 100644 --- a/runtime/lua/vim/lsp/protocol.lua +++ b/runtime/lua/vim/lsp/protocol.lua @@ -1,20 +1,18 @@ -- Protocol for the Microsoft Language Server Protocol (mslsp) -local protocol = {} - -local function ifnil(a, b) - if a == nil then return b end - return a -end +local if_nil = vim.F.if_nil +local protocol = {} --[=[ --- Useful for interfacing with: --- https://github.com/microsoft/language-server-protocol/raw/gh-pages/_specifications/specification-3-14.md +--@private +--- Useful for interfacing with: +--- https://github.com/microsoft/language-server-protocol/raw/gh-pages/_specifications/specification-3-14.md function transform_schema_comments() nvim.command [[silent! '<,'>g/\/\*\*\|\*\/\|^$/d]] nvim.command [[silent! '<,'>s/^\(\s*\) \* \=\(.*\)/\1--\2/]] end +--@private function transform_schema_to_table() transform_schema_comments() nvim.command [[silent! '<,'>s/: \S\+//]] @@ -625,15 +623,18 @@ function protocol.make_client_capabilities() codeActionLiteralSupport = { codeActionKind = { - valueSet = {}; + valueSet = vim.tbl_values(protocol.CodeActionKind); }; }; }; completion = { dynamicRegistration = false; completionItem = { + -- Until we can actually expand snippet, move cursor and allow for true snippet experience, + -- this should be disabled out of the box. + -- However, users can turn this back on if they have a snippet plugin. + snippetSupport = false; - snippetSupport = true; commitCharactersSupport = false; preselectSupport = false; deprecatedSupport = false; @@ -696,6 +697,10 @@ function protocol.make_client_capabilities() }; hierarchicalDocumentSymbolSupport = true; }; + rename = { + dynamicRegistration = false; + prepareSupport = true; + }; }; workspace = { symbol = { @@ -895,18 +900,19 @@ function protocol.resolve_capabilities(server_capabilities) } elseif type(textDocumentSync) == 'table' then text_document_sync_properties = { - text_document_open_close = ifnil(textDocumentSync.openClose, false); - text_document_did_change = ifnil(textDocumentSync.change, TextDocumentSyncKind.None); - text_document_will_save = ifnil(textDocumentSync.willSave, false); - text_document_will_save_wait_until = ifnil(textDocumentSync.willSaveWaitUntil, false); - text_document_save = ifnil(textDocumentSync.save, false); - text_document_save_include_text = ifnil(type(textDocumentSync.save) == 'table' + text_document_open_close = if_nil(textDocumentSync.openClose, false); + text_document_did_change = if_nil(textDocumentSync.change, TextDocumentSyncKind.None); + text_document_will_save = if_nil(textDocumentSync.willSave, false); + text_document_will_save_wait_until = if_nil(textDocumentSync.willSaveWaitUntil, false); + text_document_save = if_nil(textDocumentSync.save, false); + text_document_save_include_text = if_nil(type(textDocumentSync.save) == 'table' and textDocumentSync.save.includeText, false); } else return nil, string.format("Invalid type for textDocumentSync: %q", type(textDocumentSync)) end end + general_properties.completion = server_capabilities.completionProvider ~= nil general_properties.hover = server_capabilities.hoverProvider or false general_properties.goto_definition = server_capabilities.definitionProvider or false general_properties.find_references = server_capabilities.referencesProvider or false @@ -916,14 +922,21 @@ function protocol.resolve_capabilities(server_capabilities) general_properties.document_formatting = server_capabilities.documentFormattingProvider or false general_properties.document_range_formatting = server_capabilities.documentRangeFormattingProvider or false general_properties.call_hierarchy = server_capabilities.callHierarchyProvider or false + general_properties.execute_command = server_capabilities.executeCommandProvider ~= nil + + if server_capabilities.renameProvider == nil then + general_properties.rename = false + elseif type(server_capabilities.renameProvider) == 'boolean' then + general_properties.rename = server_capabilities.renameProvider + else + general_properties.rename = true + end if server_capabilities.codeActionProvider == nil then general_properties.code_action = false - elseif type(server_capabilities.codeActionProvider) == 'boolean' then + elseif type(server_capabilities.codeActionProvider) == 'boolean' + or type(server_capabilities.codeActionProvider) == 'table' then general_properties.code_action = server_capabilities.codeActionProvider - elseif type(server_capabilities.codeActionProvider) == 'table' then - -- TODO(ashkan) support CodeActionKind - general_properties.code_action = false else error("The server sent invalid codeActionProvider") end diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua index 81c92bfe05..bbcc8ea6f9 100644 --- a/runtime/lua/vim/lsp/rpc.lua +++ b/runtime/lua/vim/lsp/rpc.lua @@ -5,6 +5,11 @@ local protocol = require('vim.lsp.protocol') local validate, schedule, schedule_wrap = vim.validate, vim.schedule, vim.schedule_wrap -- TODO replace with a better implementation. +--@private +--- Encodes to JSON. +--- +--@param data (table) Data to encode +--@returns (string) Encoded object local function json_encode(data) local status, result = pcall(vim.fn.json_encode, data) if status then @@ -13,6 +18,11 @@ local function json_encode(data) return nil, result end end +--@private +--- Decodes from JSON. +--- +--@param data (string) Data to decode +--@returns (table) Decoded JSON object local function json_decode(data) local status, result = pcall(vim.fn.json_decode, data) if status then @@ -22,17 +32,41 @@ local function json_decode(data) end end +--@private +--- Checks whether a given path exists and is a directory. +--@param filename (string) path to check +--@returns (bool) local function is_dir(filename) local stat = vim.loop.fs_stat(filename) return stat and stat.type == 'directory' or false end local NIL = vim.NIL -local function convert_NIL(v) - if v == NIL then return nil end + +--@private +local recursive_convert_NIL +recursive_convert_NIL = function(v, tbl_processed) + if v == NIL then + return nil + elseif not tbl_processed[v] and type(v) == 'table' then + tbl_processed[v] = true + return vim.tbl_map(function(x) + return recursive_convert_NIL(x, tbl_processed) + end, v) + end + return v end +--@private +--- Returns its argument, but converts `vim.NIL` to Lua `nil`. +--@param v (any) Argument +--@returns (any) +local function convert_NIL(v) + return recursive_convert_NIL(v, {}) +end + +--@private --- Merges current process env with the given env and returns the result as --- a list of "k=v" strings. --- @@ -42,6 +76,8 @@ end --- in: { PRODUCTION="false", PATH="/usr/bin/", PORT=123, HOST="0.0.0.0", } --- out: { "PRODUCTION=false", "PATH=/usr/bin/", "PORT=123", "HOST=0.0.0.0", } --- </pre> +--@param env (table) table of environment variable assignments +--@returns (table) list of `"k=v"` strings local function env_merge(env) if env == nil then return env @@ -56,6 +92,11 @@ local function env_merge(env) return final_env end +--@private +--- Embeds the given string into a table and correctly computes `Content-Length`. +--- +--@param encoded_message (string) +--@returns (table) table containing encoded message and `Content-Length` attribute local function format_message_with_content_length(encoded_message) return table.concat { 'Content-Length: '; tostring(#encoded_message); '\r\n\r\n'; @@ -63,8 +104,11 @@ local function format_message_with_content_length(encoded_message) } end ---- Parse an LSP Message's header --- @param header: The header to parse. +--@private +--- Parses an LSP Message's header +--- +--@param header: The header to parse. +--@returns Parsed headers local function parse_headers(header) if type(header) ~= 'string' then return nil @@ -92,6 +136,8 @@ end -- case insensitive pattern. local header_start_pattern = ("content"):gsub("%w", function(c) return "["..c..c:upper().."]" end) +--@private +--- The actual workhorse. local function request_parser_loop() local buffer = '' while true do @@ -138,6 +184,10 @@ local client_errors = vim.tbl_add_reverse_lookup { SERVER_RESULT_CALLBACK_ERROR = 7; } +--- Constructs an error message from an LSP error object. +--- +--@param err (table) The error object +--@returns (string) The formatted error message local function format_rpc_error(err) validate { err = { err, 't' }; @@ -181,56 +231,103 @@ local function rpc_response_error(code, message, data) }) end -local default_handlers = {} -function default_handlers.notification(method, params) +local default_dispatchers = {} + +--@private +--- Default dispatcher for notifications sent to an LSP server. +--- +--@param method (string) The invoked LSP method +--@param params (table): Parameters for the invoked LSP method +function default_dispatchers.notification(method, params) local _ = log.debug() and log.debug('notification', method, params) end -function default_handlers.server_request(method, params) +--@private +--- Default dispatcher for requests sent to an LSP server. +--- +--@param method (string) The invoked LSP method +--@param params (table): Parameters for the invoked LSP method +--@returns `nil` and `vim.lsp.protocol.ErrorCodes.MethodNotFound`. +function default_dispatchers.server_request(method, params) local _ = log.debug() and log.debug('server_request', method, params) return nil, rpc_response_error(protocol.ErrorCodes.MethodNotFound) end -function default_handlers.on_exit(code, signal) - local _ = log.info() and log.info("client exit", { code = code, signal = signal }) +--@private +--- Default dispatcher for when a client exits. +--- +--@param code (number): Exit code +--@param signal (number): Number describing the signal used to terminate (if +---any) +function default_dispatchers.on_exit(code, signal) + local _ = log.info() and log.info("client_exit", { code = code, signal = signal }) end -function default_handlers.on_error(code, err) +--@private +--- Default dispatcher for client errors. +--- +--@param code (number): Error code +--@param err (any): Details about the error +---any) +function default_dispatchers.on_error(code, err) local _ = log.error() and log.error('client_error:', client_errors[code], err) end ---- Create and start an RPC client. --- @param cmd [ -local function create_and_start_client(cmd, cmd_args, handlers, extra_spawn_params) +--- Starts an LSP server process and create an LSP RPC client object to +--- interact with it. +--- +--@param cmd (string) Command to start the LSP server. +--@param cmd_args (table) List of additional string arguments to pass to {cmd}. +--@param dispatchers (table, optional) Dispatchers for LSP message types. Valid +---dispatcher names are: +--- - `"notification"` +--- - `"server_request"` +--- - `"on_error"` +--- - `"on_exit"` +--@param extra_spawn_params (table, optional) Additional context for the LSP +--- server process. May contain: +--- - {cwd} (string) Working directory for the LSP server process +--- - {env} (table) Additional environment variables for LSP server process +--@returns Client RPC object. +--- +--@returns Methods: +--- - `notify()` |vim.lsp.rpc.notify()| +--- - `request()` |vim.lsp.rpc.request()| +--- +--@returns Members: +--- - {pid} (number) The LSP server's PID. +--- - {handle} A handle for low-level interaction with the LSP server process +--- |vim.loop|. +local function start(cmd, cmd_args, dispatchers, extra_spawn_params) local _ = log.info() and log.info("Starting RPC client", {cmd = cmd, args = cmd_args, extra = extra_spawn_params}) validate { cmd = { cmd, 's' }; cmd_args = { cmd_args, 't' }; - handlers = { handlers, 't', true }; + dispatchers = { dispatchers, 't', true }; } if not (vim.fn.executable(cmd) == 1) then error(string.format("The given command %q is not executable.", cmd)) end - if handlers then - local user_handlers = handlers - handlers = {} - for handle_name, default_handler in pairs(default_handlers) do - local user_handler = user_handlers[handle_name] - if user_handler then - if type(user_handler) ~= 'function' then - error(string.format("handler.%s must be a function", handle_name)) + if dispatchers then + local user_dispatchers = dispatchers + dispatchers = {} + for dispatch_name, default_dispatch in pairs(default_dispatchers) do + local user_dispatcher = user_dispatchers[dispatch_name] + if user_dispatcher then + if type(user_dispatcher) ~= 'function' then + error(string.format("dispatcher.%s must be a function", dispatch_name)) end -- server_request is wrapped elsewhere. - if not (handle_name == 'server_request' - or handle_name == 'on_exit') -- TODO this blocks the loop exiting for some reason. + if not (dispatch_name == 'server_request' + or dispatch_name == 'on_exit') -- TODO this blocks the loop exiting for some reason. then - user_handler = schedule_wrap(user_handler) + user_dispatcher = schedule_wrap(user_dispatcher) end - handlers[handle_name] = user_handler + dispatchers[dispatch_name] = user_dispatcher else - handlers[handle_name] = default_handler + dispatchers[dispatch_name] = default_dispatch end end else - handlers = default_handlers + dispatchers = default_dispatchers end local stdin = uv.new_pipe(false) @@ -242,6 +339,10 @@ local function create_and_start_client(cmd, cmd_args, handlers, extra_spawn_para local handle, pid do + --@private + --- Callback for |vim.loop.spawn()| Closes all streams and runs the `on_exit` dispatcher. + --@param code (number) Exit code + --@param signal (number) Signal that was used to terminate (if any) local function onexit(code, signal) stdin:close() stdout:close() @@ -249,7 +350,7 @@ local function create_and_start_client(cmd, cmd_args, handlers, extra_spawn_para handle:close() -- Make sure that message_callbacks can be gc'd. message_callbacks = nil - handlers.on_exit(code, signal) + dispatchers.on_exit(code, signal) end local spawn_params = { args = cmd_args; @@ -265,6 +366,12 @@ local function create_and_start_client(cmd, cmd_args, handlers, extra_spawn_para handle, pid = uv.spawn(cmd, spawn_params, onexit) end + --@private + --- Encodes {payload} into a JSON-RPC message and sends it to the remote + --- process. + --- + --@param payload (table) Converted into a JSON string, see |json_encode()| + --@returns true if the payload could be scheduled, false if the main event-loop is in the process of closing. local function encode_and_send(payload) local _ = log.debug() and log.debug("rpc.send.payload", payload) if handle:is_closing() then return false end @@ -276,8 +383,14 @@ local function create_and_start_client(cmd, cmd_args, handlers, extra_spawn_para return true end - local function send_notification(method, params) - local _ = log.debug() and log.debug("rpc.notify", method, params) + -- FIXME: DOC: Should be placed on the RPC client object returned by + -- `start()` + -- + --- Sends a notification to the LSP server. + --@param method (string) The invoked LSP method + --@param params (table): Parameters for the invoked LSP method + --@returns (bool) `true` if notification could be sent, `false` if not + local function notify(method, params) return encode_and_send { jsonrpc = "2.0"; method = method; @@ -285,6 +398,8 @@ local function create_and_start_client(cmd, cmd_args, handlers, extra_spawn_para } end + --@private + --- sends an error object to the remote LSP process. local function send_response(request_id, err, result) return encode_and_send { id = request_id; @@ -294,7 +409,16 @@ local function create_and_start_client(cmd, cmd_args, handlers, extra_spawn_para } end - local function send_request(method, params, callback) + -- FIXME: DOC: Should be placed on the RPC client object returned by + -- `start()` + -- + --- Sends a request to the LSP server and runs {callback} upon response. + --- + --@param method (string) The invoked LSP method + --@param params (table) Parameters for the invoked LSP method + --@param callback (function) Callback to invoke + --@returns (bool, number) `(true, message_id)` if request could be sent, `false` if not + local function request(method, params, callback) validate { callback = { callback, 'f' }; } @@ -320,11 +444,13 @@ local function create_and_start_client(cmd, cmd_args, handlers, extra_spawn_para end end) + --@private local function on_error(errkind, ...) assert(client_errors[errkind]) -- TODO what to do if this fails? - pcall(handlers.on_error, errkind, ...) + pcall(dispatchers.on_error, errkind, ...) end + --@private local function pcall_handler(errkind, status, head, ...) if not status then on_error(errkind, head, ...) @@ -332,6 +458,7 @@ local function create_and_start_client(cmd, cmd_args, handlers, extra_spawn_para end return status, head, ... end + --@private local function try_call(errkind, fn, ...) return pcall_handler(errkind, pcall(fn, ...)) end @@ -340,10 +467,11 @@ local function create_and_start_client(cmd, cmd_args, handlers, extra_spawn_para -- time and log them. This would require storing the timestamp. I could call -- them with an error then, perhaps. + --@private local function handle_body(body) local decoded, err = json_decode(body) if not decoded then - on_error(client_errors.INVALID_SERVER_JSON, err) + -- on_error(client_errors.INVALID_SERVER_JSON, err) return end local _ = log.debug() and log.debug("decoded", decoded) @@ -356,7 +484,7 @@ local function create_and_start_client(cmd, cmd_args, handlers, extra_spawn_para schedule(function() local status, result status, result, err = try_call(client_errors.SERVER_REQUEST_HANDLER_ERROR, - handlers.server_request, decoded.method, decoded.params) + dispatchers.server_request, decoded.method, decoded.params) local _ = log.debug() and log.debug("server_request: callback result", { status = status, result = result, err = err }) if status then if not (result or err) then @@ -381,10 +509,13 @@ local function create_and_start_client(cmd, cmd_args, handlers, extra_spawn_para decoded.error = convert_NIL(decoded.error) decoded.result = convert_NIL(decoded.result) - -- Do not surface RequestCancelled to users, it is RPC-internal. - if decoded.error - and decoded.error.code == protocol.ErrorCodes.RequestCancelled then - local _ = log.debug() and log.debug("Received cancellation ack", decoded) + -- Do not surface RequestCancelled or ContentModified to users, it is RPC-internal. + if decoded.error then + if decoded.error.code == protocol.ErrorCodes.RequestCancelled then + local _ = log.debug() and log.debug("Received cancellation ack", decoded) + elseif decoded.error.code == protocol.ErrorCodes.ContentModified then + local _ = log.debug() and log.debug("Received content modified ack", decoded) + end local result_id = tonumber(decoded.id) -- Clear any callback since this is cancelled now. -- This is safe to do assuming that these conditions hold: @@ -420,7 +551,7 @@ local function create_and_start_client(cmd, cmd_args, handlers, extra_spawn_para -- Notification decoded.params = convert_NIL(decoded.params) try_call(client_errors.NOTIFICATION_HANDLER_ERROR, - handlers.notification, decoded.method, decoded.params) + dispatchers.notification, decoded.method, decoded.params) else -- Invalid server message on_error(client_errors.INVALID_SERVER_MESSAGE, decoded) @@ -458,13 +589,13 @@ local function create_and_start_client(cmd, cmd_args, handlers, extra_spawn_para return { pid = pid; handle = handle; - request = send_request; - notify = send_notification; + request = request; + notify = notify } end return { - start = create_and_start_client; + start = start; rpc_response_error = rpc_response_error; format_rpc_error = format_rpc_error; client_errors = client_errors; diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 5a68138f1e..3deec6d74e 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -5,46 +5,41 @@ local api = vim.api local list_extend = vim.list_extend local highlight = require 'vim.highlight' +local npcall = vim.F.npcall +local split = vim.split + +local _warned = {} +local warn_once = function(message) + if not _warned[message] then + vim.api.nvim_err_writeln(message) + _warned[message] = true + end +end + local M = {} ---- Diagnostics received from the server via `textDocument/publishDiagnostics` --- by buffer. --- --- {<bufnr>: {diagnostics}} --- --- This contains only entries for active buffers. Entries for detached buffers --- are discarded. --- --- If you override the `textDocument/publishDiagnostic` callback, --- this will be empty unless you call `buf_diagnostics_save_positions`. --- --- --- Diagnostic is: --- --- { --- range: Range --- message: string --- severity?: DiagnosticSeverity --- code?: number | string --- source?: string --- tags?: DiagnosticTag[] --- relatedInformation?: DiagnosticRelatedInformation[] --- } -M.diagnostics_by_buf = {} +-- TODO(remove-callbacks) +M.diagnostics_by_buf = setmetatable({}, { + __index = function(_, bufnr) + warn_once("diagnostics_by_buf is deprecated. Use 'vim.lsp.diagnostic.get'") + return vim.lsp.diagnostic.get(bufnr) + end +}) -local split = vim.split +--@private local function split_lines(value) return split(value, '\n', true) end -local function ok_or_nil(status, ...) - if not status then return end - return ... -end -local function npcall(fn, ...) - return ok_or_nil(pcall(fn, ...)) -end - +--- Replaces text in a range with new text. +--- +--- CAUTION: Changes in-place! +--- +--@param lines (table) Original list of strings +--@param A (table) Start position; a 2-tuple of {line, col} numbers +--@param B (table) End position; a 2-tuple of {line, col} numbers +--@param new_lines A list of strings to replace the original +--@returns (table) The modified {lines} object function M.set_lines(lines, A, B, new_lines) -- 0-indexing to 1-indexing local i_0 = A[1] + 1 @@ -78,6 +73,7 @@ function M.set_lines(lines, A, B, new_lines) return lines end +--@private local function sort_by_key(fn) return function(a,b) local ka, kb = fn(a), fn(b) @@ -91,13 +87,15 @@ local function sort_by_key(fn) return false end end +--@private local edit_sort_key = sort_by_key(function(e) - return {e.A[1], e.A[2], -e.i} + return {e.A[1], e.A[2], e.i} end) +--@private --- Position is a https://microsoft.github.io/language-server-protocol/specifications/specification-current/#position --- Returns a zero-indexed column, since set_lines() does the conversion to --- 1-indexed +--- Returns a zero-indexed column, since set_lines() does the conversion to +--- 1-indexed local function get_line_byte_from_position(bufnr, position) -- LSP's line and characters are 0-indexed -- Vim's line and columns are 1-indexed @@ -105,15 +103,26 @@ local function get_line_byte_from_position(bufnr, position) -- When on the first character, we can ignore the difference between byte and -- character if col > 0 then + if not api.nvim_buf_is_loaded(bufnr) then + vim.fn.bufload(bufnr) + end + local line = position.line local lines = api.nvim_buf_get_lines(bufnr, line, line + 1, false) if #lines > 0 then - return vim.str_byteindex(lines[1], col) + local ok, result = pcall(vim.str_byteindex, lines[1], col) + + if ok then + return result + end end end return col end +--- Applies a list of text edits to a buffer. +--@param text_edits (table) list of `TextEdit` objects +--@param buf_nr (number) Buffer id function M.apply_text_edits(text_edits, bufnr) if not next(text_edits) then return end if not api.nvim_buf_is_loaded(bufnr) then @@ -168,39 +177,53 @@ end -- function M.glob_to_regex(glob) -- end --- textDocument/completion response returns one of CompletionItem[], CompletionList or null. --- https://microsoft.github.io/language-server-protocol/specification#textDocument_completion +--- Can be used to extract the completion items from a +--- `textDocument/completion` request, which may return one of +--- `CompletionItem[]`, `CompletionList` or null. +--@param result (table) The result of a `textDocument/completion` request +--@returns (table) List of completion items +--@see https://microsoft.github.io/language-server-protocol/specification#textDocument_completion function M.extract_completion_items(result) if type(result) == 'table' and result.items then + -- result is a `CompletionList` return result.items elseif result ~= nil then + -- result is `CompletionItem[]` return result else + -- result is `null` return {} end end ---- Apply the TextDocumentEdit response. --- @params TextDocumentEdit [table] see https://microsoft.github.io/language-server-protocol/specification +--- Applies a `TextDocumentEdit`, which is a list of changes to a single +-- document. +--- +--@param text_document_edit (table) a `TextDocumentEdit` object +--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentEdit function M.apply_text_document_edit(text_document_edit) local text_document = text_document_edit.textDocument local bufnr = vim.uri_to_bufnr(text_document.uri) - if text_document.version then - -- `VersionedTextDocumentIdentifier`s version may be null https://microsoft.github.io/language-server-protocol/specification#versionedTextDocumentIdentifier - if text_document.version ~= vim.NIL and M.buf_versions[bufnr] ~= nil and M.buf_versions[bufnr] > text_document.version then - print("Buffer ", text_document.uri, " newer than edits.") - return - end + + -- `VersionedTextDocumentIdentifier`s version may be null + -- https://microsoft.github.io/language-server-protocol/specification#versionedTextDocumentIdentifier + if text_document.version + and M.buf_versions[bufnr] + and M.buf_versions[bufnr] > text_document.version then + print("Buffer ", text_document.uri, " newer than edits.") + return end - M.apply_text_edits(text_document_edit.edits, bufnr) -end -function M.get_current_line_to_cursor() - local pos = api.nvim_win_get_cursor(0) - local line = assert(api.nvim_buf_get_lines(0, pos[1]-1, pos[1], false)[1]) - return line:sub(pos[2]+1) + M.apply_text_edits(text_document_edit.edits, bufnr) end +--@private +--- Recursively parses snippets in a completion entry. +--- +--@param input (string) Snippet text to parse for snippets +--@param inner (bool) Whether this function is being called recursively +--@returns 2-tuple of strings: The first is the parsed result, the second is the +---unparsed rest of the input local function parse_snippet_rec(input, inner) local res = "" @@ -254,25 +277,30 @@ local function parse_snippet_rec(input, inner) return res, input end --- Parse completion entries, consuming snippet tokens +--- Parses snippets in a completion entry. +--- +--@param input (string) unparsed snippet +--@returns (string) parsed snippet function M.parse_snippet(input) local res, _ = parse_snippet_rec(input, false) return res end --- Sort by CompletionItem.sortText --- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion +--@private +--- Sorts by CompletionItem.sortText. +--- +--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion local function sort_completion_items(items) - if items[1] and items[1].sortText then - table.sort(items, function(a, b) return a.sortText < b.sortText - end) - end + table.sort(items, function(a, b) + return (a.sortText or a.label) < (b.sortText or b.label) + end) end --- Returns text that should be inserted when selecting completion item. The precedence is as follows: --- textEdit.newText > insertText > label --- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion +--@private +--- Returns text that should be inserted when selecting completion item. The +--- precedence is as follows: textEdit.newText > insertText > label +--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion local function get_completion_word(item) if item.textEdit ~= nil and item.textEdit.newText ~= nil then if protocol.InsertTextFormat[item.insertTextFormat] == "PlainText" then @@ -290,8 +318,10 @@ local function get_completion_word(item) return item.label end --- Some language servers return complementary candidates whose prefixes do not match are also returned. --- So we exclude completion candidates whose prefix does not match. +--@private +--- Some language servers return complementary candidates whose prefixes do not +--- match are also returned. So we exclude completion candidates whose prefix +--- does not match. local function remove_unmatch_completion_items(items, prefix) return vim.tbl_filter(function(item) local word = get_completion_word(item) @@ -299,16 +329,26 @@ local function remove_unmatch_completion_items(items, prefix) end, items) end --- Acording to LSP spec, if the client set "completionItemKind.valueSet", --- the client must handle it properly even if it receives a value outside the specification. --- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion +--- Acording to LSP spec, if the client set `completionItemKind.valueSet`, +--- the client must handle it properly even if it receives a value outside the +--- specification. +--- +--@param completion_item_kind (`vim.lsp.protocol.completionItemKind`) +--@returns (`vim.lsp.protocol.completionItemKind`) +--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion function M._get_completion_item_kind_name(completion_item_kind) return protocol.CompletionItemKind[completion_item_kind] or "Unknown" end ---- Getting vim complete-items with incomplete flag. --- @params CompletionItem[], CompletionList or nil (https://microsoft.github.io/language-server-protocol/specification#textDocument_completion) --- @return { matches = complete-items table, incomplete = boolean } +--- Turns the result of a `textDocument/completion` request into vim-compatible +--- |complete-items|. +--- +--@param result The result of a `textDocument/completion` call, e.g. from +---|vim.lsp.buf.completion()|, which may be one of `CompletionItem[]`, +--- `CompletionList` or `null` +--@param prefix (string) the prefix to filter the completion items +--@returns { matches = complete-items table, incomplete = bool } +--@see |complete-items| function M.text_document_completion_list_to_complete_items(result, prefix) local items = M.extract_completion_items(result) if vim.tbl_isempty(items) then @@ -356,7 +396,10 @@ function M.text_document_completion_list_to_complete_items(result, prefix) return matches end --- @params WorkspaceEdit [table] see https://microsoft.github.io/language-server-protocol/specification +--- Applies a `WorkspaceEdit`. +--- +--@param workspace_edit (table) `WorkspaceEdit` +-- @see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_applyEdit function M.apply_workspace_edit(workspace_edit) if workspace_edit.documentChanges then for _, change in ipairs(workspace_edit.documentChanges) do @@ -381,9 +424,15 @@ function M.apply_workspace_edit(workspace_edit) end end ---- Convert any of MarkedString | MarkedString[] | MarkupContent into markdown text lines --- see https://microsoft.github.io/language-server-protocol/specifications/specification-3-14/#textDocument_hover --- Useful for textDocument/hover, 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. +--- +--@param input (`MarkedString` | `MarkedString[]` | `MarkupContent`) +--@param contents (table, optional, default `{}`) List of strings to extend with converted lines +--@returns {contents}, extended with lines of converted markdown. +--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_hover function M.convert_input_to_markdown_lines(input, contents) contents = contents or {} -- MarkedString variation 1 @@ -422,8 +471,11 @@ function M.convert_input_to_markdown_lines(input, contents) return contents end ---- Convert SignatureHelp response to markdown lines. --- https://microsoft.github.io/language-server-protocol/specifications/specification-3-14/#textDocument_signatureHelp +--- Converts `textDocument/SignatureHelp` response to markdown lines. +--- +--@param signature_help Response of `textDocument/SignatureHelp` +--@returns list of lines of converted markdown. +--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_signatureHelp function M.convert_signature_help_to_markdown_lines(signature_help) if not signature_help.signatures then return @@ -446,13 +498,13 @@ function M.convert_signature_help_to_markdown_lines(signature_help) if signature.documentation then M.convert_input_to_markdown_lines(signature.documentation, contents) end - if signature_help.parameters then + if signature.parameters and #signature.parameters > 0 then local active_parameter = signature_help.activeParameter or 0 -- If the activeParameter is not inside the valid range, then clip it. - if active_parameter >= #signature_help.parameters then + if active_parameter >= #signature.parameters then active_parameter = 0 end - local parameter = signature.parameters and signature.parameters[active_parameter] + local parameter = signature.parameters[active_parameter + 1] if parameter then --[=[ --Represents a parameter of a callable-signature. A parameter can @@ -474,13 +526,20 @@ function M.convert_signature_help_to_markdown_lines(signature_help) --]=] -- TODO highlight parameter if parameter.documentation then - M.convert_input_help_to_markdown_lines(parameter.documentation, contents) + M.convert_input_to_markdown_lines(parameter.documentation, contents) end end end return contents end +--- Creates a table with sensible default options for a floating window. The +--- table can be passed to |nvim_open_win()|. +--- +--@param width (number) window width (in character cells) +--@param height (number) window height (in character cells) +--@param opts (table, optional) +--@returns (table) Options function M.make_floating_popup_options(width, height, opts) validate { opts = { opts, 't', true }; @@ -526,6 +585,10 @@ function M.make_floating_popup_options(width, height, opts) } end +--- Jumps to a location. +--- +--@param location (`Location`|`LocationLink`) +--@returns `true` if the jump succeeded function M.jump_to_location(location) -- location may be Location or LocationLink local uri = location.uri or location.targetUri @@ -549,14 +612,14 @@ function M.jump_to_location(location) return true end ---- Preview a location in a floating windows +--- 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) --- ---@param location a single Location or LocationLink ---@return bufnr,winnr buffer and window number of floating window or nil +--@param location a single `Location` or `LocationLink` +--@returns (bufnr,winnr) buffer and window number of floating window or nil function M.preview_location(location) -- location may be LocationLink or Location (more useful for the former) local uri = location.targetUri or location.uri @@ -571,6 +634,7 @@ function M.preview_location(location) return M.open_floating_preview(contents, filetype) end +--@private local function find_window_by_var(name, value) for _, win in ipairs(api.nvim_list_wins()) do if npcall(api.nvim_win_get_var, win, name) == value then @@ -579,19 +643,25 @@ local function find_window_by_var(name, value) end end --- Check if a window with `unique_name` tagged is associated with the current --- buffer. If not, make a new preview. --- --- fn()'s return bufnr, winnr --- case that a new floating window should be created. +--- Enters/leaves the focusable window associated with the current buffer via the +--window - variable `unique_name`. If no such window exists, run the function +--{fn}. +--- +--@param unique_name (string) Window variable +--@param fn (function) should return create a new window and return a tuple of +---({focusable_buffer_id}, {window_id}). if {focusable_buffer_id} is a valid +---buffer id, the newly created window will be the new focus associated with +---the current buffer via the tag `unique_name`. +--@returns (pbufnr, pwinnr) if `fn()` has created a new window; nil otherwise function M.focusable_float(unique_name, fn) + -- Go back to previous window if we are in a focusable one if npcall(api.nvim_win_get_var, 0, unique_name) then return api.nvim_command("wincmd p") end local bufnr = api.nvim_get_current_buf() do local win = find_window_by_var(unique_name, bufnr) - if win then + if win and api.nvim_win_is_valid(win) and not vim.fn.pumvisible() then api.nvim_set_current_win(win) api.nvim_command("stopinsert") return @@ -604,26 +674,29 @@ function M.focusable_float(unique_name, fn) end end --- Check if a window with `unique_name` tagged is associated with the current --- buffer. If not, make a new preview. --- --- fn()'s return values will be passed directly to open_floating_preview in the --- case that a new floating window should be created. +--- Focuses/unfocuses the floating preview window associated with the current +--- buffer via the window variable `unique_name`. If no such preview window +--- exists, makes a new one. +--- +--@param unique_name (string) Window variable +--@param fn (function) The return values of this function will be passed +---directly to |vim.lsp.util.open_floating_preview()|, in the case that a new +---floating window should be created function M.focusable_preview(unique_name, fn) return M.focusable_float(unique_name, function() return M.open_floating_preview(fn()) end) end ---- Trim empty lines from input and pad left and right with spaces +--- Trims empty lines from input and pad left and right with spaces --- ---@param contents table of lines to trim and pad ---@param opts dictionary with optional fields --- - pad_left number of columns to pad contents at left (default 1) --- - pad_right number of columns to pad contents at right (default 1) --- - pad_top number of lines to pad contents at top (default 0) --- - pad_bottom number of lines to pad contents at bottom (default 0) ---@return contents table of trimmed and padded lines +---@param contents table of lines to trim and pad +---@param opts dictionary with optional fields +--- - pad_left number of columns to pad contents at left (default 1) +--- - pad_right number of columns to pad contents at right (default 1) +--- - pad_top number of lines to pad contents at top (default 0) +--- - pad_bottom number of lines to pad contents at bottom (default 0) +---@return contents table of trimmed and padded lines function M._trim_and_pad(contents, opts) validate { contents = { contents, 't' }; @@ -651,26 +724,27 @@ end ---- Convert markdown into syntax highlighted regions by stripping the code +-- TODO: refactor to separate stripping/converting and make use of open_floating_preview +-- +--- 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. ---- The result is shown in a floating preview ---- TODO: refactor to separate stripping/converting and make use of open_floating_preview +--- The result is shown in a floating preview. --- ---@param contents table of lines to show in window ---@param 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_left number of columns to pad contents at left --- - pad_right number of columns to pad contents at right --- - 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 +---@param contents table of lines to show in window +---@param 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_left number of columns to pad contents at left +--- - pad_right number of columns to pad contents at right +--- - 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 +---@returns width,height size of float function M.fancy_floating_markdown(contents, opts) validate { contents = { contents, 't' }; @@ -719,13 +793,14 @@ function M.fancy_floating_markdown(contents, opts) local width, height = M._make_floating_popup_size(stripped, opts) -- Insert blank line separator after code block - local insert_separator = opts.separator or true + local insert_separator = opts.separator + if insert_separator == nil then insert_separator = true end if insert_separator then for i, h in ipairs(highlights) do h.start = h.start + i - 1 h.finish = h.finish + i - 1 if h.finish + 1 <= #stripped then - table.insert(stripped, h.finish + 1, string.rep("โ", width)) + table.insert(stripped, h.finish + 1, string.rep("โ", math.min(width, opts.wrap_at or width))) height = height + 1 end end @@ -744,6 +819,7 @@ function M.fancy_floating_markdown(contents, opts) vim.cmd("ownsyntax markdown") local idx = 1 + --@private local function apply_syntax_to_region(ft, start, finish) if ft == '' then return end local name = ft..idx @@ -769,11 +845,17 @@ function M.fancy_floating_markdown(contents, opts) return bufnr, winnr end +--- Creates autocommands to close a preview window when events happen. +--- +--@param events (table) list of events +--@param winnr (number) window id of preview window +--@see |autocmd-events| function M.close_preview_autocmd(events, winnr) api.nvim_command("autocmd "..table.concat(events, ',').." <buffer> ++once lua pcall(vim.api.nvim_win_close, "..winnr..", true)") end ---- Compute size of float needed to show contents (with optional wrapping) +--@internal +--- Computes size of float needed to show contents (with optional wrapping) --- --@param contents table of lines to show in window --@param opts dictionary with optional fields @@ -782,7 +864,7 @@ end -- - wrap_at character to wrap at for computing height -- - max_width maximal width of floating window -- - max_height maximal height of floating window ---@return width,height size of float +--@returns width,height size of float function M._make_floating_popup_size(contents, opts) validate { contents = { contents, 't' }; @@ -833,7 +915,7 @@ function M._make_floating_popup_size(contents, opts) return width, height end ---- Show contents in a floating window +--- Shows contents in a floating window. --- --@param contents table of lines to show in window --@param filetype string of filetype to set for opened buffer @@ -847,7 +929,8 @@ end -- - pad_right number of columns to pad contents at right -- - pad_top number of lines to pad contents at top -- - pad_bottom number of lines to pad contents at bottom ---@return bufnr,winnr buffer and window number of floating window or nil +--@returns bufnr,winnr buffer and window number of the newly created floating +---preview window function M.open_floating_preview(contents, filetype, opts) validate { contents = { contents, 't' }; @@ -878,159 +961,93 @@ function M.open_floating_preview(contents, filetype, opts) return floating_bufnr, floating_winnr end +-- TODO(remove-callbacks) do - local diagnostic_ns = api.nvim_create_namespace("vim_lsp_diagnostics") - local reference_ns = api.nvim_create_namespace("vim_lsp_references") - local sign_ns = 'vim_lsp_signs' - local underline_highlight_name = "LspDiagnosticsUnderline" - vim.cmd(string.format("highlight default %s gui=underline cterm=underline", underline_highlight_name)) - for kind, _ in pairs(protocol.DiagnosticSeverity) do - if type(kind) == 'string' then - vim.cmd(string.format("highlight default link %s%s %s", underline_highlight_name, kind, underline_highlight_name)) - end + --@deprecated + function M.get_severity_highlight_name(severity) + warn_once("vim.lsp.util.get_severity_highlight_name is deprecated.") + return vim.lsp.diagnostic._get_severity_highlight_name(severity) end - local severity_highlights = {} + --@deprecated + function M.buf_clear_diagnostics(bufnr, client_id) + warn_once("buf_clear_diagnostics is deprecated. Use vim.lsp.diagnostic.clear") + return vim.lsp.diagnostic.clear(bufnr, client_id) + end - local severity_floating_highlights = {} + --@deprecated + function M.get_line_diagnostics() + warn_once("get_line_diagnostics is deprecated. Use vim.lsp.diagnostic.get_line_diagnostics") - local default_severity_highlight = { - [protocol.DiagnosticSeverity.Error] = { guifg = "Red" }; - [protocol.DiagnosticSeverity.Warning] = { guifg = "Orange" }; - [protocol.DiagnosticSeverity.Information] = { guifg = "LightBlue" }; - [protocol.DiagnosticSeverity.Hint] = { guifg = "LightGrey" }; - } + local bufnr = api.nvim_get_current_buf() + local line_nr = api.nvim_win_get_cursor(0)[1] - 1 - -- Initialize default severity highlights - for severity, hi_info in pairs(default_severity_highlight) do - local severity_name = protocol.DiagnosticSeverity[severity] - local highlight_name = "LspDiagnostics"..severity_name - local floating_highlight_name = highlight_name.."Floating" - -- Try to fill in the foreground color with a sane default. - local cmd_parts = {"highlight", "default", highlight_name} - for k, v in pairs(hi_info) do - table.insert(cmd_parts, k.."="..v) - end - api.nvim_command(table.concat(cmd_parts, ' ')) - api.nvim_command('highlight link ' .. highlight_name .. 'Sign ' .. highlight_name) - api.nvim_command('highlight link ' .. highlight_name .. 'Floating ' .. highlight_name) - severity_highlights[severity] = highlight_name - severity_floating_highlights[severity] = floating_highlight_name + return vim.lsp.diagnostic.get_line_diagnostics(bufnr, line_nr) end - function M.buf_clear_diagnostics(bufnr) - validate { bufnr = {bufnr, 'n', true} } - bufnr = bufnr == 0 and api.nvim_get_current_buf() or bufnr + --@deprecated + function M.show_line_diagnostics() + warn_once("show_line_diagnostics is deprecated. Use vim.lsp.diagnostic.show_line_diagnostics") - -- clear sign group - vim.fn.sign_unplace(sign_ns, {buffer=bufnr}) + local bufnr = api.nvim_get_current_buf() + local line_nr = api.nvim_win_get_cursor(0)[1] - 1 - -- clear virtual text namespace - api.nvim_buf_clear_namespace(bufnr, diagnostic_ns, 0, -1) + return vim.lsp.diagnostic.show_line_diagnostics(bufnr, line_nr) end - function M.get_severity_highlight_name(severity) - return severity_highlights[severity] + --@deprecated + function M.buf_diagnostics_save_positions(bufnr, diagnostics, client_id) + warn_once("buf_diagnostics_save_positions is deprecated. Use vim.lsp.diagnostic.save") + return vim.lsp.diagnostic.save(diagnostics, bufnr, client_id) end - function M.get_line_diagnostics() - local bufnr = api.nvim_get_current_buf() - local linenr = api.nvim_win_get_cursor(0)[1] - 1 - - local buffer_diagnostics = M.diagnostics_by_buf[bufnr] - - if not buffer_diagnostics then - return {} - end + --@deprecated + function M.buf_diagnostics_get_positions(bufnr, client_id) + warn_once("buf_diagnostics_get_positions is deprecated. Use vim.lsp.diagnostic.get") + return vim.lsp.diagnostic.get(bufnr, client_id) + end - local diagnostics_by_line = M.diagnostics_group_by_line(buffer_diagnostics) - return diagnostics_by_line[linenr] or {} + --@deprecated + function M.buf_diagnostics_underline(bufnr, diagnostics, client_id) + warn_once("buf_diagnostics_underline is deprecated. Use 'vim.lsp.diagnostic.set_underline'") + return vim.lsp.diagnostic.set_underline(diagnostics, bufnr, client_id) end - function M.show_line_diagnostics() - -- local marks = api.nvim_buf_get_extmarks(bufnr, diagnostic_ns, {line, 0}, {line, -1}, {}) - -- if #marks == 0 then - -- return - -- end - local lines = {"Diagnostics:"} - local highlights = {{0, "Bold"}} - local line_diagnostics = M.get_line_diagnostics() - if vim.tbl_isempty(line_diagnostics) then return end - - for i, diagnostic in ipairs(line_diagnostics) do - -- for i, mark in ipairs(marks) do - -- local mark_id = mark[1] - -- local diagnostic = buffer_diagnostics[mark_id] - - -- TODO(ashkan) make format configurable? - local prefix = string.format("%d. ", i) - local hiname = severity_floating_highlights[diagnostic.severity] - assert(hiname, 'unknown severity: ' .. tostring(diagnostic.severity)) - local message_lines = split_lines(diagnostic.message) - table.insert(lines, prefix..message_lines[1]) - table.insert(highlights, {#prefix + 1, hiname}) - for j = 2, #message_lines do - table.insert(lines, message_lines[j]) - table.insert(highlights, {0, hiname}) - end - end - local popup_bufnr, winnr = M.open_floating_preview(lines, 'plaintext') - for i, hi in ipairs(highlights) do - local prefixlen, hiname = unpack(hi) - -- Start highlight after the prefix - api.nvim_buf_add_highlight(popup_bufnr, -1, hiname, i-1, prefixlen, -1) - end - return popup_bufnr, winnr + --@deprecated + function M.buf_diagnostics_virtual_text(bufnr, diagnostics, client_id) + warn_once("buf_diagnostics_virtual_text is deprecated. Use 'vim.lsp.diagnostic.set_virtual_text'") + return vim.lsp.diagnostic.set_virtual_text(diagnostics, bufnr, client_id) end - --- Saves the diagnostics (Diagnostic[]) into diagnostics_by_buf - --- - --@param bufnr bufnr for which the diagnostics are for. - --@param diagnostics Diagnostics[] received from the language server. - function M.buf_diagnostics_save_positions(bufnr, diagnostics) - validate { - bufnr = {bufnr, 'n', true}; - diagnostics = {diagnostics, 't', true}; - } - if not diagnostics then return end - bufnr = bufnr == 0 and api.nvim_get_current_buf() or bufnr - - if not M.diagnostics_by_buf[bufnr] then - -- Clean up our data when the buffer unloads. - api.nvim_buf_attach(bufnr, false, { - on_detach = function(b) - M.diagnostics_by_buf[b] = nil - end - }) - end - M.diagnostics_by_buf[bufnr] = diagnostics + --@deprecated + function M.buf_diagnostics_signs(bufnr, diagnostics, client_id) + warn_once("buf_diagnostics_signs is deprecated. Use 'vim.lsp.diagnostics.set_signs'") + return vim.lsp.diagnostic.set_signs(diagnostics, bufnr, client_id) end - function M.buf_diagnostics_underline(bufnr, diagnostics) - for _, diagnostic in ipairs(diagnostics) do - local start = diagnostic.range["start"] - local finish = diagnostic.range["end"] + --@deprecated + function M.buf_diagnostics_count(kind, client_id) + warn_once("buf_diagnostics_count is deprecated. Use 'vim.lsp.diagnostic.get_count'") + return vim.lsp.diagnostic.get_count(vim.api.nvim_get_current_buf(), client_id, kind) + end - local hlmap = { - [protocol.DiagnosticSeverity.Error]='Error', - [protocol.DiagnosticSeverity.Warning]='Warning', - [protocol.DiagnosticSeverity.Information]='Information', - [protocol.DiagnosticSeverity.Hint]='Hint', - } +end - highlight.range(bufnr, diagnostic_ns, - underline_highlight_name..hlmap[diagnostic.severity], - {start.line, start.character}, - {finish.line, finish.character} - ) - end - end +do --[[ References ]] + local reference_ns = api.nvim_create_namespace("vim_lsp_references") + --- Removes document highlights from a buffer. + --- + --@param bufnr buffer id function M.buf_clear_references(bufnr) validate { bufnr = {bufnr, 'n', true} } api.nvim_buf_clear_namespace(bufnr, reference_ns, 0, -1) end + --- Shows a list of document highlights for a certain buffer. + --- + --@param bufnr buffer id + --@param references List of `DocumentHighlight` objects to highlight function M.buf_highlight_references(bufnr, references) validate { bufnr = {bufnr, 'n', true} } for _, reference in ipairs(references) do @@ -1045,105 +1062,17 @@ do highlight.range(bufnr, reference_ns, document_highlight_kind[kind], start_pos, end_pos) end end - - function M.diagnostics_group_by_line(diagnostics) - if not diagnostics then return end - local diagnostics_by_line = {} - for _, diagnostic in ipairs(diagnostics) do - local start = diagnostic.range.start - local line_diagnostics = diagnostics_by_line[start.line] - if not line_diagnostics then - line_diagnostics = {} - diagnostics_by_line[start.line] = line_diagnostics - end - table.insert(line_diagnostics, diagnostic) - end - return diagnostics_by_line - end - - function M.buf_diagnostics_virtual_text(bufnr, diagnostics) - if not diagnostics then - return - end - local buffer_line_diagnostics = M.diagnostics_group_by_line(diagnostics) - for line, line_diags in pairs(buffer_line_diagnostics) do - local virt_texts = {} - for i = 1, #line_diags - 1 do - table.insert(virt_texts, {"โ ", severity_highlights[line_diags[i].severity]}) - end - local last = line_diags[#line_diags] - -- TODO(ashkan) use first line instead of subbing 2 spaces? - table.insert(virt_texts, {"โ "..last.message:gsub("\r", ""):gsub("\n", " "), severity_highlights[last.severity]}) - api.nvim_buf_set_virtual_text(bufnr, diagnostic_ns, line, virt_texts, {}) - end - end - - --- Returns the number of diagnostics of given kind for current buffer. - --- - --- Useful for showing diagnostic counts in statusline. eg: - --- - --- <pre> - --- function! LspStatus() abort - --- let sl = '' - --- if luaeval('not vim.tbl_isempty(vim.lsp.buf_get_clients(0))') - --- let sl.='%#MyStatuslineLSP#E:' - --- let sl.='%#MyStatuslineLSPErrors#%{luaeval("vim.lsp.util.buf_diagnostics_count([[Error]])")}' - --- let sl.='%#MyStatuslineLSP# W:' - --- let sl.='%#MyStatuslineLSPWarnings#%{luaeval("vim.lsp.util.buf_diagnostics_count([[Warning]])")}' - --- else - --- let sl.='%#MyStatuslineLSPErrors#off' - --- endif - --- return sl - --- endfunction - --- let &l:statusline = '%#MyStatuslineLSP#LSP '.LspStatus() - --- </pre> - --- - --@param kind Diagnostic severity kind: See |vim.lsp.protocol.DiagnosticSeverity| - --- - --@return Count of diagnostics - function M.buf_diagnostics_count(kind) - local bufnr = vim.api.nvim_get_current_buf() - local diagnostics = M.diagnostics_by_buf[bufnr] - if not diagnostics then return end - local count = 0 - for _, diagnostic in pairs(diagnostics) do - if protocol.DiagnosticSeverity[kind] == diagnostic.severity then - count = count + 1 - end - end - return count - end - - local diagnostic_severity_map = { - [protocol.DiagnosticSeverity.Error] = "LspDiagnosticsErrorSign"; - [protocol.DiagnosticSeverity.Warning] = "LspDiagnosticsWarningSign"; - [protocol.DiagnosticSeverity.Information] = "LspDiagnosticsInformationSign"; - [protocol.DiagnosticSeverity.Hint] = "LspDiagnosticsHintSign"; - } - - --- Place signs for each diagnostic in the sign column. - --- - --- Sign characters can be customized with the following commands: - --- - --- <pre> - --- sign define LspDiagnosticsErrorSign text=E texthl=LspDiagnosticsError linehl= numhl= - --- sign define LspDiagnosticsWarningSign text=W texthl=LspDiagnosticsWarning linehl= numhl= - --- sign define LspDiagnosticsInformationSign text=I texthl=LspDiagnosticsInformation linehl= numhl= - --- sign define LspDiagnosticsHintSign text=H texthl=LspDiagnosticsHint linehl= numhl= - --- </pre> - function M.buf_diagnostics_signs(bufnr, diagnostics) - for _, diagnostic in ipairs(diagnostics) do - vim.fn.sign_place(0, sign_ns, diagnostic_severity_map[diagnostic.severity], bufnr, {lnum=(diagnostic.range.start.line+1)}) - end - end end local position_sort = sort_by_key(function(v) return {v.start.line, v.start.character} end) --- Returns the items with the byte position calculated correctly and in sorted --- order. +--- Returns the items with the byte position calculated correctly and in sorted +--- order, for display in quickfix and location lists. +--- +--@param locations (table) list of `Location`s or `LocationLink`s +--@returns (table) list of items function M.locations_to_items(locations) local items = {} local grouped = setmetatable({}, { @@ -1186,6 +1115,10 @@ function M.locations_to_items(locations) return items end +--- Fills current window's location list with given list of items. +--- Can be obtained with e.g. |vim.lsp.util.locations_to_items()|. +--- +--@param items (table) list of items function M.set_loclist(items) vim.fn.setloclist(0, {}, ' ', { title = 'Language Server'; @@ -1193,6 +1126,10 @@ function M.set_loclist(items) }) end +--- Fills quickfix list with given list of items. +--- Can be obtained with e.g. |vim.lsp.util.locations_to_items()|. +--- +--@param items (table) list of items function M.set_qflist(items) vim.fn.setqflist({}, ' ', { title = 'Language Server'; @@ -1207,10 +1144,11 @@ function M._get_symbol_kind_name(symbol_kind) return protocol.SymbolKind[symbol_kind] or "Unknown" end ---- Convert symbols to quickfix list items +--- Converts symbols to quickfix list items. --- --@param symbols DocumentSymbol[] or SymbolInformation[] function M.symbols_to_items(symbols, bufnr) + --@private local function _symbols_to_items(_symbols, _items, _bufnr) for _, symbol in ipairs(_symbols) do if symbol.location then -- SymbolInformation type @@ -1245,7 +1183,9 @@ function M.symbols_to_items(symbols, bufnr) return _symbols_to_items(symbols, {}, bufnr) end --- Remove empty lines from the beginning and end. +--- Removes empty lines from the beginning and end. +--@param lines (table) list of lines to trim +--@returns (table) trimmed list of lines function M.trim_empty_lines(lines) local start = 1 for i = 1, #lines do @@ -1264,11 +1204,13 @@ function M.trim_empty_lines(lines) return vim.list_extend({}, lines, start, finish) end --- Accepts markdown lines and tries to reduce it to a filetype if it is --- just a single code block. --- Note: This modifies the input. --- --- Returns: filetype or 'markdown' if it was unchanged. +--- 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! +--- +--@param lines (table) list of lines +--@returns (string) filetype or 'markdown' if it was unchanged. function M.try_trim_markdown_code_blocks(lines) local language_id = lines[1]:match("^```(.*)") if language_id then @@ -1291,14 +1233,22 @@ function M.try_trim_markdown_code_blocks(lines) end local str_utfindex = vim.str_utfindex +--@private local function make_position_param() local row, col = unpack(api.nvim_win_get_cursor(0)) row = row - 1 local line = api.nvim_buf_get_lines(0, row, row+1, true)[1] + if not line then + return { line = 0; character = 0; } + end col = str_utfindex(line, col) return { line = row; character = col; } end +--- Creates a `TextDocumentPositionParams` object for the current buffer and cursor position. +--- +--@returns `TextDocumentPositionParams` object +--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentPositionParams function M.make_position_params() return { textDocument = M.make_text_document_params(); @@ -1306,19 +1256,65 @@ function M.make_position_params() } end +--- Using the current position in the current buffer, creates an object that +--- can be used as a building block for several LSP requests, such as +--- `textDocument/codeAction`, `textDocument/colorPresentation`, +--- `textDocument/rangeFormatting`. +--- +--@returns { textDocument = { uri = `current_file_uri` }, range = { start = +---`current_position`, end = `current_position` } } function M.make_range_params() local position = make_position_param() return { - textDocument = { uri = vim.uri_from_bufnr(0) }, + textDocument = M.make_text_document_params(), range = { start = position; ["end"] = position; } } end +--- Using the given range in the current buffer, creates an object that +--- is similar to |vim.lsp.util.make_range_params()|. +--- +--@param start_pos ({number, number}, optional) mark-indexed position. +---Defaults to the start of the last visual selection. +--@param end_pos ({number, number}, optional) mark-indexed position. +---Defaults to the end of the last visual selection. +--@returns { textDocument = { uri = `current_file_uri` }, range = { start = +---`start_position`, end = `end_position` } } +function M.make_given_range_params(start_pos, end_pos) + validate { + start_pos = {start_pos, 't', true}; + end_pos = {end_pos, 't', true}; + } + local A = list_extend({}, start_pos or api.nvim_buf_get_mark(0, '<')) + local B = list_extend({}, end_pos or api.nvim_buf_get_mark(0, '>')) + -- convert to 0-index + A[1] = A[1] - 1 + B[1] = B[1] - 1 + -- account for encoding. + if A[2] > 0 then + A = {A[1], M.character_offset(0, A[1], A[2])} + end + if B[2] > 0 then + B = {B[1], M.character_offset(0, B[1], B[2])} + end + return { + textDocument = M.make_text_document_params(), + range = { + start = {line = A[1], character = A[2]}, + ['end'] = {line = B[1], character = B[2]} + } + } +end + +--- Creates a `TextDocumentIdentifier` object for the current buffer. +--- +--@returns `TextDocumentIdentifier` +--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentIdentifier function M.make_text_document_params() return { uri = vim.uri_from_bufnr(0) } end ---- Get visual width of tabstop. +--- Returns visual width of tabstop. --- --@see |softtabstop| --@param bufnr (optional, number): Buffer handle, defaults to current @@ -1330,6 +1326,11 @@ function M.get_effective_tabstop(bufnr) return (sts > 0 and sts) or (sts < 0 and bo.shiftwidth) or bo.tabstop end +--- Creates a `FormattingOptions` object for the current buffer and cursor position. +--- +--@param options Table with valid `FormattingOptions` entries +--@returns `FormattingOptions object +--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_formatting function M.make_formatting_params(options) validate { options = {options, 't', true} } options = vim.tbl_extend('keep', options or {}, { @@ -1342,9 +1343,12 @@ function M.make_formatting_params(options) } end --- @param buf buffer handle or 0 for current. --- @param row 0-indexed line --- @param col 0-indexed byte offset in line +--- Returns the UTF-32 and UTF-16 offsets for a position in a certain buffer. +--- +--@param buf buffer id (0 for current) +--@param row 0-indexed line +--@param col 0-indexed byte offset in line +--@returns (number, number) UTF-32 and UTF-16 index of the character in line {row} column {col} in buffer {buf} function M.character_offset(buf, row, col) local line = api.nvim_buf_get_lines(buf, row, row+1, true)[1] -- If the col is past the EOL, use the line length. @@ -1354,6 +1358,9 @@ function M.character_offset(buf, row, col) return str_utfindex(line, col) end +M._get_line_byte_from_position = get_line_byte_from_position +M._warn_once = warn_once + M.buf_versions = {} return M diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua index 6e427665f2..998e04f568 100644 --- a/runtime/lua/vim/shared.lua +++ b/runtime/lua/vim/shared.lua @@ -96,7 +96,7 @@ end --- --- Examples: --- <pre> ---- split(":aa::b:", ":") --> {'','aa','','bb',''} +--- split(":aa::b:", ":") --> {'','aa','','b',''} --- split("axaby", "ab?") --> {'','x','y'} --- split(x*yz*o, "*", true) --> {'x','yz','o'} --- </pre> @@ -190,10 +190,10 @@ function vim.tbl_contains(t, value) return false end --- Returns true if the table is empty, and contains no indexed or keyed values. --- ---@see From https://github.com/premake/premake-core/blob/master/src/base/table.lua --- +--- Checks if a table is empty. +--- +--@see https://github.com/premake/premake-core/blob/master/src/base/table.lua +--- --@param t Table to check function vim.tbl_isempty(t) assert(type(t) == 'table', string.format("Expected table, got %s", type(t))) @@ -347,13 +347,11 @@ function vim.tbl_flatten(t) return result end ---- Determine whether a Lua table can be treated as an array. +--- Tests if a Lua table can be treated as an array. --- ---- An empty table `{}` will default to being treated as an array. ---- Use `vim.emtpy_dict()` to create a table treated as an ---- empty dict. Empty tables returned by `rpcrequest()` and ---- `vim.fn` functions can be checked using this function ---- whether they represent empty API arrays and vimL lists. +--- 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|. --- --@param t Table --@returns `true` if array-like table, else `false`. @@ -479,48 +477,77 @@ end --- 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 +--- 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 function vim.validate(opt) end -- luacheck: no unused -vim.validate = (function() + +do local type_names = { - t='table', s='string', n='number', b='boolean', f='function', c='callable', - ['table']='table', ['string']='string', ['number']='number', - ['boolean']='boolean', ['function']='function', ['callable']='callable', - ['nil']='nil', ['thread']='thread', ['userdata']='userdata', + ['table'] = 'table', t = 'table', + ['string'] = 'string', s = 'string', + ['number'] = 'number', n = 'number', + ['boolean'] = 'boolean', b = 'boolean', + ['function'] = 'function', f = 'function', + ['callable'] = 'callable', c = 'callable', + ['nil'] = 'nil', + ['thread'] = 'thread', + ['userdata'] = 'userdata', } - local function _type_name(t) - local tname = type_names[t] - if tname == nil then - error(string.format('invalid type name: %s', tostring(t))) - end - return tname - end + local function _is_type(val, t) - return t == 'callable' and vim.is_callable(val) or type(val) == t + return type(val) == t or (t == 'callable' and vim.is_callable(val)) end - return function(opt) - assert(type(opt) == 'table', string.format('opt: expected table, got %s', type(opt))) + local function is_valid(opt) + if type(opt) ~= 'table' then + return false, string.format('opt: expected table, got %s', type(opt)) + end + for param_name, spec in pairs(opt) do - assert(type(spec) == 'table', string.format('%s: expected table, got %s', param_name, type(spec))) + if type(spec) ~= 'table' then + return false, string.format('opt[%s]: expected table, got %s', param_name, type(spec)) + end local val = spec[1] -- Argument value. local t = spec[2] -- Type name, or callable. local optional = (true == spec[3]) - if not vim.is_callable(t) then -- Check type name. - if (not optional or val ~= nil) and not _is_type(val, _type_name(t)) then - error(string.format("%s: expected %s, got %s", param_name, _type_name(t), type(val))) + if type(t) == 'string' then + local t_name = type_names[t] + if not t_name then + return false, string.format('invalid type name: %s', t) + end + + if (not optional or val ~= nil) and not _is_type(val, t_name) then + return false, string.format("%s: expected %s, got %s", param_name, t_name, type(val)) end - elseif not t(val) then -- Check user-provided validation function. - error(string.format("%s: expected %s, got %s", param_name, (spec[3] or '?'), val)) + elseif vim.is_callable(t) then + -- Check user-provided validation function. + local valid, optional_message = t(val) + if not valid then + local error_message = string.format("%s: expected %s, got %s", param_name, (spec[3] or '?'), val) + if optional_message ~= nil then + error_message = error_message .. string.format(". Info: %s", optional_message) + end + + return false, error_message + end + else + return false, string.format("invalid type name: %s", tostring(t)) end end - return true + + return true, nil end -end)() + function vim.validate(opt) + local ok, err_msg = is_valid(opt) + if not ok then + error(debug.traceback(err_msg, 2), 2) + end + end +end --- Returns true if object `f` can be called as a function. --- --@param f Any object diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index 927456708c..19ef148afc 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -1,4 +1,6 @@ local a = vim.api +local query = require'vim.treesitter.query' +local language = require'vim.treesitter.language' -- TODO(bfredl): currently we retain parsers for the lifetime of the buffer. -- Consider use weak references to release parser if all plugins are done with @@ -8,12 +10,22 @@ local parsers = {} local Parser = {} Parser.__index = Parser +--- Parses the buffer if needed and returns a tree. +-- +-- Calling this will call the on_changedtree callbacks if the tree has changed. +-- +-- @returns An up to date tree +-- @returns If the tree changed with this call, the changed ranges function Parser:parse() if self.valid then - return self.tree + return self._tree_immutable end local changes - self.tree, changes = self._parser:parse_buf(self.bufnr) + + self._tree, changes = self._parser:parse(self._tree, self:input_source()) + + self._tree_immutable = self._tree:copy() + self.valid = true if not vim.tbl_isempty(changes) then @@ -22,64 +34,89 @@ function Parser:parse() end end - return self.tree, changes + return self._tree_immutable, changes +end + +function Parser:input_source() + return self.bufnr or self.str end -function Parser:_on_lines(bufnr, changed_tick, start_row, old_stop_row, stop_row, old_byte_size) - local start_byte = a.nvim_buf_get_offset(bufnr,start_row) - local stop_byte = a.nvim_buf_get_offset(bufnr,stop_row) - local old_stop_byte = start_byte + old_byte_size - self._parser:edit(start_byte,old_stop_byte,stop_byte, - start_row,0,old_stop_row,0,stop_row,0) +function Parser:_on_bytes(bufnr, changed_tick, + start_row, start_col, start_byte, + old_row, old_col, old_byte, + new_row, new_col, new_byte) + local old_end_col = old_col + ((old_row == 0) and start_col or 0) + local new_end_col = new_col + ((new_row == 0) and start_col or 0) + self._tree:edit(start_byte,start_byte+old_byte,start_byte+new_byte, + start_row, start_col, + start_row+old_row, old_end_col, + start_row+new_row, new_end_col) self.valid = false - for _, cb in ipairs(self.lines_cbs) do - cb(bufnr, changed_tick, start_row, old_stop_row, stop_row, old_byte_size) + for _, cb in ipairs(self.bytes_cbs) do + cb(bufnr, changed_tick, + start_row, start_col, start_byte, + old_row, old_col, old_byte, + new_row, new_col, new_byte) + end +end + +--- Registers callbacks for the parser +-- @param cbs An `nvim_buf_attach`-like table argument with the following keys : +-- `on_bytes` : see `nvim_buf_attach`, but this will be called _after_ the parsers callback. +-- `on_changedtree` : a callback that will be called everytime the tree has syntactical changes. +-- it will only be passed one argument, that is a table of the ranges (as node ranges) that +-- changed. +function Parser:register_cbs(cbs) + if not cbs then return end + + if cbs.on_changedtree then + table.insert(self.changedtree_cbs, cbs.on_changedtree) + end + + if cbs.on_bytes then + table.insert(self.bytes_cbs, cbs.on_bytes) end end +--- Sets the included ranges for the current parser +-- +-- @param ranges A table of nodes that will be used as the ranges the parser should include. function Parser:set_included_ranges(ranges) self._parser:set_included_ranges(ranges) -- The buffer will need to be parsed again later self.valid = false end -local M = { - parse_query = vim._ts_parse_query, -} +--- Gets the included ranges for the parsers +function Parser:included_ranges() + return self._parser:included_ranges() +end + +local M = vim.tbl_extend("error", query, language) setmetatable(M, { __index = function (t, k) if k == "TSHighlighter" then - t[k] = require'vim.tshighlighter' + a.nvim_err_writeln("vim.TSHighlighter is deprecated, please use vim.treesitter.highlighter") + t[k] = require'vim.treesitter.highlighter' + return t[k] + elseif k == "highlighter" then + t[k] = require'vim.treesitter.highlighter' return t[k] end end }) -function M.require_language(lang, path) - if vim._ts_has_language(lang) then - return true - end - if path == nil then - local fname = 'parser/' .. lang .. '.*' - local paths = a.nvim_get_runtime_file(fname, false) - if #paths == 0 then - -- TODO(bfredl): help tag? - error("no parser for '"..lang.."' language") - end - path = paths[1] - end - vim._ts_add_language(path, lang) -end - -function M.inspect_language(lang) - M.require_language(lang) - return vim._ts_inspect_language(lang) -end - -function M.create_parser(bufnr, lang, id) - M.require_language(lang) +--- Creates a new parser. +-- +-- It is not recommended to use this, use vim.treesitter.get_parser() instead. +-- +-- @param bufnr The buffer the parser will be tied to +-- @param lang The language of the parser. +-- @param id The id the parser will have +function M._create_parser(bufnr, lang, id) + language.require_language(lang) if bufnr == 0 then bufnr = a.nvim_get_current_buf() end @@ -89,12 +126,12 @@ function M.create_parser(bufnr, lang, id) local self = setmetatable({bufnr=bufnr, lang=lang, valid=false}, Parser) self._parser = vim._create_ts_parser(lang) self.changedtree_cbs = {} - self.lines_cbs = {} + self.bytes_cbs = {} self:parse() -- TODO(bfredl): use weakref to self, so that the parser is free'd is no plugin is -- using it. - local function lines_cb(_, ...) - return self:_on_lines(...) + local function bytes_cb(_, ...) + return self:_on_bytes(...) end local detach_cb = nil if id ~= nil then @@ -104,157 +141,50 @@ function M.create_parser(bufnr, lang, id) end end end - a.nvim_buf_attach(self.bufnr, false, {on_lines=lines_cb, on_detach=detach_cb}) + a.nvim_buf_attach(self.bufnr, false, {on_bytes=bytes_cb, on_detach=detach_cb}) return self end -function M.get_parser(bufnr, ft, buf_attach_cbs) +--- Gets the parser for this bufnr / ft combination. +-- +-- If needed this will create the parser. +-- Unconditionnally attach the provided callback +-- +-- @param bufnr The buffer the parser should be tied to +-- @param ft The filetype of this parser +-- @param buf_attach_cbs See Parser:register_cbs +-- +-- @returns The parser +function M.get_parser(bufnr, lang, buf_attach_cbs) if bufnr == nil or bufnr == 0 then bufnr = a.nvim_get_current_buf() end - if ft == nil then - ft = a.nvim_buf_get_option(bufnr, "filetype") + if lang == nil then + lang = a.nvim_buf_get_option(bufnr, "filetype") end - local id = tostring(bufnr)..'_'..ft + local id = tostring(bufnr)..'_'..lang if parsers[id] == nil then - parsers[id] = M.create_parser(bufnr, ft, id) - end - - if buf_attach_cbs and buf_attach_cbs.on_changedtree then - table.insert(parsers[id].changedtree_cbs, buf_attach_cbs.on_changedtree) + parsers[id] = M._create_parser(bufnr, lang, id) end - if buf_attach_cbs and buf_attach_cbs.on_lines then - table.insert(parsers[id].lines_cbs, buf_attach_cbs.on_lines) - end + parsers[id]:register_cbs(buf_attach_cbs) return parsers[id] end --- query: pattern matching on trees --- predicate matching is implemented in lua -local Query = {} -Query.__index = Query +function M.get_string_parser(str, lang) + vim.validate { + str = { str, 'string' }, + lang = { lang, 'string' } + } + language.require_language(lang) -local magic_prefixes = {['\\v']=true, ['\\m']=true, ['\\M']=true, ['\\V']=true} -local function check_magic(str) - if string.len(str) < 2 or magic_prefixes[string.sub(str,1,2)] then - return str - end - return '\\v'..str -end + local self = setmetatable({str=str, lang=lang, valid=false}, Parser) + self._parser = vim._create_ts_parser(lang) + self:parse() -function M.parse_query(lang, query) - M.require_language(lang) - local self = setmetatable({}, Query) - self.query = vim._ts_parse_query(lang, vim.fn.escape(query,'\\')) - self.info = self.query:inspect() - self.captures = self.info.captures - self.regexes = {} - for id,preds in pairs(self.info.patterns) do - local regexes = {} - for i, pred in ipairs(preds) do - if (pred[1] == "match?" and type(pred[2]) == "number" - and type(pred[3]) == "string") then - regexes[i] = vim.regex(check_magic(pred[3])) - end - end - if next(regexes) then - self.regexes[id] = regexes - end - end return self end -local function get_node_text(node, bufnr) - local start_row, start_col, end_row, end_col = node:range() - if start_row ~= end_row then - return nil - end - local line = a.nvim_buf_get_lines(bufnr, start_row, start_row+1, true)[1] - return string.sub(line, start_col+1, end_col) -end - -function Query:match_preds(match, pattern, bufnr) - local preds = self.info.patterns[pattern] - if not preds then - return true - end - local regexes = self.regexes[pattern] - for i, pred in pairs(preds) do - -- Here we only want to return if a predicate DOES NOT match, and - -- continue on the other case. This way unknown predicates will not be considered, - -- which allows some testing and easier user extensibility (#12173). - -- Also, tree-sitter strips the leading # from predicates for us. - if pred[1] == "eq?" then - local node = match[pred[2]] - local node_text = get_node_text(node, bufnr) - - local str - if type(pred[3]) == "string" then - -- (#eq? @aa "foo") - str = pred[3] - else - -- (#eq? @aa @bb) - str = get_node_text(match[pred[3]], bufnr) - end - - if node_text ~= str or str == nil then - return false - end - elseif pred[1] == "match?" then - if not regexes or not regexes[i] then - return false - end - local node = match[pred[2]] - local start_row, start_col, end_row, end_col = node:range() - if start_row ~= end_row then - return false - end - if not regexes[i]:match_line(bufnr, start_row, start_col, end_col) then - return false - end - end - end - return true -end - -function Query:iter_captures(node, bufnr, start, stop) - if bufnr == 0 then - bufnr = vim.api.nvim_get_current_buf() - end - local raw_iter = node:_rawquery(self.query,true,start,stop) - local function iter() - local capture, captured_node, match = raw_iter() - if match ~= nil then - local active = self:match_preds(match, match.pattern, bufnr) - match.active = active - if not active then - return iter() -- tail call: try next match - end - end - return capture, captured_node - end - return iter -end - -function Query:iter_matches(node, bufnr, start, stop) - if bufnr == 0 then - bufnr = vim.api.nvim_get_current_buf() - end - local raw_iter = node:_rawquery(self.query,false,start,stop) - local function iter() - local pattern, match = raw_iter() - if match ~= nil then - local active = self:match_preds(match, pattern, bufnr) - if not active then - return iter() -- tail call: try next match - end - end - return pattern, match - end - return iter -end - return M diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua new file mode 100644 index 0000000000..6714bb6354 --- /dev/null +++ b/runtime/lua/vim/treesitter/highlighter.lua @@ -0,0 +1,210 @@ +local a = vim.api + +-- support reload for quick experimentation +local TSHighlighter = rawget(vim.treesitter, 'TSHighlighter') or {} +TSHighlighter.__index = TSHighlighter + +TSHighlighter.active = TSHighlighter.active or {} + +local ns = a.nvim_create_namespace("treesitter/highlighter") + +-- These are conventions defined by nvim-treesitter, though it +-- needs to be user extensible also. +TSHighlighter.hl_map = { + ["error"] = "Error", + +-- Miscs + ["comment"] = "Comment", + ["punctuation.delimiter"] = "Delimiter", + ["punctuation.bracket"] = "Delimiter", + ["punctuation.special"] = "Delimiter", + +-- Constants + ["constant"] = "Constant", + ["constant.builtin"] = "Special", + ["constant.macro"] = "Define", + ["string"] = "String", + ["string.regex"] = "String", + ["string.escape"] = "SpecialChar", + ["character"] = "Character", + ["number"] = "Number", + ["boolean"] = "Boolean", + ["float"] = "Float", + +-- Functions + ["function"] = "Function", + ["function.special"] = "Function", + ["function.builtin"] = "Special", + ["function.macro"] = "Macro", + ["parameter"] = "Identifier", + ["method"] = "Function", + ["field"] = "Identifier", + ["property"] = "Identifier", + ["constructor"] = "Special", + +-- Keywords + ["conditional"] = "Conditional", + ["repeat"] = "Repeat", + ["label"] = "Label", + ["operator"] = "Operator", + ["keyword"] = "Keyword", + ["exception"] = "Exception", + + ["type"] = "Type", + ["type.builtin"] = "Type", + ["structure"] = "Structure", + ["include"] = "Include", +} + +function TSHighlighter.new(parser, query) + local self = setmetatable({}, TSHighlighter) + + self.parser = parser + parser:register_cbs { + on_changedtree = function(...) self:on_changedtree(...) end + } + + self:set_query(query) + self.edit_count = 0 + self.redraw_count = 0 + self.line_count = {} + self.root = self.parser:parse():root() + a.nvim_buf_set_option(self.buf, "syntax", "") + + -- TODO(bfredl): can has multiple highlighters per buffer???? + if not TSHighlighter.active[parser.bufnr] then + TSHighlighter.active[parser.bufnr] = {} + end + + TSHighlighter.active[parser.bufnr][parser.lang] = self + + -- Tricky: if syntax hasn't been enabled, we need to reload color scheme + -- but use synload.vim rather than syntax.vim to not enable + -- syntax FileType autocmds. Later on we should integrate with the + -- `:syntax` and `set syntax=...` machinery properly. + if vim.g.syntax_on ~= 1 then + vim.api.nvim_command("runtime! syntax/synload.vim") + end + return self +end + +local function is_highlight_name(capture_name) + local firstc = string.sub(capture_name, 1, 1) + return firstc ~= string.lower(firstc) +end + +function TSHighlighter:get_hl_from_capture(capture) + + local name = self.query.captures[capture] + + if is_highlight_name(name) then + -- From "Normal.left" only keep "Normal" + return vim.split(name, '.', true)[1] + else + -- Default to false to avoid recomputing + local hl = TSHighlighter.hl_map[name] + return hl and a.nvim_get_hl_id_by_name(hl) or 0 + end +end + +function TSHighlighter:on_changedtree(changes) + for _, ch in ipairs(changes or {}) do + a.nvim__buf_redraw_range(self.buf, ch[1], ch[3]+1) + end +end + +function TSHighlighter:set_query(query) + if type(query) == "string" then + query = vim.treesitter.parse_query(self.parser.lang, query) + end + + self.query = query + + self.hl_cache = setmetatable({}, { + __index = function(table, capture) + local hl = self:get_hl_from_capture(capture) + rawset(table, capture, hl) + + return hl + end + }) + + a.nvim__buf_redraw_range(self.parser.bufnr, 0, a.nvim_buf_line_count(self.parser.bufnr)) +end + +local function iter_active_tshl(buf, fn) + for _, hl in pairs(TSHighlighter.active[buf] or {}) do + fn(hl) + end +end + +local function on_line_impl(self, buf, line) + if self.root == nil then + return -- parser bought the farm already + end + + if self.iter == nil then + self.iter = self.query:iter_captures(self.root,buf,line,self.botline) + end + while line >= self.nextrow do + local capture, node = self.iter() + if capture == nil then + break + end + local start_row, start_col, end_row, end_col = node:range() + local hl = self.hl_cache[capture] + if hl and end_row >= line then + a.nvim_buf_set_extmark(buf, ns, start_row, start_col, + { end_line = end_row, end_col = end_col, + hl_group = hl, + ephemeral = true, + }) + end + if start_row > line then + self.nextrow = start_row + end + end +end + +function TSHighlighter._on_line(_, _win, buf, line, highlighter) + -- on_line is only called when this is non-nil + if highlighter then + on_line_impl(highlighter, buf, line) + else + iter_active_tshl(buf, function(self) + on_line_impl(self, buf, line) + end) + end +end + +function TSHighlighter._on_buf(_, buf) + iter_active_tshl(buf, function(self) + if self then + local tree = self.parser:parse() + self.root = (tree and tree:root()) or nil + end + end) +end + +function TSHighlighter._on_win(_, _win, buf, _topline, botline) + iter_active_tshl(buf, function(self) + if not self then + return false + end + + self.iter = nil + self.nextrow = 0 + self.botline = botline + self.redraw_count = self.redraw_count + 1 + return true + end) + return true +end + +a.nvim_set_decoration_provider(ns, { + on_buf = TSHighlighter._on_buf; + on_win = TSHighlighter._on_win; + on_line = TSHighlighter._on_line; +}) + +return TSHighlighter diff --git a/runtime/lua/vim/treesitter/language.lua b/runtime/lua/vim/treesitter/language.lua new file mode 100644 index 0000000000..a7e36a0b89 --- /dev/null +++ b/runtime/lua/vim/treesitter/language.lua @@ -0,0 +1,37 @@ +local a = vim.api + +local M = {} + +--- Asserts that the provided language is installed, and optionnaly provide a path for the parser +-- +-- Parsers are searched in the `parser` runtime directory. +-- +-- @param lang The language the parser should parse +-- @param path Optionnal path the parser is located at +function M.require_language(lang, path) + if vim._ts_has_language(lang) then + return true + end + if path == nil then + local fname = 'parser/' .. lang .. '.*' + local paths = a.nvim_get_runtime_file(fname, false) + if #paths == 0 then + -- TODO(bfredl): help tag? + error("no parser for '"..lang.."' language, see :help treesitter-parsers") + end + path = paths[1] + end + vim._ts_add_language(path, lang) +end + +--- Inspects the provided language. +-- +-- Inspecting provides some useful informations on the language like node names, ... +-- +-- @param lang The language. +function M.inspect_language(lang) + M.require_language(lang) + return vim._ts_inspect_language(lang) +end + +return M diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua new file mode 100644 index 0000000000..3537ba78f5 --- /dev/null +++ b/runtime/lua/vim/treesitter/query.lua @@ -0,0 +1,336 @@ +local a = vim.api +local language = require'vim.treesitter.language' + +-- query: pattern matching on trees +-- predicate matching is implemented in lua +local Query = {} +Query.__index = Query + +local M = {} + +-- Filter the runtime query files, the spec is like regular runtime files but in the new `queries` +-- directory. They resemble ftplugins, that is that you can override queries by adding things in the +-- `queries` directory, and extend using the `after/queries` directory. +local function filter_files(file_list) + local main = nil + local after = {} + + for _, fname in ipairs(file_list) do + -- Only get the name of the directory containing the queries directory + if vim.fn.fnamemodify(fname, ":p:h:h:h:t") == "after" then + table.insert(after, fname) + -- The first one is the one with most priority + elseif not main then + main = fname + end + end + + return main and { main, unpack(after) } or after +end + +local function runtime_query_path(lang, query_name) + return string.format('queries/%s/%s.scm', lang, query_name) +end + +local function filtered_runtime_queries(lang, query_name) + return filter_files(a.nvim_get_runtime_file(runtime_query_path(lang, query_name), true) or {}) +end + +local function get_query_files(lang, query_name, is_included) + local lang_files = filtered_runtime_queries(lang, query_name) + + if #lang_files == 0 then return {} end + + local base_langs = {} + + -- Now get the base languages by looking at the first line of every file + -- The syntax is the folowing : + -- ;+ inherits: ({language},)*{language} + -- + -- {language} ::= {lang} | ({lang}) + local MODELINE_FORMAT = "^;+%s*inherits%s*:?%s*([a-z_,()]+)%s*$" + + for _, file in ipairs(lang_files) do + local modeline = vim.fn.readfile(file, "", 1) + + if #modeline == 1 then + local langlist = modeline[1]:match(MODELINE_FORMAT) + + if langlist then + for _, incllang in ipairs(vim.split(langlist, ',', true)) do + local is_optional = incllang:match("%(.*%)") + + if is_optional then + if not is_included then + table.insert(base_langs, incllang:sub(2, #incllang - 1)) + end + else + table.insert(base_langs, incllang) + end + end + end + end + end + + local query_files = {} + for _, base_lang in ipairs(base_langs) do + local base_files = get_query_files(base_lang, query_name, true) + vim.list_extend(query_files, base_files) + end + vim.list_extend(query_files, lang_files) + + return query_files +end + +local function read_query_files(filenames) + local contents = {} + + for _,filename in ipairs(filenames) do + vim.list_extend(contents, vim.fn.readfile(filename)) + end + + return table.concat(contents, '\n') +end + +--- Returns the runtime query {query_name} for {lang}. +-- +-- @param lang The language to use for the query +-- @param query_name The name of the query (i.e. "highlights") +-- +-- @return The corresponding query, parsed. +function M.get_query(lang, query_name) + local query_files = get_query_files(lang, query_name) + local query_string = read_query_files(query_files) + + if #query_string > 0 then + return M.parse_query(lang, query_string) + end +end + +--- Parses a query. +-- +-- @param language The language +-- @param query A string containing the query (s-expr syntax) +-- +-- @returns The query +function M.parse_query(lang, query) + language.require_language(lang) + local self = setmetatable({}, Query) + self.query = vim._ts_parse_query(lang, query) + self.info = self.query:inspect() + self.captures = self.info.captures + return self +end + +-- TODO(vigoux): support multiline nodes too + +--- Gets the text corresponding to a given node +-- @param node the node +-- @param bufnr the buffer from which the node in extracted. +function M.get_node_text(node, source) + local start_row, start_col, start_byte = node:start() + local end_row, end_col, end_byte = node:end_() + + if type(source) == "number" then + if start_row ~= end_row then + return nil + end + local line = a.nvim_buf_get_lines(source, start_row, start_row+1, true)[1] + return string.sub(line, start_col+1, end_col) + elseif type(source) == "string" then + return source:sub(start_byte+1, end_byte) + end +end + +-- Predicate handler receive the following arguments +-- (match, pattern, bufnr, predicate) +local predicate_handlers = { + ["eq?"] = function(match, _, source, predicate) + local node = match[predicate[2]] + local node_text = M.get_node_text(node, source) + + local str + if type(predicate[3]) == "string" then + -- (#eq? @aa "foo") + str = predicate[3] + else + -- (#eq? @aa @bb) + str = M.get_node_text(match[predicate[3]], source) + end + + if node_text ~= str or str == nil then + return false + end + + return true + end, + + ["lua-match?"] = function(match, _, source, predicate) + local node = match[predicate[2]] + local regex = predicate[3] + local start_row, _, end_row, _ = node:range() + if start_row ~= end_row then + return false + end + + return string.find(M.get_node_text(node, source), regex) + end, + + ["match?"] = (function() + local magic_prefixes = {['\\v']=true, ['\\m']=true, ['\\M']=true, ['\\V']=true} + local function check_magic(str) + if string.len(str) < 2 or magic_prefixes[string.sub(str,1,2)] then + return str + end + return '\\v'..str + end + + local compiled_vim_regexes = setmetatable({}, { + __index = function(t, pattern) + local res = vim.regex(check_magic(vim.fn.escape(pattern, '\\'))) + rawset(t, pattern, res) + return res + end + }) + + return function(match, _, source, pred) + local node = match[pred[2]] + local start_row, start_col, end_row, end_col = node:range() + if start_row ~= end_row then + return false + end + + local regex = compiled_vim_regexes[pred[3]] + return regex:match_line(source, start_row, start_col, end_col) + end + end)(), + + ["contains?"] = function(match, _, source, predicate) + local node = match[predicate[2]] + local node_text = M.get_node_text(node, source) + + for i=3,#predicate do + if string.find(node_text, predicate[i], 1, true) then + return true + end + end + + return false + end +} + +-- As we provide lua-match? also expose vim-match? +predicate_handlers["vim-match?"] = predicate_handlers["match?"] + +--- Adds a new predicates to be used in queries +-- +-- @param name the name of the predicate, without leading # +-- @param handler the handler function to be used +-- signature will be (match, pattern, bufnr, predicate) +function M.add_predicate(name, handler, force) + if predicate_handlers[name] and not force then + a.nvim_err_writeln(string.format("Overriding %s", name)) + end + + predicate_handlers[name] = handler +end + +--- Returns the list of currently supported predicates +function M.list_predicates() + return vim.tbl_keys(predicate_handlers) +end + +local function xor(x, y) + return (x or y) and not (x and y) +end + +function Query:match_preds(match, pattern, source) + local preds = self.info.patterns[pattern] + + for _, pred in pairs(preds or {}) do + -- Here we only want to return if a predicate DOES NOT match, and + -- continue on the other case. This way unknown predicates will not be considered, + -- which allows some testing and easier user extensibility (#12173). + -- Also, tree-sitter strips the leading # from predicates for us. + local pred_name + local is_not + if string.sub(pred[1], 1, 4) == "not-" then + pred_name = string.sub(pred[1], 5) + is_not = true + else + pred_name = pred[1] + is_not = false + end + + local handler = predicate_handlers[pred_name] + + if not handler then + a.nvim_err_writeln(string.format("No handler for %s", pred[1])) + return false + end + + local pred_matches = handler(match, pattern, source, pred) + + if not xor(is_not, pred_matches) then + return false + end + end + return true +end + +--- Iterates of the captures of self on a given range. +-- +-- @param node The node under witch the search will occur +-- @param buffer The source buffer to search +-- @param start The starting line of the search +-- @param stop The stoping line of the search (end-exclusive) +-- +-- @returns The matching capture id +-- @returns The captured node +function Query:iter_captures(node, source, start, stop) + if type(source) == "number" and source == 0 then + source = vim.api.nvim_get_current_buf() + end + local raw_iter = node:_rawquery(self.query, true, start, stop) + local function iter() + local capture, captured_node, match = raw_iter() + if match ~= nil then + local active = self:match_preds(match, match.pattern, source) + match.active = active + if not active then + return iter() -- tail call: try next match + end + end + return capture, captured_node + end + return iter +end + +--- Iterates of the matches of self on a given range. +-- +-- @param node The node under witch the search will occur +-- @param buffer The source buffer to search +-- @param start The starting line of the search +-- @param stop The stoping line of the search (end-exclusive) +-- +-- @returns The matching pattern id +-- @returns The matching match +function Query:iter_matches(node, source, start, stop) + if type(source) == "number" and source == 0 then + source = vim.api.nvim_get_current_buf() + end + local raw_iter = node:_rawquery(self.query, false, start, stop) + local function iter() + local pattern, match = raw_iter() + if match ~= nil then + local active = self:match_preds(match, pattern, source) + if not active then + return iter() -- tail call: try next match + end + end + return pattern, match + end + return iter +end + +return M diff --git a/runtime/lua/vim/tshighlighter.lua b/runtime/lua/vim/tshighlighter.lua deleted file mode 100644 index 6465751ae8..0000000000 --- a/runtime/lua/vim/tshighlighter.lua +++ /dev/null @@ -1,116 +0,0 @@ -local a = vim.api - --- support reload for quick experimentation -local TSHighlighter = rawget(vim.treesitter, 'TSHighlighter') or {} -TSHighlighter.__index = TSHighlighter -local ts_hs_ns = a.nvim_create_namespace("treesitter_hl") - --- These are conventions defined by tree-sitter, though it --- needs to be user extensible also. --- TODO(bfredl): this is very much incomplete, we will need to --- go through a few tree-sitter provided queries and decide --- on translations that makes the most sense. -TSHighlighter.hl_map = { - keyword="Keyword", - string="String", - type="Type", - comment="Comment", - constant="Constant", - operator="Operator", - number="Number", - label="Label", - ["function"]="Function", - ["function.special"]="Function", -} - -function TSHighlighter.new(query, bufnr, ft) - local self = setmetatable({}, TSHighlighter) - self.parser = vim.treesitter.get_parser( - bufnr, - ft, - { - on_changedtree = function(...) self:on_changedtree(...) end, - on_lines = function() self.root = self.parser:parse():root() end - } - ) - - self.buf = self.parser.bufnr - - local tree = self.parser:parse() - self.root = tree:root() - self:set_query(query) - self.edit_count = 0 - self.redraw_count = 0 - self.line_count = {} - a.nvim_buf_set_option(self.buf, "syntax", "") - - -- Tricky: if syntax hasn't been enabled, we need to reload color scheme - -- but use synload.vim rather than syntax.vim to not enable - -- syntax FileType autocmds. Later on we should integrate with the - -- `:syntax` and `set syntax=...` machinery properly. - if vim.g.syntax_on ~= 1 then - vim.api.nvim_command("runtime! syntax/synload.vim") - end - return self -end - -local function is_highlight_name(capture_name) - local firstc = string.sub(capture_name, 1, 1) - return firstc ~= string.lower(firstc) -end - -function TSHighlighter:get_hl_from_capture(capture) - - local name = self.query.captures[capture] - - if is_highlight_name(name) then - -- From "Normal.left" only keep "Normal" - return vim.split(name, '.', true)[1] - else - -- Default to false to avoid recomputing - return TSHighlighter.hl_map[name] - end -end - -function TSHighlighter:set_query(query) - if type(query) == "string" then - query = vim.treesitter.parse_query(self.parser.lang, query) - end - self.query = query - - self.hl_cache = setmetatable({}, { - __index = function(table, capture) - local hl = self:get_hl_from_capture(capture) - rawset(table, capture, hl) - - return hl - end - }) - - self:on_changedtree({{self.root:range()}}) -end - -function TSHighlighter:on_changedtree(changes) - -- Get a fresh root - self.root = self.parser.tree:root() - - for _, ch in ipairs(changes or {}) do - -- Try to be as exact as possible - local changed_node = self.root:descendant_for_range(ch[1], ch[2], ch[3], ch[4]) - - a.nvim_buf_clear_namespace(self.buf, ts_hs_ns, ch[1], ch[3]) - - for capture, node in self.query:iter_captures(changed_node, self.buf, ch[1], ch[3] + 1) do - local start_row, start_col, end_row, end_col = node:range() - local hl = self.hl_cache[capture] - if hl then - a.nvim__buf_add_decoration(self.buf, ts_hs_ns, hl, - start_row, start_col, - end_row, end_col, - {}) - end - end - end -end - -return TSHighlighter diff --git a/runtime/lua/vim/uri.lua b/runtime/lua/vim/uri.lua index 9c3535c676..f1a12c72ec 100644 --- a/runtime/lua/vim/uri.lua +++ b/runtime/lua/vim/uri.lua @@ -7,6 +7,9 @@ local uri_decode do local schar = string.char + + --- Convert hex to char + --@private local function hex_to_char(hex) return schar(tonumber(hex, 16)) end @@ -34,6 +37,8 @@ do else tohex = function(b) return string.format("%02x", b) end end + + --@private local function percent_encode_char(char) return "%"..tohex(sbyte(char), 2) end @@ -45,10 +50,14 @@ do end +--@private local function is_windows_file_uri(uri) return uri:match('^file:///[a-zA-Z]:') ~= nil end +--- Get a URI from a file path. +--@param path (string): Path to file +--@return URI local function uri_from_fname(path) local volume_path, fname = path:match("^([a-zA-Z]:)(.*)") local is_windows = volume_path ~= nil @@ -67,6 +76,9 @@ end local URI_SCHEME_PATTERN = '^([a-zA-Z]+[a-zA-Z0-9+-.]*)://.*' +--- Get a URI from a bufnr +--@param bufnr (number): Buffer number +--@return URI local function uri_from_bufnr(bufnr) local fname = vim.api.nvim_buf_get_name(bufnr) local scheme = fname:match(URI_SCHEME_PATTERN) @@ -77,6 +89,9 @@ local function uri_from_bufnr(bufnr) end end +--- Get a filename from a URI +--@param uri (string): The URI +--@return Filename local function uri_to_fname(uri) local scheme = assert(uri:match(URI_SCHEME_PATTERN), 'URI must contain a scheme: ' .. uri) if scheme ~= 'file' then @@ -93,7 +108,10 @@ local function uri_to_fname(uri) return uri end --- Return or create a buffer for a uri. +--- Return or create a buffer for a uri. +--@param uri (string): The URI +--@return bufnr. +--@note Creates buffer but does not load it local function uri_to_bufnr(uri) local scheme = assert(uri:match(URI_SCHEME_PATTERN), 'URI must contain a scheme: ' .. uri) if scheme == 'file' then diff --git a/runtime/pack/dist/opt/cfilter/plugin/cfilter.vim b/runtime/pack/dist/opt/cfilter/plugin/cfilter.vim index 7a6464fc98..fe4455fe2e 100644 --- a/runtime/pack/dist/opt/cfilter/plugin/cfilter.vim +++ b/runtime/pack/dist/opt/cfilter/plugin/cfilter.vim @@ -1,15 +1,17 @@ " cfilter.vim: Plugin to filter entries from a quickfix/location list -" Last Change: May 12, 2018 -" Maintainer: Yegappan Lakshmanan (yegappan AT yahoo DOT com) -" Version: 1.0 +" Last Change: Aug 23, 2018 +" Maintainer: Yegappan Lakshmanan (yegappan AT yahoo DOT com) +" Version: 1.1 " " Commands to filter the quickfix list: -" :Cfilter[!] {pat} +" :Cfilter[!] /{pat}/ " Create a new quickfix list from entries matching {pat} in the current " quickfix list. Both the file name and the text of the entries are " matched against {pat}. If ! is supplied, then entries not matching -" {pat} are used. -" :Lfilter[!] {pat} +" {pat} are used. The pattern can be optionally enclosed using one of +" the following characters: ', ", /. If the pattern is empty, then the +" last used search pattern is used. +" :Lfilter[!] /{pat}/ " Same as :Cfilter but operates on the current location list. " if exists("loaded_cfilter") @@ -17,7 +19,7 @@ if exists("loaded_cfilter") endif let loaded_cfilter = 1 -func s:Qf_filter(qf, pat, bang) +func s:Qf_filter(qf, searchpat, bang) if a:qf let Xgetlist = function('getqflist') let Xsetlist = function('setqflist') @@ -28,14 +30,31 @@ func s:Qf_filter(qf, pat, bang) let cmd = ':Lfilter' . a:bang endif + let firstchar = a:searchpat[0] + let lastchar = a:searchpat[-1:] + if firstchar == lastchar && + \ (firstchar == '/' || firstchar == '"' || firstchar == "'") + let pat = a:searchpat[1:-2] + if pat == '' + " Use the last search pattern + let pat = @/ + endif + else + let pat = a:searchpat + endif + + if pat == '' + return + endif + if a:bang == '!' - let cond = 'v:val.text !~# a:pat && bufname(v:val.bufnr) !~# a:pat' + let cond = 'v:val.text !~# pat && bufname(v:val.bufnr) !~# pat' else - let cond = 'v:val.text =~# a:pat || bufname(v:val.bufnr) =~# a:pat' + let cond = 'v:val.text =~# pat || bufname(v:val.bufnr) =~# pat' endif let items = filter(Xgetlist(), cond) - let title = cmd . ' ' . a:pat + let title = cmd . ' /' . pat . '/' call Xsetlist([], ' ', {'title' : title, 'items' : items}) endfunc diff --git a/runtime/plugin/matchit.vim b/runtime/plugin/matchit.vim index 63be644062..d3583229fc 100644 --- a/runtime/plugin/matchit.vim +++ b/runtime/plugin/matchit.vim @@ -1,4 +1,4 @@ " Nvim: load the matchit plugin by default. -if stridx(&packpath, $VIMRUNTIME) >= 0 +if !exists("g:loaded_matchit") && stridx(&packpath, $VIMRUNTIME) >= 0 packadd matchit endif diff --git a/runtime/queries/c/highlights.scm b/runtime/queries/c/highlights.scm new file mode 100644 index 0000000000..96b43cf0d0 --- /dev/null +++ b/runtime/queries/c/highlights.scm @@ -0,0 +1,151 @@ +(identifier) @variable + +[ + "const" + "default" + "enum" + "extern" + "inline" + "return" + "sizeof" + "static" + "struct" + "typedef" + "union" + "volatile" + "goto" +] @keyword + +[ + "while" + "for" + "do" + "continue" + "break" +] @repeat + +[ + "if" + "else" + "case" + "switch" +] @conditional + +"#define" @constant.macro +[ + "#if" + "#ifdef" + "#ifndef" + "#else" + "#elif" + "#endif" + (preproc_directive) +] @keyword + +"#include" @include + +[ + "=" + + "-" + "*" + "/" + "+" + "%" + + "~" + "|" + "&" + "^" + "<<" + ">>" + + "->" + + "<" + "<=" + ">=" + ">" + "==" + "!=" + + "!" + "&&" + "||" + + "-=" + "+=" + "*=" + "/=" + "%=" + "|=" + "&=" + "^=" + "--" + "++" +] @operator + +[ + (true) + (false) +] @boolean + +[ "." ";" ":" "," ] @punctuation.delimiter + +(conditional_expression [ "?" ":" ] @conditional) + + +[ "(" ")" "[" "]" "{" "}"] @punctuation.bracket + +(string_literal) @string +(system_lib_string) @string + +(null) @constant.builtin +(number_literal) @number +(char_literal) @number + +(call_expression + function: (identifier) @function) +(call_expression + function: (field_expression + field: (field_identifier) @function)) +(function_declarator + declarator: (identifier) @function) +(preproc_function_def + name: (identifier) @function.macro) +[ + (preproc_arg) + (preproc_defined) +] @function.macro +; TODO (preproc_arg) @embedded + +(field_identifier) @property +(statement_identifier) @label + +[ +(type_identifier) +(primitive_type) +(sized_type_specifier) +(type_descriptor) + ] @type + +(declaration type: [(identifier) (type_identifier)] @type) +(cast_expression type: [(identifier) (type_identifier)] @type) +(sizeof_expression value: (parenthesized_expression (identifier) @type)) + +((identifier) @constant + (#match? @constant "^[A-Z][A-Z0-9_]+$")) + +(comment) @comment + +;; Parameters +(parameter_declaration + declarator: (identifier) @parameter) + +(parameter_declaration + declarator: (pointer_declarator) @parameter) + +(preproc_params + (identifier)) @parameter + +(ERROR) @error diff --git a/runtime/syntax/markdown.vim b/runtime/syntax/markdown.vim index 1955a7443e..17b61c2fa4 100644 --- a/runtime/syntax/markdown.vim +++ b/runtime/syntax/markdown.vim @@ -2,7 +2,7 @@ " Language: Markdown " Maintainer: Tim Pope <vimNOSPAM@tpope.org> " Filenames: *.markdown -" Last Change: 2016 Aug 29 +" Last Change: 2020 Jan 14 if exists("b:current_syntax") finish @@ -18,37 +18,46 @@ unlet! b:current_syntax if !exists('g:markdown_fenced_languages') let g:markdown_fenced_languages = [] endif +let s:done_include = {} for s:type in map(copy(g:markdown_fenced_languages),'matchstr(v:val,"[^=]*$")') + if has_key(s:done_include, matchstr(s:type,'[^.]*')) + continue + endif if s:type =~ '\.' let b:{matchstr(s:type,'[^.]*')}_subtype = matchstr(s:type,'\.\zs.*') endif exe 'syn include @markdownHighlight'.substitute(s:type,'\.','','g').' syntax/'.matchstr(s:type,'[^.]*').'.vim' unlet! b:current_syntax + let s:done_include[matchstr(s:type,'[^.]*')] = 1 endfor unlet! s:type +unlet! s:done_include -syn sync minlines=10 +if !exists('g:markdown_minlines') + let g:markdown_minlines = 50 +endif +execute 'syn sync minlines=' . g:markdown_minlines syn case ignore -syn match markdownValid '[<>]\c[a-z/$!]\@!' -syn match markdownValid '&\%(#\=\w*;\)\@!' +syn match markdownValid '[<>]\c[a-z/$!]\@!' transparent contains=NONE +syn match markdownValid '&\%(#\=\w*;\)\@!' transparent contains=NONE syn match markdownLineStart "^[<@]\@!" nextgroup=@markdownBlock,htmlSpecialChar syn cluster markdownBlock contains=markdownH1,markdownH2,markdownH3,markdownH4,markdownH5,markdownH6,markdownBlockquote,markdownListMarker,markdownOrderedListMarker,markdownCodeBlock,markdownRule -syn cluster markdownInline contains=markdownLineBreak,markdownLinkText,markdownItalic,markdownBold,markdownCode,markdownEscape,@htmlTop,markdownError +syn cluster markdownInline contains=markdownLineBreak,markdownLinkText,markdownItalic,markdownBold,markdownCode,markdownEscape,@htmlTop,markdownError,markdownValid syn match markdownH1 "^.\+\n=\+$" contained contains=@markdownInline,markdownHeadingRule,markdownAutomaticLink syn match markdownH2 "^.\+\n-\+$" contained contains=@markdownInline,markdownHeadingRule,markdownAutomaticLink syn match markdownHeadingRule "^[=-]\+$" contained -syn region markdownH1 matchgroup=markdownHeadingDelimiter start="##\@!" end="#*\s*$" keepend oneline contains=@markdownInline,markdownAutomaticLink contained -syn region markdownH2 matchgroup=markdownHeadingDelimiter start="###\@!" end="#*\s*$" keepend oneline contains=@markdownInline,markdownAutomaticLink contained -syn region markdownH3 matchgroup=markdownHeadingDelimiter start="####\@!" end="#*\s*$" keepend oneline contains=@markdownInline,markdownAutomaticLink contained -syn region markdownH4 matchgroup=markdownHeadingDelimiter start="#####\@!" end="#*\s*$" keepend oneline contains=@markdownInline,markdownAutomaticLink contained -syn region markdownH5 matchgroup=markdownHeadingDelimiter start="######\@!" end="#*\s*$" keepend oneline contains=@markdownInline,markdownAutomaticLink contained -syn region markdownH6 matchgroup=markdownHeadingDelimiter start="#######\@!" end="#*\s*$" keepend oneline contains=@markdownInline,markdownAutomaticLink contained +syn region markdownH1 matchgroup=markdownH1Delimiter start="##\@!" end="#*\s*$" keepend oneline contains=@markdownInline,markdownAutomaticLink contained +syn region markdownH2 matchgroup=markdownH2Delimiter start="###\@!" end="#*\s*$" keepend oneline contains=@markdownInline,markdownAutomaticLink contained +syn region markdownH3 matchgroup=markdownH3Delimiter start="####\@!" end="#*\s*$" keepend oneline contains=@markdownInline,markdownAutomaticLink contained +syn region markdownH4 matchgroup=markdownH4Delimiter start="#####\@!" end="#*\s*$" keepend oneline contains=@markdownInline,markdownAutomaticLink contained +syn region markdownH5 matchgroup=markdownH5Delimiter start="######\@!" end="#*\s*$" keepend oneline contains=@markdownInline,markdownAutomaticLink contained +syn region markdownH6 matchgroup=markdownH6Delimiter start="#######\@!" end="#*\s*$" keepend oneline contains=@markdownInline,markdownAutomaticLink contained syn match markdownBlockquote ">\%(\s\|$\)" contained nextgroup=@markdownBlock @@ -70,31 +79,40 @@ syn region markdownUrlTitle matchgroup=markdownUrlTitleDelimiter start=+"+ end=+ syn region markdownUrlTitle matchgroup=markdownUrlTitleDelimiter start=+'+ end=+'+ keepend contained syn region markdownUrlTitle matchgroup=markdownUrlTitleDelimiter start=+(+ end=+)+ keepend contained -syn region markdownLinkText matchgroup=markdownLinkTextDelimiter start="!\=\[\%(\_[^]]*]\%( \=[[(]\)\)\@=" end="\]\%( \=[[(]\)\@=" nextgroup=markdownLink,markdownId skipwhite contains=@markdownInline,markdownLineStart +syn region markdownLinkText matchgroup=markdownLinkTextDelimiter start="!\=\[\%(\%(\_[^][]\|\[\_[^][]*\]\)*]\%( \=[[(]\)\)\@=" end="\]\%( \=[[(]\)\@=" nextgroup=markdownLink,markdownId skipwhite contains=@markdownInline,markdownLineStart syn region markdownLink matchgroup=markdownLinkDelimiter start="(" end=")" contains=markdownUrl keepend contained syn region markdownId matchgroup=markdownIdDelimiter start="\[" end="\]" keepend contained syn region markdownAutomaticLink matchgroup=markdownUrlDelimiter start="<\%(\w\+:\|[[:alnum:]_+-]\+@\)\@=" end=">" keepend oneline -let s:concealends = has('conceal') ? ' concealends' : '' -exe 'syn region markdownItalic matchgroup=markdownItalicDelimiter start="\S\@<=\*\|\*\S\@=" end="\S\@<=\*\|\*\S\@=" keepend contains=markdownLineStart' . s:concealends -exe 'syn region markdownItalic matchgroup=markdownItalicDelimiter start="\S\@<=_\|_\S\@=" end="\S\@<=_\|_\S\@=" keepend contains=markdownLineStart' . s:concealends -exe 'syn region markdownBold matchgroup=markdownBoldDelimiter start="\S\@<=\*\*\|\*\*\S\@=" end="\S\@<=\*\*\|\*\*\S\@=" keepend contains=markdownLineStart,markdownItalic' . s:concealends -exe 'syn region markdownBold matchgroup=markdownBoldDelimiter start="\S\@<=__\|__\S\@=" end="\S\@<=__\|__\S\@=" keepend contains=markdownLineStart,markdownItalic' . s:concealends -exe 'syn region markdownBoldItalic matchgroup=markdownBoldItalicDelimiter start="\S\@<=\*\*\*\|\*\*\*\S\@=" end="\S\@<=\*\*\*\|\*\*\*\S\@=" keepend contains=markdownLineStart' . s:concealends -exe 'syn region markdownBoldItalic matchgroup=markdownBoldItalicDelimiter start="\S\@<=___\|___\S\@=" end="\S\@<=___\|___\S\@=" keepend contains=markdownLineStart' . s:concealends +let s:concealends = '' +if has('conceal') && get(g:, 'markdown_syntax_conceal', 1) == 1 + let s:concealends = ' concealends' +endif +exe 'syn region markdownItalic matchgroup=markdownItalicDelimiter start="\S\@<=\*\|\*\S\@=" end="\S\@<=\*\|\*\S\@=" skip="\\\*" contains=markdownLineStart,@Spell' . s:concealends +exe 'syn region markdownItalic matchgroup=markdownItalicDelimiter start="\w\@<!_\S\@=" end="\S\@<=_\w\@!" skip="\\_" contains=markdownLineStart,@Spell' . s:concealends +exe 'syn region markdownBold matchgroup=markdownBoldDelimiter start="\S\@<=\*\*\|\*\*\S\@=" end="\S\@<=\*\*\|\*\*\S\@=" skip="\\\*" contains=markdownLineStart,markdownItalic,@Spell' . s:concealends +exe 'syn region markdownBold matchgroup=markdownBoldDelimiter start="\w\@<!__\S\@=" end="\S\@<=__\w\@!" skip="\\_" contains=markdownLineStart,markdownItalic,@Spell' . s:concealends +exe 'syn region markdownBoldItalic matchgroup=markdownBoldItalicDelimiter start="\S\@<=\*\*\*\|\*\*\*\S\@=" end="\S\@<=\*\*\*\|\*\*\*\S\@=" skip="\\\*" contains=markdownLineStart,@Spell' . s:concealends +exe 'syn region markdownBoldItalic matchgroup=markdownBoldItalicDelimiter start="\w\@<!___\S\@=" end="\S\@<=___\w\@!" skip="\\_" contains=markdownLineStart,@Spell' . s:concealends syn region markdownCode matchgroup=markdownCodeDelimiter start="`" end="`" keepend contains=markdownLineStart syn region markdownCode matchgroup=markdownCodeDelimiter start="`` \=" end=" \=``" keepend contains=markdownLineStart -syn region markdownCode matchgroup=markdownCodeDelimiter start="^\s*```.*$" end="^\s*```\ze\s*$" keepend +syn region markdownCode matchgroup=markdownCodeDelimiter start="^\s*````*.*$" end="^\s*````*\ze\s*$" keepend syn match markdownFootnote "\[^[^\]]\+\]" syn match markdownFootnoteDefinition "^\[^[^\]]\+\]:" if main_syntax ==# 'markdown' + let s:done_include = {} for s:type in g:markdown_fenced_languages - exe 'syn region markdownHighlight'.substitute(matchstr(s:type,'[^=]*$'),'\..*','','').' matchgroup=markdownCodeDelimiter start="^\s*```\s*'.matchstr(s:type,'[^=]*').'\>.*$" end="^\s*```\ze\s*$" keepend contains=@markdownHighlight'.substitute(matchstr(s:type,'[^=]*$'),'\.','','g') + if has_key(s:done_include, matchstr(s:type,'[^.]*')) + continue + endif + exe 'syn region markdownHighlight'.substitute(matchstr(s:type,'[^=]*$'),'\..*','','').' matchgroup=markdownCodeDelimiter start="^\s*````*\s*\%({.\{-}\.\)\='.matchstr(s:type,'[^=]*').'}\=\S\@!.*$" end="^\s*````*\ze\s*$" keepend contains=@markdownHighlight'.substitute(matchstr(s:type,'[^=]*$'),'\.','','g') . s:concealends + let s:done_include[matchstr(s:type,'[^.]*')] = 1 endfor unlet! s:type + unlet! s:done_include endif syn match markdownEscape "\\[][\\`*_{}()<>#+.!-]" @@ -107,6 +125,12 @@ hi def link markdownH4 htmlH4 hi def link markdownH5 htmlH5 hi def link markdownH6 htmlH6 hi def link markdownHeadingRule markdownRule +hi def link markdownH1Delimiter markdownHeadingDelimiter +hi def link markdownH2Delimiter markdownHeadingDelimiter +hi def link markdownH3Delimiter markdownHeadingDelimiter +hi def link markdownH4Delimiter markdownHeadingDelimiter +hi def link markdownH5Delimiter markdownHeadingDelimiter +hi def link markdownH6Delimiter markdownHeadingDelimiter hi def link markdownHeadingDelimiter Delimiter hi def link markdownOrderedListMarker markdownListMarker hi def link markdownListMarker htmlTagName diff --git a/runtime/syntax/resolv.vim b/runtime/syntax/resolv.vim index a879116a5f..9a2dec51ce 100644 --- a/runtime/syntax/resolv.vim +++ b/runtime/syntax/resolv.vim @@ -2,12 +2,19 @@ " Language: resolver configuration file " Maintainer: Radu Dineiu <radu.dineiu@gmail.com> " URL: https://raw.github.com/rid9/vim-resolv/master/resolv.vim -" Last Change: 2013 May 21 -" Version: 1.0 +" Last Change: 2020 Mar 10 +" Version: 1.4 " " Credits: " David Necas (Yeti) <yeti@physics.muni.cz> " Stefano Zacchiroli <zack@debian.org> +" DJ Lucas <dj@linuxfromscratch.org> +" +" Changelog: +" - 1.4: Added IPv6 support for sortlist. +" - 1.3: Added IPv6 support for IPv4 dot-decimal notation. +" - 1.2: Added new options. +" - 1.1: Added IPv6 support. " quit when a syntax file was already loaded if exists("b:current_syntax") @@ -29,11 +36,47 @@ syn match resolvIP contained /\%(\d\{1,4}\.\)\{3}\d\{1,4}/ contains=@resolvIPClu syn match resolvIPNetmask contained /\%(\d\{1,4}\.\)\{3}\d\{1,4}\%(\/\%(\%(\d\{1,4}\.\)\{,3}\d\{1,4}\)\)\?/ contains=resolvOperator,@resolvIPCluster syn match resolvHostname contained /\w\{-}\.[-0-9A-Za-z_\.]*/ -" Particular +" Nameserver IPv4 syn match resolvIPNameserver contained /\%(\%(\d\{1,4}\.\)\{3}\d\{1,4}\%(\s\|$\)\)\+/ contains=@resolvIPCluster + +" Nameserver IPv6 +syn match resolvIPNameserver contained /\<\%(\x\{1,4}:\)\{6}\%(\x\{1,4}:\x\{1,4}\)\>/ +syn match resolvIPNameserver contained /\s\@<=::\%(\x\{1,4}:\)\{,6}\x\{1,4}\>/ +syn match resolvIPNameserver contained /\s\@<=::\%(\x\{1,4}:\)\{,5}\%(\d\{1,4}\.\)\{3}\d\{1,4}\>/ +syn match resolvIPNameserver contained /\<\%(\x\{1,4}:\)\{1}:\%(\x\{1,4}:\)\{,5}\x\{1,4}\>/ +syn match resolvIPNameserver contained /\<\%(\x\{1,4}:\)\{1}:\%(\x\{1,4}:\)\{,4}\%(\d\{1,4}\.\)\{3}\d\{1,4}\>/ +syn match resolvIPNameserver contained /\<\%(\x\{1,4}:\)\{2}:\%(\x\{1,4}:\)\{,4}\x\{1,4}\>/ +syn match resolvIPNameserver contained /\<\%(\x\{1,4}:\)\{2}:\%(\x\{1,4}:\)\{,3}\%(\d\{1,4}\.\)\{3}\d\{1,4}\>/ +syn match resolvIPNameserver contained /\<\%(\x\{1,4}:\)\{3}:\%(\x\{1,4}:\)\{,3}\x\{1,4}\>/ +syn match resolvIPNameserver contained /\<\%(\x\{1,4}:\)\{3}:\%(\x\{1,4}:\)\{,2}\%(\d\{1,4}\.\)\{3}\d\{1,4}\>/ +syn match resolvIPNameserver contained /\<\%(\x\{1,4}:\)\{4}:\%(\x\{1,4}:\)\{,2}\x\{1,4}\>/ +syn match resolvIPNameserver contained /\<\%(\x\{1,4}:\)\{4}:\%(\x\{1,4}:\)\{,1}\%(\d\{1,4}\.\)\{3}\d\{1,4}\>/ +syn match resolvIPNameserver contained /\<\%(\x\{1,4}:\)\{5}:\%(\d\{1,4}\.\)\{3}\d\{1,4}\>/ +syn match resolvIPNameserver contained /\<\%(\x\{1,4}:\)\{6}:\x\{1,4}\>/ +syn match resolvIPNameserver contained /\<\%(\x\{1,4}:\)\{1,7}:\%(\s\|;\|$\)\@=/ + +" Search hostname syn match resolvHostnameSearch contained /\%(\%([-0-9A-Za-z_]\+\.\)*[-0-9A-Za-z_]\+\.\?\%(\s\|$\)\)\+/ + +" Sortlist IPv4 syn match resolvIPNetmaskSortList contained /\%(\%(\d\{1,4}\.\)\{3}\d\{1,4}\%(\/\%(\%(\d\{1,4}\.\)\{,3}\d\{1,4}\)\)\?\%(\s\|$\)\)\+/ contains=resolvOperator,@resolvIPCluster +" Sortlist IPv6 +syn match resolvIPNetmaskSortList contained /\<\%(\x\{1,4}:\)\{6}\%(\x\{1,4}:\x\{1,4}\)\%(\/\d\{1,3}\)\?\>/ +syn match resolvIPNetmaskSortList contained /\s\@<=::\%(\x\{1,4}:\)\{,6}\x\{1,4}\%(\/\d\{1,3}\)\?\>/ +syn match resolvIPNetmaskSortList contained /\s\@<=::\%(\x\{1,4}:\)\{,5}\%(\d\{1,4}\.\)\{3}\d\{1,4}\%(\/\d\{1,3}\)\?\>/ +syn match resolvIPNetmaskSortList contained /\<\%(\x\{1,4}:\)\{1}:\%(\x\{1,4}:\)\{,5}\x\{1,4}\%(\/\d\{1,3}\)\?\>/ +syn match resolvIPNetmaskSortList contained /\<\%(\x\{1,4}:\)\{1}:\%(\x\{1,4}:\)\{,4}\%(\d\{1,4}\.\)\{3}\d\{1,4}\%(\/\d\{1,3}\)\?\>/ +syn match resolvIPNetmaskSortList contained /\<\%(\x\{1,4}:\)\{2}:\%(\x\{1,4}:\)\{,4}\x\{1,4}\%(\/\d\{1,3}\)\?\>/ +syn match resolvIPNetmaskSortList contained /\<\%(\x\{1,4}:\)\{2}:\%(\x\{1,4}:\)\{,3}\%(\d\{1,4}\.\)\{3}\d\{1,4}\%(\/\d\{1,3}\)\?\>/ +syn match resolvIPNetmaskSortList contained /\<\%(\x\{1,4}:\)\{3}:\%(\x\{1,4}:\)\{,3}\x\{1,4}\%(\/\d\{1,3}\)\?\>/ +syn match resolvIPNetmaskSortList contained /\<\%(\x\{1,4}:\)\{3}:\%(\x\{1,4}:\)\{,2}\%(\d\{1,4}\.\)\{3}\d\{1,4}\%(\/\d\{1,3}\)\?\>/ +syn match resolvIPNetmaskSortList contained /\<\%(\x\{1,4}:\)\{4}:\%(\x\{1,4}:\)\{,2}\x\{1,4}\%(\/\d\{1,3}\)\?\>/ +syn match resolvIPNetmaskSortList contained /\<\%(\x\{1,4}:\)\{4}:\%(\x\{1,4}:\)\{,1}\%(\d\{1,4}\.\)\{3}\d\{1,4}\%(\/\d\{1,3}\)\?\>/ +syn match resolvIPNetmaskSortList contained /\<\%(\x\{1,4}:\)\{5}:\%(\d\{1,4}\.\)\{3}\d\{1,4}\%(\/\d\{1,3}\)\?\>/ +syn match resolvIPNetmaskSortList contained /\<\%(\x\{1,4}:\)\{6}:\x\{1,4}\%(\/\d\{1,3}\)\?\>/ +syn match resolvIPNetmaskSortList contained /\<\%(\x\{1,4}:\)\{1,7}:\%(\s\|;\|$\)\@=\%(\/\d\{1,3}\)\?/ + " Identifiers syn match resolvNameserver /^\s*nameserver\>/ nextgroup=resolvIPNameserver skipwhite syn match resolvLwserver /^\s*lwserver\>/ nextgroup=resolvIPNameserver skipwhite @@ -43,13 +86,12 @@ syn match resolvSortList /^\s*sortlist\>/ nextgroup=resolvIPNetmaskSortList skip syn match resolvOptions /^\s*options\>/ nextgroup=resolvOption skipwhite " Options -syn match resolvOption /\<\%(debug\|no_tld_query\|rotate\|no-check-names\|inet6\)\>/ contained nextgroup=resolvOption skipwhite +syn match resolvOption /\<\%(debug\|no_tld_query\|no-tld-query\|rotate\|no-check-names\|inet6\|ip6-bytestring\|\%(no-\)\?ip6-dotint\|edns0\|single-request\%(-reopen\)\?\|use-vc\)\>/ contained nextgroup=resolvOption skipwhite syn match resolvOption /\<\%(ndots\|timeout\|attempts\):\d\+\>/ contained contains=resolvOperator nextgroup=resolvOption skipwhite " Additional errors syn match resolvError /^search .\{257,}/ - hi def link resolvIP Number hi def link resolvIPNetmask Number hi def link resolvHostname String @@ -72,7 +114,6 @@ hi def link resolvError Error hi def link resolvIPError Error hi def link resolvIPSpecial Special - let b:current_syntax = "resolv" " vim: ts=8 ft=vim diff --git a/runtime/syntax/tex.vim b/runtime/syntax/tex.vim index b3a8f96b6b..ec0f7b99bd 100644 --- a/runtime/syntax/tex.vim +++ b/runtime/syntax/tex.vim @@ -1,8 +1,8 @@ " Vim syntax file " Language: TeX " Maintainer: Charles E. Campbell <NcampObell@SdrPchip.AorgM-NOSPAM> -" Last Change: Jun 07, 2020 -" Version: 118 +" Last Change: Jun 29, 2020 +" Version: 119 " URL: http://www.drchip.org/astronaut/vim/index.html#SYNTAX_TEX " " Notes: {{{1 @@ -147,6 +147,11 @@ if exists("g:tex_nospell") && g:tex_nospell else let s:tex_nospell = 0 endif +if exists("g:tex_matchcheck") + let s:tex_matchcheck= g:tex_matchcheck +else + let s:tex_matchcheck= '[({[]' +endif if exists("g:tex_excludematcher") let s:tex_excludematcher= g:tex_excludematcher else @@ -205,27 +210,41 @@ if !exists("g:tex_no_math") endif endif -" Try to flag {} and () mismatches: {{{1 +" Try to flag {}, [], and () mismatches: {{{1 if s:tex_fast =~# 'm' if !s:tex_no_error - syn region texMatcher matchgroup=Delimiter start="{" skip="\\\\\|\\[{}]" end="}" transparent contains=@texMatchGroup,texError - syn region texMatcher matchgroup=Delimiter start="\[" end="]" transparent contains=@texMatchGroup,texError,@NoSpell - syn region texMatcherNM matchgroup=Delimiter start="{" skip="\\\\\|\\[{}]" end="}" transparent contains=@texMatchNMGroup,texError - syn region texMatcherNM matchgroup=Delimiter start="\[" end="]" transparent contains=@texMatchNMGroup,texError,@NoSpell + if s:tex_matchcheck =~ '{' + syn region texMatcher matchgroup=Delimiter start="{" skip="\\\\\|\\[{}]" end="}" transparent contains=@texMatchGroup,texError + syn region texMatcherNM matchgroup=Delimiter start="{" skip="\\\\\|\\[{}]" end="}" transparent contains=@texMatchNMGroup,texError + endif + if s:tex_matchcheck =~ '\[' + syn region texMatcher matchgroup=Delimiter start="\[" end="]" transparent contains=@texMatchGroup,texError,@NoSpell + syn region texMatcherNM matchgroup=Delimiter start="\[" end="]" transparent contains=@texMatchNMGroup,texError,@NoSpell + endif else - syn region texMatcher matchgroup=Delimiter start="{" skip="\\\\\|\\[{}]" end="}" transparent contains=@texMatchGroup - syn region texMatcher matchgroup=Delimiter start="\[" end="]" transparent contains=@texMatchGroup - syn region texMatcherNM matchgroup=Delimiter start="{" skip="\\\\\|\\[{}]" end="}" transparent contains=@texMatchNMGroup - syn region texMatcherNM matchgroup=Delimiter start="\[" end="]" transparent contains=@texMatchNMGroup + if s:tex_matchcheck =~ '{' + syn region texMatcher matchgroup=Delimiter start="{" skip="\\\\\|\\[{}]" end="}" transparent contains=@texMatchGroup + syn region texMatcherNM matchgroup=Delimiter start="{" skip="\\\\\|\\[{}]" end="}" transparent contains=@texMatchNMGroup + endif + if s:tex_matchcheck =~ '\[' + syn region texMatcher matchgroup=Delimiter start="\[" end="]" transparent contains=@texMatchGroup + syn region texMatcherNM matchgroup=Delimiter start="\[" end="]" transparent contains=@texMatchNMGroup + endif endif - if !s:tex_nospell - syn region texParen start="(" end=")" transparent contains=@texMatchGroup,@Spell - else - syn region texParen start="(" end=")" transparent contains=@texMatchGroup + if s:tex_matchcheck =~ '(' + if !s:tex_nospell + syn region texParen start="(" end=")" transparent contains=@texMatchGroup,@Spell + else + syn region texParen start="(" end=")" transparent contains=@texMatchGroup + endif endif endif if !s:tex_no_error - syn match texError "[}\])]" + if s:tex_matchcheck =~ '(' + syn match texError "[}\]]" + else + syn match texError "[}\])]" + endif endif if s:tex_fast =~# 'M' if !exists("g:tex_no_math") @@ -756,7 +775,7 @@ if has("conceal") && &enc == 'utf-8' \ ['ldots' , 'โฆ'], \ ['le' , 'โค'], \ ['left|' , '|'], - \ ['left\|' , 'โ'], + \ ['left\\|' , 'โ'], \ ['left(' , '('], \ ['left\[' , '['], \ ['left\\{' , '{'], diff --git a/runtime/syntax/typescript.vim b/runtime/syntax/typescript.vim index bc382610a9..767ba56d42 100644 --- a/runtime/syntax/typescript.vim +++ b/runtime/syntax/typescript.vim @@ -1,10 +1,13 @@ " Vim syntax file " Language: TypeScript -" Maintainer: Bram Moolenaar -" Last Change: 2019 Jun 07 +" Maintainer: Bram Moolenaar, Herrington Darkholme +" Last Change: 2019 Nov 30 " Based On: Herrington Darkholme's yats.vim -" Changes: See https:github.com/HerringtonDarkholme/yats.vim -" Credits: See yats.vim +" Changes: Go to https:github.com/HerringtonDarkholme/yats.vim for recent changes. +" Origin: https://github.com/othree/yajs +" Credits: Kao Wei-Ko(othree), Jose Elera Campana, Zhao Yi, Claudio Fleiner, Scott Shattuck +" (This file is based on their hard work), gumnos (From the #vim +" IRC Channel in Freenode) " This is the same syntax that is in yats.vim, but: " - flattened into one file @@ -21,6 +24,7 @@ endif let s:cpo_save = &cpo set cpo&vim +" this region is NOT used in TypeScriptReact " nextgroup doesn't contain objectLiteral, let outer region contains it syntax region typescriptTypeCast matchgroup=typescriptTypeBrackets \ start=/< \@!/ end=/>/ @@ -28,2045 +32,11 @@ syntax region typescriptTypeCast matchgroup=typescriptTypeBrackets \ nextgroup=@typescriptExpression \ contained skipwhite oneline -" runtime syntax/common.vim -" NOTE: this results in accurate highlighting, but can be slow. -syntax sync fromstart +""""""""""""""""""""""""""""""""""""""""""""""""""" +" Source the part common with typescriptreact.vim +source <sfile>:h/typescriptcommon.vim -"Dollar sign is permitted anywhere in an identifier -setlocal iskeyword-=$ -if main_syntax == 'typescript' || main_syntax == 'typescript.tsx' - setlocal iskeyword+=$ - " syntax cluster htmlJavaScript contains=TOP -endif - -" 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 - - -"runtime syntax/basic/identifiers.vim -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 - -"runtime syntax/basic/literal.vim -"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{4,5}})|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 - \ 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_]*\|\.\d[0-9]*/ - \ nextgroup=typescriptExponent,@typescriptSymbols skipwhite skipempty -syntax match typescriptExponent /[eE][+-]\=\d[0-9]*\>/ - \ nextgroup=@typescriptSymbols skipwhite skipempty contained - - -" runtime syntax/basic/object.vim -syntax region typescriptObjectLiteral matchgroup=typescriptBraces - \ start=/{/ end=/}/ - \ contains=@typescriptComments,typescriptObjectLabel,typescriptStringProperty,typescriptComputedPropertyName - \ fold 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 - -"runtime syntax/basic/symbols.vim -" + - ^ ~ -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 -" 3: &&, &=, & -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 - -"runtime syntax/basic/keyword.vim -"Import -syntax keyword typescriptImport from as import -syntax keyword typescriptExport export -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=typescriptVariableDeclaration - \ skipwhite skipempty skipnl - -syntax keyword typescriptVariable const - \ nextgroup=typescriptEnum,typescriptVariableDeclaration - \ skipwhite - -syntax match typescriptVariableDeclaration /[A-Za-z_$]\k*/ - \ nextgroup=typescriptTypeAnnotation,typescriptAssign - \ contained skipwhite skipempty skipnl - -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 - -"runtime syntax/basic/doc.vim -"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 typescriptLineComment "//.*" - \ contains=@Spell,typescriptCommentTodo,typescriptRef -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 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 main_syntax == "typescript" - syntax sync clear - syntax sync ccomment typescriptComment minlines=200 -endif - -syntax case match - -"runtime syntax/basic/type.vim -" Types -syntax match typescriptOptionalMark /?/ contained - -syntax region typescriptTypeParameters matchgroup=typescriptTypeBrackets - \ start=/</ end=/>/ - \ contains=typescriptTypeParameter - \ contained - -syntax match typescriptTypeParameter /\K\k*/ - \ nextgroup=typescriptConstraint,typescriptGenericDefault - \ 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, - \ typescriptReadonlyArrayKeyword - -syntax region typescriptStringLiteralType contained - \ start=/\z(["']\)/ skip=/\\\\\|\\\z1\|\\\n/ end=/\z1\|$/ - \ nextgroup=typescriptUnion - \ skipwhite skipempty - -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 fold - -syntax cluster typescriptTypeMember contains= - \ @typescriptCallSignature, - \ typescriptConstructSignature, - \ typescriptIndexSignature, - \ @typescriptMembers - -syntax region typescriptTupleType matchgroup=typescriptBraces - \ start=/\[/ end=/\]/ - \ contains=@typescriptType - \ contained skipwhite oneline - -syntax cluster typescriptTypeOperator - \ contains=typescriptUnion,typescriptTypeBracket - -syntax match typescriptUnion /|\|&/ 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 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, - \ 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) - "runtime syntax/yats.vim - "runtime syntax/yats/typescript.vim - syntax keyword typescriptGlobal containedin=typescriptIdentifierName Function Boolean - syntax keyword typescriptGlobal containedin=typescriptIdentifierName Error EvalError - 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 - - "runtime syntax/yats/es6-number.vim - 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 - - "runtime syntax/yats/es6-string.vim - 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 - - "runtime syntax/yats/es6-array.vim - 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 - - "runtime syntax/yats/es6-object.vim - 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 - - "runtime syntax/yats/es6-symbol.vim - 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 - - "runtime syntax/yats/es6-function.vim - 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 - - "runtime syntax/yats/es6-math.vim - 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 - - "runtime syntax/yats/es6-date.vim - 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 - - "runtime syntax/yats/es6-json.vim - 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 - - "runtime syntax/yats/es6-regexp.vim - 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 - - "runtime syntax/yats/es6-map.vim - 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 - - "runtime syntax/yats/es6-set.vim - 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 - - "runtime syntax/yats/es6-proxy.vim - 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 - - "runtime syntax/yats/es6-promise.vim - 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 - - "runtime syntax/yats/es6-reflect.vim - 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 - - "runtime syntax/yats/ecma-402.vim - 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 - - "runtime syntax/yats/node.vim - 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 typescriptGlobal containedin=typescriptIdentifierName describe it test - syntax keyword typescriptGlobal containedin=typescriptIdentifierName before after - syntax keyword typescriptGlobal containedin=typescriptIdentifierName beforeEach afterEach - syntax keyword typescriptGlobal containedin=typescriptIdentifierName beforeAll afterAll - syntax keyword typescriptGlobal containedin=typescriptIdentifierName expect assert - - "runtime syntax/yats/web.vim - 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 - - "runtime syntax/yats/web-window.vim - 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 - - "runtime syntax/yats/web-navigator.vim - 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 - - "runtime syntax/yats/web-location.vim - 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 - - "runtime syntax/yats/web-history.vim - 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 - - "runtime syntax/yats/web-console.vim - 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 - - "runtime syntax/yats/web-xhr.vim - 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 - - "runtime syntax/yats/web-blob.vim - 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 - - "runtime syntax/yats/web-crypto.vim - 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 - - "runtime syntax/yats/web-fetch.vim - 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 - - "runtime syntax/yats/web-service-worker.vim - 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 - - "runtime syntax/yats/web-encoding.vim - 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 - - "runtime syntax/yats/web-geo.vim - 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 - - "runtime syntax/yats/web-network.vim - 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 - - "runtime syntax/yats/web-payment.vim - 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 - - "runtime syntax/yats/dom-node.vim - 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 - - "runtime syntax/yats/dom-elem.vim - 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 - - "runtime syntax/yats/dom-document.vim - 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 - - "runtime syntax/yats/dom-event.vim - 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 - - "runtime syntax/yats/dom-storage.vim - 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 - - "runtime syntax/yats/dom-form.vim - 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 - - "runtime syntax/yats/css.vim - 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 - - "runtime syntax/yats/event.vim - 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 -"runtime syntax/basic/patch.vim -" patch for generated code -syntax keyword typescriptGlobal Promise - \ nextgroup=typescriptGlobalPromiseDot,typescriptFuncCallArg,typescriptTypeArguments oneline -syntax keyword typescriptGlobal Map WeakMap - \ nextgroup=typescriptGlobalPromiseDot,typescriptFuncCallArg,typescriptTypeArguments oneline - -"runtime syntax/basic/members.vim -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 - -"runtime syntax/basic/class.vim -"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=typescriptTypeParameter - \ 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=typescriptTypeParameter - \ 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 - -"runtime syntax/basic/cluster.vim -"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 - -"runtime syntax/basic/function.vim -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*=>/ - \ 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 - -"runtime syntax/basic/decorator.vim -syntax match typescriptDecorator /@\([_$a-zA-Z][_$a-zA-Z0-9]*\.\)*[_$a-zA-Z][_$a-zA-Z0-9]*\>/ - \ nextgroup=typescriptArgumentList - \ 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 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 typescriptStringMember String -hi def link typescriptTemplate String -hi def link typescriptEventString String -hi def link typescriptASCII Special -hi def link typescriptTemplateSB Label -hi def link typescriptRegexpString String -hi def link typescriptGlobal Constant -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 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 typescriptExponent Number -hi def link typescriptBoolean Boolean -hi def link typescriptObjectLabel typescriptLabel -hi def link typescriptLabel Label -hi def link typescriptStringProperty String -hi def link typescriptImport Special -hi def link typescriptAmbientDeclaration Special -hi def link typescriptExport 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 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 link typeScript NONE let b:current_syntax = "typescript" if main_syntax == 'typescript' diff --git a/runtime/syntax/typescriptcommon.vim b/runtime/syntax/typescriptcommon.vim new file mode 100644 index 0000000000..ff53168329 --- /dev/null +++ b/runtime/syntax/typescriptcommon.vim @@ -0,0 +1,2067 @@ +" Vim syntax file +" Language: TypeScript and TypeScriptReact +" Maintainer: Bram Moolenaar, Herrington Darkholme +" Last Change: 2019 Nov 30 +" 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 + +" 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 + + +"runtime syntax/basic/identifiers.vim +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 + +"runtime syntax/basic/literal.vim +"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{4,5}})|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 + \ 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_]*\|\.\d[0-9]*/ + \ nextgroup=typescriptExponent,@typescriptSymbols skipwhite skipempty +syntax match typescriptExponent /[eE][+-]\=\d[0-9]*\>/ + \ nextgroup=@typescriptSymbols skipwhite skipempty contained + + +" runtime syntax/basic/object.vim +syntax region typescriptObjectLiteral matchgroup=typescriptBraces + \ start=/{/ end=/}/ + \ contains=@typescriptComments,typescriptObjectLabel,typescriptStringProperty,typescriptComputedPropertyName + \ fold 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 + +"runtime syntax/basic/symbols.vim +" + - ^ ~ +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 +" 3: &&, &=, & +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 + +"runtime syntax/basic/keyword.vim +"Import +syntax keyword typescriptImport from as import +syntax keyword typescriptExport export +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=typescriptVariableDeclaration + \ skipwhite skipempty skipnl + +syntax keyword typescriptVariable const + \ nextgroup=typescriptEnum,typescriptVariableDeclaration + \ skipwhite + +syntax match typescriptVariableDeclaration /[A-Za-z_$]\k*/ + \ nextgroup=typescriptTypeAnnotation,typescriptAssign + \ contained skipwhite skipempty skipnl + +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 + +"runtime syntax/basic/doc.vim +"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 typescriptLineComment "//.*" + \ contains=@Spell,typescriptCommentTodo,typescriptRef +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 main_syntax == "typescript" + syntax sync clear + syntax sync ccomment typescriptComment minlines=200 +endif + +syntax case match + +"runtime syntax/basic/type.vim +" Types +syntax match typescriptOptionalMark /?/ contained + +syntax region typescriptTypeParameters matchgroup=typescriptTypeBrackets + \ start=/</ end=/>/ + \ contains=typescriptTypeParameter + \ contained + +syntax match typescriptTypeParameter /\K\k*/ + \ nextgroup=typescriptConstraint,typescriptGenericDefault + \ 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, + \ typescriptReadonlyArrayKeyword, + \ typescriptAssertType + +syntax region typescriptStringLiteralType contained + \ start=/\z(["']\)/ skip=/\\\\\|\\\z1\|\\\n/ end=/\z1\|$/ + \ nextgroup=typescriptUnion + \ skipwhite skipempty + +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 fold + +syntax cluster typescriptTypeMember contains= + \ @typescriptCallSignature, + \ typescriptConstructSignature, + \ typescriptIndexSignature, + \ @typescriptMembers + +syntax region typescriptTupleType matchgroup=typescriptBraces + \ start=/\[/ end=/\]/ + \ contains=@typescriptType,@typescriptComments + \ contained skipwhite + +syntax cluster typescriptTypeOperator + \ contains=typescriptUnion,typescriptTypeBracket + +syntax match typescriptUnion /|\|&/ 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, + \ 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) + "runtime syntax/yats.vim + "runtime syntax/yats/typescript.vim + syntax keyword typescriptGlobal containedin=typescriptIdentifierName Function Boolean + syntax keyword typescriptGlobal containedin=typescriptIdentifierName Error EvalError + 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 + + "runtime syntax/yats/es6-number.vim + 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 + + "runtime syntax/yats/es6-string.vim + 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 + + "runtime syntax/yats/es6-array.vim + 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 + + "runtime syntax/yats/es6-object.vim + 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 + + "runtime syntax/yats/es6-symbol.vim + 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 + + "runtime syntax/yats/es6-function.vim + 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 + + "runtime syntax/yats/es6-math.vim + 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 + + "runtime syntax/yats/es6-date.vim + 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 + + "runtime syntax/yats/es6-json.vim + 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 + + "runtime syntax/yats/es6-regexp.vim + 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 + + "runtime syntax/yats/es6-map.vim + 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 + + "runtime syntax/yats/es6-set.vim + 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 + + "runtime syntax/yats/es6-proxy.vim + 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 + + "runtime syntax/yats/es6-promise.vim + 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 + + "runtime syntax/yats/es6-reflect.vim + 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 + + "runtime syntax/yats/ecma-402.vim + 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 + + "runtime syntax/yats/node.vim + 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 + + "runtime syntax/yats/web.vim + 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 + + "runtime syntax/yats/web-window.vim + 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 + + "runtime syntax/yats/web-navigator.vim + 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 + + "runtime syntax/yats/web-location.vim + 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 + + "runtime syntax/yats/web-history.vim + 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 + + "runtime syntax/yats/web-console.vim + 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 + + "runtime syntax/yats/web-xhr.vim + 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 + + "runtime syntax/yats/web-blob.vim + 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 + + "runtime syntax/yats/web-crypto.vim + 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 + + "runtime syntax/yats/web-fetch.vim + 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 + + "runtime syntax/yats/web-service-worker.vim + 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 + + "runtime syntax/yats/web-encoding.vim + 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 + + "runtime syntax/yats/web-geo.vim + 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 + + "runtime syntax/yats/web-network.vim + 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 + + "runtime syntax/yats/web-payment.vim + 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 + + "runtime syntax/yats/dom-node.vim + 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 + + "runtime syntax/yats/dom-elem.vim + 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 + + "runtime syntax/yats/dom-document.vim + 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 + + "runtime syntax/yats/dom-event.vim + 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 + + "runtime syntax/yats/dom-storage.vim + 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 + + "runtime syntax/yats/dom-form.vim + 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 + + "runtime syntax/yats/css.vim + 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 + + "runtime syntax/yats/event.vim + 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 +"runtime syntax/basic/patch.vim +" patch for generated code +syntax keyword typescriptGlobal Promise + \ nextgroup=typescriptGlobalPromiseDot,typescriptFuncCallArg,typescriptTypeArguments oneline +syntax keyword typescriptGlobal Map WeakMap + \ nextgroup=typescriptGlobalPromiseDot,typescriptFuncCallArg,typescriptTypeArguments oneline + +"runtime syntax/basic/members.vim +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 + +"runtime syntax/basic/class.vim +"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=typescriptTypeParameter + \ 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=typescriptTypeParameter + \ 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 + +"runtime syntax/basic/cluster.vim +"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 + +"runtime syntax/basic/function.vim +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*=>/ + \ 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 + +"runtime syntax/basic/decorator.vim +syntax match typescriptDecorator /@\([_$a-zA-Z][_$a-zA-Z0-9]*\.\)*[_$a-zA-Z][_$a-zA-Z0-9]*\>/ + \ nextgroup=typescriptArgumentList,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 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 typescriptStringMember String +hi def link typescriptTemplate String +hi def link typescriptEventString 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 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 typescriptExponent Number +hi def link typescriptBoolean Boolean +hi def link typescriptObjectLabel typescriptLabel +hi def link typescriptLabel Label +hi def link typescriptStringProperty String +hi def link typescriptImport Special +hi def link typescriptAmbientDeclaration Special +hi def link typescriptExport 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 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/typescriptreact.vim b/runtime/syntax/typescriptreact.vim new file mode 100644 index 0000000000..f29fe785b9 --- /dev/null +++ b/runtime/syntax/typescriptreact.vim @@ -0,0 +1,160 @@ +" Vim syntax file +" Language: TypeScript with React (JSX) +" Maintainer: Bram Moolenaar +" Last Change: 2019 Nov 30 +" Based On: Herrington Darkholme's yats.vim +" Changes: See https:github.com/HerringtonDarkholme/yats.vim +" Credits: See yats.vim on github + +if !exists("main_syntax") + if exists("b:current_syntax") + finish + endif + let main_syntax = 'typescriptreact' +endif + +let s:cpo_save = &cpo +set cpo&vim + +syntax region tsxTag + \ start=+<\([^/!?<>="':]\+\)\@=+ + \ skip=+</[^ /!?<>"']\+>+ + \ end=+/\@<!>+ + \ end=+\(/>\)\@=+ + \ contained + \ contains=tsxTagName,tsxIntrinsicTagName,tsxAttrib,tsxEscJs, + \tsxCloseString,@tsxComment + +syntax match tsxTag /<>/ contained + + +" <tag></tag> +" s~~~~~~~~~e +" and self close tag +" <tag/> +" s~~~~e +" A big start regexp borrowed from https://git.io/vDyxc +syntax region tsxRegion + \ start=+<\_s*\z([a-zA-Z1-9\$_-]\+\(\.\k\+\)*\)+ + \ skip=+<!--\_.\{-}-->+ + \ end=+</\_s*\z1>+ + \ matchgroup=tsxCloseString end=+/>+ + \ fold + \ contains=tsxRegion,tsxCloseString,tsxCloseTag,tsxTag,tsxCommentInvalid,tsxFragment,tsxEscJs,@Spell + \ keepend + \ extend + +" <> </> +" s~~~~~~e +" A big start regexp borrowed from https://git.io/vDyxc +syntax region tsxFragment + \ start=+\(\((\|{\|}\|\[\|,\|&&\|||\|?\|:\|=\|=>\|\Wreturn\|^return\|\Wdefault\|^\|>\)\_s*\)\@<=<>+ + \ skip=+<!--\_.\{-}-->+ + \ end=+</>+ + \ fold + \ contains=tsxRegion,tsxCloseString,tsxCloseTag,tsxTag,tsxCommentInvalid,tsxFragment,tsxEscJs,@Spell + \ keepend + \ extend + +" </tag> +" ~~~~~~ +syntax match tsxCloseTag + \ +</\_s*[^/!?<>"']\+>+ + \ contained + \ contains=tsxTagName,tsxIntrinsicTagName + +syntax match tsxCloseTag +</>+ contained + +syntax match tsxCloseString + \ +/>+ + \ contained + +" <!-- --> +" ~~~~~~~~ +syntax match tsxCommentInvalid /<!--\_.\{-}-->/ display + +syntax region tsxBlockComment + \ contained + \ start="/\*" + \ end="\*/" + +syntax match tsxLineComment + \ "//.*$" + \ contained + \ display + +syntax cluster tsxComment contains=tsxBlockComment,tsxLineComment + +syntax match tsxEntity "&[^; \t]*;" contains=tsxEntityPunct +syntax match tsxEntityPunct contained "[&.;]" + +" <tag key={this.props.key}> +" ~~~ +syntax match tsxTagName + \ +[</]\_s*[^/!?<>"'* ]\++hs=s+1 + \ contained + \ nextgroup=tsxAttrib + \ skipwhite + \ display +syntax match tsxIntrinsicTagName + \ +[</]\_s*[a-z1-9-]\++hs=s+1 + \ contained + \ nextgroup=tsxAttrib + \ skipwhite + \ display + +" <tag key={this.props.key}> +" ~~~ +syntax match tsxAttrib + \ +[a-zA-Z_][-0-9a-zA-Z_]*+ + \ nextgroup=tsxEqual skipwhite + \ contained + \ display + +" <tag id="sample"> +" ~ +syntax match tsxEqual +=+ display contained + \ nextgroup=tsxString skipwhite + +" <tag id="sample"> +" s~~~~~~e +syntax region tsxString contained start=+"+ end=+"+ contains=tsxEntity,@Spell display + +" <tag key={this.props.key}> +" s~~~~~~~~~~~~~~e +syntax region tsxEscJs + \ contained + \ contains=@typescriptValue,@tsxComment + \ matchgroup=typescriptBraces + \ start=+{+ + \ end=+}+ + \ extend + + +""""""""""""""""""""""""""""""""""""""""""""""""""" +" Source the part common with typescriptreact.vim +source <sfile>:h/typescriptcommon.vim + + +syntax cluster typescriptExpression add=tsxRegion,tsxFragment + +hi def link tsxTag htmlTag +hi def link tsxTagName Function +hi def link tsxIntrinsicTagName htmlTagName +hi def link tsxString String +hi def link tsxNameSpace Function +hi def link tsxCommentInvalid Error +hi def link tsxBlockComment Comment +hi def link tsxLineComment Comment +hi def link tsxAttrib Type +hi def link tsxEscJs tsxEscapeJs +hi def link tsxCloseTag htmlTag +hi def link tsxCloseString Identifier + +let b:current_syntax = "typescriptreact" +if main_syntax == 'typescriptreact' + unlet main_syntax +endif + +let &cpo = s:cpo_save +unlet s:cpo_save diff --git a/runtime/tools/check_colors.vim b/runtime/tools/check_colors.vim index 57b71b19db..e4acbc33ec 100644 --- a/runtime/tools/check_colors.vim +++ b/runtime/tools/check_colors.vim @@ -1,6 +1,6 @@ " This script tests a color scheme for some errors and lists potential errors. " Load the scheme and source this script, like this: -" :edit colors/desert.vim | :so colors/tools/check_colors.vim +" :edit colors/desert.vim | :so tools/check_colors.vim let s:save_cpo= &cpo set cpo&vim @@ -8,7 +8,7 @@ set cpo&vim func! Test_check_colors() let l:savedview = winsaveview() call cursor(1,1) - let err={} + let err = {} " 1) Check g:colors_name is existing if !search('\<\%(g:\)\?colors_name\>', 'cnW') @@ -81,36 +81,39 @@ func! Test_check_colors() \ 'WarningMsg', \ 'WildMenu', \ ] - let groups={} + let groups = {} for group in hi_groups - if search('\c@suppress\s\+'.group, 'cnW') + if search('\c@suppress\s\+\<' .. group .. '\>', 'cnW') " skip check, if the script contains a line like " @suppress Visual: - let groups[group] = 'Ignoring '.group continue endif - if search('hi\%[ghlight]!\= \+link \+'.group, 'cnW') " Linked group + if search('hi\%[ghlight]!\= \+link \+' .. group, 'cnW') " Linked group continue endif - if !search('hi\%[ghlight] \+'.group, 'cnW') - let groups[group] = 'No highlight definition for '.group + if !search('hi\%[ghlight] \+\<' .. group .. '\>', 'cnW') + let groups[group] = 'No highlight definition for ' .. group continue endif - if !search('hi\%[ghlight] \+'.group. '.*fg=', 'cnW') - let groups[group] = 'Missing foreground color for '.group + if !search('hi\%[ghlight] \+\<' .. group .. '\>.*[bf]g=', 'cnW') + let groups[group] = 'Missing foreground or background color for ' .. group continue endif - if search('hi\%[ghlight] \+'.group. '.*guibg=', 'cnW') && - \ !search('hi\%[ghlight] \+'.group. '.*ctermbg=', 'cnW') - let groups[group] = 'Missing bg terminal color for '.group + if search('hi\%[ghlight] \+\<' .. group .. '\>.*guibg=', 'cnW') && + \ !search('hi\%[ghlight] \+\<' .. group .. '\>.*ctermbg=', 'cnW') + \ && group != 'Cursor' + let groups[group] = 'Missing bg terminal color for ' .. group continue endif - if !search('hi\%[ghlight] \+'.group. '.*guifg=', 'cnW') - let groups[group] = 'Missing guifg definition for '.group + if !search('hi\%[ghlight] \+\<' .. group .. '\>.*guifg=', 'cnW') + \ && group !~ '^Diff' + let groups[group] = 'Missing guifg definition for ' .. group continue endif - if !search('hi\%[ghlight] \+'.group. '.*ctermfg=', 'cnW') - let groups[group] = 'Missing ctermfg definition for '.group + if !search('hi\%[ghlight] \+\<' .. group .. '\>.*ctermfg=', 'cnW') + \ && group !~ '^Diff' + \ && group != 'Cursor' + let groups[group] = 'Missing ctermfg definition for ' .. group continue endif " do not check for background colors, they could be intentionally left out @@ -120,10 +123,10 @@ func! Test_check_colors() " 3) Check, that it does not set background highlighting " Doesn't ':hi Normal ctermfg=253 ctermfg=233' also set the background sometimes? - let bg_set='\(set\?\|setl\(ocal\)\?\) .*\(background\|bg\)=\(dark\|light\)' - let bg_let='let \%([&]\%([lg]:\)\?\)\%(background\|bg\)\s*=\s*\([''"]\?\)\w\+\1' - let bg_pat='\%('.bg_set. '\|'.bg_let.'\)' - let line=search(bg_pat, 'cnW') + let bg_set = '\(set\?\|setl\(ocal\)\?\) .*\(background\|bg\)=\(dark\|light\)' + let bg_let = 'let \%([&]\%([lg]:\)\?\)\%(background\|bg\)\s*=\s*\([''"]\?\)\w\+\1' + let bg_pat = '\%(' .. bg_set .. '\|' .. bg_let .. '\)' + let line = search(bg_pat, 'cnW') if search(bg_pat, 'cnW') exe line if search('hi \U\w\+\s\+\S', 'cbnW') @@ -145,7 +148,7 @@ func! Test_check_colors() " if exists("syntax_on") " syntax reset " endif - let pat='hi\%[ghlight]\s*clear\n\s*if\s*exists(\([''"]\)syntax_on\1)\n\s*syn\%[tax]\s*reset\n\s*endif' + let pat = 'hi\%[ghlight]\s*clear\n\s*if\s*exists(\([''"]\)syntax_on\1)\n\s*syn\%[tax]\s*reset\n\s*endif' if !search(pat, 'cnW') let err['init'] = 'No initialization' endif @@ -160,7 +163,7 @@ func! Test_check_colors() let ft_groups = [] " let group = '\%('.join(hi_groups, '\|').'\)' " More efficient than a for loop, but less informative for group in hi_groups - let pat='\Chi\%[ghlight]!\= *\%[link] \+\zs'.group.'\w\+\>\ze \+.' " Skips `hi clear` + let pat = '\Chi\%[ghlight]!\= *\%[link] \+\zs' .. group .. '\w\+\>\ze \+.' " Skips `hi clear` if search(pat, 'cW') call add(ft_groups, matchstr(getline('.'), pat)) endif @@ -172,7 +175,7 @@ func! Test_check_colors() " 8) Were debugPC and debugBreakpoint defined? for group in ['debugPC', 'debugBreakpoint'] - let pat='\Chi\%[ghlight]!\= *\%[link] \+\zs'.group.'\>' + let pat = '\Chi\%[ghlight]!\= *\%[link] \+\zs' .. group .. '\>' if search(pat, 'cnW') let line = search(pat, 'cW') let err['filetype'] = get(err, 'filetype', 'Should not define: ') . matchstr(getline('.'), pat). ' ' diff --git a/scripts/download-unicode-files.sh b/scripts/download-unicode-files.sh index 5f38d0589a..12474d3c1e 100755 --- a/scripts/download-unicode-files.sh +++ b/scripts/download-unicode-files.sh @@ -30,7 +30,7 @@ for filename in $data_files ; do done for filename in $emoji_files ; do - curl -L -o "$UNIDIR/$filename" "$DOWNLOAD_URL_BASE/emoji/latest/$filename" + curl -L -o "$UNIDIR/$filename" "$DOWNLOAD_URL_BASE/UNIDATA/emoji/$filename" ( cd "$UNIDIR" git add $filename diff --git a/scripts/gen_vimdoc.py b/scripts/gen_vimdoc.py index a61690e99f..b1a7f92854 100755 --- a/scripts/gen_vimdoc.py +++ b/scripts/gen_vimdoc.py @@ -39,6 +39,7 @@ Each function :help block is formatted as follows: parameter is marked as [out]. - Each function documentation is separated by a single line. """ +import argparse import os import re import sys @@ -57,7 +58,6 @@ if sys.version_info < MIN_PYTHON_VERSION: sys.exit(1) DEBUG = ('DEBUG' in os.environ) -TARGET = os.environ.get('TARGET', None) INCLUDE_C_DECL = ('INCLUDE_C_DECL' in os.environ) INCLUDE_DEPRECATED = ('INCLUDE_DEPRECATED' in os.environ) @@ -68,6 +68,7 @@ base_dir = os.path.dirname(os.path.dirname(script_path)) out_dir = os.path.join(base_dir, 'tmp-{target}-doc') filter_cmd = '%s %s' % (sys.executable, script_path) seen_funcs = set() +msgs = [] # Messages to show on exit. lua2dox_filter = os.path.join(base_dir, 'scripts', 'lua2dox_filter') CONFIG = { @@ -112,10 +113,12 @@ CONFIG = { 'section_order': [ 'vim.lua', 'shared.lua', + 'uri.lua', ], 'files': ' '.join([ os.path.join(base_dir, 'src/nvim/lua/vim.lua'), os.path.join(base_dir, 'runtime/lua/vim/shared.lua'), + os.path.join(base_dir, 'runtime/lua/vim/uri.lua'), ]), 'file_patterns': '*.lua', 'fn_name_prefix': '', @@ -128,6 +131,7 @@ CONFIG = { 'module_override': { # `shared` functions are exposed on the `vim` module. 'shared': 'vim', + 'uri': 'vim', }, 'append_only': [ 'shared.lua', @@ -139,12 +143,13 @@ CONFIG = { 'section_start_token': '*lsp-core*', 'section_order': [ 'lsp.lua', - 'protocol.lua', 'buf.lua', - 'callbacks.lua', + 'diagnostic.lua', + 'handlers.lua', + 'util.lua', 'log.lua', 'rpc.lua', - 'util.lua' + 'protocol.lua', ], 'files': ' '.join([ os.path.join(base_dir, 'runtime/lua/vim/lsp'), @@ -191,7 +196,7 @@ xrefs = set() # Raises an error with details about `o`, if `cond` is in object `o`, # or if `cond()` is callable and returns True. -def debug_this(cond, o): +def debug_this(o, cond=True): name = '' if not isinstance(o, str): try: @@ -205,6 +210,23 @@ def debug_this(cond, o): raise RuntimeError('xxx: {}\n{}'.format(name, o)) +# Appends a message to a list which will be printed on exit. +def msg(s): + msgs.append(s) + + +# Print all collected messages. +def msg_report(): + for m in msgs: + print(f' {m}') + + +# Print collected messages, then throw an exception. +def fail(s): + msg_report() + raise RuntimeError(s) + + def find_first(parent, name): """Finds the first matching node within parent.""" sub = parent.getElementsByTagName(name) @@ -426,7 +448,7 @@ def render_node(n, text, prefix='', indent='', width=62): indent=indent, width=width)) i = i + 1 elif n.nodeName == 'simplesect' and 'note' == n.getAttribute('kind'): - text += 'Note:\n ' + text += '\nNote:\n ' for c in n.childNodes: text += render_node(c, text, indent=' ', width=width) text += '\n' @@ -440,6 +462,8 @@ def render_node(n, text, prefix='', indent='', width=62): text += ind(' ') for c in n.childNodes: text += render_node(c, text, indent=' ', width=width) + elif n.nodeName == 'computeroutput': + return get_text(n) else: raise RuntimeError('unhandled node type: {}\n{}'.format( n.nodeName, n.toprettyxml(indent=' ', newl='\n'))) @@ -505,6 +529,7 @@ def para_as_map(parent, indent='', width=62): and is_inline(self_or_child(prev)) and is_inline(self_or_child(child)) and '' != get_text(self_or_child(child)).strip() + and text and ' ' != text[-1]): text += ' ' @@ -684,7 +709,7 @@ def extract_from_xml(filename, target, width): if len(prefix) + len(suffix) > lhs: signature = vimtag.rjust(width) + '\n' - signature += doc_wrap(suffix, width=width-8, prefix=prefix, + signature += doc_wrap(suffix, width=width, prefix=prefix, func=True) else: signature = prefix + suffix @@ -841,7 +866,7 @@ def delete_lines_below(filename, tokenstr): fp.writelines(lines[0:i]) -def main(config): +def main(config, args): """Generates: 1. Vim :help docs @@ -850,7 +875,7 @@ def main(config): Doxygen is called and configured through stdin. """ for target in CONFIG: - if TARGET is not None and target != TARGET: + if args.target is not None and target != args.target: continue mpack_file = os.path.join( base_dir, 'runtime', 'doc', @@ -915,9 +940,10 @@ def main(config): filename = get_text(find_first(compound, 'name')) if filename.endswith('.c') or filename.endswith('.lua'): + xmlfile = os.path.join(base, + '{}.xml'.format(compound.getAttribute('refid'))) # Extract unformatted (*.mpack). - fn_map, _ = extract_from_xml(os.path.join(base, '{}.xml'.format( - compound.getAttribute('refid'))), target, width=9999) + fn_map, _ = extract_from_xml(xmlfile, target, width=9999) # Extract formatted (:help). functions_text, deprecated_text = fmt_doxygen_xml_as_vimhelp( os.path.join(base, '{}.xml'.format( @@ -950,7 +976,8 @@ def main(config): sections[filename] = (title, helptag, doc) fn_map_full.update(fn_map) - assert sections + if len(sections) == 0: + fail(f'no sections for target: {target}') if len(sections) > len(CONFIG[target]['section_order']): raise RuntimeError( 'found new modules "{}"; update the "section_order" map'.format( @@ -960,7 +987,11 @@ def main(config): i = 0 for filename in CONFIG[target]['section_order']: - title, helptag, section_doc = sections.pop(filename) + try: + title, helptag, section_doc = sections.pop(filename) + except KeyError: + msg(f'warning: empty docs, skipping (target={target}): {filename}') + continue i += 1 if filename not in CONFIG[target]['append_only']: docs += sep @@ -983,7 +1014,10 @@ def main(config): with open(mpack_file, 'wb') as fp: fp.write(msgpack.packb(fn_map_full, use_bin_type=True)) - shutil.rmtree(output_dir) + if not args.keep_tmpfiles: + shutil.rmtree(output_dir) + + msg_report() def filter_source(filename): @@ -1001,6 +1035,18 @@ def filter_source(filename): fp.read(), flags=re.M)) +def parse_args(): + targets = ', '.join(CONFIG.keys()) + ap = argparse.ArgumentParser() + ap.add_argument('source_filter', nargs='*', + help="Filter source file(s)") + ap.add_argument('-k', '--keep-tmpfiles', action='store_true', + help="Keep temporary files") + ap.add_argument('-t', '--target', + help=f'One of ({targets}), defaults to "all"') + return ap.parse_args() + + Doxyfile = textwrap.dedent(''' OUTPUT_DIRECTORY = {output} INPUT = {input} @@ -1037,9 +1083,10 @@ Doxyfile = textwrap.dedent(''' ''') if __name__ == "__main__": - if len(sys.argv) > 1: - filter_source(sys.argv[1]) + args = parse_args() + if len(args.source_filter) > 0: + filter_source(args.source_filter[0]) else: - main(Doxyfile) + main(Doxyfile, args) # vim: set ft=python ts=4 sw=4 tw=79 et : diff --git a/scripts/lua2dox.lua b/scripts/lua2dox.lua index d4e68f9e45..1dc4c0a5a0 100644 --- a/scripts/lua2dox.lua +++ b/scripts/lua2dox.lua @@ -73,7 +73,7 @@ function class(BaseClass, ClassInitialiser) local newInstance = {} setmetatable(newInstance,newClass) --if init then - -- init(newInstance,...) + -- init(newInstance,...) if class_tbl.init then class_tbl.init(newInstance,...) else @@ -214,7 +214,7 @@ TStream_Read = class() --! \brief get contents of file --! --! \param Filename name of file to read (or nil == stdin) -function TStream_Read.getContents(this,Filename) +function TStream_Read.getContents(this,Filename) -- get lines from file local filecontents if Filename then @@ -365,7 +365,7 @@ end --! \brief check comment for fn local function checkComment4fn(Fn_magic,MagicLines) local fn_magic = Fn_magic - -- TCore_IO_writeln('// checkComment4fn "' .. MagicLines .. '"') + -- TCore_IO_writeln('// checkComment4fn "' .. MagicLines .. '"') local magicLines = string_split(MagicLines,'\n') @@ -375,7 +375,7 @@ local function checkComment4fn(Fn_magic,MagicLines) macro,tail = getMagicDirective(line) if macro == 'fn' then fn_magic = tail - -- TCore_IO_writeln('// found fn "' .. fn_magic .. '"') + -- TCore_IO_writeln('// found fn "' .. fn_magic .. '"') else --TCore_IO_writeln('// not found fn "' .. line .. '"') end @@ -401,15 +401,23 @@ function TLua2DoX_filter.readfile(this,AppStamp,Filename) outStream:writelnTail('// #######################') outStream:writelnTail() - local state = '' + local state, offset = '', 0 while not (err or inStream:eof()) do line = string_trim(inStream:getLine()) - -- TCore_Debug_show_var('inStream',inStream) - -- TCore_Debug_show_var('line',line ) - if string.sub(line,1,2)=='--' then -- it's a comment - if string.sub(line,3,3)=='@' then -- it's a magic comment + -- TCore_Debug_show_var('inStream',inStream) + -- TCore_Debug_show_var('line',line ) + if string.sub(line,1,2) == '--' then -- it's a comment + -- Allow people to write style similar to EmmyLua (since they are basically the same) + -- instead of silently skipping things that start with --- + if string.sub(line, 3, 3) == '@' then -- it's a magic comment + offset = 0 + elseif string.sub(line, 1, 4) == '---@' then -- it's a magic comment + offset = 1 + end + + if string.sub(line, 3, 3) == '@' or string.sub(line, 1, 4) == '---@' then -- it's a magic comment state = 'in_magic_comment' - local magic = string.sub(line,4) + local magic = string.sub(line, 4 + offset) outStream:writeln('/// @' .. magic) fn_magic = checkComment4fn(fn_magic,magic) elseif string.sub(line,3,3)=='-' then -- it's a nonmagic doc comment @@ -450,7 +458,7 @@ function TLua2DoX_filter.readfile(this,AppStamp,Filename) outStream:writeln('// zz:"' .. line .. '"') fn_magic = nil end - elseif string.find(line,'^function') or string.find(line,'^local%s+function') then + elseif string.find(line, '^function') or string.find(line, '^local%s+function') then state = 'in_function' -- it's a function local pos_fn = string.find(line,'function') -- function @@ -490,6 +498,13 @@ function TLua2DoX_filter.readfile(this,AppStamp,Filename) this:warning(inStream:getLineNo(),'something weird here') end fn_magic = nil -- mustn't indavertently use it again + + -- TODO: If we can make this learn how to generate these, that would be helpful. + -- elseif string.find(line, "^M%['.*'%] = function") then + -- state = 'in_function' -- it's a function + -- outStream:writeln("function textDocument/publishDiagnostics(...){}") + + -- fn_magic = nil -- mustn't indavertently use it again else state = '' -- unknown if #line>0 then -- we don't know what this line means, so just comment it out diff --git a/scripts/pvscheck.sh b/scripts/pvscheck.sh index c09e8c4555..f054f6e6fe 100755 --- a/scripts/pvscheck.sh +++ b/scripts/pvscheck.sh @@ -346,7 +346,7 @@ patch_sources() {( if test "$only_build" != "--only-build" ; then find \ src/nvim test/functional/fixtures test/unit/fixtures \ - -name '*.c' \ + \( -name '*.c' -a '!' -path '*xdiff*' \) \ -exec /bin/sh -c "$sh_script" - '{}' \; fi @@ -363,11 +363,17 @@ run_analysis() {( cd "$tgt" + if [ ! -r PVS-Studio.lic ]; then + pvs-studio-analyzer credentials -o PVS-Studio.lic 'PVS-Studio Free' 'FREE-FREE-FREE-FREE' + fi + # pvs-studio-analyzer exits with a non-zero exit code when there are detected # errors, so ignore its return pvs-studio-analyzer \ analyze \ + --lic-file PVS-Studio.lic \ --threads "$(get_jobs_num)" \ + --exclude-path src/nvim/xdiff \ --output-file PVS-studio.log \ --file build/compile_commands.json \ --sourcetree-root . || true diff --git a/scripts/update-ts-runtime.sh b/scripts/update-ts-runtime.sh deleted file mode 100755 index 1a947e0ac9..0000000000 --- a/scripts/update-ts-runtime.sh +++ /dev/null @@ -1,39 +0,0 @@ -#!/bin/sh -# -# This script will update the treesitter runtime to the provided commit. -# Usage : -# $0 <tree-sitter commit sha> -set -e - -ts_source_dir="/tmp/tree-sitter" -ts_url="https://github.com/tree-sitter/tree-sitter.git" - -base_dir="$(cd "$(dirname $(dirname $0))" && pwd)" -ts_dest_dir="$base_dir/src/tree_sitter/" -ts_current_commit="$ts_dest_dir/treesitter_commit_hash.txt" - -echo "Updating treesitter runtime from $(cat "$ts_current_commit") to $1..." - -if [ ! -d "$ts_source_dir" ]; then - echo "Cloning treesitter..." - git clone "$ts_url" "$ts_source_dir" -else - echo "Found a non-empty $ts_source_dir directory..." - git -C "$ts_source_dir" fetch -fi - -echo "Checking out $1..." -git -C "$ts_source_dir" checkout $1 - -echo "Removing old files..." -find "$ts_dest_dir" -not -name "LICENSE" -not -name "README.md" -not -type d -delete - -echo "Copying files..." -cp -t "$ts_dest_dir" -r "$ts_source_dir/lib/src"/* -cp -t "$ts_dest_dir" "$ts_source_dir/lib/include/tree_sitter"/* - -echo "$1" > "$ts_current_commit" - -make -TEST_FILE="$base_dir/test/functional/lua/treesitter_spec.lua" make test - diff --git a/scripts/vim-patch.sh b/scripts/vim-patch.sh index 9c4349abca..551b8fb691 100755 --- a/scripts/vim-patch.sh +++ b/scripts/vim-patch.sh @@ -211,6 +211,18 @@ preprocess_patch() { LC_ALL=C sed -e 's/\( [ab]\/src\)/\1\/nvim/g' \ "$file" > "$file".tmp && mv "$file".tmp "$file" + # Rename evalfunc.c to eval/funcs.c + LC_ALL=C sed -e 's/\( [ab]\/src\/nvim\)\/evalfunc\.c/\1\/eval\/funcs\.c/g' \ + "$file" > "$file".tmp && mv "$file".tmp "$file" + + # Rename userfunc.c to eval/userfunc.c + LC_ALL=C sed -e 's/\( [ab]\/src\/nvim\)\/userfunc\.c/\1\/eval\/userfunc\.c/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" + # Rename test_urls.vim to check_urls.vim LC_ALL=C sed -e 's@\( [ab]\)/runtime/doc/test\(_urls.vim\)@\1/scripts/check\2@g' \ "$file" > "$file".tmp && mv "$file".tmp "$file" @@ -346,7 +358,8 @@ submit_pr() { local patches # Extract just the "vim-patch:X.Y.ZZZZ" or "vim-patch:sha" portion of each log patches=("$(git log --grep=vim-patch --reverse --format='%s' "${git_remote}"/master..HEAD | sed 's/: .*//')") - patches=("${patches[@]//vim-patch:}") # Remove 'vim-patch:' prefix for each item in array. + # shellcheck disable=SC2206 + patches=(${patches[@]//vim-patch:}) # Remove 'vim-patch:' prefix for each item in array. local pr_title="${patches[*]}" # Create space-separated string from array. pr_title="${pr_title// /,}" # Replace spaces with commas. @@ -596,8 +609,11 @@ list_missing_previous_vimpatches_for_patch() { set -u local -a missing_unique + local stat while IFS= read -r line; do - missing_unique+=("$line") + local commit="${line%%:*}" + stat="$(git -C "${VIM_SOURCE_DIR}" show --format= --shortstat "${commit}")" + missing_unique+=("$(printf '%s\n %s' "$line" "$stat")") done < <(printf '%s\n' "${missing_list[@]}" | sort -u) msg_err "$(printf '%d missing previous Vim patches:' ${#missing_unique[@]})" diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index da3e74d3e7..57bcb72d5d 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -25,22 +25,47 @@ apps: parts: nvim: - source: . + source: https://github.com/neovim/neovim.git override-pull: | snapcraftctl pull + latest_tag="$(git tag -l --sort=refname|head -1)" + git checkout "${latest_tag}" major="$(awk '/NVIM_VERSION_MAJOR/{gsub(")","",$2); print $2}' CMakeLists.txt)" minor="$(awk '/NVIM_VERSION_MINOR/{gsub(")","",$2); print $2}' CMakeLists.txt)" patch="$(awk '/NVIM_VERSION_PATCH/{gsub(")","",$2); print $2}' CMakeLists.txt)" version_prefix="v$major.$minor.$patch" git_described="$(git describe --first-parent --dirty 2> /dev/null | perl -lpe 's/v\d.\d.\d-//g')" git_described="${git_described:-$(git describe --first-parent --tags --always --dirty)}" - snapcraftctl set-version "${version_prefix}-${git_described}" + if [ "${version_prefix}" != "${git_described}" ]; then + VERSION="${version_prefix}-${git_described}-${latest_tag}" + else + VERSION="${version_prefix}-${latest_tag}" + fi + snapcraftctl set-version "${VERSION}" plugin: make make-parameters: - - CMAKE_BUILD_TYPE=Release + - CMAKE_BUILD_TYPE=RelWithDebInfo - CMAKE_INSTALL_PREFIX=/usr + - CMAKE_FLAGS=-DPREFER_LUA=ON + - DEPS_CMAKE_FLAGS="-DUSE_BUNDLED_LUA=ON -DUSE_BUNDLED_LUAJIT=OFF" override-build: | - snapcraftctl build + echo "Building on $SNAP_ARCH" + set -x + case "$SNAP_ARCH" in + "arm64" | "ppc64el" | "s390x") + make -j"${SNAPCRAFT_PARALLEL_BUILD_COUNT}" \ + CMAKE_BUILD_TYPE=RelWithDebInfo \ + CMAKE_INSTALL_PREFIX=/usr \ + CMAKE_FLAGS=-DPREFER_LUA=ON \ + DEPS_CMAKE_FLAGS="-DUSE_BUNDLED_LUA=ON -DUSE_BUNDLED_LUAJIT=OFF" + ;; + *) + make -j"${SNAPCRAFT_PARALLEL_BUILD_COUNT}" \ + CMAKE_BUILD_TYPE=RelWithDebInfo \ + CMAKE_INSTALL_PREFIX=/usr + ;; + esac + make DESTDIR="$SNAPCRAFT_PART_INSTALL" install # Fix Desktop file sed -i 's|^Exec=nvim|Exec=/snap/bin/nvim.nvim|' ${SNAPCRAFT_PART_INSTALL}/usr/share/applications/nvim.desktop sed -i 's|^TryExec=nvim|TryExec=/snap/bin/nvim.nvim|' ${SNAPCRAFT_PART_INSTALL}/usr/share/applications/nvim.desktop @@ -52,11 +77,13 @@ parts: - autoconf - automake - cmake + - gawk - g++ - git - gettext - pkg-config - unzip + - wget prime: - -usr/share/man diff --git a/src/clint.py b/src/clint.py index 8dc41fdb93..9b4128a0c9 100755 --- a/src/clint.py +++ b/src/clint.py @@ -350,7 +350,7 @@ def IsErrorInSuppressedErrorsList(category, linenum): category: str, the category of the error. linenum: int, the current line number. Returns: - bool, True iff the error should be suppressed due to presense in + bool, True iff the error should be suppressed due to presence in suppressions file. """ return (category, linenum) in _error_suppressions_2 diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index c7258dde12..8ec087c626 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -55,6 +55,7 @@ set(GENERATED_UNICODE_TABLES ${GENERATED_DIR}/unicode_tables.generated.h) set(VIM_MODULE_FILE ${GENERATED_DIR}/lua/vim_module.generated.h) set(LUA_VIM_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/src/nvim/lua/vim.lua) set(LUA_SHARED_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/runtime/lua/vim/shared.lua) +set(LUA_INSPECT_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/runtime/lua/vim/inspect.lua) set(CHAR_BLOB_GENERATOR ${GENERATOR_DIR}/gen_char_blob.lua) set(LINT_SUPPRESS_FILE ${PROJECT_BINARY_DIR}/errors.json) set(LINT_SUPPRESS_URL_BASE "https://raw.githubusercontent.com/neovim/doc/gh-pages/reports/clint") @@ -87,10 +88,6 @@ file(GLOB NVIM_HEADERS *.h) file(GLOB XDIFF_SOURCES xdiff/*.c) file(GLOB XDIFF_HEADERS xdiff/*.h) -file(GLOB TREESITTER_SOURCES ../tree_sitter/*.c) -file(GLOB TS_SOURCE_AMALGAM ../tree_sitter/lib.c) -list(REMOVE_ITEM TREESITTER_SOURCES ${TS_SOURCE_AMALGAM}) - foreach(subdir os api @@ -187,9 +184,6 @@ if(NOT MSVC) set_source_files_properties( eval/funcs.c PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -Wno-conversion") endif() - - # tree-sitter: inlined external project, we don't maintain it. #10124 - set_source_files_properties(${TREESITTER_SOURCES} PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -Wno-conversion -Wno-pedantic -Wno-shadow -Wno-missing-prototypes -Wno-unused-variable") endif() if(NOT "${MIN_LOG_LEVEL}" MATCHES "^$") @@ -330,10 +324,12 @@ add_custom_command( COMMAND ${LUA_PRG} ${CHAR_BLOB_GENERATOR} ${VIM_MODULE_FILE} ${LUA_VIM_MODULE_SOURCE} vim_module ${LUA_SHARED_MODULE_SOURCE} shared_module + ${LUA_INSPECT_MODULE_SOURCE} inspect_module DEPENDS ${CHAR_BLOB_GENERATOR} ${LUA_VIM_MODULE_SOURCE} ${LUA_SHARED_MODULE_SOURCE} + ${LUA_INSPECT_MODULE_SOURCE} ) list(APPEND NVIM_GENERATED_SOURCES @@ -449,6 +445,7 @@ list(APPEND NVIM_LINK_LIBRARIES ${LIBTERMKEY_LIBRARIES} ${UNIBILIUM_LIBRARIES} ${UTF8PROC_LIBRARIES} + ${TreeSitter_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} ) @@ -468,7 +465,7 @@ endif() add_executable(nvim ${NVIM_GENERATED_FOR_SOURCES} ${NVIM_GENERATED_FOR_HEADERS} ${NVIM_GENERATED_SOURCES} ${NVIM_SOURCES} ${NVIM_HEADERS} - ${XDIFF_SOURCES} ${XDIFF_HEADERS} ${TREESITTER_SOURCES}) + ${XDIFF_SOURCES} ${XDIFF_HEADERS}) target_link_libraries(nvim ${NVIM_EXEC_LINK_LIBRARIES}) install_helper(TARGETS nvim) @@ -566,7 +563,7 @@ add_library( EXCLUDE_FROM_ALL ${NVIM_SOURCES} ${NVIM_GENERATED_SOURCES} ${NVIM_HEADERS} ${NVIM_GENERATED_FOR_SOURCES} ${NVIM_GENERATED_FOR_HEADERS} - ${XDIFF_SOURCES} ${XDIFF_HEADERS} ${TREESITTER_SOURCES} + ${XDIFF_SOURCES} ${XDIFF_HEADERS} ) set_property(TARGET libnvim APPEND PROPERTY INCLUDE_DIRECTORIES ${LUA_PREFERRED_INCLUDE_DIRS}) @@ -596,7 +593,7 @@ else() EXCLUDE_FROM_ALL ${NVIM_SOURCES} ${NVIM_GENERATED_SOURCES} ${NVIM_HEADERS} ${NVIM_GENERATED_FOR_SOURCES} ${NVIM_GENERATED_FOR_HEADERS} - ${XDIFF_SOURCES} ${XDIFF_HEADERS} ${TREESITTER_SOURCES} + ${XDIFF_SOURCES} ${XDIFF_HEADERS} ${UNIT_TEST_FIXTURES} ) target_link_libraries(nvim-test ${NVIM_TEST_LINK_LIBRARIES}) @@ -620,9 +617,19 @@ if(CLANG_ASAN_UBSAN) message(STATUS "Enabling Clang address sanitizer and undefined behavior sanitizer for nvim.") check_c_compiler_flag(-fno-sanitize-recover=all SANITIZE_RECOVER_ALL) if(SANITIZE_RECOVER_ALL) - set(SANITIZE_RECOVER -fno-sanitize-recover=all) # Clang 3.6+ + if(CI_BUILD) + # Try to recover from all sanitize issues so we get reports about all failures + set(SANITIZE_RECOVER -fsanitize-recover=all) # Clang 3.6+ + else() + set(SANITIZE_RECOVER -fno-sanitize-recover=all) # Clang 3.6+ + endif() else() - set(SANITIZE_RECOVER -fno-sanitize-recover) # Clang 3.5- + if(CI_BUILD) + # Try to recover from all sanitize issues so we get reports about all failures + set(SANITIZE_RECOVER -fsanitize-recover) # Clang 3.5- + else() + set(SANITIZE_RECOVER -fno-sanitize-recover) # Clang 3.5- + endif() endif() set_property(TARGET nvim APPEND PROPERTY COMPILE_DEFINITIONS EXITFREE) set_property(TARGET nvim APPEND PROPERTY COMPILE_OPTIONS ${SANITIZE_RECOVER} -fno-omit-frame-pointer -fno-optimize-sibling-calls -fsanitize=address -fsanitize=undefined -fsanitize-blacklist=${PROJECT_SOURCE_DIR}/src/.asan-blacklist) diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 8e61976c4b..4fc0ee4fdf 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -7,6 +7,7 @@ #include <stdint.h> #include <stdlib.h> #include <limits.h> + #include <lauxlib.h> #include "nvim/api/buffer.h" @@ -27,6 +28,7 @@ #include "nvim/map.h" #include "nvim/mark.h" #include "nvim/extmark.h" +#include "nvim/decoration.h" #include "nvim/fileio.h" #include "nvim/move.h" #include "nvim/syntax.h" @@ -175,8 +177,7 @@ Boolean nvim_buf_attach(uint64_t channel_id, } cb.on_lines = v->data.luaref; v->data.luaref = LUA_NOREF; - } else if (is_lua && strequal("_on_bytes", k.data)) { - // NB: undocumented, untested and incomplete interface! + } else if (is_lua && strequal("on_bytes", k.data)) { if (v->type != kObjectTypeLuaRef) { api_set_error(err, kErrorTypeValidation, "callback is not a function"); goto error; @@ -213,10 +214,10 @@ Boolean nvim_buf_attach(uint64_t channel_id, error: // TODO(bfredl): ASAN build should check that the ref table is empty? - executor_free_luaref(cb.on_lines); - executor_free_luaref(cb.on_bytes); - executor_free_luaref(cb.on_changedtick); - executor_free_luaref(cb.on_detach); + api_free_luaref(cb.on_lines); + api_free_luaref(cb.on_bytes); + api_free_luaref(cb.on_changedtick); + api_free_luaref(cb.on_detach); return false; } @@ -245,78 +246,6 @@ Boolean nvim_buf_detach(uint64_t channel_id, return true; } -static void buf_clear_luahl(buf_T *buf, bool force) -{ - if (buf->b_luahl || force) { - executor_free_luaref(buf->b_luahl_start); - executor_free_luaref(buf->b_luahl_window); - executor_free_luaref(buf->b_luahl_line); - executor_free_luaref(buf->b_luahl_end); - } - buf->b_luahl_start = LUA_NOREF; - buf->b_luahl_window = LUA_NOREF; - buf->b_luahl_line = LUA_NOREF; - buf->b_luahl_end = LUA_NOREF; -} - -/// Unstabilized interface for defining syntax hl in lua. -/// -/// This is not yet safe for general use, lua callbacks will need to -/// be restricted, like textlock and probably other stuff. -/// -/// The API on_line/nvim__put_attr is quite raw and not intended to be the -/// final shape. Ideally this should operate on chunks larger than a single -/// line to reduce interpreter overhead, and generate annotation objects -/// (bufhl/virttext) on the fly but using the same representation. -void nvim__buf_set_luahl(uint64_t channel_id, Buffer buffer, - DictionaryOf(LuaRef) opts, Error *err) - FUNC_API_LUA_ONLY -{ - buf_T *buf = find_buffer_by_handle(buffer, err); - - if (!buf) { - return; - } - - redraw_buf_later(buf, NOT_VALID); - buf_clear_luahl(buf, false); - - for (size_t i = 0; i < opts.size; i++) { - String k = opts.items[i].key; - Object *v = &opts.items[i].value; - if (strequal("on_start", k.data)) { - if (v->type != kObjectTypeLuaRef) { - api_set_error(err, kErrorTypeValidation, "callback is not a function"); - goto error; - } - buf->b_luahl_start = v->data.luaref; - v->data.luaref = LUA_NOREF; - } else if (strequal("on_window", k.data)) { - if (v->type != kObjectTypeLuaRef) { - api_set_error(err, kErrorTypeValidation, "callback is not a function"); - goto error; - } - buf->b_luahl_window = v->data.luaref; - v->data.luaref = LUA_NOREF; - } else if (strequal("on_line", k.data)) { - if (v->type != kObjectTypeLuaRef) { - api_set_error(err, kErrorTypeValidation, "callback is not a function"); - goto error; - } - buf->b_luahl_line = v->data.luaref; - v->data.luaref = LUA_NOREF; - } else { - api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data); - goto error; - } - } - buf->b_luahl = true; - return; -error: - buf_clear_luahl(buf, true); - buf->b_luahl = false; -} - void nvim__buf_redraw_range(Buffer buffer, Integer first, Integer last, Error *err) FUNC_API_LUA_ONLY @@ -1025,6 +954,53 @@ Boolean nvim_buf_is_loaded(Buffer buffer) return buf && buf->b_ml.ml_mfp != NULL; } +/// Deletes the buffer. See |:bwipeout| +/// +/// @param buffer Buffer handle, or 0 for current buffer +/// @param opts Optional parameters. Keys: +/// - force: Force deletion and ignore unsaved changes. +/// - unload: Unloaded only, do not delete. See |:bunload| +void nvim_buf_delete(Buffer buffer, Dictionary opts, Error *err) + FUNC_API_SINCE(7) +{ + buf_T *buf = find_buffer_by_handle(buffer, err); + + if (ERROR_SET(err)) { + return; + } + + bool force = false; + bool unload = false; + for (size_t i = 0; i < opts.size; i++) { + String k = opts.items[i].key; + Object v = opts.items[i].value; + if (strequal("force", k.data)) { + force = api_object_to_bool(v, "force", false, err); + } else if (strequal("unload", k.data)) { + unload = api_object_to_bool(v, "unload", false, err); + } else { + api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data); + return; + } + } + + if (ERROR_SET(err)) { + return; + } + + int result = do_buffer( + unload ? DOBUF_UNLOAD : DOBUF_WIPE, + DOBUF_FIRST, + FORWARD, + buf->handle, + force); + + if (result == FAIL) { + api_set_error(err, kErrorTypeException, "Failed to unload buffer."); + return; + } +} + /// Checks if a buffer is valid. /// /// @note Even if a buffer is valid it may have been unloaded. See |api-buffer| @@ -1108,15 +1084,67 @@ ArrayOf(Integer, 2) nvim_buf_get_mark(Buffer buffer, String name, Error *err) return rv; } +static Array extmark_to_array(ExtmarkInfo extmark, bool id, bool add_dict) +{ + Array rv = ARRAY_DICT_INIT; + if (id) { + ADD(rv, INTEGER_OBJ((Integer)extmark.mark_id)); + } + ADD(rv, INTEGER_OBJ(extmark.row)); + ADD(rv, INTEGER_OBJ(extmark.col)); + + if (add_dict) { + Dictionary dict = ARRAY_DICT_INIT; + + if (extmark.end_row >= 0) { + PUT(dict, "end_row", INTEGER_OBJ(extmark.end_row)); + PUT(dict, "end_col", INTEGER_OBJ(extmark.end_col)); + } + + if (extmark.decor) { + Decoration *decor = extmark.decor; + if (decor->hl_id) { + String name = cstr_to_string((const char *)syn_id2name(decor->hl_id)); + PUT(dict, "hl_group", STRING_OBJ(name)); + } + if (kv_size(decor->virt_text)) { + Array chunks = ARRAY_DICT_INIT; + for (size_t i = 0; i < decor->virt_text.size; i++) { + Array chunk = ARRAY_DICT_INIT; + VirtTextChunk *vtc = &decor->virt_text.items[i]; + ADD(chunk, STRING_OBJ(cstr_to_string(vtc->text))); + if (vtc->hl_id > 0) { + ADD(chunk, + STRING_OBJ(cstr_to_string( + (const char *)syn_id2name(vtc->hl_id)))); + } + ADD(chunks, ARRAY_OBJ(chunk)); + } + PUT(dict, "virt_text", ARRAY_OBJ(chunks)); + } + } + + if (dict.size) { + ADD(rv, DICTIONARY_OBJ(dict)); + } + } + + return rv; +} + /// Returns position for a given extmark id /// /// @param buffer Buffer handle, or 0 for current buffer /// @param ns_id Namespace id from |nvim_create_namespace()| /// @param id Extmark id +/// @param opts Optional parameters. Keys: +/// - limit: Maximum number of marks to return +/// - details Whether to include the details dict /// @param[out] err Error details, if any /// @return (row, col) tuple or empty list () if extmark id was absent ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id, - Integer id, Error *err) + Integer id, Dictionary opts, + Error *err) FUNC_API_SINCE(7) { Array rv = ARRAY_DICT_INIT; @@ -1132,13 +1160,31 @@ ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id, return rv; } + bool details = false; + for (size_t i = 0; i < opts.size; i++) { + String k = opts.items[i].key; + Object *v = &opts.items[i].value; + if (strequal("details", k.data)) { + if (v->type == kObjectTypeBoolean) { + details = v->data.boolean; + } else if (v->type == kObjectTypeInteger) { + details = v->data.integer; + } else { + api_set_error(err, kErrorTypeValidation, "details is not an boolean"); + return rv; + } + } else { + api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data); + return rv; + } + } + + ExtmarkInfo extmark = extmark_from_id(buf, (uint64_t)ns_id, (uint64_t)id); if (extmark.row < 0) { return rv; } - ADD(rv, INTEGER_OBJ((Integer)extmark.row)); - ADD(rv, INTEGER_OBJ((Integer)extmark.col)); - return rv; + return extmark_to_array(extmark, false, (bool)details); } /// Gets extmarks in "traversal order" from a |charwise| region defined by @@ -1181,10 +1227,12 @@ ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id, /// (whose position defines the bound) /// @param opts Optional parameters. Keys: /// - limit: Maximum number of marks to return +/// - details Whether to include the details dict /// @param[out] err Error details, if any /// @return List of [extmark_id, row, col] tuples in "traversal order". -Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, - Object end, Dictionary opts, Error *err) +Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, + Object start, Object end, + Dictionary opts, Error *err) FUNC_API_SINCE(7) { Array rv = ARRAY_DICT_INIT; @@ -1198,7 +1246,9 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, api_set_error(err, kErrorTypeValidation, "Invalid ns_id"); return rv; } + Integer limit = -1; + bool details = false; for (size_t i = 0; i < opts.size; i++) { String k = opts.items[i].key; @@ -1209,6 +1259,15 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, return rv; } limit = v->data.integer; + } else if (strequal("details", k.data)) { + if (v->type == kObjectTypeBoolean) { + details = v->data.boolean; + } else if (v->type == kObjectTypeInteger) { + details = v->data.integer; + } else { + api_set_error(err, kErrorTypeValidation, "details is not an boolean"); + return rv; + } } else { api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data); return rv; @@ -1241,16 +1300,11 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, } - ExtmarkArray marks = extmark_get(buf, (uint64_t)ns_id, l_row, l_col, u_row, - u_col, (int64_t)limit, reverse); + ExtmarkInfoArray marks = extmark_get(buf, (uint64_t)ns_id, l_row, l_col, + u_row, u_col, (int64_t)limit, reverse); for (size_t i = 0; i < kv_size(marks); i++) { - Array mark = ARRAY_DICT_INIT; - ExtmarkInfo extmark = kv_A(marks, i); - ADD(mark, INTEGER_OBJ((Integer)extmark.mark_id)); - ADD(mark, INTEGER_OBJ(extmark.row)); - ADD(mark, INTEGER_OBJ(extmark.col)); - ADD(rv, ARRAY_OBJ(mark)); + ADD(rv, ARRAY_OBJ(extmark_to_array(kv_A(marks, i), true, (bool)details))); } kv_destroy(marks); @@ -1260,27 +1314,40 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, /// Creates or updates an extmark. /// /// To create a new extmark, pass id=0. The extmark id will be returned. -// To move an existing mark, pass its id. +/// To move an existing mark, pass its id. /// /// It is also allowed to create a new mark by passing in a previously unused /// id, but 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. +/// /// @param buffer Buffer handle, or 0 for current buffer /// @param ns_id Namespace id from |nvim_create_namespace()| -/// @param id Extmark id, or 0 to create new /// @param line Line number where to place the mark /// @param col Column where to place the mark -/// @param opts Optional parameters. Currently not used. +/// @param opts Optional parameters. +/// - id : id of the extmark to edit. +/// - end_line : ending line of the mark, 0-based inclusive. +/// - end_col : ending col of the mark, 0-based inclusive. +/// - hl_group : name of the highlight group used to highlight +/// this mark. +/// - virt_text : virtual text to link to this mark. +/// - 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. /// @param[out] err Error details, if any /// @return Id of the created/updated extmark -Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer id, +Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer col, Dictionary opts, Error *err) FUNC_API_SINCE(7) { buf_T *buf = find_buffer_by_handle(buffer, err); if (!buf) { + api_set_error(err, kErrorTypeValidation, "Invalid buffer id"); return 0; } @@ -1289,11 +1356,6 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer id, return 0; } - if (opts.size > 0) { - api_set_error(err, kErrorTypeValidation, "opts dict isn't empty"); - return 0; - } - size_t len = 0; if (line < 0 || line > buf->b_ml.ml_line_count) { api_set_error(err, kErrorTypeValidation, "line value outside range"); @@ -1309,18 +1371,138 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer id, return 0; } - uint64_t id_num; - if (id >= 0) { - id_num = (uint64_t)id; + bool ephemeral = false; + + uint64_t id = 0; + int line2 = -1, hl_id = 0; + colnr_T col2 = 0; + VirtText virt_text = KV_INITIAL_VALUE; + for (size_t i = 0; i < opts.size; i++) { + String k = opts.items[i].key; + Object *v = &opts.items[i].value; + if (strequal("id", k.data)) { + if (v->type != kObjectTypeInteger || v->data.integer <= 0) { + api_set_error(err, kErrorTypeValidation, + "id is not a positive integer"); + goto error; + } + + id = (uint64_t)v->data.integer; + } else if (strequal("end_line", k.data)) { + if (v->type != kObjectTypeInteger) { + api_set_error(err, kErrorTypeValidation, + "end_line is not an integer"); + goto error; + } + if (v->data.integer < 0 || v->data.integer > buf->b_ml.ml_line_count) { + api_set_error(err, kErrorTypeValidation, + "end_line value outside range"); + goto error; + } + + line2 = (int)v->data.integer; + } else if (strequal("end_col", k.data)) { + if (v->type != kObjectTypeInteger) { + api_set_error(err, kErrorTypeValidation, + "end_col is not an integer"); + goto error; + } + if (v->data.integer < 0 || v->data.integer > MAXCOL) { + api_set_error(err, kErrorTypeValidation, + "end_col value outside range"); + goto error; + } + + col2 = (colnr_T)v->data.integer; + } else if (strequal("hl_group", k.data)) { + String hl_group; + switch (v->type) { + case kObjectTypeString: + hl_group = v->data.string; + hl_id = syn_check_group( + (char_u *)(hl_group.data), + (int)hl_group.size); + break; + case kObjectTypeInteger: + hl_id = (int)v->data.integer; + break; + default: + api_set_error(err, kErrorTypeValidation, + "hl_group is not valid."); + goto error; + } + } else if (strequal("virt_text", k.data)) { + if (v->type != kObjectTypeArray) { + api_set_error(err, kErrorTypeValidation, + "virt_text is not an Array"); + goto error; + } + virt_text = parse_virt_text(v->data.array, err); + if (ERROR_SET(err)) { + goto error; + } + } else if (strequal("ephemeral", k.data)) { + ephemeral = api_object_to_bool(*v, "ephemeral", false, err); + if (ERROR_SET(err)) { + goto error; + } + } else { + api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data); + goto error; + } + } + + if (col2 >= 0) { + if (line2 >= 0 && line2 < buf->b_ml.ml_line_count) { + len = STRLEN(ml_get_buf(buf, (linenr_T)line2 + 1, false)); + } else if (line2 == buf->b_ml.ml_line_count) { + // We are trying to add an extmark past final newline + len = 0; + } else { + // reuse len from before + line2 = (int)line; + } + if (col2 > (Integer)len) { + api_set_error(err, kErrorTypeValidation, "end_col value outside range"); + goto error; + } + } else if (line2 >= 0) { + col2 = 0; + } + + // TODO(bfredl): synergize these two branches even more + if (ephemeral && decor_state.buf == buf) { + int attr_id = hl_id > 0 ? syn_id2attr(hl_id) : 0; + VirtText *vt_allocated = NULL; + if (kv_size(virt_text)) { + vt_allocated = xmalloc(sizeof *vt_allocated); + *vt_allocated = virt_text; + } + decor_add_ephemeral(attr_id, (int)line, (colnr_T)col, + (int)line2, (colnr_T)col2, vt_allocated); } else { - api_set_error(err, kErrorTypeValidation, "Invalid mark id"); - return 0; + if (ephemeral) { + api_set_error(err, kErrorTypeException, "not yet implemented"); + goto error; + } + Decoration *decor = NULL; + if (kv_size(virt_text)) { + decor = xcalloc(1, sizeof(*decor)); + decor->hl_id = hl_id; + decor->virt_text = virt_text; + } else if (hl_id) { + decor = decor_hl(hl_id); + } + + id = extmark_set(buf, (uint64_t)ns_id, id, (int)line, (colnr_T)col, + line2, col2, decor, kExtmarkNoUndo); } - id_num = extmark_set(buf, (uint64_t)ns_id, id_num, - (int)line, (colnr_T)col, kExtmarkUndo); + return (Integer)id; - return (Integer)id_num; +error: + clear_virttext(&virt_text); + return 0; } /// Removes an extmark. @@ -1358,17 +1540,17 @@ Boolean nvim_buf_del_extmark(Buffer buffer, /// 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 +/// 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 +/// 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. +/// |nvim_create_namespace()| to create a new empty namespace. /// /// @param buffer Buffer handle, or 0 for current buffer /// @param ns_id namespace to use or -1 for ungrouped highlight @@ -1412,9 +1594,9 @@ Integer nvim_buf_add_highlight(Buffer buffer, return src_id; } - int hlg_id = 0; + int hl_id = 0; if (hl_group.size > 0) { - hlg_id = syn_check_group((char_u *)hl_group.data, (int)hl_group.size); + hl_id = syn_check_group((char_u *)hl_group.data, (int)hl_group.size); } else { return src_id; } @@ -1425,10 +1607,10 @@ Integer nvim_buf_add_highlight(Buffer buffer, end_line++; } - extmark_add_decoration(buf, ns_id, hlg_id, - (int)line, (colnr_T)col_start, - end_line, (colnr_T)col_end, - VIRTTEXT_EMPTY); + extmark_set(buf, ns_id, 0, + (int)line, (colnr_T)col_start, + end_line, (colnr_T)col_end, + decor_hl(hl_id), kExtmarkNoUndo); return src_id; } @@ -1470,7 +1652,7 @@ void nvim_buf_clear_namespace(Buffer buffer, /// Clears highlights and virtual text from namespace and range of lines /// -/// @deprecated use |nvim_buf_clear_namespace|. +/// @deprecated use |nvim_buf_clear_namespace()|. /// /// @param buffer Buffer handle, or 0 for current buffer /// @param ns_id Namespace to clear, or -1 to clear all. @@ -1488,43 +1670,6 @@ void nvim_buf_clear_highlight(Buffer buffer, nvim_buf_clear_namespace(buffer, ns_id, line_start, line_end, err); } -static VirtText parse_virt_text(Array chunks, Error *err) -{ - VirtText virt_text = KV_INITIAL_VALUE; - for (size_t i = 0; i < chunks.size; i++) { - if (chunks.items[i].type != kObjectTypeArray) { - api_set_error(err, kErrorTypeValidation, "Chunk is not an array"); - goto free_exit; - } - Array chunk = chunks.items[i].data.array; - if (chunk.size == 0 || chunk.size > 2 - || chunk.items[0].type != kObjectTypeString - || (chunk.size == 2 && chunk.items[1].type != kObjectTypeString)) { - api_set_error(err, kErrorTypeValidation, - "Chunk is not an array with one or two strings"); - goto free_exit; - } - - String str = chunk.items[0].data.string; - char *text = transstr(str.size > 0 ? str.data : ""); // allocates - - int hl_id = 0; - if (chunk.size == 2) { - String hl = chunk.items[1].data.string; - if (hl.size > 0) { - hl_id = syn_check_group((char_u *)hl.data, (int)hl.size); - } - } - kv_push(virt_text, ((VirtTextChunk){ .text = text, .hl_id = hl_id })); - } - - return virt_text; - -free_exit: - clear_virttext(&virt_text); - return virt_text; -} - /// Set the virtual text (annotation) for a buffer line. /// @@ -1534,11 +1679,11 @@ free_exit: /// begin one cell (|lcs-eol| or space) after the ordinary text. /// /// Namespaces are used to support batch deletion/updating of virtual text. -/// To create a namespace, use |nvim_create_namespace|. Virtual text is -/// cleared using |nvim_buf_clear_namespace|. The same `ns_id` can be used for -/// both virtual text and highlights added by |nvim_buf_add_highlight|, both -/// can then be cleared with a single call to |nvim_buf_clear_namespace|. If the -/// virtual text never will be cleared by an API call, pass `ns_id = -1`. +/// To create a namespace, use |nvim_create_namespace()|. Virtual text is +/// cleared using |nvim_buf_clear_namespace()|. The same `ns_id` can be used for +/// both virtual text and highlights added by |nvim_buf_add_highlight()|, both +/// can then be cleared with a single call to |nvim_buf_clear_namespace()|. If +/// the virtual text never will be cleared by an API call, pass `ns_id = -1`. /// /// As a shorthand, `ns_id = 0` can be used to create a new namespace for the /// virtual text, the allocated id is then returned. @@ -1584,7 +1729,7 @@ Integer nvim_buf_set_virtual_text(Buffer buffer, } - VirtText *existing = extmark_find_virttext(buf, (int)line, ns_id); + VirtText *existing = decor_find_virttext(buf, (int)line, ns_id); if (existing) { clear_virttext(existing); @@ -1592,113 +1737,49 @@ Integer nvim_buf_set_virtual_text(Buffer buffer, return src_id; } - extmark_add_decoration(buf, ns_id, 0, - (int)line, 0, -1, -1, - virt_text); + Decoration *decor = xcalloc(1, sizeof(*decor)); + decor->virt_text = virt_text; + + extmark_set(buf, ns_id, 0, (int)line, 0, -1, -1, decor, kExtmarkNoUndo); return src_id; } -/// Get the virtual text (annotation) for a buffer line. -/// -/// The virtual text is returned as list of lists, whereas the inner lists have -/// either one or two elements. The first element is the actual text, the -/// optional second element is the highlight group. +/// call a function with buffer as temporary current buffer /// -/// The format is exactly the same as given to nvim_buf_set_virtual_text(). +/// 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 (calleed the "autocmd window" for +/// historical reasons) will be used. /// -/// If there is no virtual text associated with the given line, an empty list -/// is returned. +/// This is useful e.g. to call vimL functions that only work with the current +/// buffer/window currently, like |termopen()|. /// -/// @param buffer Buffer handle, or 0 for current buffer -/// @param line Line to get the virtual text from (zero-indexed) -/// @param[out] err Error details, if any -/// @return List of virtual text chunks -Array nvim_buf_get_virtual_text(Buffer buffer, Integer line, Error *err) +/// @param buffer Buffer handle, or 0 for current buffer +/// @param fun Function to call inside the buffer (currently lua callable +/// only) +/// @param[out] err Error details, if any +/// @return Return value of function. NB: will deepcopy lua values +/// currently, use upvalues to send lua references in and out. +Object nvim_buf_call(Buffer buffer, LuaRef fun, Error *err) FUNC_API_SINCE(7) -{ - Array chunks = ARRAY_DICT_INIT; - - buf_T *buf = find_buffer_by_handle(buffer, err); - if (!buf) { - return chunks; - } - - if (line < 0 || line >= MAXLNUM) { - api_set_error(err, kErrorTypeValidation, "Line number outside range"); - return chunks; - } - - VirtText *virt_text = extmark_find_virttext(buf, (int)line, 0); - - if (!virt_text) { - return chunks; - } - - for (size_t i = 0; i < virt_text->size; i++) { - Array chunk = ARRAY_DICT_INIT; - VirtTextChunk *vtc = &virt_text->items[i]; - ADD(chunk, STRING_OBJ(cstr_to_string(vtc->text))); - if (vtc->hl_id > 0) { - ADD(chunk, STRING_OBJ(cstr_to_string( - (const char *)syn_id2name(vtc->hl_id)))); - } - ADD(chunks, ARRAY_OBJ(chunk)); - } - - return chunks; -} - -Integer nvim__buf_add_decoration(Buffer buffer, Integer ns_id, String hl_group, - Integer start_row, Integer start_col, - Integer end_row, Integer end_col, - Array virt_text, - Error *err) + FUNC_API_LUA_ONLY { buf_T *buf = find_buffer_by_handle(buffer, err); if (!buf) { - return 0; - } - - if (!ns_initialized((uint64_t)ns_id)) { - api_set_error(err, kErrorTypeValidation, "Invalid ns_id"); - return 0; - } - - - if (start_row < 0 || start_row >= MAXLNUM || end_row > MAXCOL) { - api_set_error(err, kErrorTypeValidation, "Line number outside range"); - return 0; - } - - if (start_col < 0 || start_col > MAXCOL || end_col > MAXCOL) { - api_set_error(err, kErrorTypeValidation, "Column value outside range"); - return 0; - } - if (end_row < 0 || end_col < 0) { - end_row = -1; - end_col = -1; - } - - if (start_row >= buf->b_ml.ml_line_count - || end_row >= buf->b_ml.ml_line_count) { - // safety check, we can't add marks outside the range - return 0; - } - - int hlg_id = 0; - if (hl_group.size > 0) { - hlg_id = syn_check_group((char_u *)hl_group.data, (int)hl_group.size); + return NIL; } + try_start(); + aco_save_T aco; + aucmd_prepbuf(&aco, (buf_T *)buf); - VirtText vt = parse_virt_text(virt_text, err); - if (ERROR_SET(err)) { - return 0; - } + Array args = ARRAY_DICT_INIT; + Object res = nlua_call_ref(fun, NULL, args, true, err); - uint64_t mark_id = extmark_add_decoration(buf, (uint64_t)ns_id, hlg_id, - (int)start_row, (colnr_T)start_col, - (int)end_row, (colnr_T)end_col, vt); - return (Integer)mark_id; + aucmd_restbuf(&aco); + try_end(err); + return res; } Dictionary nvim__buf_stats(Buffer buffer, Error *err) @@ -1721,6 +1802,7 @@ Dictionary nvim__buf_stats(Buffer buffer, Error *err) // NB: this should be zero at any time API functions are called, // this exists to debug issues PUT(rv, "dirty_bytes", INTEGER_OBJ((Integer)buf->deleted_bytes)); + PUT(rv, "dirty_bytes2", INTEGER_OBJ((Integer)buf->deleted_bytes2)); u_header_T *uhp = NULL; if (buf->b_u_curhead != NULL) { diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index f194b6b474..2c99d3426c 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -15,6 +15,8 @@ #include "nvim/lua/executor.h" #include "nvim/ascii.h" #include "nvim/assert.h" +#include "nvim/charset.h" +#include "nvim/syntax.h" #include "nvim/vim.h" #include "nvim/buffer.h" #include "nvim/window.h" @@ -25,6 +27,7 @@ #include "nvim/map_defs.h" #include "nvim/map.h" #include "nvim/extmark.h" +#include "nvim/decoration.h" #include "nvim/option.h" #include "nvim/option_defs.h" #include "nvim/version.h" @@ -1198,7 +1201,7 @@ void api_free_object(Object value) break; case kObjectTypeLuaRef: - executor_free_luaref(value.data.luaref); + api_free_luaref(value.data.luaref); break; default: @@ -1579,3 +1582,100 @@ bool extmark_get_index_from_obj(buf_T *buf, Integer ns_id, Object obj, int return false; } } + +VirtText parse_virt_text(Array chunks, Error *err) +{ + VirtText virt_text = KV_INITIAL_VALUE; + for (size_t i = 0; i < chunks.size; i++) { + if (chunks.items[i].type != kObjectTypeArray) { + api_set_error(err, kErrorTypeValidation, "Chunk is not an array"); + goto free_exit; + } + Array chunk = chunks.items[i].data.array; + if (chunk.size == 0 || chunk.size > 2 + || chunk.items[0].type != kObjectTypeString + || (chunk.size == 2 && chunk.items[1].type != kObjectTypeString)) { + api_set_error(err, kErrorTypeValidation, + "Chunk is not an array with one or two strings"); + goto free_exit; + } + + String str = chunk.items[0].data.string; + char *text = transstr(str.size > 0 ? str.data : ""); // allocates + + int hl_id = 0; + if (chunk.size == 2) { + String hl = chunk.items[1].data.string; + if (hl.size > 0) { + hl_id = syn_check_group((char_u *)hl.data, (int)hl.size); + } + } + kv_push(virt_text, ((VirtTextChunk){ .text = text, .hl_id = hl_id })); + } + + return virt_text; + +free_exit: + clear_virttext(&virt_text); + return virt_text; +} + +/// Force obj to bool. +/// If it fails, returns false and sets err +/// @param obj The object to coerce to a boolean +/// @param what The name of the object, used for error message +/// @param nil_value What to return if the type is nil. +/// @param err Set if there was an error in converting to a bool +bool api_object_to_bool(Object obj, const char *what, + bool nil_value, Error *err) +{ + if (obj.type == kObjectTypeBoolean) { + return obj.data.boolean; + } else if (obj.type == kObjectTypeInteger) { + return obj.data.integer; // C semantics: non-zero int is true + } else if (obj.type == kObjectTypeNil) { + return nil_value; // caller decides what NIL (missing retval in lua) means + } else { + api_set_error(err, kErrorTypeValidation, "%s is not an boolean", what); + return false; + } +} + +const char *describe_ns(NS ns_id) +{ + String name; + handle_T id; + map_foreach(namespace_ids, name, id, { + if ((NS)id == ns_id && name.size) { + return name.data; + } + }) + return "(UNKNOWN PLUGIN)"; +} + +DecorProvider *get_provider(NS ns_id, bool force) +{ + ssize_t i; + for (i = 0; i < (ssize_t)kv_size(decor_providers); i++) { + DecorProvider *item = &kv_A(decor_providers, i); + if (item->ns_id == ns_id) { + return item; + } else if (item->ns_id > ns_id) { + break; + } + } + + if (!force) { + return NULL; + } + + for (ssize_t j = (ssize_t)kv_size(decor_providers)-1; j >= i; j++) { + // allocates if needed: + (void)kv_a(decor_providers, (size_t)j+1); + kv_A(decor_providers, (size_t)j+1) = kv_A(decor_providers, j); + } + DecorProvider *item = &kv_a(decor_providers, (size_t)i); + *item = DECORATION_PROVIDER_INIT(ns_id); + + return item; +} diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h index df3a263dcf..271fd5b485 100644 --- a/src/nvim/api/private/helpers.h +++ b/src/nvim/api/private/helpers.h @@ -7,6 +7,7 @@ #include "nvim/vim.h" #include "nvim/getchar.h" #include "nvim/memory.h" +#include "nvim/decoration.h" #include "nvim/ex_eval.h" #include "nvim/lib/kvec.h" @@ -52,7 +53,8 @@ .type = kObjectTypeLuaRef, \ .data.luaref = r }) -#define NIL ((Object) {.type = kObjectTypeNil}) +#define NIL ((Object)OBJECT_INIT) +#define NULL_STRING ((String)STRING_INIT) #define PUT(dict, k, v) \ kv_push(dict, ((KeyValuePair) { .key = cstr_to_string(k), .value = v })) diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c index 717713b948..51f1af4eb5 100644 --- a/src/nvim/api/ui.c +++ b/src/nvim/api/ui.c @@ -355,7 +355,7 @@ void nvim_ui_pum_set_height(uint64_t channel_id, Integer height, Error *err) /// 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 -/// decorations such as boarders and sliders. Floats need not use the same font +/// 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. /// diff --git a/src/nvim/api/ui_events.in.h b/src/nvim/api/ui_events.in.h index ab31db39e9..e934d5dc92 100644 --- a/src/nvim/api/ui_events.in.h +++ b/src/nvim/api/ui_events.in.h @@ -1,7 +1,7 @@ #ifndef NVIM_API_UI_EVENTS_IN_H #define NVIM_API_UI_EVENTS_IN_H -// This file is not compiled, just parsed for definitons +// This file is not compiled, just parsed for definitions #ifdef INCLUDE_GENERATED_DECLARATIONS # error "don't include this file, include nvim/ui.h" #endif @@ -36,13 +36,15 @@ void set_title(String title) FUNC_API_SINCE(3); void set_icon(String icon) FUNC_API_SINCE(3); +void screenshot(String path) + FUNC_API_SINCE(7) FUNC_API_REMOTE_IMPL; void option_set(String name, Object value) FUNC_API_SINCE(4) FUNC_API_BRIDGE_IMPL; // Stop event is not exported as such, represented by EOF in the msgpack stream. void stop(void) FUNC_API_NOEXPORT; -// First revison of the grid protocol, used by default +// First revision of the grid protocol, used by default void update_fg(Integer fg) FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; void update_bg(Integer bg) @@ -66,7 +68,7 @@ void set_scroll_region(Integer top, Integer bot, Integer left, Integer right) void scroll(Integer count) FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; -// Second revison of the grid protocol, used with ext_linegrid ui option +// Second revision of the grid protocol, used with ext_linegrid ui option void default_colors_set(Integer rgb_fg, Integer rgb_bg, Integer rgb_sp, Integer cterm_fg, Integer cterm_bg) FUNC_API_SINCE(4) FUNC_API_REMOTE_IMPL; @@ -89,7 +91,7 @@ void grid_scroll(Integer grid, Integer top, Integer bot, void grid_destroy(Integer grid) FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY; -// For perfomance and simplicity, we use the dense screen representation +// For performance and simplicity, we use the dense screen representation // in internal code, such as compositor and TUI. The remote_ui module will // translate this in to the public grid_line format. void raw_line(Integer grid, Integer row, Integer startcol, diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index d6f95c7a5f..cf822782d8 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -41,7 +41,7 @@ #include "nvim/ops.h" #include "nvim/option.h" #include "nvim/state.h" -#include "nvim/extmark.h" +#include "nvim/decoration.h" #include "nvim/syntax.h" #include "nvim/getchar.h" #include "nvim/os/input.h" @@ -199,14 +199,77 @@ Integer nvim_get_hl_id_by_name(String name) return syn_check_group((const char_u *)name.data, (int)name.size); } +Dictionary nvim__get_hl_defs(Integer ns_id, Error *err) +{ + if (ns_id == 0) { + return get_global_hl_defs(); + } + abort(); +} + +/// Set a highlight group. +/// +/// @param ns_id number of namespace for this highlight +/// @param name highlight group name, like ErrorMsg +/// @param val highlight definiton map, like |nvim_get_hl_by_name|. +/// @param[out] err Error details, if any +/// +/// TODO: ns_id = 0, should modify :highlight namespace +/// TODO val should take update vs reset flag +void nvim_set_hl(Integer ns_id, String name, Dictionary val, Error *err) + FUNC_API_SINCE(7) +{ + int hl_id = syn_check_group( (char_u *)(name.data), (int)name.size); + int link_id = -1; + + HlAttrs attrs = dict2hlattrs(val, true, &link_id, err); + if (!ERROR_SET(err)) { + ns_hl_def((NS)ns_id, hl_id, attrs, link_id); + } +} + +/// 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. +/// +/// @param ns_id the namespace to activate +/// @param[out] err Error details, if any +void nvim_set_hl_ns(Integer ns_id, Error *err) + FUNC_API_SINCE(7) + FUNC_API_FAST +{ + if (ns_id >= 0) { + ns_hl_active = (NS)ns_id; + } + + // 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 (!updating_screen && !ns_hl_changed) { + multiqueue_put(main_loop.events, on_redraw_event, 0); + } + ns_hl_changed = true; +} + +static void on_redraw_event(void **argv) + FUNC_API_NOEXPORT +{ + redraw_all_later(NOT_VALID); +} + + /// 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. -// -// If you need to input sequences like <C-o> use nvim_replace_termcodes -// to replace the termcodes and then pass the resulting string to -// nvim_feedkeys. You'll also want to enable escape_csi. +/// +/// If you need to input sequences like <C-o> use |nvim_replace_termcodes| to +/// replace the termcodes and then pass the resulting string to nvim_feedkeys. +/// You'll also want to enable escape_csi. /// /// Example: /// <pre> @@ -475,7 +538,7 @@ Object nvim_execute_lua(String code, Array args, Error *err) FUNC_API_DEPRECATED_SINCE(7) FUNC_API_REMOTE_ONLY { - return executor_exec_lua_api(code, args, err); + return nlua_exec(code, args, err); } /// Execute Lua code. Parameters (if any) are available as `...` inside the @@ -494,7 +557,7 @@ Object nvim_exec_lua(String code, Array args, Error *err) FUNC_API_SINCE(7) FUNC_API_REMOTE_ONLY { - return executor_exec_lua_api(code, args, err); + return nlua_exec(code, args, err); } /// Calls a VimL function. @@ -678,7 +741,11 @@ Integer nvim_strwidth(String text, Error *err) ArrayOf(String) nvim_list_runtime_paths(void) FUNC_API_SINCE(1) { + // TODO(bfredl): this should just work: + // return nvim_get_runtime_file(NULL_STRING, true); + Array rv = ARRAY_DICT_INIT; + char_u *rtp = p_rtp; if (*rtp == NUL) { @@ -725,22 +792,30 @@ ArrayOf(String) nvim_list_runtime_paths(void) /// @param name pattern of files to search for /// @param all whether to return all matches or only the first /// @return list of absolute paths to the found files -ArrayOf(String) nvim_get_runtime_file(String name, Boolean all) +ArrayOf(String) nvim_get_runtime_file(String name, Boolean all, Error *err) FUNC_API_SINCE(7) + FUNC_API_FAST { Array rv = ARRAY_DICT_INIT; - if (!name.data) { + + // TODO(bfredl): + if (name.size == 0) { + api_set_error(err, kErrorTypeValidation, "not yet implemented"); return rv; } + int flags = DIP_START | (all ? DIP_ALL : 0); - do_in_runtimepath((char_u *)name.data, flags, find_runtime_cb, &rv); + do_in_runtimepath((char_u *)name.data, + flags, find_runtime_cb, &rv); return rv; } static void find_runtime_cb(char_u *fname, void *cookie) { Array *rv = (Array *)cookie; - ADD(*rv, STRING_OBJ(cstr_to_string((char *)fname))); + if (fname != NULL) { + ADD(*rv, STRING_OBJ(cstr_to_string((char *)fname))); + } } String nvim__get_lib_dir(void) @@ -1477,7 +1552,7 @@ void nvim_unsubscribe(uint64_t channel_id, String event) Integer nvim_get_color_by_name(String name) FUNC_API_SINCE(1) { - return name_to_color((char_u *)name.data); + return name_to_color(name.data); } /// Returns a map of color names and RGB values. @@ -2477,7 +2552,7 @@ Array nvim_get_proc_children(Integer pid, Error *err) Array a = ARRAY_DICT_INIT; ADD(a, INTEGER_OBJ(pid)); String s = cstr_to_string("return vim._os_proc_children(select(1, ...))"); - Object o = nvim_exec_lua(s, a, err); + Object o = nlua_exec(s, a, err); api_free_string(s); api_free_array(a); if (o.type == kObjectTypeArray) { @@ -2523,7 +2598,7 @@ Object nvim_get_proc(Integer pid, Error *err) Array a = ARRAY_DICT_INIT; ADD(a, INTEGER_OBJ(pid)); String s = cstr_to_string("return vim._os_proc_info(select(1, ...))"); - Object o = nvim_exec_lua(s, a, err); + Object o = nlua_exec(s, a, err); api_free_string(s); api_free_array(a); if (o.type == kObjectTypeArray && o.data.array.size == 0) { @@ -2604,26 +2679,112 @@ Array nvim__inspect_cell(Integer grid, Integer row, Integer col, Error *err) return ret; } -/// Set attrs in nvim__buf_set_lua_hl callbacks -/// -/// TODO(bfredl): This is rather pedestrian. The final -/// interface should probably be derived from a reformed -/// bufhl/virttext interface with full support for multi-line -/// ranges etc -void nvim__put_attr(Integer id, Integer start_row, Integer start_col, - Integer end_row, Integer end_col) - FUNC_API_LUA_ONLY +void nvim__screenshot(String path) + FUNC_API_FAST { - if (!lua_attr_active) { - return; - } - if (id == 0 || syn_get_final_id((int)id) == 0) { + ui_call_screenshot(path); +} + +static void clear_provider(DecorProvider *p) +{ + if (p == NULL) { return; } - int attr = syn_id2attr((int)id); - if (attr == 0) { - return; + NLUA_CLEAR_REF(p->redraw_start); + NLUA_CLEAR_REF(p->redraw_buf); + NLUA_CLEAR_REF(p->redraw_win); + NLUA_CLEAR_REF(p->redraw_line); + NLUA_CLEAR_REF(p->redraw_end); + p->active = false; +} + +/// 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. +/// Similarily, 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. +/// +/// @param ns_id Namespace id from |nvim_create_namespace()| +/// @param 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 +/// interation with fold lines is subject to change) +/// ["win", winid, bufnr, row] +/// - on_end: called at the end of a redraw cycle +/// ["end", tick] +void nvim_set_decoration_provider(Integer ns_id, DictionaryOf(LuaRef) opts, + Error *err) + FUNC_API_SINCE(7) FUNC_API_LUA_ONLY +{ + DecorProvider *p = get_provider((NS)ns_id, true); + clear_provider(p); + + // regardless of what happens, it seems good idea to redraw + redraw_all_later(NOT_VALID); // TODO(bfredl): too soon? + + struct { + const char *name; + LuaRef *dest; + } cbs[] = { + { "on_start", &p->redraw_start }, + { "on_buf", &p->redraw_buf }, + { "on_win", &p->redraw_win }, + { "on_line", &p->redraw_line }, + { "on_end", &p->redraw_end }, + { "_on_hl_def", &p->hl_def }, + { NULL, NULL }, + }; + + for (size_t i = 0; i < opts.size; i++) { + String k = opts.items[i].key; + Object *v = &opts.items[i].value; + size_t j; + for (j = 0; cbs[j].name; j++) { + if (strequal(cbs[j].name, k.data)) { + if (v->type != kObjectTypeLuaRef) { + api_set_error(err, kErrorTypeValidation, + "%s is not a function", cbs[j].name); + goto error; + } + *(cbs[j].dest) = v->data.luaref; + v->data.luaref = LUA_NOREF; + break; + } + } + if (!cbs[j].name) { + api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data); + goto error; + } } - decorations_add_luahl_attr(attr, (int)start_row, (colnr_T)start_col, - (int)end_row, (colnr_T)end_col); + + p->active = true; + return; +error: + clear_provider(p); } diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c index ba43bc6cb2..f09a03f592 100644 --- a/src/nvim/api/window.c +++ b/src/nvim/api/window.c @@ -142,7 +142,7 @@ void nvim_win_set_cursor(Window window, ArrayOf(Integer, 2) pos, Error *err) // make sure cursor is in visible range even if win != curwin update_topline_win(win); - redraw_win_later(win, VALID); + redraw_later(win, VALID); } /// Gets the window height @@ -471,7 +471,7 @@ void nvim_win_set_config(Window window, Dictionary config, Error *err) if (!win_new_float(win, fconfig, err)) { return; } - redraw_later(NOT_VALID); + redraw_later(win, NOT_VALID); } else { win_config_float(win, fconfig); win->w_pos_changed = true; diff --git a/src/nvim/ascii.h b/src/nvim/ascii.h index 31423e79af..2397af27cc 100644 --- a/src/nvim/ascii.h +++ b/src/nvim/ascii.h @@ -31,9 +31,7 @@ #define CSI 0x9b // Control Sequence Introducer #define CSI_STR "\233" #define DCS 0x90 // Device Control String -#define DCS_STR "\033P" #define STERM 0x9c // String Terminator -#define STERM_STR "\033\\" #define POUND 0xA3 diff --git a/src/nvim/auevents.lua b/src/nvim/auevents.lua index 4391d997a7..6be51c504c 100644 --- a/src/nvim/auevents.lua +++ b/src/nvim/auevents.lua @@ -7,6 +7,7 @@ return { 'BufFilePre', -- before renaming a buffer 'BufHidden', -- just after buffer becomes hidden 'BufLeave', -- before leaving a buffer + 'BufModifiedSet', -- after the 'modified' state of a buffer changes 'BufNew', -- after creating any buffer 'BufNewFile', -- when creating a buffer for a new file 'BufReadCmd', -- read buffer using command @@ -65,7 +66,8 @@ return { 'InsertChange', -- when changing Insert/Replace mode 'InsertCharPre', -- before inserting a char 'InsertEnter', -- when entering Insert mode - 'InsertLeave', -- when leaving Insert mode + 'InsertLeave', -- just after leaving Insert mode + 'InsertLeavePre', -- just before leaving Insert mode 'MenuPopup', -- just before popup menu is displayed 'OptionSet', -- after setting any option 'QuickFixCmdPost', -- after :make, :grep etc. @@ -112,6 +114,7 @@ return { 'WinEnter', -- after entering a window 'WinLeave', -- before leaving a window 'WinNew', -- when entering a new window + 'WinScrolled', -- after scrolling a window }, aliases = { BufCreate = 'BufAdd', @@ -122,6 +125,7 @@ return { -- List of nvim-specific events or aliases for the purpose of generating -- syntax file nvim_specific = { + BufModifiedSet=true, DirChanged=true, Signal=true, TabClosed=true, @@ -132,5 +136,6 @@ return { UIEnter=true, UILeave=true, WinClosed=true, + WinScrolled=true, }, } diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 86067aceac..0ebe33f2f8 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -220,13 +220,8 @@ int open_buffer( int perm; perm = os_getperm((const char *)curbuf->b_ffname); - if (perm >= 0 && (0 -# ifdef S_ISFIFO - || S_ISFIFO(perm) -# endif -# ifdef S_ISSOCK + if (perm >= 0 && (0 || S_ISFIFO(perm) || S_ISSOCK(perm) -# endif # ifdef OPEN_CHR_FILES || (S_ISCHR(perm) && is_dev_fd_file(curbuf->b_ffname)) @@ -837,7 +832,7 @@ static void clear_wininfo(buf_T *buf) buf->b_wininfo = wip->wi_next; if (wip->wi_optset) { clear_winopt(&wip->wi_opt); - deleteFoldRecurse(&wip->wi_folds); + deleteFoldRecurse(buf, &wip->wi_folds); } xfree(wip); } @@ -1623,7 +1618,7 @@ void enter_buffer(buf_T *buf) } curbuf->b_last_used = time(NULL); - redraw_later(NOT_VALID); + redraw_later(curwin, NOT_VALID); } // Change to the directory of the current buffer. @@ -1941,6 +1936,7 @@ void free_buf_options(buf_T *buf, int free_p_ff) vim_regfree(buf->b_s.b_cap_prog); buf->b_s.b_cap_prog = NULL; clear_string_option(&buf->b_s.b_p_spl); + clear_string_option(&buf->b_s.b_p_spo); clear_string_option(&buf->b_p_sua); clear_string_option(&buf->b_p_ft); clear_string_option(&buf->b_p_cink); @@ -2502,7 +2498,7 @@ void buflist_setfpos(buf_T *const buf, win_T *const win, } if (copy_options && wip->wi_optset) { clear_winopt(&wip->wi_opt); - deleteFoldRecurse(&wip->wi_folds); + deleteFoldRecurse(buf, &wip->wi_folds); } } if (lnum != 0) { @@ -2650,7 +2646,7 @@ void buflist_list(exarg_T *eap) int i; garray_T buflist; - buf_T **buflist_data = NULL, **p; + buf_T **buflist_data = NULL; if (vim_strchr(eap->arg, 't')) { ga_init(&buflist, sizeof(buf_T *), 50); @@ -2662,13 +2658,14 @@ void buflist_list(exarg_T *eap) qsort(buflist.ga_data, (size_t)buflist.ga_len, sizeof(buf_T *), buf_time_compare); - p = buflist_data = (buf_T **)buflist.ga_data; - buf = *p; + buflist_data = (buf_T **)buflist.ga_data; + buf = *buflist_data; } + buf_T **p = buflist_data; for (; buf != NULL && !got_int; - buf = buflist_data + buf = buflist_data != NULL ? (++p < buflist_data + buflist.ga_len ? *p : NULL) : buf->b_next) { const bool is_terminal = buf->terminal; @@ -3438,31 +3435,17 @@ int build_stl_str_hl( int use_sandbox, char_u fillchar, int maxwidth, - struct stl_hlrec *hltab, - StlClickRecord *tabtab + stl_hlrec_t **hltab, + StlClickRecord **tabtab ) { - int groupitems[STL_MAX_ITEM]; - struct stl_item { - // Where the item starts in the status line output buffer - char_u *start; - // Function to run for ClickFunc items. - char *cmd; - // The minimum width of the item - int minwid; - // The maximum width of the item - int maxwid; - enum { - Normal, - Empty, - Group, - Separate, - Highlight, - TabPage, - ClickFunc, - Trunc - } type; - } items[STL_MAX_ITEM]; + static size_t stl_items_len = 20; // Initial value, grows as needed. + static stl_item_t *stl_items = NULL; + static int *stl_groupitems = NULL; + static stl_hlrec_t *stl_hltab = NULL; + static StlClickRecord *stl_tabtab = NULL; + static int *stl_separator_locations = NULL; + #define TMPLEN 70 char_u buf_tmp[TMPLEN]; char_u win_tmp[TMPLEN]; @@ -3470,6 +3453,14 @@ int build_stl_str_hl( const int save_must_redraw = must_redraw; const int save_redr_type = curwin->w_redr_type; + if (stl_items == NULL) { + stl_items = xmalloc(sizeof(stl_item_t) * stl_items_len); + stl_groupitems = xmalloc(sizeof(int) * stl_items_len); + stl_hltab = xmalloc(sizeof(stl_hlrec_t) * stl_items_len); + stl_tabtab = xmalloc(sizeof(stl_hlrec_t) * stl_items_len); + stl_separator_locations = xmalloc(sizeof(int) * stl_items_len); + } + // When the format starts with "%!" then evaluate it as an expression and // use the result as the actual format string. if (fmt[0] == '%' && fmt[1] == '!') { @@ -3538,14 +3529,17 @@ int build_stl_str_hl( // Proceed character by character through the statusline format string // fmt_p is the current positon in the input buffer for (char_u *fmt_p = usefmt; *fmt_p; ) { - if (curitem == STL_MAX_ITEM) { - // There are too many items. Add the error code to the statusline - // to give the user a hint about what went wrong. - if (out_p + 5 < out_end_p) { - memmove(out_p, " E541", (size_t)5); - out_p += 5; - } - break; + if (curitem == (int)stl_items_len) { + size_t new_len = stl_items_len * 3 / 2; + + stl_items = xrealloc(stl_items, sizeof(stl_item_t) * new_len); + stl_groupitems = xrealloc(stl_groupitems, sizeof(int) * new_len); + stl_hltab = xrealloc(stl_hltab, sizeof(stl_hlrec_t) * new_len); + stl_tabtab = xrealloc(stl_tabtab, sizeof(StlClickRecord) * new_len); + stl_separator_locations = + xrealloc(stl_separator_locations, sizeof(int) * new_len); + + stl_items_len = new_len; } if (*fmt_p != NUL && *fmt_p != '%') { @@ -3589,16 +3583,16 @@ int build_stl_str_hl( if (groupdepth > 0) { continue; } - items[curitem].type = Separate; - items[curitem++].start = out_p; + stl_items[curitem].type = Separate; + stl_items[curitem++].start = out_p; continue; } // STL_TRUNCMARK: Where to begin truncating if the statusline is too long. if (*fmt_p == STL_TRUNCMARK) { fmt_p++; - items[curitem].type = Trunc; - items[curitem++].start = out_p; + stl_items[curitem].type = Trunc; + stl_items[curitem++].start = out_p; continue; } @@ -3614,7 +3608,7 @@ int build_stl_str_hl( // Determine how long the group is. // Note: We set the current output position to null // so `vim_strsize` will work. - char_u *t = items[groupitems[groupdepth]].start; + char_u *t = stl_items[stl_groupitems[groupdepth]].start; *out_p = NUL; long group_len = vim_strsize(t); @@ -3624,34 +3618,40 @@ int build_stl_str_hl( // move the output pointer back to where the group started. // Note: This erases any non-item characters that were in the group. // Otherwise there would be no reason to do this step. - if (curitem > groupitems[groupdepth] + 1 - && items[groupitems[groupdepth]].minwid == 0) { + if (curitem > stl_groupitems[groupdepth] + 1 + && stl_items[stl_groupitems[groupdepth]].minwid == 0) { // remove group if all items are empty and highlight group // doesn't change int group_start_userhl = 0; int group_end_userhl = 0; int n; - for (n = groupitems[groupdepth] - 1; n >= 0; n--) { - if (items[n].type == Highlight) { - group_start_userhl = group_end_userhl = items[n].minwid; + for (n = stl_groupitems[groupdepth] - 1; n >= 0; n--) { + if (stl_items[n].type == Highlight) { + group_start_userhl = group_end_userhl = stl_items[n].minwid; break; } } - for (n = groupitems[groupdepth] + 1; n < curitem; n++) { - if (items[n].type == Normal) { + for (n = stl_groupitems[groupdepth] + 1; n < curitem; n++) { + if (stl_items[n].type == Normal) { break; } - if (items[n].type == Highlight) { - group_end_userhl = items[n].minwid; + if (stl_items[n].type == Highlight) { + group_end_userhl = stl_items[n].minwid; } } if (n == curitem && group_start_userhl == group_end_userhl) { + // empty group out_p = t; group_len = 0; - // do not use the highlighting from the removed group - for (n = groupitems[groupdepth] + 1; n < curitem; n++) { - if (items[n].type == Highlight) { - items[n].type = Empty; + for (n = stl_groupitems[groupdepth] + 1; n < curitem; n++) { + // do not use the highlighting from the removed group + if (stl_items[n].type == Highlight) { + stl_items[n].type = Empty; + } + // adjust the start position of TabPage to the next + // item position + if (stl_items[n].type == TabPage) { + stl_items[n].start = out_p; } } } @@ -3659,18 +3659,19 @@ int build_stl_str_hl( // If the group is longer than it is allowed to be // truncate by removing bytes from the start of the group text. - if (group_len > items[groupitems[groupdepth]].maxwid) { + if (group_len > stl_items[stl_groupitems[groupdepth]].maxwid) { // { Determine the number of bytes to remove long n; if (has_mbyte) { // Find the first character that should be included. n = 0; - while (group_len >= items[groupitems[groupdepth]].maxwid) { + while (group_len >= stl_items[stl_groupitems[groupdepth]].maxwid) { group_len -= ptr2cells(t + n); n += (*mb_ptr2len)(t + n); } } else { - n = (long)(out_p - t) - items[groupitems[groupdepth]].maxwid + 1; + n = (long)(out_p - t) + - stl_items[stl_groupitems[groupdepth]].maxwid + 1; } // } @@ -3681,25 +3682,26 @@ int build_stl_str_hl( memmove(t + 1, t + n, (size_t)(out_p - (t + n))); out_p = out_p - n + 1; // Fill up space left over by half a double-wide char. - while (++group_len < items[groupitems[groupdepth]].minwid) { + while (++group_len < stl_items[stl_groupitems[groupdepth]].minwid) { *out_p++ = fillchar; } // } // correct the start of the items for the truncation - for (int idx = groupitems[groupdepth] + 1; idx < curitem; idx++) { + for (int idx = stl_groupitems[groupdepth] + 1; idx < curitem; idx++) { // Shift everything back by the number of removed bytes - items[idx].start -= n; + stl_items[idx].start -= n; // If the item was partially or completely truncated, set its // start to the start of the group - if (items[idx].start < t) { - items[idx].start = t; + if (stl_items[idx].start < t) { + stl_items[idx].start = t; } } // If the group is shorter than the minimum width, add padding characters. - } else if (abs(items[groupitems[groupdepth]].minwid) > group_len) { - long min_group_width = items[groupitems[groupdepth]].minwid; + } else if ( + abs(stl_items[stl_groupitems[groupdepth]].minwid) > group_len) { + long min_group_width = stl_items[stl_groupitems[groupdepth]].minwid; // If the group is left-aligned, add characters to the right. if (min_group_width < 0) { min_group_width = 0 - min_group_width; @@ -3718,8 +3720,8 @@ int build_stl_str_hl( // } // Adjust item start positions - for (int n = groupitems[groupdepth] + 1; n < curitem; n++) { - items[n].start += group_len; + for (int n = stl_groupitems[groupdepth] + 1; n < curitem; n++) { + stl_items[n].start += group_len; } // Prepend the fill characters @@ -3755,9 +3757,9 @@ int build_stl_str_hl( // User highlight groups override the min width field // to denote the styling to use. if (*fmt_p == STL_USER_HL) { - items[curitem].type = Highlight; - items[curitem].start = out_p; - items[curitem].minwid = minwid > 9 ? 1 : minwid; + stl_items[curitem].type = Highlight; + stl_items[curitem].start = out_p; + stl_items[curitem].minwid = minwid > 9 ? 1 : minwid; fmt_p++; curitem++; continue; @@ -3791,8 +3793,8 @@ int build_stl_str_hl( if (minwid == 0) { // %X ends the close label, go back to the previous tab label nr. for (long n = curitem - 1; n >= 0; n--) { - if (items[n].type == TabPage && items[n].minwid >= 0) { - minwid = items[n].minwid; + if (stl_items[n].type == TabPage && stl_items[n].minwid >= 0) { + minwid = stl_items[n].minwid; break; } } @@ -3801,9 +3803,9 @@ int build_stl_str_hl( minwid = -minwid; } } - items[curitem].type = TabPage; - items[curitem].start = out_p; - items[curitem].minwid = minwid; + stl_items[curitem].type = TabPage; + stl_items[curitem].start = out_p; + stl_items[curitem].minwid = minwid; fmt_p++; curitem++; continue; @@ -3818,10 +3820,10 @@ int build_stl_str_hl( if (*fmt_p != STL_CLICK_FUNC) { break; } - items[curitem].type = ClickFunc; - items[curitem].start = out_p; - items[curitem].cmd = xmemdupz(t, (size_t)(((char *)fmt_p - t))); - items[curitem].minwid = minwid; + stl_items[curitem].type = ClickFunc; + stl_items[curitem].start = out_p; + stl_items[curitem].cmd = xmemdupz(t, (size_t)(((char *)fmt_p - t))); + stl_items[curitem].minwid = minwid; fmt_p++; curitem++; continue; @@ -3842,11 +3844,11 @@ int build_stl_str_hl( // Denotes the start of a new group if (*fmt_p == '(') { - groupitems[groupdepth++] = curitem; - items[curitem].type = Group; - items[curitem].start = out_p; - items[curitem].minwid = minwid; - items[curitem].maxwid = maxwid; + stl_groupitems[groupdepth++] = curitem; + stl_items[curitem].type = Group; + stl_items[curitem].start = out_p; + stl_items[curitem].minwid = minwid; + stl_items[curitem].maxwid = maxwid; fmt_p++; curitem++; continue; @@ -4141,9 +4143,9 @@ int build_stl_str_hl( // Create a highlight item based on the name if (*fmt_p == '#') { - items[curitem].type = Highlight; - items[curitem].start = out_p; - items[curitem].minwid = -syn_namen2id(t, (int)(fmt_p - t)); + stl_items[curitem].type = Highlight; + stl_items[curitem].start = out_p; + stl_items[curitem].minwid = -syn_namen2id(t, (int)(fmt_p - t)); curitem++; fmt_p++; } @@ -4154,8 +4156,8 @@ int build_stl_str_hl( // If we made it this far, the item is normal and starts at // our current position in the output buffer. // Non-normal items would have `continued`. - items[curitem].start = out_p; - items[curitem].type = Normal; + stl_items[curitem].start = out_p; + stl_items[curitem].type = Normal; // Copy the item string into the output buffer if (str != NULL && *str) { @@ -4313,7 +4315,7 @@ int build_stl_str_hl( // Otherwise, there was nothing to print so mark the item as empty } else { - items[curitem].type = Empty; + stl_items[curitem].type = Empty; } // Only free the string buffer if we allocated it. @@ -4354,13 +4356,13 @@ int build_stl_str_hl( // Otherwise, look for the truncation item } else { // Default to truncating at the first item - trunc_p = items[0].start; + trunc_p = stl_items[0].start; item_idx = 0; for (int i = 0; i < itemcnt; i++) { - if (items[i].type == Trunc) { - // Truncate at %< items. - trunc_p = items[i].start; + if (stl_items[i].type == Trunc) { + // Truncate at %< stl_items. + trunc_p = stl_items[i].start; item_idx = i; break; } @@ -4395,7 +4397,7 @@ int build_stl_str_hl( // Ignore any items in the statusline that occur after // the truncation point for (int i = 0; i < itemcnt; i++) { - if (items[i].start > trunc_p) { + if (stl_items[i].start > trunc_p) { itemcnt = i; break; } @@ -4450,12 +4452,12 @@ int build_stl_str_hl( for (int i = item_idx; i < itemcnt; i++) { // Items starting at or after the end of the truncated section need // to be moved backwards. - if (items[i].start >= trunc_end_p) { - items[i].start -= item_offset; + if (stl_items[i].start >= trunc_end_p) { + stl_items[i].start -= item_offset; // Anything inside the truncated area is set to start // at the `<` truncation character. } else { - items[i].start = trunc_p; + stl_items[i].start = trunc_p; } } // } @@ -4471,7 +4473,7 @@ int build_stl_str_hl( // figuring out how many groups there are. int num_separators = 0; for (int i = 0; i < itemcnt; i++) { - if (items[i].type == Separate) { + if (stl_items[i].type == Separate) { num_separators++; } } @@ -4480,11 +4482,10 @@ int build_stl_str_hl( if (num_separators) { // Create an array of the start location for each // separator mark. - int separator_locations[STL_MAX_ITEM]; int index = 0; for (int i = 0; i < itemcnt; i++) { - if (items[i].type == Separate) { - separator_locations[index] = i; + if (stl_items[i].type == Separate) { + stl_separator_locations[index] = i; index++; } } @@ -4496,16 +4497,17 @@ int build_stl_str_hl( for (int i = 0; i < num_separators; i++) { int dislocation = (i == (num_separators - 1)) ? final_spaces : standard_spaces; - char_u *seploc = items[separator_locations[i]].start + dislocation; - STRMOVE(seploc, items[separator_locations[i]].start); - for (char_u *s = items[separator_locations[i]].start; s < seploc; s++) { + char_u *start = stl_items[stl_separator_locations[i]].start; + char_u *seploc = start + dislocation; + STRMOVE(seploc, start); + for (char_u *s = start; s < seploc; s++) { *s = fillchar; } - for (int item_idx = separator_locations[i] + 1; + for (int item_idx = stl_separator_locations[i] + 1; item_idx < itemcnt; item_idx++) { - items[item_idx].start += dislocation; + stl_items[item_idx].start += dislocation; } } @@ -4515,11 +4517,12 @@ int build_stl_str_hl( // Store the info about highlighting. if (hltab != NULL) { - struct stl_hlrec *sp = hltab; + *hltab = stl_hltab; + stl_hlrec_t *sp = stl_hltab; for (long l = 0; l < itemcnt; l++) { - if (items[l].type == Highlight) { - sp->start = items[l].start; - sp->userhl = items[l].minwid; + if (stl_items[l].type == Highlight) { + sp->start = stl_items[l].start; + sp->userhl = stl_items[l].minwid; sp++; } } @@ -4529,16 +4532,17 @@ int build_stl_str_hl( // Store the info about tab pages labels. if (tabtab != NULL) { - StlClickRecord *cur_tab_rec = tabtab; + *tabtab = stl_tabtab; + StlClickRecord *cur_tab_rec = stl_tabtab; for (long l = 0; l < itemcnt; l++) { - if (items[l].type == TabPage) { - cur_tab_rec->start = (char *)items[l].start; - if (items[l].minwid == 0) { + if (stl_items[l].type == TabPage) { + cur_tab_rec->start = (char *)stl_items[l].start; + if (stl_items[l].minwid == 0) { cur_tab_rec->def.type = kStlClickDisabled; cur_tab_rec->def.tabnr = 0; } else { - int tabnr = items[l].minwid; - if (items[l].minwid > 0) { + int tabnr = stl_items[l].minwid; + if (stl_items[l].minwid > 0) { cur_tab_rec->def.type = kStlClickTabSwitch; } else { cur_tab_rec->def.type = kStlClickTabClose; @@ -4548,11 +4552,11 @@ int build_stl_str_hl( } cur_tab_rec->def.func = NULL; cur_tab_rec++; - } else if (items[l].type == ClickFunc) { - cur_tab_rec->start = (char *)items[l].start; + } else if (stl_items[l].type == ClickFunc) { + cur_tab_rec->start = (char *)stl_items[l].start; cur_tab_rec->def.type = kStlClickFuncRun; - cur_tab_rec->def.tabnr = items[l].minwid; - cur_tab_rec->def.func = items[l].cmd; + cur_tab_rec->def.tabnr = stl_items[l].minwid; + cur_tab_rec->def.func = stl_items[l].cmd; cur_tab_rec++; } } @@ -5389,13 +5393,11 @@ bool buf_hide(const buf_T *const buf) char_u *buf_spname(buf_T *buf) { if (bt_quickfix(buf)) { - win_T *win; - tabpage_T *tp; + win_T *win; + tabpage_T *tp; - /* - * For location list window, w_llist_ref points to the location list. - * For quickfix window, w_llist_ref is NULL. - */ + // For location list window, w_llist_ref points to the location list. + // For quickfix window, w_llist_ref is NULL. if (find_win_for_buf(buf, &win, &tp) && win->w_llist_ref != NULL) { return (char_u *)_(msg_loclist); } else { @@ -5414,7 +5416,7 @@ char_u *buf_spname(buf_T *buf) return (char_u *)_("[Scratch]"); } if (buf->b_fname == NULL) { - return (char_u *)_("[No Name]"); + return buf_get_fname(buf); } return NULL; } @@ -5475,6 +5477,16 @@ int buf_signcols(buf_T *buf) return buf->b_signcols; } +// Get "buf->b_fname", use "[No Name]" if it is NULL. +char_u *buf_get_fname(const buf_T *buf) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL +{ + if (buf->b_fname == NULL) { + return (char_u *)_("[No Name]"); + } + return buf->b_fname; +} + /* * Set 'buflisted' for curbuf to "on" and trigger autocommands if it changed. */ diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 550f8a5e40..93fe37b585 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -91,6 +91,7 @@ typedef struct { #define BF_READERR 0x40 // got errors while reading the file #define BF_DUMMY 0x80 // dummy buffer, only used internally #define BF_PRESERVED 0x100 // ":preserve" was used +#define BF_SYN_SET 0x200 // 'syntax' option was set // Mask to check for flags that prevent normal writing #define BF_WRITE_MASK (BF_NOTEDITED + BF_NEW + BF_READERR) @@ -360,14 +361,36 @@ struct mapblock { sctx_T m_script_ctx; // SCTX where map was defined }; -/* - * Used for highlighting in the status line. - */ +/// Used for highlighting in the status line. +typedef struct stl_hlrec stl_hlrec_t; struct stl_hlrec { char_u *start; int userhl; // 0: no HL, 1-9: User HL, < 0 for syn ID }; +/// Used for building the status line. +typedef struct stl_item stl_item_t; +struct stl_item { + // Where the item starts in the status line output buffer + char_u *start; + // Function to run for ClickFunc items. + char *cmd; + // The minimum width of the item + int minwid; + // The maximum width of the item + int maxwid; + enum { + Normal, + Empty, + Group, + Separate, + Highlight, + TabPage, + ClickFunc, + Trunc + } type; +}; + // values for b_syn_spell: what to do with toplevel text #define SYNSPL_DEFAULT 0 // spell check if @Spell not defined #define SYNSPL_TOP 1 // spell check toplevel text @@ -451,6 +474,7 @@ typedef struct { regprog_T *b_cap_prog; // program for 'spellcapcheck' char_u *b_p_spf; // 'spellfile' char_u *b_p_spl; // 'spelllang' + char_u *b_p_spo; // 'spelloptions' int b_cjk; // all CJK letters as OK char_u b_syn_chartab[32]; // syntax iskeyword option char_u *b_syn_isk; // iskeyword option @@ -517,6 +541,9 @@ struct file_buffer { int b_changed; // 'modified': Set to true if something in the // file has been changed and not written out. + bool b_changed_invalid; // Set if BufModified autocmd has not been + // triggered since the last time b_changed was + // modified. /// Change-identifier incremented for each change, including undo. /// @@ -543,6 +570,9 @@ struct file_buffer { long b_mod_xlines; // number of extra buffer lines inserted; // negative when lines were deleted wininfo_T *b_wininfo; // list of last used info for each window + int b_mod_tick_syn; // last display tick syntax was updated + int b_mod_tick_decor; // last display tick decoration providers + // where invoked long b_mtime; // last change time of original file long b_mtime_read; // last change time when reading @@ -656,6 +686,9 @@ struct file_buffer { char_u *b_p_com; ///< 'comments' char_u *b_p_cms; ///< 'commentstring' char_u *b_p_cpt; ///< 'complete' +#ifdef BACKSLASH_IN_FILENAME + char_u *b_p_csl; ///< 'completeslash' +#endif char_u *b_p_cfu; ///< 'completefunc' char_u *b_p_ofu; ///< 'omnifunc' char_u *b_p_tfu; ///< 'tagfunc' @@ -765,6 +798,7 @@ struct file_buffer { int b_ind_cpp_namespace; int b_ind_if_for_while; int b_ind_cpp_extern_c; + int b_ind_pragma; linenr_T b_no_eol_lnum; /* non-zero lnum when last line of next binary * write should not have an end-of-line */ @@ -835,18 +869,13 @@ struct file_buffer { // tree-sitter) or the corresponding UTF-32/UTF-16 size (like LSP) of the // deleted text. size_t deleted_bytes; + size_t deleted_bytes2; size_t deleted_codepoints; size_t deleted_codeunits; // The number for times the current line has been flushed in the memline. int flush_count; - bool b_luahl; - LuaRef b_luahl_start; - LuaRef b_luahl_window; - LuaRef b_luahl_line; - LuaRef b_luahl_end; - int b_diff_failed; // internal diff failed for this buffer }; @@ -1204,6 +1233,13 @@ struct window_S { colnr_T w_skipcol; // starting column when a single line // doesn't fit in the window + // "w_last_topline" and "w_last_leftcol" are used to determine if + // a Scroll autocommand should be emitted. + linenr_T w_last_topline; ///< last known value for topline + colnr_T w_last_leftcol; ///< last known value for leftcol + int w_last_width; ///< last known value for width + int w_last_height; ///< last known value for height + // // Layout of the window in the screen. // May need to add "msg_scrolled" to "w_winrow" in rare situations. diff --git a/src/nvim/buffer_updates.c b/src/nvim/buffer_updates.c index e6393bf02c..fc671ad9e2 100644 --- a/src/nvim/buffer_updates.c +++ b/src/nvim/buffer_updates.c @@ -2,6 +2,7 @@ // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com #include "nvim/buffer_updates.h" +#include "nvim/extmark.h" #include "nvim/memline.h" #include "nvim/api/private/helpers.h" #include "nvim/msgpack_rpc/channel.h" @@ -157,7 +158,7 @@ void buf_updates_unregister_all(buf_T *buf) args.items[0] = BUFFER_OBJ(buf->handle); textlock++; - executor_exec_lua_cb(cb.on_detach, "detach", args, false, NULL); + nlua_call_ref(cb.on_detach, "detach", args, false, NULL); textlock--; } free_update_callbacks(cb); @@ -265,7 +266,7 @@ void buf_updates_send_changes(buf_T *buf, args.items[7] = INTEGER_OBJ((Integer)deleted_codeunits); } textlock++; - Object res = executor_exec_lua_cb(cb.on_lines, "lines", args, true, NULL); + Object res = nlua_call_ref(cb.on_lines, "lines", args, true, NULL); textlock--; if (res.type == kObjectTypeBoolean && res.data.boolean == true) { @@ -281,12 +282,14 @@ void buf_updates_send_changes(buf_T *buf, kv_size(buf->update_callbacks) = j; } -void buf_updates_send_splice(buf_T *buf, - linenr_T start_line, colnr_T start_col, - linenr_T oldextent_line, colnr_T oldextent_col, - linenr_T newextent_line, colnr_T newextent_col) +void buf_updates_send_splice( + buf_T *buf, + int start_row, colnr_T start_col, bcount_t start_byte, + int old_row, colnr_T old_col, bcount_t old_byte, + int new_row, colnr_T new_col, bcount_t new_byte) { - if (!buf_updates_active(buf)) { + if (!buf_updates_active(buf) + || (old_byte == 0 && new_byte == 0)) { return; } @@ -296,7 +299,7 @@ void buf_updates_send_splice(buf_T *buf, BufUpdateCallbacks cb = kv_A(buf->update_callbacks, i); bool keep = true; if (cb.on_bytes != LUA_NOREF) { - FIXED_TEMP_ARRAY(args, 8); + FIXED_TEMP_ARRAY(args, 11); // the first argument is always the buffer handle args.items[0] = BUFFER_OBJ(buf->handle); @@ -304,15 +307,18 @@ void buf_updates_send_splice(buf_T *buf, // next argument is b:changedtick args.items[1] = INTEGER_OBJ(buf_get_changedtick(buf)); - args.items[2] = INTEGER_OBJ(start_line); + args.items[2] = INTEGER_OBJ(start_row); args.items[3] = INTEGER_OBJ(start_col); - args.items[4] = INTEGER_OBJ(oldextent_line); - args.items[5] = INTEGER_OBJ(oldextent_col); - args.items[6] = INTEGER_OBJ(newextent_line); - args.items[7] = INTEGER_OBJ(newextent_col); + args.items[4] = INTEGER_OBJ(start_byte); + args.items[5] = INTEGER_OBJ(old_row); + args.items[6] = INTEGER_OBJ(old_col); + args.items[7] = INTEGER_OBJ(old_byte); + args.items[8] = INTEGER_OBJ(new_row); + args.items[9] = INTEGER_OBJ(new_col); + args.items[10] = INTEGER_OBJ(new_byte); textlock++; - Object res = executor_exec_lua_cb(cb.on_bytes, "bytes", args, true, NULL); + Object res = nlua_call_ref(cb.on_bytes, "bytes", args, true, NULL); textlock--; if (res.type == kObjectTypeBoolean && res.data.boolean == true) { @@ -347,8 +353,8 @@ void buf_updates_changedtick(buf_T *buf) args.items[1] = INTEGER_OBJ(buf_get_changedtick(buf)); textlock++; - Object res = executor_exec_lua_cb(cb.on_changedtick, "changedtick", - args, true, NULL); + Object res = nlua_call_ref(cb.on_changedtick, "changedtick", + args, true, NULL); textlock--; if (res.type == kObjectTypeBoolean && res.data.boolean == true) { @@ -382,6 +388,6 @@ void buf_updates_changedtick_single(buf_T *buf, uint64_t channel_id) static void free_update_callbacks(BufUpdateCallbacks cb) { - executor_free_luaref(cb.on_lines); - executor_free_luaref(cb.on_changedtick); + api_free_luaref(cb.on_lines); + api_free_luaref(cb.on_changedtick); } diff --git a/src/nvim/buffer_updates.h b/src/nvim/buffer_updates.h index b2d0a62270..961fec879b 100644 --- a/src/nvim/buffer_updates.h +++ b/src/nvim/buffer_updates.h @@ -2,6 +2,7 @@ #define NVIM_BUFFER_UPDATES_H #include "nvim/buffer_defs.h" +#include "nvim/extmark.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "buffer_updates.h.generated.h" diff --git a/src/nvim/change.c b/src/nvim/change.c index 51afb40b40..271d350967 100644 --- a/src/nvim/change.c +++ b/src/nvim/change.c @@ -129,6 +129,7 @@ void changed(void) void changed_internal(void) { curbuf->b_changed = true; + curbuf->b_changed_invalid = true; ml_setflags(curbuf); check_status(curbuf); redraw_tabline = true; @@ -142,7 +143,6 @@ static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume, long xtra) { int i; - int cols; pos_T *p; int add; @@ -170,7 +170,7 @@ static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume, if (p->lnum != lnum) { add = true; } else { - cols = comp_textwidth(false); + int cols = comp_textwidth(false); if (cols == 0) { cols = 79; } @@ -295,7 +295,7 @@ static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume, // change. if (wp->w_p_rnu || (wp->w_p_cul && lnum <= wp->w_last_cursorline)) { - redraw_win_later(wp, SOME_VALID); + redraw_later(wp, SOME_VALID); } } } @@ -349,7 +349,7 @@ void changed_bytes(linenr_T lnum, colnr_T col) FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { if (wp->w_p_diff && wp != curwin) { - redraw_win_later(wp, VALID); + redraw_later(wp, VALID); wlnum = diff_lnum_win(lnum, wp); if (wlnum > 0) { changedOneline(wp->w_buffer, wlnum); @@ -362,11 +362,10 @@ void changed_bytes(linenr_T lnum, colnr_T col) /// insert/delete bytes at column /// /// Like changed_bytes() but also adjust extmark for "new" bytes. -/// When "new" is negative text was deleted. -static void inserted_bytes(linenr_T lnum, colnr_T col, int old, int new) +void inserted_bytes(linenr_T lnum, colnr_T col, int old, int new) { if (curbuf_splice_pending == 0) { - extmark_splice(curbuf, (int)lnum-1, col, 0, old, 0, new, kExtmarkUndo); + extmark_splice_cols(curbuf, (int)lnum-1, col, old, new, kExtmarkUndo); } changed_bytes(lnum, col); @@ -477,7 +476,7 @@ changed_lines( FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { if (wp->w_p_diff && wp != curwin) { - redraw_win_later(wp, VALID); + redraw_later(wp, VALID); wlnum = diff_lnum_win(lnum, wp); if (wlnum > 0) { changed_lines_buf(wp->w_buffer, wlnum, @@ -504,6 +503,7 @@ void unchanged(buf_T *buf, int ff, bool always_inc_changedtick) { if (buf->b_changed || (ff && file_ff_differs(buf, false))) { buf->b_changed = false; + buf->b_changed_invalid = true; ml_setflags(buf); if (ff) { save_file_ff(buf); @@ -1597,7 +1597,7 @@ int open_line( if (curwin->w_cursor.lnum + 1 < curbuf->b_ml.ml_line_count || curwin->w_p_diff) { mark_adjust(curwin->w_cursor.lnum + 1, (linenr_T)MAXLNUM, 1L, 0L, - kExtmarkUndo); + kExtmarkNOOP); } did_append = true; } else { @@ -1611,6 +1611,7 @@ int open_line( } ml_replace(curwin->w_cursor.lnum, p_extra, true); changed_bytes(curwin->w_cursor.lnum, 0); + // TODO(vigoux): extmark_splice_cols here?? curwin->w_cursor.lnum--; did_append = false; } @@ -1676,6 +1677,16 @@ int open_line( truncate_spaces(saved_line); } ml_replace(curwin->w_cursor.lnum, saved_line, false); + + int new_len = (int)STRLEN(saved_line); + + // TODO(vigoux): maybe there is issues there with expandtabs ? + if (new_len < curwin->w_cursor.col) { + extmark_splice_cols( + curbuf, (int)curwin->w_cursor.lnum, + new_len, curwin->w_cursor.col - new_len, 0, kExtmarkUndo); + } + saved_line = NULL; if (did_append) { changed_lines(curwin->w_cursor.lnum, curwin->w_cursor.col, @@ -1691,8 +1702,9 @@ int open_line( // Always move extmarks - Here we move only the line where the // cursor is, the previous mark_adjust takes care of the lines after int cols_added = mincol-1+less_cols_off-less_cols; - extmark_splice(curbuf, (int)lnum-1, mincol-1, 0, less_cols_off, - 1, cols_added, kExtmarkUndo); + extmark_splice(curbuf, (int)lnum-1, mincol-1, + 0, less_cols_off, less_cols_off, + 1, cols_added, 1 + cols_added, kExtmarkUndo); } else { changed_bytes(curwin->w_cursor.lnum, curwin->w_cursor.col); } @@ -1704,8 +1716,10 @@ int open_line( } if (did_append) { changed_lines(curwin->w_cursor.lnum, 0, curwin->w_cursor.lnum, 1L, true); - extmark_splice(curbuf, (int)curwin->w_cursor.lnum-1, - 0, 0, 0, 1, 0, kExtmarkUndo); + // bail out and just get the final lenght of the line we just manipulated + bcount_t extra = (bcount_t)STRLEN(ml_get(curwin->w_cursor.lnum)); + extmark_splice(curbuf, (int)curwin->w_cursor.lnum-1, 0, + 0, 0, 0, 1, 0, 1+extra, kExtmarkUndo); } curbuf_splice_pending--; diff --git a/src/nvim/charset.c b/src/nvim/charset.c index f9d5adbc12..fb158f377a 100644 --- a/src/nvim/charset.c +++ b/src/nvim/charset.c @@ -509,7 +509,7 @@ char_u* str_foldcase(char_u *str, int orglen, char_u *buf, int buflen) // Does NOT work for multi-byte characters, c must be <= 255. // Also doesn't work for the first byte of a multi-byte, "c" must be a // character! -static char_u transchar_buf[11]; +static char_u transchar_charbuf[11]; /// Translate a character into a printable one, leaving printable ASCII intact /// @@ -520,11 +520,17 @@ static char_u transchar_buf[11]; /// @return translated character into a static buffer. char_u *transchar(int c) { + return transchar_buf(curbuf, c); +} + +char_u *transchar_buf(const buf_T *buf, int c) + FUNC_ATTR_NONNULL_ALL +{ int i = 0; if (IS_SPECIAL(c)) { // special key code, display as ~@ char - transchar_buf[0] = '~'; - transchar_buf[1] = '@'; + transchar_charbuf[0] = '~'; + transchar_charbuf[1] = '@'; i = 2; c = K_SECOND(c); } @@ -532,14 +538,14 @@ char_u *transchar(int c) if ((!chartab_initialized && (((c >= ' ') && (c <= '~')))) || ((c <= 0xFF) && vim_isprintc_strict(c))) { // printable character - transchar_buf[i] = (char_u)c; - transchar_buf[i + 1] = NUL; + transchar_charbuf[i] = (char_u)c; + transchar_charbuf[i + 1] = NUL; } else if (c <= 0xFF) { - transchar_nonprint(transchar_buf + i, c); + transchar_nonprint(buf, transchar_charbuf + i, c); } else { - transchar_hex((char *)transchar_buf + i, c); + transchar_hex((char *)transchar_charbuf + i, c); } - return transchar_buf; + return transchar_charbuf; } /// Like transchar(), but called with a byte instead of a character @@ -548,13 +554,13 @@ char_u *transchar(int c) /// /// @param[in] c Byte to translate. /// -/// @return pointer to translated character in transchar_buf. +/// @return pointer to translated character in transchar_charbuf. char_u *transchar_byte(const int c) FUNC_ATTR_WARN_UNUSED_RESULT { if (c >= 0x80) { - transchar_nonprint(transchar_buf, c); - return transchar_buf; + transchar_nonprint(curbuf, transchar_charbuf, c); + return transchar_charbuf; } return transchar(c); } @@ -563,16 +569,18 @@ char_u *transchar_byte(const int c) /// /// @warning Does not work for multi-byte characters, c must be <= 255. /// -/// @param[out] buf Buffer to store result in, must be able to hold at least -/// 5 bytes (conversion result + NUL). +/// @param[in] buf Required to check the file format +/// @param[out] charbuf Buffer to store result in, must be able to hold +/// at least 5 bytes (conversion result + NUL). /// @param[in] c Character to convert. NUL is assumed to be NL according to /// `:h NL-used-for-NUL`. -void transchar_nonprint(char_u *buf, int c) +void transchar_nonprint(const buf_T *buf, char_u *charbuf, int c) + FUNC_ATTR_NONNULL_ALL { if (c == NL) { // we use newline in place of a NUL c = NUL; - } else if ((c == CAR) && (get_fileformat(curbuf) == EOL_MAC)) { + } else if ((c == CAR) && (get_fileformat(buf) == EOL_MAC)) { // we use CR in place of NL in this case c = NL; } @@ -580,14 +588,14 @@ void transchar_nonprint(char_u *buf, int c) if (dy_flags & DY_UHEX || c > 0x7f) { // 'display' has "uhex" - transchar_hex((char *)buf, c); + transchar_hex((char *)charbuf, c); } else { // 0x00 - 0x1f and 0x7f - buf[0] = '^'; + charbuf[0] = '^'; // DEL displayed as ^? - buf[1] = (char_u)(c ^ 0x40); + charbuf[1] = (char_u)(c ^ 0x40); - buf[2] = NUL; + charbuf[2] = NUL; } } diff --git a/src/nvim/cursor.c b/src/nvim/cursor.c index 036ae32589..d3ffab1759 100644 --- a/src/nvim/cursor.c +++ b/src/nvim/cursor.c @@ -482,7 +482,7 @@ bool leftcol_changed(void) if (retval) curwin->w_set_curswant = true; - redraw_later(NOT_VALID); + redraw_later(curwin, NOT_VALID); return retval; } diff --git a/src/nvim/cursor_shape.c b/src/nvim/cursor_shape.c index 3f06340611..0d21080aa5 100644 --- a/src/nvim/cursor_shape.c +++ b/src/nvim/cursor_shape.c @@ -13,6 +13,10 @@ #include "nvim/api/private/helpers.h" #include "nvim/ui.h" +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "cursor_shape.c.generated.h" +#endif + /// Handling of cursor and mouse pointer shapes in various modes. cursorentry_T shape_table[SHAPE_IDX_COUNT] = { @@ -77,7 +81,9 @@ Array mode_style_array(void) return all; } -/// Parse the 'guicursor' option +/// Parses the 'guicursor' option. +/// +/// Clears `shape_table` if 'guicursor' is empty. /// /// @param what SHAPE_CURSOR or SHAPE_MOUSE ('mouseshape') /// @@ -99,11 +105,17 @@ char_u *parse_shape_opt(int what) // First round: check for errors; second round: do it for real. for (round = 1; round <= 2; round++) { + if (round == 2 || *p_guicursor == NUL) { + // Set all entries to default (block, blinkon0, default color). + // This is the default for anything that is not set. + clear_shape_table(); + if (*p_guicursor == NUL) { + ui_mode_info_set(); + return NULL; + } + } // Repeat for all comma separated parts. modep = p_guicursor; - if (*p_guicursor == NUL) { - modep = (char_u *)"a:block-blinkon0"; - } while (modep != NULL && *modep != NUL) { colonp = vim_strchr(modep, ':'); commap = vim_strchr(modep, ','); @@ -144,14 +156,6 @@ char_u *parse_shape_opt(int what) if (all_idx >= 0) { idx = all_idx--; - } else if (round == 2) { - { - // Set the defaults, for the missing parts - shape_table[idx].shape = SHAPE_BLOCK; - shape_table[idx].blinkwait = 0L; - shape_table[idx].blinkon = 0L; - shape_table[idx].blinkoff = 0L; - } } /* Parse the part after the colon */ @@ -330,3 +334,16 @@ int cursor_get_mode_idx(void) return SHAPE_IDX_N; } } + +/// Clears all entries in shape_table to block, blinkon0, and default color. +static void clear_shape_table(void) +{ + for (int idx = 0; idx < SHAPE_IDX_COUNT; idx++) { + shape_table[idx].shape = SHAPE_BLOCK; + shape_table[idx].blinkwait = 0L; + shape_table[idx].blinkon = 0L; + shape_table[idx].blinkoff = 0L; + shape_table[idx].id = 0; + shape_table[idx].id_lm = 0; + } +} diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c new file mode 100644 index 0000000000..03ce2a37b5 --- /dev/null +++ b/src/nvim/decoration.c @@ -0,0 +1,330 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check +// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + +#include "nvim/vim.h" +#include "nvim/extmark.h" +#include "nvim/decoration.h" +#include "nvim/screen.h" +#include "nvim/syntax.h" +#include "nvim/highlight.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "decoration.c.generated.h" +#endif + +static PMap(uint64_t) *hl_decors; + +void decor_init(void) +{ + hl_decors = pmap_new(uint64_t)(); +} + +/// Add highlighting to a buffer, bounded by two cursor positions, +/// with an offset. +/// +/// TODO(bfredl): make decoration powerful enough so that this +/// can be done with a single ephemeral decoration. +/// +/// @param buf Buffer to add highlights to +/// @param src_id src_id to use or 0 to use a new src_id group, +/// or -1 for ungrouped highlight. +/// @param hl_id Highlight group id +/// @param pos_start Cursor position to start the hightlighting at +/// @param pos_end Cursor position to end the highlighting at +/// @param offset Move the whole highlighting this many columns to the right +void bufhl_add_hl_pos_offset(buf_T *buf, + int src_id, + int hl_id, + lpos_T pos_start, + lpos_T pos_end, + colnr_T offset) +{ + colnr_T hl_start = 0; + colnr_T hl_end = 0; + Decoration *decor = decor_hl(hl_id); + + // TODO(bfredl): if decoration had blocky mode, we could avoid this loop + for (linenr_T lnum = pos_start.lnum; lnum <= pos_end.lnum; lnum ++) { + int end_off = 0; + if (pos_start.lnum < lnum && lnum < pos_end.lnum) { + // TODO(bfredl): This is quite ad-hoc, but the space between |num| and + // text being highlighted is the indication of \n being part of the + // substituted text. But it would be more consistent to highlight + // a space _after_ the previous line instead (like highlight EOL list + // char) + hl_start = MAX(offset-1, 0); + end_off = 1; + hl_end = 0; + } else if (lnum == pos_start.lnum && lnum < pos_end.lnum) { + hl_start = pos_start.col + offset; + end_off = 1; + hl_end = 0; + } else if (pos_start.lnum < lnum && lnum == pos_end.lnum) { + hl_start = MAX(offset-1, 0); + hl_end = pos_end.col + offset; + } else if (pos_start.lnum == lnum && pos_end.lnum == lnum) { + hl_start = pos_start.col + offset; + hl_end = pos_end.col + offset; + } + (void)extmark_set(buf, (uint64_t)src_id, 0, + (int)lnum-1, hl_start, (int)lnum-1+end_off, hl_end, + decor, kExtmarkNoUndo); + } +} + +Decoration *decor_hl(int hl_id) +{ + assert(hl_id > 0); + Decoration **dp = (Decoration **)pmap_ref(uint64_t)(hl_decors, + (uint64_t)hl_id, true); + if (*dp) { + return *dp; + } + + Decoration *decor = xcalloc(1, sizeof(*decor)); + decor->hl_id = hl_id; + decor->shared = true; + *dp = decor; + return decor; +} + +void decor_redraw(buf_T *buf, int row1, int row2, Decoration *decor) +{ + if (decor->hl_id && row2 >= row1) { + redraw_buf_range_later(buf, row1+1, row2+1); + } + + if (kv_size(decor->virt_text)) { + redraw_buf_line_later(buf, row1+1); + } +} + +void decor_free(Decoration *decor) +{ + if (decor && !decor->shared) { + clear_virttext(&decor->virt_text); + xfree(decor); + } +} + +void clear_virttext(VirtText *text) +{ + for (size_t i = 0; i < kv_size(*text); i++) { + xfree(kv_A(*text, i).text); + } + kv_destroy(*text); + *text = (VirtText)KV_INITIAL_VALUE; +} + +VirtText *decor_find_virttext(buf_T *buf, int row, uint64_t ns_id) +{ + MarkTreeIter itr[1] = { 0 }; + marktree_itr_get(buf->b_marktree, row, 0, itr); + while (true) { + mtmark_t mark = marktree_itr_current(itr); + if (mark.row < 0 || mark.row > row) { + break; + } + ExtmarkItem *item = map_ref(uint64_t, ExtmarkItem)(buf->b_extmark_index, + mark.id, false); + if (item && (ns_id == 0 || ns_id == item->ns_id) + && item->decor && kv_size(item->decor->virt_text)) { + return &item->decor->virt_text; + } + marktree_itr_next(buf->b_marktree, itr); + } + return NULL; +} + +bool decor_redraw_reset(buf_T *buf, DecorState *state) +{ + state->row = -1; + state->buf = buf; + for (size_t i = 0; i < kv_size(state->active); i++) { + HlRange item = kv_A(state->active, i); + if (item.virt_text_owned) { + clear_virttext(item.virt_text); + xfree(item.virt_text); + } + } + kv_size(state->active) = 0; + return buf->b_extmark_index; +} + + +bool decor_redraw_start(buf_T *buf, int top_row, DecorState *state) +{ + state->top_row = top_row; + marktree_itr_get(buf->b_marktree, top_row, 0, state->itr); + if (!state->itr->node) { + return false; + } + marktree_itr_rewind(buf->b_marktree, state->itr); + while (true) { + mtmark_t mark = marktree_itr_current(state->itr); + if (mark.row < 0) { // || mark.row > end_row + break; + } + if ((mark.row < top_row && mark.id&MARKTREE_END_FLAG)) { + goto next_mark; + } + mtpos_t altpos = marktree_lookup(buf->b_marktree, + mark.id^MARKTREE_END_FLAG, NULL); + + uint64_t start_id = mark.id & ~MARKTREE_END_FLAG; + ExtmarkItem *item = map_ref(uint64_t, ExtmarkItem)(buf->b_extmark_index, + start_id, false); + if (!item || !item->decor) { + // TODO(bfredl): dedicated flag for being a decoration? + goto next_mark; + } + Decoration *decor = item->decor; + + if ((!(mark.id&MARKTREE_END_FLAG) && altpos.row < top_row + && !kv_size(decor->virt_text)) + || ((mark.id&MARKTREE_END_FLAG) && altpos.row >= top_row)) { + goto next_mark; + } + + int attr_id = decor->hl_id > 0 ? syn_id2attr(decor->hl_id) : 0; + VirtText *vt = kv_size(decor->virt_text) ? &decor->virt_text : NULL; + HlRange range; + if (mark.id&MARKTREE_END_FLAG) { + range = (HlRange){ altpos.row, altpos.col, mark.row, mark.col, + attr_id, vt, false }; + } else { + range = (HlRange){ mark.row, mark.col, altpos.row, + altpos.col, attr_id, vt, false }; + } + kv_push(state->active, range); + +next_mark: + if (marktree_itr_node_done(state->itr)) { + break; + } + marktree_itr_next(buf->b_marktree, state->itr); + } + + return true; // TODO(bfredl): check if available in the region +} + +bool decor_redraw_line(buf_T *buf, int row, DecorState *state) +{ + if (state->row == -1) { + decor_redraw_start(buf, row, state); + } + state->row = row; + state->col_until = -1; + return true; // TODO(bfredl): be more precise +} + +int decor_redraw_col(buf_T *buf, int col, DecorState *state) +{ + if (col <= state->col_until) { + return state->current; + } + state->col_until = MAXCOL; + while (true) { + mtmark_t mark = marktree_itr_current(state->itr); + if (mark.row < 0 || mark.row > state->row) { + break; + } else if (mark.row == state->row && mark.col > col) { + state->col_until = mark.col-1; + break; + } + + if ((mark.id&MARKTREE_END_FLAG)) { + // TODO(bfredl): check decoration flag + goto next_mark; + } + mtpos_t endpos = marktree_lookup(buf->b_marktree, + mark.id|MARKTREE_END_FLAG, NULL); + + ExtmarkItem *item = map_ref(uint64_t, ExtmarkItem)(buf->b_extmark_index, + mark.id, false); + if (!item || !item->decor) { + // TODO(bfredl): dedicated flag for being a decoration? + goto next_mark; + } + Decoration *decor = item->decor; + + if (endpos.row < mark.row + || (endpos.row == mark.row && endpos.col <= mark.col)) { + if (!kv_size(decor->virt_text)) { + goto next_mark; + } + } + + int attr_id = decor->hl_id > 0 ? syn_id2attr(decor->hl_id) : 0; + VirtText *vt = kv_size(decor->virt_text) ? &decor->virt_text : NULL; + kv_push(state->active, ((HlRange){ mark.row, mark.col, + endpos.row, endpos.col, + attr_id, vt, false })); + +next_mark: + marktree_itr_next(buf->b_marktree, state->itr); + } + + int attr = 0; + size_t j = 0; + for (size_t i = 0; i < kv_size(state->active); i++) { + HlRange item = kv_A(state->active, i); + bool active = false, keep = true; + if (item.end_row < state->row + || (item.end_row == state->row && item.end_col <= col)) { + if (!(item.start_row >= state->row && item.virt_text)) { + keep = false; + } + } else { + if (item.start_row < state->row + || (item.start_row == state->row && item.start_col <= col)) { + active = true; + if (item.end_row == state->row) { + state->col_until = MIN(state->col_until, item.end_col-1); + } + } else { + if (item.start_row == state->row) { + state->col_until = MIN(state->col_until, item.start_col-1); + } + } + } + if (active && item.attr_id > 0) { + attr = hl_combine_attr(attr, item.attr_id); + } + if (keep) { + kv_A(state->active, j++) = kv_A(state->active, i); + } else if (item.virt_text_owned) { + clear_virttext(item.virt_text); + xfree(item.virt_text); + } + } + kv_size(state->active) = j; + state->current = attr; + return attr; +} + +void decor_redraw_end(DecorState *state) +{ + state->buf = NULL; +} + +VirtText *decor_redraw_virt_text(buf_T *buf, DecorState *state) +{ + decor_redraw_col(buf, MAXCOL, state); + for (size_t i = 0; i < kv_size(state->active); i++) { + HlRange item = kv_A(state->active, i); + if (item.start_row == state->row && item.virt_text) { + return item.virt_text; + } + } + return NULL; +} + +void decor_add_ephemeral(int attr_id, int start_row, int start_col, + int end_row, int end_col, VirtText *virt_text) +{ + kv_push(decor_state.active, + ((HlRange){ start_row, start_col, + end_row, end_col, + attr_id, virt_text, virt_text != NULL })); +} diff --git a/src/nvim/decoration.h b/src/nvim/decoration.h new file mode 100644 index 0000000000..90fdc3dc43 --- /dev/null +++ b/src/nvim/decoration.h @@ -0,0 +1,71 @@ +#ifndef NVIM_DECORATION_H +#define NVIM_DECORATION_H + +#include "nvim/pos.h" +#include "nvim/buffer_defs.h" +#include "nvim/extmark_defs.h" + +// actual Decoration data is in extmark_defs.h + +typedef struct { + char *text; + int hl_id; +} VirtTextChunk; + +typedef kvec_t(VirtTextChunk) VirtText; +#define VIRTTEXT_EMPTY ((VirtText)KV_INITIAL_VALUE) + +struct Decoration +{ + int hl_id; // highlight group + VirtText virt_text; + // TODO(bfredl): style, signs, etc + bool shared; // shared decoration, don't free +}; + +typedef struct { + int start_row; + int start_col; + int end_row; + int end_col; + int attr_id; + VirtText *virt_text; + bool virt_text_owned; +} HlRange; + +typedef struct { + MarkTreeIter itr[1]; + kvec_t(HlRange) active; + buf_T *buf; + int top_row; + int row; + int col_until; + int current; + VirtText *virt_text; +} DecorState; + +typedef struct { + NS ns_id; + bool active; + LuaRef redraw_start; + LuaRef redraw_buf; + LuaRef redraw_win; + LuaRef redraw_line; + LuaRef redraw_end; + LuaRef hl_def; + int hl_valid; +} DecorProvider; + +EXTERN kvec_t(DecorProvider) decor_providers INIT(= KV_INITIAL_VALUE); +EXTERN DecorState decor_state INIT(= { 0 }); + +#define DECORATION_PROVIDER_INIT(ns_id) (DecorProvider) \ + { ns_id, false, LUA_NOREF, LUA_NOREF, \ + LUA_NOREF, LUA_NOREF, LUA_NOREF, \ + LUA_NOREF, -1 } + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "decoration.h.generated.h" +#endif + +#endif // NVIM_DECORATION_H diff --git a/src/nvim/diff.c b/src/nvim/diff.c index 3de5fc49bd..b9c293f6c8 100644 --- a/src/nvim/diff.c +++ b/src/nvim/diff.c @@ -643,7 +643,7 @@ void diff_redraw(bool dofold) if (!wp->w_p_diff) { continue; } - redraw_win_later(wp, SOME_VALID); + redraw_later(wp, SOME_VALID); if (dofold && foldmethodIsDiff(wp)) { foldUpdateAll(wp); } @@ -1415,7 +1415,7 @@ void diff_win_options(win_T *wp, int addbuf) if (addbuf) { diff_buf_add(wp->w_buffer); } - redraw_win_later(wp, NOT_VALID); + redraw_later(wp, NOT_VALID); } /// Set options not to show diffs. For the current window or all windows. diff --git a/src/nvim/digraph.c b/src/nvim/digraph.c index 65d95ff158..dd32cef1e3 100644 --- a/src/nvim/digraph.c +++ b/src/nvim/digraph.c @@ -856,6 +856,7 @@ static digr_T digraphdefault[] = { '9', '"', 0x201f }, { '/', '-', 0x2020 }, { '/', '=', 0x2021 }, + { 'o', 'o', 0x2022 }, { '.', '.', 0x2025 }, { ',', '.', 0x2026 }, { '%', '0', 0x2030 }, diff --git a/src/nvim/edit.c b/src/nvim/edit.c index ea38221dc7..9c8d64a6b2 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -1254,14 +1254,6 @@ check_pum: normalchar: // Insert a normal character. - if (mod_mask == MOD_MASK_ALT || mod_mask == MOD_MASK_META) { - // Unmapped ALT/META chord behaves like ESC+c. #8213 - stuffcharReadbuff(ESC); - stuffcharReadbuff(s->c); - u_sync(false); - break; - } - if (!p_paste) { // Trigger InsertCharPre. char_u *str = do_insert_char_pre(s->c); @@ -1490,6 +1482,20 @@ static void ins_redraw( } } + // Trigger Scroll if viewport changed. + if (ready && has_event(EVENT_WINSCROLLED) + && win_did_scroll(curwin)) { + do_autocmd_winscrolled(curwin); + } + + // Trigger BufModified if b_changed_invalid is set. + if (ready && has_event(EVENT_BUFMODIFIEDSET) + && curbuf->b_changed_invalid == true + && !pum_visible()) { + apply_autocmds(EVENT_BUFMODIFIEDSET, NULL, NULL, false, curbuf); + curbuf->b_changed_invalid = false; + } + if (curwin->w_p_cole > 0 && conceal_cursor_line(curwin) && conceal_cursor_moved) { redrawWinline(curwin, curwin->w_cursor.lnum); @@ -1919,10 +1925,10 @@ change_indent ( // TODO(bfredl): test for crazy edge cases, like we stand on a TAB or // something? does this even do the right text change then? int delta = orig_col - new_col; - extmark_splice(curbuf, curwin->w_cursor.lnum-1, new_col, - 0, delta < 0 ? -delta : 0, - 0, delta > 0 ? delta : 0, - kExtmarkUndo); + extmark_splice_cols(curbuf, curwin->w_cursor.lnum-1, new_col, + delta < 0 ? -delta : 0, + delta > 0 ? delta : 0, + kExtmarkUndo); } } @@ -4179,6 +4185,21 @@ static int ins_compl_get_exp(pos_T *ini) EW_FILE|EW_DIR|EW_ADDSLASH|EW_SILENT) == OK) { // May change home directory back to "~". tilde_replace(compl_pattern, num_matches, matches); +#ifdef BACKSLASH_IN_FILENAME + if (curbuf->b_p_csl[0] != NUL) { + for (int i = 0; i < num_matches; i++) { + char_u *ptr = matches[i]; + while (*ptr != NUL) { + if (curbuf->b_p_csl[0] == 's' && *ptr == '\\') { + *ptr = '/'; + } else if (curbuf->b_p_csl[0] == 'b' && *ptr == '/') { + *ptr = '\\'; + } + ptr += utfc_ptr2len(ptr); + } + } + } +#endif ins_compl_add_matches(num_matches, matches, p_fic || p_wic); } break; @@ -5557,13 +5578,11 @@ void insertchar( int second_indent // indent for second line if >= 0 ) { - int textwidth; char_u *p; - int fo_ins_blank; int force_format = flags & INSCHAR_FORMAT; - textwidth = comp_textwidth(force_format); - fo_ins_blank = has_format_option(FO_INS_BLANK); + const int textwidth = comp_textwidth(force_format); + const bool fo_ins_blank = has_format_option(FO_INS_BLANK); /* * Try to break the line in two or more pieces when: @@ -5764,10 +5783,11 @@ internal_format ( int cc; int save_char = NUL; bool haveto_redraw = false; - int fo_ins_blank = has_format_option(FO_INS_BLANK); - int fo_multibyte = has_format_option(FO_MBYTE_BREAK); - int fo_white_par = has_format_option(FO_WHITE_PAR); - int first_line = TRUE; + const bool fo_ins_blank = has_format_option(FO_INS_BLANK); + const bool fo_multibyte = has_format_option(FO_MBYTE_BREAK); + const bool fo_rigor_tw = has_format_option(FO_RIGOROUS_TW); + const bool fo_white_par = has_format_option(FO_WHITE_PAR); + bool first_line = true; colnr_T leader_len; bool no_leader = false; int do_comments = (flags & INSCHAR_DO_COM); @@ -5846,6 +5866,7 @@ internal_format ( curwin->w_cursor.col = startcol; foundcol = 0; + int skip_pos = 0; /* * Find position to break at. @@ -5915,7 +5936,11 @@ internal_format ( foundcol = curwin->w_cursor.col; if (curwin->w_cursor.col <= (colnr_T)wantcol) break; - } else if (cc >= 0x100 && fo_multibyte) { + } else if ((cc >= 0x100 || !utf_allow_break_before(cc)) + && fo_multibyte) { + int ncc; + bool allow_break; + // Break after or before a multi-byte character. if (curwin->w_cursor.col != startcol) { // Don't break until after the comment leader @@ -5924,8 +5949,11 @@ internal_format ( } col = curwin->w_cursor.col; inc_cursor(); - // Don't change end_foundcol if already set. - if (foundcol != curwin->w_cursor.col) { + ncc = gchar_cursor(); + allow_break = utf_allow_break(cc, ncc); + + // If we have already checked this position, skip! + if (curwin->w_cursor.col != skip_pos && allow_break) { foundcol = curwin->w_cursor.col; end_foundcol = foundcol; if (curwin->w_cursor.col <= (colnr_T)wantcol) @@ -5937,6 +5965,7 @@ internal_format ( if (curwin->w_cursor.col == 0) break; + ncc = cc; col = curwin->w_cursor.col; dec_cursor(); @@ -5945,17 +5974,56 @@ internal_format ( if (WHITECHAR(cc)) { continue; // break with space } - // Don't break until after the comment leader + // Don't break until after the comment leader. if (curwin->w_cursor.col < leader_len) { break; } curwin->w_cursor.col = col; + skip_pos = curwin->w_cursor.col; - foundcol = curwin->w_cursor.col; - end_foundcol = foundcol; - if (curwin->w_cursor.col <= (colnr_T)wantcol) - break; + allow_break = utf_allow_break(cc, ncc); + + // Must handle this to respect line break prohibition. + if (allow_break) { + foundcol = curwin->w_cursor.col; + end_foundcol = foundcol; + } + if (curwin->w_cursor.col <= (colnr_T)wantcol) { + const bool ncc_allow_break = utf_allow_break_before(ncc); + + if (allow_break) { + break; + } + if (!ncc_allow_break && !fo_rigor_tw) { + // Enable at most 1 punct hang outside of textwidth. + if (curwin->w_cursor.col == startcol) { + // We are inserting a non-breakable char, postpone + // line break check to next insert. + end_foundcol = foundcol = 0; + break; + } + + // Neither cc nor ncc is NUL if we are here, so + // it's safe to inc_cursor. + col = curwin->w_cursor.col; + + inc_cursor(); + cc = ncc; + ncc = gchar_cursor(); + // handle insert + ncc = (ncc != NUL) ? ncc : c; + + allow_break = utf_allow_break(cc, ncc); + + if (allow_break) { + // Break only when we are not at end of line. + end_foundcol = foundcol = ncc == NUL? 0 : curwin->w_cursor.col; + break; + } + curwin->w_cursor.col = col; + } + } } if (curwin->w_cursor.col == 0) break; @@ -6057,7 +6125,7 @@ internal_format ( } } } - first_line = FALSE; + first_line = false; } if (State & VREPLACE_FLAG) { @@ -6244,12 +6312,10 @@ static void check_auto_format( * Set default to window width (maximum 79) for "gq" operator. */ int comp_textwidth( - int ff // force formatting (for "gq" command) + bool ff // force formatting (for "gq" command) ) { - int textwidth; - - textwidth = curbuf->b_p_tw; + int textwidth = curbuf->b_p_tw; if (textwidth == 0 && curbuf->b_p_wm) { // The width is the window width minus 'wrapmargin' minus all the // things that add to the margin. @@ -7699,6 +7765,10 @@ static bool ins_esc(long *count, int cmdchar, bool nomove) undisplay_dollar(); } + if (cmdchar != 'r' && cmdchar != 'v') { + ins_apply_autocmds(EVENT_INSERTLEAVEPRE); + } + // When an autoindent was removed, curswant stays after the // indent if (restart_edit == NUL && (colnr_T)temp == curwin->w_cursor.col) { @@ -8515,7 +8585,7 @@ static void ins_up( if (old_topline != curwin->w_topline || old_topfill != curwin->w_topfill ) - redraw_later(VALID); + redraw_later(curwin, VALID); start_arrow(&tpos); can_cindent = true; } else { @@ -8563,7 +8633,7 @@ static void ins_down( if (old_topline != curwin->w_topline || old_topfill != curwin->w_topfill ) - redraw_later(VALID); + redraw_later(curwin, VALID); start_arrow(&tpos); can_cindent = true; } else { @@ -8957,7 +9027,7 @@ static int ins_ctrl_ey(int tc) scrolldown_clamp(); else scrollup_clamp(); - redraw_later(VALID); + redraw_later(curwin, VALID); } else { c = ins_copychar(curwin->w_cursor.lnum + (c == Ctrl_Y ? -1 : 1)); if (c != NUL) { diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 0cad5fd6c1..45d2bf7a91 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -736,7 +736,7 @@ int eval_expr_typval(const typval_T *expr, typval_T *argv, if (s == NULL || *s == NUL) { return FAIL; } - if (call_func(s, (int)STRLEN(s), rettv, argc, argv, NULL, + if (call_func(s, -1, rettv, argc, argv, NULL, 0L, 0L, &dummy, true, NULL, NULL) == FAIL) { return FAIL; } @@ -746,7 +746,7 @@ int eval_expr_typval(const typval_T *expr, typval_T *argv, if (s == NULL || *s == NUL) { return FAIL; } - if (call_func(s, (int)STRLEN(s), rettv, argc, argv, NULL, + if (call_func(s, -1, rettv, argc, argv, NULL, 0L, 0L, &dummy, true, partial, NULL) == FAIL) { return FAIL; } @@ -760,7 +760,7 @@ int eval_expr_typval(const typval_T *expr, typval_T *argv, if (eval1_emsg(&s, rettv, true) == FAIL) { return FAIL; } - if (*s != NUL) { // check for trailing chars after expr + if (*skipwhite(s) != NUL) { // check for trailing chars after expr tv_clear(rettv); emsgf(_(e_invexpr2), s); return FAIL; @@ -1679,7 +1679,7 @@ static const char *list_arg_vars(exarg_T *eap, const char *arg, int *first) arg = (const char *)find_name_end((char_u *)arg, NULL, NULL, FNE_INCL_BR | FNE_CHECK_START); if (!ascii_iswhite(*arg) && !ends_excmd(*arg)) { - emsg_severe = TRUE; + emsg_severe = true; EMSG(_(e_trailing)); break; } @@ -1692,7 +1692,7 @@ static const char *list_arg_vars(exarg_T *eap, const char *arg, int *first) /* This is mainly to keep test 49 working: when expanding * curly braces fails overrule the exception error message. */ if (len < 0 && !aborting()) { - emsg_severe = TRUE; + emsg_severe = true; EMSG2(_(e_invarg2), arg); break; } @@ -2007,7 +2007,7 @@ char_u *get_lval(char_u *const name, typval_T *const rettv, * expression evaluation has been cancelled due to an * aborting error, an interrupt, or an exception. */ if (!aborting() && !quiet) { - emsg_severe = TRUE; + emsg_severe = true; EMSG2(_(e_invarg2), name); return NULL; } @@ -2086,6 +2086,7 @@ char_u *get_lval(char_u *const name, typval_T *const rettv, tv_clear(&var1); return NULL; } + p = skipwhite(p); } // Optionally get the second index [ :expr]. @@ -2674,6 +2675,7 @@ void ex_lockvar(exarg_T *eap) static void ex_unletlock(exarg_T *eap, char_u *argstart, int deep) { char_u *arg = argstart; + char_u *name_end; bool error = false; lval_T lv; @@ -2686,43 +2688,43 @@ static void ex_unletlock(exarg_T *eap, char_u *argstart, int deep) return; } os_unsetenv(name); - arg = skipwhite(arg); - continue; - } - - // Parse the name and find the end. - char_u *const name_end = (char_u *)get_lval(arg, NULL, &lv, true, - eap->skip || error, - 0, FNE_CHECK_START); - if (lv.ll_name == NULL) { - error = true; // error, but continue parsing. - } - if (name_end == NULL || (!ascii_iswhite(*name_end) - && !ends_excmd(*name_end))) { - if (name_end != NULL) { - emsg_severe = TRUE; - EMSG(_(e_trailing)); + name_end = arg; + } else { + // Parse the name and find the end. + name_end = get_lval(arg, NULL, &lv, true, eap->skip || error, + 0, FNE_CHECK_START); + if (lv.ll_name == NULL) { + error = true; // error, but continue parsing. + } + if (name_end == NULL + || (!ascii_iswhite(*name_end) && !ends_excmd(*name_end))) { + if (name_end != NULL) { + emsg_severe = true; + EMSG(_(e_trailing)); + } + if (!(eap->skip || error)) { + clear_lval(&lv); + } + break; } - if (!(eap->skip || error)) - clear_lval(&lv); - break; - } - if (!error && !eap->skip) { - if (eap->cmdidx == CMD_unlet) { - if (do_unlet_var(&lv, name_end, eap->forceit) == FAIL) - error = TRUE; - } else { - if (do_lock_var(&lv, name_end, deep, - eap->cmdidx == CMD_lockvar) == FAIL) { - error = true; + if (!error && !eap->skip) { + if (eap->cmdidx == CMD_unlet) { + if (do_unlet_var(&lv, name_end, eap->forceit) == FAIL) { + error = true; + } + } else { + if (do_lock_var(&lv, name_end, deep, + eap->cmdidx == CMD_lockvar) == FAIL) { + error = true; + } } } - } - - if (!eap->skip) - clear_lval(&lv); + if (!eap->skip) { + clear_lval(&lv); + } + } arg = skipwhite(name_end); } while (!ends_excmd(*arg)); @@ -2992,7 +2994,6 @@ char_u *get_user_var_name(expand_T *xp, int idx) static size_t tdone; static size_t vidx; static hashitem_T *hi; - hashtab_T *ht; if (idx == 0) { gdone = bdone = wdone = vidx = 0; @@ -3013,7 +3014,10 @@ char_u *get_user_var_name(expand_T *xp, int idx) } // b: variables - ht = &curbuf->b_vars->dv_hashtab; + // In cmdwin, the alternative buffer should be used. + hashtab_T *ht = (cmdwin_type != 0 && get_cmdline_type() == NUL) + ? &prevwin->w_buffer->b_vars->dv_hashtab + : &curbuf->b_vars->dv_hashtab; if (bdone < ht->ht_used) { if (bdone++ == 0) hi = ht->ht_array; @@ -3025,7 +3029,10 @@ char_u *get_user_var_name(expand_T *xp, int idx) } // w: variables - ht = &curwin->w_vars->dv_hashtab; + // In cmdwin, the alternative window should be used. + ht = (cmdwin_type != 0 && get_cmdline_type() == NUL) + ? &prevwin->w_vars->dv_hashtab + : &curwin->w_vars->dv_hashtab; if (wdone < ht->ht_used) { if (wdone++ == 0) hi = ht->ht_array; @@ -3800,8 +3807,9 @@ static int eval6(char_u **arg, typval_T *rettv, int evaluate, int want_string) */ for (;; ) { op = **arg; - if (op != '*' && op != '/' && op != '%') + if (op != '*' && op != '/' && op != '%') { break; + } if (evaluate) { if (rettv->v_type == VAR_FLOAT) { @@ -3903,6 +3911,7 @@ static int eval6(char_u **arg, typval_T *rettv, int evaluate, int want_string) // (expression) nested expression // [expr, expr] List // {key: val, key: val} Dictionary +// #{key: val, key: val} Dictionary with literal keys // // Also handle: // ! in front logical NOT @@ -4010,11 +4019,21 @@ static int eval7( case '[': ret = get_list_tv(arg, rettv, evaluate); break; + // Dictionary: #{key: val, key: val} + case '#': + if ((*arg)[1] == '{') { + (*arg)++; + ret = dict_get_tv(arg, rettv, evaluate, true); + } else { + ret = NOTDONE; + } + break; + // Lambda: {arg, arg -> expr} - // Dictionary: {key: val, key: val} + // Dictionary: {'key': val, 'key': val} case '{': ret = get_lambda_tv(arg, rettv, evaluate); if (ret == NOTDONE) { - ret = dict_get_tv(arg, rettv, evaluate); + ret = dict_get_tv(arg, rettv, evaluate, false); } break; @@ -4516,7 +4535,6 @@ int get_option_tv(const char **const arg, typval_T *const rettv, static int get_string_tv(char_u **arg, typval_T *rettv, int evaluate) { char_u *p; - char_u *name; unsigned int extra = 0; /* @@ -4524,11 +4542,14 @@ static int get_string_tv(char_u **arg, typval_T *rettv, int evaluate) */ for (p = *arg + 1; *p != NUL && *p != '"'; MB_PTR_ADV(p)) { if (*p == '\\' && p[1] != NUL) { - ++p; - /* A "\<x>" form occupies at least 4 characters, and produces up - * to 6 characters: reserve space for 2 extra */ - if (*p == '<') - extra += 2; + p++; + // A "\<x>" form occupies at least 4 characters, and produces up + // to 21 characters (3 * 6 for the char and 3 for a modifier): + // reserve space for 18 extra. + // Each byte in the char could be encoded as K_SPECIAL K_EXTRA x. + if (*p == '<') { + extra += 18; + } } } @@ -4547,7 +4568,8 @@ static int get_string_tv(char_u **arg, typval_T *rettv, int evaluate) * Copy the string into allocated memory, handling backslashed * characters. */ - name = xmalloc(p - *arg + extra); + const int len = (int)(p - *arg + extra); + char_u *name = xmalloc(len); rettv->v_type = VAR_STRING; rettv->vval.v_string = name; @@ -4614,6 +4636,9 @@ static int get_string_tv(char_u **arg, typval_T *rettv, int evaluate) extra = trans_special((const char_u **)&p, STRLEN(p), name, true, true); if (extra != 0) { name += extra; + if (name >= rettv->vval.v_string + len) { + iemsg("get_string_tv() used more space than allocated"); + } break; } FALLTHROUGH; @@ -5339,11 +5364,31 @@ static inline bool set_ref_dict(dict_T *dict, int copyID) return false; } -/* - * Allocate a variable for a Dictionary and fill it from "*arg". - * Return OK or FAIL. Returns NOTDONE for {expr}. - */ -static int dict_get_tv(char_u **arg, typval_T *rettv, int evaluate) + +// Get the key for *{key: val} into "tv" and advance "arg". +// Return FAIL when there is no valid key. +static int get_literal_key(char_u **arg, typval_T *tv) + FUNC_ATTR_NONNULL_ALL +{ + char_u *p; + + if (!ASCII_ISALNUM(**arg) && **arg != '_' && **arg != '-') { + return FAIL; + } + for (p = *arg; ASCII_ISALNUM(*p) || *p == '_' || *p == '-'; p++) { + } + tv->v_type = VAR_STRING; + tv->vval.v_string = vim_strnsave(*arg, (int)(p - *arg)); + + *arg = skipwhite(p); + return OK; +} + +// Allocate a variable for a Dictionary and fill it from "*arg". +// "literal" is true for *{key: val} +// Return OK or FAIL. Returns NOTDONE for {expr}. +static int dict_get_tv(char_u **arg, typval_T *rettv, int evaluate, + bool literal) { dict_T *d = NULL; typval_T tvkey; @@ -5364,7 +5409,7 @@ static int dict_get_tv(char_u **arg, typval_T *rettv, int evaluate) if (eval1(&start, &tv, false) == FAIL) { // recursive! return FAIL; } - if (*start == '}') { + if (*skipwhite(start) == '}') { return NOTDONE; } } @@ -5377,7 +5422,9 @@ static int dict_get_tv(char_u **arg, typval_T *rettv, int evaluate) *arg = skipwhite(*arg + 1); while (**arg != '}' && **arg != NUL) { - if (eval1(arg, &tvkey, evaluate) == FAIL) { // recursive! + if ((literal + ? get_literal_key(arg, &tvkey) + : eval1(arg, &tvkey, evaluate)) == FAIL) { // recursive! goto failret; } if (**arg != ':') { @@ -6960,9 +7007,10 @@ void set_buffer_lines(buf_T *buf, linenr_T lnum_arg, bool append, if (!append && lnum <= curbuf->b_ml.ml_line_count) { // Existing line, replace it. + int old_len = (int)STRLEN(ml_get(lnum)); if (u_savesub(lnum) == OK && ml_replace(lnum, (char_u *)line, true) == OK) { - changed_bytes(lnum, 0); + inserted_bytes(lnum, 0, old_len, STRLEN(line)); if (is_curbuf && lnum == curwin->w_cursor.lnum) { check_cursor_col(); } @@ -7073,7 +7121,7 @@ void get_xdg_var_list(const XDGVarType xdg, typval_T *rettv) do { size_t dir_len; const char *dir; - iter = vim_env_iter(':', dirs, iter, &dir, &dir_len); + iter = vim_env_iter(ENV_SEPCHAR, dirs, iter, &dir, &dir_len); if (dir != NULL && dir_len > 0) { char *dir_with_nvim = xmemdupz(dir, dir_len); dir_with_nvim = concat_fnames_realloc(dir_with_nvim, "nvim", true); @@ -7104,7 +7152,7 @@ void get_system_output_as_rettv(typval_T *argvars, typval_T *rettv, rettv->v_type = VAR_STRING; rettv->vval.v_string = NULL; - if (check_restricted() || check_secure()) { + if (check_secure()) { return; } @@ -7261,7 +7309,7 @@ bool callback_call(Callback *const callback, const int argcount_in, } int dummy; - return call_func(name, (int)STRLEN(name), rettv, argcount_in, argvars_in, + return call_func(name, -1, rettv, argcount_in, argvars_in, NULL, curwin->w_cursor.lnum, curwin->w_cursor.lnum, &dummy, true, partial, NULL); } @@ -8483,7 +8531,7 @@ handle_subscript( } else { s = (char_u *)""; } - ret = get_func_tv(s, lua ? slen : (int)STRLEN(s), rettv, (char_u **)arg, + ret = get_func_tv(s, lua ? slen : -1, rettv, (char_u **)arg, curwin->w_cursor.lnum, curwin->w_cursor.lnum, &len, evaluate, pt, selfdict); @@ -9044,7 +9092,7 @@ static void set_var_const(const char *name, const size_t name_len, } if (is_const) { - v->di_tv.v_lock |= VAR_LOCKED; + tv_item_lock(&v->di_tv, 1, true); } } @@ -10370,7 +10418,7 @@ Channel *find_job(uint64_t id, bool show_error) void script_host_eval(char *name, typval_T *argvars, typval_T *rettv) { - if (check_restricted() || check_secure()) { + if (check_secure()) { return; } @@ -10381,10 +10429,13 @@ void script_host_eval(char *name, typval_T *argvars, typval_T *rettv) list_T *args = tv_list_alloc(1); tv_list_append_string(args, (const char *)argvars[0].vval.v_string, -1); - *rettv = eval_call_provider(name, "eval", args); + *rettv = eval_call_provider(name, "eval", args, false); } -typval_T eval_call_provider(char *provider, char *method, list_T *arguments) +/// @param discard Clears the value returned by the provider and returns +/// an empty typval_T. +typval_T eval_call_provider(char *provider, char *method, list_T *arguments, + bool discard) { if (!eval_has_provider(provider)) { emsgf("E319: No \"%s\" provider found. Run \":checkhealth provider\"", @@ -10443,6 +10494,10 @@ typval_T eval_call_provider(char *provider, char *method, list_T *arguments) provider_call_nesting--; assert(provider_call_nesting >= 0); + if (discard) { + tv_clear(&rettv); + } + return rettv; } diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index 023c60f118..6c316bb1fe 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -191,6 +191,7 @@ return { inputsave={}, inputsecret={args={1, 2}}, insert={args={2, 3}}, + interrupt={args=0}, invert={args=1}, isdirectory={args=1}, isinf={args=1}, @@ -255,6 +256,7 @@ return { py3eval={args=1}, pyeval={args=1}, pyxeval={args=1}, + perleval={args=1}, range={args={1, 3}}, readdir={args={1, 2}}, readfile={args={1, 3}}, @@ -273,6 +275,7 @@ return { rpcrequest={args=varargs(2)}, rpcstart={args={1, 2}}, rpcstop={args=1}, + rubyeval={args=1}, screenattr={args=2}, screenchar={args=2}, screencol={}, @@ -335,7 +338,7 @@ return { stridx={args={2, 3}}, string={args=1}, strlen={args=1}, - strpart={args={2, 3}}, + strpart={args={2, 4}}, strridx={args={2, 3}}, strtrans={args=1}, strwidth={args=1}, @@ -369,7 +372,7 @@ return { tolower={args=1}, toupper={args=1}, tr={args=3}, - trim={args={1,2}}, + trim={args={1,3}}, trunc={args=1, func="float_op_wrapper", data="&trunc"}, type={args=1}, undofile={args=1}, diff --git a/src/nvim/eval/decode.c b/src/nvim/eval/decode.c index daba304f00..638fef331a 100644 --- a/src/nvim/eval/decode.c +++ b/src/nvim/eval/decode.c @@ -586,7 +586,7 @@ parse_json_number_check: if (p == ints) { emsgf(_("E474: Missing number after minus sign: %.*s"), LENP(s, e)); goto parse_json_number_fail; - } else if (p == fracs || exps_s == fracs + 1) { + } else if (p == fracs || (fracs != NULL && exps_s == fracs + 1)) { emsgf(_("E474: Missing number after decimal dot: %.*s"), LENP(s, e)); goto parse_json_number_fail; } else if (p == exps) { diff --git a/src/nvim/eval/encode.c b/src/nvim/eval/encode.c index 137f099df6..9a9f2e4287 100644 --- a/src/nvim/eval/encode.c +++ b/src/nvim/eval/encode.c @@ -159,8 +159,11 @@ static int conv_error(const char *const msg, const MPConvStack *const mpstack, vim_snprintf((char *)IObuff, IOSIZE, idx_msg, idx); ga_concat(&msg_ga, IObuff); } else { - typval_T key_tv = *TV_LIST_ITEM_TV( - tv_list_first(TV_LIST_ITEM_TV(li)->vval.v_list)); + assert(li != NULL); + listitem_T *const first_item = + tv_list_first(TV_LIST_ITEM_TV(li)->vval.v_list); + assert(first_item != NULL); + typval_T key_tv = *TV_LIST_ITEM_TV(first_item); char *const key = encode_tv2echo(&key_tv, NULL); vim_snprintf((char *) IObuff, IOSIZE, key_pair_msg, key, idx); xfree(key); diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index e350d09935..679548ab91 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -32,6 +32,7 @@ #include "nvim/indent.h" #include "nvim/indent_c.h" #include "nvim/lua/executor.h" +#include "nvim/macros.h" #include "nvim/mark.h" #include "nvim/math.h" #include "nvim/memline.h" @@ -86,8 +87,10 @@ KHASH_MAP_INIT_STR(functions, VimLFuncDef) #endif PRAGMA_DIAG_PUSH_IGNORE_MISSING_PROTOTYPES +PRAGMA_DIAG_PUSH_IGNORE_IMPLICIT_FALLTHROUGH #include "funcs.generated.h" PRAGMA_DIAG_POP +PRAGMA_DIAG_POP #endif @@ -202,7 +205,7 @@ static void float_op_wrapper(typval_T *argvars, typval_T *rettv, FunPtr fptr) static void api_wrapper(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - if (check_restricted() || check_secure()) { + if (check_secure()) { return; } @@ -859,7 +862,7 @@ static void f_chanclose(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->v_type = VAR_NUMBER; rettv->vval.v_number = 0; - if (check_restricted() || check_secure()) { + if (check_secure()) { return; } @@ -898,7 +901,7 @@ static void f_chansend(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->v_type = VAR_NUMBER; rettv->vval.v_number = 0; - if (check_restricted() || check_secure()) { + if (check_secure()) { return; } @@ -1477,7 +1480,7 @@ static void f_deepcopy(typval_T *argvars, typval_T *rettv, FunPtr fptr) static void f_delete(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = -1; - if (check_restricted() || check_secure()) { + if (check_secure()) { return; } @@ -1512,7 +1515,7 @@ static void f_delete(typval_T *argvars, typval_T *rettv, FunPtr fptr) // dictwatcheradd(dict, key, funcref) function static void f_dictwatcheradd(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - if (check_restricted() || check_secure()) { + if (check_secure()) { return; } @@ -1550,7 +1553,7 @@ static void f_dictwatcheradd(typval_T *argvars, typval_T *rettv, FunPtr fptr) // dictwatcherdel(dict, key, funcref) function static void f_dictwatcherdel(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - if (check_restricted() || check_secure()) { + if (check_secure()) { return; } @@ -2068,6 +2071,12 @@ static void f_expand(typval_T *argvars, typval_T *rettv, FunPtr fptr) expand_T xpc; bool error = false; char_u *result; +#ifdef BACKSLASH_IN_FILENAME + char_u *p_csl_save = p_csl; + + // avoid using 'completeslash' here + p_csl = empty_option; +#endif rettv->v_type = VAR_STRING; if (argvars[1].v_type != VAR_UNKNOWN @@ -2120,6 +2129,9 @@ static void f_expand(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_string = NULL; } } +#ifdef BACKSLASH_IN_FILENAME + p_csl = p_csl_save; +#endif } @@ -2407,9 +2419,9 @@ static void f_float2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr) float_T f; if (tv_get_float_chk(argvars, &f)) { - if (f <= -VARNUMBER_MAX + DBL_EPSILON) { + if (f <= (float_T)-VARNUMBER_MAX + DBL_EPSILON) { rettv->vval.v_number = -VARNUMBER_MAX; - } else if (f >= VARNUMBER_MAX - DBL_EPSILON) { + } else if (f >= (float_T)VARNUMBER_MAX - DBL_EPSILON) { rettv->vval.v_number = VARNUMBER_MAX; } else { rettv->vval.v_number = (varnumber_T)f; @@ -2581,8 +2593,6 @@ static void f_foldtextresult(typval_T *argvars, typval_T *rettv, FunPtr fptr) { char_u *text; char_u buf[FOLD_TEXT_LEN]; - foldinfo_T foldinfo; - int fold_count; static bool entered = false; rettv->v_type = VAR_STRING; @@ -2596,9 +2606,10 @@ static void f_foldtextresult(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (lnum < 0) { lnum = 0; } - fold_count = foldedCount(curwin, lnum, &foldinfo); - if (fold_count > 0) { - text = get_foldtext(curwin, lnum, lnum + fold_count - 1, &foldinfo, buf); + + foldinfo_T info = fold_info(curwin, lnum); + if (info.fi_lines > 0) { + text = get_foldtext(curwin, lnum, lnum + info.fi_lines - 1, info, buf); if (text == buf) { text = vim_strsave(text); } @@ -3387,63 +3398,23 @@ static void f_getftype(typval_T *argvars, typval_T *rettv, FunPtr fptr) FileInfo file_info; if (os_fileinfo_link(fname, &file_info)) { uint64_t mode = file_info.stat.st_mode; -#ifdef S_ISREG - if (S_ISREG(mode)) + if (S_ISREG(mode)) { t = "file"; - else if (S_ISDIR(mode)) + } else if (S_ISDIR(mode)) { t = "dir"; -# ifdef S_ISLNK - else if (S_ISLNK(mode)) + } else if (S_ISLNK(mode)) { t = "link"; -# endif -# ifdef S_ISBLK - else if (S_ISBLK(mode)) + } else if (S_ISBLK(mode)) { t = "bdev"; -# endif -# ifdef S_ISCHR - else if (S_ISCHR(mode)) + } else if (S_ISCHR(mode)) { t = "cdev"; -# endif -# ifdef S_ISFIFO - else if (S_ISFIFO(mode)) + } else if (S_ISFIFO(mode)) { t = "fifo"; -# endif -# ifdef S_ISSOCK - else if (S_ISSOCK(mode)) + } else if (S_ISSOCK(mode)) { t = "socket"; -# endif - else - t = "other"; -#else -# ifdef S_IFMT - switch (mode & S_IFMT) { - case S_IFREG: t = "file"; break; - case S_IFDIR: t = "dir"; break; -# ifdef S_IFLNK - case S_IFLNK: t = "link"; break; -# endif -# ifdef S_IFBLK - case S_IFBLK: t = "bdev"; break; -# endif -# ifdef S_IFCHR - case S_IFCHR: t = "cdev"; break; -# endif -# ifdef S_IFIFO - case S_IFIFO: t = "fifo"; break; -# endif -# ifdef S_IFSOCK - case S_IFSOCK: t = "socket"; break; -# endif - default: t = "other"; - } -# else - if (os_isdir((const char_u *)fname)) { - t = "dir"; } else { - t = "file"; + t = "other"; } -# endif -#endif type = vim_strsave((char_u *)t); } rettv->vval.v_string = type; @@ -4005,7 +3976,7 @@ static void f_glob(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "globpath()" function static void f_globpath(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - int flags = 0; // Flags for globpath. + int flags = WILD_IGNORE_COMPLETESLASH; // Flags for globpath. bool error = false; // Return a string, or a list if the optional third argument is non-zero. @@ -4717,6 +4688,14 @@ static void f_insert(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } +// "interrupt()" function +static void f_interrupt(typval_T *argvars FUNC_ATTR_UNUSED, + typval_T *rettv FUNC_ATTR_UNUSED, + FunPtr fptr FUNC_ATTR_UNUSED) +{ + got_int = true; +} + /* * "invert(expr)" function */ @@ -4819,7 +4798,7 @@ static void f_jobpid(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->v_type = VAR_NUMBER; rettv->vval.v_number = 0; - if (check_restricted() || check_secure()) { + if (check_secure()) { return; } @@ -4843,7 +4822,7 @@ static void f_jobresize(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->v_type = VAR_NUMBER; rettv->vval.v_number = 0; - if (check_restricted() || check_secure()) { + if (check_secure()) { return; } @@ -4876,7 +4855,7 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->v_type = VAR_NUMBER; rettv->vval.v_number = 0; - if (check_restricted() || check_secure()) { + if (check_secure()) { return; } @@ -5009,7 +4988,7 @@ static void f_jobstop(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->v_type = VAR_NUMBER; rettv->vval.v_number = 0; - if (check_restricted() || check_secure()) { + if (check_secure()) { return; } @@ -5042,7 +5021,7 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->v_type = VAR_NUMBER; rettv->vval.v_number = 0; - if (check_restricted() || check_secure()) { + if (check_secure()) { return; } if (argvars[0].v_type != VAR_LIST || (argvars[1].v_type != VAR_NUMBER @@ -5260,7 +5239,7 @@ static void libcall_common(typval_T *argvars, typval_T *rettv, int out_type) rettv->vval.v_string = NULL; } - if (check_restricted() || check_secure()) { + if (check_secure()) { return; } @@ -5277,7 +5256,7 @@ static void libcall_common(typval_T *argvars, typval_T *rettv, int out_type) // input variables char *str_in = (in_type == VAR_STRING) ? (char *)argvars[2].vval.v_string : NULL; - int64_t int_in = argvars[2].vval.v_number; + int int_in = argvars[2].vval.v_number; // output variables char **str_out = (out_type == VAR_STRING) @@ -5471,7 +5450,7 @@ static void f_luaeval(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } - executor_eval_lua(cstr_as_string((char *)str), &argvars[1], rettv); + nlua_typval_eval(cstr_as_string((char *)str), &argvars[1], rettv); } /* @@ -5963,8 +5942,9 @@ static void f_mkdir(typval_T *argvars, typval_T *rettv, FunPtr fptr) int prot = 0755; // -V536 rettv->vval.v_number = FAIL; - if (check_restricted() || check_secure()) + if (check_secure()) { return; + } char buf[NUMBUFLEN]; const char *const dir = tv_get_string_buf(&argvars[0], buf); @@ -6363,6 +6343,20 @@ static void f_pyxeval(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } +/// +/// "perleval()" function +/// +static void f_perleval(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + script_host_eval("perl", argvars, rettv); +} + +// "rubyeval()" function +static void f_rubyeval(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + script_host_eval("ruby", argvars, rettv); +} + /* * "range()" function */ @@ -6839,7 +6833,7 @@ static void f_remove(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void f_rename(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - if (check_restricted() || check_secure()) { + if (check_secure()) { rettv->vval.v_number = -1; } else { char buf[NUMBUFLEN]; @@ -6918,7 +6912,7 @@ static void f_resolve(typval_T *argvars, typval_T *rettv, FunPtr fptr) } ptrdiff_t len = (ptrdiff_t)strlen(p); - if (len > 0 && after_pathsep(p, p + len)) { + if (len > 1 && after_pathsep(p, p + len)) { has_trailing_pathsep = true; p[len - 1] = NUL; // The trailing slash breaks readlink(). } @@ -7237,7 +7231,7 @@ static void f_rpcnotify(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->v_type = VAR_NUMBER; rettv->vval.v_number = 0; - if (check_restricted() || check_secure()) { + if (check_secure()) { return; } @@ -7273,7 +7267,7 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = 0; const int l_provider_call_nesting = provider_call_nesting; - if (check_restricted() || check_secure()) { + if (check_secure()) { return; } @@ -7370,7 +7364,7 @@ static void f_rpcstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->v_type = VAR_NUMBER; rettv->vval.v_number = 0; - if (check_restricted() || check_secure()) { + if (check_secure()) { return; } @@ -7436,7 +7430,7 @@ static void f_rpcstop(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->v_type = VAR_NUMBER; rettv->vval.v_number = 0; - if (check_restricted() || check_secure()) { + if (check_secure()) { return; } @@ -7650,7 +7644,7 @@ static int searchpair_cmn(typval_T *argvars, pos_T *match_pos) } retval = do_searchpair( - (char_u *)spat, (char_u *)mpat, (char_u *)epat, dir, skip, + spat, mpat, epat, dir, skip, flags, match_pos, lnum_stop, time_limit); theend: @@ -7694,9 +7688,9 @@ static void f_searchpairpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ long do_searchpair( - char_u *spat, // start pattern - char_u *mpat, // middle pattern - char_u *epat, // end pattern + const char *spat, // start pattern + const char *mpat, // middle pattern + const char *epat, // end pattern int dir, // BACKWARD or FORWARD const typval_T *skip, // skip expression int flags, // SP_SETPCMARK and other SP_ values @@ -7704,6 +7698,7 @@ do_searchpair( linenr_T lnum_stop, // stop at this line if not zero long time_limit // stop after this many msec ) + FUNC_ATTR_NONNULL_ARG(1, 2, 3) { char_u *save_cpo; char_u *pat, *pat2 = NULL, *pat3 = NULL; @@ -7718,8 +7713,6 @@ do_searchpair( bool use_skip = false; int options = SEARCH_KEEP; proftime_T tm; - size_t pat2_len; - size_t pat3_len; // Make 'cpoptions' empty, the 'l' flag should not be used here. save_cpo = p_cpo; @@ -7730,9 +7723,9 @@ do_searchpair( // Make two search patterns: start/end (pat2, for in nested pairs) and // start/middle/end (pat3, for the top pair). - pat2_len = STRLEN(spat) + STRLEN(epat) + 17; + const size_t pat2_len = strlen(spat) + strlen(epat) + 17; pat2 = xmalloc(pat2_len); - pat3_len = STRLEN(spat) + STRLEN(mpat) + STRLEN(epat) + 25; + const size_t pat3_len = strlen(spat) + strlen(mpat) + strlen(epat) + 25; pat3 = xmalloc(pat3_len); snprintf((char *)pat2, pat2_len, "\\m\\(%s\\m\\)\\|\\(%s\\m\\)", spat, epat); if (*mpat == NUL) { @@ -7899,7 +7892,7 @@ static void f_serverstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->v_type = VAR_STRING; rettv->vval.v_string = NULL; // Address of the new server - if (check_restricted() || check_secure()) { + if (check_secure()) { return; } @@ -7941,7 +7934,7 @@ static void f_serverstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "serverstop()" function static void f_serverstop(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - if (check_restricted() || check_secure()) { + if (check_secure()) { return; } @@ -8124,15 +8117,17 @@ static void f_setline(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// Create quickfix/location list from VimL values /// /// Used by `setqflist()` and `setloclist()` functions. Accepts invalid -/// list_arg, action_arg and what_arg arguments in which case errors out, -/// including VAR_UNKNOWN parameters. +/// 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] list_arg Quickfix list contents. -/// @param[in] action_arg Action to perform: append to an existing list, -/// replace its content or create a new one. -/// @param[in] title_arg New list title. Defaults to caller function name. +/// @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) @@ -8142,7 +8137,7 @@ static void set_qf_ll_list(win_T *wp, typval_T *args, typval_T *rettv) int action = ' '; static int recursive = 0; rettv->vval.v_number = -1; - dict_T *d = NULL; + dict_T *what = NULL; typval_T *list_arg = &args[0]; if (list_arg->v_type != VAR_LIST) { @@ -8170,18 +8165,18 @@ static void set_qf_ll_list(win_T *wp, typval_T *args, typval_T *rettv) return; } - typval_T *title_arg = &args[2]; - if (title_arg->v_type == VAR_UNKNOWN) { + typval_T *const what_arg = &args[2]; + if (what_arg->v_type == VAR_UNKNOWN) { // Option argument was not given. goto skip_args; - } else if (title_arg->v_type == VAR_STRING) { - title = tv_get_string_chk(title_arg); + } 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 (title_arg->v_type == VAR_DICT) { - d = title_arg->vval.v_dict; + } 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; @@ -8194,7 +8189,7 @@ skip_args: recursive++; list_T *const l = list_arg->vval.v_list; - if (set_errorlist(wp, l, action, (char_u *)title, d) == OK) { + if (set_errorlist(wp, l, action, (char_u *)title, what) == OK) { rettv->vval.v_number = 0; } recursive--; @@ -9156,7 +9151,7 @@ static int item_compare2(const void *s1, const void *s2, bool keep_zero) rettv.v_type = VAR_UNKNOWN; // tv_clear() uses this res = call_func((const char_u *)func_name, - (int)STRLEN(func_name), + -1, &rettv, 2, argv, NULL, 0L, 0L, &dummy, true, partial, sortinfo->item_compare_selfdict); tv_clear(&argv[0]); @@ -9519,7 +9514,7 @@ static void f_split(typval_T *argvars, typval_T *rettv, FunPtr fptr) tv_list_alloc_ret(rettv, kListLenMayKnow); if (typeerr) { - return; + goto theend; } regmatch_T regmatch = { @@ -9563,6 +9558,7 @@ static void f_split(typval_T *argvars, typval_T *rettv, FunPtr fptr) vim_regfree(regmatch.regprog); } +theend: p_cpo = save_cpo; } @@ -9937,6 +9933,16 @@ static void f_strpart(typval_T *argvars, typval_T *rettv, FunPtr fptr) len = slen - n; } + if (argvars[2].v_type != VAR_UNKNOWN && argvars[3].v_type != VAR_UNKNOWN) { + int off; + + // length in characters + for (off = n; off < (int)slen && len > 0; len--) { + off += utfc_ptr2len((char_u *)p + off); + } + len = off - n; + } + rettv->v_type = VAR_STRING; rettv->vval.v_string = (char_u *)xmemdupz(p + n, (size_t)len); } @@ -10461,7 +10467,7 @@ static void f_tempname(typval_T *argvars, typval_T *rettv, FunPtr fptr) // "termopen(cmd[, cwd])" function static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - if (check_restricted() || check_secure()) { + if (check_secure()) { return; } @@ -10795,52 +10801,72 @@ static void f_trim(typval_T *argvars, typval_T *rettv, FunPtr fptr) const char_u *prev; const char_u *p; int c1; + int dir = 0; rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; if (head == NULL) { - rettv->vval.v_string = NULL; return; } if (argvars[1].v_type == VAR_STRING) { mask = (const char_u *)tv_get_string_buf_chk(&argvars[1], buf2); + if (argvars[2].v_type != VAR_UNKNOWN) { + bool error = false; + // leading or trailing characters to trim + dir = (int)tv_get_number_chk(&argvars[2], &error); + if (error) { + return; + } + if (dir < 0 || dir > 2) { + emsgf(_(e_invarg2), tv_get_string(&argvars[2])); + return; + } + } } - while (*head != NUL) { - c1 = PTR2CHAR(head); - if (mask == NULL) { - if (c1 > ' ' && c1 != 0xa0) { - break; - } - } else { - for (p = mask; *p != NUL; MB_PTR_ADV(p)) { - if (c1 == PTR2CHAR(p)) { + if (dir == 0 || dir == 1) { + // Trim leading characters + while (*head != NUL) { + c1 = PTR2CHAR(head); + if (mask == NULL) { + if (c1 > ' ' && c1 != 0xa0) { + break; + } + } else { + for (p = mask; *p != NUL; MB_PTR_ADV(p)) { + if (c1 == PTR2CHAR(p)) { + break; + } + } + if (*p == NUL) { break; } } - if (*p == NUL) { - break; - } + MB_PTR_ADV(head); } - MB_PTR_ADV(head); } - for (tail = head + STRLEN(head); tail > head; tail = prev) { - prev = tail; - MB_PTR_BACK(head, prev); - c1 = PTR2CHAR(prev); - if (mask == NULL) { - if (c1 > ' ' && c1 != 0xa0) { - break; - } - } else { - for (p = mask; *p != NUL; MB_PTR_ADV(p)) { - if (c1 == PTR2CHAR(p)) { + tail = head + STRLEN(head); + if (dir == 0 || dir == 2) { + // Trim trailing characters + for (; tail > head; tail = prev) { + prev = tail; + MB_PTR_BACK(head, prev); + c1 = PTR2CHAR(prev); + if (mask == NULL) { + if (c1 > ' ' && c1 != 0xa0) { + break; + } + } else { + for (p = mask; *p != NUL; MB_PTR_ADV(p)) { + if (c1 == PTR2CHAR(p)) { + break; + } + } + if (*p == NUL) { break; } - } - if (*p == NUL) { - break; } } } diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 8dde78de3d..ada6f78f10 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -1358,7 +1358,8 @@ void tv_dict_item_remove(dict_T *const dict, dictitem_T *const item) //{{{2 Alloc/free -/// Allocate an empty dictionary +/// Allocate an empty dictionary. +/// Caller should take care of the reference count. /// /// @return [allocated] new dictionary. dict_T *tv_dict_alloc(void) diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h index 503a32a81e..1e3e9bd366 100644 --- a/src/nvim/eval/typval.h +++ b/src/nvim/eval/typval.h @@ -301,7 +301,8 @@ struct funccall_S { int dbg_tick; ///< Debug_tick when breakpoint was set. int level; ///< Top nesting level of executed function. proftime_T prof_child; ///< Time spent in a child. - funccall_T *caller; ///< Calling function or NULL. + funccall_T *caller; ///< Calling function or NULL; or next funccal in + ///< list pointed to by previous_funccal. int fc_refcount; ///< Number of user functions that reference this funccall. int fc_copyID; ///< CopyID used for garbage collection. garray_T fc_funcs; ///< List of ufunc_T* which keep a reference to "func". diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index 229f0e8dde..dc94bc698d 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -32,7 +32,11 @@ #define FC_DELETED 0x10 // :delfunction used while uf_refcount > 0 #define FC_REMOVED 0x20 // function redefined while uf_refcount > 0 #define FC_SANDBOX 0x40 // function defined in the sandbox -#define FC_CFUNC 0x80 // C function extension +#define FC_DEAD 0x80 // function kept only for reference to dfunc +#define FC_EXPORT 0x100 // "export def Func()" +#define FC_NOARGS 0x200 // no a: variables in lambda +#define FC_VIM9 0x400 // defined in vim9 script file +#define FC_CFUNC 0x800 // C function extension #ifdef INCLUDE_GENERATED_DECLARATIONS #include "eval/userfunc.c.generated.h" @@ -246,6 +250,10 @@ int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate) ((char_u **)(newlines.ga_data))[newlines.ga_len++] = p; STRCPY(p, "return "); STRLCPY(p + 7, s, e - s + 1); + if (strstr((char *)p + 7, "a:") == NULL) { + // No a: variables are used for sure. + flags |= FC_NOARGS; + } fp->uf_refcount = 1; STRCPY(fp->uf_name, name); @@ -367,7 +375,7 @@ void emsg_funcname(char *ermsg, const char_u *name) int get_func_tv( const char_u *name, // name of the function - int len, // length of "name" + int len, // length of "name" or -1 to use strlen() typval_T *rettv, char_u **arg, // argument, pointing to the '(' linenr_T firstline, // first line of range @@ -813,17 +821,12 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, current_funccal = fc; fc->func = fp; fc->rettv = rettv; - rettv->vval.v_number = 0; - fc->linenr = 0; - fc->returned = FALSE; fc->level = ex_nesting_level; // Check if this function has a breakpoint. fc->breakpoint = dbg_find_breakpoint(false, fp->uf_name, (linenr_T)0); fc->dbg_tick = debug_tick; // Set up fields for closure. - fc->fc_refcount = 0; - fc->fc_copyID = 0; ga_init(&fc->fc_funcs, sizeof(ufunc_T *), 1); func_ptr_ref(fp); @@ -853,37 +856,42 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, ++selfdict->dv_refcount; } - /* - * Init a: variables. - * Set a:0 to "argcount". - * Set a:000 to a list with room for the "..." arguments. - */ + // Init a: variables, unless none found (in lambda). + // Set a:0 to "argcount". + // Set a:000 to a list with room for the "..." arguments. init_var_dict(&fc->l_avars, &fc->l_avars_var, VAR_SCOPE); - add_nr_var(&fc->l_avars, (dictitem_T *)&fc->fixvar[fixvar_idx++], "0", - (varnumber_T)(argcount - fp->uf_args.ga_len)); + if ((fp->uf_flags & FC_NOARGS) == 0) { + add_nr_var(&fc->l_avars, (dictitem_T *)&fc->fixvar[fixvar_idx++], "0", + (varnumber_T)(argcount - fp->uf_args.ga_len)); + } fc->l_avars.dv_lock = VAR_FIXED; - // Use "name" to avoid a warning from some compiler that checks the - // destination size. - v = (dictitem_T *)&fc->fixvar[fixvar_idx++]; + if ((fp->uf_flags & FC_NOARGS) == 0) { + // Use "name" to avoid a warning from some compiler that checks the + // destination size. + v = (dictitem_T *)&fc->fixvar[fixvar_idx++]; #ifndef __clang_analyzer__ - name = v->di_key; - STRCPY(name, "000"); + name = v->di_key; + STRCPY(name, "000"); #endif - v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; - tv_dict_add(&fc->l_avars, v); - v->di_tv.v_type = VAR_LIST; - v->di_tv.v_lock = VAR_FIXED; - v->di_tv.vval.v_list = &fc->l_varlist; + v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; + tv_dict_add(&fc->l_avars, v); + v->di_tv.v_type = VAR_LIST; + v->di_tv.v_lock = VAR_FIXED; + v->di_tv.vval.v_list = &fc->l_varlist; + } tv_list_init_static(&fc->l_varlist); tv_list_set_lock(&fc->l_varlist, VAR_FIXED); // Set a:firstline to "firstline" and a:lastline to "lastline". // Set a:name to named arguments. // Set a:N to the "..." arguments. - add_nr_var(&fc->l_avars, (dictitem_T *)&fc->fixvar[fixvar_idx++], - "firstline", (varnumber_T)firstline); - add_nr_var(&fc->l_avars, (dictitem_T *)&fc->fixvar[fixvar_idx++], - "lastline", (varnumber_T)lastline); + // Skipped when no a: variables used (in lambda). + if ((fp->uf_flags & FC_NOARGS) == 0) { + add_nr_var(&fc->l_avars, (dictitem_T *)&fc->fixvar[fixvar_idx++], + "firstline", (varnumber_T)firstline); + add_nr_var(&fc->l_avars, (dictitem_T *)&fc->fixvar[fixvar_idx++], + "lastline", (varnumber_T)lastline); + } for (int i = 0; i < argcount; i++) { bool addlocal = false; @@ -895,6 +903,10 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, addlocal = true; } } else { + if ((fp->uf_flags & FC_NOARGS) != 0) { + // Bail out if no a: arguments used (in lambda). + break; + } // "..." argument a:1, a:2, etc. snprintf((char *)numbuf, sizeof(numbuf), "%d", ai + 1); name = numbuf; @@ -1034,9 +1046,19 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, save_did_emsg = did_emsg; did_emsg = FALSE; - // call do_cmdline() to execute the lines - do_cmdline(NULL, get_func_line, (void *)fc, - DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT); + if (islambda) { + char_u *p = *(char_u **)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(&p, rettv, true); + ex_nesting_level--; + } else { + // call do_cmdline() to execute the lines + do_cmdline(NULL, get_func_line, (void *)fc, + DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT); + } --RedrawingDisabled; @@ -1291,7 +1313,7 @@ int func_call(char_u *name, typval_T *args, partial_T *partial, tv_copy(TV_LIST_ITEM_TV(item), &argv[argc++]); }); - r = call_func(name, (int)STRLEN(name), rettv, argc, argv, NULL, + r = call_func(name, -1, rettv, argc, argv, NULL, curwin->w_cursor.lnum, curwin->w_cursor.lnum, &dummy, true, partial, selfdict); @@ -1304,6 +1326,36 @@ func_call_skip_call: return r; } +// Give an error message for the result of a function. +// Nothing if "error" is FCERR_NONE. +static void user_func_error(int error, const char_u *name) + FUNC_ATTR_NONNULL_ALL +{ + switch (error) { + case ERROR_UNKNOWN: + emsg_funcname(N_("E117: Unknown function: %s"), name); + break; + case ERROR_DELETED: + emsg_funcname(N_("E933: Function was deleted: %s"), name); + break; + case ERROR_TOOMANY: + emsg_funcname(_(e_toomanyarg), name); + break; + case ERROR_TOOFEW: + emsg_funcname(N_("E119: Not enough arguments for function: %s"), + name); + break; + case ERROR_SCRIPT: + emsg_funcname(N_("E120: Using <SID> not in a script context: %s"), + name); + break; + case ERROR_DICT: + emsg_funcname(N_("E725: Calling dict function without Dictionary: %s"), + name); + break; + } +} + /// Call a function with its resolved parameters /// /// "argv_func", when not NULL, can be used to fill in arguments only when the @@ -1316,7 +1368,7 @@ func_call_skip_call: int call_func( const char_u *funcname, // name of the function - int len, // length of "name" + int len, // length of "name" or -1 to use strlen() typval_T *rettv, // [out] value goes here int argcount_in, // number of "argvars" typval_T *argvars_in, // vars for arguments, must have "argcount" @@ -1333,11 +1385,11 @@ call_func( { int ret = FAIL; int error = ERROR_NONE; - ufunc_T *fp; + ufunc_T *fp = NULL; char_u fname_buf[FLEN_FIXED + 1]; char_u *tofree = NULL; - char_u *fname; - char_u *name; + char_u *fname = NULL; + char_u *name = NULL; int argcount = argcount_in; typval_T *argvars = argvars_in; dict_T *selfdict = selfdict_in; @@ -1348,11 +1400,18 @@ call_func( // even when call_func() returns FAIL. rettv->v_type = VAR_UNKNOWN; - // Make a copy of the name, if it comes from a funcref variable it could - // be changed or deleted in the called function. - name = vim_strnsave(funcname, len); - - fname = fname_trans_sid(name, fname_buf, &tofree, &error); + if (len <= 0) { + len = (int)STRLEN(funcname); + } + if (partial != NULL) { + fp = partial->pt_func; + } + if (fp == NULL) { + // Make a copy of the name, if it comes from a funcref variable it could + // be changed or deleted in the called function. + name = vim_strnsave(funcname, len); + fname = fname_trans_sid(name, fname_buf, &tofree, &error); + } *doesrange = false; @@ -1384,7 +1443,7 @@ call_func( char_u *rfname = fname; // Ignore "g:" before a function name. - if (fname[0] == 'g' && fname[1] == ':') { + if (fp == NULL && fname[0] == 'g' && fname[1] == ':') { rfname = fname + 2; } @@ -1395,14 +1454,11 @@ call_func( if (is_luafunc(partial)) { if (len > 0) { error = ERROR_NONE; - executor_call_lua((const char *)funcname, len, - argvars, argcount, rettv); + nlua_typval_call((const char *)funcname, len, argvars, argcount, rettv); } - } else if (!builtin_function((const char *)rfname, -1)) { + } else if (fp != NULL || !builtin_function((const char *)rfname, -1)) { // User defined function. - if (partial != NULL && partial->pt_func != NULL) { - fp = partial->pt_func; - } else { + if (fp == NULL) { fp = find_func(rfname); } @@ -1481,29 +1537,7 @@ theend: // Report an error unless the argument evaluation or function call has been // cancelled due to an aborting error, an interrupt, or an exception. if (!aborting()) { - switch (error) { - case ERROR_UNKNOWN: - emsg_funcname(N_("E117: Unknown function: %s"), name); - break; - case ERROR_DELETED: - emsg_funcname(N_("E933: Function was deleted: %s"), name); - break; - case ERROR_TOOMANY: - emsg_funcname(_(e_toomanyarg), name); - break; - case ERROR_TOOFEW: - emsg_funcname(N_("E119: Not enough arguments for function: %s"), - name); - break; - case ERROR_SCRIPT: - emsg_funcname(N_("E120: Using <SID> not in a script context: %s"), - name); - break; - case ERROR_DICT: - emsg_funcname(N_("E725: Calling dict function without Dictionary: %s"), - name); - break; - } + user_func_error(error, (name != NULL) ? name : funcname); } while (argv_clear > 0) { @@ -2854,7 +2888,7 @@ void ex_call(exarg_T *eap) curwin->w_cursor.coladd = 0; } arg = startarg; - if (get_func_tv(name, (int)STRLEN(name), &rettv, &arg, + if (get_func_tv(name, -1, &rettv, &arg, eap->line1, eap->line2, &doesrange, true, partial, fudi.fd_dict) == FAIL) { failed = true; @@ -2886,8 +2920,10 @@ void ex_call(exarg_T *eap) if (!failed || eap->cstack->cs_trylevel > 0) { // Check for trailing illegal characters and a following command. if (!ends_excmd(*arg)) { - emsg_severe = TRUE; - EMSG(_(e_trailing)); + if (!failed) { + emsg_severe = true; + EMSG(_(e_trailing)); + } } else { eap->nextcmd = check_nextcmd(arg); } @@ -3348,9 +3384,13 @@ bool set_ref_in_previous_funccal(int copyID) { bool abort = false; - for (funccall_T *fc = previous_funccal; fc != NULL; fc = fc->caller) { - abort = abort || set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID + 1, NULL); - abort = abort || set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID + 1, NULL); + for (funccall_T *fc = previous_funccal; !abort && fc != NULL; + fc = fc->caller) { + fc->fc_copyID = copyID + 1; + abort = abort + || set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID + 1, NULL) + || set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID + 1, NULL) + || set_ref_in_list(&fc->l_varlist, copyID + 1, NULL); } return abort; } @@ -3361,9 +3401,11 @@ static bool set_ref_in_funccal(funccall_T *fc, int copyID) if (fc->fc_copyID != copyID) { fc->fc_copyID = copyID; - abort = abort || set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID, NULL); - abort = abort || set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID, NULL); - abort = abort || set_ref_in_func(NULL, fc->func, copyID); + abort = abort + || set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID, NULL) + || set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID, NULL) + || set_ref_in_list(&fc->l_varlist, copyID, NULL) + || set_ref_in_func(NULL, fc->func, copyID); } return abort; } @@ -3373,12 +3415,13 @@ bool set_ref_in_call_stack(int copyID) { bool abort = false; - for (funccall_T *fc = current_funccal; fc != NULL; fc = fc->caller) { + for (funccall_T *fc = current_funccal; !abort && fc != NULL; + fc = fc->caller) { abort = abort || set_ref_in_funccal(fc, copyID); } // Also go through the funccal_stack. - for (funccal_entry_T *entry = funccal_stack; entry != NULL; + for (funccal_entry_T *entry = funccal_stack; !abort && entry != NULL; entry = entry->next) { for (funccall_T *fc = entry->top_funccal; !abort && fc != NULL; fc = fc->caller) { @@ -3457,12 +3500,7 @@ bool set_ref_in_func(char_u *name, ufunc_T *fp_in, int copyID) char_u *register_cfunc(cfunc_T cb, cfunc_free_T cb_free, void *state) { char_u *name = get_lambda_name(); - ufunc_T *fp = NULL; - - fp = xcalloc(1, offsetof(ufunc_T, uf_name) + STRLEN(name) + 1); - if (fp == NULL) { - return NULL; - } + ufunc_T *fp = xcalloc(1, offsetof(ufunc_T, uf_name) + STRLEN(name) + 1); fp->uf_refcount = 1; fp->uf_varargs = true; diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 519978f4fb..17afb33059 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -41,6 +41,7 @@ #include "nvim/main.h" #include "nvim/mark.h" #include "nvim/extmark.h" +#include "nvim/decoration.h" #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/message.h" @@ -135,7 +136,7 @@ void do_ascii(const exarg_T *const eap) char buf1[20]; if (vim_isprintc_strict(c) && (c < ' ' || c > '~')) { char_u buf3[7]; - transchar_nonprint(buf3, c); + transchar_nonprint(curbuf, buf3, c); vim_snprintf(buf1, sizeof(buf1), " <%s>", (char *)buf3); } else { buf1[0] = NUL; @@ -326,14 +327,19 @@ static int linelen(int *has_tab) int save; int len; - /* find the first non-blank character */ + // Get the line. If it's empty bail out early (could be the empty string + // for an unloaded buffer). line = get_cursor_line_ptr(); + if (*line == NUL) { + return 0; + } + // find the first non-blank character first = skipwhite(line); - /* find the character after the last non-blank character */ + // find the character after the last non-blank character for (last = first + STRLEN(first); - last > first && ascii_iswhite(last[-1]); --last) - ; + last > first && ascii_iswhite(last[-1]); last--) { + } save = *last; *last = NUL; // Get line length. @@ -846,6 +852,11 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest) return OK; } + bcount_t start_byte = ml_find_line_or_offset(curbuf, line1, NULL, true); + bcount_t end_byte = ml_find_line_or_offset(curbuf, line2+1, NULL, true); + bcount_t extent_byte = end_byte-start_byte; + bcount_t dest_byte = ml_find_line_or_offset(curbuf, dest+1, NULL, true); + num_lines = line2 - line1 + 1; /* @@ -880,6 +891,8 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest) last_line = curbuf->b_ml.ml_line_count; mark_adjust_nofold(line1, line2, last_line - line2, 0L, kExtmarkNOOP); changed_lines(last_line - num_lines + 1, 0, last_line + 1, num_lines, false); + int line_off = 0; + bcount_t byte_off = 0; if (dest >= line2) { mark_adjust_nofold(line2 + 1, dest, -num_lines, 0L, kExtmarkNOOP); FOR_ALL_TAB_WINDOWS(tab, win) { @@ -889,6 +902,8 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest) } curbuf->b_op_start.lnum = dest - num_lines + 1; curbuf->b_op_end.lnum = dest; + line_off = -num_lines; + byte_off = -extent_byte; } else { mark_adjust_nofold(dest + 1, line1 - 1, num_lines, 0L, kExtmarkNOOP); FOR_ALL_TAB_WINDOWS(tab, win) { @@ -904,11 +919,10 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest) -(last_line - dest - extra), 0L, kExtmarkNOOP); // extmarks are handled separately - int size = line2-line1+1; - int off = dest >= line2 ? -size : 0; - extmark_move_region(curbuf, line1-1, 0, - line2-line1+1, 0, - dest+off, 0, kExtmarkUndo); + extmark_move_region(curbuf, line1-1, 0, start_byte, + line2-line1+1, 0, extent_byte, + dest+line_off, 0, dest_byte+byte_off, + kExtmarkUndo); changed_lines(last_line - num_lines + 1, 0, last_line + 1, -extra, false); @@ -1035,13 +1049,13 @@ void do_bang(int addr_count, exarg_T *eap, int forceit, int do_in, int do_out) int len; int scroll_save = msg_scroll; - /* - * Disallow shell commands in restricted mode (-Z) - * Disallow shell commands from .exrc and .vimrc in current directory for - * security reasons. - */ - if (check_restricted() || check_secure()) + // + // Disallow shell commands from .exrc and .vimrc in current directory for + // security reasons. + // + if (check_secure()) { return; + } if (addr_count == 0) { /* :! */ msg_scroll = FALSE; /* don't scroll here */ @@ -1369,10 +1383,9 @@ do_shell( int flags // may be SHELL_DOOUT when output is redirected ) { - // Disallow shell commands in restricted mode (-Z) // Disallow shell commands from .exrc and .vimrc in current directory for // security reasons. - if (check_restricted() || check_secure()) { + if (check_secure()) { msg_end(); return; } @@ -2227,11 +2240,9 @@ int do_ecmd( goto theend; } - /* - * if the file was changed we may not be allowed to abandon it - * - if we are going to re-edit the same file - * - or if we are the only window on this file and if ECMD_HIDE is FALSE - */ + // If the file was changed we may not be allowed to abandon it: + // - if we are going to re-edit the same file + // - or if we are the only window on this file and if ECMD_HIDE is FALSE if ( ((!other_file && !(flags & ECMD_OLDBUF)) || (curbuf->b_nwindows == 1 && !(flags & (ECMD_HIDE | ECMD_ADDBUF)))) @@ -2484,8 +2495,12 @@ int do_ecmd( new_name = NULL; } set_bufref(&bufref, buf); - if (p_ur < 0 || curbuf->b_ml.ml_line_count <= p_ur) { - // Save all the text, so that the reload can be undone. + + // If the buffer was used before, store the current contents so that + // the reload can be undone. Do not do this if the (empty) buffer is + // being re-used for another file. + if (!(curbuf->b_flags & BF_NEVERLOADED) + && (p_ur < 0 || curbuf->b_ml.ml_line_count <= p_ur)) { // Sync first so that this is a separate undo-able action. u_sync(false); if (u_savecommon(0, curbuf->b_ml.ml_line_count + 1, 0, true) @@ -3014,20 +3029,6 @@ void ex_z(exarg_T *eap) ex_no_reprint = true; } -// Check if the restricted flag is set. -// If so, give an error message and return true. -// Otherwise, return false. -bool check_restricted(void) - FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT -{ - if (restricted) { - EMSG(_("E145: Shell commands and some functionality not allowed" - " in restricted mode")); - return true; - } - return false; -} - /* * Check if the secure flag is set (.exrc or .vimrc in current directory). * If so, give an error message and return TRUE. @@ -3435,13 +3436,13 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, sub_firstline = NULL; - /* - * ~ in the substitute pattern is replaced with the old pattern. - * We do it here once to avoid it to be replaced over and over again. - * But don't do it when it starts with "\=", then it's an expression. - */ - if (!(sub[0] == '\\' && sub[1] == '=')) + // ~ in the substitute pattern is replaced with the old pattern. + // We do it here once to avoid it to be replaced over and over again. + // But don't do it when it starts with "\=", then it's an expression. + assert(sub != NULL); + if (!(sub[0] == '\\' && sub[1] == '=')) { sub = regtilde(sub, p_magic); + } // Check for a match on each line. // If preview: limit to max('cmdwinheight', viewport). @@ -3706,8 +3707,8 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, update_topline(); validate_cursor(); update_screen(SOME_VALID); - highlight_match = FALSE; - redraw_later(SOME_VALID); + highlight_match = false; + redraw_later(curwin, SOME_VALID); curwin->w_p_fen = save_p_fen; if (msg_row == Rows - 1) @@ -3908,6 +3909,18 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, ADJUST_SUB_FIRSTLNUM(); + // TODO(bfredl): adjust also in preview, because decorations? + // this has some robustness issues, will look into later. + bool do_splice = !preview; + bcount_t replaced_bytes = 0; + lpos_T start = regmatch.startpos[0], end = regmatch.endpos[0]; + if (do_splice) { + for (i = 0; i < nmatch-1; i++) { + replaced_bytes += STRLEN(ml_get(lnum_start+i)) + 1; + } + replaced_bytes += end.col - start.col; + } + // Now the trick is to replace CTRL-M chars with a real line // break. This would make it impossible to insert a CTRL-M in @@ -3951,17 +3964,14 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, current_match.end.col = new_endcol; current_match.end.lnum = lnum; - // TODO(bfredl): adjust in preview, because decorations? - // this has some robustness issues, will look into later. - if (!preview) { - lpos_T start = regmatch.startpos[0], end = regmatch.endpos[0]; + if (do_splice) { int matchcols = end.col - ((end.lnum == start.lnum) ? start.col : 0); int subcols = new_endcol - ((lnum == lnum_start) ? start_col : 0); extmark_splice(curbuf, lnum_start-1, start_col, - end.lnum-start.lnum, matchcols, - lnum-lnum_start, subcols, kExtmarkUndo); - } + end.lnum-start.lnum, matchcols, replaced_bytes, + lnum-lnum_start, subcols, sublen-1, kExtmarkUndo); + } } @@ -5727,7 +5737,7 @@ static buf_T *show_sub(exarg_T *eap, pos_T old_cusr, } xfree(str); - redraw_later(SOME_VALID); + redraw_later(curwin, SOME_VALID); win_enter(save_curwin, false); // Return to original window update_topline(); diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua index 252af409c0..d62b00fee1 100644 --- a/src/nvim/ex_cmds.lua +++ b/src/nvim/ex_cmds.lua @@ -337,7 +337,7 @@ return { }, { command='caddexpr', - flags=bit.bor(NEEDARG, WORD1, NOTRLCOM, TRLBAR), + flags=bit.bor(NEEDARG, WORD1, NOTRLCOM), addr_type=ADDR_LINES, func='ex_cexpr', }, @@ -409,7 +409,7 @@ return { }, { command='cexpr', - flags=bit.bor(NEEDARG, WORD1, NOTRLCOM, TRLBAR, BANG), + flags=bit.bor(NEEDARG, WORD1, NOTRLCOM, BANG), addr_type=ADDR_LINES, func='ex_cexpr', }, @@ -447,7 +447,7 @@ return { }, { command='cgetexpr', - flags=bit.bor(NEEDARG, WORD1, NOTRLCOM, TRLBAR), + flags=bit.bor(NEEDARG, WORD1, NOTRLCOM), addr_type=ADDR_LINES, func='ex_cexpr', }, @@ -1299,7 +1299,7 @@ return { }, { command='laddexpr', - flags=bit.bor(NEEDARG, WORD1, NOTRLCOM, TRLBAR), + flags=bit.bor(NEEDARG, WORD1, NOTRLCOM), addr_type=ADDR_LINES, func='ex_cexpr', }, @@ -1389,7 +1389,7 @@ return { }, { command='lexpr', - flags=bit.bor(NEEDARG, WORD1, NOTRLCOM, TRLBAR, BANG), + flags=bit.bor(NEEDARG, WORD1, NOTRLCOM, BANG), addr_type=ADDR_LINES, func='ex_cexpr', }, @@ -1427,7 +1427,7 @@ return { }, { command='lgetexpr', - flags=bit.bor(NEEDARG, WORD1, NOTRLCOM, TRLBAR), + flags=bit.bor(NEEDARG, WORD1, NOTRLCOM), addr_type=ADDR_LINES, func='ex_cexpr', }, @@ -1927,13 +1927,19 @@ return { command='perl', flags=bit.bor(RANGE, EXTRA, DFLALL, NEEDARG, SBOXOK, CMDWIN, RESTRICT), addr_type=ADDR_LINES, - func='ex_script_ni', + func='ex_perl', }, { command='perldo', flags=bit.bor(RANGE, EXTRA, DFLALL, NEEDARG, CMDWIN, RESTRICT), addr_type=ADDR_LINES, - func='ex_ni', + func='ex_perldo', + }, + { + command='perlfile', + flags=bit.bor(RANGE, FILE1, NEEDARG, CMDWIN, RESTRICT), + addr_type=ADDR_LINES, + func='ex_perlfile', }, { command='pedit', diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index 7f4b01e306..713d18b44d 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -935,6 +935,21 @@ void ex_pydo3(exarg_T *eap) script_host_do_range("python3", eap); } +void ex_perl(exarg_T *eap) +{ + script_host_execute("perl", eap); +} + +void ex_perlfile(exarg_T *eap) +{ + script_host_execute_file("perl", eap); +} + +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 @@ -1984,9 +1999,16 @@ void ex_argadd(exarg_T *eap) /// ":argdelete" void ex_argdelete(exarg_T *eap) { - if (eap->addr_count > 0) { - // ":1,4argdel": Delete all arguments in the range. - if (eap->line2 > ARGCOUNT) { + 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; @@ -2016,8 +2038,6 @@ void ex_argdelete(exarg_T *eap) curwin->w_arg_idx = ARGCOUNT - 1; } } - } else if (*eap->arg == NUL) { - EMSG(_(e_argreq)); } else { do_arglist(eap->arg, AL_DEL, 0); } @@ -2038,6 +2058,10 @@ void ex_listdo(exarg_T *eap) // Don't do syntax HL autocommands. Skipping the syntax file is a // great speed improvement. save_ei = au_event_disable(",Syntax"); + + FOR_ALL_BUFFERS(buf) { + buf->b_flags &= ~BF_SYN_SET; + } } if (eap->cmdidx == CMD_windo @@ -2232,9 +2256,32 @@ void ex_listdo(exarg_T *eap) } if (save_ei != NULL) { + buf_T *bnext; + aco_save_T aco; + au_event_restore(save_ei); - apply_autocmds(EVENT_SYNTAX, curbuf->b_p_syn, - curbuf->b_fname, true, curbuf); + + for (buf_T *buf = firstbuf; buf != NULL; buf = bnext) { + bnext = buf->b_next; + if (buf->b_nwindows > 0 && (buf->b_flags & BF_SYN_SET)) { + buf->b_flags &= ~BF_SYN_SET; + + // buffer was opened while Syntax autocommands were disabled, + // need to trigger them now. + if (buf == curbuf) { + apply_autocmds(EVENT_SYNTAX, curbuf->b_p_syn, + curbuf->b_fname, true, curbuf); + } else { + aucmd_prepbuf(&aco, buf); + apply_autocmds(EVENT_SYNTAX, buf->b_p_syn, + buf->b_fname, true, buf); + aucmd_restbuf(&aco); + } + + // start over, in case autocommands messed things up. + bnext = firstbuf; + } + } } } @@ -2316,7 +2363,7 @@ void ex_compiler(exarg_T *eap) do_unlet(S_LEN("b:current_compiler"), true); snprintf((char *)buf, bufsize, "compiler/%s.vim", eap->arg); - if (source_runtime(buf, DIP_ALL) == FAIL) { + if (source_in_path(p_rtp, buf, DIP_ALL) == FAIL) { EMSG2(_("E666: compiler not supported: %s"), eap->arg); } xfree(buf); @@ -2534,6 +2581,7 @@ int do_in_runtimepath(char_u *name, int flags, DoInRuntimepathCB callback, /// return FAIL when no file could be sourced, OK otherwise. int source_runtime(char_u *name, int flags) { + flags |= (flags & DIP_NORTP) ? 0 : DIP_START; return source_in_path(p_rtp, name, flags); } @@ -4152,7 +4200,7 @@ static void script_host_execute(char *name, exarg_T *eap) tv_list_append_number(args, (int)eap->line1); tv_list_append_number(args, (int)eap->line2); - (void)eval_call_provider(name, "execute", args); + (void)eval_call_provider(name, "execute", args, true); } } @@ -4167,7 +4215,7 @@ static void script_host_execute_file(char *name, exarg_T *eap) // current range tv_list_append_number(args, (int)eap->line1); tv_list_append_number(args, (int)eap->line2); - (void)eval_call_provider(name, "execute_file", args); + (void)eval_call_provider(name, "execute_file", args, true); } static void script_host_do_range(char *name, exarg_T *eap) @@ -4176,7 +4224,7 @@ static void script_host_do_range(char *name, exarg_T *eap) tv_list_append_number(args, (int)eap->line1); tv_list_append_number(args, (int)eap->line2); tv_list_append_string(args, (const char *)eap->arg, -1); - (void)eval_call_provider(name, "do_range", args); + (void)eval_call_provider(name, "do_range", args, true); } /// ":drop" diff --git a/src/nvim/ex_cmds_defs.h b/src/nvim/ex_cmds_defs.h index 1f0560ae48..21db3936b8 100644 --- a/src/nvim/ex_cmds_defs.h +++ b/src/nvim/ex_cmds_defs.h @@ -62,7 +62,6 @@ // curbuf_lock is set #define MODIFY 0x200000 // forbidden in non-'modifiable' buffer #define EXFLAGS 0x400000 // allow flags after count in argument -#define RESTRICT 0x800000L // forbidden in restricted mode #define FILES (XFILE | EXTRA) // multiple extra files allowed #define WORD1 (EXTRA | NOSPC) // one extra word allowed #define FILE1 (FILES | NOSPC) // 1 file allowed, defaults to current file @@ -169,6 +168,10 @@ struct exarg { LineGetter getline; ///< Function used to get the next line void *cookie; ///< argument for getline() cstack_T *cstack; ///< condition stack for ":if" etc. + long verbose_save; ///< saved value of p_verbose + int save_msg_silent; ///< saved value of msg_silent + int did_esilent; ///< how many times emsg_silent was incremented + bool did_sandbox; ///< when true did sandbox++ }; #define FORCE_BIN 1 // ":edit ++bin file" diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 5bf6aa73c6..a491a9d377 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -137,32 +137,6 @@ struct dbg_stuff { except_T *current_exception; }; -typedef struct { - // parsed results - exarg_T *eap; - char_u *parsed_upto; // local we've parsed up to so far - char_u *cmd; // start of command - char_u *after_modifier; - - // errors - char_u *errormsg; - - // globals that need to be updated - cmdmod_T cmdmod; - int sandbox; - int msg_silent; - int emsg_silent; - bool ex_pressedreturn; - long p_verbose; - - // other side-effects - bool set_eventignore; - long verbose_save; - int save_msg_silent; - int did_esilent; - bool did_sandbox; -} parse_state_T; - #ifdef INCLUDE_GENERATED_DECLARATIONS # include "ex_docmd.c.generated.h" #endif @@ -284,6 +258,27 @@ void do_exmode(int improved) msg_scroll = save_msg_scroll; } +// Print the executed command for when 'verbose' is set. +// When "lnum" is 0 only print the command. +static void msg_verbose_cmd(linenr_T lnum, char_u *cmd) + FUNC_ATTR_NONNULL_ALL +{ + no_wait_return++; + verbose_enter_scroll(); + + if (lnum == 0) { + smsg(_("Executing: %s"), cmd); + } else { + smsg(_("line %" PRIdLINENR ": %s"), lnum, cmd); + } + if (msg_silent == 0) { + msg_puts("\n"); // don't overwrite this + } + + verbose_leave_scroll(); + no_wait_return--; +} + /* * Execute a simple command line. Used for translated commands like "*". */ @@ -593,17 +588,8 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline, } } - if (p_verbose >= 15 && sourcing_name != NULL) { - ++no_wait_return; - verbose_enter_scroll(); - - smsg(_("line %" PRIdLINENR ": %s"), sourcing_lnum, cmdline_copy); - if (msg_silent == 0) { - msg_puts("\n"); // don't overwrite this either - } - - verbose_leave_scroll(); - --no_wait_return; + if ((p_verbose >= 15 && sourcing_name != NULL) || p_verbose >= 16) { + msg_verbose_cmd(sourcing_lnum, cmdline_copy); } /* @@ -1235,292 +1221,6 @@ static char_u *skip_colon_white(const char_u *p, bool skipleadingwhite) return (char_u *)p; } -static void parse_state_to_global(const parse_state_T *parse_state) -{ - cmdmod = parse_state->cmdmod; - sandbox = parse_state->sandbox; - msg_silent = parse_state->msg_silent; - emsg_silent = parse_state->emsg_silent; - ex_pressedreturn = parse_state->ex_pressedreturn; - p_verbose = parse_state->p_verbose; - - if (parse_state->set_eventignore) { - set_string_option_direct( - (char_u *)"ei", -1, (char_u *)"all", OPT_FREE, SID_NONE); - } -} - -static void parse_state_from_global(parse_state_T *parse_state) -{ - memset(parse_state, 0, sizeof(*parse_state)); - parse_state->cmdmod = cmdmod; - parse_state->sandbox = sandbox; - parse_state->msg_silent = msg_silent; - parse_state->emsg_silent = emsg_silent; - parse_state->ex_pressedreturn = ex_pressedreturn; - parse_state->p_verbose = p_verbose; -} - -// -// Parse one Ex command. -// -// This has no side-effects, except for modifying parameters -// passed in by pointer. -// -// The `out` should be zeroed, and its `ea` member initialised, -// before calling this function. -// -static bool parse_one_cmd( - char_u **cmdlinep, - parse_state_T *const out, - LineGetter fgetline, - void *fgetline_cookie) -{ - exarg_T ea = { - .line1 = 1, - .line2 = 1, - }; - *out->eap = ea; - - // "#!anything" is handled like a comment. - if ((*cmdlinep)[0] == '#' && (*cmdlinep)[1] == '!') { - return false; - } - - /* - * Repeat until no more command modifiers are found. - */ - ea.cmd = *cmdlinep; - for (;; ) { - /* - * 1. Skip comment lines and leading white space and colons. - */ - while (*ea.cmd == ' ' - || *ea.cmd == '\t' - || *ea.cmd == ':') { - ea.cmd++; - } - - // in ex mode, an empty line works like :+ - if (*ea.cmd == NUL && exmode_active - && (getline_equal(fgetline, fgetline_cookie, getexmodeline) - || getline_equal(fgetline, fgetline_cookie, getexline)) - && curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) { - ea.cmd = (char_u *)"+"; - out->ex_pressedreturn = true; - } - - // ignore comment and empty lines - if (*ea.cmd == '"') { - return false; - } - if (*ea.cmd == NUL) { - out->ex_pressedreturn = true; - return false; - } - - /* - * 2. Handle command modifiers. - */ - char_u *p = skip_range(ea.cmd, NULL); - switch (*p) { - // When adding an entry, also modify cmd_exists(). - case 'a': if (!checkforcmd(&ea.cmd, "aboveleft", 3)) - break; - out->cmdmod.split |= WSP_ABOVE; - continue; - - case 'b': if (checkforcmd(&ea.cmd, "belowright", 3)) { - out->cmdmod.split |= WSP_BELOW; - continue; - } - if (checkforcmd(&ea.cmd, "browse", 3)) { - out->cmdmod.browse = true; - continue; - } - if (!checkforcmd(&ea.cmd, "botright", 2)) { - break; - } - out->cmdmod.split |= WSP_BOT; - continue; - - case 'c': if (!checkforcmd(&ea.cmd, "confirm", 4)) - break; - out->cmdmod.confirm = true; - continue; - - case 'k': if (checkforcmd(&ea.cmd, "keepmarks", 3)) { - out->cmdmod.keepmarks = true; - continue; - } - if (checkforcmd(&ea.cmd, "keepalt", 5)) { - out->cmdmod.keepalt = true; - continue; - } - if (checkforcmd(&ea.cmd, "keeppatterns", 5)) { - out->cmdmod.keeppatterns = true; - continue; - } - if (!checkforcmd(&ea.cmd, "keepjumps", 5)) { - break; - } - out->cmdmod.keepjumps = true; - continue; - - case 'f': { // only accept ":filter {pat} cmd" - char_u *reg_pat; - - if (!checkforcmd(&p, "filter", 4) || *p == NUL || ends_excmd(*p)) { - break; - } - if (*p == '!') { - out->cmdmod.filter_force = true; - p = skipwhite(p + 1); - if (*p == NUL || ends_excmd(*p)) { - break; - } - } - p = skip_vimgrep_pat(p, ®_pat, NULL); - if (p == NULL || *p == NUL) { - break; - } - out->cmdmod.filter_regmatch.regprog = vim_regcomp(reg_pat, RE_MAGIC); - if (out->cmdmod.filter_regmatch.regprog == NULL) { - break; - } - ea.cmd = p; - continue; - } - - // ":hide" and ":hide | cmd" are not modifiers - case 'h': if (p != ea.cmd || !checkforcmd(&p, "hide", 3) - || *p == NUL || ends_excmd(*p)) - break; - ea.cmd = p; - out->cmdmod.hide = true; - continue; - - case 'l': if (checkforcmd(&ea.cmd, "lockmarks", 3)) { - out->cmdmod.lockmarks = true; - continue; - } - - if (!checkforcmd(&ea.cmd, "leftabove", 5)) { - break; - } - out->cmdmod.split |= WSP_ABOVE; - continue; - - case 'n': - if (checkforcmd(&ea.cmd, "noautocmd", 3)) { - if (out->cmdmod.save_ei == NULL) { - // Set 'eventignore' to "all". Restore the - // existing option value later. - out->cmdmod.save_ei = vim_strsave(p_ei); - out->set_eventignore = true; - } - continue; - } - if (!checkforcmd(&ea.cmd, "noswapfile", 3)) { - break; - } - out->cmdmod.noswapfile = true; - continue; - - case 'r': if (!checkforcmd(&ea.cmd, "rightbelow", 6)) - break; - out->cmdmod.split |= WSP_BELOW; - continue; - - case 's': if (checkforcmd(&ea.cmd, "sandbox", 3)) { - if (!out->did_sandbox) { - out->sandbox++; - } - out->did_sandbox = true; - continue; - } - if (!checkforcmd(&ea.cmd, "silent", 3)) { - break; - } - if (out->save_msg_silent == -1) { - out->save_msg_silent = out->msg_silent; - } - out->msg_silent++; - if (*ea.cmd == '!' && !ascii_iswhite(ea.cmd[-1])) { - // ":silent!", but not "silent !cmd" - ea.cmd = skipwhite(ea.cmd + 1); - out->emsg_silent++; - out->did_esilent++; - } - continue; - - case 't': if (checkforcmd(&p, "tab", 3)) { - long tabnr = get_address( - &ea, &ea.cmd, ADDR_TABS, ea.skip, false, 1); - - if (tabnr == MAXLNUM) { - out->cmdmod.tab = tabpage_index(curtab) + 1; - } else { - if (tabnr < 0 || tabnr > LAST_TAB_NR) { - out->errormsg = (char_u *)_(e_invrange); - return false; - } - out->cmdmod.tab = tabnr + 1; - } - ea.cmd = p; - continue; - } - if (!checkforcmd(&ea.cmd, "topleft", 2)) { - break; - } - out->cmdmod.split |= WSP_TOP; - continue; - - case 'u': if (!checkforcmd(&ea.cmd, "unsilent", 3)) - break; - if (out->save_msg_silent == -1) { - out->save_msg_silent = out->msg_silent; - } - out->msg_silent = 0; - continue; - - case 'v': if (checkforcmd(&ea.cmd, "vertical", 4)) { - out->cmdmod.split |= WSP_VERT; - continue; - } - if (!checkforcmd(&p, "verbose", 4)) - break; - if (out->verbose_save < 0) { - out->verbose_save = out->p_verbose; - } - if (ascii_isdigit(*ea.cmd)) { - out->p_verbose = atoi((char *)ea.cmd); - } else { - out->p_verbose = 1; - } - ea.cmd = p; - continue; - } - break; - } - out->after_modifier = ea.cmd; - - // 3. Skip over the range to find the command. Let "p" point to after it. - // - // We need the command to know what kind of range it uses. - - out->cmd = ea.cmd; - ea.cmd = skip_range(ea.cmd, NULL); - if (*ea.cmd == '*') { - ea.cmd = skipwhite(ea.cmd + 1); - } - out->parsed_upto = find_command(&ea, NULL); - - *out->eap = ea; - - return true; -} - /* * Execute one Ex command. * @@ -1549,12 +1249,16 @@ static char_u * do_one_cmd(char_u **cmdlinep, linenr_T lnum; long n; char_u *errormsg = NULL; // error message + char_u *after_modifier = NULL; exarg_T ea; - int save_msg_scroll = msg_scroll; - parse_state_T parsed; + const int save_msg_scroll = msg_scroll; cmdmod_T save_cmdmod; const int save_reg_executing = reg_executing; + char_u *cmd; + memset(&ea, 0, sizeof(ea)); + ea.line1 = 1; + ea.line2 = 1; ex_nesting_level++; /* When the last file has not been edited :q has to be typed twice. */ @@ -1571,31 +1275,44 @@ static char_u * do_one_cmd(char_u **cmdlinep, * recursive calls. */ save_cmdmod = cmdmod; - memset(&cmdmod, 0, sizeof(cmdmod)); - parse_state_from_global(&parsed); - parsed.eap = &ea; - parsed.verbose_save = -1; - parsed.save_msg_silent = -1; - parsed.did_esilent = 0; - parsed.did_sandbox = false; - bool parse_success = parse_one_cmd(cmdlinep, &parsed, fgetline, cookie); - parse_state_to_global(&parsed); + // "#!anything" is handled like a comment. + if ((*cmdlinep)[0] == '#' && (*cmdlinep)[1] == '!') { + goto doend; + } + + // 1. Skip comment lines and leading white space and colons. + // 2. Handle command modifiers. - // Update locals from parse_one_cmd() - errormsg = parsed.errormsg; - p = parsed.parsed_upto; + // The "ea" structure holds the arguments that can be used. + ea.cmd = *cmdlinep; + ea.cmdlinep = cmdlinep; + ea.getline = fgetline; + ea.cookie = cookie; + ea.cstack = cstack; - if (!parse_success) { + if (parse_command_modifiers(&ea, &errormsg, false) == FAIL) { goto doend; } + after_modifier = ea.cmd; + ea.skip = (did_emsg || got_int || current_exception || (cstack->cs_idx >= 0 && !(cstack->cs_flags[cstack->cs_idx] & CSF_ACTIVE))); + // 3. Skip over the range to find the command. Let "p" point to after it. + // + // We need the command to know what kind of range it uses. + cmd = ea.cmd; + ea.cmd = skip_range(ea.cmd, NULL); + if (*ea.cmd == '*') { + ea.cmd = skipwhite(ea.cmd + 1); + } + p = find_command(&ea, NULL); + // Count this line for profiling if skip is TRUE. if (do_profiling == PROF_YES && (!ea.skip || cstack->cs_idx == 0 @@ -1665,8 +1382,8 @@ static char_u * do_one_cmd(char_u **cmdlinep, } } - ea.cmd = parsed.cmd; - if (parse_cmd_address(&ea, &errormsg) == FAIL) { + ea.cmd = cmd; + if (parse_cmd_address(&ea, &errormsg, false) == FAIL) { goto doend; } @@ -1746,8 +1463,8 @@ static char_u * do_one_cmd(char_u **cmdlinep, if (!(flags & DOCMD_VERBOSE)) { // If the modifier was parsed OK the error must be in the following // command - if (parsed.after_modifier != NULL) { - append_command(parsed.after_modifier); + if (after_modifier != NULL) { + append_command(after_modifier); } else { append_command(*cmdlinep); } @@ -1786,10 +1503,6 @@ static char_u * do_one_cmd(char_u **cmdlinep, errormsg = (char_u *)_(e_sandbox); goto doend; } - if (restricted != 0 && (ea.argt & RESTRICT)) { - errormsg = (char_u *)_("E981: Command not allowed in restricted mode"); - goto doend; - } if (!MODIFIABLE(curbuf) && (ea.argt & MODIFY) // allow :put in terminals && (!curbuf->terminal || ea.cmdidx != CMD_put)) { @@ -2222,22 +1935,15 @@ static char_u * do_one_cmd(char_u **cmdlinep, // The :try command saves the emsg_silent flag, reset it here when // ":silent! try" was used, it should only apply to :try itself. - if (ea.cmdidx == CMD_try && parsed.did_esilent > 0) { - emsg_silent -= parsed.did_esilent; + if (ea.cmdidx == CMD_try && ea.did_esilent > 0) { + emsg_silent -= ea.did_esilent; if (emsg_silent < 0) { emsg_silent = 0; } - parsed.did_esilent = 0; + ea.did_esilent = 0; } // 7. Execute the command. - // - // The "ea" structure holds the arguments that can be used. - ea.cmdlinep = cmdlinep; - ea.getline = fgetline; - ea.cookie = cookie; - ea.cstack = cstack; - if (IS_USER_CMDIDX(ea.cmdidx)) { /* * Execute a user-defined command. @@ -2293,9 +1999,285 @@ doend: ? cmdnames[(int)ea.cmdidx].cmd_name : (char_u *)NULL); - if (parsed.verbose_save >= 0) { - p_verbose = parsed.verbose_save; + undo_cmdmod(&ea, save_msg_scroll); + cmdmod = save_cmdmod; + reg_executing = save_reg_executing; + + if (ea.did_sandbox) { + sandbox--; + } + + if (ea.nextcmd && *ea.nextcmd == NUL) /* not really a next command */ + ea.nextcmd = NULL; + + --ex_nesting_level; + + return ea.nextcmd; +} + +// Parse and skip over command modifiers: +// - update eap->cmd +// - store flags in "cmdmod". +// - Set ex_pressedreturn for an empty command line. +// - set msg_silent for ":silent" +// - set 'eventignore' to "all" for ":noautocmd" +// - set p_verbose for ":verbose" +// - Increment "sandbox" for ":sandbox" +// When "skip_only" is true the global variables are not changed, except for +// "cmdmod". +// Return FAIL when the command is not to be executed. +// May set "errormsg" to an error message. +int parse_command_modifiers(exarg_T *eap, char_u **errormsg, bool skip_only) +{ + char_u *p; + + memset(&cmdmod, 0, sizeof(cmdmod)); + eap->verbose_save = -1; + eap->save_msg_silent = -1; + + // Repeat until no more command modifiers are found. + for (;; ) { + while (*eap->cmd == ' ' + || *eap->cmd == '\t' + || *eap->cmd == ':') { + eap->cmd++; + } + + // in ex mode, an empty line works like :+ + if (*eap->cmd == NUL && exmode_active + && (getline_equal(eap->getline, eap->cookie, getexmodeline) + || getline_equal(eap->getline, eap->cookie, getexline)) + && curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) { + eap->cmd = (char_u *)"+"; + if (!skip_only) { + ex_pressedreturn = true; + } + } + + // ignore comment and empty lines + if (*eap->cmd == '"') { + return FAIL; + } + if (*eap->cmd == NUL) { + if (!skip_only) { + ex_pressedreturn = true; + } + return FAIL; + } + + p = skip_range(eap->cmd, NULL); + switch (*p) { + // When adding an entry, also modify cmd_exists(). + case 'a': if (!checkforcmd(&eap->cmd, "aboveleft", 3)) + break; + cmdmod.split |= WSP_ABOVE; + continue; + + case 'b': if (checkforcmd(&eap->cmd, "belowright", 3)) { + cmdmod.split |= WSP_BELOW; + continue; + } + if (checkforcmd(&eap->cmd, "browse", 3)) { + cmdmod.browse = true; + continue; + } + if (!checkforcmd(&eap->cmd, "botright", 2)) { + break; + } + cmdmod.split |= WSP_BOT; + continue; + + case 'c': if (!checkforcmd(&eap->cmd, "confirm", 4)) + break; + cmdmod.confirm = true; + continue; + + case 'k': if (checkforcmd(&eap->cmd, "keepmarks", 3)) { + cmdmod.keepmarks = true; + continue; + } + if (checkforcmd(&eap->cmd, "keepalt", 5)) { + cmdmod.keepalt = true; + continue; + } + if (checkforcmd(&eap->cmd, "keeppatterns", 5)) { + cmdmod.keeppatterns = true; + continue; + } + if (!checkforcmd(&eap->cmd, "keepjumps", 5)) { + break; + } + cmdmod.keepjumps = true; + continue; + + case 'f': { // only accept ":filter {pat} cmd" + char_u *reg_pat; + + if (!checkforcmd(&p, "filter", 4) || *p == NUL || ends_excmd(*p)) { + break; + } + if (*p == '!') { + cmdmod.filter_force = true; + p = skipwhite(p + 1); + if (*p == NUL || ends_excmd(*p)) { + break; + } + } + if (skip_only) { + p = skip_vimgrep_pat(p, NULL, NULL); + } else { + // NOTE: This puts a NUL after the pattern. + p = skip_vimgrep_pat(p, ®_pat, NULL); + } + if (p == NULL || *p == NUL) { + break; + } + if (!skip_only) { + cmdmod.filter_regmatch.regprog = vim_regcomp(reg_pat, RE_MAGIC); + if (cmdmod.filter_regmatch.regprog == NULL) { + break; + } + } + eap->cmd = p; + continue; + } + + // ":hide" and ":hide | cmd" are not modifiers + case 'h': if (p != eap->cmd || !checkforcmd(&p, "hide", 3) + || *p == NUL || ends_excmd(*p)) + break; + eap->cmd = p; + cmdmod.hide = true; + continue; + + case 'l': if (checkforcmd(&eap->cmd, "lockmarks", 3)) { + cmdmod.lockmarks = true; + continue; + } + + if (!checkforcmd(&eap->cmd, "leftabove", 5)) { + break; + } + cmdmod.split |= WSP_ABOVE; + continue; + + case 'n': + if (checkforcmd(&eap->cmd, "noautocmd", 3)) { + if (cmdmod.save_ei == NULL && !skip_only) { + // Set 'eventignore' to "all". Restore the + // existing option value later. + cmdmod.save_ei = vim_strsave(p_ei); + set_string_option_direct((char_u *)"ei", -1, + (char_u *)"all", OPT_FREE, SID_NONE); + } + continue; + } + if (!checkforcmd(&eap->cmd, "noswapfile", 3)) { + break; + } + cmdmod.noswapfile = true; + continue; + + case 'r': if (!checkforcmd(&eap->cmd, "rightbelow", 6)) + break; + cmdmod.split |= WSP_BELOW; + continue; + + case 's': if (checkforcmd(&eap->cmd, "sandbox", 3)) { + if (!skip_only) { + if (!eap->did_sandbox) { + sandbox++; + } + eap->did_sandbox = true; + } + continue; + } + if (!checkforcmd(&eap->cmd, "silent", 3)) { + break; + } + if (!skip_only) { + if (eap->save_msg_silent == -1) { + eap->save_msg_silent = msg_silent; + } + msg_silent++; + } + if (*eap->cmd == '!' && !ascii_iswhite(eap->cmd[-1])) { + // ":silent!", but not "silent !cmd" + eap->cmd = skipwhite(eap->cmd + 1); + if (!skip_only) { + emsg_silent++; + eap->did_esilent++; + } + } + continue; + + case 't': if (checkforcmd(&p, "tab", 3)) { + if (!skip_only) { + long tabnr = get_address( + eap, &eap->cmd, ADDR_TABS, eap->skip, skip_only, false, 1); + + if (tabnr == MAXLNUM) { + cmdmod.tab = tabpage_index(curtab) + 1; + } else { + if (tabnr < 0 || tabnr > LAST_TAB_NR) { + *errormsg = (char_u *)_(e_invrange); + return false; + } + cmdmod.tab = tabnr + 1; + } + } + eap->cmd = p; + continue; + } + if (!checkforcmd(&eap->cmd, "topleft", 2)) { + break; + } + cmdmod.split |= WSP_TOP; + continue; + + case 'u': if (!checkforcmd(&eap->cmd, "unsilent", 3)) + break; + if (!skip_only) { + if (eap->save_msg_silent == -1) { + eap->save_msg_silent = msg_silent; + } + msg_silent = 0; + } + continue; + + case 'v': if (checkforcmd(&eap->cmd, "vertical", 4)) { + cmdmod.split |= WSP_VERT; + continue; + } + if (!checkforcmd(&p, "verbose", 4)) + break; + if (!skip_only) { + if (eap->verbose_save < 0) { + eap->verbose_save = p_verbose; + } + if (ascii_isdigit(*eap->cmd)) { + p_verbose = atoi((char *)eap->cmd); + } else { + p_verbose = 1; + } + } + eap->cmd = p; + continue; + } + break; } + + return OK; +} + +// Undo and free contents of "cmdmod". +static void undo_cmdmod(const exarg_T *eap, int save_msg_scroll) + FUNC_ATTR_NONNULL_ALL +{ + if (eap->verbose_save >= 0) { + p_verbose = eap->verbose_save; + } + if (cmdmod.save_ei != NULL) { /* Restore 'eventignore' to the value before ":noautocmd". */ set_string_option_direct((char_u *)"ei", -1, cmdmod.save_ei, @@ -2303,20 +2285,15 @@ doend: free_string_option(cmdmod.save_ei); } - if (cmdmod.filter_regmatch.regprog != NULL) { - vim_regfree(cmdmod.filter_regmatch.regprog); - } - - cmdmod = save_cmdmod; - reg_executing = save_reg_executing; + vim_regfree(cmdmod.filter_regmatch.regprog); - if (parsed.save_msg_silent != -1) { + if (eap->save_msg_silent != -1) { // messages could be enabled for a serious error, need to check if the // counters don't become negative - if (!did_emsg || msg_silent > parsed.save_msg_silent) { - msg_silent = parsed.save_msg_silent; + if (!did_emsg || msg_silent > eap->save_msg_silent) { + msg_silent = eap->save_msg_silent; } - emsg_silent -= parsed.did_esilent; + emsg_silent -= eap->did_esilent; if (emsg_silent < 0) { emsg_silent = 0; } @@ -2324,27 +2301,19 @@ doend: // message is actually displayed. msg_scroll = save_msg_scroll; - /* "silent reg" or "silent echo x" inside "redir" leaves msg_col - * somewhere in the line. Put it back in the first column. */ - if (redirecting()) + // "silent reg" or "silent echo x" inside "redir" leaves msg_col + // somewhere in the line. Put it back in the first column. + if (redirecting()) { msg_col = 0; + } } - - if (parsed.did_sandbox) { - sandbox--; - } - - if (ea.nextcmd && *ea.nextcmd == NUL) /* not really a next command */ - ea.nextcmd = NULL; - - --ex_nesting_level; - - return ea.nextcmd; } + // Parse the address range, if any, in "eap". +// May set the last search pattern, unless "silent" is true. // Return FAIL and set "errormsg" or return OK. -int parse_cmd_address(exarg_T *eap, char_u **errormsg) +int parse_cmd_address(exarg_T *eap, char_u **errormsg, bool silent) FUNC_ATTR_NONNULL_ALL { int address_count = 1; @@ -2382,7 +2351,7 @@ int parse_cmd_address(exarg_T *eap, char_u **errormsg) break; } eap->cmd = skipwhite(eap->cmd); - lnum = get_address(eap, &eap->cmd, eap->addr_type, eap->skip, + lnum = get_address(eap, &eap->cmd, eap->addr_type, eap->skip, silent, eap->addr_count == 0, address_count++); if (eap->cmd == NULL) { // error detected return FAIL; @@ -2427,6 +2396,7 @@ int parse_cmd_address(exarg_T *eap, char_u **errormsg) } break; case ADDR_TABS_RELATIVE: + case ADDR_OTHER: *errormsg = (char_u *)_(e_invrange); return FAIL; case ADDR_ARGUMENTS: @@ -3725,18 +3695,18 @@ char_u *skip_range( return (char_u *)cmd; } -/* - * get a single EX address - * - * Set ptr to the next character after the part that was interpreted. - * Set ptr to NULL when an error is encountered. - * - * Return MAXLNUM when no Ex address was found. - */ +// Get a single EX address +// +// Set ptr to the next character after the part that was interpreted. +// Set ptr to NULL when an error is encountered. +// This may set the last used search pattern. +// +// Return MAXLNUM when no Ex address was found. static linenr_T get_address(exarg_T *eap, char_u **ptr, int addr_type, // flag: one of ADDR_LINES, ... int skip, // only skip the address, don't use it + bool silent, // no errors or side effects int to_other_file, // flag: may jump to other file int address_count) // 1 for first, >1 after comma { @@ -3868,13 +3838,15 @@ static linenr_T get_address(exarg_T *eap, if (*cmd == c) ++cmd; } else { - pos = curwin->w_cursor; /* save curwin->w_cursor */ - /* - * When '/' or '?' follows another address, start - * from there. - */ - if (lnum != MAXLNUM) + int flags; + + pos = curwin->w_cursor; // save curwin->w_cursor + + // When '/' or '?' follows another address, start from + // there. + if (lnum != MAXLNUM) { curwin->w_cursor.lnum = lnum; + } // Start a forward search at the end of the line (unless // before the first line). @@ -3888,7 +3860,8 @@ static linenr_T get_address(exarg_T *eap, curwin->w_cursor.col = 0; } searchcmdlen = 0; - if (!do_search(NULL, c, cmd, 1L, SEARCH_HIS | SEARCH_MSG, NULL)) { + flags = silent ? 0 : SEARCH_HIS | SEARCH_MSG; + if (!do_search(NULL, c, cmd, 1L, flags, NULL)) { curwin->w_cursor = pos; cmd = NULL; goto error; @@ -4469,6 +4442,9 @@ void separate_nextcmd(exarg_T *eap) else if (p[0] == '`' && p[1] == '=' && (eap->argt & XFILE)) { p += 2; (void)skip_expr(&p); + if (*p == NUL) { // stop at NUL after CTRL-V + break; + } } /* Check for '"': start of comment or '|': next command */ /* :@" does not start a comment! @@ -4949,7 +4925,7 @@ check_more( int n = ARGCOUNT - curwin->w_arg_idx - 1; if (!forceit && only_one_window() - && ARGCOUNT > 1 && !arg_had_last && n >= 0 && quitmore == 0) { + && ARGCOUNT > 1 && !arg_had_last && n > 0 && quitmore == 0) { if (message) { if ((p_confirm || cmdmod.confirm) && curbuf->b_fname != NULL) { char_u buff[DIALOG_MSG_SIZE]; @@ -5026,8 +5002,13 @@ static int uc_add_command(char_u *name, size_t name_len, char_u *rep, } if (cmp == 0) { - if (!force) { - EMSG(_("E174: Command already exists: add ! to replace it")); + // Command can be replaced with "command!" and when sourcing the + // same script again, but only once. + if (!force + && (cmd->uc_script_ctx.sc_sid != current_sctx.sc_sid + || cmd->uc_script_ctx.sc_seq == current_sctx.sc_seq)) { + EMSG2(_("E174: Command already exists: add ! to replace it: %s"), + name); goto fail; } @@ -5076,16 +5057,18 @@ fail: static struct { int expand; char *name; + char *shortname; } addr_type_complete[] = { - { ADDR_ARGUMENTS, "arguments" }, - { ADDR_LINES, "lines" }, - { ADDR_LOADED_BUFFERS, "loaded_buffers" }, - { ADDR_TABS, "tabs" }, - { ADDR_BUFFERS, "buffers" }, - { ADDR_WINDOWS, "windows" }, - { ADDR_QUICKFIX, "quickfix" }, - { -1, NULL } + { ADDR_ARGUMENTS, "arguments", "arg" }, + { ADDR_LINES, "lines", "line" }, + { ADDR_LOADED_BUFFERS, "loaded_buffers", "load" }, + { ADDR_TABS, "tabs", "tab" }, + { ADDR_BUFFERS, "buffers", "buf" }, + { ADDR_WINDOWS, "windows", "win" }, + { ADDR_QUICKFIX, "quickfix", "qf" }, + { ADDR_OTHER, "other", "?" }, + { -1, NULL, NULL } }; /* @@ -5170,7 +5153,7 @@ static void uc_list(char_u *name, size_t name_len) // Put out the title first time if (!found) { MSG_PUTS_TITLE(_("\n Name Args Address " - "Complete Definition")); + "Complete Definition")); } found = true; msg_putchar('\n'); @@ -5256,13 +5239,13 @@ static void uc_list(char_u *name, size_t name_len) do { IObuff[len++] = ' '; - } while (len < 9 - over); + } while (len < 8 - over); // Address Type for (j = 0; addr_type_complete[j].expand != -1; j++) { if (addr_type_complete[j].expand != ADDR_LINES && addr_type_complete[j].expand == cmd->uc_addr_type) { - STRCPY(IObuff + len, addr_type_complete[j].name); + STRCPY(IObuff + len, addr_type_complete[j].shortname); len += (int)STRLEN(IObuff + len); break; } @@ -5281,13 +5264,13 @@ static void uc_list(char_u *name, size_t name_len) do { IObuff[len++] = ' '; - } while (len < 24 - over); + } while (len < 25 - over); IObuff[len] = '\0'; msg_outtrans(IObuff); msg_outtrans_special(cmd->uc_rep, false, - name_len == 0 ? Columns - 46 : 0); + name_len == 0 ? Columns - 47 : 0); if (p_verbose > 0) { last_set_msg(cmd->uc_script_ctx); } @@ -5416,7 +5399,7 @@ invalid_count: if (parse_addr_type_arg(val, (int)vallen, argt, addr_type_arg) == FAIL) { return FAIL; } - if (addr_type_arg != ADDR_LINES) { + if (*addr_type_arg != ADDR_LINES) { *argt |= (ZEROR | NOTADR); } } else { @@ -6637,25 +6620,22 @@ static void ex_hide(exarg_T *eap) /// ":stop" and ":suspend": Suspend Vim. static void ex_stop(exarg_T *eap) { - // Disallow suspending in restricted mode (-Z) - if (!check_restricted()) { - if (!eap->forceit) { - autowrite_all(); - } - apply_autocmds(EVENT_VIMSUSPEND, NULL, NULL, false, NULL); + if (!eap->forceit) { + autowrite_all(); + } + apply_autocmds(EVENT_VIMSUSPEND, NULL, NULL, false, NULL); - // TODO(bfredl): the TUI should do this on suspend - ui_cursor_goto(Rows - 1, 0); - ui_call_grid_scroll(1, 0, Rows, 0, Columns, 1, 0); - ui_flush(); - ui_call_suspend(); // call machine specific function + // TODO(bfredl): the TUI should do this on suspend + ui_cursor_goto(Rows - 1, 0); + ui_call_grid_scroll(1, 0, Rows, 0, Columns, 1, 0); + ui_flush(); + ui_call_suspend(); // call machine specific function - ui_flush(); - maketitle(); - resettitle(); // force updating the title - ui_refresh(); // may have resized window - apply_autocmds(EVENT_VIMRESUME, NULL, NULL, false, NULL); - } + ui_flush(); + maketitle(); + resettitle(); // force updating the title + ui_refresh(); // may have resized window + apply_autocmds(EVENT_VIMRESUME, NULL, NULL, false, NULL); } // ":exit", ":xit" and ":wq": Write file and quite the current window. @@ -6945,8 +6925,9 @@ void ex_splitview(exarg_T *eap) { win_T *old_curwin = curwin; char_u *fname = NULL; - - + const bool use_tab = eap->cmdidx == CMD_tabedit + || eap->cmdidx == CMD_tabfind + || eap->cmdidx == CMD_tabnew; /* A ":split" in the quickfix window works like ":new". Don't want two * quickfix windows. But it's OK when doing ":tab split". */ @@ -6968,9 +6949,7 @@ void ex_splitview(exarg_T *eap) /* * Either open new tab page or split the window. */ - if (eap->cmdidx == CMD_tabedit - || eap->cmdidx == CMD_tabfind - || eap->cmdidx == CMD_tabnew) { + if (use_tab) { if (win_new_tabpage(cmdmod.tab != 0 ? cmdmod.tab : eap->addr_count == 0 ? 0 : (int)eap->line2 + 1, eap->arg) != FAIL) { do_exedit(eap, old_curwin); @@ -7155,14 +7134,14 @@ static void ex_resize(exarg_T *eap) n = atol((char *)eap->arg); if (cmdmod.split & WSP_VERT) { if (*eap->arg == '-' || *eap->arg == '+') { - n += curwin->w_width; + n += wp->w_width; } else if (n == 0 && eap->arg[0] == NUL) { // default is very wide n = Columns; } win_setwidth_win(n, wp); } else { if (*eap->arg == '-' || *eap->arg == '+') { - n += curwin->w_height; + n += wp->w_height; } else if (n == 0 && eap->arg[0] == NUL) { // default is very high n = Rows-1; } @@ -7396,7 +7375,7 @@ static void ex_syncbind(exarg_T *eap) else scrolldown(-y, TRUE); curwin->w_scbind_pos = topline; - redraw_later(VALID); + redraw_later(curwin, VALID); cursor_correct(); curwin->w_redr_status = TRUE; } @@ -7520,7 +7499,7 @@ void post_chdir(CdScope scope, bool trigger_dirchanged) shorten_fnames(true); if (trigger_dirchanged) { - do_autocmd_dirchanged(cwd, scope); + do_autocmd_dirchanged(cwd, scope, false); } } @@ -7788,7 +7767,7 @@ static void ex_put(exarg_T *eap) */ static void ex_copymove(exarg_T *eap) { - long n = get_address(eap, &eap->arg, eap->addr_type, false, false, 1); + long n = get_address(eap, &eap->arg, eap->addr_type, false, false, false, 1); if (eap->arg == NULL) { // error detected eap->nextcmd = NULL; return; @@ -8521,7 +8500,7 @@ static void ex_pedit(exarg_T *eap) if (curwin != curwin_save && win_valid(curwin_save)) { // Return cursor to where we were validate_cursor(); - redraw_later(VALID); + redraw_later(curwin, VALID); win_enter(curwin_save, true); } g_do_tagpreview = 0; @@ -8586,6 +8565,24 @@ static void ex_tag_cmd(exarg_T *eap, char_u *name) eap->forceit, TRUE); } +enum { + SPEC_PERC = 0, + SPEC_HASH, + SPEC_CWORD, + SPEC_CCWORD, + SPEC_CEXPR, + SPEC_CFILE, + SPEC_SFILE, + SPEC_SLNUM, + SPEC_STACK, + SPEC_AFILE, + SPEC_ABUF, + SPEC_AMATCH, + SPEC_SFLNUM, + SPEC_SID, + // SPEC_CLIENT, +}; + /* * Check "str" for starting with a special cmdline variable. * If found return one of the SPEC_ values and set "*usedlen" to the length of @@ -8596,30 +8593,21 @@ ssize_t find_cmdline_var(const char_u *src, size_t *usedlen) { size_t len; static char *(spec_str[]) = { - "%", -#define SPEC_PERC 0 - "#", -#define SPEC_HASH (SPEC_PERC + 1) - "<cword>", // cursor word -#define SPEC_CWORD (SPEC_HASH + 1) - "<cWORD>", // cursor WORD -#define SPEC_CCWORD (SPEC_CWORD + 1) - "<cexpr>", // expr under cursor -#define SPEC_CEXPR (SPEC_CCWORD + 1) - "<cfile>", // cursor path name -#define SPEC_CFILE (SPEC_CEXPR + 1) - "<sfile>", // ":so" file name -#define SPEC_SFILE (SPEC_CFILE + 1) - "<slnum>", // ":so" file line number -#define SPEC_SLNUM (SPEC_SFILE + 1) - "<afile>", // autocommand file name -#define SPEC_AFILE (SPEC_SLNUM + 1) - "<abuf>", // autocommand buffer number -#define SPEC_ABUF (SPEC_AFILE + 1) - "<amatch>", // autocommand match name -#define SPEC_AMATCH (SPEC_ABUF + 1) - "<sflnum>", // script file line number -#define SPEC_SFLNUM (SPEC_AMATCH + 1) + [SPEC_PERC] = "%", + [SPEC_HASH] = "#", + [SPEC_CWORD] = "<cword>", // cursor word + [SPEC_CCWORD] = "<cWORD>", // cursor WORD + [SPEC_CEXPR] = "<cexpr>", // expr under cursor + [SPEC_CFILE] = "<cfile>", // cursor path name + [SPEC_SFILE] = "<sfile>", // ":so" file name + [SPEC_SLNUM] = "<slnum>", // ":so" file line number + [SPEC_STACK] = "<stack>", // call stack + [SPEC_AFILE] = "<afile>", // autocommand file name + [SPEC_ABUF] = "<abuf>", // autocommand buffer number + [SPEC_AMATCH] = "<amatch>", // autocommand match name + [SPEC_SFLNUM] = "<sflnum>", // script file line number + [SPEC_SID] = "<SID>", // script ID: <SNR>123_ + // [SPEC_CLIENT] = "<client>", }; for (size_t i = 0; i < ARRAY_SIZE(spec_str); ++i) { @@ -8866,6 +8854,16 @@ eval_vars ( result = (char_u *)strbuf; break; + case SPEC_SID: + if (current_sctx.sc_sid <= 0) { + *errormsg = (char_u *)_(e_usingsid); + return NULL; + } + snprintf(strbuf, sizeof(strbuf), "<SNR>%" PRIdSCID "_", + current_sctx.sc_sid); + result = (char_u *)strbuf; + break; + default: // should not happen *errormsg = (char_u *)""; @@ -9309,14 +9307,17 @@ static void ex_match(exarg_T *eap) static void ex_fold(exarg_T *eap) { if (foldManualAllowed(true)) { - foldCreate(curwin, eap->line1, eap->line2); + pos_T start = { eap->line1, 1, 0 }; + pos_T end = { eap->line2, 1, 0 }; + foldCreate(curwin, start, end); } } static void ex_foldopen(exarg_T *eap) { - opFoldRange(eap->line1, eap->line2, eap->cmdidx == CMD_foldopen, - eap->forceit, FALSE); + pos_T start = { eap->line1, 1, 0 }; + pos_T end = { eap->line2, 1, 0 }; + opFoldRange(start, end, eap->cmdidx == CMD_foldopen, eap->forceit, false); } static void ex_folddo(exarg_T *eap) diff --git a/src/nvim/ex_eval.c b/src/nvim/ex_eval.c index 81274fcf2a..0c7562980a 100644 --- a/src/nvim/ex_eval.c +++ b/src/nvim/ex_eval.c @@ -87,17 +87,16 @@ */ static int cause_abort = FALSE; -/* - * Return TRUE when immediately aborting on error, or when an interrupt - * occurred or an exception was thrown but not caught. Use for ":{range}call" - * to check whether an aborted function that does not handle a range itself - * should be called again for the next line in the range. Also used for - * cancelling expression evaluation after a function call caused an immediate - * abort. Note that the first emsg() call temporarily resets "force_abort" - * until the throw point for error messages has been reached. That is, during - * cancellation of an expression evaluation after an aborting function call or - * due to a parsing error, aborting() always returns the same value. - */ +// Return true when immediately aborting on error, or when an interrupt +// occurred or an exception was thrown but not caught. Use for ":{range}call" +// to check whether an aborted function that does not handle a range itself +// should be called again for the next line in the range. Also used for +// cancelling expression evaluation after a function call caused an immediate +// abort. Note that the first emsg() call temporarily resets "force_abort" +// until the throw point for error messages has been reached. That is, during +// cancellation of an expression evaluation after an aborting function call or +// due to a parsing error, aborting() always returns the same value. +// "got_int" is also set by calling interrupt(). int aborting(void) { return (did_emsg && force_abort) || got_int || current_exception; @@ -139,16 +138,15 @@ int aborted_in_try(void) return force_abort; } -/* - * cause_errthrow(): Cause a throw of an error exception if appropriate. - * Return TRUE if the error message should not be displayed by emsg(). - * Sets "ignore", if the emsg() call should be ignored completely. - * - * When several messages appear in the same command, the first is usually the - * most specific one and used as the exception value. The "severe" flag can be - * set to TRUE, if a later but severer message should be used instead. - */ -int cause_errthrow(char_u *mesg, int severe, int *ignore) +// cause_errthrow(): Cause a throw of an error exception if appropriate. +// Return true if the error message should not be displayed by emsg(). +// Sets "ignore", if the emsg() call should be ignored completely. +// +// When several messages appear in the same command, the first is usually the +// most specific one and used as the exception value. The "severe" flag can be +// set to true, if a later but severer message should be used instead. +bool cause_errthrow(const char_u *mesg, bool severe, bool *ignore) + FUNC_ATTR_NONNULL_ALL { struct msglist *elem; struct msglist **plist; @@ -159,8 +157,9 @@ int cause_errthrow(char_u *mesg, int severe, int *ignore) * level. Also when no exception can be thrown. The message will be * displayed by emsg(). */ - if (suppress_errthrow) - return FALSE; + if (suppress_errthrow) { + return false; + } /* * If emsg() has not been called previously, temporarily reset @@ -196,8 +195,8 @@ int cause_errthrow(char_u *mesg, int severe, int *ignore) * not replaced by an interrupt message error exception. */ if (mesg == (char_u *)_(e_interr)) { - *ignore = TRUE; - return TRUE; + *ignore = true; + return true; } /* @@ -232,8 +231,8 @@ int cause_errthrow(char_u *mesg, int severe, int *ignore) * catch clause; just finally clauses are executed before the script * is terminated. */ - return FALSE; - } else + return false; + } else // NOLINT(readability/braces) #endif { /* @@ -272,7 +271,7 @@ int cause_errthrow(char_u *mesg, int severe, int *ignore) (*msg_list)->throw_msg = tmsg; } } - return TRUE; + return true; } } diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 93684e8606..53feffd2d7 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -137,6 +137,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. typedef struct { colnr_T vs_curswant; colnr_T vs_leftcol; @@ -146,6 +147,19 @@ typedef struct { int vs_empty_rows; } viewstate_T; +// Struct to store the state of 'incsearch' highlighting. +typedef struct { + pos_T search_start; // where 'incsearch' starts searching + pos_T save_cursor; + viewstate_T init_viewstate; + viewstate_T old_viewstate; + pos_T match_start; + pos_T match_end; + bool did_incsearch; + bool incsearch_postponed; + int magic_save; +} incsearch_state_T; + typedef struct command_line_state { VimState state; int firstc; @@ -159,14 +173,7 @@ typedef struct command_line_state { int save_hiscnt; // history line before attempting // to jump to next match int histype; // history type to be used - pos_T search_start; // where 'incsearch' starts searching - pos_T save_cursor; - viewstate_T init_viewstate; - viewstate_T old_viewstate; - pos_T match_start; - pos_T match_end; - int did_incsearch; - int incsearch_postponed; + incsearch_state_T is_state; int did_wild_list; // did wild_list() recently int wim_index; // index in wim_flags[] int res; @@ -252,6 +259,395 @@ static void restore_viewstate(viewstate_T *vs) curwin->w_empty_rows = vs->vs_empty_rows; } +static void init_incsearch_state(incsearch_state_T *s) +{ + s->match_start = curwin->w_cursor; + s->did_incsearch = false; + s->incsearch_postponed = false; + s->magic_save = p_magic; + 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); +} + +// Return true when 'incsearch' highlighting is to be done. +// Sets search_first_line and search_last_line to the address range. +static bool do_incsearch_highlighting(int firstc, incsearch_state_T *s, + int *skiplen, int *patlen) + FUNC_ATTR_NONNULL_ALL +{ + char_u *cmd; + cmdmod_T save_cmdmod = cmdmod; + char_u *p; + bool delim_optional = false; + int delim; + char_u *end; + char_u *dummy; + exarg_T ea; + pos_T save_cursor; + bool use_last_pat; + bool retval = false; + + *skiplen = 0; + *patlen = ccline.cmdlen; + + if (!p_is || cmd_silent) { + return false; + } + + // by default search all lines + search_first_line = 0; + search_last_line = MAXLNUM; + + if (firstc == '/' || firstc == '?') { + return true; + } + if (firstc != ':') { + return false; + } + + emsg_off++; + memset(&ea, 0, sizeof(ea)); + ea.line1 = 1; + ea.line2 = 1; + ea.cmd = ccline.cmdbuff; + ea.addr_type = ADDR_LINES; + + parse_command_modifiers(&ea, &dummy, true); + cmdmod = save_cmdmod; + + cmd = skip_range(ea.cmd, NULL); + if (vim_strchr((char_u *)"sgvl", *cmd) == NULL) { + goto theend; + } + + // Skip over "substitute" to find the pattern separator. + for (p = cmd; ASCII_ISALPHA(*p); p++) {} + if (*skipwhite(p) == NUL) { + goto theend; + } + + if (STRNCMP(cmd, "substitute", p - cmd) == 0 + || STRNCMP(cmd, "smagic", p - cmd) == 0 + || STRNCMP(cmd, "snomagic", MAX(p - cmd, 3)) == 0 + || STRNCMP(cmd, "vglobal", p - cmd) == 0) { + if (*cmd == 's' && cmd[1] == 'm') { + p_magic = true; + } else if (*cmd == 's' && cmd[1] == 'n') { + p_magic = false; + } + } else if (STRNCMP(cmd, "sort", MAX(p - cmd, 3)) == 0) { + // skip over ! and flags + if (*p == '!') { + p = skipwhite(p + 1); + } + while (ASCII_ISALPHA(*(p = skipwhite(p)))) { + p++; + } + if (*p == NUL) { + goto theend; + } + } else if (STRNCMP(cmd, "vimgrep", MAX(p - cmd, 3)) == 0 + || STRNCMP(cmd, "vimgrepadd", MAX(p - cmd, 8)) == 0 + || STRNCMP(cmd, "lvimgrep", MAX(p - cmd, 2)) == 0 + || STRNCMP(cmd, "lvimgrepadd", MAX(p - cmd, 9)) == 0 + || STRNCMP(cmd, "global", p - cmd) == 0) { + // skip over "!/". + if (*p == '!') { + p++; + if (*skipwhite(p) == NUL) { + goto theend; + } + } + if (*cmd != 'g') { + delim_optional = true; + } + } else { + goto theend; + } + + p = skipwhite(p); + delim = (delim_optional && vim_isIDc(*p)) ? ' ' : *p++; + end = skip_regexp(p, delim, p_magic, NULL); + + use_last_pat = end == p && *end == delim; + if (end == p && !use_last_pat) { + goto theend; + } + + // Don't do 'hlsearch' highlighting if the pattern matches everything. + if (!use_last_pat) { + char_u c = *end; + int empty; + + *end = NUL; + empty = empty_pattern(p); + *end = c; + if (empty) { + goto theend; + } + } + + // found a non-empty pattern or // + *skiplen = (int)(p - ccline.cmdbuff); + *patlen = (int)(end - p); + + // parse the address range + save_cursor = curwin->w_cursor; + curwin->w_cursor = s->search_start; + parse_cmd_address(&ea, &dummy, true); + if (ea.addr_count > 0) { + // Allow for reverse match. + if (ea.line2 < ea.line1) { + search_first_line = ea.line2; + search_last_line = ea.line1; + } else { + search_first_line = ea.line1; + search_last_line = ea.line2; + } + } else if (cmd[0] == 's' && cmd[1] != 'o') { + // :s defaults to the current line + search_first_line = curwin->w_cursor.lnum; + search_last_line = curwin->w_cursor.lnum; + } + + curwin->w_cursor = save_cursor; + retval = true; +theend: + emsg_off--; + return retval; +} + +// May do 'incsearch' highlighting if desired. +static void may_do_incsearch_highlighting(int firstc, long count, + incsearch_state_T *s) +{ + pos_T end_pos; + proftime_T tm; + searchit_arg_T sia; + int skiplen, patlen; + char_u next_char; + char_u use_last_pat; + + // Parsing range may already set the last search pattern. + // NOTE: must call restore_last_search_pattern() before returning! + save_last_search_pattern(); + + if (!do_incsearch_highlighting(firstc, s, &skiplen, &patlen)) { + restore_last_search_pattern(); + finish_incsearch_highlighting(false, s, true); + return; + } + + // if there is a character waiting, search and redraw later + if (char_avail()) { + restore_last_search_pattern(); + s->incsearch_postponed = true; + return; + } + s->incsearch_postponed = false; + + if (search_first_line == 0) { + // start at the original cursor position + curwin->w_cursor = s->search_start; + } else if (search_first_line > curbuf->b_ml.ml_line_count) { + // start after the last line + curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; + curwin->w_cursor.col = MAXCOL; + } else { + // start at the first line in the range + curwin->w_cursor.lnum = search_first_line; + curwin->w_cursor.col = 0; + } + int found; // do_search() result + + // Use the previous pattern for ":s//". + next_char = ccline.cmdbuff[skiplen + patlen]; + use_last_pat = patlen == 0 && skiplen > 0 + && ccline.cmdbuff[skiplen - 1] == next_char; + + // If there is no pattern, don't do anything. + if (patlen == 0 && !use_last_pat) { + found = 0; + set_no_hlsearch(true); // turn off previous highlight + redraw_all_later(SOME_VALID); + } else { + int search_flags = SEARCH_OPT + SEARCH_NOOF + SEARCH_PEEK; + ui_busy_start(); + ui_flush(); + emsg_off++; // So it doesn't beep if bad expr + // Set the time limit to half a second. + tm = profile_setlimit(500L); + if (!p_hls) { + search_flags += SEARCH_KEEP; + } + if (search_first_line != 0) { + search_flags += SEARCH_START; + } + ccline.cmdbuff[skiplen + patlen] = NUL; + memset(&sia, 0, sizeof(sia)); + sia.sa_tm = &tm; + found = do_search(NULL, firstc == ':' ? '/' : firstc, + ccline.cmdbuff + skiplen, count, + search_flags, &sia); + ccline.cmdbuff[skiplen + patlen] = next_char; + emsg_off--; + if (curwin->w_cursor.lnum < search_first_line + || curwin->w_cursor.lnum > search_last_line) { + // match outside of address range + found = 0; + curwin->w_cursor = s->search_start; + } + + // if interrupted while searching, behave like it failed + if (got_int) { + (void)vpeekc(); // remove <C-C> from input stream + got_int = false; // don't abandon the command line + found = 0; + } else if (char_avail()) { + // cancelled searching because a char was typed + s->incsearch_postponed = true; + } + ui_busy_stop(); + } + + if (found != 0) { + highlight_match = true; // highlight position + } else { + highlight_match = false; // remove highlight + } + + // 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); + changed_cline_bef_curs(); + update_topline(); + + if (found != 0) { + pos_T save_pos = curwin->w_cursor; + + s->match_start = curwin->w_cursor; + set_search_match(&curwin->w_cursor); + validate_cursor(); + end_pos = curwin->w_cursor; + s->match_end = end_pos; + curwin->w_cursor = save_pos; + } else { + end_pos = curwin->w_cursor; // shutup gcc 4 + } + // + // Disable 'hlsearch' highlighting if the pattern matches + // everything. Avoids a flash when typing "foo\|". + if (!use_last_pat) { + next_char = ccline.cmdbuff[skiplen + patlen]; + ccline.cmdbuff[skiplen + patlen] = NUL; + if (empty_pattern(ccline.cmdbuff) && !no_hlsearch) { + redraw_all_later(SOME_VALID); + set_no_hlsearch(true); + } + ccline.cmdbuff[skiplen + patlen] = next_char; + } + + validate_cursor(); + // May redraw the status line to show the cursor position. + if (p_ru && curwin->w_status_height > 0) { + curwin->w_redr_status = true; + } + + update_screen(SOME_VALID); + highlight_match = false; + restore_last_search_pattern(); + + // Leave it at the end to make CTRL-R CTRL-W work. But not when beyond the + // end of the pattern, e.g. for ":s/pat/". + if (ccline.cmdbuff[skiplen + patlen] != NUL) { + curwin->w_cursor = s->search_start; + } else if (found != 0) { + curwin->w_cursor = end_pos; + } + + msg_starthere(); + redrawcmdline(); + s->did_incsearch = true; +} + +// When CTRL-L typed: add character from the match to the pattern. +// May set "*c" to the added character. +// Return OK when calling command_line_not_changed. +static int may_add_char_to_search(int firstc, int *c, incsearch_state_T *s) + FUNC_ATTR_NONNULL_ALL +{ + int skiplen, patlen; + + // Parsing range may already set the last search pattern. + // NOTE: must call restore_last_search_pattern() before returning! + save_last_search_pattern(); + + // Add a character from under the cursor for 'incsearch' + if (!do_incsearch_highlighting(firstc, s, &skiplen, &patlen)) { + restore_last_search_pattern(); + return FAIL; + } + restore_last_search_pattern(); + + if (s->did_incsearch) { + curwin->w_cursor = s->match_end; + *c = gchar_cursor(); + if (*c != NUL) { + // If 'ignorecase' and 'smartcase' are set and the + // command line has no uppercase characters, convert + // the character to lowercase + if (p_ic && p_scs + && !pat_has_uppercase(ccline.cmdbuff + skiplen)) { + *c = mb_tolower(*c); + } + if (*c == firstc + || vim_strchr((char_u *)(p_magic ? "\\~^$.*[" : "\\^$"), *c) + != NULL) { + // put a backslash before special characters + stuffcharReadbuff(*c); + *c = '\\'; + } + return FAIL; + } + } + return OK; +} + +static void finish_incsearch_highlighting(int gotesc, incsearch_state_T *s, + bool call_update_screen) +{ + if (s->did_incsearch) { + s->did_incsearch = false; + if (gotesc) { + curwin->w_cursor = s->save_cursor; + } else { + if (!equalpos(s->save_cursor, s->search_start)) { + // put the '" mark at the original position + curwin->w_cursor = s->save_cursor; + setpcmark(); + } + curwin->w_cursor = s->search_start; // -V519 + } + restore_viewstate(&s->old_viewstate); + highlight_match = false; + + // by default search all lines + search_first_line = 0; + search_last_line = MAXLNUM; + + p_magic = s->magic_save; + + validate_cursor(); // needed for TAB + redraw_all_later(SOME_VALID); + if (call_update_screen) { + update_screen(SOME_VALID); + } + } +} + /// Internal entry point for cmdline mode. /// /// caller must use save_cmdline and restore_cmdline. Best is to use @@ -269,11 +665,10 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) .save_msg_scroll = msg_scroll, .save_State = State, .ignore_drag_release = true, - .match_start = curwin->w_cursor, }; CommandLineState *s = &state; s->save_p_icm = vim_strsave(p_icm); - save_viewstate(&state.init_viewstate); + init_incsearch_state(&s->is_state); if (s->firstc == -1) { s->firstc = NUL; @@ -288,10 +683,6 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) ccline.prompt_id = last_prompt_id++; ccline.level = cmdline_level; ccline.overstrike = false; // always start in insert mode - clearpos(&s->match_end); - s->save_cursor = curwin->w_cursor; // may be restored later - s->search_start = curwin->w_cursor; - save_viewstate(&state.old_viewstate); assert(indent >= 0); @@ -455,22 +846,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) close_preview_windows(); } - if (s->did_incsearch) { - if (s->gotesc) { - curwin->w_cursor = s->save_cursor; - } else { - if (!equalpos(s->save_cursor, s->search_start)) { - // put the '" mark at the original position - curwin->w_cursor = s->save_cursor; - setpcmark(); - } - curwin->w_cursor = s->search_start; // -V519 - } - restore_viewstate(&s->old_viewstate); - highlight_match = false; - validate_cursor(); // needed for TAB - redraw_all_later(SOME_VALID); - } + finish_incsearch_highlighting(s->gotesc, &s->is_state, false); if (ccline.cmdbuff != NULL) { // Put line in history buffer (":" and "=" only when it was typed). @@ -1075,24 +1451,46 @@ static int command_line_execute(VimState *state, int key) return command_line_handle_key(s); } -static void command_line_next_incsearch(CommandLineState *s, bool next_match) +// May adjust 'incsearch' highlighting for typing CTRL-G and CTRL-T, go to next +// or previous match. +// Returns FAIL when calling command_line_not_changed. +static int may_do_command_line_next_incsearch(int firstc, long count, + incsearch_state_T *s, + bool next_match) + FUNC_ATTR_NONNULL_ALL { + int skiplen, patlen; + + // Parsing range may already set the last search pattern. + // NOTE: must call restore_last_search_pattern() before returning! + save_last_search_pattern(); + + if (!do_incsearch_highlighting(firstc, s, &skiplen, &patlen)) { + restore_last_search_pattern(); + return OK; + } + if (patlen == 0 && ccline.cmdbuff[skiplen] == NUL) { + restore_last_search_pattern(); + return FAIL; + } + ui_busy_start(); ui_flush(); pos_T t; char_u *pat; int search_flags = SEARCH_NOOF; + char_u save; - if (s->firstc == ccline.cmdbuff[0]) { + if (firstc == ccline.cmdbuff[skiplen]) { pat = last_search_pattern(); + skiplen = 0; + patlen = (int)STRLEN(pat); } else { - pat = ccline.cmdbuff; + pat = ccline.cmdbuff + skiplen; } - save_last_search_pattern(); - if (next_match) { t = s->match_end; if (lt(s->match_start, s->match_end)) { @@ -1108,23 +1506,26 @@ static void command_line_next_incsearch(CommandLineState *s, bool next_match) search_flags += SEARCH_KEEP; } emsg_off++; + save = pat[patlen]; + pat[patlen] = NUL; int found = searchit(curwin, curbuf, &t, NULL, next_match ? FORWARD : BACKWARD, - pat, s->count, search_flags, + pat, count, search_flags, RE_SEARCH, NULL); emsg_off--; + pat[patlen] = save; ui_busy_stop(); if (found) { s->search_start = s->match_start; s->match_end = t; s->match_start = t; - if (!next_match && s->firstc == '/') { + if (!next_match && firstc != '?') { // move just before the current match, so that // when nv_search finishes the cursor will be // put back on the match s->search_start = t; (void)decl(&s->search_start); - } else if (next_match && s->firstc == '?') { + } else if (next_match && firstc == '?') { // move just after the current match, so that // when nv_search finishes the cursor will be // put back on the match @@ -1134,7 +1535,7 @@ static void command_line_next_incsearch(CommandLineState *s, bool next_match) if (lt(t, s->search_start) && next_match) { // wrap around s->search_start = t; - if (s->firstc == '?') { + if (firstc == '?') { (void)incl(&s->search_start); } else { (void)decl(&s->search_start); @@ -1149,12 +1550,14 @@ static void command_line_next_incsearch(CommandLineState *s, bool next_match) highlight_match = true; save_viewstate(&s->old_viewstate); update_screen(NOT_VALID); + highlight_match = false; redrawcmdline(); + curwin->w_cursor = s->match_end; } else { vim_beep(BO_ERROR); } restore_last_search_pattern(); - return; + return FAIL; } static void command_line_next_histidx(CommandLineState *s, bool next_match) @@ -1265,10 +1668,10 @@ static int command_line_handle_key(CommandLineState *s) // Truncate at the end, required for multi-byte chars. ccline.cmdbuff[ccline.cmdlen] = NUL; if (ccline.cmdlen == 0) { - s->search_start = s->save_cursor; + s->is_state.search_start = s->is_state.save_cursor; // save view settings, so that the screen won't be restored at the // wrong position - s->old_viewstate = s->init_viewstate; + s->is_state.old_viewstate = s->is_state.init_viewstate; } redrawcmd(); } else if (ccline.cmdlen == 0 && s->c != Ctrl_W @@ -1287,7 +1690,7 @@ static int command_line_handle_key(CommandLineState *s) } msg_putchar(' '); // delete ':' } - s->search_start = s->save_cursor; + s->is_state.search_start = s->is_state.save_cursor; redraw_cmdline = true; return 0; // back to cmd mode } @@ -1337,7 +1740,7 @@ static int command_line_handle_key(CommandLineState *s) // Truncate at the end, required for multi-byte chars. ccline.cmdbuff[ccline.cmdlen] = NUL; if (ccline.cmdlen == 0) { - s->search_start = s->save_cursor; + s->is_state.search_start = s->is_state.save_cursor; } redrawcmd(); return command_line_changed(s); @@ -1565,31 +1968,7 @@ static int command_line_handle_key(CommandLineState *s) return command_line_changed(s); case Ctrl_L: - if (p_is && !cmd_silent && (s->firstc == '/' || s->firstc == '?')) { - // Add a character from under the cursor for 'incsearch' - if (s->did_incsearch) { - curwin->w_cursor = s->match_end; - if (!equalpos(curwin->w_cursor, s->search_start)) { - s->c = gchar_cursor(); - // If 'ignorecase' and 'smartcase' are set and the - // command line has no uppercase characters, convert - // the character to lowercase - if (p_ic && p_scs - && !pat_has_uppercase(ccline.cmdbuff)) { - s->c = mb_tolower(s->c); - } - if (s->c != NUL) { - if (s->c == s->firstc - || vim_strchr((char_u *)(p_magic ? "\\~^$.*[" : "\\^$"), s->c) - != NULL) { - // put a backslash before special characters - stuffcharReadbuff(s->c); - s->c = '\\'; - } - break; - } - } - } + if (may_add_char_to_search(s->firstc, &s->c, &s->is_state) == OK) { return command_line_not_changed(s); } @@ -1703,10 +2082,8 @@ static int command_line_handle_key(CommandLineState *s) case Ctrl_G: // next match case Ctrl_T: // previous match - if (p_is && !cmd_silent && (s->firstc == '/' || s->firstc == '?')) { - if (ccline.cmdlen != 0) { - command_line_next_incsearch(s, s->c == Ctrl_G); - } + if (may_do_command_line_next_incsearch(s->firstc, s->count, &s->is_state, + s->c == Ctrl_G) == FAIL) { return command_line_not_changed(s); } break; @@ -1791,7 +2168,7 @@ static int command_line_not_changed(CommandLineState *s) // command line did not change. Then we only search and redraw if something // changed in the past. // Enter command_line_changed() when the command line did change. - if (!s->incsearch_postponed) { + if (!s->is_state.incsearch_postponed) { return 1; } return command_line_changed(s); @@ -1843,108 +2220,13 @@ static int command_line_changed(CommandLineState *s) } // 'incsearch' highlighting. - if (p_is && !cmd_silent && (s->firstc == '/' || s->firstc == '?')) { - pos_T end_pos; - proftime_T tm; - searchit_arg_T sia; - - // if there is a character waiting, search and redraw later - if (char_avail()) { - s->incsearch_postponed = true; - return 1; - } - s->incsearch_postponed = false; - curwin->w_cursor = s->search_start; // start at old position - save_last_search_pattern(); - int i; - - // If there is no command line, don't do anything - if (ccline.cmdlen == 0) { - i = 0; - set_no_hlsearch(true); // turn off previous highlight - redraw_all_later(SOME_VALID); - } else { - int search_flags = SEARCH_OPT + SEARCH_NOOF + SEARCH_PEEK; - ui_busy_start(); - ui_flush(); - ++emsg_off; // So it doesn't beep if bad expr - // Set the time limit to half a second. - tm = profile_setlimit(500L); - if (!p_hls) { - search_flags += SEARCH_KEEP; - } - memset(&sia, 0, sizeof(sia)); - sia.sa_tm = &tm; - i = do_search(NULL, s->firstc, ccline.cmdbuff, s->count, - search_flags, &sia); - emsg_off--; - // if interrupted while searching, behave like it failed - if (got_int) { - (void)vpeekc(); // remove <C-C> from input stream - got_int = false; // don't abandon the command line - i = 0; - } else if (char_avail()) { - // cancelled searching because a char was typed - s->incsearch_postponed = true; - } - ui_busy_stop(); - } - - if (i != 0) { - highlight_match = true; // highlight position - } else { - highlight_match = false; // remove highlight - } - - // 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); - changed_cline_bef_curs(); - update_topline(); - - if (i != 0) { - pos_T save_pos = curwin->w_cursor; - - s->match_start = curwin->w_cursor; - set_search_match(&curwin->w_cursor); - validate_cursor(); - end_pos = curwin->w_cursor; - s->match_end = end_pos; - curwin->w_cursor = save_pos; - } else { - end_pos = curwin->w_cursor; // shutup gcc 4 - } - - // Disable 'hlsearch' highlighting if the pattern matches - // everything. Avoids a flash when typing "foo\|". - if (empty_pattern(ccline.cmdbuff)) { - set_no_hlsearch(true); - } - - validate_cursor(); - // May redraw the status line to show the cursor position. - if (p_ru && curwin->w_status_height > 0) { - curwin->w_redr_status = true; - } - - update_screen(SOME_VALID); - restore_last_search_pattern(); - - // Leave it at the end to make CTRL-R CTRL-W work. - if (i != 0) { - curwin->w_cursor = end_pos; - } - - msg_starthere(); - redrawcmdline(); - s->did_incsearch = true; - } else if (s->firstc == ':' - && current_sctx.sc_sid == 0 // only if interactive - && *p_icm != NUL // 'inccommand' is set - && curbuf->b_p_ma // buffer is modifiable - && cmdline_star == 0 // not typing a password - && cmd_can_preview(ccline.cmdbuff) - && !vpeekc_any()) { + if (s->firstc == ':' + && current_sctx.sc_sid == 0 // only if interactive + && *p_icm != NUL // 'inccommand' is set + && curbuf->b_p_ma // buffer is modifiable + && cmdline_star == 0 // not typing a password + && cmd_can_preview(ccline.cmdbuff) + && !vpeekc_any()) { // Show 'inccommand' preview. It works like this: // 1. Do the command. // 2. Command implementation detects CMDPREVIEW state, then: @@ -1958,8 +2240,8 @@ static int command_line_changed(CommandLineState *s) emsg_silent--; // Unblock error reporting // Restore the window "view". - curwin->w_cursor = s->save_cursor; - restore_viewstate(&s->old_viewstate); + curwin->w_cursor = s->is_state.save_cursor; + restore_viewstate(&s->is_state.old_viewstate); update_topline(); redrawcmdline(); @@ -1968,6 +2250,8 @@ static int command_line_changed(CommandLineState *s) State = (State & ~CMDPREVIEW); close_preview_windows(); update_screen(SOME_VALID); // Clear 'inccommand' preview. + } else { + may_do_incsearch_highlighting(s->firstc, s->count, &s->is_state); } if (cmdmsg_rl || (p_arshape && !p_tbidi && enc_utf8)) { @@ -4706,7 +4990,7 @@ ExpandFromContext ( char_u *pat, int *num_file, char_u ***file, - int options /* EW_ flags */ + int options // WILD_ flags ) { regmatch_T regmatch; @@ -4768,6 +5052,21 @@ ExpandFromContext ( ret = expand_wildcards_eval(&pat, num_file, file, flags); if (free_pat) xfree(pat); +#ifdef BACKSLASH_IN_FILENAME + if (p_csl[0] != NUL && (options & WILD_IGNORE_COMPLETESLASH) == 0) { + for (int i = 0; i < *num_file; i++) { + char_u *ptr = (*file)[i]; + while (*ptr != NUL) { + if (p_csl[0] == 's' && *ptr == '\\') { + *ptr = '/'; + } else if (p_csl[0] == 'b' && *ptr == '/') { + *ptr = '\\'; + } + ptr += utfc_ptr2len(ptr); + } + } + } +#endif return ret; } @@ -4909,7 +5208,7 @@ ExpandFromContext ( * obtain strings, one by one. The strings are matched against a regexp * program. Matching strings are copied into an array, which is returned. */ -void ExpandGeneric( +static void ExpandGeneric( expand_T *xp, regmatch_T *regmatch, int *num_file, @@ -6184,17 +6483,20 @@ static int open_cmdwin(void) ccline.redraw_state = kCmdRedrawNone; ui_call_cmdline_hide(ccline.level); } - redraw_later(SOME_VALID); + redraw_later(curwin, SOME_VALID); // Save the command line info, can be used recursively. save_cmdline(&save_ccline); - /* No Ex mode here! */ + // No Ex mode here! exmode_active = 0; State = NORMAL; setmouse(); + // Reset here so it can be set by a CmdWinEnter autocommand. + cmdwin_result = 0; + // Trigger CmdwinEnter autocommands. typestr[0] = (char_u)cmdwin_type; typestr[1] = NUL; @@ -6210,7 +6512,6 @@ static int open_cmdwin(void) /* * Call the main loop until <CR> or CTRL-C is typed. */ - cmdwin_result = 0; normal_enter(true, false); RedrawingDisabled = i; diff --git a/src/nvim/ex_session.c b/src/nvim/ex_session.c index 1b797c6168..42a9ef08f9 100644 --- a/src/nvim/ex_session.c +++ b/src/nvim/ex_session.c @@ -493,7 +493,8 @@ static int put_view( /// Writes commands for restoring the current buffers, for :mksession. /// -/// Legacy 'sessionoptions' flags SSOP_UNIX, SSOP_SLASH are always enabled. +/// Legacy 'sessionoptions'/'viewoptions' flags SSOP_UNIX, SSOP_SLASH are +/// always enabled. /// /// @param dirnow Current directory name /// @param fd File descriptor to write to @@ -822,9 +823,9 @@ void ex_loadview(exarg_T *eap) /// ":mkexrc", ":mkvimrc", ":mkview", ":mksession". /// -/// Legacy 'sessionoptions' flags SSOP_UNIX, SSOP_SLASH are always enabled. -/// - SSOP_UNIX: line-endings are always LF -/// - SSOP_SLASH: filenames are always written with "/" slash +/// Legacy 'sessionoptions'/'viewoptions' flags are always enabled: +/// - SSOP_UNIX: line-endings are LF +/// - SSOP_SLASH: filenames are written with "/" slash void ex_mkrc(exarg_T *eap) { FILE *fd; @@ -896,8 +897,8 @@ void ex_mkrc(exarg_T *eap) if (!failed && view_session) { if (put_line(fd, - "let s:so_save = &so | let s:siso_save = &siso" - " | set so=0 siso=0") == FAIL) { + "let s:so_save = &g:so | let s:siso_save = &g:siso" + " | setg so=0 siso=0 | setl so=-1 siso=-1") == FAIL) { failed = true; } if (eap->cmdidx == CMD_mksession) { @@ -948,7 +949,7 @@ void ex_mkrc(exarg_T *eap) } if (fprintf(fd, "%s", - "let &so = s:so_save | let &siso = s:siso_save\n" + "let &g:so = s:so_save | let &g:siso = s:siso_save\n" "doautoall SessionLoadPost\n") < 0) { failed = true; diff --git a/src/nvim/extmark.c b/src/nvim/extmark.c index 1457a1172d..ba685b158e 100644 --- a/src/nvim/extmark.c +++ b/src/nvim/extmark.c @@ -1,38 +1,39 @@ // 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 -// Implements extended marks for plugins. Each mark exists in a btree of -// lines containing btrees of columns. +// Implements extended marks for plugins. Marks sit in a MarkTree +// datastructure which provides both efficient mark insertations/lookups +// and adjustment to text changes. See marktree.c for more details. // -// The btree provides efficient range lookups. // A map of pointers to the marks is used for fast lookup by mark id. // -// Marks are moved by calls to extmark_splice. Additionally mark_adjust -// might adjust extmarks to line inserts/deletes. +// Marks are moved by calls to extmark_splice. Some standard interfaces +// mark_adjust and inserted_bytes already adjust marks, check if these are +// being used before adding extmark_splice calls! // // Undo/Redo of marks is implemented by storing the call arguments to // extmark_splice. The list of arguments is applied in extmark_apply_undo. -// The only case where we have to copy extmarks is for the area being effected -// by a delete. +// We have to copy extmark positions when the extmarks are within a +// deleted/changed region. // // Marks live in namespaces that allow plugins/users to segregate marks // from other users. // -// For possible ideas for efficency improvements see: -// http://blog.atom.io/2015/06/16/optimizing-an-important-atom-primitive.html -// TODO(bfredl): These ideas could be used for an enhanced btree, which -// wouldn't need separate line and column layers. -// Other implementations exist in gtk and tk toolkits. -// // Deleting marks only happens when explicitly calling extmark_del, deleteing // over a range of marks will only move the marks. Deleting on a mark will // leave it in same position unless it is on the EOL of a line. +// +// Extmarks are used to implement buffer decoration. Decoration is mostly +// regarded as an application of extmarks, however for practical reasons code +// that deletes an extmark with decoration will call back into the decoration +// code for redrawing the line with the deleted decoration. #include <assert.h> #include "nvim/api/vim.h" #include "nvim/vim.h" #include "nvim/charset.h" #include "nvim/extmark.h" +#include "nvim/decoration.h" #include "nvim/buffer_updates.h" #include "nvim/memline.h" #include "nvim/pos.h" @@ -41,8 +42,6 @@ #include "nvim/lib/kbtree.h" #include "nvim/undo.h" #include "nvim/buffer.h" -#include "nvim/syntax.h" -#include "nvim/highlight.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "extmark.c.generated.h" @@ -71,9 +70,11 @@ static ExtmarkNs *buf_ns_ref(buf_T *buf, uint64_t ns_id, bool put) { /// must not be used during iteration! /// @returns the mark id uint64_t extmark_set(buf_T *buf, uint64_t ns_id, uint64_t id, - int row, colnr_T col, ExtmarkOp op) + int row, colnr_T col, int end_row, colnr_T end_col, + Decoration *decor, ExtmarkOp op) { ExtmarkNs *ns = buf_ns_ref(buf, ns_id, true); + assert(ns != NULL); mtpos_t old_pos; uint64_t mark = 0; @@ -82,7 +83,7 @@ uint64_t extmark_set(buf_T *buf, uint64_t ns_id, uint64_t id, } else { uint64_t old_mark = map_get(uint64_t, uint64_t)(ns->map, id); if (old_mark) { - if (old_mark & MARKTREE_PAIRED_FLAG) { + if (old_mark & MARKTREE_PAIRED_FLAG || end_row > -1) { extmark_del(buf, ns_id, id); } else { // TODO(bfredl): we need to do more if "revising" a decoration mark. @@ -90,7 +91,12 @@ uint64_t extmark_set(buf_T *buf, uint64_t ns_id, uint64_t id, old_pos = marktree_lookup(buf->b_marktree, old_mark, itr); assert(itr->node); if (old_pos.row == row && old_pos.col == col) { - map_del(uint64_t, ExtmarkItem)(buf->b_extmark_index, old_mark); + ExtmarkItem it = map_del(uint64_t, ExtmarkItem)(buf->b_extmark_index, + old_mark); + if (it.decor) { + decor_redraw(buf, row, row, it.decor); + decor_free(it.decor); + } mark = marktree_revise(buf->b_marktree, itr); goto revised; } @@ -101,11 +107,17 @@ uint64_t extmark_set(buf_T *buf, uint64_t ns_id, uint64_t id, } } - mark = marktree_put(buf->b_marktree, row, col, true); + if (end_row > -1) { + mark = marktree_put_pair(buf->b_marktree, + row, col, true, + end_row, end_col, false); + } else { + mark = marktree_put(buf->b_marktree, row, col, true); + } + revised: map_put(uint64_t, ExtmarkItem)(buf->b_extmark_index, mark, - (ExtmarkItem){ ns_id, id, 0, - KV_INITIAL_VALUE }); + (ExtmarkItem){ ns_id, id, decor }); map_put(uint64_t, uint64_t)(ns->map, id, mark); if (op != kExtmarkNoUndo) { @@ -114,6 +126,10 @@ revised: // adding new marks to old undo headers. u_extmark_set(buf, mark, row, col); } + + if (decor) { + decor_redraw(buf, row, end_row > -1 ? end_row : row, decor); + } return id; } @@ -152,27 +168,23 @@ bool extmark_del(buf_T *buf, uint64_t ns_id, uint64_t id) assert(pos.row >= 0); marktree_del_itr(buf->b_marktree, itr, false); ExtmarkItem item = map_get(uint64_t, ExtmarkItem)(buf->b_extmark_index, mark); + mtpos_t pos2 = pos; if (mark & MARKTREE_PAIRED_FLAG) { - mtpos_t pos2 = marktree_lookup(buf->b_marktree, - mark|MARKTREE_END_FLAG, itr); + pos2 = marktree_lookup(buf->b_marktree, mark|MARKTREE_END_FLAG, itr); assert(pos2.row >= 0); marktree_del_itr(buf->b_marktree, itr, false); - if (item.hl_id && pos2.row >= pos.row) { - redraw_buf_range_later(buf, pos.row+1, pos2.row+1); - } } - if (kv_size(item.virt_text)) { - redraw_buf_line_later(buf, pos.row+1); + if (item.decor) { + decor_redraw(buf, pos.row, pos2.row, item.decor); + decor_free(item.decor); } - clear_virttext(&item.virt_text); map_del(uint64_t, uint64_t)(ns->map, id); map_del(uint64_t, ExtmarkItem)(buf->b_extmark_index, mark); // TODO(bfredl): delete it from current undo header, opportunistically? - return true; } @@ -202,9 +214,11 @@ bool extmark_clear(buf_T *buf, uint64_t ns_id, } // the value is either zero or the lnum (row+1) if highlight was present. - static Map(uint64_t, uint64_t) *delete_set = NULL; + static Map(uint64_t, ssize_t) *delete_set = NULL; + typedef struct { Decoration *decor; int row1; } DecorItem; + static kvec_t(DecorItem) decors; if (delete_set == NULL) { - delete_set = map_new(uint64_t, uint64_t)(); + delete_set = map_new(uint64_t, ssize_t)(); } MarkTreeIter itr[1] = { 0 }; @@ -216,14 +230,16 @@ bool extmark_clear(buf_T *buf, uint64_t ns_id, || (mark.row == u_row && mark.col > u_col)) { break; } - uint64_t *del_status = map_ref(uint64_t, uint64_t)(delete_set, mark.id, - false); + ssize_t *del_status = map_ref(uint64_t, ssize_t)(delete_set, mark.id, + false); if (del_status) { marktree_del_itr(buf->b_marktree, itr, false); - map_del(uint64_t, uint64_t)(delete_set, mark.id); - if (*del_status > 0) { - redraw_buf_range_later(buf, (linenr_T)(*del_status), mark.row+1); + if (*del_status >= 0) { // we had a decor_id + DecorItem it = kv_A(decors, *del_status); + decor_redraw(buf, it.row1, mark.row, it.decor); + decor_free(it.decor); } + map_del(uint64_t, ssize_t)(delete_set, mark.id); continue; } @@ -233,15 +249,21 @@ bool extmark_clear(buf_T *buf, uint64_t ns_id, assert(item.ns_id > 0 && item.mark_id > 0); if (item.mark_id > 0 && (item.ns_id == ns_id || all_ns)) { - if (kv_size(item.virt_text)) { - redraw_buf_line_later(buf, mark.row+1); - } - clear_virttext(&item.virt_text); marks_cleared = true; if (mark.id & MARKTREE_PAIRED_FLAG) { uint64_t other = mark.id ^ MARKTREE_END_FLAG; - uint64_t status = item.hl_id ? ((uint64_t)mark.row+1) : 0; - map_put(uint64_t, uint64_t)(delete_set, other, status); + ssize_t decor_id = -1; + if (item.decor) { + // Save the decoration and the first pos. Clear the decoration + // later when we know the full range. + decor_id = (ssize_t)kv_size(decors); + kv_push(decors, + ((DecorItem) { .decor = item.decor, .row1 = mark.row })); + } + map_put(uint64_t, ssize_t)(delete_set, other, decor_id); + } else if (item.decor) { + decor_redraw(buf, mark.row, mark.row, item.decor); + decor_free(item.decor); } ExtmarkNs *my_ns = all_ns ? buf_ns_ref(buf, item.ns_id, false) : ns; map_del(uint64_t, uint64_t)(my_ns->map, item.mark_id); @@ -251,16 +273,20 @@ bool extmark_clear(buf_T *buf, uint64_t ns_id, marktree_itr_next(buf->b_marktree, itr); } } - uint64_t id, status; - map_foreach(delete_set, id, status, { + uint64_t id; + ssize_t decor_id; + map_foreach(delete_set, id, decor_id, { mtpos_t pos = marktree_lookup(buf->b_marktree, id, itr); assert(itr->node); marktree_del_itr(buf->b_marktree, itr, false); - if (status > 0) { - redraw_buf_range_later(buf, (linenr_T)status, pos.row+1); + if (decor_id >= 0) { + DecorItem it = kv_A(decors, decor_id); + decor_redraw(buf, it.row1, pos.row, it.decor); + decor_free(it.decor); } }); - map_clear(uint64_t, uint64_t)(delete_set); + map_clear(uint64_t, ssize_t)(delete_set); + kv_size(decors) = 0; return marks_cleared; } @@ -270,31 +296,44 @@ bool extmark_clear(buf_T *buf, uint64_t ns_id, // will be searched to the start, or end // dir can be set to control the order of the array // amount = amount of marks to find or -1 for all -ExtmarkArray extmark_get(buf_T *buf, uint64_t ns_id, - int l_row, colnr_T l_col, - int u_row, colnr_T u_col, - int64_t amount, bool reverse) +ExtmarkInfoArray extmark_get(buf_T *buf, uint64_t ns_id, + int l_row, colnr_T l_col, + int u_row, colnr_T u_col, + int64_t amount, bool reverse) { - ExtmarkArray array = KV_INITIAL_VALUE; - MarkTreeIter itr[1] = { 0 }; + ExtmarkInfoArray array = KV_INITIAL_VALUE; + MarkTreeIter itr[1]; // Find all the marks marktree_itr_get_ext(buf->b_marktree, (mtpos_t){ l_row, l_col }, itr, reverse, false, NULL); int order = reverse ? -1 : 1; while ((int64_t)kv_size(array) < amount) { mtmark_t mark = marktree_itr_current(itr); + mtpos_t endpos = { -1, -1 }; if (mark.row < 0 || (mark.row - u_row) * order > 0 || (mark.row == u_row && (mark.col - u_col) * order > 0)) { break; } + if (mark.id & MARKTREE_END_FLAG) { + goto next_mark; + } else if (mark.id & MARKTREE_PAIRED_FLAG) { + endpos = marktree_lookup(buf->b_marktree, mark.id | MARKTREE_END_FLAG, + NULL); + } + + ExtmarkItem item = map_get(uint64_t, ExtmarkItem)(buf->b_extmark_index, mark.id); if (item.ns_id == ns_id) { kv_push(array, ((ExtmarkInfo) { .ns_id = item.ns_id, .mark_id = item.mark_id, - .row = mark.row, .col = mark.col })); + .row = mark.row, .col = mark.col, + .end_row = endpos.row, + .end_col = endpos.col, + .decor = item.decor })); } +next_mark: if (reverse) { marktree_itr_prev(buf->b_marktree, itr); } else { @@ -308,7 +347,7 @@ ExtmarkArray extmark_get(buf_T *buf, uint64_t ns_id, ExtmarkInfo extmark_from_id(buf_T *buf, uint64_t ns_id, uint64_t id) { ExtmarkNs *ns = buf_ns_ref(buf, ns_id, false); - ExtmarkInfo ret = { 0, 0, -1, -1 }; + ExtmarkInfo ret = { 0, 0, -1, -1, -1, -1, NULL }; if (!ns) { return ret; } @@ -319,12 +358,22 @@ ExtmarkInfo extmark_from_id(buf_T *buf, uint64_t ns_id, uint64_t id) } mtpos_t pos = marktree_lookup(buf->b_marktree, mark, NULL); + mtpos_t endpos = { -1, -1 }; + if (mark & MARKTREE_PAIRED_FLAG) { + endpos = marktree_lookup(buf->b_marktree, mark | MARKTREE_END_FLAG, NULL); + } assert(pos.row >= 0); + ExtmarkItem item = map_get(uint64_t, ExtmarkItem)(buf->b_extmark_index, + mark); + ret.ns_id = ns_id; ret.mark_id = id; ret.row = pos.row; ret.col = pos.col; + ret.end_row = endpos.row; + ret.end_col = endpos.col; + ret.decor = item.decor; return ret; } @@ -352,7 +401,7 @@ void extmark_free_all(buf_T *buf) map_foreach(buf->b_extmark_index, id, item, { (void)id; - clear_virttext(&item.virt_text); + decor_free(item.decor); }); map_free(uint64_t, ExtmarkItem)(buf->b_extmark_index); buf->b_extmark_index = NULL; @@ -428,18 +477,18 @@ void extmark_apply_undo(ExtmarkUndoObject undo_info, bool undo) // Undo ExtmarkSplice splice = undo_info.data.splice; if (undo) { - extmark_splice(curbuf, - splice.start_row, splice.start_col, - splice.newextent_row, splice.newextent_col, - splice.oldextent_row, splice.oldextent_col, - kExtmarkNoUndo); + extmark_splice_impl(curbuf, + splice.start_row, splice.start_col, splice.start_byte, + splice.new_row, splice.new_col, splice.new_byte, + splice.old_row, splice.old_col, splice.old_byte, + kExtmarkNoUndo); } else { - extmark_splice(curbuf, - splice.start_row, splice.start_col, - splice.oldextent_row, splice.oldextent_col, - splice.newextent_row, splice.newextent_col, - kExtmarkNoUndo); + extmark_splice_impl(curbuf, + splice.start_row, splice.start_col, splice.start_byte, + splice.old_row, splice.old_col, splice.old_byte, + splice.new_row, splice.new_col, splice.new_byte, + kExtmarkNoUndo); } // kExtmarkSavePos } else if (undo_info.type == kExtmarkSavePos) { @@ -458,15 +507,15 @@ void extmark_apply_undo(ExtmarkUndoObject undo_info, bool undo) ExtmarkMove move = undo_info.data.move; if (undo) { extmark_move_region(curbuf, - move.new_row, move.new_col, - move.extent_row, move.extent_col, - move.start_row, move.start_col, + move.new_row, move.new_col, move.new_byte, + move.extent_row, move.extent_col, move.extent_byte, + move.start_row, move.start_col, move.start_byte, kExtmarkNoUndo); } else { extmark_move_region(curbuf, - move.start_row, move.start_col, - move.extent_row, move.extent_col, - move.new_row, move.new_col, + move.start_row, move.start_col, move.start_byte, + move.extent_row, move.extent_col, move.extent_byte, + move.new_row, move.new_col, move.new_byte, kExtmarkNoUndo); } } @@ -481,51 +530,84 @@ void extmark_adjust(buf_T *buf, long amount_after, ExtmarkOp undo) { - if (!curbuf_splice_pending) { - int old_extent, new_extent; - if (amount == MAXLNUM) { - old_extent = (int)(line2 - line1+1); - new_extent = (int)(amount_after + old_extent); - } else { - // A region is either deleted (amount == MAXLNUM) or - // added (line2 == MAXLNUM). The only other case is :move - // which is handled by a separate entry point extmark_move_region. - assert(line2 == MAXLNUM); - old_extent = 0; - new_extent = (int)amount; - } - extmark_splice(buf, - (int)line1-1, 0, - old_extent, 0, - new_extent, 0, undo); + if (curbuf_splice_pending) { + return; } + bcount_t start_byte = ml_find_line_or_offset(buf, line1, NULL, true); + bcount_t old_byte = 0, new_byte = 0; + int old_row, new_row; + if (amount == MAXLNUM) { + old_row = (int)(line2 - line1+1); + // TODO(bfredl): ej kasta? + old_byte = (bcount_t)buf->deleted_bytes2; + + new_row = (int)(amount_after + old_row); + } else { + // A region is either deleted (amount == MAXLNUM) or + // added (line2 == MAXLNUM). The only other case is :move + // which is handled by a separate entry point extmark_move_region. + assert(line2 == MAXLNUM); + old_row = 0; + new_row = (int)amount; + } + if (new_row > 0) { + new_byte = ml_find_line_or_offset(buf, line1+new_row, NULL, true) + - start_byte; + } + extmark_splice_impl(buf, + (int)line1-1, 0, start_byte, + old_row, 0, old_byte, + new_row, 0, new_byte, undo); } void extmark_splice(buf_T *buf, int start_row, colnr_T start_col, - int oldextent_row, colnr_T oldextent_col, - int newextent_row, colnr_T newextent_col, + int old_row, colnr_T old_col, bcount_t old_byte, + int new_row, colnr_T new_col, bcount_t new_byte, ExtmarkOp undo) { - buf_updates_send_splice(buf, start_row, start_col, - oldextent_row, oldextent_col, - newextent_row, newextent_col); + long offset = ml_find_line_or_offset(buf, start_row + 1, NULL, true); + + // On empty buffers, when editing the first line, the line is buffered, + // causing offset to be < 0. While the buffer is not actually empty, the + // buffered line has not been flushed (and should not be) yet, so the call is + // valid but an edge case. + // + // TODO(vigoux): maybe the is a better way of testing that ? + if (offset < 0 && buf->b_ml.ml_chunksize == NULL) { + offset = 0; + } + extmark_splice_impl(buf, start_row, start_col, offset + start_col, + old_row, old_col, old_byte, new_row, new_col, new_byte, + undo); +} + +void extmark_splice_impl(buf_T *buf, + int start_row, colnr_T start_col, bcount_t start_byte, + int old_row, colnr_T old_col, bcount_t old_byte, + int new_row, colnr_T new_col, bcount_t new_byte, + ExtmarkOp undo) +{ + curbuf->deleted_bytes2 = 0; + buf_updates_send_splice(buf, start_row, start_col, start_byte, + old_row, old_col, old_byte, + new_row, new_col, new_byte); - if (undo == kExtmarkUndo && (oldextent_row > 0 || oldextent_col > 0)) { + if (undo == kExtmarkUndo && (old_row > 0 || old_col > 0)) { // Copy marks that would be effected by delete // TODO(bfredl): Be "smart" about gravity here, left-gravity at the // beginning and right-gravity at the end need not be preserved. // Also be smart about marks that already have been saved (important for // merge!) - int end_row = start_row + oldextent_row; - int end_col = (oldextent_row ? 0 : start_col) + oldextent_col; + int end_row = start_row + old_row; + int end_col = (old_row ? 0 : start_col) + old_col; u_extmark_copy(buf, start_row, start_col, end_row, end_col); } marktree_splice(buf->b_marktree, start_row, start_col, - oldextent_row, oldextent_col, - newextent_row, newextent_col); + old_row, old_col, + new_row, new_col); if (undo == kExtmarkUndo) { u_header_T *uhp = u_force_get_undo_header(buf); @@ -537,25 +619,29 @@ void extmark_splice(buf_T *buf, // TODO(bfredl): this is quite rudimentary. We merge small (within line) // inserts with each other and small deletes with each other. Add full // merge algorithm later. - if (oldextent_row == 0 && newextent_row == 0 && kv_size(uhp->uh_extmark)) { + if (old_row == 0 && new_row == 0 && kv_size(uhp->uh_extmark)) { ExtmarkUndoObject *item = &kv_A(uhp->uh_extmark, kv_size(uhp->uh_extmark)-1); if (item->type == kExtmarkSplice) { ExtmarkSplice *splice = &item->data.splice; - if (splice->start_row == start_row && splice->oldextent_row == 0 - && splice->newextent_row == 0) { - if (oldextent_col == 0 && start_col >= splice->start_col - && start_col <= splice->start_col+splice->newextent_col) { - splice->newextent_col += newextent_col; + if (splice->start_row == start_row && splice->old_row == 0 + && splice->new_row == 0) { + if (old_col == 0 && start_col >= splice->start_col + && start_col <= splice->start_col+splice->new_col) { + splice->new_col += new_col; + splice->new_byte += new_byte; merged = true; - } else if (newextent_col == 0 - && start_col == splice->start_col+splice->newextent_col) { - splice->oldextent_col += oldextent_col; + } else if (new_col == 0 + && start_col == splice->start_col+splice->new_col) { + splice->old_col += old_col; + splice->old_byte += old_byte; merged = true; - } else if (newextent_col == 0 - && start_col + oldextent_col == splice->start_col) { + } else if (new_col == 0 + && start_col + old_col == splice->start_col) { splice->start_col = start_col; - splice->oldextent_col += oldextent_col; + splice->start_byte = start_byte; + splice->old_col += old_col; + splice->old_byte += old_byte; merged = true; } } @@ -566,10 +652,13 @@ void extmark_splice(buf_T *buf, ExtmarkSplice splice; splice.start_row = start_row; splice.start_col = start_col; - splice.oldextent_row = oldextent_row; - splice.oldextent_col = oldextent_col; - splice.newextent_row = newextent_row; - splice.newextent_col = newextent_col; + splice.start_byte = start_byte; + splice.old_row = old_row; + splice.old_col = old_col; + splice.old_byte = old_byte; + splice.new_row = new_row; + splice.new_col = new_col; + splice.new_byte = new_byte; kv_push(uhp->uh_extmark, ((ExtmarkUndoObject){ .type = kExtmarkSplice, @@ -584,30 +673,31 @@ void extmark_splice_cols(buf_T *buf, ExtmarkOp undo) { extmark_splice(buf, start_row, start_col, - 0, old_col, - 0, new_col, undo); + 0, old_col, old_col, + 0, new_col, new_col, undo); } -void extmark_move_region(buf_T *buf, - int start_row, colnr_T start_col, - int extent_row, colnr_T extent_col, - int new_row, colnr_T new_col, - ExtmarkOp undo) +void extmark_move_region( + buf_T *buf, + int start_row, colnr_T start_col, bcount_t start_byte, + int extent_row, colnr_T extent_col, bcount_t extent_byte, + int new_row, colnr_T new_col, bcount_t new_byte, + ExtmarkOp undo) { // TODO(bfredl): this is not synced to the buffer state inside the callback. // But unless we make the undo implementation smarter, this is not ensured // anyway. - buf_updates_send_splice(buf, start_row, start_col, - extent_row, extent_col, - 0, 0); + buf_updates_send_splice(buf, start_row, start_col, start_byte, + extent_row, extent_col, extent_byte, + 0, 0, 0); marktree_move_region(buf->b_marktree, start_row, start_col, extent_row, extent_col, new_row, new_col); - buf_updates_send_splice(buf, new_row, new_col, - 0, 0, - extent_row, extent_col); + buf_updates_send_splice(buf, new_row, new_col, new_byte, + 0, 0, 0, + extent_row, extent_col, extent_byte); if (undo == kExtmarkUndo) { @@ -619,10 +709,13 @@ void extmark_move_region(buf_T *buf, ExtmarkMove move; move.start_row = start_row; move.start_col = start_col; + move.start_byte = start_byte; move.extent_row = extent_row; move.extent_col = extent_col; + move.extent_byte = extent_byte; move.new_row = new_row; move.new_col = new_col; + move.new_byte = new_byte; kv_push(uhp->uh_extmark, ((ExtmarkUndoObject){ .type = kExtmarkMove, @@ -642,287 +735,3 @@ uint64_t src2ns(Integer *src_id) } } -/// Adds a decoration to a buffer. -/// -/// Unlike matchaddpos() highlights, these follow changes to the the buffer -/// texts. Decorations are represented internally and in the API as extmarks. -/// -/// @param buf The buffer to add decorations to -/// @param ns_id A valid namespace id. -/// @param hl_id Id of the highlight group to use (or zero) -/// @param start_row The line to highlight -/// @param start_col First column to highlight -/// @param end_row The line to highlight -/// @param end_col The last column to highlight -/// @param virt_text Virtual text (currently placed at the EOL of start_row) -/// @return The extmark id inside the namespace -uint64_t extmark_add_decoration(buf_T *buf, uint64_t ns_id, int hl_id, - int start_row, colnr_T start_col, - int end_row, colnr_T end_col, - VirtText virt_text) -{ - ExtmarkNs *ns = buf_ns_ref(buf, ns_id, true); - ExtmarkItem item; - item.ns_id = ns_id; - item.mark_id = ns->free_id++; - item.hl_id = hl_id; - item.virt_text = virt_text; - - uint64_t mark; - - if (end_row > -1) { - mark = marktree_put_pair(buf->b_marktree, - start_row, start_col, true, - end_row, end_col, false); - } else { - mark = marktree_put(buf->b_marktree, start_row, start_col, true); - } - - map_put(uint64_t, ExtmarkItem)(buf->b_extmark_index, mark, item); - map_put(uint64_t, uint64_t)(ns->map, item.mark_id, mark); - - redraw_buf_range_later(buf, start_row+1, - (end_row >= 0 ? end_row : start_row) + 1); - return item.mark_id; -} - -/// Add highlighting to a buffer, bounded by two cursor positions, -/// with an offset. -/// -/// @param buf Buffer to add highlights to -/// @param src_id src_id to use or 0 to use a new src_id group, -/// or -1 for ungrouped highlight. -/// @param hl_id Highlight group id -/// @param pos_start Cursor position to start the hightlighting at -/// @param pos_end Cursor position to end the highlighting at -/// @param offset Move the whole highlighting this many columns to the right -void bufhl_add_hl_pos_offset(buf_T *buf, - int src_id, - int hl_id, - lpos_T pos_start, - lpos_T pos_end, - colnr_T offset) -{ - colnr_T hl_start = 0; - colnr_T hl_end = 0; - - // TODO(bfredl): if decoration had blocky mode, we could avoid this loop - for (linenr_T lnum = pos_start.lnum; lnum <= pos_end.lnum; lnum ++) { - int end_off = 0; - if (pos_start.lnum < lnum && lnum < pos_end.lnum) { - // TODO(bfredl): This is quite ad-hoc, but the space between |num| and - // text being highlighted is the indication of \n being part of the - // substituted text. But it would be more consistent to highlight - // a space _after_ the previous line instead (like highlight EOL list - // char) - hl_start = MAX(offset-1, 0); - end_off = 1; - hl_end = 0; - } else if (lnum == pos_start.lnum && lnum < pos_end.lnum) { - hl_start = pos_start.col + offset; - end_off = 1; - hl_end = 0; - } else if (pos_start.lnum < lnum && lnum == pos_end.lnum) { - hl_start = MAX(offset-1, 0); - hl_end = pos_end.col + offset; - } else if (pos_start.lnum == lnum && pos_end.lnum == lnum) { - hl_start = pos_start.col + offset; - hl_end = pos_end.col + offset; - } - (void)extmark_add_decoration(buf, (uint64_t)src_id, hl_id, - (int)lnum-1, hl_start, - (int)lnum-1+end_off, hl_end, - VIRTTEXT_EMPTY); - } -} - -void clear_virttext(VirtText *text) -{ - for (size_t i = 0; i < kv_size(*text); i++) { - xfree(kv_A(*text, i).text); - } - kv_destroy(*text); - *text = (VirtText)KV_INITIAL_VALUE; -} - -VirtText *extmark_find_virttext(buf_T *buf, int row, uint64_t ns_id) -{ - MarkTreeIter itr[1] = { 0 }; - marktree_itr_get(buf->b_marktree, row, 0, itr); - while (true) { - mtmark_t mark = marktree_itr_current(itr); - if (mark.row < 0 || mark.row > row) { - break; - } - ExtmarkItem *item = map_ref(uint64_t, ExtmarkItem)(buf->b_extmark_index, - mark.id, false); - if (item && (ns_id == 0 || ns_id == item->ns_id) - && kv_size(item->virt_text)) { - return &item->virt_text; - } - marktree_itr_next(buf->b_marktree, itr); - } - return NULL; -} - -bool decorations_redraw_reset(buf_T *buf, DecorationRedrawState *state) -{ - state->row = -1; - kv_size(state->active) = 0; - return buf->b_extmark_index || buf->b_luahl; -} - - -bool decorations_redraw_start(buf_T *buf, int top_row, - DecorationRedrawState *state) -{ - state->top_row = top_row; - marktree_itr_get(buf->b_marktree, top_row, 0, state->itr); - if (!state->itr->node) { - return false; - } - marktree_itr_rewind(buf->b_marktree, state->itr); - while (true) { - mtmark_t mark = marktree_itr_current(state->itr); - if (mark.row < 0) { // || mark.row > end_row - break; - } - // TODO(bfredl): dedicated flag for being a decoration? - if ((mark.row < top_row && mark.id&MARKTREE_END_FLAG)) { - goto next_mark; - } - mtpos_t altpos = marktree_lookup(buf->b_marktree, - mark.id^MARKTREE_END_FLAG, NULL); - - uint64_t start_id = mark.id & ~MARKTREE_END_FLAG; - ExtmarkItem *item = map_ref(uint64_t, ExtmarkItem)(buf->b_extmark_index, - start_id, false); - if ((!(mark.id&MARKTREE_END_FLAG) && altpos.row < top_row - && item && !kv_size(item->virt_text)) - || ((mark.id&MARKTREE_END_FLAG) && altpos.row >= top_row)) { - goto next_mark; - } - - if (item && (item->hl_id > 0 || kv_size(item->virt_text))) { - int attr_id = item->hl_id > 0 ? syn_id2attr(item->hl_id) : 0; - VirtText *vt = kv_size(item->virt_text) ? &item->virt_text : NULL; - HlRange range; - if (mark.id&MARKTREE_END_FLAG) { - range = (HlRange){ altpos.row, altpos.col, mark.row, mark.col, - attr_id, vt }; - } else { - range = (HlRange){ mark.row, mark.col, altpos.row, - altpos.col, attr_id, vt }; - } - kv_push(state->active, range); - } -next_mark: - if (marktree_itr_node_done(state->itr)) { - break; - } - marktree_itr_next(buf->b_marktree, state->itr); - } - - return true; // TODO(bfredl): check if available in the region -} - -bool decorations_redraw_line(buf_T *buf, int row, DecorationRedrawState *state) -{ - if (state->row == -1) { - decorations_redraw_start(buf, row, state); - } - state->row = row; - state->col_until = -1; - return true; // TODO(bfredl): be more precise -} - -int decorations_redraw_col(buf_T *buf, int col, DecorationRedrawState *state) -{ - if (col <= state->col_until) { - return state->current; - } - state->col_until = MAXCOL; - while (true) { - mtmark_t mark = marktree_itr_current(state->itr); - if (mark.row < 0 || mark.row > state->row) { - break; - } else if (mark.row == state->row && mark.col > col) { - state->col_until = mark.col-1; - break; - } - - if ((mark.id&MARKTREE_END_FLAG)) { - // TODO(bfredl): check decorations flag - goto next_mark; - } - mtpos_t endpos = marktree_lookup(buf->b_marktree, - mark.id|MARKTREE_END_FLAG, NULL); - - ExtmarkItem *item = map_ref(uint64_t, ExtmarkItem)(buf->b_extmark_index, - mark.id, false); - - if (endpos.row < mark.row - || (endpos.row == mark.row && endpos.col <= mark.col)) { - if (item && !kv_size(item->virt_text)) { - goto next_mark; - } - } - - if (item && (item->hl_id > 0 || kv_size(item->virt_text))) { - int attr_id = item->hl_id > 0 ? syn_id2attr(item->hl_id) : 0; - VirtText *vt = kv_size(item->virt_text) ? &item->virt_text : NULL; - kv_push(state->active, ((HlRange){ mark.row, mark.col, - endpos.row, endpos.col, - attr_id, vt })); - } - -next_mark: - marktree_itr_next(buf->b_marktree, state->itr); - } - - int attr = 0; - size_t j = 0; - for (size_t i = 0; i < kv_size(state->active); i++) { - HlRange item = kv_A(state->active, i); - bool active = false, keep = true; - if (item.end_row < state->row - || (item.end_row == state->row && item.end_col <= col)) { - if (!(item.start_row >= state->row && item.virt_text)) { - keep = false; - } - } else { - if (item.start_row < state->row - || (item.start_row == state->row && item.start_col <= col)) { - active = true; - if (item.end_row == state->row) { - state->col_until = MIN(state->col_until, item.end_col-1); - } - } else { - if (item.start_row == state->row) { - state->col_until = MIN(state->col_until, item.start_col-1); - } - } - } - if (active && item.attr_id > 0) { - attr = hl_combine_attr(attr, item.attr_id); - } - if (keep) { - kv_A(state->active, j++) = kv_A(state->active, i); - } - } - kv_size(state->active) = j; - state->current = attr; - return attr; -} - -VirtText *decorations_redraw_virt_text(buf_T *buf, DecorationRedrawState *state) -{ - decorations_redraw_col(buf, MAXCOL, state); - for (size_t i = 0; i < kv_size(state->active); i++) { - HlRange item = kv_A(state->active, i); - if (item.start_row == state->row && item.virt_text) { - return item.virt_text; - } - } - return NULL; -} diff --git a/src/nvim/extmark.h b/src/nvim/extmark.h index b5eb0db3b6..1bc42322a3 100644 --- a/src/nvim/extmark.h +++ b/src/nvim/extmark.h @@ -1,6 +1,7 @@ #ifndef NVIM_EXTMARK_H #define NVIM_EXTMARK_H +#include "nvim/pos.h" #include "nvim/buffer_defs.h" #include "nvim/extmark_defs.h" #include "nvim/marktree.h" @@ -13,19 +14,28 @@ typedef struct uint64_t mark_id; int row; colnr_T col; + int end_row; + colnr_T end_col; + Decoration *decor; } ExtmarkInfo; -typedef kvec_t(ExtmarkInfo) ExtmarkArray; +typedef kvec_t(ExtmarkInfo) ExtmarkInfoArray; + +// TODO(bfredl): good enough name for now. +typedef ptrdiff_t bcount_t; // delete the columns between mincol and endcol typedef struct { int start_row; colnr_T start_col; - int oldextent_row; - colnr_T oldextent_col; - int newextent_row; - colnr_T newextent_col; + int old_row; + colnr_T old_col; + int new_row; + colnr_T new_col; + bcount_t start_byte; + bcount_t old_byte; + bcount_t new_byte; } ExtmarkSplice; // adjust marks after :move operation @@ -36,6 +46,9 @@ typedef struct { int extent_col; int new_row; int new_col; + bcount_t start_byte; + bcount_t extent_byte; + bcount_t new_byte; } ExtmarkMove; // extmark was updated @@ -66,26 +79,6 @@ struct undo_object { }; -typedef struct { - int start_row; - int start_col; - int end_row; - int end_col; - int attr_id; - VirtText *virt_text; -} HlRange; - -typedef struct { - MarkTreeIter itr[1]; - kvec_t(HlRange) active; - int top_row; - int row; - int col_until; - int current; - VirtText *virt_text; -} DecorationRedrawState; - - #ifdef INCLUDE_GENERATED_DECLARATIONS # include "extmark.h.generated.h" #endif diff --git a/src/nvim/extmark_defs.h b/src/nvim/extmark_defs.h index c927048981..784280dace 100644 --- a/src/nvim/extmark_defs.h +++ b/src/nvim/extmark_defs.h @@ -1,25 +1,19 @@ #ifndef NVIM_EXTMARK_DEFS_H #define NVIM_EXTMARK_DEFS_H -#include "nvim/pos.h" // for colnr_T +#include "nvim/types.h" #include "nvim/lib/kvec.h" -typedef struct { - char *text; - int hl_id; -} VirtTextChunk; - -typedef kvec_t(VirtTextChunk) VirtText; -#define VIRTTEXT_EMPTY ((VirtText)KV_INITIAL_VALUE) +typedef struct Decoration Decoration; typedef struct { uint64_t ns_id; uint64_t mark_id; - int hl_id; // highlight group - // TODO(bfredl): virt_text is pretty larger than the rest, - // pointer indirection? - VirtText virt_text; + // TODO(bfredl): a lot of small allocations. Should probably use + // kvec_t(Decoration) as an arena. Alternatively, store ns_id/mark_id + // _inline_ in MarkTree and use the map only for decorations. + Decoration *decor; } ExtmarkItem; typedef struct undo_object ExtmarkUndoObject; diff --git a/src/nvim/file_search.c b/src/nvim/file_search.c index 47272df2f0..b1fa0b6779 100644 --- a/src/nvim/file_search.c +++ b/src/nvim/file_search.c @@ -1565,7 +1565,7 @@ theend: return file_name; } -void do_autocmd_dirchanged(char *new_dir, CdScope scope) +void do_autocmd_dirchanged(char *new_dir, CdScope scope, bool changed_window) { static bool recursive = false; @@ -1601,6 +1601,7 @@ void do_autocmd_dirchanged(char *new_dir, CdScope scope) tv_dict_add_str(dict, S_LEN("scope"), buf); // -V614 tv_dict_add_str(dict, S_LEN("cwd"), new_dir); + tv_dict_add_bool(dict, S_LEN("changed_window"), changed_window); tv_dict_set_keys_readonly(dict); apply_autocmds(EVENT_DIRCHANGED, (char_u *)buf, (char_u *)new_dir, false, @@ -1633,7 +1634,7 @@ int vim_chdirfile(char_u *fname) slash_adjust((char_u *)dir); #endif if (!strequal(dir, (char *)NameBuff)) { - do_autocmd_dirchanged(dir, kCdScopeWindow); + do_autocmd_dirchanged(dir, kCdScopeWindow, false); } return OK; diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index 20f0cdccc3..e349f00fba 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -210,7 +210,8 @@ void filemess(buf_T *buf, char_u *name, char_u *s, int attr) if (msg_silent != 0) { return; } - add_quoted_fname((char *)IObuff, IOSIZE - 80, buf, (const char *)name); + add_quoted_fname((char *)IObuff, IOSIZE - 100, buf, (const char *)name); + // Avoid an over-long translation to cause trouble. xstrlcat((char *)IObuff, (const char *)s, IOSIZE); // For the first message may have to start a new line. // For further ones overwrite the previous one, reset msg_scroll before @@ -300,6 +301,7 @@ readfile( int skip_read = false; context_sha256_T sha_ctx; int read_undo_file = false; + int split = 0; // number of split lines linenr_T linecnt; int error = FALSE; /* errors encountered */ int ff_error = EOL_UNKNOWN; /* file format with errors */ @@ -348,6 +350,7 @@ readfile( char_u *old_b_fname; int using_b_ffname; int using_b_fname; + static char *msg_is_a_directory = N_("is a directory"); au_did_filetype = false; // reset before triggering any autocommands @@ -442,37 +445,43 @@ readfile( else msg_scroll = TRUE; /* don't overwrite previous file message */ - /* - * If the name is too long we might crash further on, quit here. - */ + // If the name is too long we might crash further on, quit here. if (fname != NULL && *fname != NUL) { - if (STRLEN(fname) >= MAXPATHL) { + size_t namelen = STRLEN(fname); + + // If the name is too long we might crash further on, quit here. + if (namelen >= MAXPATHL) { filemess(curbuf, fname, (char_u *)_("Illegal file name"), 0); msg_end(); msg_scroll = msg_save; return FAIL; } + + // If the name ends in a path separator, we can't open it. Check here, + // because reading the file may actually work, but then creating the + // swap file may destroy it! Reported on MS-DOS and Win 95. + if (after_pathsep((const char *)fname, (const char *)(fname + namelen))) { + filemess(curbuf, fname, (char_u *)_(msg_is_a_directory), 0); + msg_end(); + msg_scroll = msg_save; + return FAIL; + } } if (!read_buffer && !read_stdin && !read_fifo) { perm = os_getperm((const char *)fname); -#ifdef UNIX // On Unix it is possible to read a directory, so we have to // check for it before os_open(). if (perm >= 0 && !S_ISREG(perm) // not a regular file ... -# ifdef S_ISFIFO && !S_ISFIFO(perm) // ... or fifo -# endif -# ifdef S_ISSOCK && !S_ISSOCK(perm) // ... or socket -# endif # ifdef OPEN_CHR_FILES && !(S_ISCHR(perm) && is_dev_fd_file(fname)) // ... or a character special file named /dev/fd/<n> # endif ) { if (S_ISDIR(perm)) { - filemess(curbuf, fname, (char_u *)_("is a directory"), 0); + filemess(curbuf, fname, (char_u *)_(msg_is_a_directory), 0); } else { filemess(curbuf, fname, (char_u *)_("is not a file"), 0); } @@ -480,7 +489,6 @@ readfile( msg_scroll = msg_save; return S_ISDIR(perm) ? NOTDONE : FAIL; } -#endif } /* Set default or forced 'fileformat' and 'binary'. */ @@ -539,13 +547,6 @@ readfile( if (fd < 0) { // cannot open at all msg_scroll = msg_save; -#ifndef UNIX - // On non-unix systems we can't open a directory, check here. - if (os_isdir(fname)) { - filemess(curbuf, sfname, (char_u *)_("is a directory"), 0); - curbuf->b_p_ro = true; // must use "w!" now - } else { -#endif if (!newfile) { return FAIL; } @@ -603,9 +604,6 @@ readfile( return FAIL; } -#ifndef UNIX - } -#endif /* * Only set the 'ro' flag for readonly files the first time they are @@ -1013,8 +1011,21 @@ retry: */ { if (!skip_read) { - size = 0x10000L; /* use buffer >= 64K */ + // Use buffer >= 64K. Add linerest to double the size if the + // line gets very long, to avoid a lot of copying. But don't + // read more than 1 Mbyte at a time, so we can be interrupted. + size = 0x10000L + linerest; + if (size > 0x100000L) { + size = 0x100000L; + } + } + // Protect against the argument of lalloc() going negative. + if (size < 0 || size + linerest + 1 < 0 || linerest >= MAXCOL) { + split++; + *ptr = NL; // split line by inserting a NL + size = 1; + } else if (!skip_read) { for (; size >= 10; size /= 2) { new_buffer = verbose_try_malloc((size_t)size + (size_t)linerest + 1); if (new_buffer) { @@ -1783,6 +1794,7 @@ failed: linecnt--; } curbuf->deleted_bytes = 0; + curbuf->deleted_bytes2 = 0; curbuf->deleted_codepoints = 0; curbuf->deleted_codeunits = 0; linecnt = curbuf->b_ml.ml_line_count - linecnt; @@ -1824,25 +1836,14 @@ failed: c = false; #ifdef UNIX -# ifdef S_ISFIFO - if (S_ISFIFO(perm)) { /* fifo or socket */ - STRCAT(IObuff, _("[fifo/socket]")); - c = TRUE; - } -# else -# ifdef S_IFIFO - if ((perm & S_IFMT) == S_IFIFO) { /* fifo */ + if (S_ISFIFO(perm)) { // fifo STRCAT(IObuff, _("[fifo]")); c = TRUE; } -# endif -# ifdef S_IFSOCK - if ((perm & S_IFMT) == S_IFSOCK) { /* or socket */ + if (S_ISSOCK(perm)) { // or socket STRCAT(IObuff, _("[socket]")); c = TRUE; } -# endif -# endif # ifdef OPEN_CHR_FILES if (S_ISCHR(perm)) { /* or character special */ STRCAT(IObuff, _("[character special]")); @@ -1862,6 +1863,10 @@ failed: STRCAT(IObuff, _("[CR missing]")); c = TRUE; } + if (split) { + STRCAT(IObuff, _("[long lines split]")); + c = true; + } if (notconverted) { STRCAT(IObuff, _("[NOT converted]")); c = TRUE; @@ -3573,7 +3578,7 @@ restore_backup: * the backup file our 'original' file. */ if (*p_pm && dobackup) { - char *org = modname((char *)fname, (char *)p_pm, FALSE); + char *const org = modname((char *)fname, (char *)p_pm, false); if (backup != NULL) { /* @@ -5535,7 +5540,6 @@ static void au_del_cmd(AutoCmd *ac) static void au_cleanup(void) { AutoPat *ap, **prev_ap; - AutoCmd *ac, **prev_ac; event_T event; if (autocmd_busy || !au_need_clean) { @@ -5548,11 +5552,11 @@ static void au_cleanup(void) // Loop over all autocommand patterns. prev_ap = &(first_autopat[(int)event]); for (ap = *prev_ap; ap != NULL; ap = *prev_ap) { - // Loop over all commands for this pattern. - prev_ac = &(ap->cmds); bool has_cmd = false; - for (ac = *prev_ac; ac != NULL; ac = *prev_ac) { + // Loop over all commands for this pattern. + AutoCmd **prev_ac = &(ap->cmds); + for (AutoCmd *ac = *prev_ac; ac != NULL; ac = *prev_ac) { // Remove the command if the pattern is to be deleted or when // the command has been marked for deletion. if (ap->pat == NULL || ac->cmd == NULL) { diff --git a/src/nvim/fold.c b/src/nvim/fold.c index 61a85171e8..24a73a5b9f 100644 --- a/src/nvim/fold.c +++ b/src/nvim/fold.c @@ -153,14 +153,22 @@ bool hasFolding(linenr_T lnum, linenr_T *firstp, linenr_T *lastp) return hasFoldingWin(curwin, lnum, firstp, lastp, true, NULL); } -/* hasFoldingWin() {{{2 */ +// hasFoldingWin() {{{2 +/// Search folds starting at lnum +/// @param lnum first line to search +/// @param[out] first first line of fold containing lnum +/// @param[out] lastp last line with a fold +/// @param cache when true: use cached values of window +/// @param[out] infop where to store fold info +/// +/// @return true if range contains folds bool hasFoldingWin( win_T *const win, const linenr_T lnum, linenr_T *const firstp, linenr_T *const lastp, - const bool cache, // when true: use cached values of window - foldinfo_T *const infop // where to store fold info + const bool cache, + foldinfo_T *const infop ) { bool had_folded = false; @@ -280,26 +288,31 @@ int foldLevel(linenr_T lnum) // Return false if line is not folded. bool lineFolded(win_T *const win, const linenr_T lnum) { - return foldedCount(win, lnum, NULL) != 0; + return fold_info(win, lnum).fi_lines != 0; } -/* foldedCount() {{{2 */ -/* - * Count the number of lines that are folded at line number "lnum". - * Normally "lnum" is the first line of a possible fold, and the returned - * number is the number of lines in the fold. - * Doesn't use caching from the displayed window. - * Returns number of folded lines from "lnum", or 0 if line is not folded. - * When "infop" is not NULL, fills *infop with the fold level info. - */ -long foldedCount(win_T *win, linenr_T lnum, foldinfo_T *infop) +/// fold_info() {{{2 +/// +/// Count the number of lines that are folded at line number "lnum". +/// Normally "lnum" is the first line of a possible fold, and the returned +/// number is the number of lines in the fold. +/// Doesn't use caching from the displayed window. +/// +/// @return with the fold level info. +/// fi_lines = number of folded lines from "lnum", +/// or 0 if line is not folded. +foldinfo_T fold_info(win_T *win, linenr_T lnum) { + foldinfo_T info; linenr_T last; - if (hasFoldingWin(win, lnum, NULL, &last, false, infop)) { - return (long)(last - lnum + 1); + if (hasFoldingWin(win, lnum, NULL, &last, false, &info)) { + info.fi_lines = (long)(last - lnum + 1); + } else { + info.fi_lines = 0; } - return 0; + + return info; } /* foldmethodIsManual() {{{2 */ @@ -356,23 +369,21 @@ int foldmethodIsDiff(win_T *wp) return wp->w_p_fdm[0] == 'd'; } -/* closeFold() {{{2 */ -/* - * Close fold for current window at line "lnum". - * Repeat "count" times. - */ -void closeFold(linenr_T lnum, long count) +// closeFold() {{{2 +/// Close fold for current window at line "lnum". +/// Repeat "count" times. +void closeFold(pos_T pos, long count) { - setFoldRepeat(lnum, count, FALSE); + setFoldRepeat(pos, count, false); } /* closeFoldRecurse() {{{2 */ /* * Close fold for current window at line "lnum" recursively. */ -void closeFoldRecurse(linenr_T lnum) +void closeFoldRecurse(pos_T pos) { - (void)setManualFold(lnum, FALSE, TRUE, NULL); + (void)setManualFold(pos, false, true, NULL); } /* opFoldRange() {{{2 */ @@ -382,28 +393,32 @@ void closeFoldRecurse(linenr_T lnum) */ void opFoldRange( - linenr_T first, - linenr_T last, + pos_T firstpos, + pos_T lastpos, int opening, // TRUE to open, FALSE to close int recurse, // TRUE to do it recursively int had_visual // TRUE when Visual selection used ) { - int done = DONE_NOTHING; /* avoid error messages */ + int done = DONE_NOTHING; // avoid error messages + linenr_T first = firstpos.lnum; + linenr_T last = lastpos.lnum; linenr_T lnum; linenr_T lnum_next; for (lnum = first; lnum <= last; lnum = lnum_next + 1) { + pos_T temp = { lnum, 0, 0 }; lnum_next = lnum; /* Opening one level only: next fold to open is after the one going to * be opened. */ if (opening && !recurse) (void)hasFolding(lnum, NULL, &lnum_next); - (void)setManualFold(lnum, opening, recurse, &done); - /* Closing one level only: next line to close a fold is after just - * closed fold. */ - if (!opening && !recurse) + (void)setManualFold(temp, opening, recurse, &done); + // Closing one level only: next line to close a fold is after just + // closed fold. + if (!opening && !recurse) { (void)hasFolding(lnum, NULL, &lnum_next); + } } if (done == DONE_NOTHING) EMSG(_(e_nofold)); @@ -417,18 +432,18 @@ opFoldRange( * Open fold for current window at line "lnum". * Repeat "count" times. */ -void openFold(linenr_T lnum, long count) +void openFold(pos_T pos, long count) { - setFoldRepeat(lnum, count, TRUE); + setFoldRepeat(pos, count, true); } /* openFoldRecurse() {{{2 */ /* * Open fold for current window at line "lnum" recursively. */ -void openFoldRecurse(linenr_T lnum) +void openFoldRecurse(pos_T pos) { - (void)setManualFold(lnum, TRUE, TRUE, NULL); + (void)setManualFold(pos, true, true, NULL); } /* foldOpenCursor() {{{2 */ @@ -443,9 +458,10 @@ void foldOpenCursor(void) if (hasAnyFolding(curwin)) for (;; ) { done = DONE_NOTHING; - (void)setManualFold(curwin->w_cursor.lnum, TRUE, FALSE, &done); - if (!(done & DONE_ACTION)) + (void)setManualFold(curwin->w_cursor, true, false, &done); + if (!(done & DONE_ACTION)) { break; + } } } @@ -542,21 +558,21 @@ int foldManualAllowed(int create) // foldCreate() {{{2 /// Create a fold from line "start" to line "end" (inclusive) in the current /// window. -void foldCreate(win_T *wp, linenr_T start, linenr_T end) +void foldCreate(win_T *wp, pos_T start, pos_T end) { fold_T *fp; garray_T *gap; garray_T fold_ga; - int i, j; + int i; int cont; int use_level = FALSE; int closed = FALSE; int level = 0; - linenr_T start_rel = start; - linenr_T end_rel = end; + pos_T start_rel = start; + pos_T end_rel = end; - if (start > end) { - /* reverse the range */ + if (start.lnum > end.lnum) { + // reverse the range end = start_rel; start = end_rel; start_rel = start; @@ -573,60 +589,74 @@ void foldCreate(win_T *wp, linenr_T start, linenr_T end) // Find the place to insert the new fold gap = &wp->w_folds; - for (;; ) { - if (!foldFind(gap, start_rel, &fp)) - break; - if (fp->fd_top + fp->fd_len > end_rel) { - /* New fold is completely inside this fold: Go one level deeper. */ - gap = &fp->fd_nested; - start_rel -= fp->fd_top; - end_rel -= fp->fd_top; - if (use_level || fp->fd_flags == FD_LEVEL) { - use_level = true; - if (level >= wp->w_p_fdl) { + if (gap->ga_len == 0) { + i = 0; + } else { + for (;;) { + if (!foldFind(gap, start_rel.lnum, &fp)) { + break; + } + if (fp->fd_top + fp->fd_len > end_rel.lnum) { + // New fold is completely inside this fold: Go one level deeper. + gap = &fp->fd_nested; + start_rel.lnum -= fp->fd_top; + end_rel.lnum -= fp->fd_top; + if (use_level || fp->fd_flags == FD_LEVEL) { + use_level = true; + if (level >= wp->w_p_fdl) { + closed = true; + } + } else if (fp->fd_flags == FD_CLOSED) { closed = true; } - } else if (fp->fd_flags == FD_CLOSED) { - closed = true; + level++; + } else { + // This fold and new fold overlap: Insert here and move some folds + // inside the new fold. + break; } - level++; + } + if (gap->ga_len == 0) { + i = 0; } else { - /* This fold and new fold overlap: Insert here and move some folds - * inside the new fold. */ - break; + i = (int)(fp - (fold_T *)gap->ga_data); } } - i = (int)(fp - (fold_T *)gap->ga_data); ga_grow(gap, 1); { fp = (fold_T *)gap->ga_data + i; ga_init(&fold_ga, (int)sizeof(fold_T), 10); - /* Count number of folds that will be contained in the new fold. */ - for (cont = 0; i + cont < gap->ga_len; ++cont) - if (fp[cont].fd_top > end_rel) + // Count number of folds that will be contained in the new fold. + for (cont = 0; i + cont < gap->ga_len; cont++) { + if (fp[cont].fd_top > end_rel.lnum) { break; + } + } if (cont > 0) { ga_grow(&fold_ga, cont); /* If the first fold starts before the new fold, let the new fold * start there. Otherwise the existing fold would change. */ - if (start_rel > fp->fd_top) - start_rel = fp->fd_top; - - /* When last contained fold isn't completely contained, adjust end - * of new fold. */ - if (end_rel < fp[cont - 1].fd_top + fp[cont - 1].fd_len - 1) - end_rel = fp[cont - 1].fd_top + fp[cont - 1].fd_len - 1; - /* Move contained folds to inside new fold. */ + if (start_rel.lnum > fp->fd_top) { + start_rel.lnum = fp->fd_top; + } + + // When last contained fold isn't completely contained, adjust end + // of new fold. + if (end_rel.lnum < fp[cont - 1].fd_top + fp[cont - 1].fd_len - 1) { + end_rel.lnum = fp[cont - 1].fd_top + fp[cont - 1].fd_len - 1; + } + // Move contained folds to inside new fold memmove(fold_ga.ga_data, fp, sizeof(fold_T) * (size_t)cont); fold_ga.ga_len += cont; i += cont; /* Adjust line numbers in contained folds to be relative to the * new fold. */ - for (j = 0; j < cont; ++j) - ((fold_T *)fold_ga.ga_data)[j].fd_top -= start_rel; + for (int j = 0; j < cont; j++) { + ((fold_T *)fold_ga.ga_data)[j].fd_top -= start_rel.lnum; + } } /* Move remaining entries to after the new fold. */ if (i < gap->ga_len) @@ -636,8 +666,8 @@ void foldCreate(win_T *wp, linenr_T start, linenr_T end) /* insert new fold */ fp->fd_nested = fold_ga; - fp->fd_top = start_rel; - fp->fd_len = end_rel - start_rel + 1; + fp->fd_top = start_rel.lnum; + fp->fd_len = end_rel.lnum - start_rel.lnum + 1; /* We want the new fold to be closed. If it would remain open because * of using 'foldlevel', need to adjust fd_flags of containing folds. @@ -766,7 +796,7 @@ void deleteFold( */ void clearFolding(win_T *win) { - deleteFoldRecurse(&win->w_folds); + deleteFoldRecurse(win->w_buffer, &win->w_folds); win->w_foldinvalid = false; } @@ -788,13 +818,15 @@ void foldUpdate(win_T *wp, linenr_T top, linenr_T bot) return; } - // Mark all folds from top to bot as maybe-small. - fold_T *fp; - (void)foldFind(&wp->w_folds, top, &fp); - while (fp < (fold_T *)wp->w_folds.ga_data + wp->w_folds.ga_len - && fp->fd_top < bot) { - fp->fd_small = kNone; - fp++; + if (wp->w_folds.ga_len > 0) { + // Mark all folds from top to bot as maybe-small. + fold_T *fp; + (void)foldFind(&wp->w_folds, top, &fp); + while (fp < (fold_T *)wp->w_folds.ga_data + wp->w_folds.ga_len + && fp->fd_top < bot) { + fp->fd_small = kNone; + fp++; + } } if (foldmethodIsIndent(wp) @@ -834,7 +866,7 @@ void foldUpdateAfterInsert(void) void foldUpdateAll(win_T *win) { win->w_foldinvalid = true; - redraw_win_later(win, NOT_VALID); + redraw_later(win, NOT_VALID); } // foldMoveTo() {{{2 @@ -860,6 +892,9 @@ int foldMoveTo( // that moves the cursor is used. linenr_T lnum_off = 0; garray_T *gap = &curwin->w_folds; + if (gap->ga_len == 0) { + break; + } bool use_level = false; bool maybe_small = false; linenr_T lnum_found = curwin->w_cursor.lnum; @@ -1058,11 +1093,16 @@ void cloneFoldGrowArray(garray_T *from, garray_T *to) * the first fold below it (careful: it can be beyond the end of the array!). * Returns FALSE when there is no fold that contains "lnum". */ -static int foldFind(const garray_T *gap, linenr_T lnum, fold_T **fpp) +static bool foldFind(const garray_T *gap, linenr_T lnum, fold_T **fpp) { linenr_T low, high; fold_T *fp; + if (gap->ga_len == 0) { + *fpp = NULL; + return false; + } + /* * Perform a binary search. * "low" is lowest index of possible match. @@ -1086,7 +1126,7 @@ static int foldFind(const garray_T *gap, linenr_T lnum, fold_T **fpp) } } *fpp = fp + low; - return FALSE; + return false; } /* foldLevelWin() {{{2 */ @@ -1131,14 +1171,14 @@ static void checkupdate(win_T *wp) * Open or close fold for current window at line "lnum". * Repeat "count" times. */ -static void setFoldRepeat(linenr_T lnum, long count, int do_open) +static void setFoldRepeat(pos_T pos, long count, int do_open) { int done; long n; for (n = 0; n < count; ++n) { done = DONE_NOTHING; - (void)setManualFold(lnum, do_open, FALSE, &done); + (void)setManualFold(pos, do_open, false, &done); if (!(done & DONE_ACTION)) { /* Only give an error message when no fold could be opened. */ if (n == 0 && !(done & DONE_FOLD)) @@ -1155,12 +1195,13 @@ static void setFoldRepeat(linenr_T lnum, long count, int do_open) */ static linenr_T setManualFold( - linenr_T lnum, + pos_T pos, int opening, // TRUE when opening, FALSE when closing int recurse, // TRUE when closing/opening recursive int *donep ) { + linenr_T lnum = pos.lnum; if (foldmethodIsDiff(curwin) && curwin->w_p_scb) { linenr_T dlnum; @@ -1220,9 +1261,10 @@ setManualFoldWin( gap = &wp->w_folds; for (;; ) { if (!foldFind(gap, lnum, &fp)) { - /* If there is a following fold, continue there next time. */ - if (fp < (fold_T *)gap->ga_data + gap->ga_len) + // If there is a following fold, continue there next time. + if (fp != NULL && fp < (fold_T *)gap->ga_data + gap->ga_len) { next = fp->fd_top + off; + } break; } @@ -1313,7 +1355,7 @@ static void deleteFoldEntry(win_T *const wp, garray_T *const gap, const int idx, fold_T *fp = (fold_T *)gap->ga_data + idx; if (recursive || GA_EMPTY(&fp->fd_nested)) { // recursively delete the contained folds - deleteFoldRecurse(&fp->fd_nested); + deleteFoldRecurse(wp->w_buffer, &fp->fd_nested); gap->ga_len--; if (idx < gap->ga_len) { memmove(fp, fp + 1, sizeof(*fp) * (size_t)(gap->ga_len - idx)); @@ -1355,9 +1397,9 @@ static void deleteFoldEntry(win_T *const wp, garray_T *const gap, const int idx, /* * Delete nested folds in a fold. */ -void deleteFoldRecurse(garray_T *gap) +void deleteFoldRecurse(buf_T *bp, garray_T *gap) { -# define DELETE_FOLD_NESTED(fd) deleteFoldRecurse(&((fd)->fd_nested)) +# define DELETE_FOLD_NESTED(fd) deleteFoldRecurse(bp, &((fd)->fd_nested)) GA_DEEP_CLEAR(gap, fold_T, DELETE_FOLD_NESTED); } @@ -1389,6 +1431,10 @@ static void foldMarkAdjustRecurse( linenr_T last; linenr_T top; + if (gap->ga_len == 0) { + return; + } + /* In Insert mode an inserted line at the top of a fold is considered part * of the fold, otherwise it isn't. */ if ((State & INSERT) && amount == (linenr_T)1 && line2 == MAXLNUM) @@ -1593,7 +1639,7 @@ static void setSmallMaybe(garray_T *gap) * Create a fold from line "start" to line "end" (inclusive) in the current * window by adding markers. */ -static void foldCreateMarkers(win_T *wp, linenr_T start, linenr_T end) +static void foldCreateMarkers(win_T *wp, pos_T start, pos_T end) { buf_T *buf = wp->w_buffer; if (!MODIFIABLE(buf)) { @@ -1608,13 +1654,13 @@ static void foldCreateMarkers(win_T *wp, linenr_T start, linenr_T end) /* Update both changes here, to avoid all folds after the start are * changed when the start marker is inserted and the end isn't. */ // TODO(teto): pass the buffer - changed_lines(start, (colnr_T)0, end, 0L, false); + changed_lines(start.lnum, (colnr_T)0, end.lnum, 0L, false); // Note: foldAddMarker() may not actually change start and/or end if // u_save() is unable to save the buffer line, but we send the // nvim_buf_lines_event anyway since it won't do any harm. - int64_t num_changed = 1 + end - start; - buf_updates_send_changes(buf, start, num_changed, num_changed, true); + int64_t num_changed = 1 + end.lnum - start.lnum; + buf_updates_send_changes(buf, start.lnum, num_changed, num_changed, true); } /* foldAddMarker() {{{2 */ @@ -1622,13 +1668,14 @@ static void foldCreateMarkers(win_T *wp, linenr_T start, linenr_T end) * Add "marker[markerlen]" in 'commentstring' to line "lnum". */ static void foldAddMarker( - buf_T *buf, linenr_T lnum, const char_u *marker, size_t markerlen) + buf_T *buf, pos_T pos, const char_u *marker, size_t markerlen) { char_u *cms = buf->b_p_cms; char_u *line; char_u *newline; char_u *p = (char_u *)strstr((char *)buf->b_p_cms, "%s"); bool line_is_comment = false; + linenr_T lnum = pos.lnum; // Allocate a new line: old-line + 'cms'-start + marker + 'cms'-end line = ml_get_buf(buf, lnum, false); @@ -1727,19 +1774,23 @@ static void foldDelMarker( STRCPY(newline + (p - line), p + len); ml_replace_buf(buf, lnum, newline, false); extmark_splice_cols(buf, (int)lnum-1, (int)(p - line), - (int)len, - 0, kExtmarkUndo); + (int)len, 0, kExtmarkUndo); } break; } } // get_foldtext() {{{2 -/// Return the text for a closed fold at line "lnum", with last line "lnume". -/// When 'foldtext' isn't set puts the result in "buf[FOLD_TEXT_LEN]". +/// Generates text to display +/// +/// @param buf allocated memory of length FOLD_TEXT_LEN. Used when 'foldtext' +/// isn't set puts the result in "buf[FOLD_TEXT_LEN]". +/// @param at line "lnum", with last line "lnume". +/// @return the text for a closed fold +/// /// Otherwise the result is in allocated memory. char_u *get_foldtext(win_T *wp, linenr_T lnum, linenr_T lnume, - foldinfo_T *foldinfo, char_u *buf) + foldinfo_T foldinfo, char_u *buf) FUNC_ATTR_NONNULL_ARG(1) { char_u *text = NULL; @@ -1767,11 +1818,12 @@ char_u *get_foldtext(win_T *wp, linenr_T lnum, linenr_T lnume, set_vim_var_nr(VV_FOLDSTART, (varnumber_T) lnum); set_vim_var_nr(VV_FOLDEND, (varnumber_T) lnume); - /* Set "v:folddashes" to a string of "level" dashes. */ - /* Set "v:foldlevel" to "level". */ - level = foldinfo->fi_level; - if (level > (int)sizeof(dashes) - 1) + // Set "v:folddashes" to a string of "level" dashes. + // Set "v:foldlevel" to "level". + level = foldinfo.fi_level; + if (level > (int)sizeof(dashes) - 1) { level = (int)sizeof(dashes) - 1; + } memset(dashes, '-', (size_t)level); dashes[level] = NUL; set_vim_var_string(VV_FOLDDASHES, dashes, -1); @@ -2265,14 +2317,15 @@ static linenr_T foldUpdateIEMSRecurse( /* Find an existing fold to re-use. Preferably one that * includes startlnum, otherwise one that ends just before * startlnum or starts after it. */ - if (foldFind(gap, startlnum, &fp) - || (fp < ((fold_T *)gap->ga_data) + gap->ga_len - && fp->fd_top <= firstlnum) - || foldFind(gap, firstlnum - concat, &fp) - || (fp < ((fold_T *)gap->ga_data) + gap->ga_len - && ((lvl < level && fp->fd_top < flp->lnum) - || (lvl >= level - && fp->fd_top <= flp->lnum_save)))) { + if (gap->ga_len > 0 + && (foldFind(gap, startlnum, &fp) + || (fp < ((fold_T *)gap->ga_data) + gap->ga_len + && fp->fd_top <= firstlnum) + || foldFind(gap, firstlnum - concat, &fp) + || (fp < ((fold_T *)gap->ga_data) + gap->ga_len + && ((lvl < level && fp->fd_top < flp->lnum) + || (lvl >= level + && fp->fd_top <= flp->lnum_save))))) { if (fp->fd_top + fp->fd_len + concat > firstlnum) { /* Use existing fold for the new fold. If it starts * before where we started looking, extend it. If it @@ -2363,7 +2416,11 @@ static linenr_T foldUpdateIEMSRecurse( } else { /* Insert new fold. Careful: ga_data may be NULL and it * may change! */ - i = (int)(fp - (fold_T *)gap->ga_data); + if (gap->ga_len == 0) { + i = 0; + } else { + i = (int)(fp - (fold_T *)gap->ga_data); + } foldInsert(gap, i); fp = (fold_T *)gap->ga_data + i; /* The new fold continues until bot, unless we find the @@ -2559,9 +2616,10 @@ static void foldInsert(garray_T *gap, int i) ga_grow(gap, 1); fp = (fold_T *)gap->ga_data + i; - if (i < gap->ga_len) + if (gap->ga_len > 0 && i < gap->ga_len) { memmove(fp + 1, fp, sizeof(fold_T) * (size_t)(gap->ga_len - i)); - ++gap->ga_len; + } + gap->ga_len++; ga_init(&fp->fd_nested, (int)sizeof(fold_T), 10); } @@ -2596,17 +2654,18 @@ static void foldSplit(buf_T *buf, garray_T *const gap, * any between top and bot, they have been removed by the caller. */ garray_T *const gap1 = &fp->fd_nested; garray_T *const gap2 = &fp[1].fd_nested; - (void)(foldFind(gap1, bot + 1 - fp->fd_top, &fp2)); - const int len = (int)((fold_T *)gap1->ga_data + gap1->ga_len - fp2); - if (len > 0) { - ga_grow(gap2, len); - for (int idx = 0; idx < len; idx++) { - ((fold_T *)gap2->ga_data)[idx] = fp2[idx]; - ((fold_T *)gap2->ga_data)[idx].fd_top - -= fp[1].fd_top - fp->fd_top; - } - gap2->ga_len = len; - gap1->ga_len -= len; + if (foldFind(gap1, bot + 1 - fp->fd_top, &fp2)) { + const int len = (int)((fold_T *)gap1->ga_data + gap1->ga_len - fp2); + if (len > 0) { + ga_grow(gap2, len); + for (int idx = 0; idx < len; idx++) { + ((fold_T *)gap2->ga_data)[idx] = fp2[idx]; + ((fold_T *)gap2->ga_data)[idx].fd_top + -= fp[1].fd_top - fp->fd_top; + } + gap2->ga_len = len; + gap1->ga_len -= len; + } } fp->fd_len = top - fp->fd_top; fold_changed = true; @@ -2641,7 +2700,7 @@ static void foldRemove( return; // nothing to do } - for (;; ) { + while (gap->ga_len > 0) { // Find fold that includes top or a following one. if (foldFind(gap, top, &fp) && fp->fd_top < top) { // 2: or 3: need to delete nested folds @@ -2657,7 +2716,8 @@ static void foldRemove( fold_changed = true; continue; } - if (fp >= (fold_T *)(gap->ga_data) + gap->ga_len + if (gap->ga_data == NULL + || fp >= (fold_T *)(gap->ga_data) + gap->ga_len || fp->fd_top > bot) { // 6: Found a fold below bot, can stop looking. break; @@ -2738,7 +2798,8 @@ static void truncate_fold(win_T *const wp, fold_T *fp, linenr_T end) } #define FOLD_END(fp) ((fp)->fd_top + (fp)->fd_len - 1) -#define VALID_FOLD(fp, gap) ((fp) < ((fold_T *)(gap)->ga_data + (gap)->ga_len)) +#define VALID_FOLD(fp, gap) \ + ((gap)->ga_len > 0 && (fp) < ((fold_T *)(gap)->ga_data + (gap)->ga_len)) #define FOLD_INDEX(fp, gap) ((size_t)(fp - ((fold_T *)(gap)->ga_data))) void foldMoveRange( win_T *const wp, garray_T *gap, diff --git a/src/nvim/fold.h b/src/nvim/fold.h index f35b328fb1..95c4b0c1dc 100644 --- a/src/nvim/fold.h +++ b/src/nvim/fold.h @@ -18,6 +18,7 @@ typedef struct foldinfo { other fields are invalid */ int fi_low_level; /* lowest fold level that starts in the same line */ + long fi_lines; } foldinfo_T; diff --git a/src/nvim/generators/gen_api_dispatch.lua b/src/nvim/generators/gen_api_dispatch.lua index 6e80ad0e5c..b31209e8ff 100644 --- a/src/nvim/generators/gen_api_dispatch.lua +++ b/src/nvim/generators/gen_api_dispatch.lua @@ -250,7 +250,7 @@ for i = 1, #functions do end output:write('\n } else {') output:write('\n api_set_error(error, kErrorTypeException, \ - "Wrong type for argument '..j..', expecting '..param[1]..'");') + "Wrong type for argument '..j..' when calling '..fn.name..', expecting '..param[1]..'");') output:write('\n goto cleanup;') output:write('\n }\n') else diff --git a/src/nvim/generators/gen_ex_cmds.lua b/src/nvim/generators/gen_ex_cmds.lua index 075d8ba9cc..849c82f50e 100644 --- a/src/nvim/generators/gen_ex_cmds.lua +++ b/src/nvim/generators/gen_ex_cmds.lua @@ -24,8 +24,6 @@ local defsfile = io.open(defsfname, 'w') local defs = require('ex_cmds') -local first = true - local byte_a = string.byte('a') local byte_z = string.byte('z') local a_to_z = byte_z - byte_a + 1 @@ -41,8 +39,7 @@ static const uint16_t cmdidxs1[%u] = { -- fit in a byte. local cmdidxs2_out = string.format([[ static const char_u cmdidxs2[%u][%u] = { -/* a b c d e f g h i j k l m n o p q r s t u v w x y z */ - + /* a b c d e f g h i j k l m n o p q r s t u v w x y z */ ]], a_to_z, a_to_z) enumfile:write([[ @@ -50,10 +47,8 @@ typedef enum CMD_index { ]]) defsfile:write(string.format([[ static const int command_count = %u; -]], #defs)) -defsfile:write(string.format([[ static CommandDefinition cmdnames[%u] = { -]], #defs)) +]], #defs, #defs)) local cmds, cmdidxs1, cmdidxs2 = {}, {}, {} for _, cmd in ipairs(defs) do local enumname = cmd.enum or ('CMD_' .. cmd.command) @@ -61,11 +56,6 @@ for _, cmd in ipairs(defs) do if byte_a <= byte_cmd and byte_cmd <= byte_z then table.insert(cmds, cmd.command) end - if first then - first = false - else - defsfile:write(',\n') - end enumfile:write(' ' .. enumname .. ',\n') defsfile:write(string.format([[ [%s] = { @@ -73,7 +63,8 @@ for _, cmd in ipairs(defs) do .cmd_func = (ex_func_T)&%s, .cmd_argt = %uL, .cmd_addr_type = %i - }]], enumname, cmd.command, cmd.func, cmd.flags, cmd.addr_type)) + }, +]], enumname, cmd.command, cmd.func, cmd.flags, cmd.addr_type)) end for i = #cmds, 1, -1 do local cmd = cmds[i] @@ -104,15 +95,14 @@ for i = byte_a, byte_z do end cmdidxs2_out = cmdidxs2_out .. ' },\n' end -defsfile:write([[ - -}; -]]) enumfile:write([[ CMD_SIZE, CMD_USER = -1, CMD_USER_BUF = -2 } cmdidx_T; ]]) -defsfile:write(cmdidxs1_out .. '};\n') -defsfile:write(cmdidxs2_out .. '};\n') +defsfile:write(string.format([[ +}; +%s}; +%s}; +]], cmdidxs1_out, cmdidxs2_out)) diff --git a/src/nvim/generators/gen_options.lua b/src/nvim/generators/gen_options.lua index a8cf496cb9..d80a6219eb 100644 --- a/src/nvim/generators/gen_options.lua +++ b/src/nvim/generators/gen_options.lua @@ -141,9 +141,6 @@ local dump_option = function(i, o) elseif #o.scope == 1 and o.scope[1] == 'window' then w(' .var=VAR_WIN') end - if o.enable_if then - w('#endif') - end if #o.scope == 1 and o.scope[1] == 'global' then w(' .indir=PV_NONE') else @@ -163,6 +160,12 @@ local dump_option = function(i, o) defines['PV_' .. varname:sub(3):upper()] = pv_name w(' .indir=' .. pv_name) end + if o.enable_if then + w('#else') + w(' .var=NULL') + w(' .indir=PV_NONE') + w('#endif') + end if o.defaults then if o.defaults.condition then w(get_cond(o.defaults.condition)) diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index 5ab5a7db1b..456979be00 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -27,6 +27,7 @@ #include "nvim/ex_docmd.h" #include "nvim/ex_getln.h" #include "nvim/func_attr.h" +#include "nvim/lua/executor.h" #include "nvim/main.h" #include "nvim/mbyte.h" #include "nvim/memline.h" @@ -455,6 +456,9 @@ void flush_buffers(flush_buffers_T flush_typeahead) typebuf.tb_silent = 0; cmd_silent = false; typebuf.tb_no_abbr_cnt = 0; + if (++typebuf.tb_change_cnt == 0) { + typebuf.tb_change_cnt = 1; + } } /* @@ -1524,6 +1528,17 @@ int vgetc(void) c = utf_ptr2char(buf); } + // If mappings are enabled (i.e., not Ctrl-v) and the user directly typed + // something with a meta- or alt- modifier that was not mapped, interpret + // <M-x> as <Esc>x rather than as an unbound meta keypress. #8213 + if (!no_mapping && KeyTyped + && (mod_mask == MOD_MASK_ALT || mod_mask == MOD_MASK_META)) { + mod_mask = 0; + stuffcharReadbuff(c); + u_sync(false); + c = ESC; + } + break; } } @@ -1535,6 +1550,9 @@ int vgetc(void) */ may_garbage_collect = false; + // Exec lua callbacks for on_keystroke + nlua_execute_log_keystroke(c); + return c; } @@ -2037,14 +2055,19 @@ static int vgetorpeek(bool advance) */ if (mp->m_expr) { int save_vgetc_busy = vgetc_busy; + const bool save_may_garbage_collect = may_garbage_collect; vgetc_busy = 0; + may_garbage_collect = false; + save_m_keys = vim_strsave(mp->m_keys); save_m_str = vim_strsave(mp->m_str); s = eval_map_expr(save_m_str, NUL); vgetc_busy = save_vgetc_busy; - } else + may_garbage_collect = save_may_garbage_collect; + } else { s = mp->m_str; + } /* * Insert the 'to' part in the typebuf.tb_buf. @@ -4181,7 +4204,6 @@ int put_escstr(FILE *fd, char_u *strstart, int what) { char_u *str = strstart; int c; - int modifiers; // :map xx <Nop> if (*str == NUL && what == 1) { @@ -4208,7 +4230,7 @@ int put_escstr(FILE *fd, char_u *strstart, int what) * when they are read back. */ if (c == K_SPECIAL && what != 2) { - modifiers = 0x0; + int modifiers = 0; if (str[1] == KS_MODIFIER) { modifiers = str[2]; str += 3; diff --git a/src/nvim/globals.h b/src/nvim/globals.h index d6d00d6e83..657afeaf4c 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -92,6 +92,10 @@ 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. @@ -125,8 +129,6 @@ typedef off_t off_T; EXTERN int mod_mask INIT(= 0x0); // current key modifiers -EXTERN bool lua_attr_active INIT(= false); - // Cmdline_row is the row where the command line starts, just below the // last window. // When the cmdline gets longer than the available space the screen gets @@ -208,7 +210,7 @@ EXTERN int need_clr_eos INIT(= false); // need to clear text before // displaying a message. EXTERN int emsg_skip INIT(= 0); // don't display errors for // expression that is skipped -EXTERN int emsg_severe INIT(= false); // use message of next of several +EXTERN bool emsg_severe INIT(= false); // use message of next of several // emsg() calls for throw EXTERN int did_endif INIT(= false); // just had ":endif" EXTERN dict_T vimvardict; // Dictionary with v: variables @@ -353,9 +355,11 @@ EXTERN int t_colors INIT(= 256); // int value of T_CCO // position. Search_match_lines is the number of lines after the match (0 for // a match within one line), search_match_endcol the column number of the // character just after the match in the last line. -EXTERN int highlight_match INIT(= false); // show search match pos -EXTERN linenr_T search_match_lines; // lines of of matched string -EXTERN colnr_T search_match_endcol; // col nr of match end +EXTERN bool highlight_match INIT(= false); // show search match pos +EXTERN linenr_T search_match_lines; // lines of of matched string +EXTERN colnr_T search_match_endcol; // col nr of match end +EXTERN linenr_T search_first_line INIT(= 0); // for :{FIRST},{last}s/pat +EXTERN linenr_T search_last_line INIT(= MAXLNUM); // for :{first},{LAST}s/pat EXTERN int no_smartcase INIT(= false); // don't use 'smartcase' once @@ -488,9 +492,6 @@ EXTERN int stdout_isatty INIT(= true); // volatile because it is used in a signal handler. EXTERN volatile int full_screen INIT(= false); -// When started in restricted mode (-Z). -EXTERN int restricted INIT(= false); - /// Non-zero when only "safe" commands are allowed, e.g. when sourcing .exrc or /// .vimrc in current directory. EXTERN int secure INIT(= false); @@ -941,8 +942,10 @@ EXTERN char_u e_readonly[] INIT(= N_( EXTERN char_u e_readonlyvar[] INIT(= N_( "E46: Cannot change read-only variable \"%.*s\"")); EXTERN char_u e_dictreq[] INIT(= N_("E715: Dictionary required")); -EXTERN char_u e_toomanyarg[] INIT(= N_("E118: Too many arguments for function: %s")); -EXTERN char_u e_dictkey[] INIT(= N_("E716: Key not present in Dictionary: %s")); +EXTERN char_u e_toomanyarg[] INIT(= N_( + "E118: Too many arguments for function: %s")); +EXTERN char_u e_dictkey[] INIT(= N_( + "E716: Key not present in Dictionary: \"%s\"")); EXTERN char_u e_listreq[] INIT(= N_("E714: List required")); EXTERN char_u e_listdictarg[] INIT(= N_( "E712: Argument of %s must be a List or Dictionary")); diff --git a/src/nvim/grid_defs.h b/src/nvim/grid_defs.h index c6687c8da9..e14aae73d8 100644 --- a/src/nvim/grid_defs.h +++ b/src/nvim/grid_defs.h @@ -11,7 +11,7 @@ // The characters and attributes drawn on grids. typedef char_u schar_T[(MAX_MCO+1) * 4 + 1]; -typedef int16_t sattr_T; +typedef int sattr_T; /// ScreenGrid represents a resizable rectuangular grid displayed by UI clients. /// diff --git a/src/nvim/hashtab.h b/src/nvim/hashtab.h index 19633d455f..c82a6cc121 100644 --- a/src/nvim/hashtab.h +++ b/src/nvim/hashtab.h @@ -51,6 +51,7 @@ typedef struct hashitem_S { /// Initial size for a hashtable. /// Our items are relatively small and growing is expensive, thus start with 16. /// Must be a power of 2. +/// This allows for storing 10 items (2/3 of 16) before a resize is needed. #define HT_INIT_SIZE 16 /// An array-based hashtable. diff --git a/src/nvim/highlight.c b/src/nvim/highlight.c index c0cae54572..898ff4ebfe 100644 --- a/src/nvim/highlight.c +++ b/src/nvim/highlight.c @@ -14,6 +14,7 @@ #include "nvim/ui.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" +#include "nvim/lua/executor.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "highlight.c.generated.h" @@ -28,12 +29,16 @@ static Map(int, int) *combine_attr_entries; static Map(int, int) *blend_attr_entries; static Map(int, int) *blendthrough_attr_entries; +/// highlight entries private to a namespace +static Map(ColorKey, ColorItem) *ns_hl; + void highlight_init(void) { attr_entry_ids = map_new(HlEntry, int)(); combine_attr_entries = map_new(int, int)(); blend_attr_entries = map_new(int, int)(); blendthrough_attr_entries = map_new(int, int)(); + ns_hl = map_new(ColorKey, ColorItem)(); // index 0 is no attribute, add dummy entry: kv_push(attr_entries, ((HlEntry){ .attr = HLATTRS_INIT, .kind = kHlUnknown, @@ -90,7 +95,12 @@ static int get_attr_entry(HlEntry entry) } } - id = (int)kv_size(attr_entries); + size_t next_id = kv_size(attr_entries); + if (next_id > INT_MAX) { + ELOG("The index on attr_entries has overflowed"); + return 0; + } + id = (int)next_id; kv_push(attr_entries, entry); map_put(HlEntry, int)(attr_entry_ids, entry, id); @@ -124,21 +134,114 @@ void ui_send_all_hls(UI *ui) } /// Get attribute code for a syntax group. -int hl_get_syn_attr(int idx, HlAttrs at_en) +int hl_get_syn_attr(int ns_id, int idx, HlAttrs at_en) { // TODO(bfredl): should we do this unconditionally if (at_en.cterm_fg_color != 0 || at_en.cterm_bg_color != 0 || at_en.rgb_fg_color != -1 || at_en.rgb_bg_color != -1 || at_en.rgb_sp_color != -1 || at_en.cterm_ae_attr != 0 - || at_en.rgb_ae_attr != 0) { + || at_en.rgb_ae_attr != 0 || ns_id != 0) { return get_attr_entry((HlEntry){ .attr = at_en, .kind = kHlSyntax, - .id1 = idx, .id2 = 0 }); + .id1 = idx, .id2 = ns_id }); } else { // If all the fields are cleared, clear the attr field back to default value return 0; } } +static ColorKey colored_key(NS ns_id, int syn_id) +{ + return (ColorKey){ .ns_id = (int)ns_id, .syn_id = syn_id }; +} + +void ns_hl_def(NS ns_id, int hl_id, HlAttrs attrs, int link_id) +{ + DecorProvider *p = get_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 }; + map_put(ColorKey, ColorItem)(ns_hl, colored_key(ns_id, hl_id), it); +} + +int ns_get_hl(NS ns_id, int hl_id, bool link) +{ + static int recursive = 0; + + if (ns_id < 0) { + if (ns_hl_active <= 0) { + return -1; + } + ns_id = ns_hl_active; + } + + DecorProvider *p = get_provider(ns_id, true); + ColorItem it = map_get(ColorKey, ColorItem)(ns_hl, colored_key(ns_id, hl_id)); + // TODO(bfredl): map_ref true even this? + bool valid_cache = it.version >= p->hl_valid; + + if (!valid_cache && p->hl_def != LUA_NOREF && !recursive) { + FIXED_TEMP_ARRAY(args, 3); + args.items[0] = INTEGER_OBJ((Integer)ns_id); + args.items[1] = STRING_OBJ(cstr_to_string((char *)syn_id2name(hl_id))); + args.items[2] = BOOLEAN_OBJ(link); + // TODO(bfredl): preload the "global" attr dict? + + Error err = ERROR_INIT; + recursive++; + Object ret = nlua_call_ref(p->hl_def, "hl_def", args, true, &err); + recursive--; + + // TODO(bfredl): or "inherit", combine with global value? + bool fallback = true; + int tmp = false; + HlAttrs attrs = HLATTRS_INIT; + if (ret.type == kObjectTypeDictionary) { + Dictionary dict = ret.data.dictionary; + fallback = false; + attrs = dict2hlattrs(dict, true, &it.link_id, &err); + for (size_t i = 0; i < dict.size; i++) { + char *key = dict.items[i].key.data; + Object val = dict.items[i].value; + bool truthy = api_object_to_bool(val, key, false, &err); + + if (strequal(key, "fallback")) { + fallback = truthy; + } else if (strequal(key, "temp")) { + tmp = truthy; + } + } + if (it.link_id >= 0) { + fallback = true; + } + } + + it.attr_id = fallback ? -1 : hl_get_syn_attr((int)ns_id, hl_id, attrs); + it.version = p->hl_valid-tmp; + map_put(ColorKey, ColorItem)(ns_hl, colored_key(ns_id, hl_id), it); + } + + if (link) { + return it.attr_id >= 0 ? -1 : it.link_id; + } else { + return it.attr_id; + } +} + + +bool win_check_ns_hl(win_T *wp) +{ + if (ns_hl_changed) { + highlight_changed(); + if (wp) { + update_window_hl(wp, true); + } + ns_hl_changed = false; + return true; + } + return false; +} + /// Get attribute code for a builtin highlight group. /// /// The final syntax group could be modified by hi-link or 'winhighlight'. @@ -199,6 +302,17 @@ void update_window_hl(win_T *wp, bool invalid) 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((const char_u *)S_LEN("Normal")); + int ns_attr = ns_get_hl(-1, normality, 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); @@ -270,6 +384,7 @@ void clear_hl_tables(bool reinit) map_free(int, int)(combine_attr_entries); map_free(int, int)(blend_attr_entries); map_free(int, int)(blendthrough_attr_entries); + map_free(ColorKey, ColorItem)(ns_hl); } } @@ -658,6 +773,103 @@ Dictionary hlattrs2dict(HlAttrs ae, bool use_rgb) return hl; } +HlAttrs dict2hlattrs(Dictionary dict, bool use_rgb, int *link_id, Error *err) +{ + HlAttrs hlattrs = HLATTRS_INIT; + + int32_t fg = -1, bg = -1, sp = -1; + int16_t mask = 0; + for (size_t i = 0; i < dict.size; i++) { + char *key = dict.items[i].key.data; + Object val = dict.items[i].value; + + struct { + const char *name; + int16_t flag; + } flags[] = { + { "bold", HL_BOLD }, + { "standout", HL_STANDOUT }, + { "underline", HL_UNDERLINE }, + { "undercurl", HL_UNDERCURL }, + { "italic", HL_ITALIC }, + { "reverse", HL_INVERSE }, + { NULL, 0 }, + }; + + int j; + for (j = 0; flags[j].name; j++) { + if (strequal(flags[j].name, key)) { + if (api_object_to_bool(val, key, false, err)) { + mask = mask | flags[j].flag; + } + break; + } + } + + struct { + const char *name; + const char *shortname; + int *dest; + } colors[] = { + { "foreground", "fg", &fg }, + { "background", "bg", &bg }, + { "special", "sp", &sp }, + { NULL, NULL, NULL }, + }; + + int k; + for (k = 0; (!flags[j].name) && colors[k].name; k++) { + if (strequal(colors[k].name, key) || strequal(colors[k].shortname, key)) { + if (val.type == kObjectTypeInteger) { + *colors[k].dest = (int)val.data.integer; + } else if (val.type == kObjectTypeString) { + String str = val.data.string; + // TODO(bfredl): be more fancy with "bg", "fg" etc + if (str.size) { + *colors[k].dest = name_to_color(str.data); + } + } else { + api_set_error(err, kErrorTypeValidation, + "'%s' must be string or integer", key); + } + break; + } + } + + + if (flags[j].name || colors[k].name) { + // handled above + } else if (link_id && strequal(key, "link")) { + if (val.type == kObjectTypeString) { + String str = val.data.string; + *link_id = syn_check_group((const char_u *)str.data, (int)str.size); + } else if (val.type == kObjectTypeInteger) { + // TODO(bfredl): validate range? + *link_id = (int)val.data.integer; + } else { + api_set_error(err, kErrorTypeValidation, + "'link' must be string or integer"); + } + } + + if (ERROR_SET(err)) { + return hlattrs; // error set, caller should not use retval + } + } + + if (use_rgb) { + hlattrs.rgb_ae_attr = mask; + hlattrs.rgb_bg_color = bg; + hlattrs.rgb_fg_color = fg; + hlattrs.rgb_sp_color = sp; + } else { + hlattrs.cterm_ae_attr = mask; + hlattrs.cterm_bg_color = bg == -1 ? cterm_normal_bg_color : bg + 1; + hlattrs.cterm_fg_color = fg == -1 ? cterm_normal_fg_color : fg + 1; + } + + return hlattrs; +} Array hl_inspect(int attr) { Array ret = ARRAY_DICT_INIT; diff --git a/src/nvim/highlight_defs.h b/src/nvim/highlight_defs.h index 36f3181674..6a5c593ab1 100644 --- a/src/nvim/highlight_defs.h +++ b/src/nvim/highlight_defs.h @@ -4,6 +4,7 @@ #include <inttypes.h> #include "nvim/macros.h" +#include "nvim/types.h" typedef int32_t RgbValue; @@ -180,6 +181,20 @@ typedef struct { HlKind kind; int id1; int id2; + int winid; } HlEntry; +typedef struct { + int ns_id; + int syn_id; +} ColorKey; + +typedef struct { + int attr_id; + int link_id; + int version; +} ColorItem; +#define COLOR_ITEM_INITIALIZER { .attr_id = -1, .link_id = -1, .version = -1 } + + #endif // NVIM_HIGHLIGHT_DEFS_H diff --git a/src/nvim/if_cscope.c b/src/nvim/if_cscope.c index 2af09f10cb..2dad8fb781 100644 --- a/src/nvim/if_cscope.c +++ b/src/nvim/if_cscope.c @@ -459,7 +459,7 @@ staterr: int i; // if filename is a directory, append the cscope database name to it - if ((file_info.stat.st_mode & S_IFMT) == S_IFDIR) { + if (S_ISDIR(file_info.stat.st_mode)) { fname2 = (char *)xmalloc(strlen(CSCOPE_DBFILE) + strlen(fname) + 2); while (fname[strlen(fname)-1] == '/' diff --git a/src/nvim/indent.c b/src/nvim/indent.c index fb277b25fd..9e6693afdf 100644 --- a/src/nvim/indent.c +++ b/src/nvim/indent.c @@ -295,13 +295,18 @@ int set_indent(int size, int flags) // Replace the line (unless undo fails). if (!(flags & SIN_UNDO) || (u_savesub(curwin->w_cursor.lnum) == OK)) { + const colnr_T old_offset = (colnr_T)(p - oldline); + const colnr_T new_offset = (colnr_T)(s - newline); + + // this may free "newline" ml_replace(curwin->w_cursor.lnum, newline, false); if (!(flags & SIN_NOMARK)) { - extmark_splice(curbuf, - (int)curwin->w_cursor.lnum-1, skipcols, - 0, (int)(p-oldline) - skipcols, - 0, (int)(s-newline) - skipcols, - kExtmarkUndo); + extmark_splice_cols(curbuf, + (int)curwin->w_cursor.lnum-1, + skipcols, + old_offset - skipcols, + new_offset - skipcols, + kExtmarkUndo); } if (flags & SIN_CHANGED) { @@ -310,15 +315,14 @@ int set_indent(int size, int flags) // Correct saved cursor position if it is in this line. if (saved_cursor.lnum == curwin->w_cursor.lnum) { - if (saved_cursor.col >= (colnr_T)(p - oldline)) { + if (saved_cursor.col >= old_offset) { // Cursor was after the indent, adjust for the number of // bytes added/removed. - saved_cursor.col += ind_len - (colnr_T)(p - oldline); - - } else if (saved_cursor.col >= (colnr_T)(s - newline)) { + saved_cursor.col += ind_len - old_offset; + } else if (saved_cursor.col >= new_offset) { // Cursor was in the indent, and is now after it, put it back // at the start of the indent (replacing spaces with TAB). - saved_cursor.col = (colnr_T)(s - newline); + saved_cursor.col = new_offset; } } retval = true; diff --git a/src/nvim/indent_c.c b/src/nvim/indent_c.c index bb443161ef..9298e57411 100644 --- a/src/nvim/indent_c.c +++ b/src/nvim/indent_c.c @@ -1676,6 +1676,9 @@ void parse_cino(buf_T *buf) // Handle C++ extern "C" or "C++" buf->b_ind_cpp_extern_c = 0; + // Handle C #pragma directives + buf->b_ind_pragma = 0; + for (p = buf->b_p_cino; *p; ) { l = p++; if (*p == '-') { @@ -1747,6 +1750,7 @@ void parse_cino(buf_T *buf) case 'N': buf->b_ind_cpp_namespace = n; break; case 'k': buf->b_ind_if_for_while = n; break; case 'E': buf->b_ind_cpp_extern_c = n; break; + case 'P': buf->b_ind_pragma = n; break; } if (*p == ',') ++p; @@ -1858,12 +1862,14 @@ int get_c_indent(void) goto laterend; } - /* - * #defines and so on always go at the left when included in 'cinkeys'. - */ + // #defines and so on go at the left when included in 'cinkeys', + // exluding pragmas when customized in 'cinoptions' if (*theline == '#' && (*linecopy == '#' || in_cinkeys('#', ' ', true))) { - amount = curbuf->b_ind_hash_comment; - goto theend; + const char_u *const directive = skipwhite(theline + 1); + if (curbuf->b_ind_pragma == 0 || STRNCMP(directive, "pragma", 6) != 0) { + amount = curbuf->b_ind_hash_comment; + goto theend; + } } /* diff --git a/src/nvim/keymap.c b/src/nvim/keymap.c index a553110552..2b6f022d9d 100644 --- a/src/nvim/keymap.c +++ b/src/nvim/keymap.c @@ -530,13 +530,24 @@ unsigned int trans_special(const char_u **srcp, const size_t src_len, { int modifiers = 0; int key; - unsigned int dlen = 0; key = find_special_key(srcp, src_len, &modifiers, keycode, false, in_string); if (key == 0) { return 0; } + return special_to_buf(key, modifiers, keycode, dst); +} + +/// Put the character sequence for "key" with "modifiers" into "dst" and return +/// the resulting length. +/// When "keycode" is TRUE prefer key code, e.g. K_DEL instead of DEL. +/// The sequence is not NUL terminated. +/// This is how characters in a string are encoded. +unsigned int special_to_buf(int key, int modifiers, bool keycode, char_u *dst) +{ + unsigned int dlen = 0; + // Put the appropriate modifier in a string. if (modifiers != 0) { dst[dlen++] = K_SPECIAL; diff --git a/src/nvim/log.h b/src/nvim/log.h index 17ff095473..f2e74df031 100644 --- a/src/nvim/log.h +++ b/src/nvim/log.h @@ -5,6 +5,17 @@ #include <stdbool.h> #include "auto/config.h" +#include "nvim/macros.h" + +// USDT probes. Example invokation: +// NVIM_PROBE(nvim_foo_bar, 1, string.data); +#if defined(HAVE_SYS_SDT_H) +#include <sys/sdt.h> // NOLINT +#define NVIM_PROBE(name, n, ...) STAP_PROBE##n(neovim, name, __VA_ARGS__) +#else +#define NVIM_PROBE(name, n, ...) +#endif + #define DEBUG_LOG_LEVEL 0 #define INFO_LOG_LEVEL 1 @@ -68,6 +79,10 @@ # define LOG_CALLSTACK_TO_FILE(fp) log_callstack_to_file(fp, __func__, __LINE__) #endif +#if NVIM_HAS_INCLUDE("sanitizer/asan_interface.h") +# include "sanitizer/asan_interface.h" +#endif + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "log.h.generated.h" #endif diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c index 32e804d213..030df69caa 100644 --- a/src/nvim/lua/converter.c +++ b/src/nvim/lua/converter.c @@ -1249,6 +1249,13 @@ type_error: return ret; } +LuaRef nlua_pop_LuaRef(lua_State *const lstate, Error *err) +{ + LuaRef rv = nlua_ref(lstate, -1); + lua_pop(lstate, 1); + return rv; +} + #define GENERATE_INDEX_FUNCTION(type) \ type nlua_pop_##type(lua_State *lstate, Error *err) \ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT \ diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 0d5622f1e7..0a3c30134b 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -299,45 +299,66 @@ static int nlua_wait(lua_State *lstate) return luaL_error(lstate, "timeout must be > 0"); } - // Check if condition can be called. - bool is_function = (lua_type(lstate, 2) == LUA_TFUNCTION); + int lua_top = lua_gettop(lstate); - // Check if condition is callable table - if (!is_function && luaL_getmetafield(lstate, 2, "__call") != 0) { - is_function = (lua_type(lstate, -1) == LUA_TFUNCTION); - lua_pop(lstate, 1); - } + // Check if condition can be called. + bool is_function = false; + if (lua_top >= 2 && !lua_isnil(lstate, 2)) { + is_function = (lua_type(lstate, 2) == LUA_TFUNCTION); + + // Check if condition is callable table + if (!is_function && luaL_getmetafield(lstate, 2, "__call") != 0) { + is_function = (lua_type(lstate, -1) == LUA_TFUNCTION); + lua_pop(lstate, 1); + } - if (!is_function) { - lua_pushliteral(lstate, "vim.wait: condition must be a function"); - return lua_error(lstate); + if (!is_function) { + lua_pushliteral( + lstate, + "vim.wait: if passed, condition must be a function"); + return lua_error(lstate); + } } intptr_t interval = 200; - if (lua_gettop(lstate) >= 3) { + if (lua_top >= 3 && !lua_isnil(lstate, 3)) { interval = luaL_checkinteger(lstate, 3); if (interval < 0) { return luaL_error(lstate, "interval must be > 0"); } } + bool fast_only = false; + if (lua_top >= 4) { + fast_only = lua_toboolean(lstate, 4); + } + + MultiQueue *loop_events = fast_only || in_fast_callback > 0 + ? main_loop.fast_events : main_loop.events; + TimeWatcher *tw = xmalloc(sizeof(TimeWatcher)); // Start dummy timer. time_watcher_init(&main_loop, tw, NULL); - tw->events = main_loop.events; + tw->events = loop_events; tw->blockable = true; - time_watcher_start(tw, dummy_timer_due_cb, - (uint64_t)interval, (uint64_t)interval); + time_watcher_start( + tw, + dummy_timer_due_cb, + (uint64_t)interval, + (uint64_t)interval); int pcall_status = 0; bool callback_result = false; LOOP_PROCESS_EVENTS_UNTIL( &main_loop, - main_loop.events, + loop_events, (int)timeout, - nlua_wait_condition(lstate, &pcall_status, &callback_result) || got_int); + is_function ? nlua_wait_condition( + lstate, + &pcall_status, + &callback_result) : false || got_int); // Stop dummy timer time_watcher_stop(tw); @@ -467,7 +488,7 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL { const char *code = (char *)&shared_module[0]; - if (luaL_loadbuffer(lstate, code, strlen(code), "@shared.lua") + if (luaL_loadbuffer(lstate, code, strlen(code), "@vim/shared.lua") || lua_pcall(lstate, 0, 0, 0)) { nlua_error(lstate, _("E5106: Error while creating shared module: %.*s")); return 1; @@ -475,6 +496,22 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL } { + lua_getglobal(lstate, "package"); // [package] + lua_getfield(lstate, -1, "loaded"); // [package, loaded] + + const char *code = (char *)&inspect_module[0]; + if (luaL_loadbuffer(lstate, code, strlen(code), "@vim/inspect.lua") + || lua_pcall(lstate, 0, 1, 0)) { + nlua_error(lstate, _("E5106: Error while creating inspect module: %.*s")); + return 1; + } + // [package, loaded, inspect] + + lua_setfield(lstate, -2, "vim.inspect"); // [package, loaded] + lua_pop(lstate, 2); // [] + } + + { const char *code = (char *)&vim_module[0]; if (luaL_loadbuffer(lstate, code, strlen(code), "@vim.lua") || lua_pcall(lstate, 0, 0, 0)) { @@ -528,14 +565,6 @@ static lua_State *nlua_enter(void) // stack: (empty) lua_getglobal(lstate, "vim"); // stack: vim - lua_getfield(lstate, -1, "_update_package_paths"); - // stack: vim, vim._update_package_paths - if (lua_pcall(lstate, 0, 0, 0)) { - // stack: vim, error - nlua_error(lstate, _("E5117: Error while updating package paths: %.*s")); - // stack: vim - } - // stack: vim lua_pop(lstate, 1); // stack: (empty) last_p_rtp = (const void *)p_rtp; @@ -837,7 +866,7 @@ void nlua_unref(lua_State *lstate, LuaRef ref) } } -void executor_free_luaref(LuaRef ref) +void api_free_luaref(LuaRef ref) { lua_State *const lstate = nlua_enter(); nlua_unref(lstate, ref); @@ -862,6 +891,17 @@ LuaRef nlua_newref(lua_State *lstate, LuaRef original_ref) return new_ref; } +LuaRef api_new_luaref(LuaRef original_ref) +{ + if (original_ref == LUA_NOREF) { + return LUA_NOREF; + } + + lua_State *const lstate = nlua_enter(); + return nlua_newref(lstate, original_ref); +} + + /// Evaluate lua string /// /// Used for luaeval(). @@ -871,8 +911,8 @@ LuaRef nlua_newref(lua_State *lstate, LuaRef original_ref) /// @param[out] ret_tv Location where result will be saved. /// /// @return Result of the execution. -void executor_eval_lua(const String str, typval_T *const arg, - typval_T *const ret_tv) +void nlua_typval_eval(const String str, typval_T *const arg, + typval_T *const ret_tv) FUNC_ATTR_NONNULL_ALL { #define EVALHEADER "local _A=select(1,...) return (" @@ -894,8 +934,8 @@ void executor_eval_lua(const String str, typval_T *const arg, } } -void executor_call_lua(const char *str, size_t len, typval_T *const args, - int argcount, typval_T *ret_tv) +void nlua_typval_call(const char *str, size_t len, typval_T *const args, + int argcount, typval_T *ret_tv) FUNC_ATTR_NONNULL_ALL { #define CALLHEADER "return " @@ -925,7 +965,7 @@ static void typval_exec_lua(const char *lcmd, size_t lcmd_len, const char *name, typval_T *const args, int argcount, bool special, typval_T *ret_tv) { - if (check_restricted() || check_secure()) { + if (check_secure()) { if (ret_tv) { ret_tv->v_type = VAR_NUMBER; ret_tv->vval.v_number = 0; @@ -998,14 +1038,14 @@ int typval_exec_lua_callable( /// Execute Lua string /// -/// Used for nvim_exec_lua(). +/// Used for nvim_exec_lua() and internally to execute a lua string. /// /// @param[in] str String to execute. /// @param[in] args array of ... args /// @param[out] err Location where error will be saved. /// /// @return Return value of the execution. -Object executor_exec_lua_api(const String str, const Array args, Error *err) +Object nlua_exec(const String str, const Array args, Error *err) { lua_State *const lstate = nlua_enter(); @@ -1032,17 +1072,30 @@ Object executor_exec_lua_api(const String str, const Array args, Error *err) return nlua_pop_Object(lstate, false, err); } -Object executor_exec_lua_cb(LuaRef ref, const char *name, Array args, - bool retval, Error *err) +/// call a LuaRef as a function (or table with __call metamethod) +/// +/// @param ref the reference to call (not consumed) +/// @param name if non-NULL, sent to callback as first arg +/// if NULL, only args are used +/// @param retval if true, convert return value to Object +/// if false, discard return value +/// @param err Error details, if any (if NULL, errors are echoed) +/// @return Return value of function, if retval was set. Otherwise NIL. +Object nlua_call_ref(LuaRef ref, const char *name, Array args, + bool retval, Error *err) { lua_State *const lstate = nlua_enter(); nlua_pushref(lstate, ref); - lua_pushstring(lstate, name); + int nargs = (int)args.size; + if (name != NULL) { + lua_pushstring(lstate, name); + nargs++; + } for (size_t i = 0; i < args.size; i++) { nlua_push_Object(lstate, args.items[i], false); } - if (lua_pcall(lstate, (int)args.size+1, retval ? 1 : 0, 0)) { + if (lua_pcall(lstate, nargs, retval ? 1 : 0, 0)) { // if err is passed, the caller will deal with the error. if (err) { size_t len; @@ -1457,3 +1510,40 @@ void nlua_free_typval_dict(dict_T *const d) d->lua_table_ref = LUA_NOREF; } } + +void nlua_execute_log_keystroke(int c) +{ + char_u buf[NUMBUFLEN]; + size_t buf_len = special_to_buf(c, mod_mask, false, buf); + + lua_State *const lstate = nlua_enter(); + +#ifndef NDEBUG + int top = lua_gettop(lstate); +#endif + + // [ vim ] + lua_getglobal(lstate, "vim"); + + // [ vim, vim._log_keystroke ] + lua_getfield(lstate, -1, "_log_keystroke"); + luaL_checktype(lstate, -1, LUA_TFUNCTION); + + // [ vim, vim._log_keystroke, buf ] + lua_pushlstring(lstate, (const char *)buf, buf_len); + + if (lua_pcall(lstate, 1, 0, 0)) { + nlua_error( + lstate, + _("Error executing vim.log_keystroke lua callback: %.*s")); + } + + // [ vim ] + lua_pop(lstate, 1); + +#ifndef NDEBUG + // [ ] + assert(top == lua_gettop(lstate)); +#endif +} + diff --git a/src/nvim/lua/executor.h b/src/nvim/lua/executor.h index 6599b44584..1d7a15d9aa 100644 --- a/src/nvim/lua/executor.h +++ b/src/nvim/lua/executor.h @@ -24,6 +24,15 @@ EXTERN LuaRef nlua_empty_dict_ref INIT(= LUA_NOREF); memcpy(&err_->msg[0], s, sizeof(s)); \ } while (0) +#define NLUA_CLEAR_REF(x) \ + do { \ + /* Take the address to avoid double evaluation. #1375 */ \ + if ((x) != LUA_NOREF) { \ + api_free_luaref(x); \ + (x) = LUA_NOREF; \ + } \ + } while (0) + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "lua/executor.h.generated.h" #endif diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index 138031237e..0515bbfe53 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -23,11 +23,6 @@ #include "nvim/buffer.h" typedef struct { - TSParser *parser; - TSTree *tree; // internal tree, used for editing/reparsing -} TSLua_parser; - -typedef struct { TSQueryCursor *cursor; int predicated_match; } TSLua_cursor; @@ -39,10 +34,9 @@ typedef struct { static struct luaL_Reg parser_meta[] = { { "__gc", parser_gc }, { "__tostring", parser_tostring }, - { "parse_buf", parser_parse_buf }, - { "edit", parser_edit }, - { "tree", parser_tree }, + { "parse", parser_parse }, { "set_included_ranges", parser_set_ranges }, + { "included_ranges", parser_get_ranges }, { NULL, NULL } }; @@ -50,6 +44,8 @@ static struct luaL_Reg tree_meta[] = { { "__gc", tree_gc }, { "__tostring", tree_tostring }, { "root", tree_root }, + { "edit", tree_edit }, + { "copy", tree_copy }, { NULL, NULL } }; @@ -57,11 +53,13 @@ static struct luaL_Reg node_meta[] = { { "__tostring", node_tostring }, { "__eq", node_eq }, { "__len", node_child_count }, + { "id", node_id }, { "range", node_range }, { "start", node_start }, { "end_", node_end }, { "type", node_type }, { "symbol", node_symbol }, + { "field", node_field }, { "named", node_named }, { "missing", node_missing }, { "has_error", node_has_error }, @@ -73,6 +71,7 @@ static struct luaL_Reg node_meta[] = { { "descendant_for_range", node_descendant_for_range }, { "named_descendant_for_range", node_named_descendant_for_range }, { "parent", node_parent }, + { "iter_children", node_iter_children }, { "_rawquery", node_rawquery }, { NULL, NULL } }; @@ -84,12 +83,17 @@ static struct luaL_Reg query_meta[] = { { NULL, NULL } }; -// cursor is not exposed, but still needs garbage collection +// cursors are not exposed, but still needs garbage collection static struct luaL_Reg querycursor_meta[] = { { "__gc", querycursor_gc }, { NULL, NULL } }; +static struct luaL_Reg treecursor_meta[] = { + { "__gc", treecursor_gc }, + { NULL, NULL } +}; + static PMap(cstr_t) *langs; static void build_meta(lua_State *L, const char *tname, const luaL_Reg *meta) @@ -116,6 +120,7 @@ void tslua_init(lua_State *L) build_meta(L, "treesitter_node", node_meta); build_meta(L, "treesitter_query", query_meta); build_meta(L, "treesitter_querycursor", querycursor_meta); + build_meta(L, "treesitter_treecursor", treecursor_meta); } int tslua_has_language(lua_State *L) @@ -166,6 +171,15 @@ int tslua_add_language(lua_State *L) return luaL_error(L, "Failed to load parser: internal error"); } + uint32_t lang_version = ts_language_version(lang); + if (lang_version < TREE_SITTER_MIN_COMPATIBLE_LANGUAGE_VERSION + || lang_version > TREE_SITTER_LANGUAGE_VERSION) { + return luaL_error( + L, + "ABI version mismatch : expected %d, found %d", + TREE_SITTER_MIN_COMPATIBLE_LANGUAGE_VERSION, lang_version); + } + pmap_put(cstr_t)(langs, xstrdup(lang_name), lang); lua_pushboolean(L, true); @@ -228,34 +242,32 @@ int tslua_push_parser(lua_State *L) return luaL_error(L, "no such language: %s", lang_name); } - TSParser *parser = ts_parser_new(); - ts_parser_set_language(parser, lang); - TSLua_parser *p = lua_newuserdata(L, sizeof(TSLua_parser)); // [udata] - p->parser = parser; - p->tree = NULL; + TSParser **parser = lua_newuserdata(L, sizeof(TSParser *)); + *parser = ts_parser_new(); + + if (!ts_parser_set_language(*parser, lang)) { + ts_parser_delete(*parser); + return luaL_error(L, "Failed to load language : %s", lang_name); + } lua_getfield(L, LUA_REGISTRYINDEX, "treesitter_parser"); // [udata, meta] lua_setmetatable(L, -2); // [udata] return 1; } -static TSLua_parser *parser_check(lua_State *L) +static TSParser ** parser_check(lua_State *L, uint16_t index) { - return luaL_checkudata(L, 1, "treesitter_parser"); + return luaL_checkudata(L, index, "treesitter_parser"); } static int parser_gc(lua_State *L) { - TSLua_parser *p = parser_check(L); + TSParser **p = parser_check(L, 1); if (!p) { return 0; } - ts_parser_delete(p->parser); - if (p->tree) { - ts_tree_delete(p->tree); - } - + ts_parser_delete(*p); return 0; } @@ -278,96 +290,133 @@ static const char *input_cb(void *payload, uint32_t byte_index, } char_u *line = ml_get_buf(bp, position.row+1, false); size_t len = STRLEN(line); - if (position.column > len) { *bytes_read = 0; - } else { - size_t tocopy = MIN(len-position.column, BUFSIZE); - - memcpy(buf, line+position.column, tocopy); - // Translate embedded \n to NUL - memchrsub(buf, '\n', '\0', tocopy); - *bytes_read = (uint32_t)tocopy; - if (tocopy < BUFSIZE) { - // now add the final \n. If it didn't fit, input_cb will be called again - // on the same line with advanced column. - buf[tocopy] = '\n'; - (*bytes_read)++; - } + return ""; + } + size_t tocopy = MIN(len-position.column, BUFSIZE); + + memcpy(buf, line+position.column, tocopy); + // Translate embedded \n to NUL + memchrsub(buf, '\n', '\0', tocopy); + *bytes_read = (uint32_t)tocopy; + if (tocopy < BUFSIZE) { + // now add the final \n. If it didn't fit, input_cb will be called again + // on the same line with advanced column. + buf[tocopy] = '\n'; + (*bytes_read)++; } return buf; #undef BUFSIZE } -static int parser_parse_buf(lua_State *L) +static void push_ranges(lua_State *L, + const TSRange *ranges, + const unsigned int length) { - TSLua_parser *p = parser_check(L); - if (!p) { + lua_createtable(L, length, 0); + for (size_t i = 0; i < length; i++) { + lua_createtable(L, 4, 0); + lua_pushinteger(L, ranges[i].start_point.row); + lua_rawseti(L, -2, 1); + lua_pushinteger(L, ranges[i].start_point.column); + lua_rawseti(L, -2, 2); + lua_pushinteger(L, ranges[i].end_point.row); + lua_rawseti(L, -2, 3); + lua_pushinteger(L, ranges[i].end_point.column); + lua_rawseti(L, -2, 4); + + lua_rawseti(L, -2, i+1); + } +} + +static int parser_parse(lua_State *L) +{ + TSParser **p = parser_check(L, 1); + if (!p || !(*p)) { return 0; } - long bufnr = lua_tointeger(L, 2); - buf_T *buf = handle_get_buffer(bufnr); + TSTree *old_tree = NULL; + if (!lua_isnil(L, 2)) { + TSTree **tmp = tree_check(L, 2); + old_tree = tmp ? *tmp : NULL; + } - if (!buf) { - return luaL_error(L, "invalid buffer handle: %d", bufnr); + TSTree *new_tree = NULL; + size_t len; + const char *str; + long bufnr; + buf_T *buf; + TSInput input; + + // This switch is necessary because of the behavior of lua_isstring, that + // consider numbers as strings... + switch (lua_type(L, 3)) { + case LUA_TSTRING: + str = lua_tolstring(L, 3, &len); + new_tree = ts_parser_parse_string(*p, old_tree, str, len); + break; + + case LUA_TNUMBER: + bufnr = lua_tointeger(L, 3); + buf = handle_get_buffer(bufnr); + + if (!buf) { + return luaL_error(L, "invalid buffer handle: %d", bufnr); + } + + input = (TSInput){ (void *)buf, input_cb, TSInputEncodingUTF8 }; + new_tree = ts_parser_parse(*p, old_tree, input); + + break; + + default: + return luaL_error(L, "invalid argument to parser:parse()"); } - TSInput input = { (void *)buf, input_cb, TSInputEncodingUTF8 }; - TSTree *new_tree = ts_parser_parse(p->parser, p->tree, input); + // Sometimes parsing fails (timeout, or wrong parser ABI) + // In those case, just return an error. + if (!new_tree) { + return luaL_error(L, "An error occured when parsing."); + } + // The new tree will be pushed to the stack, without copy, owwership is now to + // the lua GC. + // Old tree is still owned by the lua GC. uint32_t n_ranges = 0; - TSRange *changed = p->tree ? ts_tree_get_changed_ranges(p->tree, new_tree, - &n_ranges) : NULL; - if (p->tree) { - ts_tree_delete(p->tree); - } - p->tree = new_tree; + TSRange *changed = old_tree ? ts_tree_get_changed_ranges( + old_tree, new_tree, &n_ranges) : NULL; - tslua_push_tree(L, p->tree); + tslua_push_tree(L, new_tree, false); // [tree] - lua_createtable(L, n_ranges, 0); - for (size_t i = 0; i < n_ranges; i++) { - lua_createtable(L, 4, 0); - lua_pushinteger(L, changed[i].start_point.row); - lua_rawseti(L, -2, 1); - lua_pushinteger(L, changed[i].start_point.column); - lua_rawseti(L, -2, 2); - lua_pushinteger(L, changed[i].end_point.row); - lua_rawseti(L, -2, 3); - lua_pushinteger(L, changed[i].end_point.column); - lua_rawseti(L, -2, 4); + push_ranges(L, changed, n_ranges); // [tree, ranges] - lua_rawseti(L, -2, i+1); - } xfree(changed); return 2; } -static int parser_tree(lua_State *L) +static int tree_copy(lua_State *L) { - TSLua_parser *p = parser_check(L); - if (!p) { + TSTree **tree = tree_check(L, 1); + if (!(*tree)) { return 0; } - tslua_push_tree(L, p->tree); + tslua_push_tree(L, *tree, true); // [tree] + return 1; } -static int parser_edit(lua_State *L) +static int tree_edit(lua_State *L) { if (lua_gettop(L) < 10) { - lua_pushstring(L, "not enough args to parser:edit()"); + lua_pushstring(L, "not enough args to tree:edit()"); return lua_error(L); } - TSLua_parser *p = parser_check(L); - if (!p) { - return 0; - } - - if (!p->tree) { + TSTree **tree = tree_check(L, 1); + if (!(*tree)) { return 0; } @@ -381,7 +430,7 @@ static int parser_edit(lua_State *L) TSInputEdit edit = { start_byte, old_end_byte, new_end_byte, start_point, old_end_point, new_end_point }; - ts_tree_edit(p->tree, &edit); + ts_tree_edit(*tree, &edit); return 0; } @@ -394,8 +443,8 @@ static int parser_set_ranges(lua_State *L) "not enough args to parser:set_included_ranges()"); } - TSLua_parser *p = parser_check(L); - if (!p || !p->tree) { + TSParser **p = parser_check(L, 1); + if (!p) { return 0; } @@ -431,26 +480,46 @@ static int parser_set_ranges(lua_State *L) } // This memcpies ranges, thus we can free it afterwards - ts_parser_set_included_ranges(p->parser, ranges, tbl_len); + ts_parser_set_included_ranges(*p, ranges, tbl_len); xfree(ranges); return 0; } +static int parser_get_ranges(lua_State *L) +{ + TSParser **p = parser_check(L, 1); + if (!p) { + return 0; + } + + unsigned int len; + const TSRange *ranges = ts_parser_included_ranges(*p, &len); + + push_ranges(L, ranges, len); + return 1; +} + // Tree methods /// push tree interface on lua stack. /// /// This makes a copy of the tree, so ownership of the argument is unaffected. -void tslua_push_tree(lua_State *L, TSTree *tree) +void tslua_push_tree(lua_State *L, TSTree *tree, bool do_copy) { if (tree == NULL) { lua_pushnil(L); return; } TSTree **ud = lua_newuserdata(L, sizeof(TSTree *)); // [udata] - *ud = ts_tree_copy(tree); + + if (do_copy) { + *ud = ts_tree_copy(tree); + } else { + *ud = tree; + } + lua_getfield(L, LUA_REGISTRYINDEX, "treesitter_tree"); // [udata, meta] lua_setmetatable(L, -2); // [udata] @@ -463,20 +532,20 @@ void tslua_push_tree(lua_State *L, TSTree *tree) lua_setfenv(L, -2); // [udata] } -static TSTree *tree_check(lua_State *L) +static TSTree **tree_check(lua_State *L, uint16_t index) { - TSTree **ud = luaL_checkudata(L, 1, "treesitter_tree"); - return *ud; + TSTree **ud = luaL_checkudata(L, index, "treesitter_tree"); + return ud; } static int tree_gc(lua_State *L) { - TSTree *tree = tree_check(L); + TSTree **tree = tree_check(L, 1); if (!tree) { return 0; } - ts_tree_delete(tree); + ts_tree_delete(*tree); return 0; } @@ -488,11 +557,11 @@ static int tree_tostring(lua_State *L) static int tree_root(lua_State *L) { - TSTree *tree = tree_check(L); + TSTree **tree = tree_check(L, 1); if (!tree) { return 0; } - TSNode root = ts_tree_root_node(tree); + TSNode root = ts_tree_root_node(*tree); push_node(L, root, 1); return 1; } @@ -560,6 +629,17 @@ static int node_eq(lua_State *L) return 1; } +static int node_id(lua_State *L) +{ + TSNode node; + if (!node_check(L, 1, &node)) { + return 0; + } + + lua_pushlstring(L, (const char *)&node.id, sizeof node.id); + return 1; +} + static int node_range(lua_State *L) { TSNode node; @@ -646,6 +726,34 @@ static int node_symbol(lua_State *L) return 1; } +static int node_field(lua_State *L) +{ + TSNode node; + if (!node_check(L, 1, &node)) { + return 0; + } + + size_t name_len; + const char *field_name = luaL_checklstring(L, 2, &name_len); + + TSTreeCursor cursor = ts_tree_cursor_new(node); + + lua_newtable(L); // [table] + unsigned int curr_index = 0; + + if (ts_tree_cursor_goto_first_child(&cursor)) { + do { + if (!STRCMP(field_name, ts_tree_cursor_current_field_name(&cursor))) { + push_node(L, ts_tree_cursor_current_node(&cursor), 1); // [table, node] + lua_rawseti(L, -2, ++curr_index); + } + } while (ts_tree_cursor_goto_next_sibling(&cursor)); + } + + ts_tree_cursor_delete(&cursor); + return 1; +} + static int node_named(lua_State *L) { TSNode node; @@ -746,6 +854,74 @@ static int node_named_descendant_for_range(lua_State *L) return 1; } +static int node_next_child(lua_State *L) +{ + TSTreeCursor *ud = luaL_checkudata( + L, lua_upvalueindex(1), "treesitter_treecursor"); + if (!ud) { + return 0; + } + + TSNode source; + if (!node_check(L, lua_upvalueindex(2), &source)) { + return 0; + } + + // First call should return first child + if (ts_node_eq(source, ts_tree_cursor_current_node(ud))) { + if (ts_tree_cursor_goto_first_child(ud)) { + goto push; + } else { + goto end; + } + } + + if (ts_tree_cursor_goto_next_sibling(ud)) { +push: + push_node( + L, + ts_tree_cursor_current_node(ud), + lua_upvalueindex(2)); // [node] + + const char * field = ts_tree_cursor_current_field_name(ud); + + if (field != NULL) { + lua_pushstring(L, ts_tree_cursor_current_field_name(ud)); + } else { + lua_pushnil(L); + } // [node, field_name_or_nil] + return 2; + } + +end: + return 0; +} + +static int node_iter_children(lua_State *L) +{ + TSNode source; + if (!node_check(L, 1, &source)) { + return 0; + } + + TSTreeCursor *ud = lua_newuserdata(L, sizeof(TSTreeCursor)); // [udata] + *ud = ts_tree_cursor_new(source); + + lua_getfield(L, LUA_REGISTRYINDEX, "treesitter_treecursor"); // [udata, mt] + lua_setmetatable(L, -2); // [udata] + lua_pushvalue(L, 1); // [udata, source_node] + lua_pushcclosure(L, node_next_child, 2); + + return 1; +} + +static int treecursor_gc(lua_State *L) +{ + TSTreeCursor *ud = luaL_checkudata(L, 1, "treesitter_treecursor"); + ts_tree_cursor_delete(ud); + return 0; +} + static int node_parent(lua_State *L) { TSNode node; diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua index 820b237c4f..2c7ab46ffe 100644 --- a/src/nvim/lua/vim.lua +++ b/src/nvim/lua/vim.lua @@ -36,6 +36,9 @@ local vim = vim assert(vim) +vim.inspect = package.loaded['vim.inspect'] +assert(vim.inspect) + -- Internal-only until comments in #8107 are addressed. -- Returns: -- {errcode}, {output} @@ -92,67 +95,44 @@ function vim._os_proc_children(ppid) return children end --- TODO(ZyX-I): Create compatibility layer. ---{{{1 package.path updater function --- Last inserted paths. Used to clear out items from package.[c]path when they --- are no longer in &runtimepath. -local last_nvim_paths = {} -function vim._update_package_paths() - local cur_nvim_paths = {} - local rtps = vim.api.nvim_list_runtime_paths() - local sep = package.config:sub(1, 1) - for _, key in ipairs({'path', 'cpath'}) do - local orig_str = package[key] .. ';' - local pathtrails_ordered = {} - local orig = {} - -- Note: ignores trailing item without trailing `;`. Not using something - -- simpler in order to preserve empty items (stand for default path). - for s in orig_str:gmatch('[^;]*;') do - s = s:sub(1, -2) -- Strip trailing semicolon - orig[#orig + 1] = s - end - if key == 'path' then - -- /?.lua and /?/init.lua - pathtrails_ordered = {sep .. '?.lua', sep .. '?' .. sep .. 'init.lua'} - else - local pathtrails = {} - for _, s in ipairs(orig) do - -- Find out path patterns. pathtrail should contain something like - -- /?.so, \?.dll. This allows not to bother determining what correct - -- suffixes are. - local pathtrail = s:match('[/\\][^/\\]*%?.*$') - if pathtrail and not pathtrails[pathtrail] then - pathtrails[pathtrail] = true - pathtrails_ordered[#pathtrails_ordered + 1] = pathtrail - end - end - end - local new = {} - for _, rtp in ipairs(rtps) do - if not rtp:match(';') then - for _, pathtrail in pairs(pathtrails_ordered) do - local new_path = rtp .. sep .. 'lua' .. pathtrail - -- Always keep paths from &runtimepath at the start: - -- append them here disregarding orig possibly containing one of them. - new[#new + 1] = new_path - cur_nvim_paths[new_path] = true - end - end +local pathtrails = {} +vim._so_trails = {} +for s in (package.cpath..';'):gmatch('[^;]*;') do + s = s:sub(1, -2) -- Strip trailing semicolon + -- Find out path patterns. pathtrail should contain something like + -- /?.so, \?.dll. This allows not to bother determining what correct + -- suffixes are. + local pathtrail = s:match('[/\\][^/\\]*%?.*$') + if pathtrail and not pathtrails[pathtrail] then + pathtrails[pathtrail] = true + table.insert(vim._so_trails, pathtrail) + end +end + +function vim._load_package(name) + local basename = name:gsub('%.', '/') + local paths = {"lua/"..basename..".lua", "lua/"..basename.."/init.lua"} + for _,path in ipairs(paths) do + local found = vim.api.nvim_get_runtime_file(path, false) + if #found > 0 then + return loadfile(found[1]) end - for _, orig_path in ipairs(orig) do - -- Handle removing obsolete paths originating from &runtimepath: such - -- paths either belong to cur_nvim_paths and were already added above or - -- to last_nvim_paths and should not be added at all if corresponding - -- entry was removed from &runtimepath list. - if not (cur_nvim_paths[orig_path] or last_nvim_paths[orig_path]) then - new[#new + 1] = orig_path - end + end + + for _,trail in ipairs(vim._so_trails) do + local path = "lua/"..trail:gsub('?',basename) + local found = vim.api.nvim_get_runtime_file(path, false) + if #found > 0 then + return package.loadlib(found[1]) end - package[key] = table.concat(new, ';') end - last_nvim_paths = cur_nvim_paths + return nil end +table.insert(package.loaders, 1, vim._load_package) + +-- TODO(ZyX-I): Create compatibility layer. + --- Return a human-readable representation of the given object. --- --@see https://github.com/kikito/inspect.lua @@ -279,10 +259,7 @@ end -- These are for loading runtime modules lazily since they aren't available in -- the nvim binary as specified in executor.c local function __index(t, key) - if key == 'inspect' then - t.inspect = require('vim.inspect') - return t.inspect - elseif key == 'treesitter' then + if key == 'treesitter' then t.treesitter = require('vim.treesitter') return t.treesitter elseif require('vim.uri')[key] ~= nil then @@ -295,6 +272,9 @@ local function __index(t, key) elseif key == 'highlight' then t.highlight = require('vim.highlight') return t.highlight + elseif key == 'F' then + t.F = require('vim.F') + return t.F end end @@ -489,4 +469,60 @@ function vim.defer_fn(fn, timeout) return timer end +local on_keystroke_callbacks = {} + +--- Register a lua {fn} with an {id} to be run after every keystroke. +--- +--@param fn function: Function to call. It should take one argument, which is a string. +--- The string will contain the literal keys typed. +--- See |i_CTRL-V| +--- +--- If {fn} is nil, it removes the callback for the associated {ns_id} +--@param ns_id number? Namespace ID. If not passed or 0, will generate and return a new +--- namespace ID from |nvim_create_namesapce()| +--- +--@return number Namespace ID associated with {fn} +--- +--@note {fn} will be automatically removed if an error occurs while calling. +--- This is to prevent the annoying situation of every keystroke erroring +--- while trying to remove a broken callback. +--@note {fn} will not be cleared from |nvim_buf_clear_namespace()| +--@note {fn} will receive the keystrokes after mappings have been evaluated +function vim.register_keystroke_callback(fn, ns_id) + vim.validate { + fn = { fn, 'c', true}, + ns_id = { ns_id, 'n', true } + } + + if ns_id == nil or ns_id == 0 then + ns_id = vim.api.nvim_create_namespace('') + end + + on_keystroke_callbacks[ns_id] = fn + return ns_id +end + +--- Function that executes the keystroke callbacks. +--@private +function vim._log_keystroke(char) + local failed_ns_ids = {} + local failed_messages = {} + for k, v in pairs(on_keystroke_callbacks) do + local ok, err_msg = pcall(v, char) + if not ok then + vim.register_keystroke_callback(nil, k) + + table.insert(failed_ns_ids, k) + table.insert(failed_messages, err_msg) + end + end + + if failed_ns_ids[1] then + error(string.format( + "Error executing 'on_keystroke' with ns_ids of '%s'\n With messages: %s", + table.concat(failed_ns_ids, ", "), + table.concat(failed_messages, "\n"))) + end +end + return module diff --git a/src/nvim/macros.h b/src/nvim/macros.h index 3df7fa768d..07dcb4a8e8 100644 --- a/src/nvim/macros.h +++ b/src/nvim/macros.h @@ -1,6 +1,8 @@ #ifndef NVIM_MACROS_H #define NVIM_MACROS_H +#include "auto/config.h" + // EXTERN is only defined in main.c. That's where global variables are // actually defined and initialized. #ifndef EXTERN @@ -150,6 +152,12 @@ #define STR_(x) #x #define STR(x) STR_(x) +#ifndef __has_include +# define NVIM_HAS_INCLUDE(x) 0 +#else +# define NVIM_HAS_INCLUDE __has_include +#endif + #ifndef __has_attribute # define NVIM_HAS_ATTRIBUTE(x) 0 #elif defined(__clang__) && __clang__ == 1 \ @@ -203,16 +211,33 @@ # define PRAGMA_DIAG_PUSH_IGNORE_MISSING_PROTOTYPES \ _Pragma("clang diagnostic push") \ _Pragma("clang diagnostic ignored \"-Wmissing-prototypes\"") +# ifdef HAVE_WIMPLICIT_FALLTHROUGH_FLAG +# define PRAGMA_DIAG_PUSH_IGNORE_IMPLICIT_FALLTHROUGH \ + _Pragma("clang diagnostic push") \ + _Pragma("clang diagnostic ignored \"-Wimplicit-fallthrough\"") +# else +# define PRAGMA_DIAG_PUSH_IGNORE_IMPLICIT_FALLTHROUGH \ + _Pragma("clang diagnostic push") +# endif # define PRAGMA_DIAG_POP \ _Pragma("clang diagnostic pop") #elif defined(__GNUC__) # define PRAGMA_DIAG_PUSH_IGNORE_MISSING_PROTOTYPES \ _Pragma("GCC diagnostic push") \ _Pragma("GCC diagnostic ignored \"-Wmissing-prototypes\"") +# ifdef HAVE_WIMPLICIT_FALLTHROUGH_FLAG +# define PRAGMA_DIAG_PUSH_IGNORE_IMPLICIT_FALLTHROUGH \ + _Pragma("GCC diagnostic push") \ + _Pragma("GCC diagnostic ignored \"-Wimplicit-fallthrough\"") +# else +# define PRAGMA_DIAG_PUSH_IGNORE_IMPLICIT_FALLTHROUGH \ + _Pragma("GCC diagnostic push") +# endif # define PRAGMA_DIAG_POP \ _Pragma("GCC diagnostic pop") #else # define PRAGMA_DIAG_PUSH_IGNORE_MISSING_PROTOTYPES +# define PRAGMA_DIAG_PUSH_IGNORE_IMPLICIT_FALLTHROUGH # define PRAGMA_DIAG_POP #endif diff --git a/src/nvim/main.c b/src/nvim/main.c index f79fb57eae..6ff5216a84 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -7,6 +7,8 @@ #include <string.h> #include <stdbool.h> +#include <lua.h> +#include <lauxlib.h> #include <msgpack.h> #include "nvim/ascii.h" @@ -21,6 +23,7 @@ #include "nvim/ex_cmds.h" #include "nvim/ex_cmds2.h" #include "nvim/ex_docmd.h" +#include "nvim/decoration.h" #include "nvim/fileio.h" #include "nvim/fold.h" #include "nvim/getchar.h" @@ -160,6 +163,7 @@ void early_init(mparm_T *paramp) env_init(); fs_init(); handle_init(); + decor_init(); eval_init(); // init global variables init_path(argv0 ? argv0 : "nvim"); init_normal_cmds(); // Init the table of Normal mode commands. @@ -442,7 +446,7 @@ int main(int argc, char **argv) if (exmode_active || use_remote_ui || use_builtin_ui) { // Don't clear the screen when starting in Ex mode, or when a UI might have // displayed messages. - redraw_later(VALID); + redraw_later(curwin, VALID); } else { screenclear(); // clear screen TIME_MSG("clearing screen"); @@ -1008,10 +1012,6 @@ static void command_line_scan(mparm_T *parmp) want_argument = true; break; } - case 'Z': { // "-Z" restricted mode - restricted = true; - break; - } case 'c': { // "-c{command}" or "-c {command}" exec command if (argv[0][argv_idx] != NUL) { diff --git a/src/nvim/map.c b/src/nvim/map.c index cba39f24b3..7d97b7f13d 100644 --- a/src/nvim/map.c +++ b/src/nvim/map.c @@ -12,6 +12,9 @@ #include <stdbool.h> #include <string.h> +#include <lua.h> +#include <lauxlib.h> + #include "nvim/map.h" #include "nvim/map_defs.h" #include "nvim/vim.h" @@ -173,6 +176,20 @@ static inline bool HlEntry_eq(HlEntry ae1, HlEntry ae2) return memcmp(&ae1, &ae2, sizeof(ae1)) == 0; } +static inline khint_t ColorKey_hash(ColorKey ae) +{ + const uint8_t *data = (const uint8_t *)&ae; + khint_t h = 0; + for (size_t i = 0; i < sizeof(ae); i++) { + h = (h << 5) - h + data[i]; + } + return h; +} + +static inline bool ColorKey_eq(ColorKey ae1, ColorKey ae2) +{ + return memcmp(&ae1, &ae2, sizeof(ae1)) == 0; +} MAP_IMPL(int, int, DEFAULT_INITIALIZER) @@ -183,8 +200,7 @@ MAP_IMPL(uint64_t, ssize_t, SSIZE_INITIALIZER) MAP_IMPL(uint64_t, uint64_t, DEFAULT_INITIALIZER) #define EXTMARK_NS_INITIALIZER { 0, 0 } MAP_IMPL(uint64_t, ExtmarkNs, EXTMARK_NS_INITIALIZER) -#define KVEC_INITIALIZER { .size = 0, .capacity = 0, .items = NULL } -#define EXTMARK_ITEM_INITIALIZER { 0, 0, 0, KVEC_INITIALIZER } +#define EXTMARK_ITEM_INITIALIZER { 0, 0, NULL } MAP_IMPL(uint64_t, ExtmarkItem, EXTMARK_ITEM_INITIALIZER) MAP_IMPL(handle_T, ptr_t, DEFAULT_INITIALIZER) #define MSGPACK_HANDLER_INITIALIZER { .fn = NULL, .fast = false } @@ -192,6 +208,7 @@ MAP_IMPL(String, MsgpackRpcRequestHandler, MSGPACK_HANDLER_INITIALIZER) MAP_IMPL(HlEntry, int, DEFAULT_INITIALIZER) MAP_IMPL(String, handle_T, 0) +MAP_IMPL(ColorKey, ColorItem, COLOR_ITEM_INITIALIZER) /// Deletes a key:value pair from a string:pointer map, and frees the /// storage of both key and value. diff --git a/src/nvim/map.h b/src/nvim/map.h index 0ad7865bf0..7bd3d31330 100644 --- a/src/nvim/map.h +++ b/src/nvim/map.h @@ -55,6 +55,8 @@ MAP_DECLS(String, MsgpackRpcRequestHandler) MAP_DECLS(HlEntry, int) MAP_DECLS(String, handle_T) +MAP_DECLS(ColorKey, ColorItem) + #define map_new(T, U) map_##T##_##U##_new #define map_free(T, U) map_##T##_##U##_free #define map_get(T, U) map_##T##_##U##_get @@ -73,6 +75,7 @@ MAP_DECLS(String, handle_T) #define pmap_has(T) map_has(T, ptr_t) #define pmap_key(T) map_key(T, ptr_t) #define pmap_put(T) map_put(T, ptr_t) +#define pmap_ref(T) map_ref(T, ptr_t) /// @see pmap_del2 #define pmap_del(T) map_del(T, ptr_t) #define pmap_clear(T) map_clear(T, ptr_t) diff --git a/src/nvim/mark.c b/src/nvim/mark.c index fa7c7d61c9..1ecfae57ed 100644 --- a/src/nvim/mark.c +++ b/src/nvim/mark.c @@ -632,6 +632,7 @@ void ex_marks(exarg_T *eap) char_u *arg = eap->arg; int i; char_u *name; + pos_T *posp, *startp, *endp; if (arg != NULL && *arg == NUL) arg = NULL; @@ -657,8 +658,18 @@ void ex_marks(exarg_T *eap) show_one_mark(']', arg, &curbuf->b_op_end, NULL, true); show_one_mark('^', arg, &curbuf->b_last_insert.mark, NULL, true); show_one_mark('.', arg, &curbuf->b_last_change.mark, NULL, true); - show_one_mark('<', arg, &curbuf->b_visual.vi_start, NULL, true); - show_one_mark('>', arg, &curbuf->b_visual.vi_end, NULL, true); + + // Show the marks as where they will jump to. + startp = &curbuf->b_visual.vi_start; + endp = &curbuf->b_visual.vi_end; + if ((lt(*startp, *endp) || endp->lnum == 0) && startp->lnum != 0) { + posp = startp; + } else { + posp = endp; + } + show_one_mark('<', arg, posp, NULL, true); + show_one_mark('>', arg, posp == startp ? endp : startp, NULL, true); + show_one_mark(-1, arg, NULL, NULL, false); } diff --git a/src/nvim/marktree.c b/src/nvim/marktree.c index 6dd452b5af..e9ea2cbba9 100644 --- a/src/nvim/marktree.c +++ b/src/nvim/marktree.c @@ -326,38 +326,37 @@ void marktree_del_itr(MarkTree *b, MarkTreeIter *itr, bool rev) x->n--; // 4. - if (adjustment) { - if (adjustment == 1) { - abort(); - } else { // adjustment == -1 - int ilvl = itr->lvl-1; - mtnode_t *lnode = x; - do { - mtnode_t *p = lnode->parent; - if (ilvl < 0) { - abort(); - } - int i = itr->s[ilvl].i; - assert(p->ptr[i] == lnode); - if (i > 0) { - unrelative(p->key[i-1].pos, &intkey.pos); - } - lnode = p; - ilvl--; - } while (lnode != cur); - - mtkey_t deleted = cur->key[curi]; - cur->key[curi] = intkey; - refkey(b, cur, curi); - relative(intkey.pos, &deleted.pos); - mtnode_t *y = cur->ptr[curi+1]; - if (deleted.pos.row || deleted.pos.col) { - while (y) { - for (int k = 0; k < y->n; k++) { - unrelative(deleted.pos, &y->key[k].pos); - } - y = y->level ? y->ptr[0] : NULL; + // if (adjustment == 1) { + // abort(); + // } + if (adjustment == -1) { + int ilvl = itr->lvl-1; + const mtnode_t *lnode = x; + do { + const mtnode_t *const p = lnode->parent; + if (ilvl < 0) { + abort(); + } + const int i = itr->s[ilvl].i; + assert(p->ptr[i] == lnode); + if (i > 0) { + unrelative(p->key[i-1].pos, &intkey.pos); + } + lnode = p; + ilvl--; + } while (lnode != cur); + + mtkey_t deleted = cur->key[curi]; + cur->key[curi] = intkey; + refkey(b, cur, curi); + relative(intkey.pos, &deleted.pos); + mtnode_t *y = cur->ptr[curi+1]; + if (deleted.pos.row || deleted.pos.col) { + while (y) { + for (int k = 0; k < y->n; k++) { + unrelative(deleted.pos, &y->key[k].pos); } + y = y->level ? y->ptr[0] : NULL; } } } @@ -435,9 +434,10 @@ void marktree_del_itr(MarkTree *b, MarkTreeIter *itr, bool rev) // BONUS STEP: fix the iterator, so that it points to the key afterwards // TODO(bfredl): with "rev" should point before - if (adjustment == 1) { - abort(); - } else if (adjustment == -1) { + // if (adjustment == 1) { + // abort(); + // } + if (adjustment == -1) { // tricky: we stand at the deleted space in the previous leaf node. // But the inner key is now the previous key we stole, so we need // to skip that one as well. diff --git a/src/nvim/marktree.h b/src/nvim/marktree.h index 0c73e75b2e..8a1c564a6d 100644 --- a/src/nvim/marktree.h +++ b/src/nvim/marktree.h @@ -2,6 +2,7 @@ #define NVIM_MARKTREE_H #include <stdint.h> +#include "nvim/pos.h" #include "nvim/map.h" #include "nvim/garray.h" diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c index e67be60aa6..ec4f4cbc21 100644 --- a/src/nvim/mbyte.c +++ b/src/nvim/mbyte.c @@ -512,7 +512,7 @@ int utf_ptr2cells(const char_u *p) { int c; - /* Need to convert to a wide character. */ + // Need to convert to a character number. if (*p >= 0x80) { c = utf_ptr2char(p); /* An illegal byte is displayed as <xx>. */ @@ -582,7 +582,7 @@ size_t mb_string2cells_len(const char_u *str, size_t size) return clen; } -/// Convert a UTF-8 byte sequence to a wide character +/// Convert a UTF-8 byte sequence to a character number. /// /// If the sequence is illegal or truncated by a NUL then the first byte is /// returned. @@ -1624,6 +1624,146 @@ int utf_head_off(const char_u *base, const char_u *p) return (int)(p - q); } +// Whether space is NOT allowed before/after 'c'. +bool utf_eat_space(int cc) + FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT +{ + return (cc >= 0x2000 && cc <= 0x206F) // General punctuations + || (cc >= 0x2e00 && cc <= 0x2e7f) // Supplemental punctuations + || (cc >= 0x3000 && cc <= 0x303f) // CJK symbols and punctuations + || (cc >= 0xff01 && cc <= 0xff0f) // Full width ASCII punctuations + || (cc >= 0xff1a && cc <= 0xff20) // .. + || (cc >= 0xff3b && cc <= 0xff40) // .. + || (cc >= 0xff5b && cc <= 0xff65); // .. +} + +// Whether line break is allowed before "cc". +bool utf_allow_break_before(int cc) + FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT +{ + static const int BOL_prohibition_punct[] = { + '!', + '%', + ')', + ',', + ':', + ';', + '>', + '?', + ']', + '}', + 0x2019, // โ right single quotation mark + 0x201d, // โ right double quotation mark + 0x2020, // โ dagger + 0x2021, // โก double dagger + 0x2026, // โฆ horizontal ellipsis + 0x2030, // โฐ per mille sign + 0x2031, // โฑ per then thousand sign + 0x203c, // โผ double exclamation mark + 0x2047, // โ double question mark + 0x2048, // โ question exclamation mark + 0x2049, // โ exclamation question mark + 0x2103, // โ degree celsius + 0x2109, // โ degree fahrenheit + 0x3001, // ใ ideographic comma + 0x3002, // ใ ideographic full stop + 0x3009, // ใ right angle bracket + 0x300b, // ใ right double angle bracket + 0x300d, // ใ right corner bracket + 0x300f, // ใ right white corner bracket + 0x3011, // ใ right black lenticular bracket + 0x3015, // ใ right tortoise shell bracket + 0x3017, // ใ right white lenticular bracket + 0x3019, // ใ right white tortoise shell bracket + 0x301b, // ใ right white square bracket + 0xff01, // ๏ผ fullwidth exclamation mark + 0xff09, // ๏ผ fullwidth right parenthesis + 0xff0c, // ๏ผ fullwidth comma + 0xff0e, // ๏ผ fullwidth full stop + 0xff1a, // ๏ผ fullwidth colon + 0xff1b, // ๏ผ fullwidth semicolon + 0xff1f, // ๏ผ fullwidth question mark + 0xff3d, // ๏ผฝ fullwidth right square bracket + 0xff5d, // ๏ฝ fullwidth right curly bracket + }; + + int first = 0; + int last = ARRAY_SIZE(BOL_prohibition_punct) - 1; + + while (first < last) { + const int mid = (first + last) / 2; + + if (cc == BOL_prohibition_punct[mid]) { + return false; + } else if (cc > BOL_prohibition_punct[mid]) { + first = mid + 1; + } else { + last = mid - 1; + } + } + + return cc != BOL_prohibition_punct[first]; +} + +// Whether line break is allowed after "cc". +bool utf_allow_break_after(int cc) + FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT +{ + static const int EOL_prohibition_punct[] = { + '(', + '<', + '[', + '`', + '{', + // 0x2014, // โ em dash + 0x2018, // โ left single quotation mark + 0x201c, // โ left double quotation mark + // 0x2053, // ๏ฝ swung dash + 0x3008, // ใ left angle bracket + 0x300a, // ใ left double angle bracket + 0x300c, // ใ left corner bracket + 0x300e, // ใ left white corner bracket + 0x3010, // ใ left black lenticular bracket + 0x3014, // ใ left tortoise shell bracket + 0x3016, // ใ left white lenticular bracket + 0x3018, // ใ left white tortoise shell bracket + 0x301a, // ใ left white square bracket + 0xff08, // ๏ผ fullwidth left parenthesis + 0xff3b, // ๏ผป fullwidth left square bracket + 0xff5b, // ๏ฝ fullwidth left curly bracket + }; + + int first = 0; + int last = ARRAY_SIZE(EOL_prohibition_punct) - 1; + + while (first < last) { + const int mid = (first + last)/2; + + if (cc == EOL_prohibition_punct[mid]) { + return false; + } else if (cc > EOL_prohibition_punct[mid]) { + first = mid + 1; + } else { + last = mid - 1; + } + } + + return cc != EOL_prohibition_punct[first]; +} + +// Whether line break is allowed between "cc" and "ncc". +bool utf_allow_break(int cc, int ncc) + FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT +{ + // don't break between two-letter punctuations + if (cc == ncc + && (cc == 0x2014 // em dash + || cc == 0x2026)) { // horizontal ellipsis + return false; + } + return utf_allow_break_after(cc) && utf_allow_break_before(ncc); +} + /// Copy a character, advancing the pointers /// /// @param[in,out] fp Source of the character to copy. diff --git a/src/nvim/memline.c b/src/nvim/memline.c index d5788d96b3..57ed0d6588 100644 --- a/src/nvim/memline.c +++ b/src/nvim/memline.c @@ -257,11 +257,12 @@ int ml_open(buf_T *buf) /* * init fields in memline struct */ - buf->b_ml.ml_stack_size = 0; /* no stack yet */ - buf->b_ml.ml_stack = NULL; /* no stack yet */ - buf->b_ml.ml_stack_top = 0; /* nothing in the stack */ - buf->b_ml.ml_locked = NULL; /* no cached block */ - buf->b_ml.ml_line_lnum = 0; /* no cached line */ + buf->b_ml.ml_stack_size = 0; // no stack yet + buf->b_ml.ml_stack = NULL; // no stack yet + buf->b_ml.ml_stack_top = 0; // nothing in the stack + buf->b_ml.ml_locked = NULL; // no cached block + buf->b_ml.ml_line_lnum = 0; // no cached line + buf->b_ml.ml_line_offset = 0; buf->b_ml.ml_chunksize = NULL; if (cmdmod.noswapfile) { @@ -831,11 +832,12 @@ void ml_recover(bool checkext) /* * init fields in memline struct */ - buf->b_ml.ml_stack_size = 0; /* no stack yet */ - buf->b_ml.ml_stack = NULL; /* no stack yet */ - buf->b_ml.ml_stack_top = 0; /* nothing in the stack */ - buf->b_ml.ml_line_lnum = 0; /* no cached line */ - buf->b_ml.ml_locked = NULL; /* no locked block */ + buf->b_ml.ml_stack_size = 0; // no stack yet + buf->b_ml.ml_stack = NULL; // no stack yet + buf->b_ml.ml_stack_top = 0; // nothing in the stack + buf->b_ml.ml_line_lnum = 0; // no cached line + buf->b_ml.ml_line_offset = 0; + buf->b_ml.ml_locked = NULL; // no locked block buf->b_ml.ml_flags = 0; /* @@ -1358,7 +1360,7 @@ recover_names ( * Try finding a swap file by simply adding ".swp" to the file name. */ if (*dirp == NUL && file_count + num_files == 0 && fname != NULL) { - char_u *swapname = (char_u *)modname((char *)fname_res, ".swp", TRUE); + char_u *swapname = (char_u *)modname((char *)fname_res, ".swp", true); if (swapname != NULL) { if (os_path_exists(swapname)) { files = xmalloc(sizeof(char_u *)); @@ -1636,10 +1638,11 @@ static int recov_file_names(char_u **names, char_u *path, int prepend_dot) // May also add the file name with a dot prepended, for swap file in same // dir as original file. if (prepend_dot) { - names[num_names] = (char_u *)modname((char *)path, ".sw?", TRUE); - if (names[num_names] == NULL) + names[num_names] = (char_u *)modname((char *)path, ".sw?", true); + if (names[num_names] == NULL) { return num_names; - ++num_names; + } + num_names++; } // Form the normal swap file name pattern by appending ".sw?". @@ -1816,6 +1819,7 @@ ml_get_buf ( linenr_T lnum, bool will_change // line will be changed ) + FUNC_ATTR_NONNULL_ALL { bhdr_T *hp; DATA_BL *dp; @@ -2403,12 +2407,13 @@ void ml_add_deleted_len_buf(buf_T *buf, char_u *ptr, ssize_t len) if (len == -1) { len = STRLEN(ptr); } - buf->deleted_bytes += len+1; - if (buf->update_need_codepoints) { - mb_utflen(ptr, len, &buf->deleted_codepoints, - &buf->deleted_codeunits); - buf->deleted_codepoints++; // NL char - buf->deleted_codeunits++; + curbuf->deleted_bytes += len+1; + curbuf->deleted_bytes2 += len+1; + if (curbuf->update_need_codepoints) { + mb_utflen(ptr, len, &curbuf->deleted_codepoints, + &curbuf->deleted_codeunits); + curbuf->deleted_codepoints++; // NL char + curbuf->deleted_codeunits++; } } @@ -2418,17 +2423,17 @@ int ml_replace(linenr_T lnum, char_u *line, bool copy) return ml_replace_buf(curbuf, lnum, line, copy); } -/* - * Replace line lnum, with buffering, in current buffer. - * - * If "copy" is TRUE, make a copy of the line, otherwise the line has been - * copied to allocated memory already. - * - * Check: The caller of this function should probably also call - * changed_lines(), unless update_screen(NOT_VALID) is used. - * - * return FAIL for failure, OK otherwise - */ +// Replace line "lnum", with buffering, in current buffer. +// +// If "copy" is true, make a copy of the line, otherwise the line has been +// copied to allocated memory already. +// If "copy" is false the "line" may be freed to add text properties! +// Do not use it after calling ml_replace(). +// +// Check: The caller of this function should probably also call +// changed_lines(), unless update_screen(NOT_VALID) is used. +// +// return FAIL for failure, OK otherwise int ml_replace_buf(buf_T *buf, linenr_T lnum, char_u *line, bool copy) { if (line == NULL) /* just checking... */ @@ -2831,6 +2836,7 @@ static void ml_flush_line(buf_T *buf) } buf->b_ml.ml_line_lnum = 0; + buf->b_ml.ml_line_offset = 0; } /* @@ -3188,6 +3194,12 @@ char_u *makeswapname(char_u *fname, char_u *ffname, buf_T *buf, char_u *dir_name char_u *fname_res = fname; #ifdef HAVE_READLINK char_u fname_buf[MAXPATHL]; + + // Expand symlink in the file name, so that we put the swap file with the + // actual file instead of with the symlink. + if (resolve_symlink(fname, fname_buf) == OK) { + fname_res = fname_buf; + } #endif int len = (int)STRLEN(dir_name); @@ -3196,20 +3208,14 @@ char_u *makeswapname(char_u *fname, char_u *ffname, buf_T *buf, char_u *dir_name && len > 1 && s[-1] == s[-2]) { // Ends with '//', Use Full path r = NULL; - if ((s = (char_u *)make_percent_swname((char *)dir_name, (char *)fname)) != NULL) { - r = (char_u *)modname((char *)s, ".swp", FALSE); + s = (char_u *)make_percent_swname((char *)dir_name, (char *)fname_res); + if (s != NULL) { + r = (char_u *)modname((char *)s, ".swp", false); xfree(s); } return r; } -#ifdef HAVE_READLINK - /* Expand symlink in the file name, so that we put the swap file with the - * actual file instead of with the symlink. */ - if (resolve_symlink(fname, fname_buf) == OK) - fname_res = fname_buf; -#endif - // Prepend a '.' to the swap file name for the current directory. r = (char_u *)modname((char *)fname_res, ".swp", dir_name[0] == '.' && dir_name[1] == NUL); @@ -3980,10 +3986,10 @@ static void ml_updatechunk(buf_T *buf, linenr_T line, long len, int updtype) /// Find offset for line or line with offset. /// /// @param buf buffer to use -/// @param lnum if > 0, find offset of lnum, store offset in offp +/// @param lnum if > 0, find offset of lnum, return offset /// if == 0, return line with offset *offp -/// @param offp Location where offset of line is stored, or to read offset to -/// use to find line. In the later case, store remaining offset. +/// @param offp offset to use to find line, store remaining column offset +/// Should be NULL when getting offset of line /// @param no_ff ignore 'fileformat' option, always use one byte for NL. /// /// @return -1 if information is not available @@ -4003,8 +4009,22 @@ long ml_find_line_or_offset(buf_T *buf, linenr_T lnum, long *offp, bool no_ff) int ffdos = !no_ff && (get_fileformat(buf) == EOL_DOS); int extra = 0; - // take care of cached line first - ml_flush_line(buf); + // take care of cached line first. Only needed if the cached line is before + // the requested line. Additionally cache the value for the cached line. + // This is used by the extmark code which needs the byte offset of the edited + // line. So when doing multiple small edits on the same line the value is + // only calculated once. + // + // NB: caching doesn't work with 'fileformat'. This is not a problem for + // bytetracking, as bytetracking ignores 'fileformat' option. But calling + // line2byte() will invalidate the cache for the time being (this function + // was never cached to start with anyway). + bool can_cache = (lnum != 0 && !ffdos && buf->b_ml.ml_line_lnum == lnum); + if (lnum == 0 || buf->b_ml.ml_line_lnum < lnum || !no_ff) { + ml_flush_line(curbuf); + } else if (can_cache && buf->b_ml.ml_line_offset > 0) { + return buf->b_ml.ml_line_offset; + } if (buf->b_ml.ml_usedchunks == -1 || buf->b_ml.ml_chunksize == NULL @@ -4100,6 +4120,10 @@ long ml_find_line_or_offset(buf_T *buf, linenr_T lnum, long *offp, bool no_ff) } } + if (can_cache && size > 0) { + buf->b_ml.ml_line_offset = size; + } + return size; } diff --git a/src/nvim/memline_defs.h b/src/nvim/memline_defs.h index edd933b2cd..9a6f29a908 100644 --- a/src/nvim/memline_defs.h +++ b/src/nvim/memline_defs.h @@ -57,6 +57,8 @@ typedef struct memline { linenr_T ml_line_lnum; // line number of cached line, 0 if not valid char_u *ml_line_ptr; // pointer to cached line + size_t ml_line_offset; // cached byte offset of ml_line_lnum + int ml_line_offset_ff; // fileformat of cached line bhdr_T *ml_locked; // block used by last ml_get linenr_T ml_locked_low; // first line in ml_locked diff --git a/src/nvim/menu.c b/src/nvim/menu.c index b060464383..4ba2e36656 100644 --- a/src/nvim/menu.c +++ b/src/nvim/menu.c @@ -193,7 +193,7 @@ ex_menu(exarg_T *eap) vimmenu_T **root_menu_ptr = get_root_menu(menu_path); if (root_menu_ptr == &curwin->w_winbar) { // Assume the window toolbar menu will change. - redraw_later(NOT_VALID); + redraw_later(curwin, NOT_VALID); } if (enable != kNone) { diff --git a/src/nvim/message.c b/src/nvim/message.c index 8999365d32..06ba607323 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -607,8 +607,7 @@ int emsg_not_now(void) static bool emsg_multiline(const char *s, bool multiline) { int attr; - int ignore = false; - int severe; + bool ignore = false; // Skip this if not giving error messages at the moment. if (emsg_not_now()) { @@ -617,9 +616,9 @@ static bool emsg_multiline(const char *s, bool multiline) called_emsg = true; - // If "emsg_severe" is TRUE: When an error exception is to be thrown, + // If "emsg_severe" is true: When an error exception is to be thrown, // prefer this message over previous messages for the same command. - severe = emsg_severe; + bool severe = emsg_severe; emsg_severe = false; if (!emsg_off || vim_strchr(p_debug, 't') != NULL) { @@ -630,7 +629,7 @@ static bool emsg_multiline(const char *s, bool multiline) * when the message should be ignored completely (used for the * interrupt message). */ - if (cause_errthrow((char_u *)s, severe, &ignore) == true) { + if (cause_errthrow((char_u *)s, severe, &ignore)) { if (!ignore) { did_emsg++; } @@ -1217,7 +1216,7 @@ void wait_return(int redraw) ui_refresh(); } else if (!skip_redraw) { if (redraw == true || (msg_scrolled != 0 && redraw != -1)) { - redraw_later(VALID); + redraw_later(curwin, VALID); } if (ui_has(kUIMessages)) { msg_ext_clear(true); @@ -1622,7 +1621,7 @@ const char *str2special(const char **const sp, const bool replace_spaces, // Check for an illegal byte. if (MB_BYTE2LEN((uint8_t)(*str)) > len) { - transchar_nonprint((char_u *)buf, c); + transchar_nonprint(curbuf, (char_u *)buf, c); *sp = str + 1; return buf; } @@ -1887,6 +1886,7 @@ void msg_puts_attr_len(const char *const str, const ptrdiff_t len, int attr) // wait-return prompt later. Needed when scrolling, resetting // need_wait_return after some prompt, and then outputting something // without scrolling + // Not needed when only using CR to move the cursor. bool overflow = false; if (ui_has(kUIMessages)) { int count = msg_ext_visible + (msg_ext_overwrite ? 0 : 1); @@ -1898,7 +1898,7 @@ void msg_puts_attr_len(const char *const str, const ptrdiff_t len, int attr) overflow = msg_scrolled != 0; } - if (overflow && !msg_scrolled_ign) { + if (overflow && !msg_scrolled_ign && strcmp(str, "\r") != 0) { need_wait_return = true; } msg_didany = true; // remember that something was outputted @@ -2000,7 +2000,7 @@ static void msg_puts_display(const char_u *str, int maxlen, int attr, || (*s == TAB && msg_col <= 7) || (utf_ptr2cells(s) > 1 && msg_col <= 2)) - : (msg_col + t_col >= Columns - 1 + : ((*s != '\r' && msg_col + t_col >= Columns - 1) || (*s == TAB && msg_col + t_col >= ((Columns - 1) & ~7)) || (utf_ptr2cells(s) > 1 @@ -2222,7 +2222,7 @@ void msg_scroll_up(bool may_throttle) /// /// Probably message scrollback storage should reimplented as a file_buffer, and /// message scrolling in TUI be reimplemented as a modal floating window. Then -/// we get throttling "for free" using standard redraw_win_later code paths. +/// we get throttling "for free" using standard redraw_later code paths. void msg_scroll_flush(void) { if (msg_grid.throttled) { diff --git a/src/nvim/misc1.c b/src/nvim/misc1.c index 6dafbafb3e..fcffe64595 100644 --- a/src/nvim/misc1.c +++ b/src/nvim/misc1.c @@ -983,7 +983,7 @@ void preserve_exit(void) FOR_ALL_BUFFERS(buf) { if (buf->b_ml.ml_mfp != NULL && buf->b_ml.ml_mfp->mf_fname != NULL) { - mch_errmsg((uint8_t *)"Vim: preserving files...\n"); + mch_errmsg("Vim: preserving files...\r\n"); ui_flush(); ml_sync_all(false, false, true); // preserve all swap files break; @@ -992,7 +992,7 @@ void preserve_exit(void) ml_close_all(false); // close all memfiles, without deleting - mch_errmsg("Vim: Finished.\n"); + mch_errmsg("Vim: Finished.\r\n"); getout(1); } @@ -1091,8 +1091,9 @@ char_u *get_cmd_output(char_u *cmd, char_u *infile, ShellOpts flags, { char_u *buffer = NULL; - if (check_restricted() || check_secure()) + if (check_secure()) { return NULL; + } // get a name for the temp file char_u *tempname = vim_tempname(); diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c index fcd9ee4f75..cff88de00b 100644 --- a/src/nvim/mouse.c +++ b/src/nvim/mouse.c @@ -75,6 +75,7 @@ int jump_to_mouse(int flags, int grid = mouse_grid; int mouse_char; int fdc = 0; + ScreenGrid *gp = &default_grid; mouse_past_bottom = false; mouse_past_eol = false; @@ -125,16 +126,6 @@ retnomove: if (flags & MOUSE_SETPOS) goto retnomove; // ugly goto... - // Remember the character under the mouse, might be one of foldclose or - // foldopen fillchars in the fold column. - if (row >= 0 && row < Rows && col >= 0 && col <= Columns - && default_grid.chars != NULL) { - mouse_char = utf_ptr2char(default_grid.chars[default_grid.line_offset[row] - + (unsigned)col]); - } else { - mouse_char = ' '; - } - old_curwin = curwin; old_cursor = curwin->w_cursor; @@ -286,7 +277,7 @@ retnomove: check_topfill(curwin, false); curwin->w_valid &= ~(VALID_WROW|VALID_CROW|VALID_BOTLINE|VALID_BOTLINE_AP); - redraw_later(VALID); + redraw_later(curwin, VALID); row = 0; } else if (row >= curwin->w_height_inner) { count = 0; @@ -316,7 +307,7 @@ retnomove: } } check_topfill(curwin, false); - redraw_later(VALID); + redraw_later(curwin, VALID); curwin->w_valid &= ~(VALID_WROW|VALID_CROW|VALID_BOTLINE|VALID_BOTLINE_AP); row = curwin->w_height_inner - 1; @@ -333,6 +324,19 @@ retnomove: } } + // Remember the character under the mouse, might be one of foldclose or + // foldopen fillchars in the fold column. + if (ui_has(kUIMultigrid)) { + gp = &curwin->w_grid; + } + if (row >= 0 && row < Rows && col >= 0 && col <= Columns + && gp->chars != NULL) { + mouse_char = utf_ptr2char(gp->chars[gp->line_offset[row] + + (unsigned)col]); + } else { + mouse_char = ' '; + } + // Check for position outside of the fold column. if (curwin->w_p_rl ? col < curwin->w_width_inner - fdc : col >= fdc + (cmdwin_type == 0 ? 0 : 1)) { @@ -450,7 +454,7 @@ bool mouse_comp_pos(win_T *win, int *rowp, int *colp, linenr_T *lnump) // skip line number and fold column in front of the line col -= win_col_off(win); - if (col < 0) { + if (col <= 0) { col = 0; } diff --git a/src/nvim/move.c b/src/nvim/move.c index 8a8a639a52..ccd19a81de 100644 --- a/src/nvim/move.c +++ b/src/nvim/move.c @@ -107,7 +107,7 @@ void redraw_for_cursorline(win_T *wp) && !pum_visible()) { if (wp->w_p_rnu) { // win_line() will redraw the number column only. - redraw_win_later(wp, VALID); + redraw_later(wp, VALID); } if (win_cursorline_standout(wp)) { if (wp->w_redr_type <= VALID && wp->w_last_cursorline != 0) { @@ -117,7 +117,7 @@ void redraw_for_cursorline(win_T *wp) redrawWinline(wp, wp->w_last_cursorline); redrawWinline(wp, wp->w_cursor.lnum); } else { - redraw_win_later(wp, SOME_VALID); + redraw_later(wp, SOME_VALID); } } } @@ -172,7 +172,7 @@ void update_topline(void) // If the buffer is empty, always set topline to 1. if (BUFEMPTY()) { // special case - file is empty if (curwin->w_topline != 1) { - redraw_later(NOT_VALID); + redraw_later(curwin, NOT_VALID); } curwin->w_topline = 1; curwin->w_botline = 2; @@ -326,12 +326,14 @@ void update_topline(void) dollar_vcol = -1; if (curwin->w_skipcol != 0) { curwin->w_skipcol = 0; - redraw_later(NOT_VALID); - } else - redraw_later(VALID); - /* May need to set w_skipcol when cursor in w_topline. */ - if (curwin->w_cursor.lnum == curwin->w_topline) + redraw_later(curwin, NOT_VALID); + } else { + redraw_later(curwin, VALID); + } + // May need to set w_skipcol when cursor in w_topline. + if (curwin->w_cursor.lnum == curwin->w_topline) { validate_cursor(); + } } *so_ptr = save_so; @@ -439,7 +441,7 @@ void changed_window_setting_win(win_T *wp) wp->w_lines_valid = 0; changed_line_abv_curs_win(wp); wp->w_valid &= ~(VALID_BOTLINE|VALID_BOTLINE_AP|VALID_TOPLINE); - redraw_win_later(wp, NOT_VALID); + redraw_later(wp, NOT_VALID); } /* @@ -455,8 +457,8 @@ void set_topline(win_T *wp, linenr_T lnum) wp->w_topline_was_set = true; wp->w_topfill = 0; wp->w_valid &= ~(VALID_WROW|VALID_CROW|VALID_BOTLINE|VALID_TOPLINE); - /* Don't set VALID_TOPLINE here, 'scrolloff' needs to be checked. */ - redraw_later(VALID); + // Don't set VALID_TOPLINE here, 'scrolloff' needs to be checked. + redraw_later(wp, VALID); } /* @@ -634,14 +636,14 @@ void validate_virtcol_win(win_T *wp) if (wp->w_p_cuc && !pum_visible() ) - redraw_win_later(wp, SOME_VALID); + redraw_later(wp, SOME_VALID); } } /* * Validate curwin->w_cline_height only. */ -static void validate_cheight(void) +void validate_cheight(void) { check_cursor_moved(curwin); if (!(curwin->w_valid & VALID_CHEIGHT)) { @@ -830,7 +832,7 @@ void curs_columns( curwin->w_leftcol = new_leftcol; win_check_anchored_floats(curwin); // screen has to be redrawn with new curwin->w_leftcol - redraw_later(NOT_VALID); + redraw_later(curwin, NOT_VALID); } } curwin->w_wcol -= curwin->w_leftcol; @@ -934,15 +936,19 @@ void curs_columns( } else { curwin->w_skipcol = 0; } - if (prev_skipcol != curwin->w_skipcol) - redraw_later(NOT_VALID); + if (prev_skipcol != curwin->w_skipcol) { + redraw_later(curwin, NOT_VALID); + } /* Redraw when w_virtcol changes and 'cursorcolumn' is set */ if (curwin->w_p_cuc && (curwin->w_valid & VALID_VIRTCOL) == 0 && !pum_visible()) { - redraw_later(SOME_VALID); + redraw_later(curwin, SOME_VALID); } + // now w_leftcol is valid, avoid check_cursor_moved() thinking otherwise + curwin->w_valid_leftcol = curwin->w_leftcol; + curwin->w_valid |= VALID_WCOL|VALID_WROW|VALID_VIRTCOL; } @@ -1014,16 +1020,13 @@ void textpos2screenpos(win_T *wp, pos_T *pos, int *rowp, int *scolp, *ecolp = ecol + coloff; } -/* - * Scroll the current window down by "line_count" logical lines. "CTRL-Y" - */ -void -scrolldown ( - long line_count, - int byfold /* true: count a closed fold as one line */ -) +/// Scroll the current window down by "line_count" logical lines. "CTRL-Y" +/// +/// @param line_count number of lines to scroll +/// @param byfold if true, count a closed fold as one line +bool scrolldown(long line_count, int byfold) { - int done = 0; /* total # of physical lines done */ + int done = 0; // total # of physical lines done /* Make sure w_topline is at the first of a sequence of folded lines. */ (void)hasFolding(curwin->w_topline, &curwin->w_topline, NULL); @@ -1092,17 +1095,18 @@ scrolldown ( foldAdjustCursor(); coladvance(curwin->w_curswant); } + return moved; } -/* - * Scroll the current window up by "line_count" logical lines. "CTRL-E" - */ -void -scrollup ( - long line_count, - int byfold /* true: count a closed fold as one line */ -) +/// Scroll the current window up by "line_count" logical lines. "CTRL-E" +/// +/// @param line_count number of lines to scroll +/// @param byfold if true, count a closed fold as one line +bool scrollup(long line_count, int byfold) { + linenr_T topline = curwin->w_topline; + linenr_T botline = curwin->w_botline; + if ((byfold && hasAnyFolding(curwin)) || curwin->w_p_diff) { // count each sequence of folded lines as one logical line @@ -1145,6 +1149,11 @@ scrollup ( ~(VALID_WROW|VALID_WCOL|VALID_CHEIGHT|VALID_CROW|VALID_VIRTCOL); coladvance(curwin->w_curswant); } + + bool moved = topline != curwin->w_topline + || botline != curwin->w_botline; + + return moved; } /* @@ -2010,7 +2019,7 @@ int onepage(Direction dir, long count) } } - redraw_later(VALID); + redraw_later(curwin, VALID); return retval; } @@ -2196,7 +2205,7 @@ void halfpage(bool flag, linenr_T Prenum) check_topfill(curwin, !flag); cursor_correct(); beginline(BL_SOL | BL_FIX); - redraw_later(VALID); + redraw_later(curwin, VALID); } void do_check_cursorbind(void) @@ -2244,7 +2253,7 @@ void do_check_cursorbind(void) } // Correct cursor for multi-byte character. mb_adjust_cursor(); - redraw_later(VALID); + redraw_later(curwin, VALID); // Only scroll when 'scrollbind' hasn't done this. if (!curwin->w_p_scb) { diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c index 92ca29209e..68ef4cd41e 100644 --- a/src/nvim/msgpack_rpc/channel.c +++ b/src/nvim/msgpack_rpc/channel.c @@ -262,11 +262,9 @@ static void parse_msgpack(Channel *channel) call_set_error(channel, buf, ERROR_LOG_LEVEL); } msgpack_unpacked_destroy(&unpacked); - // Bail out from this event loop iteration - return; + } else { + handle_request(channel, &unpacked.data); } - - handle_request(channel, &unpacked.data); } if (result == MSGPACK_UNPACK_NOMEM_ERROR) { diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 968cfde388..2805a7d74e 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -903,6 +903,10 @@ normal_end: msg_nowait = false; + if (finish_op) { + set_reg_var(get_default_register_name()); + } + // Reset finish_op, in case it was set s->c = finish_op; finish_op = false; @@ -1193,6 +1197,15 @@ static void normal_check_interrupt(NormalState *s) } } +static void normal_check_window_scrolled(NormalState *s) +{ + // Trigger Scroll if the viewport changed. + if (!finish_op && has_event(EVENT_WINSCROLLED) + && win_did_scroll(curwin)) { + do_autocmd_winscrolled(curwin); + } +} + static void normal_check_cursor_moved(NormalState *s) { // Trigger CursorMoved if the cursor moved. @@ -1216,6 +1229,16 @@ static void normal_check_text_changed(NormalState *s) } } +static void normal_check_buffer_modified(NormalState *s) +{ + // Trigger BufModified if b_modified changed + if (!finish_op && has_event(EVENT_BUFMODIFIEDSET) + && curbuf->b_changed_invalid == true) { + apply_autocmds(EVENT_BUFMODIFIEDSET, NULL, NULL, false, curbuf); + curbuf->b_changed_invalid = false; + } +} + static void normal_check_folds(NormalState *s) { // Include a closed fold completely in the Visual area. @@ -1316,8 +1339,14 @@ static int normal_check(VimState *state) if (skip_redraw || exmode_active) { skip_redraw = false; } else if (do_redraw || stuff_empty()) { + // Need to make sure w_topline and w_leftcol are correct before + // normal_check_window_scrolled() is called. + update_topline(); + normal_check_cursor_moved(s); normal_check_text_changed(s); + normal_check_window_scrolled(s); + normal_check_buffer_modified(s); // Updating diffs from changed() does not always work properly, // esp. updating folds. Do an update just before redrawing if @@ -1977,20 +2006,20 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) case OP_FOLD: VIsual_reselect = false; // don't reselect now - foldCreate(curwin, oap->start.lnum, oap->end.lnum); + foldCreate(curwin, oap->start, oap->end); break; case OP_FOLDOPEN: case OP_FOLDOPENREC: case OP_FOLDCLOSE: case OP_FOLDCLOSEREC: - VIsual_reselect = false; /* don't reselect now */ - opFoldRange(oap->start.lnum, oap->end.lnum, - oap->op_type == OP_FOLDOPEN - || oap->op_type == OP_FOLDOPENREC, - oap->op_type == OP_FOLDOPENREC - || oap->op_type == OP_FOLDCLOSEREC, - oap->is_VIsual); + VIsual_reselect = false; // don't reselect now + opFoldRange(oap->start, oap->end, + oap->op_type == OP_FOLDOPEN + || oap->op_type == OP_FOLDOPENREC, + oap->op_type == OP_FOLDOPENREC + || oap->op_type == OP_FOLDCLOSEREC, + oap->is_VIsual); break; case OP_FOLDDEL: @@ -2483,7 +2512,7 @@ do_mouse ( typval_T rettv; int doesrange; (void)call_func((char_u *)tab_page_click_defs[mouse_col].func, - (int)strlen(tab_page_click_defs[mouse_col].func), + -1, &rettv, ARRAY_SIZE(argv), argv, NULL, curwin->w_cursor.lnum, curwin->w_cursor.lnum, &doesrange, true, NULL, NULL); @@ -2590,14 +2619,16 @@ do_mouse ( && !is_drag && (jump_flags & (MOUSE_FOLD_CLOSE | MOUSE_FOLD_OPEN)) && which_button == MOUSE_LEFT) { - /* open or close a fold at this line */ - if (jump_flags & MOUSE_FOLD_OPEN) - openFold(curwin->w_cursor.lnum, 1L); - else - closeFold(curwin->w_cursor.lnum, 1L); - /* don't move the cursor if still in the same window */ - if (curwin == old_curwin) + // open or close a fold at this line + if (jump_flags & MOUSE_FOLD_OPEN) { + openFold(curwin->w_cursor, 1L); + } else { + closeFold(curwin->w_cursor, 1L); + } + // don't move the cursor if still in the same window + if (curwin == old_curwin) { curwin->w_cursor = save_cursor; + } } @@ -3599,7 +3630,7 @@ void check_scrollbind(linenr_T topline_diff, long leftcol_diff) scrolldown(-y, false); } - redraw_later(VALID); + redraw_later(curwin, VALID); cursor_correct(); curwin->w_redr_status = true; } @@ -4105,10 +4136,10 @@ void scroll_redraw(int up, long count) int prev_topfill = curwin->w_topfill; linenr_T prev_lnum = curwin->w_cursor.lnum; - if (up) - scrollup(count, true); - else + bool moved = up ? + scrollup(count, true) : scrolldown(count, true); + if (get_scrolloff_value()) { // Adjust the cursor position for 'scrolloff'. Mark w_topline as // valid, otherwise the screen jumps back at the end of the file. @@ -4138,10 +4169,13 @@ void scroll_redraw(int up, long count) curwin->w_valid |= VALID_TOPLINE; } } - if (curwin->w_cursor.lnum != prev_lnum) + if (curwin->w_cursor.lnum != prev_lnum) { coladvance(curwin->w_curswant); - curwin->w_viewport_invalid = true; - redraw_later(VALID); + } + if (moved) { + curwin->w_viewport_invalid = true; + } + redraw_later(curwin, VALID); } /* @@ -4239,7 +4273,7 @@ dozet: FALLTHROUGH; case 't': scroll_cursor_top(0, true); - redraw_later(VALID); + redraw_later(curwin, VALID); set_fraction(curwin); break; @@ -4248,7 +4282,7 @@ dozet: FALLTHROUGH; case 'z': scroll_cursor_halfway(true); - redraw_later(VALID); + redraw_later(curwin, VALID); set_fraction(curwin); break; @@ -4269,7 +4303,7 @@ dozet: FALLTHROUGH; case 'b': scroll_cursor_bot(0, true); - redraw_later(VALID); + redraw_later(curwin, VALID); set_fraction(curwin); break; @@ -4316,7 +4350,7 @@ dozet: col = 0; if (curwin->w_leftcol != col) { curwin->w_leftcol = col; - redraw_later(NOT_VALID); + redraw_later(curwin, NOT_VALID); } } break; @@ -4335,7 +4369,7 @@ dozet: } if (curwin->w_leftcol != col) { curwin->w_leftcol = col; - redraw_later(NOT_VALID); + redraw_later(curwin, NOT_VALID); } } break; @@ -4393,51 +4427,55 @@ dozet: case 'i': curwin->w_p_fen = !curwin->w_p_fen; break; - /* "za": open closed fold or close open fold at cursor */ - case 'a': if (hasFolding(curwin->w_cursor.lnum, NULL, NULL)) - openFold(curwin->w_cursor.lnum, cap->count1); - else { - closeFold(curwin->w_cursor.lnum, cap->count1); + // "za": open closed fold or close open fold at cursor + case 'a': if (hasFolding(curwin->w_cursor.lnum, NULL, NULL)) { + openFold(curwin->w_cursor, cap->count1); + } else { + closeFold(curwin->w_cursor, cap->count1); curwin->w_p_fen = true; } break; - /* "zA": open fold at cursor recursively */ - case 'A': if (hasFolding(curwin->w_cursor.lnum, NULL, NULL)) - openFoldRecurse(curwin->w_cursor.lnum); - else { - closeFoldRecurse(curwin->w_cursor.lnum); + // "zA": open fold at cursor recursively + case 'A': if (hasFolding(curwin->w_cursor.lnum, NULL, NULL)) { + openFoldRecurse(curwin->w_cursor); + } else { + closeFoldRecurse(curwin->w_cursor); curwin->w_p_fen = true; } break; - /* "zo": open fold at cursor or Visual area */ - case 'o': if (VIsual_active) + // "zo": open fold at cursor or Visual area + case 'o': if (VIsual_active) { nv_operator(cap); - else - openFold(curwin->w_cursor.lnum, cap->count1); + } else { + openFold(curwin->w_cursor, cap->count1); + } break; - /* "zO": open fold recursively */ - case 'O': if (VIsual_active) + // "zO": open fold recursively + case 'O': if (VIsual_active) { nv_operator(cap); - else - openFoldRecurse(curwin->w_cursor.lnum); + } else { + openFoldRecurse(curwin->w_cursor); + } break; - /* "zc": close fold at cursor or Visual area */ - case 'c': if (VIsual_active) + // "zc": close fold at cursor or Visual area + case 'c': if (VIsual_active) { nv_operator(cap); - else - closeFold(curwin->w_cursor.lnum, cap->count1); + } else { + closeFold(curwin->w_cursor, cap->count1); + } curwin->w_p_fen = true; break; - /* "zC": close fold recursively */ - case 'C': if (VIsual_active) + // "zC": close fold recursively + case 'C': if (VIsual_active) { nv_operator(cap); - else - closeFoldRecurse(curwin->w_cursor.lnum); + } else { + closeFoldRecurse(curwin->w_cursor); + } curwin->w_p_fen = true; break; @@ -4684,7 +4722,7 @@ static void nv_clear(cmdarg_T *cap) FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { wp->w_s->b_syn_slow = false; } - redraw_later(CLEAR); + redraw_later(curwin, CLEAR); } } @@ -6835,7 +6873,7 @@ static void nv_g_cmd(cmdarg_T *cap) } else { if (cap->count1 > 1) { // if it fails, let the cursor still move to the last char - cursor_down(cap->count1 - 1, false); + (void)cursor_down(cap->count1 - 1, false); } i = curwin->w_leftcol + curwin->w_width_inner - col_off - 1; coladvance((colnr_T)i); diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 595a699563..9e7d81fc82 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -119,7 +119,7 @@ static char opchars[][3] = { 'r', NUL, OPF_CHANGE }, // OP_REPLACE { 'I', NUL, OPF_CHANGE }, // OP_INSERT { 'A', NUL, OPF_CHANGE }, // OP_APPEND - { 'z', 'f', OPF_LINES }, // OP_FOLD + { 'z', 'f', 0 }, // OP_FOLD { 'z', 'o', OPF_LINES }, // OP_FOLDOPEN { 'z', 'O', OPF_LINES }, // OP_FOLDOPENREC { 'z', 'c', OPF_LINES }, // OP_FOLDCLOSE @@ -496,9 +496,9 @@ static void shift_block(oparg_T *oap, int amount) // replace the line ml_replace(curwin->w_cursor.lnum, newp, false); changed_bytes(curwin->w_cursor.lnum, (colnr_T)bd.textcol); - extmark_splice(curbuf, (int)curwin->w_cursor.lnum-1, startcol, - 0, oldlen, 0, newlen, - kExtmarkUndo); + extmark_splice_cols(curbuf, (int)curwin->w_cursor.lnum-1, startcol, + oldlen, newlen, + kExtmarkUndo); State = oldstate; curwin->w_cursor.col = oldcol; p_ri = old_p_ri; @@ -595,9 +595,8 @@ static void block_insert(oparg_T *oap, char_u *s, int b_insert, struct block_def STRMOVE(newp + offset, oldp); ml_replace(lnum, newp, false); - extmark_splice(curbuf, (int)lnum-1, startcol, - 0, skipped, - 0, offset-startcol, kExtmarkUndo); + extmark_splice_cols(curbuf, (int)lnum-1, startcol, + skipped, offset-startcol, kExtmarkUndo); if (lnum == oap->end.lnum) { /* Set "']" mark to the end of the block instead of the end of @@ -1534,10 +1533,9 @@ int op_delete(oparg_T *oap) // replace the line ml_replace(lnum, newp, false); - extmark_splice(curbuf, (int)lnum-1, bd.textcol, - 0, bd.textlen, - 0, bd.startspaces+bd.endspaces, - kExtmarkUndo); + extmark_splice_cols(curbuf, (int)lnum-1, bd.textcol, + bd.textlen, bd.startspaces+bd.endspaces, + kExtmarkUndo); } check_cursor_col(); @@ -1546,10 +1544,10 @@ int op_delete(oparg_T *oap) oap->line_count = 0; // no lines deleted } else if (oap->motion_type == kMTLineWise) { if (oap->op_type == OP_CHANGE) { - /* Delete the lines except the first one. Temporarily move the - * cursor to the next line. Save the current line number, if the - * last line is deleted it may be changed. - */ + // Delete the lines except the first one. Temporarily move the + // cursor to the next line. Save the current line number, if the + // last line is deleted it may be changed. + if (oap->line_count > 1) { lnum = curwin->w_cursor.lnum; ++curwin->w_cursor.lnum; @@ -1562,12 +1560,21 @@ int op_delete(oparg_T *oap) beginline(BL_WHITE); // cursor on first non-white did_ai = true; // delete the indent when ESC hit ai_col = curwin->w_cursor.col; - } else - beginline(0); /* cursor in column 0 */ - truncate_line(FALSE); /* delete the rest of the line */ - /* leave cursor past last char in line */ - if (oap->line_count > 1) - u_clearline(); /* "U" command not possible after "2cc" */ + } else { + beginline(0); // cursor in column 0 + } + + int old_len = (int)STRLEN(ml_get(curwin->w_cursor.lnum)); + truncate_line(false); // delete the rest of the line + + extmark_splice_cols(curbuf, + (int)curwin->w_cursor.lnum-1, curwin->w_cursor.col, + old_len - curwin->w_cursor.col, 0, kExtmarkUndo); + + // leave cursor past last char in line + if (oap->line_count > 1) { + u_clearline(); // "U" command not possible after "2cc" + } } else { del_lines(oap->line_count, TRUE); beginline(BL_WHITE | BL_FIX); @@ -1661,17 +1668,20 @@ int op_delete(oparg_T *oap) curpos = curwin->w_cursor; // remember curwin->w_cursor curwin->w_cursor.lnum++; del_lines(oap->line_count - 2, false); + bcount_t deleted_bytes = (bcount_t)curbuf->deleted_bytes2 - startpos.col; // delete from start of line until op_end n = (oap->end.col + 1 - !oap->inclusive); curwin->w_cursor.col = 0; (void)del_bytes((colnr_T)n, !virtual_op, oap->op_type == OP_DELETE && !oap->is_VIsual); + deleted_bytes += n; curwin->w_cursor = curpos; // restore curwin->w_cursor (void)do_join(2, false, false, false, false); curbuf_splice_pending--; extmark_splice(curbuf, (int)startpos.lnum-1, startpos.col, - (int)oap->line_count-1, n, 0, 0, kExtmarkUndo); + (int)oap->line_count-1, n, deleted_bytes, + 0, 0, 0, kExtmarkUndo); } } @@ -1711,7 +1721,7 @@ static inline void pbyte(pos_T lp, int c) assert(c <= UCHAR_MAX); *(ml_get_buf(curbuf, lp.lnum, true) + lp.col) = (char_u)c; if (!curbuf_splice_pending) { - extmark_splice(curbuf, (int)lp.lnum-1, lp.col, 0, 1, 0, 1, kExtmarkUndo); + extmark_splice_cols(curbuf, (int)lp.lnum-1, lp.col, 1, 1, kExtmarkUndo); } } @@ -1856,6 +1866,7 @@ int op_replace(oparg_T *oap, int c) } // replace the line ml_replace(curwin->w_cursor.lnum, newp, false); + curbuf_splice_pending++; linenr_T baselnum = curwin->w_cursor.lnum; if (after_p != NULL) { ml_append(curwin->w_cursor.lnum++, after_p, (int)after_p_len, false); @@ -1863,9 +1874,10 @@ int op_replace(oparg_T *oap, int c) oap->end.lnum++; xfree(after_p); } + curbuf_splice_pending--; extmark_splice(curbuf, (int)baselnum-1, bd.textcol, - 0, bd.textlen, - newrows, newcols, kExtmarkUndo); + 0, bd.textlen, bd.textlen, + newrows, newcols, newrows+newcols, kExtmarkUndo); } } else { // Characterwise or linewise motion replace. @@ -2399,9 +2411,8 @@ int op_change(oparg_T *oap) oldp += bd.textcol; STRMOVE(newp + offset, oldp); ml_replace(linenr, newp, false); - extmark_splice(curbuf, (int)linenr-1, bd.textcol, - 0, 0, - 0, vpos.coladd+(int)ins_len, kExtmarkUndo); + extmark_splice_cols(curbuf, (int)linenr-1, bd.textcol, + 0, vpos.coladd+(int)ins_len, kExtmarkUndo); } } check_cursor(); @@ -2641,7 +2652,7 @@ static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append) xfree(reg->y_array); } if (curwin->w_p_rnu) { - redraw_later(SOME_VALID); // cursor moved to start + redraw_later(curwin, SOME_VALID); // cursor moved to start } if (message) { // Display message about yank? if (yank_type == kMTCharWise && yanklines == 1) { @@ -3098,6 +3109,9 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) for (i = 0; i < y_size; i++) { int spaces; char shortline; + // can just be 0 or 1, needed for blockwise paste beyond the current + // buffer end + int lines_appended = 0; bd.startspaces = 0; bd.endspaces = 0; @@ -3111,6 +3125,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) break; } nr_lines++; + lines_appended = 1; } /* get the old line and advance to the position to insert at */ oldp = get_cursor_line_ptr(); @@ -3182,17 +3197,16 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) assert(columns >= 0); memmove(ptr, oldp + bd.textcol + delcount, (size_t)columns); ml_replace(curwin->w_cursor.lnum, newp, false); - extmark_splice(curbuf, (int)curwin->w_cursor.lnum-1, bd.textcol, - 0, delcount, - 0, (int)totlen, - kExtmarkUndo); + extmark_splice_cols(curbuf, (int)curwin->w_cursor.lnum-1, bd.textcol, + delcount, (int)totlen + lines_appended, kExtmarkUndo); ++curwin->w_cursor.lnum; if (i == 0) curwin->w_cursor.col += bd.startspaces; } - changed_lines(lnum, 0, curwin->w_cursor.lnum, nr_lines, true); + changed_lines(lnum, 0, curbuf->b_op_start.lnum + (linenr_T)y_size + - (linenr_T)nr_lines , nr_lines, true); /* Set '[ mark. */ curbuf->b_op_start = curwin->w_cursor; @@ -3238,21 +3252,43 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) --lnum; new_cursor = curwin->w_cursor; - // simple case: insert into current line + // simple case: insert into one line at a time if (y_type == kMTCharWise && y_size == 1) { linenr_T end_lnum = 0; // init for gcc + linenr_T start_lnum = lnum; if (VIsual_active) { end_lnum = curbuf->b_visual.vi_end.lnum; if (end_lnum < curbuf->b_visual.vi_start.lnum) { end_lnum = curbuf->b_visual.vi_start.lnum; } + if (end_lnum > start_lnum) { + // "col" is valid for the first line, in following lines + // the virtual column needs to be used. Matters for + // multi-byte characters. + pos_T pos = { + .lnum = lnum, + .col = col, + .coladd = 0, + }; + getvcol(curwin, &pos, NULL, &vcol, NULL); + } } do { totlen = (size_t)(count * yanklen); if (totlen > 0) { oldp = ml_get(lnum); + if (lnum > start_lnum) { + pos_T pos = { + .lnum = lnum, + }; + if (getvpos(&pos, vcol) == OK) { + col = pos.col; + } else { + col = MAXCOL; + } + } if (VIsual_active && col > (int)STRLEN(oldp)) { lnum++; continue; @@ -3287,9 +3323,8 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) if (totlen && (restart_edit != 0 || (flags & PUT_CURSEND))) ++curwin->w_cursor.col; changed_bytes(lnum, col); - extmark_splice(curbuf, (int)lnum-1, col, - 0, 0, - 0, (int)totlen, kExtmarkUndo); + extmark_splice_cols(curbuf, (int)lnum-1, col, + 0, (int)totlen, kExtmarkUndo); } else { // Insert at least one line. When y_type is kMTCharWise, break the first // line in two. @@ -3353,13 +3388,23 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) } } + bcount_t totsize = 0; + int lastsize = 0; + if (y_type == kMTCharWise + || (y_type == kMTLineWise && flags & PUT_LINE_SPLIT)) { + for (i = 0; i < y_size-1; i++) { + totsize += (bcount_t)STRLEN(y_array[i]) + 1; + } + lastsize = (int)STRLEN(y_array[y_size-1]); + totsize += lastsize; + } if (y_type == kMTCharWise) { - extmark_splice(curbuf, (int)new_cursor.lnum-1, col, 0, 0, - (int)y_size-1, (int)STRLEN(y_array[y_size-1]), + extmark_splice(curbuf, (int)new_cursor.lnum-1, col, 0, 0, 0, + (int)y_size-1, lastsize, totsize, kExtmarkUndo); } else if (y_type == kMTLineWise && flags & PUT_LINE_SPLIT) { - extmark_splice(curbuf, (int)new_cursor.lnum-1, col, 0, 0, - (int)y_size+1, 0, kExtmarkUndo); + extmark_splice(curbuf, (int)new_cursor.lnum-1, col, 0, 0, 0, + (int)y_size+1, 0, totsize+1, kExtmarkUndo); } } @@ -3788,7 +3833,8 @@ int do_join(size_t count, && (!has_format_option(FO_MBYTE_JOIN) || (utf_ptr2char(curr) < 0x100 && endcurr1 < 0x100)) && (!has_format_option(FO_MBYTE_JOIN2) - || utf_ptr2char(curr) < 0x100 || endcurr1 < 0x100) + || (utf_ptr2char(curr) < 0x100 && !utf_eat_space(endcurr1)) + || (endcurr1 < 0x100 && !utf_eat_space(utf_ptr2char(curr)))) ) { /* don't add a space if the line is ending in a space */ if (endcurr1 == ' ') @@ -3803,9 +3849,10 @@ int do_join(size_t count, } if (t > 0 && curbuf_splice_pending == 0) { + colnr_T removed = (int)(curr- curr_start); extmark_splice(curbuf, (int)curwin->w_cursor.lnum-1, sumsize, - 1, (int)(curr- curr_start), - 0, spaces[t], + 1, removed, removed + 1, + 0, spaces[t], spaces[t], kExtmarkUndo); } currsize = (int)STRLEN(curr); @@ -4112,49 +4159,41 @@ format_lines( int avoid_fex /* don't use 'formatexpr' */ ) { - int max_len; - int is_not_par; /* current line not part of parag. */ - int next_is_not_par; /* next line not part of paragraph */ - int is_end_par; /* at end of paragraph */ - int prev_is_end_par = FALSE; /* prev. line not part of parag. */ - int next_is_start_par = FALSE; - int leader_len = 0; /* leader len of current line */ - int next_leader_len; /* leader len of next line */ - char_u *leader_flags = NULL; /* flags for leader of current line */ - char_u *next_leader_flags; /* flags for leader of next line */ - int do_comments; /* format comments */ - int do_comments_list = 0; /* format comments with 'n' or '2' */ - int advance = TRUE; - int second_indent = -1; /* indent for second line (comment - * aware) */ - int do_second_indent; - int do_number_indent; - int do_trail_white; - int first_par_line = TRUE; + bool is_not_par; // current line not part of parag. + bool next_is_not_par; // next line not part of paragraph + bool is_end_par; // at end of paragraph + bool prev_is_end_par = false; // prev. line not part of parag. + bool next_is_start_par = false; + int leader_len = 0; // leader len of current line + int next_leader_len; // leader len of next line + char_u *leader_flags = NULL; // flags for leader of current line + char_u *next_leader_flags; // flags for leader of next line + bool advance = true; + int second_indent = -1; // indent for second line (comment aware) + bool first_par_line = true; int smd_save; long count; - int need_set_indent = TRUE; /* set indent of next paragraph */ - int force_format = FALSE; - int old_State = State; - - /* length of a line to force formatting: 3 * 'tw' */ - max_len = comp_textwidth(TRUE) * 3; - - /* check for 'q', '2' and '1' in 'formatoptions' */ - do_comments = has_format_option(FO_Q_COMS); - do_second_indent = has_format_option(FO_Q_SECOND); - do_number_indent = has_format_option(FO_Q_NUMBER); - do_trail_white = has_format_option(FO_WHITE_PAR); - - /* - * Get info about the previous and current line. - */ - if (curwin->w_cursor.lnum > 1) - is_not_par = fmt_check_par(curwin->w_cursor.lnum - 1 - , &leader_len, &leader_flags, do_comments - ); - else - is_not_par = TRUE; + bool need_set_indent = true; // set indent of next paragraph + bool force_format = false; + const int old_State = State; + + // length of a line to force formatting: 3 * 'tw' + const int max_len = comp_textwidth(true) * 3; + + // check for 'q', '2' and '1' in 'formatoptions' + const bool do_comments = has_format_option(FO_Q_COMS); // format comments + int do_comments_list = 0; // format comments with 'n' or '2' + const bool do_second_indent = has_format_option(FO_Q_SECOND); + const bool do_number_indent = has_format_option(FO_Q_NUMBER); + const bool do_trail_white = has_format_option(FO_WHITE_PAR); + + // Get info about the previous and current line. + if (curwin->w_cursor.lnum > 1) { + is_not_par = fmt_check_par(curwin->w_cursor.lnum - 1, + &leader_len, &leader_flags, do_comments); + } else { + is_not_par = true; + } next_is_not_par = fmt_check_par(curwin->w_cursor.lnum , &next_leader_len, &next_leader_flags, do_comments ); @@ -4179,7 +4218,7 @@ format_lines( * The last line to be formatted. */ if (count == 1 || curwin->w_cursor.lnum == curbuf->b_ml.ml_line_count) { - next_is_not_par = TRUE; + next_is_not_par = true; next_leader_len = 0; next_leader_flags = NULL; } else { @@ -4190,7 +4229,7 @@ format_lines( next_is_start_par = (get_number_indent(curwin->w_cursor.lnum + 1) > 0); } - advance = TRUE; + advance = true; is_end_par = (is_not_par || next_is_not_par || next_is_start_par); if (!is_end_par && do_trail_white) is_end_par = !ends_in_white(curwin->w_cursor.lnum); @@ -4241,7 +4280,7 @@ format_lines( leader_len, leader_flags, next_leader_len, next_leader_flags) ) - is_end_par = TRUE; + is_end_par = true; /* * If we have got to the end of a paragraph, or the line is @@ -4278,9 +4317,9 @@ format_lines( * end of the paragraph. */ if (line_count < 0) break; - first_par_line = TRUE; + first_par_line = true; } - force_format = FALSE; + force_format = false; } /* @@ -4288,7 +4327,7 @@ format_lines( * first delete the leader from the second line. */ if (!is_end_par) { - advance = FALSE; + advance = false; curwin->w_cursor.lnum++; curwin->w_cursor.col = 0; if (line_count < 0 && u_save_cursor() == FAIL) @@ -4311,12 +4350,13 @@ format_lines( beep_flush(); break; } - first_par_line = FALSE; - /* If the line is getting long, format it next time */ - if (STRLEN(get_cursor_line_ptr()) > (size_t)max_len) - force_format = TRUE; - else - force_format = FALSE; + first_par_line = false; + // If the line is getting long, format it next time + if (STRLEN(get_cursor_line_ptr()) > (size_t)max_len) { + force_format = true; + } else { + force_format = false; + } } } line_breakcheck(); @@ -4377,11 +4417,10 @@ static int fmt_check_par(linenr_T lnum, int *leader_len, char_u **leader_flags, int paragraph_start(linenr_T lnum) { char_u *p; - int leader_len = 0; /* leader len of current line */ - char_u *leader_flags = NULL; /* flags for leader of current line */ - int next_leader_len = 0; /* leader len of next line */ - char_u *next_leader_flags = NULL; /* flags for leader of next line */ - int do_comments; /* format comments */ + int leader_len = 0; // leader len of current line + char_u *leader_flags = NULL; // flags for leader of current line + int next_leader_len = 0; // leader len of next line + char_u *next_leader_flags = NULL; // flags for leader of next line if (lnum <= 1) return TRUE; /* start of the file */ @@ -4390,7 +4429,7 @@ int paragraph_start(linenr_T lnum) if (*p == NUL) return TRUE; /* after empty line */ - do_comments = has_format_option(FO_Q_COMS); + const bool do_comments = has_format_option(FO_Q_COMS); // format comments if (fmt_check_par(lnum - 1, &leader_len, &leader_flags, do_comments)) { return true; // after non-paragraph line } @@ -5897,7 +5936,7 @@ static bool get_clipboard(int name, yankreg_T **target, bool quiet) const char regname = (char)name; tv_list_append_string(args, ®name, 1); - typval_T result = eval_call_provider("clipboard", "get", args); + typval_T result = eval_call_provider("clipboard", "get", args, false); if (result.v_type != VAR_LIST) { if (result.v_type == VAR_NUMBER && result.vval.v_number == 0) { @@ -6045,7 +6084,7 @@ static void set_clipboard(int name, yankreg_T *reg) tv_list_append_string(args, ®type, 1); // -V614 tv_list_append_string(args, ((char[]) { (char)name }), 1); - (void)eval_call_provider("clipboard", "set", args); + (void)eval_call_provider("clipboard", "set", args, true); } /// Avoid slow things (clipboard) during batch operations (while/for-loops). diff --git a/src/nvim/option.c b/src/nvim/option.c index d789ad3587..fcc051ef1a 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -80,6 +80,7 @@ #ifdef WIN32 # include "nvim/os/pty_conpty_win.h" #endif +#include "nvim/lua/executor.h" #include "nvim/api/private/helpers.h" #include "nvim/os/input.h" #include "nvim/os/lang.h" @@ -173,6 +174,7 @@ static char_u *p_syn; static char_u *p_spc; static char_u *p_spf; static char_u *p_spl; +static char_u *p_spo; static long p_ts; static long p_tw; static int p_udf; @@ -310,11 +312,14 @@ static char *(p_fdm_values[]) = { "manual", "expr", "marker", "indent", static char *(p_fcl_values[]) = { "all", NULL }; static char *(p_cot_values[]) = { "menu", "menuone", "longest", "preview", "noinsert", "noselect", NULL }; +#ifdef BACKSLASH_IN_FILENAME +static char *(p_csl_values[]) = { "slash", "backslash", NULL }; +#endif static char *(p_icm_values[]) = { "nosplit", "split", NULL }; static char *(p_scl_values[]) = { "yes", "no", "auto", "auto:1", "auto:2", "auto:3", "auto:4", "auto:5", "auto:6", "auto:7", "auto:8", "auto:9", "yes:1", "yes:2", "yes:3", "yes:4", "yes:5", "yes:6", "yes:7", "yes:8", - "yes:9", NULL }; + "yes:9", "number", NULL }; static char *(p_fdc_values[]) = { "auto:1", "auto:2", "auto:3", "auto:4", "auto:5", "auto:6", "auto:7", "auto:8", "auto:9", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", NULL }; @@ -346,22 +351,23 @@ static char *strcpy_comma_escaped(char *dest, const char *src, const size_t len) return &dest[len + shift]; } -/// Compute length of a colon-separated value, doubled and with some suffixes +/// Compute length of a ENV_SEPCHAR-separated value, doubled and with some +/// suffixes /// -/// @param[in] val Colon-separated array value. +/// @param[in] val ENV_SEPCHAR-separated array value. /// @param[in] common_suf_len Length of the common suffix which is appended to /// each item in the array, twice. /// @param[in] single_suf_len Length of the suffix which is appended to each /// item in the array once. /// -/// @return Length of the comma-separated string array that contains each item -/// in the original array twice with suffixes with given length +/// @return Length of the ENV_SEPCHAR-separated string array that contains each +/// item in the original array twice with suffixes with given length /// (common_suf is present after each new item, single_suf is present /// after half of the new items) and with commas after each item, commas /// inside the values are escaped. -static inline size_t compute_double_colon_len(const char *const val, - const size_t common_suf_len, - const size_t single_suf_len) +static inline size_t compute_double_env_sep_len(const char *const val, + const size_t common_suf_len, + const size_t single_suf_len) FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE { if (val == NULL || *val == NUL) { @@ -372,7 +378,7 @@ static inline size_t compute_double_colon_len(const char *const val, do { size_t dir_len; const char *dir; - iter = vim_env_iter(':', val, iter, &dir, &dir_len); + iter = vim_env_iter(ENV_SEPCHAR, val, iter, &dir, &dir_len); if (dir != NULL && dir_len > 0) { ret += ((dir_len + memcnt(dir, ',', dir_len) + common_suf_len + !after_pathsep(dir, dir + dir_len)) * 2 @@ -384,13 +390,13 @@ static inline size_t compute_double_colon_len(const char *const val, #define NVIM_SIZE (sizeof("nvim") - 1) -/// Add directories to a comma-separated array from a colon-separated one +/// Add directories to a ENV_SEPCHAR-separated array from a colon-separated one /// /// Commas are escaped in process. To each item PATHSEP "nvim" is appended in /// addition to suf1 and suf2. /// /// @param[in,out] dest Destination comma-separated array. -/// @param[in] val Source colon-separated array. +/// @param[in] val Source ENV_SEPCHAR-separated array. /// @param[in] suf1 If not NULL, suffix appended to destination. Prior to it /// directory separator is appended. Suffix must not contain /// commas. @@ -403,10 +409,10 @@ static inline size_t compute_double_colon_len(const char *const val, /// Otherwise in reverse. /// /// @return (dest + appended_characters_length) -static inline char *add_colon_dirs(char *dest, const char *const val, - const char *const suf1, const size_t len1, - const char *const suf2, const size_t len2, - const bool forward) +static inline char *add_env_sep_dirs(char *dest, const char *const val, + const char *const suf1, const size_t len1, + const char *const suf2, const size_t len2, + const bool forward) FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_RET FUNC_ATTR_NONNULL_ARG(1) { if (val == NULL || *val == NUL) { @@ -416,8 +422,8 @@ static inline char *add_colon_dirs(char *dest, const char *const val, do { size_t dir_len; const char *dir; - iter = (forward ? vim_env_iter : vim_env_iter_rev)(':', val, iter, &dir, - &dir_len); + iter = (forward ? vim_env_iter : vim_env_iter_rev)(ENV_SEPCHAR, val, iter, + &dir, &dir_len); if (dir != NULL && dir_len > 0) { dest = strcpy_comma_escaped(dest, dir, dir_len); if (!after_pathsep(dest - 1, dest)) { @@ -580,10 +586,11 @@ static void set_runtimepath_default(bool clean_arg) rtp_size += libdir_len + memcnt(libdir, ',', libdir_len) + 1; } } - rtp_size += compute_double_colon_len(data_dirs, NVIM_SIZE + 1 + SITE_SIZE + 1, - AFTER_SIZE + 1); - rtp_size += compute_double_colon_len(config_dirs, NVIM_SIZE + 1, - AFTER_SIZE + 1); + rtp_size += compute_double_env_sep_len(data_dirs, + NVIM_SIZE + 1 + SITE_SIZE + 1, + AFTER_SIZE + 1); + rtp_size += compute_double_env_sep_len(config_dirs, NVIM_SIZE + 1, + AFTER_SIZE + 1); if (rtp_size == 0) { return; } @@ -591,20 +598,20 @@ static void set_runtimepath_default(bool clean_arg) char *rtp_cur = rtp; rtp_cur = add_dir(rtp_cur, config_home, config_len, kXDGConfigHome, NULL, 0, NULL, 0); - rtp_cur = add_colon_dirs(rtp_cur, config_dirs, NULL, 0, NULL, 0, true); + rtp_cur = add_env_sep_dirs(rtp_cur, config_dirs, NULL, 0, NULL, 0, true); rtp_cur = add_dir(rtp_cur, data_home, data_len, kXDGDataHome, "site", SITE_SIZE, NULL, 0); - rtp_cur = add_colon_dirs(rtp_cur, data_dirs, "site", SITE_SIZE, NULL, 0, - true); + rtp_cur = add_env_sep_dirs(rtp_cur, data_dirs, "site", SITE_SIZE, NULL, 0, + true); rtp_cur = add_dir(rtp_cur, vimruntime, vimruntime_len, kXDGNone, NULL, 0, NULL, 0); rtp_cur = add_dir(rtp_cur, libdir, libdir_len, kXDGNone, NULL, 0, NULL, 0); - rtp_cur = add_colon_dirs(rtp_cur, data_dirs, "site", SITE_SIZE, - "after", AFTER_SIZE, false); + rtp_cur = add_env_sep_dirs(rtp_cur, data_dirs, "site", SITE_SIZE, + "after", AFTER_SIZE, false); rtp_cur = add_dir(rtp_cur, data_home, data_len, kXDGDataHome, "site", SITE_SIZE, "after", AFTER_SIZE); - rtp_cur = add_colon_dirs(rtp_cur, config_dirs, "after", AFTER_SIZE, NULL, 0, - false); + rtp_cur = add_env_sep_dirs(rtp_cur, config_dirs, "after", AFTER_SIZE, NULL, 0, + false); rtp_cur = add_dir(rtp_cur, config_home, config_len, kXDGConfigHome, "after", AFTER_SIZE, NULL, 0); // Strip trailing comma. @@ -1354,11 +1361,11 @@ int do_set( // Disallow changing some options from modelines. if (opt_flags & OPT_MODELINE) { if (flags & (P_SECURE | P_NO_ML)) { - errmsg = (char_u *)_("E520: Not allowed in a modeline"); + errmsg = (char_u *)N_("E520: Not allowed in a modeline"); goto skip; } if ((flags & P_MLE) && !p_mle) { - errmsg = (char_u *)_( + errmsg = (char_u *)N_( "E992: Not allowed in a modeline when 'modelineexpr' is off"); goto skip; } @@ -1375,7 +1382,7 @@ int do_set( // Disallow changing some options in the sandbox if (sandbox != 0 && (flags & P_SECURE)) { - errmsg = (char_u *)_(e_sandbox); + errmsg = e_sandbox; goto skip; } @@ -1711,6 +1718,7 @@ int do_set( #ifdef BACKSLASH_IN_FILENAME && !((flags & P_EXPAND) && vim_isfilec(arg[1]) + && !ascii_iswhite(arg[1]) && (arg[1] != '\\' || (s == newval && arg[2] != '\\'))) @@ -2281,6 +2289,7 @@ void check_buf_options(buf_T *buf) check_string_option(&buf->b_s.b_p_spc); check_string_option(&buf->b_s.b_p_spf); check_string_option(&buf->b_s.b_p_spl); + check_string_option(&buf->b_s.b_p_spo); check_string_option(&buf->b_p_sua); check_string_option(&buf->b_p_cink); check_string_option(&buf->b_p_cino); @@ -2557,7 +2566,7 @@ 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 != ',') { + if (!vim_isfilec(*s) && *s != ',' && *s != ' ') { return false; } } @@ -3086,6 +3095,10 @@ ambw_end: } else if (varp == &(curwin->w_s->b_p_spc)) { // When 'spellcapcheck' is set compile the regexp program. errmsg = compile_cap_prog(curwin->w_s); + } else if (varp == &(curwin->w_s->b_p_spo)) { // 'spelloptions' + if (**varp != NUL && STRCMP("camel", *varp) != 0) { + errmsg = e_invarg; + } } else if (varp == &p_sps) { // 'spellsuggest' if (spell_check_sps() != OK) { errmsg = e_invarg; @@ -3108,7 +3121,7 @@ ambw_end: } else { if (curwin->w_status_height) { curwin->w_redr_status = true; - redraw_later(VALID); + redraw_later(curwin, VALID); } curbuf->b_help = (curbuf->b_p_bt[0] == 'h'); redraw_titles(); @@ -3178,11 +3191,25 @@ ambw_end: } else { completeopt_was_set(); } +#ifdef BACKSLASH_IN_FILENAME + } else if (gvarp == &p_csl) { // 'completeslash' + if (check_opt_strings(p_csl, p_csl_values, false) != OK + || check_opt_strings(curbuf->b_p_csl, p_csl_values, false) != OK) { + errmsg = e_invarg; + } +#endif } else if (varp == &curwin->w_p_scl) { // 'signcolumn' if (check_opt_strings(*varp, p_scl_values, false) != OK) { errmsg = e_invarg; } + // When changing the 'signcolumn' to or from 'number', recompute the + // width of the number column if 'number' or 'relativenumber' is set. + if (((*oldval == 'n' && *(oldval + 1) == 'u') + || (*curwin->w_p_scl == 'n' && *(curwin->w_p_scl + 1) =='u')) + && (curwin->w_p_nu || curwin->w_p_rnu)) { + curwin->w_nrwidth_line_count = 0; + } } else if (varp == &curwin->w_p_fdc || varp == &curwin->w_allbuf_opt.wo_fdc) { // 'foldcolumn' if (check_opt_strings(*varp, p_fdc_values, false) != OK) { @@ -3417,6 +3444,7 @@ ambw_end: // recursively, to avoid endless recurrence. apply_autocmds(EVENT_SYNTAX, curbuf->b_p_syn, curbuf->b_fname, value_changed || syn_recursive == 1, curbuf); + curbuf->b_flags |= BF_SYN_SET; syn_recursive--; } else if (varp == &(curbuf->b_p_ft)) { // 'filetype' is set, trigger the FileType autocommand @@ -3719,11 +3747,10 @@ static char_u *set_chars_option(win_T *wp, char_u **varp, bool set) /// Return error message or NULL. char_u *check_stl_option(char_u *s) { - int itemcnt = 0; int groupdepth = 0; static char_u errbuf[80]; - while (*s && itemcnt < STL_MAX_ITEM) { + while (*s) { // Check for valid keys after % sequences while (*s && *s != '%') { s++; @@ -3732,9 +3759,6 @@ char_u *check_stl_option(char_u *s) break; } s++; - if (*s != '%' && *s != ')') { - itemcnt++; - } if (*s == '%' || *s == STL_TRUNCMARK || *s == STL_SEPARATE) { s++; continue; @@ -3776,9 +3800,6 @@ char_u *check_stl_option(char_u *s) } } } - if (itemcnt >= STL_MAX_ITEM) { - return (char_u *)N_("E541: too many items"); - } if (groupdepth != 0) { return (char_u *)N_("E542: unbalanced groups"); } @@ -4670,7 +4691,7 @@ static void check_redraw(uint32_t flags) redraw_curbuf_later(NOT_VALID); } if (flags & P_RWINONLY) { - redraw_later(NOT_VALID); + redraw_later(curwin, NOT_VALID); } if (doclear) { redraw_all_later(CLEAR); @@ -5684,12 +5705,12 @@ void unset_global_local_option(char *name, void *from) case PV_LCS: clear_string_option(&((win_T *)from)->w_p_lcs); set_chars_option((win_T *)from, &((win_T *)from)->w_p_lcs, true); - redraw_win_later((win_T *)from, NOT_VALID); + redraw_later((win_T *)from, NOT_VALID); break; case PV_FCS: clear_string_option(&((win_T *)from)->w_p_fcs); set_chars_option((win_T *)from, &((win_T *)from)->w_p_fcs, true); - redraw_win_later((win_T *)from, NOT_VALID); + redraw_later((win_T *)from, NOT_VALID); break; } } @@ -5844,6 +5865,9 @@ static char_u *get_varp(vimoption_T *p) case PV_COM: return (char_u *)&(curbuf->b_p_com); case PV_CMS: return (char_u *)&(curbuf->b_p_cms); case PV_CPT: return (char_u *)&(curbuf->b_p_cpt); +# ifdef BACKSLASH_IN_FILENAME + case PV_CSL: return (char_u *)&(curbuf->b_p_csl); +# endif case PV_CFU: return (char_u *)&(curbuf->b_p_cfu); case PV_OFU: return (char_u *)&(curbuf->b_p_ofu); case PV_EOL: return (char_u *)&(curbuf->b_p_eol); @@ -5881,6 +5905,7 @@ static char_u *get_varp(vimoption_T *p) case PV_SPC: return (char_u *)&(curwin->w_s->b_p_spc); case PV_SPF: return (char_u *)&(curwin->w_s->b_p_spf); case PV_SPL: return (char_u *)&(curwin->w_s->b_p_spl); + case PV_SPO: return (char_u *)&(curwin->w_s->b_p_spo); case PV_SW: return (char_u *)&(curbuf->b_p_sw); case PV_TFU: return (char_u *)&(curbuf->b_p_tfu); case PV_TS: return (char_u *)&(curbuf->b_p_ts); @@ -6130,6 +6155,9 @@ void buf_copy_options(buf_T *buf, int flags) buf->b_p_inf = p_inf; buf->b_p_swf = cmdmod.noswapfile ? false : p_swf; buf->b_p_cpt = vim_strsave(p_cpt); +# ifdef BACKSLASH_IN_FILENAME + buf->b_p_csl = vim_strsave(p_csl); +# endif buf->b_p_cfu = vim_strsave(p_cfu); buf->b_p_ofu = vim_strsave(p_ofu); buf->b_p_tfu = vim_strsave(p_tfu); @@ -6160,6 +6188,7 @@ void buf_copy_options(buf_T *buf, int flags) (void)compile_cap_prog(&buf->b_s); buf->b_s.b_p_spf = vim_strsave(p_spf); buf->b_s.b_p_spl = vim_strsave(p_spl); + buf->b_s.b_p_spo = vim_strsave(p_spo); buf->b_p_inde = vim_strsave(p_inde); buf->b_p_indk = vim_strsave(p_indk); buf->b_p_fp = empty_option; @@ -6779,7 +6808,8 @@ static void langmap_set(void) /// Return true if format option 'x' is in effect. /// Take care of no formatting when 'paste' is set. -int has_format_option(int x) +bool has_format_option(int x) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { if (p_paste) { return false; @@ -7246,7 +7276,8 @@ unsigned int get_bkc_value(buf_T *buf) } /// Return the current end-of-line type: EOL_DOS, EOL_UNIX or EOL_MAC. -int get_fileformat(buf_T *buf) +int get_fileformat(const buf_T *buf) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { int c = *buf->b_p_ff; @@ -7397,7 +7428,10 @@ int win_signcol_count(win_T *wp) int maximum = 1, needed_signcols; const char *scl = (const char *)wp->w_p_scl; - if (*scl == 'n') { + // Note: It checks "no" or "number" in 'signcolumn' option + if (*scl == 'n' + && (*(scl + 1) == 'o' || (*(scl + 1) == 'u' + && (wp->w_p_nu || wp->w_p_rnu)))) { return 0; } needed_signcols = buf_signcols(wp->w_buffer); diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index ecaa941082..af0ea7f4a2 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -77,12 +77,13 @@ #define FO_ONE_LETTER '1' #define FO_WHITE_PAR 'w' // trailing white space continues paragr. #define FO_AUTO 'a' // automatic formatting +#define FO_RIGOROUS_TW ']' // respect textwidth rigorously #define FO_REMOVE_COMS 'j' // remove comment leaders when joining lines #define FO_PERIOD_ABBR 'p' // don't break a single space after a period #define DFLT_FO_VI "vt" #define DFLT_FO_VIM "tcqj" -#define FO_ALL "tcroq2vlb1mMBn,awjp" // for do_set() +#define FO_ALL "tcroq2vlb1mMBn,aw]jp" // for do_set() // characters for the p_cpo option: #define CPO_ALTREAD 'a' // ":read" sets alternate file name @@ -187,6 +188,7 @@ enum { #define GO_ASELML 'A' // autoselect modeless selection #define GO_BOT 'b' // use bottom scrollbar #define GO_CONDIALOG 'c' // use console dialog +#define GO_DARKTHEME 'd' // use dark theme variant #define GO_TABLINE 'e' // may show tabline #define GO_FORG 'f' // start GUI in foreground #define GO_GREY 'g' // use grey menu items @@ -204,7 +206,7 @@ enum { #define GO_FOOTER 'F' // add footer #define GO_VERTICAL 'v' // arrange dialog buttons vertically #define GO_KEEPWINSIZE 'k' // keep GUI window size -#define GO_ALL "aAbcefFghilmMprTvk" // all possible flags for 'go' +#define GO_ALL "aAbcdefFghilmMprTvk" // all possible flags for 'go' // flags for 'comments' option #define COM_NEST 'n' // comments strings nest @@ -372,6 +374,9 @@ EXTERN long p_columns; // 'columns' EXTERN int p_confirm; // 'confirm' EXTERN int p_cp; // 'compatible' EXTERN char_u *p_cot; // 'completeopt' +# ifdef BACKSLASH_IN_FILENAME +EXTERN char_u *p_csl; // 'completeslash' +# endif EXTERN long p_pb; // 'pumblend' EXTERN long p_ph; // 'pumheight' EXTERN long p_pw; // 'pumwidth' @@ -453,7 +458,6 @@ EXTERN char_u *p_header; // 'printheader' EXTERN int p_prompt; // 'prompt' EXTERN char_u *p_guicursor; // 'guicursor' EXTERN char_u *p_guifont; // 'guifont' -EXTERN char_u *p_guifontset; // 'guifontset' EXTERN char_u *p_guifontwide; // 'guifontwide' EXTERN char_u *p_hf; // 'helpfile' EXTERN long p_hh; // 'helpheight' @@ -513,6 +517,7 @@ EXTERN long p_mle; // 'modelineexpr' EXTERN long p_mls; // 'modelines' EXTERN char_u *p_mouse; // 'mouse' EXTERN char_u *p_mousem; // 'mousemodel' +EXTERN long p_mousef; // 'mousefocus' EXTERN long p_mouset; // 'mousetime' EXTERN int p_more; // 'more' EXTERN char_u *p_opfunc; // 'operatorfunc' @@ -743,6 +748,7 @@ enum { , BV_CPT , BV_DICT , BV_TSR + , BV_CSL , BV_CFU , BV_DEF , BV_INC @@ -787,6 +793,7 @@ enum { , BV_SPC , BV_SPF , BV_SPL + , BV_SPO , BV_STS , BV_SUA , BV_SW diff --git a/src/nvim/options.lua b/src/nvim/options.lua index e7c1a3fe88..64a09dc2b4 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -1,6 +1,7 @@ -- { -- { -- full_name='aleph', abbreviation='al', +-- short_desc="ASCII code of the letter Aleph (Hebrew)", -- varname='p_aleph', pv_name=nil, -- type='number', list=nil, scope={'global'}, -- deny_duplicates=nil, @@ -52,6 +53,7 @@ return { options={ { full_name='aleph', abbreviation='al', + short_desc=N_("ASCII code of the letter Aleph (Hebrew)"), type='number', scope={'global'}, vi_def=true, redraw={'curswant'}, @@ -60,6 +62,7 @@ return { }, { full_name='arabic', abbreviation='arab', + short_desc=N_("Arabic as a default second language"), type='bool', scope={'window'}, vi_def=true, vim=true, @@ -68,6 +71,7 @@ return { }, { full_name='arabicshape', abbreviation='arshape', + short_desc=N_("do shaping for Arabic characters"), type='bool', scope={'global'}, vi_def=true, vim=true, @@ -78,6 +82,7 @@ return { }, { full_name='allowrevins', abbreviation='ari', + short_desc=N_("allow CTRL-_ in Insert and Command-line mode"), type='bool', scope={'global'}, vi_def=true, vim=true, @@ -86,6 +91,7 @@ return { }, { full_name='ambiwidth', abbreviation='ambw', + short_desc=N_("what to do with Unicode chars of ambiguous width"), type='string', scope={'global'}, vi_def=true, redraw={'all_windows', 'ui_option'}, @@ -94,6 +100,7 @@ return { }, { full_name='autochdir', abbreviation='acd', + short_desc=N_("change directory to the file in the current window"), type='bool', scope={'global'}, vi_def=true, varname='p_acd', @@ -101,18 +108,21 @@ return { }, { full_name='autoindent', abbreviation='ai', + short_desc=N_("take indent for new line from previous line"), type='bool', scope={'buffer'}, varname='p_ai', defaults={if_true={vi=false, vim=true}} }, { full_name='autoread', abbreviation='ar', + short_desc=N_("autom. read file when changed outside of Vim"), type='bool', scope={'global', 'buffer'}, varname='p_ar', defaults={if_true={vi=false, vim=true}} }, { full_name='autowrite', abbreviation='aw', + short_desc=N_("automatically write file if changed"), type='bool', scope={'global'}, vi_def=true, varname='p_aw', @@ -120,6 +130,7 @@ return { }, { full_name='autowriteall', abbreviation='awa', + short_desc=N_("as 'autowrite', but works with more commands"), type='bool', scope={'global'}, vi_def=true, varname='p_awa', @@ -127,6 +138,7 @@ return { }, { full_name='background', abbreviation='bg', + short_desc=N_("\"dark\" or \"light\", used for highlight colors"), type='string', scope={'global'}, vim=true, redraw={'all_windows'}, @@ -135,6 +147,7 @@ return { }, { full_name='backspace', abbreviation='bs', + short_desc=N_("how backspace works at start of line"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vim=true, @@ -143,6 +156,7 @@ return { }, { full_name='backup', abbreviation='bk', + short_desc=N_("keep backup file after overwriting a file"), type='bool', scope={'global'}, vi_def=true, vim=true, @@ -151,6 +165,7 @@ return { }, { full_name='backupcopy', abbreviation='bkc', + short_desc=N_("make backup as a copy, don't rename the file"), type='string', list='onecomma', scope={'global', 'buffer'}, deny_duplicates=true, vim=true, @@ -163,6 +178,7 @@ return { }, { full_name='backupdir', abbreviation='bdir', + short_desc=N_("list of directories for the backup file"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, secure=true, @@ -173,6 +189,7 @@ return { }, { full_name='backupext', abbreviation='bex', + short_desc=N_("extension used for the backup file"), type='string', scope={'global'}, normal_fname_chars=true, vi_def=true, @@ -181,6 +198,7 @@ return { }, { full_name='backupskip', abbreviation='bsk', + short_desc=N_("no backup for files that match these patterns"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vi_def=true, @@ -189,6 +207,7 @@ return { }, { full_name='belloff', abbreviation='bo', + short_desc=N_("do not ring the bell for these reasons"), type='string', list='comma', scope={'global'}, deny_duplicates=true, vi_def=true, @@ -197,6 +216,7 @@ return { }, { full_name='binary', abbreviation='bin', + short_desc=N_("read/write/edit file in binary mode"), type='bool', scope={'buffer'}, vi_def=true, redraw={'statuslines'}, @@ -205,6 +225,7 @@ return { }, { full_name='bomb', + short_desc=N_("a Byte Order Mark to the file"), type='bool', scope={'buffer'}, no_mkrc=true, vi_def=true, @@ -214,6 +235,7 @@ return { }, { full_name='breakat', abbreviation='brk', + short_desc=N_("characters that may cause a line break"), type='string', list='flags', scope={'global'}, vi_def=true, redraw={'all_windows'}, @@ -222,6 +244,7 @@ return { }, { full_name='breakindent', abbreviation='bri', + short_desc=N_("wrapped line repeats indent"), type='bool', scope={'window'}, vi_def=true, vim=true, @@ -230,6 +253,7 @@ return { }, { full_name='breakindentopt', abbreviation='briopt', + short_desc=N_("settings for 'breakindent'"), type='string', list='onecomma', scope={'window'}, deny_duplicates=true, vi_def=true, @@ -239,12 +263,14 @@ return { }, { full_name='browsedir', abbreviation='bsdir', + short_desc=N_("which directory to start browsing in"), type='string', scope={'global'}, vi_def=true, enable_if=false, }, { full_name='bufhidden', abbreviation='bh', + short_desc=N_("what to do when buffer is no longer in window"), type='string', scope={'buffer'}, noglob=true, vi_def=true, @@ -254,6 +280,7 @@ return { }, { full_name='buflisted', abbreviation='bl', + short_desc=N_("whether the buffer shows up in the buffer list"), type='bool', scope={'buffer'}, noglob=true, vi_def=true, @@ -262,6 +289,7 @@ return { }, { full_name='buftype', abbreviation='bt', + short_desc=N_("special type of buffer"), type='string', scope={'buffer'}, noglob=true, vi_def=true, @@ -271,6 +299,7 @@ return { }, { full_name='casemap', abbreviation='cmp', + short_desc=N_("specifies how case of letters is changed"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vi_def=true, @@ -279,6 +308,7 @@ return { }, { full_name='cdpath', abbreviation='cd', + short_desc=N_("list of directories searched with \":cd\""), type='string', list='comma', scope={'global'}, deny_duplicates=true, vi_def=true, @@ -289,12 +319,14 @@ return { }, { full_name='cedit', + short_desc=N_("used to open the command-line window"), type='string', scope={'global'}, varname='p_cedit', defaults={if_true={vi="", vim=macros('CTRL_F_STR')}} }, { full_name='channel', + short_desc=N_("Channel connected to the buffer"), type='number', scope={'buffer'}, no_mkrc=true, nodefault=true, @@ -303,6 +335,7 @@ return { }, { full_name='charconvert', abbreviation='ccv', + short_desc=N_("expression for character encoding conversion"), type='string', scope={'global'}, secure=true, vi_def=true, @@ -311,6 +344,7 @@ return { }, { full_name='cindent', abbreviation='cin', + short_desc=N_("do C program indenting"), type='bool', scope={'buffer'}, vi_def=true, vim=true, @@ -319,6 +353,7 @@ return { }, { full_name='cinkeys', abbreviation='cink', + short_desc=N_("keys that trigger indent when 'cindent' is set"), type='string', list='onecomma', scope={'buffer'}, deny_duplicates=true, vi_def=true, @@ -328,6 +363,7 @@ return { }, { full_name='cinoptions', abbreviation='cino', + short_desc=N_("how to do indenting when 'cindent' is set"), type='string', list='onecomma', scope={'buffer'}, deny_duplicates=true, vi_def=true, @@ -337,6 +373,7 @@ return { }, { full_name='cinwords', abbreviation='cinw', + short_desc=N_("words where 'si' and 'cin' add an indent"), type='string', list='onecomma', scope={'buffer'}, deny_duplicates=true, vi_def=true, @@ -346,6 +383,7 @@ return { }, { full_name='clipboard', abbreviation='cb', + short_desc=N_("use the clipboard as the unnamed register"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vi_def=true, @@ -354,6 +392,7 @@ return { }, { full_name='cmdheight', abbreviation='ch', + short_desc=N_("number of lines to use for the command-line"), type='number', scope={'global'}, vi_def=true, redraw={'all_windows'}, @@ -362,6 +401,7 @@ return { }, { full_name='cmdwinheight', abbreviation='cwh', + short_desc=N_("height of the command-line window"), type='number', scope={'global'}, vi_def=true, varname='p_cwh', @@ -369,6 +409,7 @@ return { }, { full_name='colorcolumn', abbreviation='cc', + short_desc=N_("columns to highlight"), type='string', list='onecomma', scope={'window'}, deny_duplicates=true, vi_def=true, @@ -377,6 +418,7 @@ return { }, { full_name='columns', abbreviation='co', + short_desc=N_("number of columns in the display"), type='number', scope={'global'}, no_mkrc=true, vi_def=true, @@ -386,6 +428,7 @@ return { }, { full_name='comments', abbreviation='com', + short_desc=N_("patterns that can start a comment line"), type='string', list='onecomma', scope={'buffer'}, deny_duplicates=true, vi_def=true, @@ -396,6 +439,7 @@ return { }, { full_name='commentstring', abbreviation='cms', + short_desc=N_("template for comments; used for fold marker"), type='string', scope={'buffer'}, vi_def=true, alloced=true, @@ -405,6 +449,7 @@ return { }, { full_name='compatible', abbreviation='cp', + short_desc=N_("No description"), type='bool', scope={'global'}, redraw={'all_windows'}, varname='p_force_off', @@ -414,6 +459,7 @@ return { }, { full_name='complete', abbreviation='cpt', + short_desc=N_("specify how Insert mode completion works"), type='string', list='onecomma', scope={'buffer'}, deny_duplicates=true, alloced=true, @@ -422,6 +468,7 @@ return { }, { full_name='concealcursor', abbreviation='cocu', + short_desc=N_("whether concealable text is hidden in cursor line"), type='string', scope={'window'}, vi_def=true, alloced=true, @@ -430,6 +477,7 @@ return { }, { full_name='conceallevel', abbreviation='cole', + short_desc=N_("whether concealable text is shown or hidden"), type='number', scope={'window'}, vi_def=true, redraw={'current_window'}, @@ -437,6 +485,7 @@ return { }, { full_name='completefunc', abbreviation='cfu', + short_desc=N_("function to be used for Insert mode completion"), type='string', scope={'buffer'}, secure=true, vi_def=true, @@ -446,6 +495,7 @@ return { }, { full_name='completeopt', abbreviation='cot', + short_desc=N_("options for Insert mode completion"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vi_def=true, @@ -453,7 +503,17 @@ return { defaults={if_true={vi="menu,preview"}} }, { + full_name='completeslash', abbreviation='csl', + type='string', scope={'buffer'}, + vi_def=true, + vim=true, + varname='p_csl', + enable_if='BACKSLASH_IN_FILENAME', + defaults={if_true={vi=""}} + }, + { full_name='confirm', abbreviation='cf', + short_desc=N_("ask what to do about unsaved/read-only files"), type='bool', scope={'global'}, vi_def=true, varname='p_confirm', @@ -461,6 +521,7 @@ return { }, { full_name='copyindent', abbreviation='ci', + short_desc=N_("make 'autoindent' use existing indent structure"), type='bool', scope={'buffer'}, vi_def=true, vim=true, @@ -469,6 +530,7 @@ return { }, { full_name='cpoptions', abbreviation='cpo', + short_desc=N_("flags for Vi-compatible behavior"), type='string', list='flags', scope={'global'}, vim=true, redraw={'all_windows'}, @@ -477,6 +539,7 @@ return { }, { full_name='cscopepathcomp', abbreviation='cspc', + short_desc=N_("how many components of the path to show"), type='number', scope={'global'}, vi_def=true, vim=true, @@ -485,6 +548,7 @@ return { }, { full_name='cscopeprg', abbreviation='csprg', + short_desc=N_("command to execute cscope"), type='string', scope={'global'}, secure=true, vi_def=true, @@ -494,6 +558,7 @@ return { }, { full_name='cscopequickfix', abbreviation='csqf', + short_desc=N_("use quickfix window for cscope results"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vi_def=true, @@ -502,6 +567,7 @@ return { }, { full_name='cscoperelative', abbreviation='csre', + short_desc=N_("Use cscope.out path basename as prefix"), type='bool', scope={'global'}, vi_def=true, vim=true, @@ -510,6 +576,7 @@ return { }, { full_name='cscopetag', abbreviation='cst', + short_desc=N_("use cscope for tag commands"), type='bool', scope={'global'}, vi_def=true, vim=true, @@ -518,6 +585,7 @@ return { }, { full_name='cscopetagorder', abbreviation='csto', + short_desc=N_("determines \":cstag\" search order"), type='number', scope={'global'}, vi_def=true, vim=true, @@ -526,6 +594,7 @@ return { }, { full_name='cscopeverbose', abbreviation='csverb', + short_desc=N_("give messages when adding a cscope database"), type='bool', scope={'global'}, vi_def=true, vim=true, @@ -534,6 +603,7 @@ return { }, { full_name='cursorbind', abbreviation='crb', + short_desc=N_("move cursor in window as it moves in other windows"), type='bool', scope={'window'}, vi_def=true, pv_name='p_crbind', @@ -541,6 +611,7 @@ return { }, { full_name='cursorcolumn', abbreviation='cuc', + short_desc=N_("highlight the screen column of the cursor"), type='bool', scope={'window'}, vi_def=true, redraw={'current_window_only'}, @@ -548,6 +619,7 @@ return { }, { full_name='cursorline', abbreviation='cul', + short_desc=N_("highlight the screen line of the cursor"), type='bool', scope={'window'}, vi_def=true, redraw={'current_window_only'}, @@ -555,6 +627,7 @@ return { }, { full_name='debug', + short_desc=N_("to \"msg\" to see all error messages"), type='string', scope={'global'}, vi_def=true, varname='p_debug', @@ -562,6 +635,7 @@ return { }, { full_name='define', abbreviation='def', + short_desc=N_("pattern to be used to find a macro definition"), type='string', scope={'global', 'buffer'}, vi_def=true, alloced=true, @@ -571,6 +645,7 @@ return { }, { full_name='delcombine', abbreviation='deco', + short_desc=N_("delete combining characters on their own"), type='bool', scope={'global'}, vi_def=true, vim=true, @@ -579,6 +654,7 @@ return { }, { full_name='dictionary', abbreviation='dict', + short_desc=N_("list of file names used for keyword completion"), type='string', list='onecomma', scope={'global', 'buffer'}, deny_duplicates=true, normal_dname_chars=true, @@ -589,6 +665,7 @@ return { }, { full_name='diff', + short_desc=N_("diff mode for the current window"), type='bool', scope={'window'}, noglob=true, vi_def=true, @@ -597,6 +674,7 @@ return { }, { full_name='diffexpr', abbreviation='dex', + short_desc=N_("expression used to obtain a diff file"), type='string', scope={'global'}, secure=true, vi_def=true, @@ -606,6 +684,7 @@ return { }, { full_name='diffopt', abbreviation='dip', + short_desc=N_("options for using diff mode"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vi_def=true, @@ -616,6 +695,7 @@ return { }, { full_name='digraph', abbreviation='dg', + short_desc=N_("enable the entering of digraphs in Insert mode"), type='bool', scope={'global'}, vi_def=true, vim=true, @@ -624,6 +704,7 @@ return { }, { full_name='directory', abbreviation='dir', + short_desc=N_("list of directory names for the swap file"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, secure=true, @@ -634,6 +715,7 @@ return { }, { full_name='display', abbreviation='dy', + short_desc=N_("list of flags for how to display text"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vim=true, @@ -643,6 +725,7 @@ return { }, { full_name='eadirection', abbreviation='ead', + short_desc=N_("in which direction 'equalalways' works"), type='string', scope={'global'}, vi_def=true, varname='p_ead', @@ -650,6 +733,7 @@ return { }, { full_name='edcompatible', abbreviation='ed', + short_desc=N_("No description"), type='bool', scope={'global'}, vi_def=true, varname='p_force_off', @@ -657,6 +741,7 @@ return { }, { full_name='emoji', abbreviation='emo', + short_desc=N_("No description"), type='bool', scope={'global'}, vi_def=true, redraw={'all_windows', 'ui_option'}, @@ -665,6 +750,7 @@ return { }, { full_name='encoding', abbreviation='enc', + short_desc=N_("encoding used internally"), type='string', scope={'global'}, deny_in_modelines=true, vi_def=true, @@ -673,6 +759,7 @@ return { }, { full_name='endofline', abbreviation='eol', + short_desc=N_("write <EOL> for last line in file"), type='bool', scope={'buffer'}, no_mkrc=true, vi_def=true, @@ -682,6 +769,7 @@ return { }, { full_name='equalalways', abbreviation='ea', + short_desc=N_("windows are automatically made the same size"), type='bool', scope={'global'}, vi_def=true, redraw={'all_windows'}, @@ -690,6 +778,7 @@ return { }, { full_name='equalprg', abbreviation='ep', + short_desc=N_("external program to use for \"=\" command"), type='string', scope={'global', 'buffer'}, secure=true, vi_def=true, @@ -699,6 +788,7 @@ return { }, { full_name='errorbells', abbreviation='eb', + short_desc=N_("ring the bell for error messages"), type='bool', scope={'global'}, vi_def=true, varname='p_eb', @@ -706,6 +796,7 @@ return { }, { full_name='errorfile', abbreviation='ef', + short_desc=N_("name of the errorfile for the QuickFix mode"), type='string', scope={'global'}, secure=true, vi_def=true, @@ -715,6 +806,7 @@ return { }, { full_name='errorformat', abbreviation='efm', + short_desc=N_("description of the lines in the error file"), type='string', list='onecomma', scope={'global', 'buffer'}, deny_duplicates=true, vi_def=true, @@ -723,6 +815,7 @@ return { }, { full_name='eventignore', abbreviation='ei', + short_desc=N_("autocommand events that are ignored"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vi_def=true, @@ -731,6 +824,7 @@ return { }, { full_name='expandtab', abbreviation='et', + short_desc=N_("use spaces when <Tab> is inserted"), type='bool', scope={'buffer'}, vi_def=true, vim=true, @@ -739,6 +833,7 @@ return { }, { full_name='exrc', abbreviation='ex', + short_desc=N_("read .nvimrc and .exrc in the current directory"), type='bool', scope={'global'}, secure=true, vi_def=true, @@ -747,6 +842,7 @@ return { }, { full_name='fileencoding', abbreviation='fenc', + short_desc=N_("file encoding for multi-byte text"), type='string', scope={'buffer'}, no_mkrc=true, vi_def=true, @@ -757,6 +853,7 @@ return { }, { full_name='fileencodings', abbreviation='fencs', + short_desc=N_("automatically detected character encodings"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vi_def=true, @@ -765,6 +862,7 @@ return { }, { full_name='fileformat', abbreviation='ff', + short_desc=N_("file format used for file I/O"), type='string', scope={'buffer'}, no_mkrc=true, vi_def=true, @@ -775,6 +873,7 @@ return { }, { full_name='fileformats', abbreviation='ffs', + short_desc=N_("automatically detected values for 'fileformat'"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vim=true, @@ -783,6 +882,7 @@ return { }, { full_name='fileignorecase', abbreviation='fic', + short_desc=N_("ignore case when using file names"), type='bool', scope={'global'}, vi_def=true, varname='p_fic', @@ -794,6 +894,7 @@ return { }, { full_name='filetype', abbreviation='ft', + short_desc=N_("type of file, used for autocommands"), type='string', scope={'buffer'}, noglob=true, normal_fname_chars=true, @@ -804,6 +905,7 @@ return { }, { full_name='fillchars', abbreviation='fcs', + short_desc=N_("characters to use for displaying special items"), type='string', list='onecomma', scope={'global', 'window'}, deny_duplicates=true, vi_def=true, @@ -814,6 +916,7 @@ return { }, { full_name='fixendofline', abbreviation='fixeol', + short_desc=N_("make sure last line in file has <EOL>"), type='bool', scope={'buffer'}, vi_def=true, redraw={'statuslines'}, @@ -822,6 +925,7 @@ return { }, { full_name='foldclose', abbreviation='fcl', + short_desc=N_("close a fold when the cursor leaves it"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vi_def=true, @@ -831,6 +935,7 @@ return { }, { full_name='foldcolumn', abbreviation='fdc', + short_desc=N_("width of the column used to indicate folds"), type='string', scope={'window'}, vi_def=true, alloced=true, @@ -839,6 +944,7 @@ return { }, { full_name='foldenable', abbreviation='fen', + short_desc=N_("set to display all folds open"), type='bool', scope={'window'}, vi_def=true, redraw={'current_window'}, @@ -846,6 +952,7 @@ return { }, { full_name='foldexpr', abbreviation='fde', + short_desc=N_("expression used when 'foldmethod' is \"expr\""), type='string', scope={'window'}, vi_def=true, vim=true, @@ -856,6 +963,7 @@ return { }, { full_name='foldignore', abbreviation='fdi', + short_desc=N_("ignore lines when 'foldmethod' is \"indent\""), type='string', scope={'window'}, vi_def=true, vim=true, @@ -865,6 +973,7 @@ return { }, { full_name='foldlevel', abbreviation='fdl', + short_desc=N_("close folds with a level higher than this"), type='number', scope={'window'}, vi_def=true, redraw={'current_window'}, @@ -872,6 +981,7 @@ return { }, { full_name='foldlevelstart', abbreviation='fdls', + short_desc=N_("'foldlevel' when starting to edit a file"), type='number', scope={'global'}, vi_def=true, redraw={'curswant'}, @@ -880,6 +990,7 @@ return { }, { full_name='foldmarker', abbreviation='fmr', + short_desc=N_("markers used when 'foldmethod' is \"marker\""), type='string', list='onecomma', scope={'window'}, deny_duplicates=true, vi_def=true, @@ -890,6 +1001,7 @@ return { }, { full_name='foldmethod', abbreviation='fdm', + short_desc=N_("folding type"), type='string', scope={'window'}, vi_def=true, vim=true, @@ -899,6 +1011,7 @@ return { }, { full_name='foldminlines', abbreviation='fml', + short_desc=N_("minimum number of lines for a fold to be closed"), type='number', scope={'window'}, vi_def=true, redraw={'current_window'}, @@ -906,6 +1019,7 @@ return { }, { full_name='foldnestmax', abbreviation='fdn', + short_desc=N_("maximum fold depth"), type='number', scope={'window'}, vi_def=true, redraw={'current_window'}, @@ -913,6 +1027,7 @@ return { }, { full_name='foldopen', abbreviation='fdo', + short_desc=N_("for which commands a fold will be opened"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vi_def=true, @@ -922,6 +1037,7 @@ return { }, { full_name='foldtext', abbreviation='fdt', + short_desc=N_("expression used to display for a closed fold"), type='string', scope={'window'}, vi_def=true, vim=true, @@ -932,6 +1048,7 @@ return { }, { full_name='formatexpr', abbreviation='fex', + short_desc=N_("expression used with \"gq\" command"), type='string', scope={'buffer'}, vi_def=true, vim=true, @@ -942,6 +1059,7 @@ return { }, { full_name='formatoptions', abbreviation='fo', + short_desc=N_("how automatic formatting is to be done"), type='string', list='flags', scope={'buffer'}, vim=true, alloced=true, @@ -950,6 +1068,7 @@ return { }, { full_name='formatlistpat', abbreviation='flp', + short_desc=N_("pattern used to recognize a list header"), type='string', scope={'buffer'}, vi_def=true, alloced=true, @@ -958,6 +1077,7 @@ return { }, { full_name='formatprg', abbreviation='fp', + short_desc=N_("name of external program used with \"gq\" command"), type='string', scope={'global', 'buffer'}, secure=true, vi_def=true, @@ -967,6 +1087,7 @@ return { }, { full_name='fsync', abbreviation='fs', + short_desc=N_("whether to invoke fsync() after file write"), type='bool', scope={'global'}, secure=true, vi_def=true, @@ -975,6 +1096,7 @@ return { }, { full_name='gdefault', abbreviation='gd', + short_desc=N_("the \":substitute\" flag 'g' is default on"), type='bool', scope={'global'}, vi_def=true, vim=true, @@ -983,6 +1105,7 @@ return { }, { full_name='grepformat', abbreviation='gfm', + short_desc=N_("format of 'grepprg' output"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vi_def=true, @@ -991,6 +1114,7 @@ return { }, { full_name='grepprg', abbreviation='gp', + short_desc=N_("program to use for \":grep\""), type='string', scope={'global', 'buffer'}, secure=true, vi_def=true, @@ -1006,6 +1130,7 @@ return { }, { full_name='guicursor', abbreviation='gcr', + short_desc=N_("GUI: settings for cursor shape and blinking"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vi_def=true, @@ -1014,6 +1139,7 @@ return { }, { full_name='guifont', abbreviation='gfn', + short_desc=N_("GUI: Name(s) of font(s) to be used"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vi_def=true, @@ -1022,16 +1148,8 @@ return { defaults={if_true={vi=""}} }, { - full_name='guifontset', abbreviation='gfs', - type='string', list='onecomma', scope={'global'}, - deny_duplicates=true, - vi_def=true, - varname='p_guifontset', - redraw={'ui_option'}, - defaults={if_true={vi=""}} - }, - { full_name='guifontwide', abbreviation='gfw', + short_desc=N_("list of font names for double-wide characters"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vi_def=true, @@ -1041,6 +1159,7 @@ return { }, { full_name='guioptions', abbreviation='go', + short_desc=N_("GUI: Which components and options are used"), type='string', list='flags', scope={'global'}, vi_def=true, redraw={'all_windows'}, @@ -1048,6 +1167,7 @@ return { }, { full_name='guitablabel', abbreviation='gtl', + short_desc=N_("GUI: custom label for a tab page"), type='string', scope={'global'}, vi_def=true, modelineexpr=true, @@ -1056,6 +1176,7 @@ return { }, { full_name='guitabtooltip', abbreviation='gtt', + short_desc=N_("GUI: custom tooltip for a tab page"), type='string', scope={'global'}, vi_def=true, redraw={'current_window'}, @@ -1063,6 +1184,7 @@ return { }, { full_name='helpfile', abbreviation='hf', + short_desc=N_("full path name of the main help file"), type='string', scope={'global'}, secure=true, vi_def=true, @@ -1072,6 +1194,7 @@ return { }, { full_name='helpheight', abbreviation='hh', + short_desc=N_("minimum height of a new help window"), type='number', scope={'global'}, vi_def=true, varname='p_hh', @@ -1079,6 +1202,7 @@ return { }, { full_name='helplang', abbreviation='hlg', + short_desc=N_("preferred help languages"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vi_def=true, @@ -1087,6 +1211,7 @@ return { }, { full_name='hidden', abbreviation='hid', + short_desc=N_("don't unload buffer when it is |abandon|ed"), type='bool', scope={'global'}, vi_def=true, varname='p_hid', @@ -1094,6 +1219,7 @@ return { }, { full_name='highlight', abbreviation='hl', + short_desc=N_("sets highlighting mode for various occasions"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vi_def=true, @@ -1102,6 +1228,7 @@ return { }, { full_name='history', abbreviation='hi', + short_desc=N_("number of command-lines that are remembered"), type='number', scope={'global'}, vim=true, varname='p_hi', @@ -1109,6 +1236,7 @@ return { }, { full_name='hkmap', abbreviation='hk', + short_desc=N_("Hebrew keyboard mapping"), type='bool', scope={'global'}, vi_def=true, vim=true, @@ -1117,6 +1245,7 @@ return { }, { full_name='hkmapp', abbreviation='hkp', + short_desc=N_("phonetic Hebrew keyboard mapping"), type='bool', scope={'global'}, vi_def=true, vim=true, @@ -1125,6 +1254,7 @@ return { }, { full_name='hlsearch', abbreviation='hls', + short_desc=N_("highlight matches with last search pattern"), type='bool', scope={'global'}, vim=true, redraw={'all_windows'}, @@ -1133,6 +1263,7 @@ return { }, { full_name='icon', + short_desc=N_("Vim set the text of the window icon"), type='bool', scope={'global'}, vi_def=true, varname='p_icon', @@ -1140,6 +1271,7 @@ return { }, { full_name='iconstring', + short_desc=N_("to use for the Vim icon text"), type='string', scope={'global'}, vi_def=true, modelineexpr=true, @@ -1148,6 +1280,7 @@ return { }, { full_name='ignorecase', abbreviation='ic', + short_desc=N_("ignore case in search patterns"), type='bool', scope={'global'}, vi_def=true, varname='p_ic', @@ -1155,6 +1288,7 @@ return { }, { full_name='imcmdline', abbreviation='imc', + short_desc=N_("use IM when starting to edit a command line"), type='bool', scope={'global'}, vi_def=true, enable_if=false, @@ -1162,6 +1296,7 @@ return { }, { full_name='imdisable', abbreviation='imd', + short_desc=N_("do not use the IM in any mode"), type='bool', scope={'global'}, vi_def=true, enable_if=false, @@ -1169,6 +1304,7 @@ return { }, { full_name='iminsert', abbreviation='imi', + short_desc=N_("use :lmap or IM in Insert mode"), type='number', scope={'buffer'}, vi_def=true, varname='p_iminsert', pv_name='p_imi', @@ -1178,6 +1314,7 @@ return { }, { full_name='imsearch', abbreviation='ims', + short_desc=N_("use :lmap or IM when typing a search pattern"), type='number', scope={'buffer'}, vi_def=true, varname='p_imsearch', pv_name='p_ims', @@ -1187,6 +1324,7 @@ return { }, { full_name='inccommand', abbreviation='icm', + short_desc=N_("Live preview of substitution"), type='string', scope={'global'}, vi_def=true, redraw={'all_windows'}, @@ -1195,6 +1333,7 @@ return { }, { full_name='include', abbreviation='inc', + short_desc=N_("pattern to be used to find an include file"), type='string', scope={'global', 'buffer'}, vi_def=true, alloced=true, @@ -1203,6 +1342,7 @@ return { }, { full_name='includeexpr', abbreviation='inex', + short_desc=N_("expression used to process an include line"), type='string', scope={'buffer'}, vi_def=true, modelineexpr=true, @@ -1212,6 +1352,7 @@ return { }, { full_name='incsearch', abbreviation='is', + short_desc=N_("highlight match while typing search pattern"), type='bool', scope={'global'}, vim=true, varname='p_is', @@ -1219,6 +1360,7 @@ return { }, { full_name='indentexpr', abbreviation='inde', + short_desc=N_("expression used to obtain the indent of a line"), type='string', scope={'buffer'}, vi_def=true, vim=true, @@ -1229,6 +1371,7 @@ return { }, { full_name='indentkeys', abbreviation='indk', + short_desc=N_("keys that trigger indenting with 'indentexpr'"), type='string', list='onecomma', scope={'buffer'}, deny_duplicates=true, vi_def=true, @@ -1238,6 +1381,7 @@ return { }, { full_name='infercase', abbreviation='inf', + short_desc=N_("adjust case of match for keyword completion"), type='bool', scope={'buffer'}, vi_def=true, varname='p_inf', @@ -1245,6 +1389,7 @@ return { }, { full_name='insertmode', abbreviation='im', + short_desc=N_("start the edit of a file in Insert mode"), type='bool', scope={'global'}, vi_def=true, vim=true, @@ -1253,6 +1398,7 @@ return { }, { full_name='isfname', abbreviation='isf', + short_desc=N_("characters included in file names and pathnames"), type='string', list='comma', scope={'global'}, deny_duplicates=true, vi_def=true, @@ -1267,14 +1413,20 @@ return { }, { full_name='isident', abbreviation='isi', + short_desc=N_("characters included in identifiers"), type='string', list='comma', scope={'global'}, deny_duplicates=true, vi_def=true, varname='p_isi', - defaults={if_true={vi="@,48-57,_,192-255"}} + defaults={ + condition='WIN32', + if_true={vi="@,48-57,_,128-167,224-235"}, + if_false={vi="@,48-57,_,192-255"} + } }, { full_name='iskeyword', abbreviation='isk', + short_desc=N_("characters included in keywords"), type='string', list='comma', scope={'buffer'}, deny_duplicates=true, vim=true, @@ -1284,6 +1436,7 @@ return { }, { full_name='isprint', abbreviation='isp', + short_desc=N_("printable characters"), type='string', list='comma', scope={'global'}, deny_duplicates=true, vi_def=true, @@ -1294,6 +1447,7 @@ return { }, { full_name='joinspaces', abbreviation='js', + short_desc=N_("two spaces after a period with a join command"), type='bool', scope={'global'}, vi_def=true, vim=true, @@ -1302,6 +1456,7 @@ return { }, { full_name='jumpoptions', abbreviation='jop', + short_desc=N_("Controls the behavior of the jumplist"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, varname='p_jop', @@ -1310,6 +1465,7 @@ return { }, { full_name='keymap', abbreviation='kmp', + short_desc=N_("name of a keyboard mapping"), type='string', scope={'buffer'}, normal_fname_chars=true, pri_mkrc=true, @@ -1321,6 +1477,7 @@ return { }, { full_name='keymodel', abbreviation='km', + short_desc=N_("enable starting/stopping selection with keys"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vi_def=true, @@ -1329,6 +1486,7 @@ return { }, { full_name='keywordprg', abbreviation='kp', + short_desc=N_("program to use for the \"K\" command"), type='string', scope={'global', 'buffer'}, secure=true, vi_def=true, @@ -1340,6 +1498,7 @@ return { }, { full_name='langmap', abbreviation='lmap', + short_desc=N_("alphabetic characters for other language mode"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, secure=true, @@ -1349,6 +1508,7 @@ return { }, { full_name='langmenu', abbreviation='lm', + short_desc=N_("language to be used for the menus"), type='string', scope={'global'}, normal_fname_chars=true, vi_def=true, @@ -1357,18 +1517,21 @@ return { }, { full_name='langnoremap', abbreviation='lnr', + short_desc=N_("do not apply 'langmap' to mapped characters"), type='bool', scope={'global'}, varname='p_lnr', defaults={if_true={vi=false, vim=true}} }, { full_name='langremap', abbreviation='lrm', + short_desc=N_('No description'), type='bool', scope={'global'}, varname='p_lrm', defaults={if_true={vi=true, vim=false}} }, { full_name='laststatus', abbreviation='ls', + short_desc=N_("tells when last window has status lines"), type='number', scope={'global'}, vim=true, redraw={'all_windows'}, @@ -1377,6 +1540,7 @@ return { }, { full_name='lazyredraw', abbreviation='lz', + short_desc=N_("don't redraw while executing macros"), type='bool', scope={'global'}, vi_def=true, varname='p_lz', @@ -1384,6 +1548,7 @@ return { }, { full_name='linebreak', abbreviation='lbr', + short_desc=N_("wrap long lines at a blank"), type='bool', scope={'window'}, vi_def=true, redraw={'current_window'}, @@ -1391,6 +1556,7 @@ return { }, { full_name='lines', + short_desc=N_("of lines in the display"), type='number', scope={'global'}, no_mkrc=true, vi_def=true, @@ -1400,6 +1566,7 @@ return { }, { full_name='linespace', abbreviation='lsp', + short_desc=N_("number of pixel lines to use between characters"), type='number', scope={'global'}, vi_def=true, redraw={'ui_option'}, @@ -1408,6 +1575,7 @@ return { }, { full_name='lisp', + short_desc=N_("indenting for Lisp"), type='bool', scope={'buffer'}, vi_def=true, varname='p_lisp', @@ -1415,6 +1583,7 @@ return { }, { full_name='lispwords', abbreviation='lw', + short_desc=N_("words that change how lisp indenting works"), type='string', list='onecomma', scope={'global', 'buffer'}, deny_duplicates=true, vi_def=true, @@ -1423,6 +1592,7 @@ return { }, { full_name='list', + short_desc=N_("<Tab> and <EOL>"), type='bool', scope={'window'}, vi_def=true, redraw={'current_window'}, @@ -1430,6 +1600,7 @@ return { }, { full_name='listchars', abbreviation='lcs', + short_desc=N_("characters for displaying in list mode"), type='string', list='onecomma', scope={'global', 'window'}, deny_duplicates=true, vim=true, @@ -1440,6 +1611,7 @@ return { }, { full_name='loadplugins', abbreviation='lpl', + short_desc=N_("load plugin scripts when starting up"), type='bool', scope={'global'}, vi_def=true, varname='p_lpl', @@ -1447,6 +1619,7 @@ return { }, { full_name='magic', + short_desc=N_("special characters in search patterns"), type='bool', scope={'global'}, vi_def=true, varname='p_magic', @@ -1454,6 +1627,7 @@ return { }, { full_name='makeef', abbreviation='mef', + short_desc=N_("name of the errorfile for \":make\""), type='string', scope={'global'}, secure=true, vi_def=true, @@ -1463,6 +1637,7 @@ return { }, { full_name='makeencoding', abbreviation='menc', + short_desc=N_("Converts the output of external commands"), type='string', scope={'global', 'buffer'}, vi_def=true, varname='p_menc', @@ -1470,6 +1645,7 @@ return { }, { full_name='makeprg', abbreviation='mp', + short_desc=N_("program to use for the \":make\" command"), type='string', scope={'global', 'buffer'}, secure=true, vi_def=true, @@ -1479,6 +1655,7 @@ return { }, { full_name='matchpairs', abbreviation='mps', + short_desc=N_("pairs of characters that \"%\" can match"), type='string', list='onecomma', scope={'buffer'}, deny_duplicates=true, vi_def=true, @@ -1488,6 +1665,7 @@ return { }, { full_name='matchtime', abbreviation='mat', + short_desc=N_("tenths of a second to show matching paren"), type='number', scope={'global'}, vi_def=true, varname='p_mat', @@ -1495,6 +1673,7 @@ return { }, { full_name='maxcombine', abbreviation='mco', + short_desc=N_("maximum nr of combining characters displayed"), type='number', scope={'global'}, vi_def=true, varname='p_mco', @@ -1502,6 +1681,7 @@ return { }, { full_name='maxfuncdepth', abbreviation='mfd', + short_desc=N_("maximum recursive depth for user functions"), type='number', scope={'global'}, vi_def=true, varname='p_mfd', @@ -1509,6 +1689,7 @@ return { }, { full_name='maxmapdepth', abbreviation='mmd', + short_desc=N_("maximum recursive depth for mapping"), type='number', scope={'global'}, vi_def=true, varname='p_mmd', @@ -1516,6 +1697,7 @@ return { }, { full_name='maxmempattern', abbreviation='mmp', + short_desc=N_("maximum memory (in Kbyte) used for pattern search"), type='number', scope={'global'}, vi_def=true, varname='p_mmp', @@ -1523,6 +1705,7 @@ return { }, { full_name='menuitems', abbreviation='mis', + short_desc=N_("maximum number of items in a menu"), type='number', scope={'global'}, vi_def=true, varname='p_mis', @@ -1530,6 +1713,7 @@ return { }, { full_name='mkspellmem', abbreviation='msm', + short_desc=N_("memory used before |:mkspell| compresses the tree"), type='string', scope={'global'}, secure=true, vi_def=true, @@ -1539,6 +1723,7 @@ return { }, { full_name='modeline', abbreviation='ml', + short_desc=N_("recognize modelines at start or end of file"), type='bool', scope={'buffer'}, vim=true, varname='p_ml', @@ -1546,6 +1731,7 @@ return { }, { full_name='modelineexpr', abbreviation='mle', + short_desc=N_("allow some options to be set in modeline"), type='bool', scope={'global'}, vi_def=true, secure=true, @@ -1554,6 +1740,7 @@ return { }, { full_name='modelines', abbreviation='mls', + short_desc=N_("number of lines checked for modelines"), type='number', scope={'global'}, vi_def=true, varname='p_mls', @@ -1561,6 +1748,7 @@ return { }, { full_name='modifiable', abbreviation='ma', + short_desc=N_("changes to the text are not possible"), type='bool', scope={'buffer'}, noglob=true, vi_def=true, @@ -1569,6 +1757,7 @@ return { }, { full_name='modified', abbreviation='mod', + short_desc=N_("buffer has been modified"), type='bool', scope={'buffer'}, no_mkrc=true, vi_def=true, @@ -1578,6 +1767,7 @@ return { }, { full_name='more', + short_desc=N_("listings when the whole screen is filled"), type='bool', scope={'global'}, vim=true, varname='p_more', @@ -1585,19 +1775,23 @@ return { }, { full_name='mouse', + short_desc=N_("the use of mouse clicks"), type='string', list='flags', scope={'global'}, varname='p_mouse', defaults={if_true={vi="", vim=""}} }, { full_name='mousefocus', abbreviation='mousef', + short_desc=N_("keyboard focus follows the mouse"), type='bool', scope={'global'}, vi_def=true, - enable_if=false, + redraw={'ui_option'}, + varname='p_mousef', defaults={if_true={vi=false}} }, { full_name='mousehide', abbreviation='mh', + short_desc=N_("hide mouse pointer while typing"), type='bool', scope={'global'}, vi_def=true, enable_if=false, @@ -1605,6 +1799,7 @@ return { }, { full_name='mousemodel', abbreviation='mousem', + short_desc=N_("changes meaning of mouse buttons"), type='string', scope={'global'}, vi_def=true, varname='p_mousem', @@ -1612,6 +1807,7 @@ return { }, { full_name='mouseshape', abbreviation='mouses', + short_desc=N_("shape of the mouse pointer in different modes"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vi_def=true, @@ -1619,6 +1815,7 @@ return { }, { full_name='mousetime', abbreviation='mouset', + short_desc=N_("max time between mouse double-click"), type='number', scope={'global'}, vi_def=true, varname='p_mouset', @@ -1626,6 +1823,7 @@ return { }, { full_name='nrformats', abbreviation='nf', + short_desc=N_("number formats recognized for CTRL-A command"), type='string', list='onecomma', scope={'buffer'}, deny_duplicates=true, alloced=true, @@ -1634,6 +1832,7 @@ return { }, { full_name='number', abbreviation='nu', + short_desc=N_("print the line number in front of each line"), type='bool', scope={'window'}, vi_def=true, redraw={'current_window'}, @@ -1641,6 +1840,7 @@ return { }, { full_name='numberwidth', abbreviation='nuw', + short_desc=N_("number of columns used for the line number"), type='number', scope={'window'}, vim=true, redraw={'current_window'}, @@ -1648,6 +1848,7 @@ return { }, { full_name='omnifunc', abbreviation='ofu', + short_desc=N_("function for filetype-specific completion"), type='string', scope={'buffer'}, secure=true, vi_def=true, @@ -1657,6 +1858,7 @@ return { }, { full_name='opendevice', abbreviation='odev', + short_desc=N_("allow reading/writing devices on MS-Windows"), type='bool', scope={'global'}, vi_def=true, enable_if=false, @@ -1664,6 +1866,7 @@ return { }, { full_name='operatorfunc', abbreviation='opfunc', + short_desc=N_("function to be called for |g@| operator"), type='string', scope={'global'}, secure=true, vi_def=true, @@ -1672,6 +1875,7 @@ return { }, { full_name='packpath', abbreviation='pp', + short_desc=N_("list of directories used for packages"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, secure=true, @@ -1682,6 +1886,7 @@ return { }, { full_name='paragraphs', abbreviation='para', + short_desc=N_("nroff macros that separate paragraphs"), type='string', scope={'global'}, vi_def=true, varname='p_para', @@ -1689,6 +1894,7 @@ return { }, { full_name='paste', + short_desc=N_("pasting text"), type='bool', scope={'global'}, pri_mkrc=true, vi_def=true, @@ -1697,6 +1903,7 @@ return { }, { full_name='pastetoggle', abbreviation='pt', + short_desc=N_("key code that causes 'paste' to toggle"), type='string', scope={'global'}, vi_def=true, varname='p_pt', @@ -1704,6 +1911,7 @@ return { }, { full_name='patchexpr', abbreviation='pex', + short_desc=N_("expression used to patch a file"), type='string', scope={'global'}, secure=true, vi_def=true, @@ -1712,6 +1920,7 @@ return { }, { full_name='patchmode', abbreviation='pm', + short_desc=N_("keep the oldest version of a file"), type='string', scope={'global'}, normal_fname_chars=true, vi_def=true, @@ -1720,6 +1929,7 @@ return { }, { full_name='path', abbreviation='pa', + short_desc=N_("list of directories searched with \"gf\" et.al."), type='string', list='comma', scope={'global', 'buffer'}, deny_duplicates=true, vi_def=true, @@ -1729,6 +1939,7 @@ return { }, { full_name='preserveindent', abbreviation='pi', + short_desc=N_("preserve the indent structure when reindenting"), type='bool', scope={'buffer'}, vi_def=true, vim=true, @@ -1737,6 +1948,7 @@ return { }, { full_name='previewheight', abbreviation='pvh', + short_desc=N_("height of the preview window"), type='number', scope={'global'}, vi_def=true, varname='p_pvh', @@ -1744,6 +1956,7 @@ return { }, { full_name='previewwindow', abbreviation='pvw', + short_desc=N_("identifies the preview window"), type='bool', scope={'window'}, noglob=true, vi_def=true, @@ -1752,6 +1965,7 @@ return { }, { full_name='printdevice', abbreviation='pdev', + short_desc=N_("name of the printer to be used for :hardcopy"), type='string', scope={'global'}, secure=true, vi_def=true, @@ -1760,6 +1974,7 @@ return { }, { full_name='printencoding', abbreviation='penc', + short_desc=N_("encoding to be used for printing"), type='string', scope={'global'}, vi_def=true, varname='p_penc', @@ -1767,6 +1982,7 @@ return { }, { full_name='printexpr', abbreviation='pexpr', + short_desc=N_("expression used to print PostScript for :hardcopy"), type='string', scope={'global'}, secure=true, vi_def=true, @@ -1775,6 +1991,7 @@ return { }, { full_name='printfont', abbreviation='pfn', + short_desc=N_("name of the font to be used for :hardcopy"), type='string', scope={'global'}, vi_def=true, varname='p_pfn', @@ -1782,6 +1999,7 @@ return { }, { full_name='printheader', abbreviation='pheader', + short_desc=N_("format of the header used for :hardcopy"), type='string', scope={'global'}, vi_def=true, varname='p_header', @@ -1789,6 +2007,7 @@ return { }, { full_name='printmbcharset', abbreviation='pmbcs', + short_desc=N_("CJK character set to be used for :hardcopy"), type='string', scope={'global'}, vi_def=true, varname='p_pmcs', @@ -1796,6 +2015,7 @@ return { }, { full_name='printmbfont', abbreviation='pmbfn', + short_desc=N_("font names to be used for CJK output of :hardcopy"), type='string', scope={'global'}, vi_def=true, varname='p_pmfn', @@ -1803,6 +2023,7 @@ return { }, { full_name='printoptions', abbreviation='popt', + short_desc=N_("controls the format of :hardcopy output"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vi_def=true, @@ -1811,6 +2032,7 @@ return { }, { full_name='prompt', + short_desc=N_("enable prompt in Ex mode"), type='bool', scope={'global'}, vi_def=true, varname='p_prompt', @@ -1818,6 +2040,7 @@ return { }, { full_name='pumblend', abbreviation='pb', + short_desc=N_("Controls transparency level of popup menu"), type='number', scope={'global'}, vi_def=true, redraw={'ui_option'}, @@ -1826,6 +2049,7 @@ return { }, { full_name='pumheight', abbreviation='ph', + short_desc=N_("maximum height of the popup menu"), type='number', scope={'global'}, vi_def=true, varname='p_ph', @@ -1833,6 +2057,7 @@ return { }, { full_name='pumwidth', abbreviation='pw', + short_desc=N_("minimum width of the popup menu"), type='number', scope={'global'}, vi_def=true, varname='p_pw', @@ -1840,6 +2065,7 @@ return { }, { full_name='pyxversion', abbreviation='pyx', + short_desc=N_("selects default python version to use"), type='number', scope={'global'}, secure=true, vi_def=true, @@ -1848,6 +2074,7 @@ return { }, { full_name='quoteescape', abbreviation='qe', + short_desc=N_("escape characters used in a string"), type='string', scope={'buffer'}, vi_def=true, alloced=true, @@ -1856,6 +2083,7 @@ return { }, { full_name='readonly', abbreviation='ro', + short_desc=N_("disallow writing the buffer"), type='bool', scope={'buffer'}, noglob=true, vi_def=true, @@ -1865,6 +2093,7 @@ return { }, { full_name='redrawdebug', abbreviation='rdb', + short_desc=N_("Changes the way redrawing works (debug)"), type='string', list='onecomma', scope={'global'}, vi_def=true, varname='p_rdb', @@ -1872,6 +2101,7 @@ return { }, { full_name='redrawtime', abbreviation='rdt', + short_desc=N_("timeout for 'hlsearch' and |:match| highlighting"), type='number', scope={'global'}, vi_def=true, varname='p_rdt', @@ -1879,6 +2109,7 @@ return { }, { full_name='regexpengine', abbreviation='re', + short_desc=N_("default regexp engine to use"), type='number', scope={'global'}, vi_def=true, varname='p_re', @@ -1886,6 +2117,7 @@ return { }, { full_name='relativenumber', abbreviation='rnu', + short_desc=N_("show relative line number in front of each line"), type='bool', scope={'window'}, vi_def=true, redraw={'current_window'}, @@ -1893,6 +2125,7 @@ return { }, { full_name='remap', + short_desc=N_("mappings to work recursively"), type='bool', scope={'global'}, vi_def=true, varname='p_remap', @@ -1900,6 +2133,7 @@ return { }, { full_name='report', + short_desc=N_("for reporting nr. of lines changed"), type='number', scope={'global'}, vi_def=true, varname='p_report', @@ -1907,6 +2141,7 @@ return { }, { full_name='revins', abbreviation='ri', + short_desc=N_("inserting characters will work backwards"), type='bool', scope={'global'}, vi_def=true, vim=true, @@ -1915,6 +2150,7 @@ return { }, { full_name='rightleft', abbreviation='rl', + short_desc=N_("window is right-to-left oriented"), type='bool', scope={'window'}, vi_def=true, redraw={'current_window'}, @@ -1922,6 +2158,7 @@ return { }, { full_name='rightleftcmd', abbreviation='rlc', + short_desc=N_("commands for which editing works right-to-left"), type='string', scope={'window'}, vi_def=true, alloced=true, @@ -1930,6 +2167,7 @@ return { }, { full_name='ruler', abbreviation='ru', + short_desc=N_("show cursor line and column in the status line"), type='bool', scope={'global'}, vi_def=true, vim=true, @@ -1939,6 +2177,7 @@ return { }, { full_name='rulerformat', abbreviation='ruf', + short_desc=N_("custom format for the ruler"), type='string', scope={'global'}, vi_def=true, alloced=true, @@ -1949,6 +2188,7 @@ return { }, { full_name='runtimepath', abbreviation='rtp', + short_desc=N_("list of directories used for runtime files"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, secure=true, @@ -1959,6 +2199,7 @@ return { }, { full_name='scroll', abbreviation='scr', + short_desc=N_("lines to scroll with CTRL-U and CTRL-D"), type='number', scope={'window'}, no_mkrc=true, vi_def=true, @@ -1967,6 +2208,7 @@ return { }, { full_name='scrollback', abbreviation='scbk', + short_desc=N_("lines to scroll with CTRL-U and CTRL-D"), type='number', scope={'buffer'}, vi_def=true, varname='p_scbk', @@ -1975,6 +2217,7 @@ return { }, { full_name='scrollbind', abbreviation='scb', + short_desc=N_("scroll in window as other windows scroll"), type='bool', scope={'window'}, vi_def=true, pv_name='p_scbind', @@ -1982,6 +2225,7 @@ return { }, { full_name='scrolljump', abbreviation='sj', + short_desc=N_("minimum number of lines to scroll"), type='number', scope={'global'}, vi_def=true, vim=true, @@ -1990,6 +2234,7 @@ return { }, { full_name='scrolloff', abbreviation='so', + short_desc=N_("minimum nr. of lines above and below cursor"), type='number', scope={'global', 'window'}, vi_def=true, vim=true, @@ -1999,6 +2244,7 @@ return { }, { full_name='scrollopt', abbreviation='sbo', + short_desc=N_("how 'scrollbind' should behave"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vi_def=true, @@ -2007,6 +2253,7 @@ return { }, { full_name='sections', abbreviation='sect', + short_desc=N_("nroff macros that separate sections"), type='string', scope={'global'}, vi_def=true, varname='p_sections', @@ -2014,6 +2261,7 @@ return { }, { full_name='secure', + short_desc=N_("mode for reading .vimrc in current dir"), type='bool', scope={'global'}, secure=true, vi_def=true, @@ -2022,6 +2270,7 @@ return { }, { full_name='selection', abbreviation='sel', + short_desc=N_("what type of selection to use"), type='string', scope={'global'}, vi_def=true, varname='p_sel', @@ -2029,6 +2278,7 @@ return { }, { full_name='selectmode', abbreviation='slm', + short_desc=N_("when to use Select mode instead of Visual mode"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vi_def=true, @@ -2037,6 +2287,7 @@ return { }, { full_name='sessionoptions', abbreviation='ssop', + short_desc=N_("options for |:mksession|"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vim=true, @@ -2048,6 +2299,7 @@ return { }, { full_name='shada', abbreviation='sd', + short_desc=N_("use .shada file upon startup and exiting"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, secure=true, @@ -2056,6 +2308,7 @@ return { }, { full_name='shadafile', abbreviation='sdf', + short_desc=N_("overrides the filename used for shada"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vi_def=true, @@ -2065,6 +2318,7 @@ return { }, { full_name='shell', abbreviation='sh', + short_desc=N_("name of shell to use for external commands"), type='string', scope={'global'}, secure=true, vi_def=true, @@ -2078,6 +2332,7 @@ return { }, { full_name='shellcmdflag', abbreviation='shcf', + short_desc=N_("flag to shell to execute one command"), type='string', scope={'global'}, secure=true, vi_def=true, @@ -2090,6 +2345,7 @@ return { }, { full_name='shellpipe', abbreviation='sp', + short_desc=N_("string to put output of \":make\" in error file"), type='string', scope={'global'}, secure=true, vi_def=true, @@ -2102,6 +2358,7 @@ return { }, { full_name='shellquote', abbreviation='shq', + short_desc=N_("quote character(s) for around shell command"), type='string', scope={'global'}, secure=true, vi_def=true, @@ -2110,6 +2367,7 @@ return { }, { full_name='shellredir', abbreviation='srr', + short_desc=N_("string to put output of filter in a temp file"), type='string', scope={'global'}, secure=true, vi_def=true, @@ -2122,6 +2380,7 @@ return { }, { full_name='shellslash', abbreviation='ssl', + short_desc=N_("use forward slash for shell file names"), type='bool', scope={'global'}, vi_def=true, varname='p_ssl', @@ -2130,12 +2389,14 @@ return { }, { full_name='shelltemp', abbreviation='stmp', + short_desc=N_("whether to use a temp file for shell commands"), type='bool', scope={'global'}, varname='p_stmp', defaults={if_true={vi=false, vim=true}} }, { full_name='shellxquote', abbreviation='sxq', + short_desc=N_("like 'shellquote', but include redirection"), type='string', scope={'global'}, secure=true, vi_def=true, @@ -2148,6 +2409,7 @@ return { }, { full_name='shellxescape', abbreviation='sxe', + short_desc=N_("characters to escape when 'shellxquote' is ("), type='string', scope={'global'}, secure=true, vi_def=true, @@ -2156,6 +2418,7 @@ return { }, { full_name='shiftround', abbreviation='sr', + short_desc=N_("round indent to multiple of shiftwidth"), type='bool', scope={'global'}, vi_def=true, vim=true, @@ -2164,6 +2427,7 @@ return { }, { full_name='shiftwidth', abbreviation='sw', + short_desc=N_("number of spaces to use for (auto)indent step"), type='number', scope={'buffer'}, vi_def=true, varname='p_sw', @@ -2171,6 +2435,7 @@ return { }, { full_name='shortmess', abbreviation='shm', + short_desc=N_("list of flags, reduce length of messages"), type='string', list='flags', scope={'global'}, vim=true, varname='p_shm', @@ -2178,6 +2443,7 @@ return { }, { full_name='showbreak', abbreviation='sbr', + short_desc=N_("string to use at the start of wrapped lines"), type='string', scope={'global'}, vi_def=true, redraw={'all_windows'}, @@ -2186,6 +2452,7 @@ return { }, { full_name='showcmd', abbreviation='sc', + short_desc=N_("show (partial) command in status line"), type='bool', scope={'global'}, vim=true, varname='p_sc', @@ -2193,6 +2460,7 @@ return { }, { full_name='showfulltag', abbreviation='sft', + short_desc=N_("show full tag pattern when completing tag"), type='bool', scope={'global'}, vi_def=true, varname='p_sft', @@ -2200,6 +2468,7 @@ return { }, { full_name='showmatch', abbreviation='sm', + short_desc=N_("briefly jump to matching bracket if insert one"), type='bool', scope={'global'}, vi_def=true, varname='p_sm', @@ -2207,6 +2476,7 @@ return { }, { full_name='showmode', abbreviation='smd', + short_desc=N_("message on status line to show current mode"), type='bool', scope={'global'}, vim=true, varname='p_smd', @@ -2214,6 +2484,7 @@ return { }, { full_name='showtabline', abbreviation='stal', + short_desc=N_("tells when the tab pages line is displayed"), type='number', scope={'global'}, vi_def=true, redraw={'all_windows', 'ui_option'}, @@ -2222,6 +2493,7 @@ return { }, { full_name='sidescroll', abbreviation='ss', + short_desc=N_("minimum number of columns to scroll horizontal"), type='number', scope={'global'}, vi_def=true, varname='p_ss', @@ -2229,6 +2501,7 @@ return { }, { full_name='sidescrolloff', abbreviation='siso', + short_desc=N_("min. nr. of columns to left and right of cursor"), type='number', scope={'global', 'window'}, vi_def=true, vim=true, @@ -2238,6 +2511,7 @@ return { }, { full_name='signcolumn', abbreviation='scl', + short_desc=N_("when to display the sign column"), type='string', scope={'window'}, vi_def=true, alloced=true, @@ -2246,6 +2520,7 @@ return { }, { full_name='smartcase', abbreviation='scs', + short_desc=N_("no ignore case when pattern has uppercase"), type='bool', scope={'global'}, vi_def=true, vim=true, @@ -2254,6 +2529,7 @@ return { }, { full_name='smartindent', abbreviation='si', + short_desc=N_("smart autoindenting for C programs"), type='bool', scope={'buffer'}, vi_def=true, vim=true, @@ -2262,6 +2538,7 @@ return { }, { full_name='smarttab', abbreviation='sta', + short_desc=N_("use 'shiftwidth' when inserting <Tab>"), type='bool', scope={'global'}, vim=true, varname='p_sta', @@ -2269,6 +2546,7 @@ return { }, { full_name='softtabstop', abbreviation='sts', + short_desc=N_("number of spaces that <Tab> uses while editing"), type='number', scope={'buffer'}, vi_def=true, vim=true, @@ -2277,6 +2555,7 @@ return { }, { full_name='spell', + short_desc=N_("spell checking"), type='bool', scope={'window'}, vi_def=true, redraw={'current_window'}, @@ -2284,6 +2563,7 @@ return { }, { full_name='spellcapcheck', abbreviation='spc', + short_desc=N_("pattern to locate end of a sentence"), type='string', scope={'buffer'}, vi_def=true, alloced=true, @@ -2293,6 +2573,7 @@ return { }, { full_name='spellfile', abbreviation='spf', + short_desc=N_("files where |zg| and |zw| store words"), type='string', list='onecomma', scope={'buffer'}, deny_duplicates=true, secure=true, @@ -2304,6 +2585,7 @@ return { }, { full_name='spelllang', abbreviation='spl', + short_desc=N_("language(s) to do spell checking for"), type='string', list='onecomma', scope={'buffer'}, deny_duplicates=true, vi_def=true, @@ -2315,6 +2597,7 @@ return { }, { full_name='spellsuggest', abbreviation='sps', + short_desc=N_("method(s) used to suggest spelling corrections"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, secure=true, @@ -2324,7 +2607,18 @@ return { defaults={if_true={vi="best"}} }, { + full_name='spelloptions', abbreviation='spo', + type='string', list='onecomma', scope={'buffer'}, + deny_duplicates=true, + secure=true, + vi_def=true, + expand=true, + varname='p_spo', + defaults={if_true={vi="", vim=""}} + }, + { full_name='splitbelow', abbreviation='sb', + short_desc=N_("new window from split is below the current one"), type='bool', scope={'global'}, vi_def=true, varname='p_sb', @@ -2332,6 +2626,7 @@ return { }, { full_name='splitright', abbreviation='spr', + short_desc=N_("new window is put right of the current one"), type='bool', scope={'global'}, vi_def=true, varname='p_spr', @@ -2339,6 +2634,7 @@ return { }, { full_name='startofline', abbreviation='sol', + short_desc=N_("commands move cursor to first non-blank in line"), type='bool', scope={'global'}, vi_def=true, vim=false, @@ -2347,6 +2643,7 @@ return { }, { full_name='statusline', abbreviation='stl', + short_desc=N_("custom format for the status line"), type='string', scope={'global', 'window'}, vi_def=true, alloced=true, @@ -2357,6 +2654,7 @@ return { }, { full_name='suffixes', abbreviation='su', + short_desc=N_("suffixes that are ignored with multiple match"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vi_def=true, @@ -2365,6 +2663,7 @@ return { }, { full_name='suffixesadd', abbreviation='sua', + short_desc=N_("suffixes added when searching for a file"), type='string', list='onecomma', scope={'buffer'}, deny_duplicates=true, vi_def=true, @@ -2374,6 +2673,7 @@ return { }, { full_name='swapfile', abbreviation='swf', + short_desc=N_("whether to use a swapfile for a buffer"), type='bool', scope={'buffer'}, vi_def=true, redraw={'statuslines'}, @@ -2382,6 +2682,7 @@ return { }, { full_name='switchbuf', abbreviation='swb', + short_desc=N_("sets behavior when switching to another buffer"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vi_def=true, @@ -2390,6 +2691,7 @@ return { }, { full_name='synmaxcol', abbreviation='smc', + short_desc=N_("maximum column to find syntax items"), type='number', scope={'buffer'}, vi_def=true, redraw={'current_buffer'}, @@ -2398,6 +2700,7 @@ return { }, { full_name='syntax', abbreviation='syn', + short_desc=N_("syntax to be loaded for current buffer"), type='string', scope={'buffer'}, noglob=true, normal_fname_chars=true, @@ -2408,6 +2711,7 @@ return { }, { full_name='tagfunc', abbreviation='tfu', + short_desc=N_("function used to perform tag searches"), type='string', scope={'buffer'}, vim=true, vi_def=true, @@ -2416,6 +2720,7 @@ return { }, { full_name='tabline', abbreviation='tal', + short_desc=N_("custom format for the console tab pages line"), type='string', scope={'global'}, vi_def=true, modelineexpr=true, @@ -2425,6 +2730,7 @@ return { }, { full_name='tabpagemax', abbreviation='tpm', + short_desc=N_("maximum number of tab pages for |-p| and \"tab all\""), type='number', scope={'global'}, vim=true, varname='p_tpm', @@ -2432,6 +2738,7 @@ return { }, { full_name='tabstop', abbreviation='ts', + short_desc=N_("number of spaces that <Tab> in file uses"), type='number', scope={'buffer'}, vi_def=true, redraw={'current_buffer'}, @@ -2440,6 +2747,7 @@ return { }, { full_name='tagbsearch', abbreviation='tbs', + short_desc=N_("use binary searching in tags files"), type='bool', scope={'global'}, vi_def=true, varname='p_tbs', @@ -2447,6 +2755,7 @@ return { }, { full_name='tagcase', abbreviation='tc', + short_desc=N_("how to handle case when searching in tags files"), type='string', scope={'global', 'buffer'}, vim=true, varname='p_tc', @@ -2454,6 +2763,7 @@ return { }, { full_name='taglength', abbreviation='tl', + short_desc=N_("number of significant characters for a tag"), type='number', scope={'global'}, vi_def=true, varname='p_tl', @@ -2461,6 +2771,7 @@ return { }, { full_name='tagrelative', abbreviation='tr', + short_desc=N_("file names in tag file are relative"), type='bool', scope={'global'}, vim=true, varname='p_tr', @@ -2468,6 +2779,7 @@ return { }, { full_name='tags', abbreviation='tag', + short_desc=N_("list of file names used by the tag command"), type='string', list='onecomma', scope={'global', 'buffer'}, deny_duplicates=true, vi_def=true, @@ -2477,6 +2789,7 @@ return { }, { full_name='tagstack', abbreviation='tgst', + short_desc=N_("push tags onto the tag stack"), type='bool', scope={'global'}, vi_def=true, varname='p_tgst', @@ -2484,6 +2797,7 @@ return { }, { full_name='termbidi', abbreviation='tbidi', + short_desc=N_("terminal takes care of bi-directionality"), type='bool', scope={'global'}, vi_def=true, varname='p_tbidi', @@ -2491,12 +2805,14 @@ return { }, { full_name='termencoding', abbreviation='tenc', + short_desc=N_("Terminal encodig"), type='string', scope={'global'}, vi_def=true, defaults={if_true={vi=""}} }, { full_name='termguicolors', abbreviation='tgc', + short_desc=N_("Terminal true color support"), type='bool', scope={'global'}, vi_def=false, redraw={'ui_option'}, @@ -2505,6 +2821,7 @@ return { }, { full_name='terse', + short_desc=N_("hides notification of search wrap"), type='bool', scope={'global'}, vi_def=true, varname='p_terse', @@ -2512,6 +2829,7 @@ return { }, { full_name='textwidth', abbreviation='tw', + short_desc=N_("maximum width of text that is being inserted"), type='number', scope={'buffer'}, vi_def=true, vim=true, @@ -2521,6 +2839,7 @@ return { }, { full_name='thesaurus', abbreviation='tsr', + short_desc=N_("list of thesaurus files for keyword completion"), type='string', list='onecomma', scope={'global', 'buffer'}, deny_duplicates=true, normal_dname_chars=true, @@ -2531,6 +2850,7 @@ return { }, { full_name='tildeop', abbreviation='top', + short_desc=N_("tilde command \"~\" behaves like an operator"), type='bool', scope={'global'}, vi_def=true, vim=true, @@ -2539,6 +2859,7 @@ return { }, { full_name='timeout', abbreviation='to', + short_desc=N_("time out on mappings and key codes"), type='bool', scope={'global'}, vi_def=true, varname='p_timeout', @@ -2546,6 +2867,7 @@ return { }, { full_name='timeoutlen', abbreviation='tm', + short_desc=N_("time out time in milliseconds"), type='number', scope={'global'}, vi_def=true, varname='p_tm', @@ -2553,6 +2875,7 @@ return { }, { full_name='title', + short_desc=N_("Vim set the title of the window"), type='bool', scope={'global'}, vi_def=true, varname='p_title', @@ -2560,6 +2883,7 @@ return { }, { full_name='titlelen', + short_desc=N_("of 'columns' used for window title"), type='number', scope={'global'}, vi_def=true, varname='p_titlelen', @@ -2567,6 +2891,7 @@ return { }, { full_name='titleold', + short_desc=N_("title, restored when exiting"), type='string', scope={'global'}, secure=true, no_mkrc=true, @@ -2576,6 +2901,7 @@ return { }, { full_name='titlestring', + short_desc=N_("to use for the Vim window title"), type='string', scope={'global'}, vi_def=true, modelineexpr=true, @@ -2584,6 +2910,7 @@ return { }, { full_name='ttimeout', + short_desc=N_("out on mappings"), type='bool', scope={'global'}, vi_def=true, vim=true, @@ -2593,6 +2920,7 @@ return { }, { full_name='ttimeoutlen', abbreviation='ttm', + short_desc=N_("time out time for key codes in milliseconds"), type='number', scope={'global'}, vi_def=true, redraw={'ui_option'}, @@ -2601,6 +2929,7 @@ return { }, { full_name='ttyfast', abbreviation='tf', + short_desc=N_("No description"), type='bool', scope={'global'}, no_mkrc=true, vi_def=true, @@ -2609,6 +2938,7 @@ return { }, { full_name='undodir', abbreviation='udir', + short_desc=N_("where to store undo files"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, secure=true, @@ -2619,6 +2949,7 @@ return { }, { full_name='undofile', abbreviation='udf', + short_desc=N_("save undo information in a file"), type='bool', scope={'buffer'}, vi_def=true, vim=true, @@ -2627,6 +2958,7 @@ return { }, { full_name='undolevels', abbreviation='ul', + short_desc=N_("maximum number of changes that can be undone"), type='number', scope={'global', 'buffer'}, vi_def=true, varname='p_ul', @@ -2634,6 +2966,7 @@ return { }, { full_name='undoreload', abbreviation='ur', + short_desc=N_("max nr of lines to save for undo on a buffer reload"), type='number', scope={'global'}, vi_def=true, varname='p_ur', @@ -2641,6 +2974,7 @@ return { }, { full_name='updatecount', abbreviation='uc', + short_desc=N_("after this many characters flush swap file"), type='number', scope={'global'}, vi_def=true, varname='p_uc', @@ -2648,6 +2982,7 @@ return { }, { full_name='updatetime', abbreviation='ut', + short_desc=N_("after this many milliseconds flush swap file"), type='number', scope={'global'}, vi_def=true, varname='p_ut', @@ -2655,6 +2990,7 @@ return { }, { full_name='verbose', abbreviation='vbs', + short_desc=N_("give informative messages"), type='number', scope={'global'}, vi_def=true, varname='p_verbose', @@ -2662,6 +2998,7 @@ return { }, { full_name='verbosefile', abbreviation='vfile', + short_desc=N_("file to write messages in"), type='string', scope={'global'}, secure=true, vi_def=true, @@ -2671,6 +3008,7 @@ return { }, { full_name='viewdir', abbreviation='vdir', + short_desc=N_("directory where to store files with :mkview"), type='string', scope={'global'}, secure=true, vi_def=true, @@ -2680,6 +3018,7 @@ return { }, { full_name='viewoptions', abbreviation='vop', + short_desc=N_("specifies what to save for :mkview"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vi_def=true, @@ -2689,15 +3028,18 @@ return { { -- Alias for "shada". full_name='viminfo', abbreviation='vi', + short_desc=N_("Alias for shada"), type='string', scope={'global'}, nodefault=true, }, { -- Alias for "shadafile". full_name='viminfofile', abbreviation='vif', + short_desc=N_("Alias for shadafile instead"), type='string', scope={'global'}, nodefault=true, }, { full_name='virtualedit', abbreviation='ve', + short_desc=N_("when to use virtual editing"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vi_def=true, @@ -2708,6 +3050,7 @@ return { }, { full_name='visualbell', abbreviation='vb', + short_desc=N_("use visual bell instead of beeping"), type='bool', scope={'global'}, vi_def=true, varname='p_vb', @@ -2715,6 +3058,7 @@ return { }, { full_name='warn', + short_desc=N_("for shell command when buffer was changed"), type='bool', scope={'global'}, vi_def=true, varname='p_warn', @@ -2722,6 +3066,7 @@ return { }, { full_name='whichwrap', abbreviation='ww', + short_desc=N_("allow specified keys to cross line boundaries"), type='string', list='flagscomma', scope={'global'}, vim=true, varname='p_ww', @@ -2729,6 +3074,7 @@ return { }, { full_name='wildchar', abbreviation='wc', + short_desc=N_("command-line character for wildcard expansion"), type='number', scope={'global'}, vim=true, varname='p_wc', @@ -2736,6 +3082,7 @@ return { }, { full_name='wildcharm', abbreviation='wcm', + short_desc=N_("like 'wildchar' but also works when mapped"), type='number', scope={'global'}, vi_def=true, varname='p_wcm', @@ -2743,6 +3090,7 @@ return { }, { full_name='wildignore', abbreviation='wig', + short_desc=N_("files matching these patterns are not completed"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vi_def=true, @@ -2751,6 +3099,7 @@ return { }, { full_name='wildignorecase', abbreviation='wic', + short_desc=N_("ignore case when completing file names"), type='bool', scope={'global'}, vi_def=true, varname='p_wic', @@ -2758,6 +3107,7 @@ return { }, { full_name='wildmenu', abbreviation='wmnu', + short_desc=N_("use menu for command line completion"), type='bool', scope={'global'}, vim=true, varname='p_wmnu', @@ -2765,6 +3115,7 @@ return { }, { full_name='wildmode', abbreviation='wim', + short_desc=N_("mode for 'wildchar' command-line expansion"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vim=true, @@ -2773,6 +3124,7 @@ return { }, { full_name='wildoptions', abbreviation='wop', + short_desc=N_("specifies how command line completion is done"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vim=true, @@ -2781,6 +3133,7 @@ return { }, { full_name='winaltkeys', abbreviation='wak', + short_desc=N_("when the windows system handles ALT keys"), type='string', scope={'global'}, vi_def=true, varname='p_wak', @@ -2788,6 +3141,7 @@ return { }, { full_name='winblend', abbreviation='winbl', + short_desc=N_("Controls transparency level for floating windows"), type='number', scope={'window'}, vi_def=true, redraw={'current_window'}, @@ -2795,6 +3149,7 @@ return { }, { full_name='winhighlight', abbreviation='winhl', + short_desc=N_("Setup window-local highlights"); type='string', scope={'window'}, vi_def=true, alloced=true, @@ -2803,6 +3158,7 @@ return { }, { full_name='window', abbreviation='wi', + short_desc=N_("nr of lines to scroll for CTRL-F and CTRL-B"), type='number', scope={'global'}, vi_def=true, varname='p_window', @@ -2810,6 +3166,7 @@ return { }, { full_name='winheight', abbreviation='wh', + short_desc=N_("minimum number of lines for the current window"), type='number', scope={'global'}, vi_def=true, varname='p_wh', @@ -2817,6 +3174,7 @@ return { }, { full_name='winfixheight', abbreviation='wfh', + short_desc=N_("keep window height when opening/closing windows"), type='bool', scope={'window'}, vi_def=true, redraw={'statuslines'}, @@ -2824,6 +3182,7 @@ return { }, { full_name='winfixwidth', abbreviation='wfw', + short_desc=N_("keep window width when opening/closing windows"), type='bool', scope={'window'}, vi_def=true, redraw={'statuslines'}, @@ -2831,6 +3190,7 @@ return { }, { full_name='winminheight', abbreviation='wmh', + short_desc=N_("minimum number of lines for any window"), type='number', scope={'global'}, vi_def=true, varname='p_wmh', @@ -2838,6 +3198,7 @@ return { }, { full_name='winminwidth', abbreviation='wmw', + short_desc=N_("minimal number of columns for any window"), type='number', scope={'global'}, vi_def=true, varname='p_wmw', @@ -2845,6 +3206,7 @@ return { }, { full_name='winwidth', abbreviation='wiw', + short_desc=N_("minimal number of columns for current window"), type='number', scope={'global'}, vi_def=true, varname='p_wiw', @@ -2852,6 +3214,7 @@ return { }, { full_name='wrap', + short_desc=N_("lines wrap and continue on the next line"), type='bool', scope={'window'}, vi_def=true, redraw={'current_window'}, @@ -2859,6 +3222,7 @@ return { }, { full_name='wrapmargin', abbreviation='wm', + short_desc=N_("chars from the right where wrapping starts"), type='number', scope={'buffer'}, vi_def=true, varname='p_wm', @@ -2866,6 +3230,7 @@ return { }, { full_name='wrapscan', abbreviation='ws', + short_desc=N_("searches wrap around the end of the file"), type='bool', scope={'global'}, vi_def=true, varname='p_ws', @@ -2873,6 +3238,7 @@ return { }, { full_name='write', + short_desc=N_("to a file is allowed"), type='bool', scope={'global'}, vi_def=true, varname='p_write', @@ -2880,6 +3246,7 @@ return { }, { full_name='writeany', abbreviation='wa', + short_desc=N_("write to file with no need for \"!\" override"), type='bool', scope={'global'}, vi_def=true, varname='p_wa', @@ -2887,6 +3254,7 @@ return { }, { full_name='writebackup', abbreviation='wb', + short_desc=N_("make a backup before overwriting a file"), type='bool', scope={'global'}, vi_def=true, vim=true, @@ -2895,6 +3263,7 @@ return { }, { full_name='writedelay', abbreviation='wd', + short_desc=N_("delay this many msec for each char (for debug)"), type='number', scope={'global'}, vi_def=true, varname='p_wd', diff --git a/src/nvim/os/dl.c b/src/nvim/os/dl.c index 2783411574..8483d316f3 100644 --- a/src/nvim/os/dl.c +++ b/src/nvim/os/dl.c @@ -20,8 +20,8 @@ typedef void (*gen_fn)(void); typedef const char *(*str_str_fn)(const char *str); typedef int (*str_int_fn)(const char *str); -typedef const char *(*int_str_fn)(int64_t i); -typedef int (*int_int_fn)(int64_t i); +typedef const char *(*int_str_fn)(int i); +typedef int (*int_int_fn)(int i); /// os_libcall - call a function in a dynamic loadable library /// @@ -41,7 +41,7 @@ typedef int (*int_int_fn)(int64_t i); bool os_libcall(const char *libname, const char *funcname, const char *argv, - int64_t argi, + int argi, char **str_out, int *int_out) { diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c index 082ad58223..879266e3d4 100644 --- a/src/nvim/os/env.c +++ b/src/nvim/os/env.c @@ -1176,7 +1176,9 @@ bool os_setenv_append_path(const char *fname) temp[0] = NUL; } else { xstrlcpy(temp, path, newlen); - xstrlcat(temp, ENV_SEPSTR, newlen); + if (ENV_SEPCHAR != path[pathlen - 1]) { + xstrlcat(temp, ENV_SEPSTR, newlen); + } } xstrlcat(temp, os_buf, newlen); os_setenv("PATH", temp, 1); diff --git a/src/nvim/os/fs.c b/src/nvim/os/fs.c index 873b611151..a3bef3389c 100644 --- a/src/nvim/os/fs.c +++ b/src/nvim/os/fs.c @@ -743,7 +743,9 @@ static int os_stat(const char *name, uv_stat_t *statbuf) } uv_fs_t request; int result = uv_fs_stat(&fs_loop, &request, name, NULL); - *statbuf = request.statbuf; + if (result == kLibuvSuccess) { + *statbuf = request.statbuf; + } uv_fs_req_cleanup(&request); return result; } @@ -1009,6 +1011,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)); return os_stat(path, &(file_info->stat)) == kLibuvSuccess; } @@ -1020,14 +1023,17 @@ 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)); if (path == NULL) { return false; } uv_fs_t request; - int result = uv_fs_lstat(&fs_loop, &request, path, NULL); - file_info->stat = request.statbuf; + bool ok = uv_fs_lstat(&fs_loop, &request, path, NULL) == kLibuvSuccess; + if (ok) { + file_info->stat = request.statbuf; + } uv_fs_req_cleanup(&request); - return (result == kLibuvSuccess); + return ok; } /// Get the file information for a given file descriptor @@ -1039,10 +1045,16 @@ bool os_fileinfo_fd(int file_descriptor, FileInfo *file_info) FUNC_ATTR_NONNULL_ALL { uv_fs_t request; - int result = uv_fs_fstat(&fs_loop, &request, file_descriptor, NULL); - file_info->stat = request.statbuf; + memset(file_info, 0, sizeof(*file_info)); + bool ok = uv_fs_fstat(&fs_loop, + &request, + file_descriptor, + NULL) == kLibuvSuccess; + if (ok) { + file_info->stat = request.statbuf; + } uv_fs_req_cleanup(&request); - return (result == kLibuvSuccess); + return ok; } /// Compare the inodes of two FileInfos diff --git a/src/nvim/os/lang.c b/src/nvim/os/lang.c index fe2d7986bf..603191a0ff 100644 --- a/src/nvim/os/lang.c +++ b/src/nvim/os/lang.c @@ -43,14 +43,20 @@ void lang_init(void) } } + char buf[50] = { 0 }; + bool set_lang; if (lang_region) { - os_setenv("LANG", lang_region, true); + set_lang = true; + xstrlcpy(buf, lang_region, sizeof(buf)); } else { - char buf[20] = { 0 }; - if (CFStringGetCString(cf_lang_region, buf, 20, - kCFStringEncodingUTF8)) { - os_setenv("LANG", buf, true); + set_lang = CFStringGetCString(cf_lang_region, buf, 40, + kCFStringEncodingUTF8); + } + if (set_lang) { + if (strcasestr(buf, "utf-8") == NULL) { + xstrlcat(buf, ".UTF-8", sizeof(buf)); } + os_setenv("LANG", buf, true); } CFRelease(cf_lang_region); # ifdef HAVE_LOCALE_H diff --git a/src/nvim/os/os_defs.h b/src/nvim/os/os_defs.h index c8ac4218f6..1b7859b0d3 100644 --- a/src/nvim/os/os_defs.h +++ b/src/nvim/os/os_defs.h @@ -16,10 +16,11 @@ #define BASENAMELEN (NAME_MAX - 5) // Use the system path length if it makes sense. -#if defined(PATH_MAX) && (PATH_MAX > 1024) +# define DEFAULT_MAXPATHL 4096 +#if defined(PATH_MAX) && (PATH_MAX > DEFAULT_MAXPATHL) # define MAXPATHL PATH_MAX #else -# define MAXPATHL 1024 +# define MAXPATHL DEFAULT_MAXPATHL #endif // Command-processing buffer. Use large buffers for all platforms. @@ -44,4 +45,55 @@ # define os_strtok strtok_r #endif +// stat macros +#ifndef S_ISDIR +# ifdef S_IFDIR +# define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) +# else +# define S_ISDIR(m) 0 +# endif +#endif +#ifndef S_ISREG +# ifdef S_IFREG +# define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) +# else +# define S_ISREG(m) 0 +# endif +#endif +#ifndef S_ISBLK +# ifdef S_IFBLK +# define S_ISBLK(m) (((m) & S_IFMT) == S_IFBLK) +# else +# define S_ISBLK(m) 0 +# endif +#endif +#ifndef S_ISSOCK +# ifdef S_IFSOCK +# define S_ISSOCK(m) (((m) & S_IFMT) == S_IFSOCK) +# else +# define S_ISSOCK(m) 0 +# endif +#endif +#ifndef S_ISFIFO +# ifdef S_IFIFO +# define S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO) +# else +# define S_ISFIFO(m) 0 +# endif +#endif +#ifndef S_ISCHR +# ifdef S_IFCHR +# define S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR) +# else +# define S_ISCHR(m) 0 +# endif +#endif +#ifndef S_ISLNK +# ifdef S_IFLNK +# define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK) +# else +# define S_ISLNK(m) 0 +# endif +#endif + #endif // NVIM_OS_OS_DEFS_H diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c index 6294d5e4e2..b5d890bf52 100644 --- a/src/nvim/os/shell.c +++ b/src/nvim/os/shell.c @@ -150,11 +150,11 @@ int os_expand_wildcards(int num_pat, char_u **pat, int *num_file, return FAIL; } - // Don't allow the use of backticks in secure and restricted mode. - if (secure || restricted) { + // Don't allow the use of backticks in secure. + if (secure) { for (i = 0; i < num_pat; i++) { if (vim_strchr(pat[i], '`') != NULL - && (check_restricted() || check_secure())) { + && (check_secure())) { return FAIL; } } diff --git a/src/nvim/os/signal.c b/src/nvim/os/signal.c index bfe230b521..bc774b8ebc 100644 --- a/src/nvim/os/signal.c +++ b/src/nvim/os/signal.c @@ -161,8 +161,8 @@ static void deadly_signal(int signum) WLOG("got signal %d (%s)", signum, signal_name(signum)); - snprintf((char *)IObuff, sizeof(IObuff), "Vim: Caught deadly signal '%s'\n", - signal_name(signum)); + snprintf((char *)IObuff, sizeof(IObuff), "Vim: Caught deadly signal '%s'\r\n", + signal_name(signum)); // Preserve files and exit. preserve_exit(); diff --git a/src/nvim/os/time.c b/src/nvim/os/time.c index 346e40e02e..4b6533cd0c 100644 --- a/src/nvim/os/time.c +++ b/src/nvim/os/time.c @@ -56,6 +56,8 @@ uint64_t os_now(void) /// Sleeps for `ms` milliseconds. /// +/// @see uv_sleep() (libuv v1.34.0) +/// /// @param ms Number of milliseconds to sleep /// @param ignoreinput If true, only SIGINT (CTRL-C) can interrupt. void os_delay(uint64_t ms, bool ignoreinput) @@ -72,6 +74,8 @@ void os_delay(uint64_t ms, bool ignoreinput) /// Sleeps for `us` microseconds. /// +/// @see uv_sleep() (libuv v1.34.0) +/// /// @param us Number of microseconds to sleep. /// @param ignoreinput If true, ignore all input (including SIGINT/CTRL-C). /// If false, waiting is aborted on any input. @@ -172,10 +176,11 @@ char *os_ctime_r(const time_t *restrict clock, char *restrict result, struct tm *clock_local_ptr = os_localtime_r(clock, &clock_local); // MSVC returns NULL for an invalid value of seconds. if (clock_local_ptr == NULL) { - snprintf(result, result_len, "%s\n", _("(Invalid)")); + xstrlcpy(result, _("(Invalid)"), result_len); } else { - strftime(result, result_len, "%a %b %d %H:%M:%S %Y\n", clock_local_ptr); + strftime(result, result_len, _("%a %b %d %H:%M:%S %Y"), clock_local_ptr); } + xstrlcat(result, "\n", result_len); return result; } diff --git a/src/nvim/os/win_defs.h b/src/nvim/os/win_defs.h index 356094baa1..66d72de08d 100644 --- a/src/nvim/os/win_defs.h +++ b/src/nvim/os/win_defs.h @@ -74,28 +74,6 @@ typedef int mode_t; # define O_NOFOLLOW 0 #endif -#if !defined(S_ISDIR) && defined(S_IFDIR) -# define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) -#endif -#if !defined(S_ISREG) && defined(S_IFREG) -# define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) -#endif -#if !defined(S_ISLNK) && defined(S_IFLNK) -# define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK) -#endif -#if !defined(S_ISBLK) && defined(S_IFBLK) -# define S_ISBLK(m) (((m) & S_IFMT) == S_IFBLK) -#endif -#if !defined(S_ISSOCK) && defined(S_IFSOCK) -# define S_ISSOCK(m) (((m) & S_IFMT) == S_IFSOCK) -#endif -#if !defined(S_ISFIFO) && defined(S_IFIFO) -# define S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO) -#endif -#if !defined(S_ISCHR) && defined(S_IFCHR) -# define S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR) -#endif - #ifndef STDIN_FILENO # define STDIN_FILENO 0 #endif diff --git a/src/nvim/po/check.vim b/src/nvim/po/check.vim index 650c6155e2..d55d4cfa4d 100644 --- a/src/nvim/po/check.vim +++ b/src/nvim/po/check.vim @@ -25,6 +25,7 @@ func! GetMline() " remove '%', not used for formatting. let idline = substitute(idline, "'%'", '', 'g') + let idline = substitute(idline, "%%", '', 'g') " remove '%' used for plural forms. let idline = substitute(idline, '\\nPlural-Forms: .\+;\\n', '', '') diff --git a/src/nvim/po/uk.po b/src/nvim/po/uk.po index 19ea8e897a..f2179f4f1b 100644 --- a/src/nvim/po/uk.po +++ b/src/nvim/po/uk.po @@ -14,8 +14,8 @@ msgid "" msgstr "" "Project-Id-Version: vim 7.4\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-12-18 22:42+0200\n" -"PO-Revision-Date: 2018-12-18 22:42+0200\n" +"POT-Creation-Date: 2020-08-23 18:45+0300\n" +"PO-Revision-Date: 2020-08-23 20:19+0300\n" "Last-Translator: ะะฝะฐัะพะปัะน ะกะฐั
ะฝัะบ <sakhnik@gmail.com>\n" "Language-Team: Ukrainian\n" "Language: uk\n" @@ -95,6 +95,12 @@ msgid "" "E89: No write since last change for buffer %<PRId64> (add ! to override)" msgstr "E89: ะััะตั %<PRId64> ะผะฐั ะทะผัะฝะธ (! ัะพะฑ ะฝะต ะทะฒะฐะถะฐัะธ)" +msgid "E37: No write since last change (add ! to override)" +msgstr "E37: ะะผัะฝะธ ะฝะต ะฑัะปะพ ะทะฐะฟะธัะฐะฝะพ (! ัะพะฑ ะฝะต ะทะฒะฐะถะฐัะธ)" + +msgid "E37: No write since last change" +msgstr "E37: ะะต ะทะฐะฟะธัะฐะฝะพ ะฟััะปั ะพััะฐะฝะฝัั
ะทะผัะฝ" + msgid "W14: Warning: List of file names overflow" msgstr "W14: ะะฑะตัะตะถะฝะพ: ะกะฟะธัะพะบ ะฝะฐะทะฒ ัะฐะนะปัะฒ ะฟะตัะตะฟะพะฒะฝะตะฝะพ" @@ -123,9 +129,6 @@ msgstr " [ะะผัะฝะตะฝะพ]" msgid "[Not edited]" msgstr "[ะะต ัะตะดะฐะณะพะฒะฐะฝะพ]" -msgid "[New file]" -msgstr "[ะะพะฒะธะน ัะฐะนะป]" - msgid "[Read errors]" msgstr "[ะะพะผะธะปะบะธ ัะธัะฐะฝะฝั]" @@ -168,23 +171,17 @@ msgstr "ะะฝะธะทั" msgid "Top" msgstr "ะะณะพัั" -msgid "[Scratch]" -msgstr "[ะ ะฝัะปั]" +msgid "E382: Cannot write, 'buftype' option is set" +msgstr "E382: ะะต ะผะพะถั ะทะฐะฟะธัะฐัะธ, ะฒะบะฐะทะฐะฝะฐ ะพะฟััั 'buftype'" -msgid "" -"\n" -"--- Signs ---" -msgstr "" -"\n" -"--- ะะพะทะฝะฐัะบะธ ---" +msgid "[Prompt]" +msgstr "[ะะฐะฟะธั]" -#, c-format -msgid "Signs for %s:" -msgstr "ะะพะทะฝะฐัะบะธ ะดะปั %s:" +msgid "[Scratch]" +msgstr "[ะ ะฝัะปั]" -#, c-format -msgid " line=%<PRId64> id=%d name=%s" -msgstr " ััะดะพะบ=%<PRId64> id=%d ะฝะฐะทะฒะฐ=%s" +msgid "W10: Warning: Changing a readonly file" +msgstr "W10: ะะฐััะตัะตะถะตะฝะฝั: ะะผัะฝัััััั ัะฐะนะป ะฟัะธะทะฝะฐัะตะฝะธะน ะปะธัะต ะดะปั ัะธัะฐะฝะฝั" msgid "can only be opened in headless mode" msgstr "ะผะพะถะฝะฐ ะฒัะดะบัะธัะธ ััะปัะบะธ ะฑะตะท ัะฝัะตััะตะนัั ะบะพัะธัััะฒะฐัะฐ" @@ -198,6 +195,9 @@ msgstr "ะะตะผะพะถะปะธะฒะพ ะฝะฐะดััะปะฐัะธ ะดะฐะฝั ั ะทะฐะบัะธัะธะน ะฟะพั msgid "Can't send raw data to rpc channel" msgstr "ะะตะผะพะถะปะธะฒะพ ะฝะฐะดััะปะฐัะธ ะดะฐะฝั ั ะบะฐะฝะฐะป ะทะฐะฒะดะฐะฝะฝั" +msgid "E474: Failed to convert list to msgpack string buffer" +msgstr "E474: ะะต ะฒะดะฐะปะพัั ะฟะตัะตัะฒะพัะธัะธ ัะฟะธัะพะบ ั ัะตะบััะพะฒะธะน ะฑััะตั msgpack" + msgid "E545: Missing colon" msgstr "E545: ะัะพะฟััะตะฝะพ ะดะฒะพะบัะฐะฟะบั" @@ -233,9 +233,6 @@ msgstr "E816: ะะต ะฒะดะฐะปะพัั ะฟัะพัะธัะฐัะธ ัะตะทัะปััะฐั patch" msgid "E98: Cannot read diff output" msgstr "E98: ะะต ะฒะดะฐะปะพัั ะฟัะพัะธัะฐัะธ ัะตะทัะปััะฐั diff" -msgid "E959: Invalid diff format." -msgstr "E959: ะะตะฟัะฐะฒะธะปัะฝะธะน ัะพัะผะฐั ะฟะพััะฒะฝัะฝะฝั." - msgid "E99: Current buffer is not in diff mode" msgstr "E99: ะฆะตะน ะฑััะตั ะฝะต ะฒ ัะตะถะธะผั ะฟะพััะฒะฝัะฝะฝั" @@ -264,6 +261,81 @@ msgstr "E787: ะััะตั ะฝะตัะฟะพะดัะฒะฐะฝะพ ะทะผัะฝะธะฒัั" msgid "E104: Escape not allowed in digraph" msgstr "E104: ะฃ ะดะธะณัะฐัะฐั
ะฝะต ะผะพะถะต ะผัััะธัะธัั escape" +msgid "Custom" +msgstr "ะะปะฐัะฝะต" + +msgid "Latin supplement" +msgstr "ะะฐัะธะฝะธัั ะดะพะฟะพะฒะฝะตะฝะฝั" + +msgid "Greek and Coptic" +msgstr "ะัะตััะบะฐ ั ะบะพะฟัััะบะฐ" + +msgid "Cyrillic" +msgstr "ะะธัะธะปะธัั" + +msgid "Hebrew" +msgstr "ะะฒัะธั" + +msgid "Arabic" +msgstr "ะัะฐะฑััะบะฐ" + +msgid "Latin extended" +msgstr "ะะฐัะธะฝะธัั ัะพะทัะธัะตะฝะฐ" + +msgid "Greek extended" +msgstr "ะัะตััะบะฐ ัะพะทัะธัะตะฝะฐ" + +msgid "Punctuation" +msgstr "ะัะฝะบััะฐััั" + +msgid "Super- and subscripts" +msgstr "ะะฐะด- ั ะฟัะดะฟะธัะธ" + +msgid "Currency" +msgstr "ะะฐะปััะฐ" + +msgid "Other" +msgstr "ะะฝัะต" + +msgid "Roman numbers" +msgstr "ะ ะธะผััะบั ัะธััะธ" + +msgid "Arrows" +msgstr "ะกัััะปะบะธ" + +msgid "Mathematical operators" +msgstr "ะะฐัะตะผะฐัะธัะฝั ะพะฟะตัะฐัะพัะธ" + +msgid "Technical" +msgstr "ะขะตั
ะฝััะฝะต" + +msgid "Box drawing" +msgstr "ะะฐะปัะฒะฐะฝะฝั ะฟััะผะพะบััะฝะธะบัะฒ" + +msgid "Block elements" +msgstr "ะงะฐััะธะฝะธ ะฑะปะพะบัะฒ" + +msgid "Geometric shapes" +msgstr "ะะตะพะผะตััะธัะฝั ััะณััะธ" + +msgid "Symbols" +msgstr "ะกะธะผะฒะพะปะธ" + +msgid "Dingbats" +msgstr "ะััะฝะธัั" + +msgid "CJK symbols and punctuation" +msgstr "ะกะธะผะฒะพะปะธ ั ะฟัะฝะบััะฐััั CJK" + +msgid "Hiragana" +msgstr "ะฅััะฐะณะฐะฝะฐ" + +msgid "Katakana" +msgstr "ะะฐัะฐะบะฐะฝะฐ" + +msgid "Bopomofo" +msgstr "ะะพะฟะพะผะพัะพ" + msgid "E544: Keymap file not found" msgstr "E544: ะะต ะทะฝะฐะนะดะตะฝะพ ัะฐะนะป ัะพะทะบะปะฐะดะบะธ" @@ -379,55 +451,18 @@ msgstr "E18: ะะตะพััะบัะฒะฐะฝั ัะธะผะฒะพะปะธ ั :let" msgid "E111: Missing ']'" msgstr "E111: ะัะฐะบัั ']'" -#, c-format -msgid "E686: Argument of %s must be a List" -msgstr "E686: ะัะณัะผะตะฝั ั %s ะผะฐั ะฑััะธ ัะฟะธัะบะพะผ" - -#, c-format -msgid "E712: Argument of %s must be a List or Dictionary" -msgstr "E712: ะัะณัะผะตะฝั ั %s ะผะฐั ะฑััะธ ัะฟะธัะบะพะผ ัะธ ัะปะพะฒะฝะธะบะพะผ" - -msgid "E714: List required" -msgstr "E714: ะะพัััะฑะตะฝ ัะฟะธัะพะบ" - -msgid "E715: Dictionary required" -msgstr "E715: ะะพัััะฑะตะฝ ัะปะพะฒะฝะธะบ" - -msgid "E928: String required" -msgstr "E928: ะะพัััะฑะตะฝ String" - -#, c-format -msgid "E118: Too many arguments for function: %s" -msgstr "E118: ะะฐะฑะฐะณะฐัะพ ะฐัะณัะผะตะฝััะฒ ะดะปั ััะฝะบััั: %s" - -#, c-format -msgid "E716: Key not present in Dictionary: %s" -msgstr "E716: ะะตะผะฐั ัะฐะบะพะณะพ ะบะปััะฐ ั ัะปะพะฒะฝะธะบั: %s" - -#, c-format -msgid "E122: Function %s already exists, add ! to replace it" -msgstr "E122: ะคัะฝะบััั %s ัะถะต ััะฝัั, ! ัะพะฑ ะทะฐะผัะฝะธัะธ" - -msgid "E717: Dictionary entry already exists" -msgstr "E717: ะะฐะฟะธั ั ัะปะพะฒะฝะธะบั ะฒะถะต ััะฝัั" - -msgid "E718: Funcref required" -msgstr "E718: ะขัะตะฑะฐ ะฟะพัะธะปะฐะฝะฝั ะฝะฐ ััะฝะบััั" - msgid "E719: Cannot use [:] with a Dictionary" msgstr "E719: ะะต ะผะพะถะฝะฐ ะฒะธะบะพัะธััะฐัะธ [:] ะทั ัะปะพะฒะฝะธะบะพะผ" #, c-format -msgid "E130: Unknown function: %s" -msgstr "E130: ะะตะฒัะดะพะผะฐ ััะฝะบััั: %s" - -#, c-format msgid "E461: Illegal variable name: %s" msgstr "E461: ะะตะฟัะธะฟัััะธะผะฐ ะฝะฐะทะฒะฐ ะทะผัะฝะฝะพั: %s" -#, c-format -msgid "E46: Cannot change read-only variable \"%.*s\"" -msgstr "E46: ะะผัะฝะฝะฐ ััะปัะบะธ ะดะปั ัะธัะฐะฝะฝั: ยซ%.*sยป" +msgid "E995: Cannot modify existing variable" +msgstr "E995: ะะตะผะพะถะปะธะฒะพ ะทะผัะฝะธัะธ ะฝะฐัะฒะฝั ะทะผัะฝะฝั" + +msgid "E957: Invalid window number" +msgstr "E957: ะะตะบะพัะตะบัะฝะธะน ะฝะพะผะตั ะฒัะบะฝะฐ" #, c-format msgid "E734: Wrong variable type for %s=" @@ -439,6 +474,19 @@ msgid "" msgstr "" "E5700: ะะธัะฐะท ัะท 'spellsuggest' ะผะฐั ะฟะพะฒะตััะฐัะธ ัะฟะธัะพะบ ัะท ััะฒะฝะพ ะดะฒะพะผะฐ ะตะปะตะผะตะฝัะฐะผะธ" +msgid "E991: cannot use =<< here" +msgstr "E991: ะขัั ะฝะต ะผะพะถะฝะฐ ะฒะธะบะพัะธััะฐัะธ =<<" + +msgid "E221: Marker cannot start with lower case letter" +msgstr "E221: ะะพะทะฝะฐัะบะฐ ะฝะต ะผะพะถะต ะฟะพัะธะฝะฐัะธัั ัะท ะผะฐะปะพั ะปััะตัะธ" + +msgid "E172: Missing marker" +msgstr "E172: ะัะฐะบัั ะฟะพะทะฝะฐัะบะธ" + +#, c-format +msgid "E990: Missing end marker '%s'" +msgstr "E990: ะัะฐะบัั ะฟะพะทะฝะฐัะบะธ ะบัะฝัั ยซ%sยป" + msgid "E687: Less targets than List items" msgstr "E687: ะฆัะปะตะน ะผะตะฝัะต, ะฝัะถ ะตะปะตะผะตะฝััะฒ ัะฟะธัะบั" @@ -452,6 +500,15 @@ msgstr "ะััะณะฐ ; ั ัะฟะธัะบั ะทะผัะฝะฝะธั
" msgid "E738: Can't list variables for %s" msgstr "E738: ะะต ะผะพะถะฝะฐ ะฟะตัะตัะฐั
ัะฒะฐัะธ ะทะผัะฝะฝั ั %s" +msgid "E996: Cannot lock an environment variable" +msgstr "E996: ะะตะผะพะถะปะธะฒะพ ะทะฐะฑะปะพะบัะฒะฐัะธ ะทะผัะฝะฝั ะพัะพัะตะฝะฝั" + +msgid "E996: Cannot lock an option" +msgstr "E996: ะะตะผะพะถะปะธะฒะพ ะทะฐะฑะปะพะบัะฒะฐัะธ ะพะฟััั" + +msgid "E996: Cannot lock a register" +msgstr "E996: ะะตะผะพะถะปะธะฒะพ ะทะฐะฑะปะพะบัะฒะฐัะธ ัะตะณัััั" + #, c-format msgid "E121: Undefined variable: %.*s" msgstr "E121: ะะตะฒะธะทะฝะฐัะตะฝะฐ ะทะผัะฝะฝะฐ: %.*s" @@ -468,20 +525,22 @@ msgstr "E713: ะะตะผะพะถะปะธะฒะพ ะฒะถะธัะธ ะฟะพัะพะถะฝัะน ะบะปัั ะฟััะปั msgid "E709: [:] requires a List value" msgstr "E709: [:] ะฒะธะผะฐะณะฐั ัะฟะธัะพะบ" +msgid "E996: Cannot lock a range" +msgstr "E996: ะะตะผะพะถะปะธะฒะพ ะทะฐะฑะปะพะบัะฒะฐัะธ ะดัะฐะฟะฐะทะพะฝ" + msgid "E710: List value has more items than target" msgstr "E710: ะกะฟะธัะพะบ ะผะฐั ะฑัะปััะต ะตะปะตะผะตะฝััะฒ, ะฝัะถ ััะปั" msgid "E711: List value has not enough items" msgstr "E711: ะกะฟะธัะพะบ ะผะฐั ะฝะตะดะพััะฐัะฝัะพ ะตะปะตะผะตะฝััะฒ" +msgid "E996: Cannot lock a list or dict" +msgstr "E996: ะะตะผะพะถะปะธะฒะพ ะทะฐะฑะปะพะบัะฒะฐัะธ ัะฟะธัะพะบ ัะธ ัะปะพะฒะฝะธะบ" + msgid "E690: Missing \"in\" after :for" msgstr "E690: ะัะพะฟััะตะฝะพ ยซinยป ะฟััะปั :for" #, c-format -msgid "E107: Missing parentheses: %s" -msgstr "E107: ะัะพะฟััะตะฝะพ ะดัะถะบะธ: %s" - -#, c-format msgid "E108: No such variable: \"%s\"" msgstr "E108: ะะผัะฝะฝะพั ะฝะตะผะฐั: ยซ%sยป" @@ -563,68 +622,6 @@ msgstr "E722: ะัะฐะบัั ะบะพะผะธ ั ัะปะพะฒะฝะธะบั: %s" msgid "E723: Missing end of Dictionary '}': %s" msgstr "E723: ะะตะผะฐั ะบัะฝััะฒะบะธ ัะปะพะฒะฝะธะบะฐ '}': %s" -#, c-format -msgid "E125: Illegal argument: %s" -msgstr "E125: ะะตะดะพะทะฒะพะปะตะฝะธะน ะฐัะณัะผะตะฝั: %s" - -#, c-format -msgid "E853: Duplicate argument name: %s" -msgstr "E853: ะะฐะทะฒะฐ ะฐัะณัะผะตะฝัั ะฟะพะฒัะพััััััั: %s" - -#, c-format -msgid "E740: Too many arguments for function %s" -msgstr "E740: ะะฐะฑะฐะณะฐัะพ ะฐัะณัะผะตะฝััะฒ ะดะปั ััะฝะบััั %s" - -#, c-format -msgid "E116: Invalid arguments for function %s" -msgstr "E116: ะะตะฟัะฐะฒะธะปัะฝั ะฐัะณัะผะตะฝัะธ ััะฝะบััั %s" - -#, c-format -msgid "E117: Unknown function: %s" -msgstr "E117: ะะตะฒัะดะพะผะฐ ััะฝะบััั: %s" - -#, c-format -msgid "E933: Function was deleted: %s" -msgstr "E933: ะคัะฝะบััั ะฑัะปะพ ะฒะธะดะฐะปะตะฝะพ: %s" - -#, c-format -msgid "E119: Not enough arguments for function: %s" -msgstr "E119: ะะฐะผะฐะปะพ ะฐัะณัะผะตะฝััะฒ ะดะปั ััะฝะบััั %s" - -#, c-format -msgid "E120: Using <SID> not in a script context: %s" -msgstr "E120: <SID> ะฒะธะบะพัะธััะพะฒัััััั ะฝะต ั ะบะพะฝัะตะบััั ัะบัะธะฟัั: %s" - -#, c-format -msgid "E725: Calling dict function without Dictionary: %s" -msgstr "E725: ะะธะบะปะธะบ dict-ััะฝะบััั ะฑะตะท ัะปะพะฒะฝะธะบะฐ: %s" - -#, c-format -msgid "Error converting the call result: %s" -msgstr "ะะต ะฒะดะฐะปะพัั ะฟะตัะตัะฒะพัะธัะธ ัะตะทัะปััะฐั ะฒะธะบะปะธะบั: %s" - -msgid "add() argument" -msgstr "ะฐัะณัะผะตะฝั add()" - -msgid "E699: Too many arguments" -msgstr "E699: ะะฐะฑะฐะณะฐัะพ ะฐัะณัะผะตะฝััะฒ" - -#, c-format -msgid "Invalid channel stream \"%s\"" -msgstr "ะะตะบะพัะตะบัะฝะธะน ะฟะพััะบ ะทะฐะฒะดะฐะฝะฝั ยซ%sยป" - -msgid "E785: complete() can only be used in Insert mode" -msgstr "E785: complete() ะผะพะถะฝะฐ ะฒะถะธะฒะฐัะธ ััะปัะบะธ ะฒ ัะตะถะธะผั ะฒััะฐะฒะปัะฝะฝั" - -msgid "&Ok" -msgstr "&O:ะะฐัะฐะทะด" - -msgid "dictwatcheradd() argument" -msgstr "ะฐัะณัะผะตะฝั dictwatcheradd()" - -msgid "extend() argument" -msgstr "ะฐัะณัะผะตะฝั extend()" - msgid "map() argument" msgstr "ะฐัะณัะผะตะฝั map()" @@ -641,124 +638,13 @@ msgstr "E922: ะพััะบัััััั ัะปะพะฒะฝะธะบ" msgid "E923: Second argument of function() must be a list or a dict" msgstr "E923: ะััะณะธะน ะฐัะณัะผะตะฝั function() ะผะฐั ะฑััะธ ัะฟะธัะบะพะผ ัะธ ัะปะพะฒะฝะธะบะพะผ" -msgid "E5000: Cannot find tab number." -msgstr "E5000: ะะต ะผะพะถะฝะฐ ะทะฝะฐะนัะธ ะฝะพะผะตั ะฒะบะปะฐะดะบะธ." - -msgid "E5001: Higher scope cannot be -1 if lower scope is >= 0." -msgstr "E5001: ะะธัะฐ ะพะฑะปะฐััั ะฝะต ะผะพะถะต ะฑััะธ -1, ัะบัะพ ะฝะธะถัะฐ ะพะฑะปะฐััั >= 0." - -msgid "E5002: Cannot find window number." -msgstr "E5002: ะะตะผะพะถะปะธะฒะพ ะทะฝะฐะนัะธ ะฝะพะผะตั ะฒัะบะฝะฐ." - msgid "E5050: {opts} must be the only argument" msgstr "E5050: {opts} ะผะฐั ะฑััะธ ัะดะธะฝะธะผ ะฐัะณัะผะตะฝัะพะผ" -msgid "called inputrestore() more often than inputsave()" -msgstr "ะะธะบะปะธะบะธ ะดะพ inputrestore() ัะฐััััะต, ะฝัะถ ะดะพ inputsave()" - -msgid "insert() argument" -msgstr "ะฐัะณัะผะตะฝั insert()" - -msgid "E786: Range not allowed" -msgstr "E786: ะะฝัะตัะฒะฐะป ะฝะต ะดะพะทะฒะพะปะตะฝะพ" - -msgid "E474: Failed to convert list to string" -msgstr "E474: ะะต ะฒะดะฐะปะพัั ะฟะตัะตัะฒะพัะธัะธ ัะฟะธัะพะบ ั ัะตะบัั" - -#, c-format -msgid "E474: Failed to parse %.*s" -msgstr "E474: ะะต ะฒะดะฐะปะพัั ัะพะทัะฑัะฐัะธ %.*s" - -msgid "E701: Invalid type for len()" -msgstr "E701: ะะตะบะพัะตะบัะฝะธะน ัะธะฟ ะดะปั len()" - -#, c-format -msgid "E798: ID is reserved for \":match\": %<PRId64>" -msgstr "E798: ID ะทะฐัะตะทะตัะฒะพะฒะฐะฝะพ ะดะปั \":match\": %<PRId64>" - -#, c-format -msgid "E798: ID is reserved for \"match\": %<PRId64>" -msgstr "E798: ID ะทะฐัะตะทะตัะฒะพะฒะฐะฝะพ ะดะปั \"match\": %<PRId64>" - -#, c-format -msgid "msgpackdump() argument, index %i" -msgstr "ะฐัะณัะผะตะฝั msgpackdump(), ัะฝะดะตะบั %i" - -msgid "E5070: Character number must not be less than zero" -msgstr "E5070: ะะพะผะตั ัะธะผะฒะพะปะฐ ะผะฐั ะฑััะธ ะฝะตะฒัะดโัะผะฝะธะผ" - -#, c-format -msgid "E5071: Character number must not be greater than INT_MAX (%i)" -msgstr "E5071: ะะพะผะตั ัะธะผะฒะพะปะฐ ะฝะต ะผะพะถะต ะฑััะธ ะฑัะปััะธะผ, ะฝัะถ INT_MAX (%i)" - -msgid "E726: Stride is zero" -msgstr "E726: ะัะพะบ ะฝัะปัะพะฒะธะน" - -msgid "E727: Start past end" -msgstr "E727: ะะพัะฐัะพะบ ะทะฐ ะบัะฝัะตะผ" - -msgid "<empty>" -msgstr "<ะฝััะพะณะพ>" - -msgid "remove() argument" -msgstr "ะฐัะณัะผะตะฝั remove()" - -msgid "E655: Too many symbolic links (cycle?)" -msgstr "E655: ะะฐะฑะฐะณะฐัะพ ัะธะผะฒะพะปัะฝะธั
ะฟะพัะธะปะฐะฝั (ัะธะบะป?)" - -msgid "reverse() argument" -msgstr "ะฐัะณัะผะตะฝั reverse()" - -#, c-format -msgid "E5010: List item %d of the second argument is not a string" -msgstr "E5010: ะะปะตะผะตะฝั ัะฟะธัะบั %d ะดััะณะพะณะพ ะฐัะณัะผะตะฝัั ะฝะต ัะตะบัั" - -#, c-format -msgid "E927: Invalid action: '%s'" -msgstr "E927: ะะตะฟัะฐะฒะธะปัะฝะฐ ะดัั: ยซ%sยป" - -#, c-format -msgid "E474: List item %d is either not a dictionary or an empty one" -msgstr "E474: ะะปะตะผะตะฝั ัะฟะธัะบั %d ะฐะฑะพ ะฝะต ัะปะพะฒะฝะธะบ ะฐะฑะพ ะฟะพัะพะถะฝัะน" - -#, c-format -msgid "E474: List item %d is missing one of the required keys" -msgstr "E474: ะะปะตะผะตะฝั ัะฟะธัะบั %d ะฝะตะผะฐั ะพะดะฝะพะณะพ ะท ะพะฑะพะฒโัะทะบะพะฒะธั
ะบะปัััะฒ" - -#, c-format -msgid "connection failed: %s" -msgstr "ะทโัะดะฝะฐะฝะฝั ะฝะต ะฒะดะฐะปะพัั: %s" - -msgid "sort() argument" -msgstr "ะฐัะณัะผะตะฝั sort()" - -msgid "uniq() argument" -msgstr "ะฐัะณัะผะตะฝั uniq()" - -msgid "E702: Sort compare function failed" -msgstr "E702: ะะพะผะธะปะบะฐ ั ััะฝะบััั ะฟะพััะฒะฝัะฝะฝั" - -msgid "E882: Uniq compare function failed" -msgstr "E882: ะะพะผะธะปะบะฐ ั ััะฝะบััั ะฟะพััะฒะฝัะฝะฝั uniq" - -#, c-format -msgid "E6100: \"%s\" is not a valid stdpath" -msgstr "E6100: \"%s\" โ ะฝะตะบะพัะตะบัะฝะธะน stdpath" - -msgid "(Invalid)" -msgstr "(ะะตะผะพะถะปะธะฒะพ)" - -#, c-format -msgid "E935: invalid submatch number: %d" -msgstr "E935: ะฝะตะฟัะฐะฒะธะปัะฝะธะน ะฝะพะผะตั ะณััะฟะธ ัะฟัะฒะฟะฐะดัะฝะฝั: %d" - #, c-format msgid "Executing command: \"%s\"" msgstr "ะะธะบะพะฝัััััั ะบะพะผะฐะฝะดะฐ: ยซ%sยป" -msgid "Can only call this function in an unmodified buffer" -msgstr "ะฆั ััะฝะบััั ะผะพะถะฝะฐ ะฒะธะบะปะธะบะฐัะธ ััะปัะบะธ ั ะฝะตะทะผัะฝะตะฝะพะผั ะฑััะตัั" - msgid "E921: Invalid callback argument" msgstr "E921: ะะตะบะพัะตะบัะฝะธะน ะฐัะณัะผะตะฝั ััะฝะบััั ะทะฒะพัะพัะฝัะพะณะพ ะฒะธะบะปะธะบั" @@ -767,21 +653,6 @@ msgid "E80: Error while writing: %s" msgstr "E80: ะะพะผะธะปะบะฐ ะฟัะด ัะฐั ะทะฐะฟะธัั: %s" #, c-format -msgid "E5060: Unknown flag: %s" -msgstr "E5060: ะะตะฒัะดะพะผะธะน ะฟัะฐะฟะพัะตัั: %s" - -msgid "E482: Can't open file with an empty name" -msgstr "E482: ะะต ะฒะดะฐะปะพัั ะฒัะดะบัะธัะธ ัะฐะนะป ะท ะฟะพัะพะถะฝัะผ ัะผโัะผ" - -#, c-format -msgid "E482: Can't open file %s for writing: %s" -msgstr "E482: ะะต ะฒะดะฐะปะพัั ะฒัะดะบัะธัะธ ัะฐะนะป %s ะดะปั ะทะฐะฟะธัั: %s" - -#, c-format -msgid "E80: Error when closing file %s: %s" -msgstr "E80: ะะพะผะธะปะบะฐ ะฟัะธ ะทะฐะบัะธััั ัะฐะนะปั %s: %s" - -#, c-format msgid "E963: setting %s to value with wrong type" msgstr "E963: ะฒััะฐะฝะพะฒะปะตะฝะฝั %s ะดะพ ะทะฝะฐัะตะฝะฝั ะท ะฝะตะฟัะฐะฒะธะปัะฝะธะผ ัะธะฟะพะผ" @@ -799,90 +670,11 @@ msgstr "E704: ะะฐะทะฒะฐ ะทะผัะฝะฝะพั Funcref ะผะฐั ะฟะพัะธะฝะฐัะธัั ะท ะ #, c-format msgid "E705: Variable name conflicts with existing function: %s" -msgstr "E705: ะะฐะทะฒะฐ ะทะผัะฝะฝะพั ัะฟัะฒะฟะฐะดะฐั ะท ััะฝัััะพั ััะฝะบัััั: %s" +msgstr "E705: ะะฐะทะฒะฐ ะทะผัะฝะฝะพั ัะฟัะฒะฟะฐะดะฐั ะท ะฝะฐัะฒะฝะพั ััะฝะบัััั: %s" msgid "E698: variable nested too deep for making a copy" msgstr "E698: ะะผัะฝะฝะฐ ะฒะบะปะฐะดะตะฝะฐ ะทะฐะฝะฐะดัะพ ะณะปะธะฑะพะบะพ ัะพะฑ ะทัะพะฑะธัะธ ัั ะบะพะฟัั" -#, c-format -msgid "E123: Undefined function: %s" -msgstr "E123: ะะตะฒะธะทะฝะฐัะตะฝะฐ ััะฝะบััั: %s" - -#, c-format -msgid "E124: Missing '(': %s" -msgstr "E124: ะัะฐะบัั '(': %s" - -msgid "E862: Cannot use g: here" -msgstr "E862: ะขัั ะฝะต ะผะพะถะฝะฐ ะฒะธะบะพัะธััะฐัะธ g:" - -#, c-format -msgid "E932: Closure function should not be at top level: %s" -msgstr "E932: ะคัะฝะบััั ะทะฐะผะธะบะฐะฝะฝั ะฝะต ะผะพะถะต ะฑััะธ ะฝะฐ ะฒะตัั
ะฝัะพะผั ััะฒะฝั: %s" - -msgid "E126: Missing :endfunction" -msgstr "E126: ะัะฐะบัั :endfunction" - -#, c-format -msgid "W22: Text found after :endfunction: %s" -msgstr "W22: ะขัะฐะฟะธะฒัั ัะตะบัั ะฟััะปั :endfunction: %s" - -#, c-format -msgid "E707: Function name conflicts with variable: %s" -msgstr "E707: ะะฐะทะฒะฐ ััะฝะบััั ัะฟัะฒะฟะฐะดะฐั ะทั ะทะผัะฝะฝะพั: %s" - -#, c-format -msgid "E127: Cannot redefine function %s: It is in use" -msgstr "E127: ะะต ะฒะดะฐะปะพัั ะฟะตัะตะฒะธะทะฝะฐัะธัะธ ััะฝะบััั %s: ะฒะพะฝะฐ ะฒะธะบะพัะธััะพะฒัััััั" - -#, c-format -msgid "E746: Function name does not match script file name: %s" -msgstr "E746: ะะฐะทะฒะฐ ััะฝะบััั ะฝะต ะทะฑัะณะฐััััั ะท ะฝะฐะทะฒะพั ัะฐะนะปั ัะบัะธะฟัั: %s" - -msgid "E129: Function name required" -msgstr "E129: ะะต ะฒะบะฐะทะฐะฝะพ ะฝะฐะทะฒั ััะฝะบััั" - -#, c-format -msgid "E128: Function name must start with a capital or \"s:\": %s" -msgstr "E128: ะะฐะทะฒะฐ ััะฝะบััั ะผะฐั ะฟะพัะธะฝะฐัะธัั ะท ะฒะตะปะธะบะพั ะปััะตัะธ ะฐะฑะพ \"s:\": %s" - -#, c-format -msgid "E884: Function name cannot contain a colon: %s" -msgstr "E884: ะะฐะทะฒะฐ ััะฝะบััั ะฝะต ะผะพะถะต ะผัััะธัะธ ะดะฒะพะบัะฐะฟะบั: %s" - -#, c-format -msgid "E131: Cannot delete function %s: It is in use" -msgstr "E131: ะะต ะฒะดะฐะปะพัั ะทะฝะธัะธัะธ ััะฝะบััั %s: ะะพะฝะฐ ะฒะธะบะพัะธััะพะฒัััััั" - -#, c-format -msgid "Cannot delete function %s: It is being used internally" -msgstr "ะะต ะฒะดะฐะปะพัั ะทะฝะธัะธัะธ ััะฝะบััั %s: ะะพะฝะฐ ะฒะธะบะพัะธััะพะฒัััััั" - -msgid "E132: Function call depth is higher than 'maxfuncdepth'" -msgstr "E132: ะะปะธะฑะธะฝะฐ ะฒะธะบะปะธะบัะฒ ััะฝะบััั ะฟะตัะตะฒะธััั 'maxfuncdepth'" - -#, c-format -msgid "calling %s" -msgstr "ะฒะธะบะปะธะบะฐััััั %s" - -#, c-format -msgid "%s aborted" -msgstr "%s ะฟัะธะฟะธะฝะตะฝะพ" - -#, c-format -msgid "%s returning #%<PRId64>" -msgstr "%s ะฟะพะฒะตััะฐั #%<PRId64>" - -#, c-format -msgid "%s returning %s" -msgstr "%s ะฟะพะฒะตััะฐั %s" - -#, c-format -msgid "continuing in %s" -msgstr "ะฟัะพะดะพะฒะถะตะฝะฝั ะฒ %s" - -msgid "E133: :return not inside a function" -msgstr "E133: :return ะฟะพะทะฐ ะผะตะถะฐะผะธ ััะฝะบััั" - msgid "" "\n" "\tLast set from " @@ -1155,6 +947,179 @@ msgid "E684: list index out of range: %<PRId64>" msgstr "E684: ะะฝะดะตะบั ัะฟะธัะบั ะฟะพะทะฐ ะผะตะถะฐะผะธ: %<PRId64>" #, c-format +msgid "E686: Argument of %s must be a List" +msgstr "E686: ะัะณัะผะตะฝั ั %s ะผะฐั ะฑััะธ ัะฟะธัะบะพะผ" + +msgid "E928: String required" +msgstr "E928: ะะพัััะฑะตะฝ String" + +#, c-format +msgid "Error converting the call result: %s" +msgstr "ะะต ะฒะดะฐะปะพัั ะฟะตัะตัะฒะพัะธัะธ ัะตะทัะปััะฐั ะฒะธะบะปะธะบั: %s" + +msgid "add() argument" +msgstr "ะฐัะณัะผะตะฝั add()" + +#, c-format +msgid "E158: Invalid buffer name: %s" +msgstr "E158: ะะตะบะพัะตะบัะฝะฐ ะฝะฐะทะฒะฐ ะฑััะตัะฐ: %s" + +#, c-format +msgid "Invalid channel stream \"%s\"" +msgstr "ะะตะบะพัะตะบัะฝะธะน ะฟะพััะบ ะทะฐะฒะดะฐะฝะฝั ยซ%sยป" + +msgid "E785: complete() can only be used in Insert mode" +msgstr "E785: complete() ะผะพะถะฝะฐ ะฒะถะธะฒะฐัะธ ััะปัะบะธ ะฒ ัะตะถะธะผั ะฒััะฐะฒะปัะฝะฝั" + +msgid "&Ok" +msgstr "&O:ะะฐัะฐะทะด" + +msgid "Context stack is empty" +msgstr "ะกัะตะบ ะบะพะฝัะตะบััั ะฟะพัะพะถะฝัะน" + +msgid "dictwatcheradd() argument" +msgstr "ะฐัะณัะผะตะฝั dictwatcheradd()" + +msgid "E900: maxdepth must be non-negative number" +msgstr "E900: maxdepth ะผะฐั ะฑััะธ ะฝะตะฒัะดโัะผะฝะธะผ ัะธัะปะพะผ" + +msgid "flatten() argument" +msgstr "ะฐัะณัะผะตะฝั flatten()" + +msgid "extend() argument" +msgstr "ะฐัะณัะผะตะฝั extend()" + +msgid "E5000: Cannot find tab number." +msgstr "E5000: ะะต ะผะพะถะฝะฐ ะทะฝะฐะนัะธ ะฝะพะผะตั ะฒะบะปะฐะดะบะธ." + +msgid "E5001: Higher scope cannot be -1 if lower scope is >= 0." +msgstr "E5001: ะะธัะฐ ะพะฑะปะฐััั ะฝะต ะผะพะถะต ะฑััะธ -1, ัะบัะพ ะฝะธะถัะฐ ะพะฑะปะฐััั >= 0." + +msgid "E5002: Cannot find window number." +msgstr "E5002: ะะตะผะพะถะปะธะฒะพ ะทะฝะฐะนัะธ ะฝะพะผะตั ะฒัะบะฝะฐ." + +msgid "called inputrestore() more often than inputsave()" +msgstr "ะะธะบะปะธะบะธ ะดะพ inputrestore() ัะฐััััะต, ะฝัะถ ะดะพ inputsave()" + +msgid "insert() argument" +msgstr "ะฐัะณัะผะตะฝั insert()" + +msgid "E786: Range not allowed" +msgstr "E786: ะะฝัะตัะฒะฐะป ะฝะต ะดะพะทะฒะพะปะตะฝะพ" + +msgid "E474: Failed to convert list to string" +msgstr "E474: ะะต ะฒะดะฐะปะพัั ะฟะตัะตัะฒะพัะธัะธ ัะฟะธัะพะบ ั ัะตะบัั" + +#, c-format +msgid "E474: Failed to parse %.*s" +msgstr "E474: ะะต ะฒะดะฐะปะพัั ัะพะทัะฑัะฐัะธ %.*s" + +msgid "E701: Invalid type for len()" +msgstr "E701: ะะตะบะพัะตะบัะฝะธะน ัะธะฟ ะดะปั len()" + +#, c-format +msgid "E798: ID is reserved for \":match\": %<PRId64>" +msgstr "E798: ID ะทะฐัะตะทะตัะฒะพะฒะฐะฝะพ ะดะปั \":match\": %<PRId64>" + +#, c-format +msgid "E798: ID is reserved for \"match\": %<PRId64>" +msgstr "E798: ID ะทะฐัะตะทะตัะฒะพะฒะฐะฝะพ ะดะปั \"match\": %<PRId64>" + +#, c-format +msgid "msgpackdump() argument, index %i" +msgstr "ะฐัะณัะผะตะฝั msgpackdump(), ัะฝะดะตะบั %i" + +msgid "E5070: Character number must not be less than zero" +msgstr "E5070: ะะพะผะตั ัะธะผะฒะพะปะฐ ะผะฐั ะฑััะธ ะฝะตะฒัะดโัะผะฝะธะผ" + +#, c-format +msgid "E5071: Character number must not be greater than INT_MAX (%i)" +msgstr "E5071: ะะพะผะตั ัะธะผะฒะพะปะฐ ะฝะต ะผะพะถะต ะฑััะธ ะฑัะปััะธะผ, ะฝัะถ INT_MAX (%i)" + +msgid "E726: Stride is zero" +msgstr "E726: ะัะพะบ ะฝัะปัะพะฒะธะน" + +msgid "E727: Start past end" +msgstr "E727: ะะพัะฐัะพะบ ะทะฐ ะบัะฝัะตะผ" + +msgid "<empty>" +msgstr "<ะฝััะพะณะพ>" + +msgid "remove() argument" +msgstr "ะฐัะณัะผะตะฝั remove()" + +msgid "E655: Too many symbolic links (cycle?)" +msgstr "E655: ะะฐะฑะฐะณะฐัะพ ัะธะผะฒะพะปัะฝะธั
ะฟะพัะธะปะฐะฝั (ัะธะบะป?)" + +msgid "reverse() argument" +msgstr "ะฐัะณัะผะตะฝั reverse()" + +#, c-format +msgid "E5010: List item %d of the second argument is not a string" +msgstr "E5010: ะะปะตะผะตะฝั ัะฟะธัะบั %d ะดััะณะพะณะพ ะฐัะณัะผะตะฝัั ะฝะต ัะตะบัั" + +#, c-format +msgid "E927: Invalid action: '%s'" +msgstr "E927: ะะตะฟัะฐะฒะธะปัะฝะฐ ะดัั: ยซ%sยป" + +#, c-format +msgid "E474: List item %d is either not a dictionary or an empty one" +msgstr "E474: ะะปะตะผะตะฝั ัะฟะธัะบั %d ะฐะฑะพ ะฝะต ัะปะพะฒะฝะธะบ ะฐะฑะพ ะฟะพัะพะถะฝัะน" + +#, c-format +msgid "E474: List item %d is missing one of the required keys" +msgstr "E474: ะะปะตะผะตะฝั ัะฟะธัะบั %d ะฝะตะผะฐั ะพะดะฝะพะณะพ ะท ะพะฑะพะฒโัะทะบะพะฒะธั
ะบะปัััะฒ" + +#, c-format +msgid "E962: Invalid action: '%s'" +msgstr "E962: ะะตะฟัะฐะฒะธะปัะฝะฐ ะดัั: ยซ%sยป" + +#, c-format +msgid "connection failed: %s" +msgstr "ะทโัะดะฝะฐะฝะฝั ะฝะต ะฒะดะฐะปะพัั: %s" + +msgid "sort() argument" +msgstr "ะฐัะณัะผะตะฝั sort()" + +msgid "uniq() argument" +msgstr "ะฐัะณัะผะตะฝั uniq()" + +msgid "E702: Sort compare function failed" +msgstr "E702: ะะพะผะธะปะบะฐ ั ััะฝะบััั ะฟะพััะฒะฝัะฝะฝั" + +msgid "E882: Uniq compare function failed" +msgstr "E882: ะะพะผะธะปะบะฐ ั ััะฝะบััั ะฟะพััะฒะฝัะฝะฝั uniq" + +#, c-format +msgid "E6100: \"%s\" is not a valid stdpath" +msgstr "E6100: \"%s\" โ ะฝะตะบะพัะตะบัะฝะธะน stdpath" + +msgid "(Invalid)" +msgstr "(ะะตะผะพะถะปะธะฒะพ)" + +#, c-format +msgid "E935: invalid submatch number: %d" +msgstr "E935: ะฝะตะฟัะฐะฒะธะปัะฝะธะน ะฝะพะผะตั ะณััะฟะธ ัะฟัะฒะฟะฐะดัะฝะฝั: %d" + +msgid "Can only call this function in an unmodified buffer" +msgstr "ะฆั ััะฝะบััั ะผะพะถะฝะฐ ะฒะธะบะปะธะบะฐัะธ ััะปัะบะธ ั ะฝะตะทะผัะฝะตะฝะพะผั ะฑััะตัั" + +#, c-format +msgid "E5060: Unknown flag: %s" +msgstr "E5060: ะะตะฒัะดะพะผะธะน ะฟัะฐะฟะพัะตัั: %s" + +msgid "E482: Can't open file with an empty name" +msgstr "E482: ะะต ะฒะดะฐะปะพัั ะฒัะดะบัะธัะธ ัะฐะนะป ะท ะฟะพัะพะถะฝัะผ ัะผโัะผ" + +#, c-format +msgid "E482: Can't open file %s for writing: %s" +msgstr "E482: ะะต ะฒะดะฐะปะพัั ะฒัะดะบัะธัะธ ัะฐะนะป %s ะดะปั ะทะฐะฟะธัั: %s" + +#, c-format +msgid "E80: Error when closing file %s: %s" +msgstr "E80: ะะพะผะธะปะบะฐ ะฟัะธ ะทะฐะบัะธััั ัะฐะนะปั %s: %s" + +#, c-format msgid "E5142: Failed to open file %s: %s" msgstr "E5142: ะะต ะฒะดะฐะปะพัั ะฒัะดะบัะธัะธ ัะฐะนะป %s: %s" @@ -1199,6 +1164,9 @@ msgstr "E745: ะััะบัััััั Number ัะธ String, ััะฐะฟะธะฒัั List" msgid "E728: Expected a Number or a String, Dictionary found" msgstr "E728: ะััะบัััััั Number ัะธ String, ััะฐะฟะธะฒัั Dictionary" +msgid "E5299: Expected a Number or a String, Boolean found" +msgstr "E5299: ะััะบัััััั Number ัะธ String, ััะฐะฟะธะฒัั Boolean" + msgid "E5300: Expected a Number or a String" msgstr "E5300: ะััะบัััััั Number ัะธ String" @@ -1235,12 +1203,151 @@ msgstr "E893: List ะฒะถะธัะพ ัะบ Float" msgid "E894: Using a Dictionary as a Float" msgstr "E894: Dictionary ะฒะถะธัะพ ัะบ Float" +msgid "E362: Using a boolean value as a Float" +msgstr "E362: ะะธะบะพัะธััะฐะฝะพ ะปะพะณััะฝะต ะทะฝะฐัะตะฝะฝั ัะบ Float" + msgid "E907: Using a special value as a Float" msgstr "E907: ะะธะบะพัะธััะฐะฝะพ ัะฟะตััะฐะปัะฝะต ะทะฝะฐัะตะฝะฝั ัะบ Float" msgid "E808: Number or Float required" msgstr "E808: ะขัะตะฑะฐ ะฒะบะฐะทะฐัะธ Number ัะธ Float" +#, c-format +msgid "E122: Function %s already exists, add ! to replace it" +msgstr "E122: ะคัะฝะบััั %s ัะถะต ััะฝัั, ! ัะพะฑ ะทะฐะผัะฝะธัะธ" + +msgid "E717: Dictionary entry already exists" +msgstr "E717: ะะฐะฟะธั ั ัะปะพะฒะฝะธะบั ะฒะถะต ััะฝัั" + +msgid "E718: Funcref required" +msgstr "E718: ะขัะตะฑะฐ ะฟะพัะธะปะฐะฝะฝั ะฝะฐ ััะฝะบััั" + +#, c-format +msgid "E130: Unknown function: %s" +msgstr "E130: ะะตะฒัะดะพะผะฐ ััะฝะบััั: %s" + +#, c-format +msgid "E125: Illegal argument: %s" +msgstr "E125: ะะตะดะพะทะฒะพะปะตะฝะธะน ะฐัะณัะผะตะฝั: %s" + +#, c-format +msgid "E853: Duplicate argument name: %s" +msgstr "E853: ะะฐะทะฒะฐ ะฐัะณัะผะตะฝัั ะฟะพะฒัะพััััััั: %s" + +#, c-format +msgid "E740: Too many arguments for function %s" +msgstr "E740: ะะฐะฑะฐะณะฐัะพ ะฐัะณัะผะตะฝััะฒ ะดะปั ััะฝะบััั %s" + +#, c-format +msgid "E116: Invalid arguments for function %s" +msgstr "E116: ะะตะฟัะฐะฒะธะปัะฝั ะฐัะณัะผะตะฝัะธ ััะฝะบััั %s" + +msgid "E132: Function call depth is higher than 'maxfuncdepth'" +msgstr "E132: ะะปะธะฑะธะฝะฐ ะฒะธะบะปะธะบัะฒ ััะฝะบััั ะฟะตัะตะฒะธััั 'maxfuncdepth'" + +#, c-format +msgid "calling %s" +msgstr "ะฒะธะบะปะธะบะฐััััั %s" + +#, c-format +msgid "%s aborted" +msgstr "%s ะฟัะธะฟะธะฝะตะฝะพ" + +#, c-format +msgid "%s returning #%<PRId64>" +msgstr "%s ะฟะพะฒะตััะฐั #%<PRId64>" + +#, c-format +msgid "%s returning %s" +msgstr "%s ะฟะพะฒะตััะฐั %s" + +#, c-format +msgid "continuing in %s" +msgstr "ะฟัะพะดะพะฒะถะตะฝะฝั ะฒ %s" + +msgid "E699: Too many arguments" +msgstr "E699: ะะฐะฑะฐะณะฐัะพ ะฐัะณัะผะตะฝััะฒ" + +#, c-format +msgid "E117: Unknown function: %s" +msgstr "E117: ะะตะฒัะดะพะผะฐ ััะฝะบััั: %s" + +#, c-format +msgid "E933: Function was deleted: %s" +msgstr "E933: ะคัะฝะบััั ะฑัะปะพ ะฒะธะดะฐะปะตะฝะพ: %s" + +#, c-format +msgid "E119: Not enough arguments for function: %s" +msgstr "E119: ะะฐะผะฐะปะพ ะฐัะณัะผะตะฝััะฒ ะดะปั ััะฝะบััั %s" + +#, c-format +msgid "E120: Using <SID> not in a script context: %s" +msgstr "E120: <SID> ะฒะธะบะพัะธััะพะฒัััััั ะฝะต ั ะบะพะฝัะตะบััั ัะบัะธะฟัั: %s" + +#, c-format +msgid "E725: Calling dict function without Dictionary: %s" +msgstr "E725: ะะธะบะปะธะบ dict-ััะฝะบััั ะฑะตะท ัะปะพะฒะฝะธะบะฐ: %s" + +msgid "E129: Function name required" +msgstr "E129: ะะต ะฒะบะฐะทะฐะฝะพ ะฝะฐะทะฒั ััะฝะบััั" + +#, c-format +msgid "E128: Function name must start with a capital or \"s:\": %s" +msgstr "E128: ะะฐะทะฒะฐ ััะฝะบััั ะผะฐั ะฟะพัะธะฝะฐัะธัั ะท ะฒะตะปะธะบะพั ะปััะตัะธ ะฐะฑะพ \"s:\": %s" + +#, c-format +msgid "E884: Function name cannot contain a colon: %s" +msgstr "E884: ะะฐะทะฒะฐ ััะฝะบััั ะฝะต ะผะพะถะต ะผัััะธัะธ ะดะฒะพะบัะฐะฟะบั: %s" + +#, c-format +msgid "E123: Undefined function: %s" +msgstr "E123: ะะตะฒะธะทะฝะฐัะตะฝะฐ ััะฝะบััั: %s" + +#, c-format +msgid "E124: Missing '(': %s" +msgstr "E124: ะัะฐะบัั '(': %s" + +msgid "E862: Cannot use g: here" +msgstr "E862: ะขัั ะฝะต ะผะพะถะฝะฐ ะฒะธะบะพัะธััะฐัะธ g:" + +#, c-format +msgid "E932: Closure function should not be at top level: %s" +msgstr "E932: ะคัะฝะบััั ะทะฐะผะธะบะฐะฝะฝั ะฝะต ะผะพะถะต ะฑััะธ ะฝะฐ ะฒะตัั
ะฝัะพะผั ััะฒะฝั: %s" + +msgid "E126: Missing :endfunction" +msgstr "E126: ะัะฐะบัั :endfunction" + +#, c-format +msgid "W22: Text found after :endfunction: %s" +msgstr "W22: ะขัะฐะฟะธะฒัั ัะตะบัั ะฟััะปั :endfunction: %s" + +#, c-format +msgid "E707: Function name conflicts with variable: %s" +msgstr "E707: ะะฐะทะฒะฐ ััะฝะบััั ัะฟัะฒะฟะฐะดะฐั ะทั ะทะผัะฝะฝะพั: %s" + +#, c-format +msgid "E127: Cannot redefine function %s: It is in use" +msgstr "E127: ะะต ะฒะดะฐะปะพัั ะฟะตัะตะฒะธะทะฝะฐัะธัะธ ััะฝะบััั %s: ะฒะพะฝะฐ ะฒะธะบะพัะธััะพะฒัััััั" + +#, c-format +msgid "E746: Function name does not match script file name: %s" +msgstr "E746: ะะฐะทะฒะฐ ััะฝะบััั ะฝะต ะทะฑัะณะฐััััั ะท ะฝะฐะทะฒะพั ัะฐะนะปั ัะบัะธะฟัั: %s" + +#, c-format +msgid "E131: Cannot delete function %s: It is in use" +msgstr "E131: ะะต ะฒะดะฐะปะพัั ะทะฝะธัะธัะธ ััะฝะบััั %s: ะะพะฝะฐ ะฒะธะบะพัะธััะพะฒัััััั" + +#, c-format +msgid "Cannot delete function %s: It is being used internally" +msgstr "ะะต ะฒะดะฐะปะพัั ะทะฝะธัะธัะธ ััะฝะบััั %s: ะะพะฝะฐ ะฒะธะบะพัะธััะพะฒัััััั" + +msgid "E133: :return not inside a function" +msgstr "E133: :return ะฟะพะทะฐ ะผะตะถะฐะผะธ ััะฝะบััั" + +#, c-format +msgid "E107: Missing parentheses: %s" +msgstr "E107: ะัะพะฟััะตะฝะพ ะดัะถะบะธ: %s" + msgid "tcp address must be host:port" msgstr "ะฐะดัะตัะฐ tcp ะผะฐั ะฑััะธ ะฒัะทะพะป:ะฟะพัั" @@ -1306,7 +1413,7 @@ msgstr "E140: ะะธะบะพัะธััะฐะนัะต ! ะดะปั ะทะฐะฟะธัั ัะฐััะธะฝะธ ะฑั #, c-format msgid "Overwrite existing file \"%s\"?" -msgstr "ะะตัะตะฟะธัะฐัะธ ััะฝัััะธะน ัะฐะนะป ยซ%sยป?" +msgstr "ะะตัะตะฟะธัะฐัะธ ะฝะฐัะฒะฝะธะน ัะฐะนะป ยซ%sยป?" #, c-format msgid "Swap file \"%s\" exists, overwrite anyway?" @@ -1352,8 +1459,9 @@ msgstr "E143: ะะฒัะพะบะพะผะฐะฝะดะธ ะฝะตัะฟะพะดัะฒะฐะฝะพ ะทะฝะธัะธะปะธ ะฝะพะ msgid "E144: non-numeric argument to :z" msgstr "E144: ะฝะตัะธัะปะพะฒะธะน ะฐัะณัะผะตะฝั ะดะปั :z" -msgid "E145: Shell commands not allowed in restricted mode" -msgstr "E145: ะฃ ะพะฑะผะตะถะตะฝะพะผั ัะตะถะธะผั ะฝะต ะดะพะทะฒะพะปะตะฝั ะบะพะผะฐะฝะดะธ ะพะฑะพะปะพะฝะบะธ" +msgid "" +"E145: Shell commands and some functionality not allowed in restricted mode" +msgstr "E145: ะฃ ะพะฑะผะตะถะตะฝะพะผั ัะตะถะธะผั ะฝะต ะดะพะทะฒะพะปะตะฝั ะบะพะผะฐะฝะดะธ ะพะฑะพะปะพะฝะบะธ ั ะดะตัะบะฐ ััะฝะบัะพะฝะฐะปัะฝัััั" msgid "E146: Regular expressions can't be delimited by letters" msgstr "E146: ะ ะตะณัะปััะฝั ะฒะธัะฐะทะธ ะฝะต ะผะพะถะฝะฐ ัะพะทะดัะปััะธ ะปััะตัะฐะผะธ" @@ -1439,48 +1547,6 @@ msgstr "E154: ะะพะฒัะพัะตะฝะฝั ะผััะบะธ ยซ%sยป ั ัะฐะนะปั %s/%s" msgid "E150: Not a directory: %s" msgstr "E150: ะะต ั ะบะฐัะฐะปะพะณะพะผ: %s" -#, c-format -msgid "E160: Unknown sign command: %s" -msgstr "E160: ะะตะฒัะดะพะผะฐ ะบะพะผะฐะฝะดะฐ ะฝะฐะดะฟะธัั: %s" - -msgid "E156: Missing sign name" -msgstr "E156: ะัะพะฟััะตะฝะพ ะฝะฐะทะฒั ะฝะฐะดะฟะธัั" - -msgid "E612: Too many signs defined" -msgstr "E612: ะะธะทะฝะฐัะตะฝะพ ะทะฐะฑะฐะณะฐัะพ ะฝะฐะดะฟะธััะฒ" - -#, c-format -msgid "E239: Invalid sign text: %s" -msgstr "E239: ะะตะบะพัะตะบัะฝะธะน ะฝะฐะดะฟะธั: %s" - -#, c-format -msgid "E155: Unknown sign: %s" -msgstr "E155: ะะตะฒัะดะพะผะธะน ะฝะฐะดะฟะธั: %s" - -msgid "E159: Missing sign number" -msgstr "E159: ะัะพะฟััะตะฝะพ ะฝะพะผะตั ะฝะฐะดะฟะธัั" - -#, c-format -msgid "E158: Invalid buffer name: %s" -msgstr "E158: ะะตะบะพัะตะบัะฝะฐ ะฝะฐะทะฒะฐ ะฑััะตัะฐ: %s" - -msgid "E934: Cannot jump to a buffer that does not have a name" -msgstr "E934: ะะต ะผะพะถะฝะฐ ะฟะตัะตะนัะธ ะดะพ ะฑััะตัะฐ ะฑะตะท ะฝะฐะทะฒะธ" - -#, c-format -msgid "E157: Invalid sign ID: %<PRId64>" -msgstr "E157: ะะตะฟัะฐะฒะธะปัะฝะธะน ID ะฝะฐะดะฟะธัั: %<PRId64>" - -#, c-format -msgid "E885: Not possible to change sign %s" -msgstr "E885: ะะตะผะพะถะปะธะฒะพ ะทะผัะฝะธัะธ ะทะฝะฐะบ %s" - -msgid " (not supported)" -msgstr " (ะฝะต ะฟัะดััะธะผัััััั)" - -msgid "[Deleted]" -msgstr "[ะะฝะธัะตะฝะพ]" - msgid "No old files" msgstr "ะะพะดะฝะพะณะพ ััะฐัะพะณะพ ัะฐะนะปั" @@ -1545,6 +1611,9 @@ msgstr "E164: ะฆะต ะฒะถะต ะฝะฐะนะฟะตััะธะน ัะฐะนะป" msgid "E165: Cannot go beyond last file" msgstr "E165: ะฆะต ะฒะถะต ะพััะฐะฝะฝัะน ัะฐะนะป" +msgid "E610: No argument to delete" +msgstr "E610: ะะตะผะฐั ะฐัะณัะผะตะฝััะฒ ะดะปั ะทะฝะธัะตะฝะฝั" + #, c-format msgid "E666: compiler not supported: %s" msgstr "E666: ะะพะผะฟัะปััะพั ะฝะต ะฟัะดััะธะผัััััั: %s" @@ -1562,6 +1631,10 @@ msgid "not found in '%s': \"%s\"" msgstr "ะฝะต ะทะฝะฐะนะดะตะฝะพ ะฒ '%s': ยซ%sยป" #, c-format +msgid ":source error parsing command %s" +msgstr ":source ะฟะพะผะธะปะบะฐ ัะพะทะฑะพัั ะบะพะผะฐะฝะดะธ %s" + +#, c-format msgid "Cannot source a directory: \"%s\"" msgstr "ะะต ะฒะดะฐะปะพัั ะฟัะพัะธัะฐัะธ ะบะฐัะฐะปะพะณ: ยซ%sยป" @@ -1607,6 +1680,9 @@ msgstr "Lua" msgid "API client (channel id %<PRIu64>)" msgstr "ะะปััะฝั API (ะบะฐะฝะฐะป ยซ%<PRIu64>ยป)" +msgid "anonymous :source" +msgstr "ะฐะฝะพะฝัะผะฝะธะน :source" + msgid "W15: Warning: Wrong line separator, ^M may be missing" msgstr "W15: ะะฐััะตัะตะถะตะฝะฝั: ะะตะฟัะฐะฒะธะปัะฝะธะน ัะพะทะดัะปัะฝะธะบ ััะดะบัะฒ, ะผะพะถะปะธะฒะพ, ะฑัะฐะบัั ^M" @@ -1652,6 +1728,9 @@ msgstr "E464: ะะตะพะดะฝะพะทะฝะฐัะฝะธะน ะฒะถะธัะพะบ ะบะพะผะฐะฝะดะธ ะบะพัะธัั msgid "E492: Not an editor command" msgstr "E492: ะฆะต ะฝะต ะบะพะผะฐะฝะดะฐ ัะตะดะฐะบัะพัะฐ" +msgid "E981: Command not allowed in restricted mode" +msgstr "E981: ะะพะผะฐะฝะดั ะฝะต ะดะพะทะฒะพะปะตะฝะพ ั ะพะฑะผะตะถะตะฝะพะผั ัะตะถะธะผั" + msgid "E493: Backwards range given" msgstr "E493: ะะฝัะตัะฒะฐะป ะทะฐะดะฐะฝะพ ะฝะฐะฒะธะฒะพััั" @@ -1661,6 +1740,9 @@ msgstr "ะะฝัะตัะฒะฐะป ะทะฐะดะฐะฝะพ ะฝะฐะฒะธะฒะพััั, ัะพะฑ ะฟะพะผัะฝััะ msgid "E494: Use w or w>>" msgstr "E494: ะกะฟัะพะฑัะนัะต w ะฐะฑะพ w>>" +msgid "E943: Command table needs to be updated, run 'make'" +msgstr "E943: ะะพัััะฑะฝะพ ะฟะพะฝะพะฒะธัะธ ัะฐะฑะปะธัั ะบะพะผะฐะฝะด, ะทะฐะฟัััััั 'make'" + msgid "E319: The command is not available in this version" msgstr "E319: ะะธะฑะฐััะต, ัััั ะบะพะผะฐะฝะดะธ ะฝะตะผะฐั ั ััะน ะฒะตัััั" @@ -1678,15 +1760,16 @@ msgstr "E173: ะะฐะปะธัะธะปะพัั ะฒัะดัะตะดะฐะณัะฒะฐัะธ ัะต ะพะดะธะฝ ัะฐ msgid "E173: %<PRId64> more files to edit" msgstr "E173: ะะฐะปะธัะธะปะพัั %<PRId64> ะฝะต ัะตะดะฐะณะพะฒะฐะฝะธั
ัะฐะนะปัะฒ" -msgid "E174: Command already exists: add ! to replace it" -msgstr "E174: ะะพะผะฐะฝะดะฐ ะฒะถะต ััะฝัั, ! ัะพะฑ ะทะฐะผัะฝะธัะธ ัั" +#, c-format +msgid "E174: Command already exists: add ! to replace it: %s" +msgstr "E174: ะะพะผะฐะฝะดะฐ ะฒะถะต ััะฝัั, ! ัะพะฑ ะทะฐะผัะฝะธัะธ ัั: %s" msgid "" "\n" -" Name Args Address Complete Definition" +" Name Args Address Complete Definition" msgstr "" "\n" -" ะะฐะทะฒะฐ ะัะณ. ะะดัะตัะฐ ะะพะฟะพะฒะฝะตะฝะฝั ะะธะทะฝะฐัะตะฝะฝั" +" ะะฐะทะฒะฐ ะัะณ. ะะดัะตัะฐ ะะพะฟะพะฒะฝะตะฝะฝั ะะธะทะฝะฐัะตะฝะฝั" msgid "No user-defined commands found" msgstr "ะะต ะทะฝะฐะนะดะตะฝะพ ะบะพะผะฐะฝะด ะบะพัะธัััะฒะฐัะฐ" @@ -1805,6 +1888,9 @@ msgstr "E498: ะะตะผะฐั ะฝะฐะทะฒะธ ัะฐะนะปั :source ะดะปั ะทะฐะผัะฝะธ ยซ<sf msgid "E842: no line number to use for \"<slnum>\"" msgstr "E842: ะฝะตะผะฐั ะฝะพะผะตัะฐ ััะดะบะฐ, ัะพะฑ ะฒะธะบะพัะธััะฐัะธ ะท ยซ<sfile>ยป" +msgid "E961: no line number to use for \"<sflnum>\"" +msgstr "E961: ะฝะตะผะฐั ะฝะพะผะตัะฐ ััะดะบะฐ, ัะพะฑ ะฒะธะบะพัะธััะฐัะธ ะท ยซ<sflnum>ยป" + #, c-format msgid "E499: Empty file name for '%' or '#', only works with \":p:h\"" msgstr "E499: ะะฐะทะฒะฐ ัะฐะนะปั ะดะปั '%' ัะธ '#' ะฟะพัะพะถะฝั, ะฟัะฐััั ะปะธัะต ะท ยซ:p:hยป" @@ -1962,9 +2048,6 @@ msgstr " ัะธะฟ ัะฐะนะปั\n" msgid "'history' option is zero" msgstr "ะะฟััั 'history' ะฟะพัะพะถะฝั" -msgid "E198: cmd_pchar beyond the command length" -msgstr "E198: cmd_pchar ะฟะพะทะฐ ะผะตะถะฐะผะธ ะบะพะผะฐะฝะดะธ" - msgid "E199: Active window or buffer deleted" msgstr "E199: ะะบัะธะฒะฝะต ะฒัะบะฝะพ ะฐะฑะพ ะฑััะตั ะฑัะปะพ ะทะฝะธัะตะฝะพ" @@ -2007,9 +2090,6 @@ msgstr "ะบะฐัะฐะปะพะณ" msgid "is not a file" msgstr "ะฝะต ัะฐะนะป" -msgid "[New File]" -msgstr "[ะะพะฒะธะน ัะฐะนะป]" - msgid "[New DIRECTORY]" msgstr "[ะะพะฒะธะน ะบะฐัะฐะปะพะณ]" @@ -2043,6 +2123,9 @@ msgstr "[ัะฟะตั. ัะธะผะฒะพะปัะฝะธะน]" msgid "[CR missing]" msgstr "[ะัะฐะบัั CR]" +msgid "[long lines split]" +msgstr "[ะดะพะฒะณั ััะดะบะธ ัะพะทะฑะธัะพ]" + msgid "[NOT converted]" msgstr "[ะะ ะบะพะฝะฒะตััะพะฒะฐะฝะพ]" @@ -2069,6 +2152,12 @@ msgstr "ะะพะฝะฒะตััะฐััั ะท 'charconvert' ะฝะต ะฒะดะฐะปะฐัั" msgid "can't read output of 'charconvert'" msgstr "ะฝะต ะฒะดะฐะปะพัั ะฟัะพัะธัะฐัะธ ะฒะธะฒัะด 'charconvert'" +msgid "[New File]" +msgstr "[ะะพะฒะธะน ัะฐะนะป]" + +msgid "[New]" +msgstr "[ะะพะฒะธะน]" + msgid "E676: No matching autocommands for acwrite buffer" msgstr "E676: ะะตะผะฐั ะฒัะดะฟะพะฒัะดะฝะธั
ะฐะฒัะพะบะพะผะฐะฝะด" @@ -2087,15 +2176,6 @@ msgstr "ะปะธัะต ะดะปั ัะธัะฐะฝะฝั (! ัะพะฑ ะฝะต ะทะฒะฐะถะฐัะธ)" msgid "E506: Can't write to backup file (add ! to override)" msgstr "E506: ะะต ะฒะดะฐะปะพัั ะทะฐะฟะธัะฐัะธ ัะตะทะตัะฒะฝะธะน ัะฐะนะป (! ัะพะฑ ะฝะต ะทะฒะฐะถะฐัะธ)" -#, c-format -msgid "E507: Close error for backup file (add ! to override): %s" -msgstr "E507: ะะพะผะธะปะบะฐ ะทะฐะบัะธััั ัะตะทะตัะฒะฝะพะณะพ ัะฐะนะปั (! ัะพะฑ ะฝะต ะทะฒะฐะถะฐัะธ): %s" - -msgid "E508: Can't read file for backup (add ! to override)" -msgstr "" -"E508: ะะต ะฒะดะฐะปะพัั ะฟัะพัะธัะฐัะธ ัะฐะนะป ัะพะฑ ััะฒะพัะธัะธ ัะตะทะตัะฒะฝั ะบะพะฟัั (! ัะพะฑ ะฝะต " -"ะทะฒะฐะถะฐัะธ)" - msgid "E509: Cannot create backup file (add ! to override)" msgstr "E509: ะะต ะฒะดะฐะปะพัั ััะฒะพัะธัะธ ัะตะทะตัะฒะฝั ะบะพะฟัั (! ัะพะฑ ะฝะต ะทะฒะฐะถะฐัะธ)" @@ -2116,10 +2196,6 @@ msgid "E212: Can't open file for writing: %s" msgstr "E212: ะะต ะฒะดะฐะปะพัั ะฒัะดะบัะธัะธ ัะฐะนะป ะดะปั ะทะฐะฟะธัั: %s" #, c-format -msgid "E667: Fsync failed: %s" -msgstr "E667: ะะต ะฒะดะฐะปะพัั ะฒะธะบะพะฝะฐัะธ fsync: %s" - -#, c-format msgid "E512: Close failed: %s" msgstr "E512: ะะต ะฒะดะฐะปะพัั ะทะฐะบัะธัะธ: %s" @@ -2142,9 +2218,6 @@ msgstr " ั ััะดะบั %<PRId64>;" msgid "[Device]" msgstr "[ะัะธััััะน]" -msgid "[New]" -msgstr "[ะะพะฒะธะน]" - msgid " [a]" msgstr "[ะด]" @@ -2461,6 +2534,18 @@ msgid "E475: Invalid argument: %s" msgstr "E475: ะะตะบะพัะตะบัะฝะธะน ะฐัะณัะผะตะฝั: %s" #, c-format +msgid "E475: Invalid value for argument %s" +msgstr "E475: ะะตะบะพัะตะบัะฝะต ะทะฝะฐัะตะฝะฝั ะฐัะณัะผะตะฝัั %s" + +#, c-format +msgid "E475: Invalid value for argument %s: %s" +msgstr "E475: ะะตะบะพัะตะบัะฝะต ะทะฝะฐัะตะฝะฝั ะฐัะณัะผะตะฝัั %s: %s" + +#, c-format +msgid "E983: Duplicate argument: %s" +msgstr "E983: ะัะณัะผะตะฝั ะฟะพะฒัะพััััััั: %s" + +#, c-format msgid "E15: Invalid expression: %s" msgstr "E15: ะะตะฟัะฐะฒะธะปัะฝะธะน ะฒะธัะฐะท: %s" @@ -2512,6 +2597,10 @@ msgid "E364: Library call failed for \"%s()\"" msgstr "E364: ะัะฑะปัะพัะตัะฝะธะน ะฒะธะบะปะธะบ ะดะพ ยซ%s()ยป ะฝะต ะฒะดะฐะฒัั" #, c-format +msgid "E667: Fsync failed: %s" +msgstr "E667: ะะต ะฒะดะฐะปะพัั ะฒะธะบะพะฝะฐัะธ fsync: %s" + +#, c-format msgid "E739: Cannot create directory %s: %s" msgstr "E739: ะะต ะฒะดะฐะปะพัั ััะฒะพัะธัะธ ะบะฐัะฐะปะพะณ %s: %s" @@ -2589,12 +2678,6 @@ msgstr "E484: ะะต ะฒะดะฐะปะพัั ะฒัะดะบัะธัะธ ัะฐะนะป %s: %s" msgid "E485: Can't read file %s" msgstr "E485: ะะต ะฒะดะฐะปะพัั ะฟัะพัะธัะฐัะธ ัะฐะนะป %s" -msgid "E37: No write since last change (add ! to override)" -msgstr "E37: ะะผัะฝะธ ะฝะต ะฑัะปะพ ะทะฐะฟะธัะฐะฝะพ (! ัะพะฑ ะฝะต ะทะฒะฐะถะฐัะธ)" - -msgid "E37: No write since last change" -msgstr "E37: ะะต ะทะฐะฟะธัะฐะฝะพ ะฟััะปั ะพััะฐะฝะฝัั
ะทะผัะฝ" - msgid "E38: Null argument" msgstr "E38: ะัะดัััะฝัะน ะฐัะณัะผะตะฝั" @@ -2636,6 +2719,28 @@ msgstr "E44: ะัะฟัะพะฒะฐะฝะฐ ะฟัะพะณัะฐะผะฐ ัะตะณัะปััะฝะธั
ะฒะธัะฐะท msgid "E45: 'readonly' option is set (add ! to override)" msgstr "E45: ะััะฐะฝะพะฒะปะตะฝะพ ะพะฟััั 'readonly' (! ัะพะฑ ะฝะต ะทะฒะฐะถะฐัะธ)" +#, c-format +msgid "E46: Cannot change read-only variable \"%.*s\"" +msgstr "E46: ะะผัะฝะฝะฐ ััะปัะบะธ ะดะปั ัะธัะฐะฝะฝั: ยซ%.*sยป" + +msgid "E715: Dictionary required" +msgstr "E715: ะะพัััะฑะตะฝ ัะปะพะฒะฝะธะบ" + +#, c-format +msgid "E118: Too many arguments for function: %s" +msgstr "E118: ะะฐะฑะฐะณะฐัะพ ะฐัะณัะผะตะฝััะฒ ะดะปั ััะฝะบััั: %s" + +#, c-format +msgid "E716: Key not present in Dictionary: %s" +msgstr "E716: ะะตะผะฐั ัะฐะบะพะณะพ ะบะปััะฐ ั ัะปะพะฒะฝะธะบั: %s" + +msgid "E714: List required" +msgstr "E714: ะะพัััะฑะตะฝ ัะฟะธัะพะบ" + +#, c-format +msgid "E712: Argument of %s must be a List or Dictionary" +msgstr "E712: ะัะณัะผะตะฝั ั %s ะผะฐั ะฑััะธ ัะฟะธัะบะพะผ ัะธ ัะปะพะฒะฝะธะบะพะผ" + msgid "E47: Error while reading errorfile" msgstr "E47: ะะพะผะธะปะบะฐ ัะธัะฐะฝะฝั ัะฐะนะปั ะฟะพะผะธะปะพะบ" @@ -2756,12 +2861,29 @@ msgstr "E5521: ะะฐะผัะฝะฐ ะบะปะฐะฒัั <Cmd> ะผะฐั ะทะฐะบัะฝััะฒะฐัะธัั msgid "E5522: <Cmd> mapping must not include %s key" msgstr "E5522: ะะฐะผัะฝะฐ ะบะปะฐะฒัั <Cmd> ะฝะต ะผะพะถะต ะผัััะธัะธ ะบะปัั %s" +#, c-format +msgid "E5555: API call: %s" +msgstr "E5555: ะฒะธะบะปะธะบ API: %s" + +#, c-format +msgid "E5560: %s must not be called in a lua loop callback" +msgstr "E5560: %s ะผะฐั ะฑััะธ ะฒะธะบะปะธะบะฐะฝะต ั ะบะพะฝัะตะบััั ัะธะบะปั lua" + +msgid "E5601: Cannot close window, only floating window would remain" +msgstr "E5601: ะะต ะฒะดะฐะปะพัั ะทะฐะบัะธัะธ ะฒัะบะฝะพ, ะทะฐะปะธัะธะปะพัั ะฑ ััะปัะบะธ ะฟะปะฐะฒััะต ะฒัะบะฝะพ" + +msgid "E5602: Cannot exchange or rotate float" +msgstr "E5602: ะะต ะผะพะถะฝะฐ ะพะฑะผัะฝััะธ ัะธ ะฟะพะบัััะธัะธ ะฟะปะฐะฒััะต ะฒัะบะฝะพ" + msgid "search hit TOP, continuing at BOTTOM" msgstr "ะะพััะบ ะดัะนัะพะฒ ะดะพ ะะะงะะขะะฃ, ะฟัะพะดะพะฒะถัััััั ะท ะะะะฆะฏ" msgid "search hit BOTTOM, continuing at TOP" msgstr "ะะพััะบ ะดัะนัะพะฒ ะดะพ ะะะะฆะฏ, ะฟัะพะดะพะฒะถัััััั ะท ะะะงะะขะะฃ" +msgid " line " +msgstr " ััะดะพะบ " + msgid "E550: Missing colon" msgstr "E550: ะัะพะฟััะตะฝะพ ะดะฒะพะบัะฐะฟะบั" @@ -3032,6 +3154,14 @@ msgid "E5102: Lua failed to grow stack to %i" msgstr "E5102: Lua ะฝะต ะฒะดะฐะปะพัั ะทะฑัะปััะธัะธ ััะตะบ ะดะพ %i" #, c-format +msgid "Error executing vim.schedule lua callback: %.*s" +msgstr "ะะพะผะธะปะบะฐ ะฒะธะบะพะฝะฐะฝะฝั ะพะฑัะพะฑะฝะธะบะฐ lua vim.schedule: %.*s" + +#, c-format +msgid "E5106: Error while creating shared module: %.*s" +msgstr "E5106: ะะพะผะธะปะบะฐ ััะฒะพัะตะฝะฝั ัะพะทะดัะปัะฒะฐะฝะพะณะพ ะผะพะดัะปั: %.*s" + +#, c-format msgid "E5106: Error while creating vim module: %.*s" msgstr "E5106: ะะพะผะธะปะบะฐ ััะฒะพัะตะฝะฝั ะผะพะดัะปั vim: %.*s" @@ -3043,14 +3173,6 @@ msgid "E5117: Error while updating package paths: %.*s" msgstr "E5117: ะะพะผะธะปะบะฐ ะพะฝะพะฒะปะตะฝะฝั ัะปัั
ัะฒ ะฟะฐะบัะฝะบั: %.*s" #, c-format -msgid "E5104: Error while creating lua chunk: %.*s" -msgstr "E5104: ะะพะผะธะปะบะฐ ััะฒะพัะตะฝะฝั ัะผะฐัะบั lua: %.*s" - -#, c-format -msgid "E5105: Error while calling lua chunk: %.*s" -msgstr "E5105: ะะพะผะธะปะบะฐ ะฒะธะบะปะธะบั ัะผะฐัะบั lua: %.*s" - -#, c-format msgid "E5114: Error while converting print argument #%i: %.*s" msgstr "E5114: ะะต ะฒะดะฐะปะพัั ะฟะตัะตัะฒะพัะธัะธ ะฐัะณัะผะตะฝั #%i ะดััะบั: %.*s" @@ -3063,27 +3185,31 @@ msgid "E5116: Error while calling debug string: %.*s" msgstr "E5116: ะะพะผะธะปะบะฐ ะฒะธะบะปะธะบั ะฝะฐะปะฐะณะพะดะถะตะฝะฝั: %.*s" #, c-format -msgid "E5107: Error while creating lua chunk for luaeval(): %.*s" -msgstr "E5107: ะะพะผะธะปะบะฐ ััะฒะพัะตะฝะฝั ัะผะฐัะบั lua ะดะปั luaeval(): %.*s" +msgid "E5107: Error loading lua %.*s" +msgstr "E5107: ะะพะผะธะปะบะฐ ะทะฐะฒะฐะฝัะฐะถะตะฝะฝั lua %.*s" #, c-format -msgid "E5108: Error while calling lua chunk for luaeval(): %.*s" -msgstr "E5108: ะะพะผะธะปะบะฐ ะฒะธะบะปะธะบั ัะผะฐัะบั lua ะดะปั luaeval(): %.*s" +msgid "E5108: Error executing lua %.*s" +msgstr "E5108: ะะพะผะธะปะบะฐ ะฒะธะบะพะฝะฐะฝะฝั lua %.*s" + +#, c-format +msgid "Error executing lua callback: %.*s" +msgstr "ะะพะผะธะปะบะฐ ะฒะธะบะพะฝะฐะฝะฝั ะพะฑัะพะฑะฝะธะบะฐ lua: %.*s" msgid "cannot save undo information" msgstr "ะะต ะฒะดะฐะปะพัั ะทะฐะฟะธัะฐัะธ ัะฝัะพัะผะฐััั ะฟะพะฒะตัะฝะตะฝะฝั" #, c-format -msgid "E5109: Error while creating lua chunk: %.*s" -msgstr "E5109: ะะพะผะธะปะบะฐ ััะฒะพัะตะฝะฝั ัะผะฐัะบั lua: %.*s" +msgid "E5109: Error loading lua: %.*s" +msgstr "E5109: ะะพะผะธะปะบะฐ ะทะฐะฒะฐะฝัะฐะถะตะฝะฝั lua: %.*s" #, c-format -msgid "E5110: Error while creating lua function: %.*s" -msgstr "E5110: ะะพะผะธะปะบะฐ ััะฒะพัะตะฝะฝั ััะฝะบััั lua: %.*s" +msgid "E5110: Error executing lua: %.*s" +msgstr "E5110: ะะพะผะธะปะบะฐ ะฒะธะบะพะฝะฐะฝะฝั lua: %.*s" #, c-format -msgid "E5111: Error while calling lua function: %.*s" -msgstr "E5111: ะะพะผะธะปะบะฐ ะฒะธะบะปะธะบั ััะฝะบััั lua: %.*s" +msgid "E5111: Error calling lua: %.*s" +msgstr "E5111: ะะพะผะธะปะบะฐ ะฒะธะบะปะธะบั lua: %.*s" #, c-format msgid "E5112: Error while creating lua chunk: %.*s" @@ -3093,6 +3219,10 @@ msgstr "E5112: ะะพะผะธะปะบะฐ ััะฒะพัะตะฝะฝั ัะผะฐัะบั lua: %.*s" msgid "E5113: Error while calling lua chunk: %.*s" msgstr "E5113: ะะพะผะธะปะบะฐ ะฒะธะบะปะธะบั ัะผะฐัะบั lua: %.*s" +#, c-format +msgid "Error executing vim.log_keystroke lua callback: %.*s" +msgstr "ะะพะผะธะปะบะฐ ะฒะธะบะพะฝะฐะฝะฝั ะพะฑัะพะฑะฝะธะบะฐ lua vim.log_keystroke: %.*s" + msgid "Argument missing after" msgstr "ะัะพะฟััะตะฝะพ ะฐัะณัะผะตะฝั ะฟััะปั" @@ -3123,6 +3253,9 @@ msgstr "ะะต ะฒะดะฐะปะพัั ะฒัะดะบัะธัะธ ะดะปั ัะธัะฐะฝะฝั: \"%s\": %s\n msgid "Cannot open for script output: \"" msgstr "ะะต ะฒะดะฐะปะพัั ะฒัะดะบัะธัะธ ัะบ ะฒะธั
ัะดะฝะธะน ัะฐะนะป: \"" +msgid "--embed conflicts with -es/-Es" +msgstr "--embed ะบะพะฝัะปัะบััั ะท -es/-Es" + msgid "pre-vimrc command line" msgstr "ะบะพะผะฐะฝะดะธ ะฟะตัะตะด vimrc" @@ -3567,8 +3700,8 @@ msgid "E315: ml_get: invalid lnum: %<PRId64>" msgstr "E315: ml_get: ะฝะตะฟัะฐะฒะธะปัะฝะธะน lnum: %<PRId64>" #, c-format -msgid "E316: ml_get: cannot find line %<PRId64>" -msgstr "E316: ml_get: ะฝะต ะทะฝะฐะนัะพะฒ ััะดะพะบ %<PRId64>" +msgid "E316: ml_get: cannot find line %<PRId64> in buffer %d %s" +msgstr "E316: ml_get: ะฝะต ะทะฝะฐะนัะพะฒ ััะดะพะบ %<PRId64> ั ะฑััะตัั %d %s" msgid "E317: pointer block id wrong 3" msgstr "E317: ะะบะฐะทัะฒะฝะธะบ ะฑะปะพะบั ะฟะพะผะธะปะบะพะฒะธะน 3" @@ -3666,6 +3799,9 @@ msgstr "" "ยป,\n" " ัะพะฑ ะฟะพะทะฑััะธัั ััะพะณะพ ะฟะพะฒัะดะพะผะปะตะฝะฝั.\n" +msgid "Found a swap file that is not useful, deleting it" +msgstr "ะะฝะฐะนะดะตะฝะพ ัะฐะนะป ะพะฑะผัะฝั, ัะบะธะผ ะฝะต ะผะพะถะฝะฐ ัะบะพัะธััะฐัะธัั, ะทะฝะธัะตะฝะฝั" + msgid "Swap file \"" msgstr "ะคะฐะนะป ะพะฑะผัะฝั ยซ" @@ -3750,6 +3886,10 @@ msgstr "" "\n" "--- ะะตะฝั ---" +#, c-format +msgid "E335: Menu not defined for %s mode" +msgstr "E335: ะะปั ัะตะถะธะผั %s ะผะตะฝั ะฝะต ะฒะธะทะฝะฐัะตะฝะพ" + msgid "E333: Menu path must lead to a menu item" msgstr "E333: ะจะปัั
ะฟะพะฒะธะฝะตะฝ ะฒะตััะธ ะดะพ ะตะปะตะผะตะฝัะฐ ะผะตะฝั" @@ -3758,10 +3898,6 @@ msgid "E334: Menu not found: %s" msgstr "E334: ะะตะฝั ะฝะต ะทะฝะฐะนะดะตะฝะพ: %s" #, c-format -msgid "E335: Menu not defined for %s mode" -msgstr "E335: ะะปั ัะตะถะธะผั %s ะผะตะฝั ะฝะต ะฒะธะทะฝะฐัะตะฝะพ" - -#, c-format msgid "Error detected while processing %s:" msgstr "ะะธัะฒะปะตะฝะพ ะฟะพะผะธะปะบั ะฟัะด ัะฐั ะฒะธะบะพะฝะฐะฝะฝั %s:" @@ -3821,9 +3957,6 @@ msgstr "" "&D:ะะพะดะฝะพะณะพ\n" "&C:ะกะบะฐััะฒะฐัะธ" -msgid "W10: Warning: Changing a readonly file" -msgstr "W10: ะะฐััะตัะตะถะตะฝะฝั: ะะผัะฝัััััั ัะฐะนะป ะฟัะธะทะฝะฐัะตะฝะธะน ะปะธัะต ะดะปั ัะธัะฐะฝะฝั" - msgid "Type number and <Enter> or click with mouse (empty cancels): " msgstr "ะะฐะฑะตัััั ัะธัะปะพ ะน <Enter> ัะธ ะบะปะฐัะฝััั ะผะธัะบะพั (ะฟะพัะพะถะฝั ัะบะฐัะพะฒัั): " @@ -3856,9 +3989,6 @@ msgstr "E349: ะะตะผะฐั ัะดะตะฝัะธััะบะฐัะพัะฐ ะฝะฐะด ะบัััะพัะพะผ" msgid "E774: 'operatorfunc' is empty" msgstr "E774: 'operatorfunc' ะฟะพัะพะถะฝั" -msgid "Warning: terminal cannot highlight" -msgstr "ะะฐััะตัะตะถะตะฝะฝั: ะขะตัะผัะฝะฐะป ะฝะต ะฟัะดััะธะผัั ะบะพะปัะพัะธ" - msgid "E348: No string under cursor" msgstr "E348: ะะตะผะฐั ััะดะบะฐ ะฝะฐ ะบัััะพัั" @@ -3878,6 +4008,10 @@ msgid "Type :qa! and press <Enter> to abandon all changes and exit Nvim" msgstr "" "ะะฒะตะดััั :qa! ั ะฝะฐัะธัะฝัััั <Enter> ัะพะฑ ะฒัะดะบะธะฝััะธ ะฒัั ะทะผัะฝะธ ั ะฒะธะนัะธ Nvim" +msgid "Type :qa and press <Enter> to exit Nvim" +msgstr "" +"ะะฒะตะดััั :qa ั ะฝะฐัะธัะฝัััั <Enter> ัะพะฑ ะฒะธะนัะธ ะท Nvim" + #, c-format msgid "1 line %sed 1 time" msgstr "ะะดะธะฝ ััะดะพะบ %s-ะฝะพ" @@ -3998,6 +4132,9 @@ msgstr "E518: ะะตะฒัะดะพะผะฐ ะพะฟััั" msgid "E520: Not allowed in a modeline" msgstr "E520: ะะต ะดะพะทะฒะพะปะตะฝะพ ั modeline" +msgid "E992: Not allowed in a modeline when 'modelineexpr' is off" +msgstr "E992: ะะต ะดะพะทะฒะพะปะตะฝะพ ั modeline, ะบะพะปะธ ะฒะธะผะบะฝะตะฝะพ 'modelineexpr'" + msgid "E846: Key code not set" msgstr "E846: ะะพะด ะบะปััะฐ ะฝะต ะฒััะฐะฝะพะฒะปะตะฝะพ" @@ -4144,24 +4281,16 @@ msgstr "" msgid "E5677: Error writing input to shell-command: %s" msgstr "E5677: ะะต ะฒะดะฐะปะพัั ะทะฐะฟะธัะฐัะธ ะฝะฐ ะฒั
ัะด ะบะพะผะฐะฝะดะธ ะพะฑะพะปะพะฝะบะธ: %s" -msgid "" -"\n" -"Could not get security context for " -msgstr "" -"\n" -"ะะต ะฒะดะฐะปะพัั ะพััะธะผะฐัะธ ะบะพะฝัะตะบัั ะฑะตะทะฟะตะบะธ ะดะปั " - -msgid "" -"\n" -"Could not set security context for " -msgstr "" -"\n" -"ะะต ะฒะดะฐะปะพัั ะฒััะฐะฝะพะฒะธัะธ ะบะพะฝัะตะบัั ะฑะตะทะฟะตะบะธ ะดะปั " - #, c-format msgid "E447: Can't find file \"%s\" in path" msgstr "E447: ะคะฐะนะป ยซ%sยป ะฝะต ะทะฝะฐะนะดะตะฝะพ ั ัะปัั
ั ะฟะพััะบั" +msgid "E553: No more items" +msgstr "E553: ะะตะผะฐั ะฑัะปััะต ะตะปะตะผะตะฝััะฒ" + +msgid "E926: Current location list was changed" +msgstr "E926: ะฆะตะน ัะฟะธัะพะบ ะผัััั ะฑัะปะพ ะทะผัะฝะตะฝะพ" + #, c-format msgid "E372: Too many %%%c in format string" msgstr "E372: ะะฐะฑะฐะณะฐัะพ %%%c ั ััะดะบั ัะพัะผะฐัั" @@ -4191,18 +4320,12 @@ msgstr "E378: 'errorformat' ะฝะต ะผัััะธัั ะทัะฐะทะพะบ" msgid "E379: Missing or empty directory name" msgstr "E379: ะัะพะฟััะตะฝะฐ ัะธ ะฟะพัะพะถะฝั ะฝะฐะทะฒะฐ ะบะฐัะฐะปะพะณั" -msgid "E553: No more items" -msgstr "E553: ะะตะผะฐั ะฑัะปััะต ะตะปะตะผะตะฝััะฒ" - msgid "E924: Current window was closed" msgstr "E924: ะะบัะธะฒะฝะต ะฒัะบะฝะพ ะฑัะปะพ ะทะฐะบัะธัะพ" msgid "E925: Current quickfix was changed" msgstr "E925: ะฆะตะน quickfix ะฑัะปะพ ะทะผัะฝะตะฝะพ" -msgid "E926: Current location list was changed" -msgstr "E926: ะฆะตะน ัะฟะธัะพะบ ะผัััั ะฑัะปะพ ะทะผัะฝะตะฝะพ" - #, c-format msgid "(%d of %d)%s%s: " msgstr "(%d ะท %d)%s%s: " @@ -4223,9 +4346,6 @@ msgstr "E381: ะะตััะธะฝะฐ ััะตะบั ะฒะธะฟัะฐะฒะปะตะฝั" msgid "No entries" msgstr "ะััะพะณะพ" -msgid "E382: Cannot write, 'buftype' option is set" -msgstr "E382: ะะต ะผะพะถั ะทะฐะฟะธัะฐัะธ, ะฒะบะฐะทะฐะฝะฐ ะพะฟััั 'buftype'" - msgid "E683: File name missing or invalid pattern" msgstr "E683: ะัะพะฟััะตะฝะพ ะฝะฐะทะฒั ัะฐะนะปั ัะธ ะฝะตะบะพัะตะบัะฝะธะน ัะฐะฑะปะพะฝ" @@ -4279,6 +4399,12 @@ msgstr "E69: ะัะพะฟััะตะฝะพ ] ะฟััะปั %s%%[" msgid "E70: Empty %s%%[]" msgstr "E70: %s%%[] ะฟะพัะพะถะฝัะน" +msgid "E956: Cannot use pattern recursively" +msgstr "E956: ะะต ะผะพะถะฝะฐ ัะตะบัััะธะฒะฝะพ ะฒะธะบะพัะธััะฐัะธ ัะฐะฑะปะพะฝ" + +msgid "E65: Illegal back reference" +msgstr "E65: ะะตะบะพัะตะบัะฝะต ะทะฒะพัะพัะฝั ะฟะพัะธะปะฐะฝะฝั" + msgid "E339: Pattern too long" msgstr "E339: ะัะฐะทะพะบ ะทะฐะฝะฐะดัะพ ะดะพะฒะณะธะน" @@ -4315,9 +4441,6 @@ msgstr "E63: ะะตะบะพัะตะบัะฝะพ ะฒะถะธัะพ \\_" msgid "E64: %s%c follows nothing" msgstr "E64: ะััะปั %s%c ะฝััะพะณะพ ะฝะตะผะฐั" -msgid "E65: Illegal back reference" -msgstr "E65: ะะตะบะพัะตะบัะฝะต ะทะฒะพัะพัะฝั ะฟะพัะธะปะฐะฝะฝั" - msgid "E68: Invalid character after \\z" msgstr "E68: ะะตะฟัะฐะฒะธะปัะฝะธะน ัะธะผะฒะพะป ะฟััะปั \\z" @@ -4658,6 +4781,63 @@ msgstr "" "ะะพะผะธะปะบะฐ ะฟัะธ ัะธัะฐะฝะฝั ัะฐะนะปั ShaDa: ัะฟะธัะพะบ ะฑััะตััะฒ ั ะฟะพะทะธััั %<PRIu64> ะผัััะธัั " "ะฟะพะปะต, ัะบะต ะฝะต ะผะฐั ะฝะฐะทะฒั ัะฐะนะปั" +msgid "[Deleted]" +msgstr "[ะะฝะธัะตะฝะพ]" + +msgid "" +"\n" +"--- Signs ---" +msgstr "" +"\n" +"--- ะะพะทะฝะฐัะบะธ ---" + +#, c-format +msgid "Signs for %s:" +msgstr "ะะพะทะฝะฐัะบะธ ะดะปั %s:" + +#, c-format +msgid " group=%s" +msgstr " ะณััะฟะฐ=%s" + +#, c-format +msgid " line=%ld id=%d%s name=%s priority=%d" +msgstr " ััะดะพะบ=%ld id=%d%s ะฝะฐะทะฒะฐ=%s ะฟััะพัะธัะตั=%d" + +msgid "E612: Too many signs defined" +msgstr "E612: ะะธะทะฝะฐัะตะฝะพ ะทะฐะฑะฐะณะฐัะพ ะฝะฐะดะฟะธััะฒ" + +#, c-format +msgid "E239: Invalid sign text: %s" +msgstr "E239: ะะตะบะพัะตะบัะฝะธะน ะฝะฐะดะฟะธั: %s" + +#, c-format +msgid "E155: Unknown sign: %s" +msgstr "E155: ะะตะฒัะดะพะผะธะน ะฝะฐะดะฟะธั: %s" + +#, c-format +msgid "E885: Not possible to change sign %s" +msgstr "E885: ะะตะผะพะถะปะธะฒะพ ะทะผัะฝะธัะธ ะทะฝะฐะบ %s" + +msgid "E159: Missing sign number" +msgstr "E159: ะัะพะฟััะตะฝะพ ะฝะพะผะตั ะฝะฐะดะฟะธัั" + +#, c-format +msgid "E157: Invalid sign ID: %<PRId64>" +msgstr "E157: ะะตะฟัะฐะฒะธะปัะฝะธะน ID ะฝะฐะดะฟะธัั: %<PRId64>" + +msgid "E934: Cannot jump to a buffer that does not have a name" +msgstr "E934: ะะต ะผะพะถะฝะฐ ะฟะตัะตะนัะธ ะดะพ ะฑััะตัะฐ ะฑะตะท ะฝะฐะทะฒะธ" + +#, c-format +msgid "E160: Unknown sign command: %s" +msgstr "E160: ะะตะฒัะดะพะผะฐ ะบะพะผะฐะฝะดะฐ ะฝะฐะดะฟะธัั: %s" + +msgid "E156: Missing sign name" +msgstr "E156: ะัะพะฟััะตะฝะพ ะฝะฐะทะฒั ะฝะฐะดะฟะธัั" + +msgid " (not supported)" +msgstr " (ะฝะต ะฟัะดััะธะผัััััั)" + msgid "E759: Format error in spell file" msgstr "E759: ะะพะผะธะปะบะฐ ัะพัะผะฐัั ั ัะฐะนะปั ะพััะพะณัะฐััั" @@ -4709,12 +4889,6 @@ msgstr "ะะฐะนะฒะธะน ัะตะบัั ั %s ั ััะดะบั %d: %s" msgid "Affix name too long in %s line %d: %s" msgstr "ะะฐะทะฒะฐ ะฐััะบัั ะทะฐะฒะตะปะธะบะฐ ั %s ั ััะดะบั %d: %s" -msgid "E761: Format error in affix file FOL, LOW or UPP" -msgstr "E761: ะะพะผะธะปะบะฐ ัะพัะผะฐัั ั ัะฐะนะปั ะฐััะบััะฒ FOL, LOW ัะธ UPP" - -msgid "E762: Character in FOL, LOW or UPP is out of range" -msgstr "E762: ะกะธะผะฒะพะป ั FOL, LOW ัะธ UPP ะฟะพะทะฐ ะผะตะถะฐะผะธ" - msgid "Compressing word tree..." msgstr "ะกัะธัะบัััััั ะดะตัะตะฒะพ ัะปัะฒ..." @@ -4855,10 +5029,6 @@ msgstr "ะะพะฒัะพัะตะฝะฝั ัะธะผะฒะพะปั ั MAP ั %s ั ััะดะบั %d" msgid "Unrecognized or duplicate item in %s line %d: %s" msgstr "ะะตัะพะทะฟัะทะฝะฐะฝะธะน ัะธ ะฟะพะฒัะพัะฝะธะน ะตะปะตะผะตะฝั ั %s ั ััะดะบั %d: %s" -#, c-format -msgid "Missing FOL/LOW/UPP line in %s" -msgstr "ะัะพะฟััะตะฝะพ ััะดะพะบ FOL/LOW/UPP ั %s" - msgid "COMPOUNDSYLMAX used without SYLLABLE" msgstr "ะะถะธัะพ COMPOUNDSYLMAX ะฑะตะท SYLLABLE" @@ -4960,8 +5130,8 @@ msgid "Ignored %d words with non-ASCII characters" msgstr "ะัะพัะณะฝะพัะพะฒะฐะฝะพ %d ัะปัะฒ ัะท ะฝะต-ASCII ัะธะผะฒะพะปะฐะผะธ" #, c-format -msgid "Compressed %d of %d nodes; %d (%d%%) remaining" -msgstr "ะกัะธัะฝะตะฝะพ %d ะท %d ะฒัะทะปัะฒ; ะทะฐะปะธัะธะปะพัั %d (%d%%)" +msgid "Compressed %s of %ld nodes; %ld (%ld%%) remaining" +msgstr "ะกัะธัะฝะตะฝะพ %s ะท %ld ะฒัะทะปัะฒ; ะทะฐะปะธัะธะปะพัั %ld (%ld%%)" msgid "Reading back spell file..." msgstr "ะะตัะตัะธััััััั ัะฐะนะป ะพััะพะณัะฐััั..." @@ -5033,25 +5203,34 @@ msgstr "E807: ะััะบัััััั ะฐัะณัะผะตะฝั Float ะดะปั printf()" msgid "E767: Too many arguments to printf()" msgstr "E767: ะะฐะฑะฐะณะฐัะพ ะฐัะณัะผะตะฝััะฒ ะดะปั printf()" +#, c-format +msgid "E390: Illegal argument: %s" +msgstr "E390: ะะตะฟัะฐะฒะธะปัะฝะธะน ะฐัะณัะผะตะฝั: %s" + msgid "No Syntax items defined for this buffer" msgstr "ะะปั ะฑััะตัะฐ ะฝะต ะฒะธะทะฝะฐัะตะฝะพ ะตะปะตะผะตะฝััะฒ ัะธะฝัะฐะบัะธัั" +msgid "'redrawtime' exceeded, syntax highlighting disabled" +msgstr "'redrawtime' ะฟะตัะตะฒะธัะตะฝะพ, ะฟัะดัะฒัััะฒะฐะฝะฝั ัะธะฝัะฐะบัะธัั ะฒะธะผะบะฝะตะฝะพ" + msgid "syntax conceal on" msgstr "ัะธะฝัะฐะบัะธัะฝะต ะฟัะธั
ะพะฒัะฒะฐะฝะฝั ัะฒัะผะบ" msgid "syntax conceal off" msgstr "ัะธะฝัะฐะบัะธัะฝะต ะฟัะธั
ะพะฒัะฒะฐะฝะฝั ะฒะธะผะบ" -#, c-format -msgid "E390: Illegal argument: %s" -msgstr "E390: ะะตะฟัะฐะฒะธะปัะฝะธะน ะฐัะณัะผะตะฝั: %s" - msgid "syntax case ignore" msgstr "ัะธะฝัะฐะบัะธั ัะณะฝะพััะฒะฐัะธ ัะตะณัััั" msgid "syntax case match" msgstr "ัะธะฝัะฐะบัะธั ะดะพััะธะผัะฒะฐัะธัั ัะตะณััััั" +msgid "syntax foldlevel start" +msgstr "ััะฒะตะฝั ะทะณะพััะบะธ ัะธะฝัะฐะบัะธัั ะฟะพัะฐัะพะบ" + +msgid "syntax foldlevel minimum" +msgstr "ััะฒะตะฝั ะทะณะพััะบะธ ัะธะฝัะฐะบัะธัั ะผัะฝัะผัะผ" + msgid "syntax spell toplevel" msgstr "ัะธะฝัะฐะบัะธั ะฟะตัะตะฒััััะธ ะฒััะดะธ" @@ -5064,6 +5243,9 @@ msgstr "ัะธะฝัะฐะบัะธั ะฟะพัะฐัะบะพะฒะพ" msgid "syntax iskeyword " msgstr "ัะธะฝัะฐะบัะธั iskeyword " +msgid "syntax iskeyword not set" +msgstr "ะฝะต ะฒััะฐะฝะพะฒะปะตะฝะพ ัะธะฝัะฐะบัะธั iskeyword" + #, c-format msgid "E391: No such syntax cluster: %s" msgstr "E391: ะะตะผะฐั ัะฐะบะพะณะพ ัะธะฝัะฐะบัะธัะฝะพะณะพ ะบะปะฐััะตัะฐ: %s" @@ -5124,7 +5306,7 @@ msgid "E844: invalid cchar value" msgstr "E844: ะะตะบะพัะตะบัะฝะต ะทะฝะฐัะตะฝะฝั cchar" msgid "E393: group[t]here not accepted here" -msgstr "E393: group[t]hete ััั ะฝะตะฟัะธะนะฝััะฝะธะน" +msgstr "E393: group[t]here ััั ะฝะตะฟัะธะนะฝััะฝะธะน" #, c-format msgid "E394: Didn't find region item for %s" @@ -5266,6 +5448,12 @@ msgstr "E555: ะัะฝะตัั ััะตะบั ะผััะพะบ" msgid "E556: at top of tag stack" msgstr "E556: ะะตััะธะฝะฐ ััะตะบั ะผััะพะบ" +msgid "E986: cannot modify the tag stack within tagfunc" +msgstr "E986: ะะต ะผะพะถะฝะฐ ะทะผัะฝัะฒะฐัะธ ััะตะบ ะผััะพะบ ั tagfunc" + +msgid "E987: invalid return value from tagfunc" +msgstr "E987: ะะตะบะพัะตะบัะฝะต ะฟะพะฒะตัะฝะตะฝะต ะทะฝะฐัะตะฝะฝั ะท tagfunc" + msgid "E425: Cannot go before first matching tag" msgstr "E425: ะฆะต ะฒะถะต ะฝะฐะนะฟะตััะฐ ะฒัะดะฟะพะฒัะดะฝะฐ ะผััะบะฐ" @@ -5273,12 +5461,6 @@ msgstr "E425: ะฆะต ะฒะถะต ะฝะฐะนะฟะตััะฐ ะฒัะดะฟะพะฒัะดะฝะฐ ะผััะบะฐ" msgid "E426: tag not found: %s" msgstr "E426: ะััะบั ะฝะต ะทะฝะฐะนะดะตะฝะพ: %s" -msgid " # pri kind tag" -msgstr " # ะฟัั ัะธะฟ ะผััะบะฐ" - -msgid "file\n" -msgstr "ัะฐะนะป\n" - msgid "E427: There is only one matching tag" msgstr "E427: ะะธัะต ะพะดะฝะฐ ะฒัะดะฟะพะฒัะดะฝะฐ ะผััะบะฐ" @@ -5303,6 +5485,12 @@ msgstr " ะะธะบะพัะธััะฐะฝะพ ะผััะบั, ะฝะต ัะพะทััะทะฝัััะธ ะฒะตะปะ msgid "E429: File \"%s\" does not exist" msgstr "E429: ะคะฐะนะป ยซ%sยป ะฝะต ััะฝัั" +msgid " # pri kind tag" +msgstr " # ะฟัั ัะธะฟ ะผััะบะฐ" + +msgid "file\n" +msgstr "ัะฐะนะป\n" + msgid "" "\n" " # TO tag FROM line in file/text" @@ -5314,9 +5502,6 @@ msgstr "" msgid "Searching tags file %s" msgstr "ะจัะบะฐััััั ั ัะฐะนะปั ัะตาัะฒ %s" -msgid "Ignoring long line in tags file" -msgstr "ะะณะฝะพััััััั ะดะพะฒะณะธะน ััะดะพะบ ั ัะฐะนะปั ะท ะฟะพะทะฝะฐัะบะฐะผะธ" - #, c-format msgid "E431: Format error in tags file \"%s\"" msgstr "E431: ะะพะผะธะปะบะฐ ัะพัะผะฐัั ั ัะฐะนะปั ัะตาัะฒ ยซ%sยป" @@ -5453,10 +5638,6 @@ msgstr "ะะตะผะฐั ะฝััะพะณะพ ัะบะฐัะพะฒัะฒะฐัะธ" msgid "number changes when saved" msgstr "ะฝะพะผะตั ะทะผัะฝะธ ัะฐั ะทะฑะตัะตะถะตะฝะพ" -#, c-format -msgid "%<PRId64> seconds ago" -msgstr "%<PRId64> ัะตะบัะฝะด ัะพะผั" - msgid "E790: undojoin is not allowed after undo" msgstr "E790: ะะต ะผะพะถะฝะฐ ะฒะธะบะพะฝะฐัะธ undojoin ะฟััะปั undo" @@ -5468,22 +5649,22 @@ msgstr "E440: ะัะดัััะฝัะน ััะดะพะบ ัะบะฐััะฒะฐะฝะฝั" msgid "" "\n" -"Compiled " +"\n" +"Features: " msgstr "" "\n" -"ะกะบะพะผะฟัะปัะฒะฐะฒ " - -msgid "by " -msgstr " " +"\n" +"ะฅะฐัะฐะบัะตัะธััะธะบะธ: " msgid "" "\n" -"\n" -"Features: " +"Compiled " msgstr "" "\n" -"\n" -"ะฅะฐัะฐะบัะตัะธััะธะบะธ: " +"ะกะบะพะผะฟัะปัะฒะฐะฒ " + +msgid "by " +msgstr " " msgid " system vimrc file: \"" msgstr " ัะธััะตะผะฝะธะน vimrc: \"" diff --git a/src/nvim/popupmnu.c b/src/nvim/popupmnu.c index c712762bdf..ac0c1f6eb1 100644 --- a/src/nvim/popupmnu.c +++ b/src/nvim/popupmnu.c @@ -226,6 +226,7 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, pum_above = false; // Leave two lines of context if possible + validate_cheight(); if (curwin->w_cline_row + curwin->w_cline_height - curwin->w_wrow >= 3) { context_lines = 3; } else { @@ -300,49 +301,49 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, if (pum_width < p_pw) { pum_width = (int)p_pw; } - } - } else if (((cursor_col > p_pw || cursor_col > max_width) && !pum_rl) - || (pum_rl && (cursor_col < Columns - p_pw - || cursor_col < Columns - max_width))) { - // align pum edge with "cursor_col" - if (pum_rl && W_ENDCOL(curwin) < max_width + pum_scrollbar + 1) { - pum_col = cursor_col + max_width + pum_scrollbar + 1; - if (pum_col >= Columns) { - pum_col = Columns - 1; - } - } else if (!pum_rl) { - if (curwin->w_wincol > Columns - max_width - pum_scrollbar - && max_width <= p_pw) { - // use full width to end of the screen - pum_col = cursor_col - max_width - pum_scrollbar; - if (pum_col < 0) { - pum_col = 0; + } else if (((cursor_col > p_pw || cursor_col > max_width) && !pum_rl) + || (pum_rl && (cursor_col < Columns - p_pw + || cursor_col < Columns - max_width))) { + // align pum edge with "cursor_col" + if (pum_rl && W_ENDCOL(curwin) < max_width + pum_scrollbar + 1) { + pum_col = cursor_col + max_width + pum_scrollbar + 1; + if (pum_col >= Columns) { + pum_col = Columns - 1; + } + } else if (!pum_rl) { + if (curwin->w_wincol > Columns - max_width - pum_scrollbar + && max_width <= p_pw) { + // use full width to end of the screen + pum_col = Columns - max_width - pum_scrollbar; + if (pum_col < 0) { + pum_col = 0; + } } } - } - - if (pum_rl) { - pum_width = pum_col - pum_scrollbar + 1; - } else { - pum_width = Columns - pum_col - pum_scrollbar; - } - if (pum_width < p_pw) { - pum_width = (int)p_pw; if (pum_rl) { - if (pum_width > pum_col) { - pum_width = pum_col; - } + pum_width = pum_col - pum_scrollbar + 1; } else { - if (pum_width >= Columns - pum_col) { - pum_width = Columns - pum_col - 1; - } + pum_width = Columns - pum_col - pum_scrollbar; } - } else if (pum_width > max_width + pum_kind_width + pum_extra_width + 1 - && pum_width > p_pw) { - pum_width = max_width + pum_kind_width + pum_extra_width + 1; + if (pum_width < p_pw) { pum_width = (int)p_pw; + if (pum_rl) { + if (pum_width > pum_col) { + pum_width = pum_col; + } + } else { + if (pum_width >= Columns - pum_col) { + pum_width = Columns - pum_col - 1; + } + } + } else if (pum_width > max_width + pum_kind_width + pum_extra_width + 1 + && pum_width > p_pw) { + pum_width = max_width + pum_kind_width + pum_extra_width + 1; + if (pum_width < p_pw) { + pum_width = (int)p_pw; + } } } } else if (Columns < def_width) { @@ -785,7 +786,7 @@ static int pum_set_selected(int n, int repeat) // Return cursor to where we were validate_cursor(); - redraw_later(SOME_VALID); + redraw_later(curwin, SOME_VALID); // When the preview window was resized we need to // update the view on the buffer. Only go back to @@ -918,11 +919,11 @@ void pum_set_event_info(dict_T *dict) r = (double)pum_row; c = (double)pum_col; } - tv_dict_add_float(dict, S_LEN("height"), h); - tv_dict_add_float(dict, S_LEN("width"), w); - tv_dict_add_float(dict, S_LEN("row"), r); - tv_dict_add_float(dict, S_LEN("col"), c); - tv_dict_add_nr(dict, S_LEN("size"), pum_size); - tv_dict_add_bool(dict, S_LEN("scrollbar"), - pum_scrollbar ? kBoolVarTrue : kBoolVarFalse); + (void)tv_dict_add_float(dict, S_LEN("height"), h); + (void)tv_dict_add_float(dict, S_LEN("width"), w); + (void)tv_dict_add_float(dict, S_LEN("row"), r); + (void)tv_dict_add_float(dict, S_LEN("col"), c); + (void)tv_dict_add_nr(dict, S_LEN("size"), pum_size); + (void)tv_dict_add_bool(dict, S_LEN("scrollbar"), + pum_scrollbar ? kBoolVarTrue : kBoolVarFalse); } diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 484168e798..6ca97c3072 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -239,7 +239,10 @@ static char_u *e_no_more_items = (char_u *)N_("E553: No more items"); static char_u *qf_last_bufname = NULL; static bufref_T qf_last_bufref = { NULL, 0, 0 }; -static char *e_loc_list_changed = N_("E926: Current location list was changed"); +static char *e_current_quickfix_list_was_changed = + N_("E925: Current quickfix list was changed"); +static char *e_current_location_list_was_changed = + N_("E926: Current location list was changed"); // Counter to prevent autocmds from freeing up location lists when they are // still being used. @@ -808,7 +811,7 @@ retry: } break; } - if (STRLEN(IObuff) < IOSIZE - 1 || IObuff[IOSIZE - 1] == '\n') { + if (STRLEN(IObuff) < IOSIZE - 1 || IObuff[IOSIZE - 2] == '\n') { break; } } @@ -901,6 +904,7 @@ static bool qf_list_has_valid_entries(qf_list_T *qfl) /// Return a pointer to a list in the specified quickfix stack static qf_list_T * qf_get_list(qf_info_T *qi, int idx) + FUNC_ATTR_NONNULL_ALL { return &qi->qf_lists[idx]; } @@ -1088,6 +1092,7 @@ qf_init_ext( ) FUNC_ATTR_NONNULL_ARG(1) { + qf_list_T *qfl; qfstate_T state = { 0 }; qffields_T fields = { 0 }; qfline_T *old_last = NULL; @@ -1111,15 +1116,16 @@ qf_init_ext( // make place for a new list qf_new_list(qi, qf_title); qf_idx = qi->qf_curlist; + qfl = qf_get_list(qi, qf_idx); } else { // Adding to existing list, use last entry. adding = true; - if (!qf_list_empty(qf_get_list(qi, qf_idx) )) { - old_last = qi->qf_lists[qf_idx].qf_last; + qfl = qf_get_list(qi, qf_idx); + if (!qf_list_empty(qfl)) { + old_last = qfl->qf_last; } } - qf_list_T *qfl = qf_get_list(qi, qf_idx); // Use the local value of 'errorformat' if it's set. if (errorformat == p_efm && tv == NULL && buf && *buf->b_p_efm != NUL) { @@ -1230,6 +1236,7 @@ static char_u * qf_cmdtitle(char_u *cmd) /// Return a pointer to the current list in the specified quickfix stack static qf_list_T * qf_get_curlist(qf_info_T *qi) + FUNC_ATTR_NONNULL_ALL { return qf_get_list(qi, qi->qf_curlist); } @@ -1660,8 +1667,8 @@ static int qf_parse_multiline_pfx(int idx, qf_list_T *qfl, qffields_T *fields) } if (!qfprev->qf_col) { qfprev->qf_col = fields->col; + qfprev->qf_viscol = fields->use_viscol; } - qfprev->qf_viscol = fields->use_viscol; if (!qfprev->qf_fnum) { qfprev->qf_fnum = qf_get_fnum(qfl, qfl->qf_directory, *fields->namebuf || qfl->qf_directory @@ -1770,7 +1777,7 @@ static void decr_quickfix_busy(void) void check_quickfix_busy(void) { if (quickfix_busy != 0) { - EMSGN("quickfix_busy not zero on exit: %ld", (long)quickfix_busy); + EMSGN("quickfix_busy not zero on exit: %" PRId64, (int64_t)quickfix_busy); # ifdef ABORT_ON_INTERNAL_ERROR abort(); # endif @@ -2348,25 +2355,27 @@ static qfline_T *get_prev_valid_entry(qf_list_T *qfl, qfline_T *qf_ptr, /// dir == FORWARD or FORWARD_FILE: next valid entry /// dir == BACKWARD or BACKWARD_FILE: previous valid entry static qfline_T *get_nth_valid_entry(qf_list_T *qfl, int errornr, - qfline_T *qf_ptr, int *qf_index, int dir) + int dir, int *new_qfidx) { + qfline_T *qf_ptr = qfl->qf_ptr; + int qf_idx = qfl->qf_index; qfline_T *prev_qf_ptr; int prev_index; char_u *err = e_no_more_items; while (errornr--) { prev_qf_ptr = qf_ptr; - prev_index = *qf_index; + prev_index = qf_idx; if (dir == FORWARD || dir == FORWARD_FILE) { - qf_ptr = get_next_valid_entry(qfl, qf_ptr, qf_index, dir); + qf_ptr = get_next_valid_entry(qfl, qf_ptr, &qf_idx, dir); } else { - qf_ptr = get_prev_valid_entry(qfl, qf_ptr, qf_index, dir); + qf_ptr = get_prev_valid_entry(qfl, qf_ptr, &qf_idx, dir); } if (qf_ptr == NULL) { qf_ptr = prev_qf_ptr; - *qf_index = prev_index; + qf_idx = prev_index; if (err != NULL) { EMSG(_(err)); return NULL; @@ -2377,14 +2386,16 @@ static qfline_T *get_nth_valid_entry(qf_list_T *qfl, int errornr, err = NULL; } + *new_qfidx = qf_idx; return qf_ptr; } -/// Get n'th (errornr) quickfix entry -static qfline_T *get_nth_entry(qf_list_T *qfl, int errornr, qfline_T *qf_ptr, - int *cur_qfidx) +/// Get n'th (errornr) quickfix entry from the current entry in the quickfix +/// list 'qfl'. Returns a pointer to the new entry and the index in 'new_qfidx' +static qfline_T *get_nth_entry(qf_list_T *qfl, int errornr, int *new_qfidx) { - int qf_idx = *cur_qfidx; + qfline_T *qf_ptr = qfl->qf_ptr; + int qf_idx = qfl->qf_index;; // New error number is less than the current error number while (errornr < qf_idx && qf_idx > 1 && qf_ptr->qf_prev != NULL) { @@ -2400,10 +2411,31 @@ static qfline_T *get_nth_entry(qf_list_T *qfl, int errornr, qfline_T *qf_ptr, qf_ptr = qf_ptr->qf_next; } - *cur_qfidx = qf_idx; + *new_qfidx = qf_idx; return qf_ptr; } +/// Get a entry specied by 'errornr' and 'dir' from the current +/// quickfix/location list. 'errornr' specifies the index of the entry and 'dir' +/// specifies the direction (FORWARD/BACKWARD/FORWARD_FILE/BACKWARD_FILE). +/// Returns a pointer to the entry and the index of the new entry is stored in +/// 'new_qfidx'. +static qfline_T * qf_get_entry(qf_list_T *qfl, int errornr, + int dir, int *new_qfidx) +{ + qfline_T *qf_ptr = qfl->qf_ptr; + int qfidx = qfl->qf_index; + + if (dir != 0) { // next/prev valid entry + qf_ptr = get_nth_valid_entry(qfl, errornr, dir, &qfidx); + } else if (errornr != 0) { // go to specified number + qf_ptr = get_nth_entry(qfl, errornr, &qfidx); + } + + *new_qfidx = qfidx; + return qf_ptr; +} + // Find a window displaying a Vim help file. static win_T *qf_find_help_win(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT @@ -2423,12 +2455,13 @@ static void win_set_loclist(win_T *wp, qf_info_T *qi) qi->qf_refcount++; } -/// Find a help window or open one. -static int jump_to_help_window(qf_info_T *qi, int *opened_window) +/// Find a help window or open one. If 'newwin' is true, then open a new help +/// window. +static int jump_to_help_window(qf_info_T *qi, bool newwin, int *opened_window) { win_T *wp = NULL; - if (cmdmod.tab != 0) { + if (cmdmod.tab != 0 || newwin) { wp = NULL; } else { wp = qf_find_help_win(); @@ -2446,8 +2479,10 @@ static int jump_to_help_window(qf_info_T *qi, int *opened_window) flags |= WSP_TOP; } - if (IS_LL_STACK(qi)) { - flags |= WSP_NEWLOC; // don't copy the location list + // If the user asks to open a new window, then copy the location list. + // Otherwise, don't copy the location list. + if (IS_LL_STACK(qi) && !newwin) { + flags |= WSP_NEWLOC; } if (win_split(0, flags) == FAIL) { @@ -2460,8 +2495,10 @@ static int jump_to_help_window(qf_info_T *qi, int *opened_window) win_setheight((int)p_hh); } - if (IS_LL_STACK(qi)) { // not a quickfix list - // The new window should use the supplied location list + // When using location list, the new window should use the supplied + // location list. If the user asks to open a new window, then the new + // window will get a copy of the location list. + if (IS_LL_STACK(qi) && !newwin) { win_set_loclist(curwin, qi); } } @@ -2622,14 +2659,19 @@ static void qf_goto_win_with_qfl_file(int qf_fnum) // Find a suitable window for opening a file (qf_fnum) from the // quickfix/location list and jump to it. If the file is already opened in a -// window, jump to it. Otherwise open a new window to display the file. This is -// called from either a quickfix or a location list window. -static int qf_jump_to_usable_window(int qf_fnum, int *opened_window) +// window, jump to it. Otherwise open a new window to display the file. If +// 'newwin' is true, then always open a new window. This is called from either +// a quickfix or a location list window. +static int qf_jump_to_usable_window(int qf_fnum, bool newwin, + int *opened_window) { win_T *usable_win_ptr = NULL; bool usable_win = false; - qf_info_T *ll_ref = curwin->w_llist_ref; + // If opening a new window, then don't use the location list referred by + // the current window. Otherwise two windows will refer to the same + // location list. + qf_info_T *ll_ref = newwin ? NULL : curwin->w_llist_ref; if (ll_ref != NULL) { // Find a non-quickfix window with this location list usable_win_ptr = qf_find_win_with_loclist(ll_ref); @@ -2654,7 +2696,7 @@ static int qf_jump_to_usable_window(int qf_fnum, int *opened_window) // If there is only one window and it is the quickfix window, create a // new one above the quickfix window. - if ((ONE_WINDOW && bt_quickfix(curbuf)) || !usable_win) { + if ((ONE_WINDOW && bt_quickfix(curbuf)) || !usable_win || newwin) { if (qf_open_new_file_win(ll_ref) != OK) { return FAIL; } @@ -2672,52 +2714,52 @@ static int qf_jump_to_usable_window(int qf_fnum, int *opened_window) /// Edit the selected file or help file. static int qf_jump_edit_buffer(qf_info_T *qi, qfline_T *qf_ptr, int forceit, - win_T *oldwin, int *opened_window, int *abort) + win_T *oldwin, int *opened_window) { qf_list_T *qfl = qf_get_curlist(qi); + long old_changetick = qfl->qf_changedtick; + int old_qf_curlist = qi->qf_curlist; qfltype_T qfl_type = qfl->qfl_type; int retval = OK; + unsigned save_qfid = qfl->qf_id; if (qf_ptr->qf_type == 1) { // Open help file (do_ecmd() will set b_help flag, readfile() will // set b_p_ro flag). if (!can_abandon(curbuf, forceit)) { no_write_message(); - retval = FAIL; + return FAIL; } else { retval = do_ecmd(qf_ptr->qf_fnum, NULL, NULL, NULL, (linenr_T)1, ECMD_HIDE + ECMD_SET_HELP, oldwin == curwin ? curwin : NULL); } } else { - unsigned save_qfid = qfl->qf_id; - retval = buflist_getfile(qf_ptr->qf_fnum, (linenr_T)1, GETF_SETMARK | GETF_SWITCH, forceit); + } + // If a location list, check whether the associated window is still + // present. + if (qfl_type == QFLT_LOCATION && !win_valid_any_tab(oldwin)) { + EMSG(_("E924: Current window was closed")); + *opened_window = false; + return NOTDONE; + } - if (qfl_type == QFLT_LOCATION) { - // Location list. Check whether the associated window is still - // present and the list is still valid. - if (!win_valid_any_tab(oldwin)) { - EMSG(_("E924: Current window was closed")); - *abort = true; - *opened_window = false; - } else if (!qflist_valid(oldwin, save_qfid)) { - EMSG(_(e_loc_list_changed)); - *abort = true; - } - } else if (!is_qf_entry_present(qfl, qf_ptr)) { - if (qfl_type == QFLT_QUICKFIX) { - EMSG(_("E925: Current quickfix was changed")); - } else { - EMSG(_(e_loc_list_changed)); - } - *abort = true; - } + if (qfl_type == QFLT_QUICKFIX && !qflist_valid(NULL, save_qfid)) { + EMSG(_(e_current_quickfix_list_was_changed)); + return NOTDONE; + } - if (*abort) { - retval = FAIL; + if (old_qf_curlist != qi->qf_curlist + || old_changetick != qfl->qf_changedtick + || !is_qf_entry_present(qfl, qf_ptr)) { + if (qfl_type == QFLT_QUICKFIX) { + EMSG(_(e_current_quickfix_list_was_changed)); + } else { + EMSG(_(e_current_location_list_was_changed)); } + return NOTDONE; } return retval; @@ -2792,22 +2834,133 @@ static void qf_jump_print_msg(qf_info_T *qi, int qf_index, qfline_T *qf_ptr, msg_scroll = (int)i; } -/// jump to a quickfix line -/// if dir == FORWARD go "errornr" valid entries forward -/// if dir == BACKWARD go "errornr" valid entries backward -/// if dir == FORWARD_FILE go "errornr" valid entries files backward -/// if dir == BACKWARD_FILE go "errornr" valid entries files backward -/// else if "errornr" is zero, redisplay the same line -/// else go to entry "errornr" +/// Find a usable window for opening a file from the quickfix/location list. If +/// a window is not found then open a new window. If 'newwin' is true, then open +/// a new window. +/// Returns OK if successfully jumped or opened a window. Returns FAIL if not +/// able to jump/open a window. Returns NOTDONE if a file is not associated +/// with the entry. +static int qf_jump_open_window(qf_info_T *qi, qfline_T *qf_ptr, bool newwin, + int *opened_window) +{ + qf_list_T *qfl = qf_get_curlist(qi); + long old_changetick = qfl->qf_changedtick; + int old_qf_curlist = qi->qf_curlist; + qfltype_T qfl_type = qfl->qfl_type; + + // For ":helpgrep" find a help window or open one. + if (qf_ptr->qf_type == 1 && (!bt_help(curwin->w_buffer) || cmdmod.tab != 0)) { + if (jump_to_help_window(qi, newwin, opened_window) == FAIL) { + return FAIL; + } + } + if (old_qf_curlist != qi->qf_curlist + || old_changetick != qfl->qf_changedtick + || !is_qf_entry_present(qfl, qf_ptr)) { + if (qfl_type == QFLT_QUICKFIX) { + EMSG(_(e_current_quickfix_list_was_changed)); + } else { + EMSG(_(e_current_location_list_was_changed)); + } + return FAIL; + } + + // If currently in the quickfix window, find another window to show the + // file in. + if (bt_quickfix(curbuf) && !*opened_window) { + // If there is no file specified, we don't know where to go. + // But do advance, otherwise ":cn" gets stuck. + if (qf_ptr->qf_fnum == 0) { + return NOTDONE; + } + + if (qf_jump_to_usable_window(qf_ptr->qf_fnum, newwin, opened_window) + == FAIL) { + return FAIL; + } + } + if (old_qf_curlist != qi->qf_curlist + || old_changetick != qfl->qf_changedtick + || !is_qf_entry_present(qfl, qf_ptr)) { + if (qfl_type == QFLT_QUICKFIX) { + EMSG(_(e_current_quickfix_list_was_changed)); + } else { + EMSG(_(e_current_location_list_was_changed)); + } + return FAIL; + } + + return OK; +} + +/// Edit a selected file from the quickfix/location list and jump to a +/// particular line/column, adjust the folds and display a message about the +/// jump. +/// Returns OK on success and FAIL on failing to open the file/buffer. Returns +/// NOTDONE if the quickfix/location list is freed by an autocmd when opening +/// the file. +static int qf_jump_to_buffer(qf_info_T *qi, int qf_index, qfline_T *qf_ptr, + int forceit, win_T *oldwin, int *opened_window, + int openfold, int print_message) +{ + buf_T *old_curbuf; + linenr_T old_lnum; + int retval = OK; + + // If there is a file name, read the wanted file if needed, and check + // autowrite etc. + old_curbuf = curbuf; + old_lnum = curwin->w_cursor.lnum; + + if (qf_ptr->qf_fnum != 0) { + retval = qf_jump_edit_buffer(qi, qf_ptr, forceit, oldwin, + opened_window); + if (retval != OK) { + return retval; + } + } + + // When not switched to another buffer, still need to set pc mark + if (curbuf == old_curbuf) { + setpcmark(); + } + + qf_jump_goto_line(qf_ptr->qf_lnum, qf_ptr->qf_col, qf_ptr->qf_viscol, + qf_ptr->qf_pattern); + + if ((fdo_flags & FDO_QUICKFIX) && openfold) { + foldOpenCursor(); + } + if (print_message) { + qf_jump_print_msg(qi, qf_index, qf_ptr, old_curbuf, old_lnum); + } + + return retval; +} + +/// Jump to a quickfix line and try to use an existing window. void qf_jump(qf_info_T *qi, int dir, int errornr, int forceit) { + qf_jump_newwin(qi, dir, errornr, forceit, false); +} + +// Jump to a quickfix line. +// If dir == 0 go to entry "errornr". +// If dir == FORWARD go "errornr" valid entries forward. +// If dir == BACKWARD go "errornr" valid entries backward. +// If dir == FORWARD_FILE go "errornr" valid entries files backward. +// If dir == BACKWARD_FILE go "errornr" valid entries files backward +// else if "errornr" is zero, redisplay the same line +// If 'forceit' is true, then can discard changes to the current buffer. +// If 'newwin' is true, then open the file in a new window. +static void qf_jump_newwin(qf_info_T *qi, int dir, int errornr, int forceit, + bool newwin) +{ qf_list_T *qfl; qfline_T *qf_ptr; qfline_T *old_qf_ptr; int qf_index; int old_qf_index; - buf_T *old_curbuf; - linenr_T old_lnum; char_u *old_swb = p_swb; unsigned old_swb_flags = swb_flags; int opened_window = false; @@ -2830,15 +2983,12 @@ void qf_jump(qf_info_T *qi, int dir, int errornr, int forceit) old_qf_ptr = qf_ptr; qf_index = qfl->qf_index; old_qf_index = qf_index; - if (dir != 0) { // next/prev valid entry - qf_ptr = get_nth_valid_entry(qfl, errornr, qf_ptr, &qf_index, dir); - if (qf_ptr == NULL) { - qf_ptr = old_qf_ptr; - qf_index = old_qf_index; - goto theend; // The horror... the horror... - } - } else if (errornr != 0) { // go to specified number - qf_ptr = get_nth_entry(qfl, errornr, qf_ptr, &qf_index); + + qf_ptr = qf_get_entry(qfl, errornr, dir, &qf_index); + if (qf_ptr == NULL) { + qf_ptr = old_qf_ptr; + qf_index = old_qf_index; + goto theend; } qfl->qf_index = qf_index; @@ -2848,58 +2998,23 @@ void qf_jump(qf_info_T *qi, int dir, int errornr, int forceit) print_message = false; } - // For ":helpgrep" find a help window or open one. - if (qf_ptr->qf_type == 1 && (!bt_help(curwin->w_buffer) || cmdmod.tab != 0)) { - if (jump_to_help_window(qi, &opened_window) == FAIL) { - goto theend; - } + retval = qf_jump_open_window(qi, qf_ptr, newwin, &opened_window); + if (retval == FAIL) { + goto failed; } - - // If currently in the quickfix window, find another window to show the - // file in. - if (bt_quickfix(curbuf) && !opened_window) { - // If there is no file specified, we don't know where to go. - // But do advance, otherwise ":cn" gets stuck. - if (qf_ptr->qf_fnum == 0) { - goto theend; - } - if (qf_jump_to_usable_window(qf_ptr->qf_fnum, &opened_window) == FAIL) { - goto failed; - } + if (retval == NOTDONE) { + goto theend; } - // If there is a file name, - // read the wanted file if needed, and check autowrite etc. - old_curbuf = curbuf; - old_lnum = curwin->w_cursor.lnum; - - if (qf_ptr->qf_fnum != 0) { - int abort = false; - retval = qf_jump_edit_buffer(qi, qf_ptr, forceit, oldwin, &opened_window, - &abort); - if (abort) { - qi = NULL; - qf_ptr = NULL; - } + retval = qf_jump_to_buffer(qi, qf_index, qf_ptr, forceit, oldwin, + &opened_window, old_KeyTyped, print_message); + if (retval == NOTDONE) { + // Quickfix/location list is freed by an autocmd + qi = NULL; + qf_ptr = NULL; } - if (retval == OK) { - // When not switched to another buffer, still need to set pc mark - if (curbuf == old_curbuf) { - setpcmark(); - } - - if (qf_ptr != NULL) { - qf_jump_goto_line(qf_ptr->qf_lnum, qf_ptr->qf_col, qf_ptr->qf_viscol, - qf_ptr->qf_pattern); - } - - if ((fdo_flags & FDO_QUICKFIX) && old_KeyTyped) - foldOpenCursor(); - if (print_message) { - qf_jump_print_msg(qi, qf_index, qf_ptr, old_curbuf, old_lnum); - } - } else { + if (retval != OK) { if (opened_window) { win_close(curwin, true); // Close opened window } @@ -3185,7 +3300,7 @@ void qf_history(exarg_T *eap) qf_info_T *qi = qf_cmd_get_stack(eap, false); int i; - if (qf_stack_empty(qi) || qf_list_empty(qf_get_curlist(qi))) { + if (qf_stack_empty(qi)) { MSG(_("No entries")); } else { for (i = 0; i < qi->qf_listcount; i++) { @@ -3353,14 +3468,9 @@ void qf_view_result(bool split) } if (split) { - char cmd[32]; - - snprintf(cmd, sizeof(cmd), "split +%" PRId64 "%s", - (int64_t)curwin->w_cursor.lnum, - IS_LL_WINDOW(curwin) ? "ll" : "cc"); - if (do_cmdline_cmd(cmd) == OK) { - do_cmdline_cmd("clearjumps"); - } + // Open the selected entry in a new window + qf_jump_newwin(qi, 0, (int)curwin->w_cursor.lnum, false, true); + do_cmdline_cmd("clearjumps"); return; } @@ -3391,7 +3501,7 @@ void ex_cwindow(exarg_T *eap) // it if we have errors; otherwise, leave it closed. if (qf_stack_empty(qi) || qfl->qf_nonevalid - || qf_list_empty(qf_get_curlist(qi))) { + || qf_list_empty(qfl)) { if (win != NULL) { ex_cclose(eap); } @@ -3444,10 +3554,23 @@ static int qf_goto_cwindow(const qf_info_T *qi, bool resize, int sz, return OK; } +// Set options for the buffer in the quickfix or location list window. +static void qf_set_cwindow_options(void) +{ + // switch off 'swapfile' + set_option_value("swf", 0L, NULL, OPT_LOCAL); + set_option_value("bt", 0L, "quickfix", OPT_LOCAL); + set_option_value("bh", 0L, "wipe", OPT_LOCAL); + RESET_BINDING(curwin); + curwin->w_p_diff = false; + set_option_value("fdm", 0L, "manual", OPT_LOCAL); +} + // Open a new quickfix or location list window, load the quickfix buffer and // set the appropriate options for the window. // Returns FAIL if the window could not be opened. -static int qf_open_new_cwindow(const qf_info_T *qi, int height) +static int qf_open_new_cwindow(qf_info_T *qi, int height) + FUNC_ATTR_NONNULL_ALL { win_T *oldwin = curwin; const tabpage_T *const prevtab = curtab; @@ -3490,14 +3613,13 @@ static int qf_open_new_cwindow(const qf_info_T *qi, int height) } else { // Create a new quickfix buffer (void)do_ecmd(0, NULL, NULL, NULL, ECMD_ONE, ECMD_HIDE, oldwin); + } - // switch off 'swapfile' - set_option_value("swf", 0L, NULL, OPT_LOCAL); - set_option_value("bt", 0L, "quickfix", OPT_LOCAL); - set_option_value("bh", 0L, "wipe", OPT_LOCAL); - RESET_BINDING(curwin); - curwin->w_p_diff = false; - set_option_value("fdm", 0L, "manual", OPT_LOCAL); + // Set the options for the quickfix buffer/window (if not already done) + // Do this even if the quickfix buffer was already present, as an autocmd + // might have previously deleted (:bdelete) the quickfix buffer. + if (curbuf->b_p_bt[0] != 'q') { + qf_set_cwindow_options(); } // Only set the height when still in the same tab page and there is no @@ -3585,7 +3707,7 @@ static void qf_win_goto(win_T *win, linenr_T lnum) curwin->w_cursor.coladd = 0; curwin->w_curswant = 0; update_topline(); // scroll to show the line - redraw_later(VALID); + redraw_later(curwin, VALID); curwin->w_redr_status = true; // update ruler curwin = old_curwin; curbuf = curwin->w_buffer; @@ -3686,8 +3808,8 @@ static win_T *qf_find_win(const qf_info_T *qi) // Find a quickfix buffer. // Searches in windows opened in all the tabs. -static buf_T *qf_find_buf(const qf_info_T *qi) - FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +static buf_T *qf_find_buf(qf_info_T *qi) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { FOR_ALL_TAB_WINDOWS(tp, win) { if (is_qf_win(win, qi)) { @@ -3750,56 +3872,63 @@ static void qf_update_buffer(qf_info_T *qi, qfline_T *old_last) // Add an error line to the quickfix buffer. static int qf_buf_add_line(buf_T *buf, linenr_T lnum, const qfline_T *qfp, - char_u *dirname) + char_u *dirname, bool first_bufline) FUNC_ATTR_NONNULL_ALL { int len; buf_T *errbuf; if (qfp->qf_module != NULL) { - STRCPY(IObuff, qfp->qf_module); + STRLCPY(IObuff, qfp->qf_module, IOSIZE - 1); len = (int)STRLEN(IObuff); } else if (qfp->qf_fnum != 0 && (errbuf = buflist_findnr(qfp->qf_fnum)) != NULL && errbuf->b_fname != NULL) { if (qfp->qf_type == 1) { // :helpgrep - STRLCPY(IObuff, path_tail(errbuf->b_fname), sizeof(IObuff)); + STRLCPY(IObuff, path_tail(errbuf->b_fname), IOSIZE - 1); } else { - // shorten the file name if not done already - if (errbuf->b_sfname == NULL - || path_is_absolute(errbuf->b_sfname)) { + // Shorten the file name if not done already. + // For optimization, do this only for the first entry in a + // buffer. + if (first_bufline + && (errbuf->b_sfname == NULL + || path_is_absolute(errbuf->b_sfname))) { if (*dirname == NUL) { os_dirname(dirname, MAXPATHL); } shorten_buf_fname(errbuf, dirname, false); } - STRLCPY(IObuff, errbuf->b_fname, sizeof(IObuff)); + STRLCPY(IObuff, errbuf->b_fname, IOSIZE - 1); } len = (int)STRLEN(IObuff); } else { len = 0; } - IObuff[len++] = '|'; - + if (len < IOSIZE - 1) { + IObuff[len++] = '|'; + } if (qfp->qf_lnum > 0) { - snprintf((char *)IObuff + len, sizeof(IObuff), "%" PRId64, + snprintf((char *)IObuff + len, (size_t)(IOSIZE - len), "%" PRId64, (int64_t)qfp->qf_lnum); len += (int)STRLEN(IObuff + len); if (qfp->qf_col > 0) { - snprintf((char *)IObuff + len, sizeof(IObuff), " col %d", qfp->qf_col); + snprintf((char *)IObuff + len, (size_t)(IOSIZE - len), " col %d", + qfp->qf_col); len += (int)STRLEN(IObuff + len); } - snprintf((char *)IObuff + len, sizeof(IObuff), "%s", + snprintf((char *)IObuff + len, (size_t)(IOSIZE - len), "%s", (char *)qf_types(qfp->qf_type, qfp->qf_nr)); len += (int)STRLEN(IObuff + len); } else if (qfp->qf_pattern != NULL) { qf_fmt_text(qfp->qf_pattern, IObuff + len, IOSIZE - len); len += (int)STRLEN(IObuff + len); } - IObuff[len++] = '|'; - IObuff[len++] = ' '; + if (len < IOSIZE - 2) { + IObuff[len++] = '|'; + IObuff[len++] = ' '; + } // Remove newlines and leading whitespace from the text. // For an unrecognized line keep the indent, the compiler may @@ -3841,6 +3970,7 @@ static void qf_fill_buffer(qf_list_T *qfl, buf_T *buf, qfline_T *old_last) // Check if there is anything to display if (qfl != NULL) { char_u dirname[MAXPATHL]; + int prev_bufnr = -1; *dirname = NUL; @@ -3853,9 +3983,11 @@ static void qf_fill_buffer(qf_list_T *qfl, buf_T *buf, qfline_T *old_last) lnum = buf->b_ml.ml_line_count; } while (lnum < qfl->qf_count) { - if (qf_buf_add_line(buf, lnum, qfp, dirname) == FAIL) { + if (qf_buf_add_line(buf, lnum, qfp, dirname, + prev_bufnr != qfp->qf_fnum) == FAIL) { break; } + prev_bufnr = qfp->qf_fnum; lnum++; qfp = qfp->qf_next; if (qfp == NULL) { @@ -4802,7 +4934,7 @@ static bool vgr_qflist_valid(win_T *wp, qf_info_T *qi, unsigned qfid, if (!qflist_valid(wp, qfid)) { if (wp != NULL) { // An autocmd has freed the location list - EMSG(_(e_loc_list_changed)); + EMSG(_(e_current_location_list_was_changed)); return false; } else { // Quickfix list is not found, create a new one. @@ -4820,20 +4952,21 @@ static bool vgr_qflist_valid(win_T *wp, qf_info_T *qi, unsigned qfid, /// Search for a pattern in all the lines in a buffer and add the matching lines /// to a quickfix list. -static bool vgr_match_buflines(qf_info_T *qi, char_u *fname, buf_T *buf, - regmmatch_T *regmatch, long tomatch, +static bool vgr_match_buflines(qf_list_T *qfl, char_u *fname, buf_T *buf, + regmmatch_T *regmatch, long *tomatch, int duplicate_name, int flags) + FUNC_ATTR_NONNULL_ARG(1, 3, 4, 5) { bool found_match = false; - for (long lnum = 1; lnum <= buf->b_ml.ml_line_count && tomatch > 0; lnum++) { + for (long lnum = 1; lnum <= buf->b_ml.ml_line_count && *tomatch > 0; lnum++) { colnr_T col = 0; while (vim_regexec_multi(regmatch, curwin, buf, lnum, col, NULL, NULL) > 0) { // Pass the buffer number so that it gets used even for a // dummy buffer, unless duplicate_name is set, then the // buffer will be wiped out below. - if (qf_add_entry(qf_get_curlist(qi), + if (qf_add_entry(qfl, NULL, // dir fname, NULL, @@ -4852,7 +4985,7 @@ static bool vgr_match_buflines(qf_info_T *qi, char_u *fname, buf_T *buf, break; } found_match = true; - if (--tomatch == 0) { + if (--*tomatch == 0) { break; } if ((flags & VGR_GLOBAL) == 0 || regmatch->endpos[0].lnum > 0) { @@ -4894,6 +5027,20 @@ static void vgr_jump_to_match(qf_info_T *qi, int forceit, int *redraw_for_dummy, } } +// Return true if "buf" had an existing swap file, the current swap file does +// not end in ".swp". +static bool existing_swapfile(const buf_T *buf) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ + if (buf->b_ml.ml_mfp != NULL && buf->b_ml.ml_mfp->mf_fname != NULL) { + const char_u *const fname = buf->b_ml.ml_mfp->mf_fname; + const size_t len = STRLEN(fname); + + return fname[len - 1] != 'p' || fname[len - 2] != 'w'; + } + return false; +} + // ":vimgrep {pattern} file(s)" // ":vimgrepadd {pattern} file(s)" // ":lvimgrep {pattern} file(s)" @@ -5026,7 +5173,8 @@ void ex_vimgrep(exarg_T *eap) } else { // Try for a match in all lines of the buffer. // For ":1vimgrep" look for first match only. - found_match = vgr_match_buflines(qi, fname, buf, ®match, tomatch, + found_match = vgr_match_buflines(qf_get_curlist(qi), + fname, buf, ®match, &tomatch, duplicate_name, flags); if (using_dummy) { @@ -5050,7 +5198,9 @@ void ex_vimgrep(exarg_T *eap) if (!found_match) { wipe_dummy_buffer(buf, dirname_start); buf = NULL; - } else if (buf != first_match_buf || (flags & VGR_NOJUMP)) { + } else if (buf != first_match_buf + || (flags & VGR_NOJUMP) + || existing_swapfile(buf)) { unload_dummy_buffer(buf, dirname_start); // Keeping the buffer, remove the dummy flag. buf->b_flags &= ~BF_DUMMY; @@ -6016,6 +6166,49 @@ static int qf_setprop_context(qf_list_T *qfl, dictitem_T *di) return OK; } +// Set the current index in the specified quickfix list +static int qf_setprop_curidx(qf_info_T *qi, qf_list_T *qfl, + const dictitem_T *di) + FUNC_ATTR_NONNULL_ALL +{ + int newidx; + + // If the specified index is '$', then use the last entry + if (di->di_tv.v_type == VAR_STRING + && di->di_tv.vval.v_string != NULL + && STRCMP(di->di_tv.vval.v_string, "$") == 0) { + newidx = qfl->qf_count; + } else { + // Otherwise use the specified index + bool denote = false; + newidx = (int)tv_get_number_chk(&di->di_tv, &denote); + if (denote) { + return FAIL; + } + } + + if (newidx < 1) { // sanity check + return FAIL; + } + if (newidx > qfl->qf_count) { + newidx = qfl->qf_count; + } + const int old_qfidx = qfl->qf_index; + qfline_T *const qf_ptr = get_nth_entry(qfl, newidx, &newidx); + if (qf_ptr == NULL) { + return FAIL; + } + qfl->qf_ptr = qf_ptr; + qfl->qf_index = newidx; + + // If the current list is modified and it is displayed in the quickfix + // window, then Update it. + if (qi->qf_lists[qi->qf_curlist].qf_id == qfl->qf_id) { + qf_win_pos_update(qi, old_qfidx); + } + return OK; +} + /// Set quickfix/location list properties (title, items, context). /// Also used to add items from parsing a list of lines. /// Used by the setqflist() and setloclist() Vim script functions. @@ -6051,6 +6244,9 @@ static int qf_set_properties(qf_info_T *qi, const dict_T *what, int action, if ((di = tv_dict_find(what, S_LEN("context"))) != NULL) { retval = qf_setprop_context(qfl, di); } + if ((di = tv_dict_find(what, S_LEN("idx"))) != NULL) { + retval = qf_setprop_curidx(qi, qfl, di); + } if (retval == OK) { qf_list_changed(qfl); @@ -6061,7 +6257,8 @@ static int qf_set_properties(qf_info_T *qi, const dict_T *what, int action, /// Find the non-location list window with the specified location list stack in /// the current tabpage. -static win_T * find_win_with_ll(qf_info_T *qi) +static win_T *find_win_with_ll(const qf_info_T *qi) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { if ((wp->w_llist == qi) && !bt_quickfix(wp->w_buffer)) { @@ -6120,6 +6317,7 @@ static void qf_free_stack(win_T *wp, qf_info_T *qi) // Populate the quickfix list with the items supplied in the list // of dictionaries. "title" will be copied to w:quickfix_title // "action" is 'a' for add, 'r' for replace. Otherwise create a new list. +// When "what" is not NULL then only set some properties. int set_errorlist(win_T *wp, list_T *list, int action, char_u *title, dict_T *what) { @@ -6136,6 +6334,12 @@ int set_errorlist(win_T *wp, list_T *list, int action, char_u *title, return OK; } + // A dict argument cannot be specified with a non-empty list argument + if (list != NULL && tv_list_len(list) != 0 && what != NULL) { + EMSG2(_(e_invarg2), _("cannot have both a list and a \"what\" argument")); + return FAIL; + } + incr_quickfix_busy(); if (what != NULL) { @@ -6364,7 +6568,7 @@ void ex_cexpr(exarg_T *eap) // Evaluate the expression. When the result is a string or a list we can // use it to fill the errorlist. typval_T tv; - if (eval0(eap->arg, &tv, NULL, true) != FAIL) { + if (eval0(eap->arg, &tv, &eap->nextcmd, true) != FAIL) { if ((tv.v_type == VAR_STRING && tv.vval.v_string != NULL) || tv.v_type == VAR_LIST) { incr_quickfix_busy(); @@ -6435,7 +6639,7 @@ static qf_info_T *hgr_get_ll(bool *new_ll) // Search for a pattern in a help file. static void hgr_search_file( - qf_info_T *qi, + qf_list_T *qfl, char_u *fname, regmatch_T *p_regmatch) FUNC_ATTR_NONNULL_ARG(1, 3) @@ -6457,7 +6661,7 @@ static void hgr_search_file( line[--l] = NUL; } - if (qf_add_entry(qf_get_curlist(qi), + if (qf_add_entry(qfl, NULL, // dir fname, NULL, @@ -6490,7 +6694,7 @@ static void hgr_search_file( // Search for a pattern in all the help files in the doc directory under // the given directory. static void hgr_search_files_in_dir( - qf_info_T *qi, + qf_list_T *qfl, char_u *dirname, regmatch_T *p_regmatch, const char_u *lang) @@ -6515,7 +6719,7 @@ static void hgr_search_files_in_dir( continue; } - hgr_search_file(qi, fnames[fi], p_regmatch); + hgr_search_file(qfl, fnames[fi], p_regmatch); } FreeWild(fcount, fnames); } @@ -6525,7 +6729,7 @@ static void hgr_search_files_in_dir( // and add the matches to a quickfix list. // 'lang' is the language specifier. If supplied, then only matches in the // specified language are found. -static void hgr_search_in_rtp(qf_info_T *qi, regmatch_T *p_regmatch, +static void hgr_search_in_rtp(qf_list_T *qfl, regmatch_T *p_regmatch, const char_u *lang) FUNC_ATTR_NONNULL_ARG(1, 2) { @@ -6534,7 +6738,7 @@ static void hgr_search_in_rtp(qf_info_T *qi, regmatch_T *p_regmatch, while (*p != NUL && !got_int) { copy_option_part(&p, NameBuff, MAXPATHL, ","); - hgr_search_files_in_dir(qi, NameBuff, p_regmatch, lang); + hgr_search_files_in_dir(qfl, NameBuff, p_regmatch, lang); } } @@ -6576,12 +6780,12 @@ void ex_helpgrep(exarg_T *eap) if (regmatch.regprog != NULL) { // Create a new quickfix list. qf_new_list(qi, qf_cmdtitle(*eap->cmdlinep)); + qf_list_T *const qfl = qf_get_curlist(qi); - hgr_search_in_rtp(qi, ®match, lang); + hgr_search_in_rtp(qfl, ®match, lang); vim_regfree(regmatch.regprog); - qf_list_T *qfl = qf_get_curlist(qi); qfl->qf_nonevalid = false; qfl->qf_ptr = qfl->qf_start; qfl->qf_index = 1; diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c index 34553fcec4..6316129c6a 100644 --- a/src/nvim/regexp.c +++ b/src/nvim/regexp.c @@ -40,11 +40,11 @@ * Named character class support added by Walter Briscoe (1998 Jul 01) */ -/* Uncomment the first if you do not want to see debugging logs or files - * related to regular expressions, even when compiling with -DDEBUG. - * Uncomment the second to get the regexp debugging. */ -/* #undef REGEXP_DEBUG */ -/* #define REGEXP_DEBUG */ +// By default: do not create debugging logs or files related to regular +// expressions, even when compiling with -DDEBUG. +// Uncomment the second line to get the regexp debugging. +// #undef REGEXP_DEBUG +// #define REGEXP_DEBUG #include <assert.h> #include <inttypes.h> @@ -301,8 +301,8 @@ typedef struct { */ typedef struct { union { - char_u *ptr; /* reginput pointer, for single-line regexp */ - lpos_T pos; /* reginput pos, for multi-line regexp */ + char_u *ptr; ///< rex.input pointer, for single-line regexp + lpos_T pos; ///< rex.input pos, for multi-line regexp } rs_u; int rs_len; } regsave_T; @@ -355,7 +355,7 @@ typedef struct regitem_S { union { save_se_T sesave; regsave_T regsave; - } rs_un; // room for saving reginput + } rs_un; ///< room for saving rex.input } regitem_T; @@ -490,6 +490,8 @@ static char_u e_z_not_allowed[] = N_("E66: \\z( not allowed here"); static char_u e_z1_not_allowed[] = N_("E67: \\z1 - \\z9 not allowed here"); static char_u e_missing_sb[] = N_("E69: Missing ] after %s%%["); static char_u e_empty_sb[] = N_("E70: Empty %s%%[]"); +static char_u e_recursive[] = N_("E956: Cannot use pattern recursively"); + #define NOT_MULTI 0 #define MULTI_ONE 1 #define MULTI_MULT 2 @@ -600,6 +602,12 @@ static int get_char_class(char_u **pp) #define CLASS_BACKSPACE 14 "escape:]", #define CLASS_ESCAPE 15 + "ident:]", +#define CLASS_IDENT 16 + "keyword:]", +#define CLASS_KEYWORD 17 + "fname:]", +#define CLASS_FNAME 18 }; #define CLASS_NONE 99 int i; @@ -633,7 +641,7 @@ static short class_tab[256]; static void init_class_tab(void) { int i; - static int done = FALSE; + static int done = false; if (done) return; @@ -658,7 +666,7 @@ static void init_class_tab(void) } class_tab[' '] |= RI_WHITE; class_tab['\t'] |= RI_WHITE; - done = TRUE; + done = true; } # define ri_digit(c) (c < 0x100 && (class_tab[c] & RI_DIGIT)) @@ -678,26 +686,24 @@ static void init_class_tab(void) #define RF_ICOMBINE 8 /* ignore combining characters */ #define RF_LOOKBH 16 /* uses "\@<=" or "\@<!" */ -/* - * Global work variables for vim_regcomp(). - */ - -static char_u *regparse; /* Input-scan pointer. */ -static int prevchr_len; /* byte length of previous char */ -static int num_complex_braces; /* Complex \{...} count */ -static int regnpar; /* () count. */ -static int regnzpar; /* \z() count. */ -static int re_has_z; /* \z item detected */ -static char_u *regcode; /* Code-emit pointer, or JUST_CALC_SIZE */ -static long regsize; /* Code size. */ -static int reg_toolong; /* TRUE when offset out of range */ -static char_u had_endbrace[NSUBEXP]; /* flags, TRUE if end of () found */ -static unsigned regflags; /* RF_ flags for prog */ -static long brace_min[10]; /* Minimums for complex brace repeats */ -static long brace_max[10]; /* Maximums for complex brace repeats */ -static int brace_count[10]; /* Current counts for complex brace repeats */ -static int had_eol; /* TRUE when EOL found by vim_regcomp() */ -static int one_exactly = FALSE; /* only do one char for EXACTLY */ +// Global work variables for vim_regcomp(). + +static char_u *regparse; ///< Input-scan pointer. +static int prevchr_len; ///< byte length of previous char +static int num_complex_braces; ///< Complex \{...} count +static int regnpar; ///< () count. +static int regnzpar; ///< \z() count. +static int re_has_z; ///< \z item detected +static char_u *regcode; ///< Code-emit pointer, or JUST_CALC_SIZE +static long regsize; ///< Code size. +static int reg_toolong; ///< true when offset out of range +static char_u had_endbrace[NSUBEXP]; ///< flags, true if end of () found +static unsigned regflags; ///< RF_ flags for prog +static long brace_min[10]; ///< Minimums for complex brace repeats +static long brace_max[10]; ///< Maximums for complex brace repeats +static int brace_count[10]; ///< Current counts for complex brace repeats +static int had_eol; ///< true when EOL found by vim_regcomp() +static int one_exactly = false; ///< only do one char for EXACTLY static int reg_magic; /* magicness of the pattern: */ #define MAGIC_NONE 1 /* "\V" very unmagic */ @@ -754,10 +760,9 @@ static int nextchr; /* used for ungetchr() */ static regengine_T bt_regengine; static regengine_T nfa_regengine; -/* - * Return TRUE if compiled regular expression "prog" can match a line break. - */ -int re_multiline(regprog_T *prog) +// Return true if compiled regular expression "prog" can match a line break. +int re_multiline(const regprog_T *prog) + FUNC_ATTR_NONNULL_ALL { return prog->regflags & RF_HASNL; } @@ -1211,7 +1216,7 @@ char_u *skip_regexp(char_u *startp, int dirc, int magic, char_u **newp) return p; } -/// Return TRUE if the back reference is legal. We must have seen the close +/// Return true if the back reference is legal. We must have seen the close /// brace. /// TODO(vim): Should also check that we don't refer to something repeated /// (+*=): what instance of the repetition should we match? @@ -1234,7 +1239,7 @@ static int seen_endbrace(int refnum) return false; } } - return TRUE; + return true; } /* @@ -1265,8 +1270,9 @@ static regprog_T *bt_regcomp(char_u *expr, int re_flags) int len; int flags; - if (expr == NULL) - EMSG_RET_NULL(_(e_null)); + if (expr == NULL) { + IEMSG_RET_NULL(_(e_null)); + } init_class_tab(); @@ -1281,6 +1287,7 @@ static regprog_T *bt_regcomp(char_u *expr, int re_flags) /* Allocate space. */ bt_regprog_T *r = xmalloc(sizeof(bt_regprog_T) + regsize); + r->re_in_use = false; /* * Second pass: emit code. @@ -1394,9 +1401,9 @@ regcomp_start ( regnzpar = 1; re_has_z = 0; regsize = 0L; - reg_toolong = FALSE; + reg_toolong = false; regflags = 0; - had_eol = FALSE; + had_eol = false; } /* @@ -1408,7 +1415,7 @@ int vim_regcomp_had_eol(void) return had_eol; } -// variables for parsing reginput +// variables used for parsing static int at_start; // True when on the first character static int prev_at_start; // True when on the second character @@ -1506,12 +1513,11 @@ reg ( EMSG_RET_NULL(_(e_trailing)); /* "Can't happen". */ /* NOTREACHED */ } - /* - * Here we set the flag allowing back references to this set of - * parentheses. - */ - if (paren == REG_PAREN) - had_endbrace[parno] = TRUE; /* have seen the close paren */ + // Here we set the flag allowing back references to this set of + // parentheses. + if (paren == REG_PAREN) { + had_endbrace[parno] = true; // have seen the close paren + } return ret; } @@ -1565,7 +1571,7 @@ static char_u *regconcat(int *flagp) char_u *chain = NULL; char_u *latest; int flags; - int cont = TRUE; + int cont = true; *flagp = WORST; /* Tentatively. */ @@ -1575,7 +1581,7 @@ static char_u *regconcat(int *flagp) case Magic('|'): case Magic('&'): case Magic(')'): - cont = FALSE; + cont = false; break; case Magic('Z'): regflags |= RF_ICOMBINE; @@ -1802,7 +1808,7 @@ static char_u *regatom(int *flagp) case Magic('$'): ret = regnode(EOL); - had_eol = TRUE; + had_eol = true; break; case Magic('<'): @@ -1821,7 +1827,7 @@ static char_u *regatom(int *flagp) } if (c == '$') { /* "\_$" is end-of-line */ ret = regnode(EOL); - had_eol = TRUE; + had_eol = true; break; } @@ -2069,11 +2075,12 @@ static char_u *regatom(int *flagp) } ungetchr(); - one_exactly = TRUE; + one_exactly = true; lastnode = regatom(flagp); - one_exactly = FALSE; - if (lastnode == NULL) + one_exactly = false; + if (lastnode == NULL) { return NULL; + } } if (ret == NULL) EMSG2_RET_NULL(_(e_empty_sb), @@ -2417,6 +2424,27 @@ collection: case CLASS_ESCAPE: regc(ESC); break; + case CLASS_IDENT: + for (cu = 1; cu <= 255; cu++) { + if (vim_isIDc(cu)) { + regmbc(cu); + } + } + break; + case CLASS_KEYWORD: + for (cu = 1; cu <= 255; cu++) { + if (reg_iswordc(cu)) { + regmbc(cu); + } + } + break; + case CLASS_FNAME: + for (cu = 1; cu <= 255; cu++) { + if (vim_isfilec(cu)) { + regmbc(cu); + } + } + break; } } else { // produce a multibyte character, including any @@ -2514,15 +2542,13 @@ static bool re_mult_next(char *what) return true; } -/* - * Return TRUE if MULTIBYTECODE should be used instead of EXACTLY for - * character "c". - */ -static int use_multibytecode(int c) +// Return true if MULTIBYTECODE should be used instead of EXACTLY for +// character "c". +static bool use_multibytecode(int c) { - return has_mbyte && (*mb_char2len)(c) > 1 + return utf_char2len(c) > 1 && (re_multi_type(peekchr()) != NOT_MULTI - || (enc_utf8 && utf_iscomposing(c))); + || utf_iscomposing(c)); } /* @@ -2667,39 +2693,38 @@ static char_u *re_put_uint32(char_u *p, uint32_t val) return p; } -/* - * Set the next-pointer at the end of a node chain. - */ +// Set the next-pointer at the end of a node chain. static void regtail(char_u *p, char_u *val) { - char_u *scan; - char_u *temp; int offset; - if (p == JUST_CALC_SIZE) + if (p == JUST_CALC_SIZE) { return; + } - /* Find last node. */ - scan = p; + // Find last node. + char_u *scan = p; for (;; ) { - temp = regnext(scan); - if (temp == NULL) + char_u *temp = regnext(scan); + if (temp == NULL) { break; + } scan = temp; } - if (OP(scan) == BACK) + if (OP(scan) == BACK) { offset = (int)(scan - val); - else + } else { offset = (int)(val - scan); - /* When the offset uses more than 16 bits it can no longer fit in the two - * bytes available. Use a global flag to avoid having to check return - * values in too many places. */ - if (offset > 0xffff) - reg_toolong = TRUE; - else { - *(scan + 1) = (char_u) (((unsigned)offset >> 8) & 0377); - *(scan + 2) = (char_u) (offset & 0377); + } + // When the offset uses more than 16 bits it can no longer fit in the two + // bytes available. Use a global flag to avoid having to check return + // values in too many places. + if (offset > 0xffff) { + reg_toolong = true; + } else { + *(scan + 1) = (char_u)(((unsigned)offset >> 8) & 0377); + *(scan + 2) = (char_u)(offset & 0377); } } @@ -2728,8 +2753,8 @@ static void initchr(char_u *str) regparse = str; prevchr_len = 0; curchr = prevprevchr = prevchr = nextchr = -1; - at_start = TRUE; - prev_at_start = FALSE; + at_start = true; + prev_at_start = false; } /* @@ -2771,7 +2796,7 @@ static void restore_parse_state(parse_state_T *ps) */ static int peekchr(void) { - static int after_slash = FALSE; + static int after_slash = false; if (curchr != -1) { return curchr; @@ -2837,8 +2862,8 @@ static int peekchr(void) || (no_Magic(prevchr) == '(' && prevprevchr == Magic('%')))) { curchr = Magic('^'); - at_start = TRUE; - prev_at_start = FALSE; + at_start = true; + prev_at_start = false; } break; case '$': @@ -2889,12 +2914,12 @@ static int peekchr(void) */ curchr = -1; prev_at_start = at_start; - at_start = FALSE; /* be able to say "/\*ptr" */ - ++regparse; - ++after_slash; + at_start = false; // be able to say "/\*ptr" + regparse++; + after_slash++; peekchr(); - --regparse; - --after_slash; + regparse--; + after_slash--; curchr = toggle_Magic(curchr); } else if (vim_strchr(REGEXP_ABBR, c)) { /* @@ -2936,7 +2961,7 @@ static void skipchr(void) } regparse += prevchr_len; prev_at_start = at_start; - at_start = FALSE; + at_start = false; prevprevchr = prevchr; prevchr = curchr; curchr = nextchr; /* use previously unget char, or -1 */ @@ -2980,7 +3005,7 @@ static void ungetchr(void) curchr = prevchr; prevchr = prevprevchr; at_start = prev_at_start; - prev_at_start = FALSE; + prev_at_start = false; /* Backup regparse, so that it's at the same position as before the * getchr(). */ @@ -3101,14 +3126,14 @@ static int coll_get_char(void) */ static int read_limits(long *minval, long *maxval) { - int reverse = FALSE; + int reverse = false; char_u *first_char; long tmp; if (*regparse == '-') { // Starts with '-', so reverse the range later. regparse++; - reverse = TRUE; + reverse = true; } first_char = regparse; *minval = getdigits_long(®parse, false, 0); @@ -3153,17 +3178,6 @@ static int read_limits(long *minval, long *maxval) * Global work variables for vim_regexec(). */ -/* The current match-position is remembered with these variables: */ -static linenr_T reglnum; /* line number, relative to first line */ -static char_u *regline; /* start of current line */ -static char_u *reginput; /* current input, points into "regline" */ - -static int need_clear_subexpr; /* subexpressions still need to be - * cleared */ -static int need_clear_zsubexpr = FALSE; /* extmatch subexpressions - * still need to be cleared */ - - /* Save the sub-expressions before attempting a match. */ #define save_se(savep, posp, pp) \ REG_MULTI ? save_se_multi((savep), (posp)) : save_se_one((savep), (pp)) @@ -3214,18 +3228,42 @@ typedef struct { linenr_T reg_maxline; bool reg_line_lbr; // "\n" in string is line break + // The current match-position is remembered with these variables: + linenr_T lnum; ///< line number, relative to first line + char_u *line; ///< start of current line + char_u *input; ///< current input, points into "regline" + + int need_clear_subexpr; ///< subexpressions still need to be cleared + int need_clear_zsubexpr; ///< extmatch subexpressions still need to be + ///< cleared + + // Internal copy of 'ignorecase'. It is set at each call to vim_regexec(). // Normally it gets the value of "rm_ic" or "rmm_ic", but when the pattern // contains '\c' or '\C' the value is overruled. bool reg_ic; - // Similar to rex.reg_ic, but only for 'combining' characters. Set with \Z + // Similar to "reg_ic", but only for 'combining' characters. Set with \Z // flag in the regexp. Defaults to false, always. bool reg_icombine; // Copy of "rmm_maxcol": maximum column to search for a match. Zero when // there is no maximum. colnr_T reg_maxcol; + + // State for the NFA engine regexec. + int nfa_has_zend; ///< NFA regexp \ze operator encountered. + int nfa_has_backref; ///< NFA regexp \1 .. \9 encountered. + int nfa_nsubexpr; ///< Number of sub expressions actually being used + ///< during execution. 1 if only the whole match + ///< (subexpr 0) is used. + // listid is global, so that it increases on recursive calls to + // nfa_regmatch(), which means we don't have to clear the lastlist field of + // all the states. + int nfa_listid; + int nfa_alt_listid; + + int nfa_has_zsubexpr; ///< NFA regexp has \z( ), set zsubexpr. } regexec_T; static regexec_T rex; @@ -3266,6 +3304,13 @@ void free_regexp_stuff(void) #endif +// Return true if character 'c' is included in 'iskeyword' option for +// "reg_buf" buffer. +static bool reg_iswordc(int c) +{ + return vim_iswordc_buf(c, rex.reg_buf); +} + /* * Get pointer to the line "lnum", which is relative to "reg_firstlnum". */ @@ -3290,7 +3335,7 @@ static char_u *reg_endzp[NSUBEXP]; /* and end of \z(...\) matches */ static lpos_T reg_startzpos[NSUBEXP]; /* idem, beginning pos */ static lpos_T reg_endzpos[NSUBEXP]; /* idem, end pos */ -// TRUE if using multi-line regexp. +// true if using multi-line regexp. #define REG_MULTI (rex.reg_match == NULL) /* @@ -3439,7 +3484,7 @@ static long bt_regexec_both(char_u *line, /* Be paranoid... */ if (prog == NULL || line == NULL) { - EMSG(_(e_null)); + IEMSG(_(e_null)); goto theend; } @@ -3491,13 +3536,13 @@ static long bt_regexec_both(char_u *line, } } - regline = line; - reglnum = 0; - reg_toolong = FALSE; + rex.line = line; + rex.lnum = 0; + reg_toolong = false; /* Simplest case: Anchored match need be tried only once. */ if (prog->reganch) { - int c = utf_ptr2char(regline + col); + int c = utf_ptr2char(rex.line + col); if (prog->regstart == NUL || prog->regstart == c || (rex.reg_ic @@ -3514,12 +3559,12 @@ static long bt_regexec_both(char_u *line, while (!got_int) { if (prog->regstart != NUL) { // Skip until the char we know it must start with. - s = cstrchr(regline + col, prog->regstart); + s = cstrchr(rex.line + col, prog->regstart); if (s == NULL) { retval = 0; break; } - col = (int)(s - regline); + col = (int)(s - rex.line); } // Check for maximum column to try. @@ -3533,18 +3578,16 @@ static long bt_regexec_both(char_u *line, break; } - /* if not currently on the first line, get it again */ - if (reglnum != 0) { - reglnum = 0; - regline = reg_getline((linenr_T)0); + // if not currently on the first line, get it again + if (rex.lnum != 0) { + rex.lnum = 0; + rex.line = reg_getline((linenr_T)0); } - if (regline[col] == NUL) + if (rex.line[col] == NUL) { break; - if (has_mbyte) - col += (*mb_ptr2len)(regline + col); - else - ++col; - /* Check for timeout once in a twenty times to avoid overhead. */ + } + col += (*mb_ptr2len)(rex.line + col); + // Check for timeout once in a twenty times to avoid overhead. if (tm != NULL && ++tm_count == 20) { tm_count = 0; if (profile_passed_limit(*tm)) { @@ -3608,18 +3651,17 @@ void unref_extmatch(reg_extmatch_T *em) } } -/// Try match of "prog" with at regline["col"]. +/// Try match of "prog" with at rex.line["col"]. /// @returns 0 for failure, or number of lines contained in the match. static long regtry(bt_regprog_T *prog, colnr_T col, proftime_T *tm, // timeout limit or NULL int *timed_out) // flag set on timeout or NULL { - reginput = regline + col; - need_clear_subexpr = TRUE; - /* Clear the external match subpointers if necessary. */ - if (prog->reghasz == REX_SET) - need_clear_zsubexpr = TRUE; + rex.input = rex.line + col; + rex.need_clear_subexpr = true; + // Clear the external match subpointers if necessaey. + rex.need_clear_zsubexpr = (prog->reghasz == REX_SET); if (regmatch(prog->program + 1, tm, timed_out) == 0) { return 0; @@ -3632,18 +3674,18 @@ static long regtry(bt_regprog_T *prog, rex.reg_startpos[0].col = col; } if (rex.reg_endpos[0].lnum < 0) { - rex.reg_endpos[0].lnum = reglnum; - rex.reg_endpos[0].col = (int)(reginput - regline); + rex.reg_endpos[0].lnum = rex.lnum; + rex.reg_endpos[0].col = (int)(rex.input - rex.line); } else { // Use line number of "\ze". - reglnum = rex.reg_endpos[0].lnum; + rex.lnum = rex.reg_endpos[0].lnum; } } else { if (rex.reg_startp[0] == NULL) { - rex.reg_startp[0] = regline + col; + rex.reg_startp[0] = rex.line + col; } if (rex.reg_endp[0] == NULL) { - rex.reg_endp[0] = reginput; + rex.reg_endp[0] = rex.input; } } /* Package any found \z(...\) matches for export. Default is none. */ @@ -3675,23 +3717,24 @@ static long regtry(bt_regprog_T *prog, } } } - return 1 + reglnum; + return 1 + rex.lnum; } // Get class of previous character. static int reg_prev_class(void) { - if (reginput > regline) { - return mb_get_class_tab(reginput - 1 - utf_head_off(regline, reginput - 1), - rex.reg_buf->b_chartab); + if (rex.input > rex.line) { + return mb_get_class_tab( + rex.input - 1 - utf_head_off(rex.line, rex.input - 1), + rex.reg_buf->b_chartab); } return -1; } -// Return TRUE if the current reginput position matches the Visual area. -static int reg_match_visual(void) +// Return true if the current rex.input position matches the Visual area. +static bool reg_match_visual(void) { pos_T top, bot; linenr_T lnum; @@ -3725,16 +3768,17 @@ static int reg_match_visual(void) } mode = curbuf->b_visual.vi_mode; } - lnum = reglnum + rex.reg_firstlnum; + lnum = rex.lnum + rex.reg_firstlnum; if (lnum < top.lnum || lnum > bot.lnum) { return false; } if (mode == 'v') { - col = (colnr_T)(reginput - regline); + col = (colnr_T)(rex.input - rex.line); if ((lnum == top.lnum && col < top.col) - || (lnum == bot.lnum && col >= bot.col + (*p_sel != 'e'))) - return FALSE; + || (lnum == bot.lnum && col >= bot.col + (*p_sel != 'e'))) { + return false; + } } else if (mode == Ctrl_V) { getvvcol(wp, &top, &start, NULL, &end); getvvcol(wp, &bot, &start2, NULL, &end2); @@ -3744,17 +3788,18 @@ static int reg_match_visual(void) end = end2; if (top.col == MAXCOL || bot.col == MAXCOL) end = MAXCOL; - unsigned int cols_u = win_linetabsize(wp, regline, - (colnr_T)(reginput - regline)); + unsigned int cols_u = win_linetabsize(wp, rex.line, + (colnr_T)(rex.input - rex.line)); assert(cols_u <= MAXCOL); colnr_T cols = (colnr_T)cols_u; - if (cols < start || cols > end - (*p_sel == 'e')) - return FALSE; + if (cols < start || cols > end - (*p_sel == 'e')) { + return false; + } } - return TRUE; + return true; } -#define ADVANCE_REGINPUT() MB_PTR_ADV(reginput) +#define ADVANCE_REGINPUT() MB_PTR_ADV(rex.input) /* * The arguments from BRACE_LIMITS are stored here. They are actually local @@ -3773,11 +3818,11 @@ static long bl_maxval; /// (that don't need to know whether the rest of the match failed) by a nested /// loop. /// -/// Returns TRUE when there is a match. Leaves reginput and reglnum just after -/// the last matched character. -/// Returns FALSE when there is no match. Leaves reginput and reglnum in an +/// Returns true when there is a match. Leaves rex.input and rex.lnum +/// just after the last matched character. +/// Returns false when there is no match. Leaves rex.input and rex.lnum in an /// undefined state! -static int regmatch( +static bool regmatch( char_u *scan, // Current node. proftime_T *tm, // timeout limit or NULL int *timed_out // flag set on timeout or NULL @@ -3860,38 +3905,40 @@ static int regmatch( op = OP(scan); // Check for character class with NL added. if (!rex.reg_line_lbr && WITH_NL(op) && REG_MULTI - && *reginput == NUL && reglnum <= rex.reg_maxline) { + && *rex.input == NUL && rex.lnum <= rex.reg_maxline) { reg_nextline(); - } else if (rex.reg_line_lbr && WITH_NL(op) && *reginput == '\n') { + } else if (rex.reg_line_lbr && WITH_NL(op) && *rex.input == '\n') { ADVANCE_REGINPUT(); } else { if (WITH_NL(op)) { op -= ADD_NL; } - c = utf_ptr2char(reginput); + c = utf_ptr2char(rex.input); switch (op) { case BOL: - if (reginput != regline) + if (rex.input != rex.line) { status = RA_NOMATCH; + } break; case EOL: - if (c != NUL) + if (c != NUL) { status = RA_NOMATCH; + } break; case RE_BOF: // We're not at the beginning of the file when below the first // line where we started, not at the start of the line or we // didn't start at the first line of the buffer. - if (reglnum != 0 || reginput != regline + if (rex.lnum != 0 || rex.input != rex.line || (REG_MULTI && rex.reg_firstlnum > 1)) { status = RA_NOMATCH; } break; case RE_EOF: - if (reglnum != rex.reg_maxline || c != NUL) { + if (rex.lnum != rex.reg_maxline || c != NUL) { status = RA_NOMATCH; } break; @@ -3900,8 +3947,9 @@ static int regmatch( // Check if the buffer is in a window and compare the // rex.reg_win->w_cursor position to the match position. if (rex.reg_win == NULL - || (reglnum + rex.reg_firstlnum != rex.reg_win->w_cursor.lnum) - || ((colnr_T)(reginput - regline) != rex.reg_win->w_cursor.col)) { + || (rex.lnum + rex.reg_firstlnum != rex.reg_win->w_cursor.lnum) + || ((colnr_T)(rex.input - rex.line) != + rex.reg_win->w_cursor.col)) { status = RA_NOMATCH; } break; @@ -3916,13 +3964,13 @@ static int regmatch( pos = getmark_buf(rex.reg_buf, mark, false); if (pos == NULL // mark doesn't exist || pos->lnum <= 0 // mark isn't set in reg_buf - || (pos->lnum == reglnum + rex.reg_firstlnum - ? (pos->col == (colnr_T)(reginput - regline) + || (pos->lnum == rex.lnum + rex.reg_firstlnum + ? (pos->col == (colnr_T)(rex.input - rex.line) ? (cmp == '<' || cmp == '>') - : (pos->col < (colnr_T)(reginput - regline) + : (pos->col < (colnr_T)(rex.input - rex.line) ? cmp != '>' : cmp != '<')) - : (pos->lnum < reglnum + rex.reg_firstlnum + : (pos->lnum < rex.lnum + rex.reg_firstlnum ? cmp != '>' : cmp != '<'))) { status = RA_NOMATCH; @@ -3936,79 +3984,70 @@ static int regmatch( break; case RE_LNUM: - assert(reglnum + rex.reg_firstlnum >= 0 - && (uintmax_t)(reglnum + rex.reg_firstlnum) <= UINT32_MAX); + assert(rex.lnum + rex.reg_firstlnum >= 0 + && (uintmax_t)(rex.lnum + rex.reg_firstlnum) <= UINT32_MAX); if (!REG_MULTI - || !re_num_cmp((uint32_t)(reglnum + rex.reg_firstlnum), scan)) { + || !re_num_cmp((uint32_t)(rex.lnum + rex.reg_firstlnum), scan)) { status = RA_NOMATCH; } break; case RE_COL: - assert(reginput - regline + 1 >= 0 - && (uintmax_t)(reginput - regline + 1) <= UINT32_MAX); - if (!re_num_cmp((uint32_t)(reginput - regline + 1), scan)) + assert(rex.input - rex.line + 1 >= 0 + && (uintmax_t)(rex.input - rex.line + 1) <= UINT32_MAX); + if (!re_num_cmp((uint32_t)(rex.input - rex.line + 1), scan)) { status = RA_NOMATCH; + } break; case RE_VCOL: if (!re_num_cmp(win_linetabsize(rex.reg_win == NULL ? curwin : rex.reg_win, - regline, - (colnr_T)(reginput - regline)) + 1, + rex.line, + (colnr_T)(rex.input - rex.line)) + 1, scan)) { status = RA_NOMATCH; } break; - case BOW: /* \<word; reginput points to w */ - if (c == NUL) /* Can't match at end of line */ + case BOW: // \<word; rex.input points to w + if (c == NUL) { // Can't match at end of line status = RA_NOMATCH; - else if (has_mbyte) { - int this_class; - + } else { // Get class of current and previous char (if it exists). - this_class = mb_get_class_tab(reginput, rex.reg_buf->b_chartab); + const int this_class = + mb_get_class_tab(rex.input, rex.reg_buf->b_chartab); if (this_class <= 1) { status = RA_NOMATCH; // Not on a word at all. } else if (reg_prev_class() == this_class) { status = RA_NOMATCH; // Previous char is in same word. } - } else { - if (!vim_iswordc_buf(c, rex.reg_buf) - || (reginput > regline - && vim_iswordc_buf(reginput[-1], rex.reg_buf))) { - status = RA_NOMATCH; - } } break; - case EOW: /* word\>; reginput points after d */ - if (reginput == regline) /* Can't match at start of line */ + case EOW: // word\>; rex.input points after d + if (rex.input == rex.line) { // Can't match at start of line status = RA_NOMATCH; - else if (has_mbyte) { + } else { int this_class, prev_class; // Get class of current and previous char (if it exists). - this_class = mb_get_class_tab(reginput, rex.reg_buf->b_chartab); + this_class = mb_get_class_tab(rex.input, rex.reg_buf->b_chartab); prev_class = reg_prev_class(); if (this_class == prev_class - || prev_class == 0 || prev_class == 1) - status = RA_NOMATCH; - } else { - if (!vim_iswordc_buf(reginput[-1], rex.reg_buf) - || (reginput[0] != NUL && vim_iswordc_buf(c, rex.reg_buf))) { + || prev_class == 0 || prev_class == 1) { status = RA_NOMATCH; } } - break; /* Matched with EOW */ + break; // Matched with EOW case ANY: - /* ANY does not match new lines. */ - if (c == NUL) + // ANY does not match new lines. + if (c == NUL) { status = RA_NOMATCH; - else + } else { ADVANCE_REGINPUT(); + } break; case IDENT: @@ -4019,14 +4058,15 @@ static int regmatch( break; case SIDENT: - if (ascii_isdigit(*reginput) || !vim_isIDc(c)) + if (ascii_isdigit(*rex.input) || !vim_isIDc(c)) { status = RA_NOMATCH; - else + } else { ADVANCE_REGINPUT(); + } break; case KWORD: - if (!vim_iswordp_buf(reginput, rex.reg_buf)) { + if (!vim_iswordp_buf(rex.input, rex.reg_buf)) { status = RA_NOMATCH; } else { ADVANCE_REGINPUT(); @@ -4034,8 +4074,8 @@ static int regmatch( break; case SKWORD: - if (ascii_isdigit(*reginput) - || !vim_iswordp_buf(reginput, rex.reg_buf)) { + if (ascii_isdigit(*rex.input) + || !vim_iswordp_buf(rex.input, rex.reg_buf)) { status = RA_NOMATCH; } else { ADVANCE_REGINPUT(); @@ -4043,31 +4083,35 @@ static int regmatch( break; case FNAME: - if (!vim_isfilec(c)) + if (!vim_isfilec(c)) { status = RA_NOMATCH; - else + } else { ADVANCE_REGINPUT(); + } break; case SFNAME: - if (ascii_isdigit(*reginput) || !vim_isfilec(c)) + if (ascii_isdigit(*rex.input) || !vim_isfilec(c)) { status = RA_NOMATCH; - else + } else { ADVANCE_REGINPUT(); + } break; case PRINT: - if (!vim_isprintc(PTR2CHAR(reginput))) + if (!vim_isprintc(PTR2CHAR(rex.input))) { status = RA_NOMATCH; - else + } else { ADVANCE_REGINPUT(); + } break; case SPRINT: - if (ascii_isdigit(*reginput) || !vim_isprintc(PTR2CHAR(reginput))) + if (ascii_isdigit(*rex.input) || !vim_isprintc(PTR2CHAR(rex.input))) { status = RA_NOMATCH; - else + } else { ADVANCE_REGINPUT(); + } break; case WHITE: @@ -4203,10 +4247,10 @@ static int regmatch( opnd = OPERAND(scan); // Inline the first byte, for speed. - if (*opnd != *reginput + if (*opnd != *rex.input && (!rex.reg_ic || (!enc_utf8 - && mb_tolower(*opnd) != mb_tolower(*reginput)))) { + && mb_tolower(*opnd) != mb_tolower(*rex.input)))) { status = RA_NOMATCH; } else if (*opnd == NUL) { // match empty string always works; happens when "~" is @@ -4217,14 +4261,14 @@ static int regmatch( } else { // Need to match first byte again for multi-byte. len = (int)STRLEN(opnd); - if (cstrncmp(opnd, reginput, &len) != 0) { + if (cstrncmp(opnd, rex.input, &len) != 0) { status = RA_NOMATCH; } } // Check for following composing character, unless %C // follows (skips over all composing chars). if (status != RA_NOMATCH && enc_utf8 - && UTF_COMPOSINGLIKE(reginput, reginput + len) + && UTF_COMPOSINGLIKE(rex.input, rex.input + len) && !rex.reg_icombine && OP(next) != RE_COMPOSING) { // raaron: This code makes a composing character get @@ -4233,7 +4277,7 @@ static int regmatch( status = RA_NOMATCH; } if (status != RA_NOMATCH) { - reginput += len; + rex.input += len; } } } @@ -4250,54 +4294,52 @@ static int regmatch( break; case MULTIBYTECODE: - if (has_mbyte) { + { int i, len; - char_u *opnd; - int opndc = 0, inpc; - opnd = OPERAND(scan); + const char_u *opnd = OPERAND(scan); // Safety check (just in case 'encoding' was changed since // compiling the program). if ((len = (*mb_ptr2len)(opnd)) < 2) { status = RA_NOMATCH; break; } - if (enc_utf8) { - opndc = utf_ptr2char(opnd); - } - if (enc_utf8 && utf_iscomposing(opndc)) { - /* When only a composing char is given match at any - * position where that composing char appears. */ + const int opndc = utf_ptr2char(opnd); + if (utf_iscomposing(opndc)) { + // When only a composing char is given match at any + // position where that composing char appears. status = RA_NOMATCH; - for (i = 0; reginput[i] != NUL; i += utf_ptr2len(reginput + i)) { - inpc = utf_ptr2char(reginput + i); + for (i = 0; rex.input[i] != NUL; + i += utf_ptr2len(rex.input + i)) { + const int inpc = utf_ptr2char(rex.input + i); if (!utf_iscomposing(inpc)) { if (i > 0) { break; } } else if (opndc == inpc) { // Include all following composing chars. - len = i + utfc_ptr2len(reginput + i); + len = i + utfc_ptr2len(rex.input + i); status = RA_MATCH; break; } } - } else - for (i = 0; i < len; ++i) - if (opnd[i] != reginput[i]) { + } else { + for (i = 0; i < len; i++) { + if (opnd[i] != rex.input[i]) { status = RA_NOMATCH; break; } - reginput += len; - } else - status = RA_NOMATCH; + } + } + rex.input += len; + } break; case RE_COMPOSING: if (enc_utf8) { // Skip composing characters. - while (utf_iscomposing(utf_ptr2char(reginput))) { - MB_CPTR_ADV(reginput); + while (utf_iscomposing(utf_ptr2char(rex.input))) { + MB_CPTR_ADV(rex.input); } } break; @@ -4460,7 +4502,7 @@ static int regmatch( } else { // Compare current input with back-ref in the same line. len = (int)(rex.reg_endp[no] - rex.reg_startp[no]); - if (cstrncmp(rex.reg_startp[no], reginput, &len) != 0) { + if (cstrncmp(rex.reg_startp[no], rex.input, &len) != 0) { status = RA_NOMATCH; } } @@ -4469,12 +4511,12 @@ static int regmatch( // Backref was not set: Match an empty string. len = 0; } else { - if (rex.reg_startpos[no].lnum == reglnum - && rex.reg_endpos[no].lnum == reglnum) { + if (rex.reg_startpos[no].lnum == rex.lnum + && rex.reg_endpos[no].lnum == rex.lnum) { // Compare back-ref within the current line. len = rex.reg_endpos[no].col - rex.reg_startpos[no].col; - if (cstrncmp(regline + rex.reg_startpos[no].col, - reginput, &len) != 0) { + if (cstrncmp(rex.line + rex.reg_startpos[no].col, + rex.input, &len) != 0) { status = RA_NOMATCH; } } else { @@ -4491,8 +4533,8 @@ static int regmatch( } } - /* Matched the backref, skip over it. */ - reginput += len; + // Matched the backref, skip over it. + rex.input += len; } break; @@ -4506,20 +4548,18 @@ static int regmatch( case ZREF + 8: case ZREF + 9: { - int len; - cleanup_zsubexpr(); no = op - ZREF; if (re_extmatch_in != NULL && re_extmatch_in->matches[no] != NULL) { - len = (int)STRLEN(re_extmatch_in->matches[no]); - if (cstrncmp(re_extmatch_in->matches[no], - reginput, &len) != 0) + int len = (int)STRLEN(re_extmatch_in->matches[no]); + if (cstrncmp(re_extmatch_in->matches[no], rex.input, &len) != 0) { status = RA_NOMATCH; - else - reginput += len; + } else { + rex.input += len; + } } else { - /* Backref was not set: Match an empty string. */ + // Backref was not set: Match an empty string. } } break; @@ -4725,15 +4765,17 @@ static int regmatch( case BHPOS: if (REG_MULTI) { - if (behind_pos.rs_u.pos.col != (colnr_T)(reginput - regline) - || behind_pos.rs_u.pos.lnum != reglnum) + if (behind_pos.rs_u.pos.col != (colnr_T)(rex.input - rex.line) + || behind_pos.rs_u.pos.lnum != rex.lnum) { status = RA_NOMATCH; - } else if (behind_pos.rs_u.ptr != reginput) + } + } else if (behind_pos.rs_u.ptr != rex.input) { status = RA_NOMATCH; + } break; case NEWL: - if ((c != NUL || !REG_MULTI || reglnum > rex.reg_maxline + if ((c != NUL || !REG_MULTI || rex.lnum > rex.reg_maxline || rex.reg_line_lbr) && (c != '\n' || !rex.reg_line_lbr)) { status = RA_NOMATCH; } else if (rex.reg_line_lbr) { @@ -4748,7 +4790,7 @@ static int regmatch( break; default: - EMSG(_(e_re_corr)); + IEMSG(_(e_re_corr)); #ifdef REGEXP_DEBUG printf("Illegal op code %d\n", op); #endif @@ -4946,7 +4988,7 @@ static int regmatch( if (limit > 0 && ((rp->rs_un.regsave.rs_u.pos.lnum < behind_pos.rs_u.pos.lnum - ? (colnr_T)STRLEN(regline) + ? (colnr_T)STRLEN(rex.line) : behind_pos.rs_u.pos.col) - rp->rs_un.regsave.rs_u.pos.col >= limit)) no = FAIL; @@ -4960,7 +5002,7 @@ static int regmatch( else { reg_restore(&rp->rs_un.regsave, &backpos); rp->rs_un.regsave.rs_u.pos.col = - (colnr_T)STRLEN(regline); + (colnr_T)STRLEN(rex.line); } } else { const char_u *const line = @@ -4972,10 +5014,10 @@ static int regmatch( + 1; } } else { - if (rp->rs_un.regsave.rs_u.ptr == regline) { + if (rp->rs_un.regsave.rs_u.ptr == rex.line) { no = FAIL; } else { - MB_PTR_BACK(regline, rp->rs_un.regsave.rs_u.ptr); + MB_PTR_BACK(rex.line, rp->rs_un.regsave.rs_u.ptr); if (limit > 0 && (long)(behind_pos.rs_u.ptr - rp->rs_un.regsave.rs_u.ptr) > limit) { @@ -5039,18 +5081,18 @@ static int regmatch( * didn't match -- back up one char. */ if (--rst->count < rst->minval) break; - if (reginput == regline) { + if (rex.input == rex.line) { // backup to last char of previous line - reglnum--; - regline = reg_getline(reglnum); + rex.lnum--; + rex.line = reg_getline(rex.lnum); // Just in case regrepeat() didn't count right. - if (regline == NULL) { + if (rex.line == NULL) { break; } - reginput = regline + STRLEN(regline); + rex.input = rex.line + STRLEN(rex.line); fast_breakcheck(); } else { - MB_PTR_BACK(regline, reginput); + MB_PTR_BACK(rex.line, rex.input); } } else { /* Range is backwards, use shortest match first. @@ -5067,9 +5109,9 @@ static int regmatch( } else status = RA_NOMATCH; - /* If it could match, try it. */ - if (rst->nextb == NUL || *reginput == rst->nextb - || *reginput == rst->nextb_ic) { + // If it could match, try it. + if (rst->nextb == NUL || *rex.input == rst->nextb + || *rex.input == rst->nextb_ic) { reg_save(&rp->rs_un.regsave, &backpos); scan = regnext(rp->rs_scan); status = RA_CONT; @@ -5106,7 +5148,7 @@ static int regmatch( * We get here only if there's trouble -- normally "case END" is * the terminating point. */ - EMSG(_(e_re_corr)); + IEMSG(_(e_re_corr)); #ifdef REGEXP_DEBUG printf("Premature EOL\n"); #endif @@ -5156,7 +5198,7 @@ static void regstack_pop(char_u **scan) /* * regrepeat - repeatedly match something simple, return how many. - * Advances reginput (and reglnum) to just after the matched chars. + * Advances rex.input (and rex.lnum) to just after the matched chars. */ static int regrepeat ( @@ -5165,12 +5207,11 @@ regrepeat ( ) { long count = 0; - char_u *scan; char_u *opnd; int mask; int testval = 0; - scan = reginput; /* Make local copy of reginput for speed. */ + char_u *scan = rex.input; // Make local copy of rex.input for speed. opnd = OPERAND(p); switch (OP(p)) { case ANY: @@ -5182,15 +5223,16 @@ regrepeat ( count++; MB_PTR_ADV(scan); } - if (!REG_MULTI || !WITH_NL(OP(p)) || reglnum > rex.reg_maxline + if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline || rex.reg_line_lbr || count == maxcount) { break; } count++; // count the line-break reg_nextline(); - scan = reginput; - if (got_int) + scan = rex.input; + if (got_int) { break; + } } break; @@ -5204,14 +5246,15 @@ regrepeat ( if (vim_isIDc(PTR2CHAR(scan)) && (testval || !ascii_isdigit(*scan))) { MB_PTR_ADV(scan); } else if (*scan == NUL) { - if (!REG_MULTI || !WITH_NL(OP(p)) || reglnum > rex.reg_maxline + if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline || rex.reg_line_lbr) { break; } reg_nextline(); - scan = reginput; - if (got_int) + scan = rex.input; + if (got_int) { break; + } } else if (rex.reg_line_lbr && *scan == '\n' && WITH_NL(OP(p))) { scan++; } else { @@ -5232,12 +5275,12 @@ regrepeat ( && (testval || !ascii_isdigit(*scan))) { MB_PTR_ADV(scan); } else if (*scan == NUL) { - if (!REG_MULTI || !WITH_NL(OP(p)) || reglnum > rex.reg_maxline + if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline || rex.reg_line_lbr) { break; } reg_nextline(); - scan = reginput; + scan = rex.input; if (got_int) { break; } @@ -5260,12 +5303,12 @@ regrepeat ( if (vim_isfilec(PTR2CHAR(scan)) && (testval || !ascii_isdigit(*scan))) { MB_PTR_ADV(scan); } else if (*scan == NUL) { - if (!REG_MULTI || !WITH_NL(OP(p)) || reglnum > rex.reg_maxline + if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline || rex.reg_line_lbr) { break; } reg_nextline(); - scan = reginput; + scan = rex.input; if (got_int) { break; } @@ -5286,12 +5329,12 @@ regrepeat ( case SPRINT + ADD_NL: while (count < maxcount) { if (*scan == NUL) { - if (!REG_MULTI || !WITH_NL(OP(p)) || reglnum > rex.reg_maxline + if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline || rex.reg_line_lbr) { break; } reg_nextline(); - scan = reginput; + scan = rex.input; if (got_int) { break; } @@ -5314,14 +5357,15 @@ do_class: while (count < maxcount) { int l; if (*scan == NUL) { - if (!REG_MULTI || !WITH_NL(OP(p)) || reglnum > rex.reg_maxline + if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline || rex.reg_line_lbr) { break; } reg_nextline(); - scan = reginput; - if (got_int) + scan = rex.input; + if (got_int) { break; + } } else if (has_mbyte && (l = (*mb_ptr2len)(scan)) > 1) { if (testval != 0) break; @@ -5467,12 +5511,12 @@ do_class: while (count < maxcount) { int len; if (*scan == NUL) { - if (!REG_MULTI || !WITH_NL(OP(p)) || reglnum > rex.reg_maxline + if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline || rex.reg_line_lbr) { break; } reg_nextline(); - scan = reginput; + scan = rex.input; if (got_int) { break; } @@ -5494,7 +5538,7 @@ do_class: case NEWL: while (count < maxcount - && ((*scan == NUL && reglnum <= rex.reg_maxline && !rex.reg_line_lbr + && ((*scan == NUL && rex.lnum <= rex.reg_maxline && !rex.reg_line_lbr && REG_MULTI) || (*scan == '\n' && rex.reg_line_lbr))) { count++; if (rex.reg_line_lbr) { @@ -5502,21 +5546,22 @@ do_class: } else { reg_nextline(); } - scan = reginput; - if (got_int) + scan = rex.input; + if (got_int) { break; + } } break; - default: /* Oh dear. Called inappropriately. */ - EMSG(_(e_re_corr)); + default: // Oh dear. Called inappropriately. + IEMSG(_(e_re_corr)); #ifdef REGEXP_DEBUG printf("Called regrepeat with op code %d\n", OP(p)); #endif break; } - reginput = scan; + rex.input = scan; return (int)count; } @@ -5546,7 +5591,7 @@ static char_u *regnext(char_u *p) /* * Check the regexp program for its magic number. - * Return TRUE if it's wrong. + * Return true if it's wrong. */ static int prog_magic_wrong(void) { @@ -5560,9 +5605,9 @@ static int prog_magic_wrong(void) if (UCHARAT(((bt_regprog_T *)prog)->program) != REGMAGIC) { EMSG(_(e_re_corr)); - return TRUE; + return true; } - return FALSE; + return false; } /* @@ -5572,7 +5617,7 @@ static int prog_magic_wrong(void) */ static void cleanup_subexpr(void) { - if (need_clear_subexpr) { + if (rex.need_clear_subexpr) { if (REG_MULTI) { // Use 0xff to set lnum to -1 memset(rex.reg_startpos, 0xff, sizeof(lpos_T) * NSUBEXP); @@ -5581,13 +5626,13 @@ static void cleanup_subexpr(void) memset(rex.reg_startp, 0, sizeof(char_u *) * NSUBEXP); memset(rex.reg_endp, 0, sizeof(char_u *) * NSUBEXP); } - need_clear_subexpr = FALSE; + rex.need_clear_subexpr = false; } } static void cleanup_zsubexpr(void) { - if (need_clear_zsubexpr) { + if (rex.need_clear_zsubexpr) { if (REG_MULTI) { /* Use 0xff to set lnum to -1 */ memset(reg_startzpos, 0xff, sizeof(lpos_T) * NSUBEXP); @@ -5596,23 +5641,20 @@ static void cleanup_zsubexpr(void) memset(reg_startzp, 0, sizeof(char_u *) * NSUBEXP); memset(reg_endzp, 0, sizeof(char_u *) * NSUBEXP); } - need_clear_zsubexpr = FALSE; + rex.need_clear_zsubexpr = false; } } -/* - * Save the current subexpr to "bp", so that they can be restored - * later by restore_subexpr(). - */ +// Save the current subexpr to "bp", so that they can be restored +// later by restore_subexpr(). static void save_subexpr(regbehind_T *bp) + FUNC_ATTR_NONNULL_ALL { - int i; - - // When "need_clear_subexpr" is set we don't need to save the values, only + // When "rex.need_clear_subexpr" is set we don't need to save the values, only // remember that this flag needs to be set again when restoring. - bp->save_need_clear_subexpr = need_clear_subexpr; - if (!need_clear_subexpr) { - for (i = 0; i < NSUBEXP; ++i) { + bp->save_need_clear_subexpr = rex.need_clear_subexpr; + if (!rex.need_clear_subexpr) { + for (int i = 0; i < NSUBEXP; i++) { if (REG_MULTI) { bp->save_start[i].se_u.pos = rex.reg_startpos[i]; bp->save_end[i].se_u.pos = rex.reg_endpos[i]; @@ -5624,17 +5666,14 @@ static void save_subexpr(regbehind_T *bp) } } -/* - * Restore the subexpr from "bp". - */ +// Restore the subexpr from "bp". static void restore_subexpr(regbehind_T *bp) + FUNC_ATTR_NONNULL_ALL { - int i; - - /* Only need to restore saved values when they are not to be cleared. */ - need_clear_subexpr = bp->save_need_clear_subexpr; - if (!need_clear_subexpr) { - for (i = 0; i < NSUBEXP; ++i) { + // Only need to restore saved values when they are not to be cleared. + rex.need_clear_subexpr = bp->save_need_clear_subexpr; + if (!rex.need_clear_subexpr) { + for (int i = 0; i < NSUBEXP; i++) { if (REG_MULTI) { rex.reg_startpos[i] = bp->save_start[i].se_u.pos; rex.reg_endpos[i] = bp->save_end[i].se_u.pos; @@ -5646,56 +5685,54 @@ static void restore_subexpr(regbehind_T *bp) } } -/* - * Advance reglnum, regline and reginput to the next line. - */ +// Advance rex.lnum, rex.line and rex.input to the next line. static void reg_nextline(void) { - regline = reg_getline(++reglnum); - reginput = regline; + rex.line = reg_getline(++rex.lnum); + rex.input = rex.line; fast_breakcheck(); } -/* - * Save the input line and position in a regsave_T. - */ +// Save the input line and position in a regsave_T. static void reg_save(regsave_T *save, garray_T *gap) + FUNC_ATTR_NONNULL_ALL { if (REG_MULTI) { - save->rs_u.pos.col = (colnr_T)(reginput - regline); - save->rs_u.pos.lnum = reglnum; - } else - save->rs_u.ptr = reginput; + save->rs_u.pos.col = (colnr_T)(rex.input - rex.line); + save->rs_u.pos.lnum = rex.lnum; + } else { + save->rs_u.ptr = rex.input; + } save->rs_len = gap->ga_len; } -/* - * Restore the input line and position from a regsave_T. - */ +// Restore the input line and position from a regsave_T. static void reg_restore(regsave_T *save, garray_T *gap) + FUNC_ATTR_NONNULL_ALL { if (REG_MULTI) { - if (reglnum != save->rs_u.pos.lnum) { - /* only call reg_getline() when the line number changed to save - * a bit of time */ - reglnum = save->rs_u.pos.lnum; - regline = reg_getline(reglnum); + if (rex.lnum != save->rs_u.pos.lnum) { + // only call reg_getline() when the line number changed to save + // a bit of time + rex.lnum = save->rs_u.pos.lnum; + rex.line = reg_getline(rex.lnum); } - reginput = regline + save->rs_u.pos.col; - } else - reginput = save->rs_u.ptr; + rex.input = rex.line + save->rs_u.pos.col; + } else { + rex.input = save->rs_u.ptr; + } gap->ga_len = save->rs_len; } -/* - * Return TRUE if current position is equal to saved position. - */ -static int reg_save_equal(regsave_T *save) +// Return true if current position is equal to saved position. +static bool reg_save_equal(const regsave_T *save) + FUNC_ATTR_NONNULL_ALL { - if (REG_MULTI) - return reglnum == save->rs_u.pos.lnum - && reginput == regline + save->rs_u.pos.col; - return reginput == save->rs_u.ptr; + if (REG_MULTI) { + return rex.lnum == save->rs_u.pos.lnum + && rex.input == rex.line + save->rs_u.pos.col; + } + return rex.input == save->rs_u.ptr; } /* @@ -5708,14 +5745,14 @@ static int reg_save_equal(regsave_T *save) static void save_se_multi(save_se_T *savep, lpos_T *posp) { savep->se_u.pos = *posp; - posp->lnum = reglnum; - posp->col = (colnr_T)(reginput - regline); + posp->lnum = rex.lnum; + posp->col = (colnr_T)(rex.input - rex.line); } static void save_se_one(save_se_T *savep, char_u **pp) { savep->se_u.ptr = *pp; - *pp = reginput; + *pp = rex.input; } /* @@ -5750,17 +5787,17 @@ static int match_with_backref(linenr_T start_lnum, colnr_T start_col, linenr_T e for (;; ) { /* Since getting one line may invalidate the other, need to make copy. * Slow! */ - if (regline != reg_tofree) { - len = (int)STRLEN(regline); + if (rex.line != reg_tofree) { + len = (int)STRLEN(rex.line); if (reg_tofree == NULL || len >= (int)reg_tofreelen) { len += 50; /* get some extra */ xfree(reg_tofree); reg_tofree = xmalloc(len); reg_tofreelen = len; } - STRCPY(reg_tofree, regline); - reginput = reg_tofree + (reginput - regline); - regline = reg_tofree; + STRCPY(reg_tofree, rex.line); + rex.input = reg_tofree + (rex.input - rex.line); + rex.line = reg_tofree; } /* Get the line to compare with. */ @@ -5772,14 +5809,16 @@ static int match_with_backref(linenr_T start_lnum, colnr_T start_col, linenr_T e else len = (int)STRLEN(p + ccol); - if (cstrncmp(p + ccol, reginput, &len) != 0) - return RA_NOMATCH; /* doesn't match */ - if (bytelen != NULL) + if (cstrncmp(p + ccol, rex.input, &len) != 0) { + return RA_NOMATCH; // doesn't match + } + if (bytelen != NULL) { *bytelen += len; + } if (clnum == end_lnum) { break; // match and at end! } - if (reglnum >= rex.reg_maxline) { + if (rex.lnum >= rex.reg_maxline) { return RA_NOMATCH; // text too short } @@ -5793,8 +5832,8 @@ static int match_with_backref(linenr_T start_lnum, colnr_T start_col, linenr_T e return RA_FAIL; } - /* found a match! Note that regline may now point to a copy of the line, - * that should not matter. */ + // found a match! Note that rex.line may now point to a copy of the line, + // that should not matter. return RA_MATCH; } @@ -6477,7 +6516,7 @@ char_u *regtilde(char_u *source, int magic) return newsub; } -static int can_f_submatch = FALSE; /* TRUE when submatch() can be used */ +static bool can_f_submatch = false; // true when submatch() can be used // These pointers are used for reg_submatch(). Needed for when the // substitution string is an expression that contains a call to substitute() @@ -6534,11 +6573,11 @@ static void clear_submatch_list(staticList10_T *sl) /// vim_regsub() - perform substitutions after a vim_regexec() or /// vim_regexec_multi() match. /// -/// If "copy" is TRUE really copy into "dest". -/// If "copy" is FALSE nothing is copied, this is just to find out the length +/// If "copy" is true really copy into "dest". +/// If "copy" is false nothing is copied, this is just to find out the length /// of the result. /// -/// If "backslash" is TRUE, a backslash will be removed later, need to double +/// If "backslash" is true, a backslash will be removed later, need to double /// them to keep them, and insert a backslash before a CR to avoid it being /// replaced with a line break later. /// @@ -6630,8 +6669,8 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, if (expr != NULL || (source[0] == '\\' && source[1] == '=')) { // To make sure that the length doesn't change between checking the // length and copying the string, and to speed up things, the - // resulting string is saved from the call with "copy" == FALSE to the - // call with "copy" == TRUE. + // resulting string is saved from the call with "copy" == false to the + // call with "copy" == true. if (copy) { if (eval_result != NULL) { STRCPY(dest, eval_result); @@ -6639,7 +6678,7 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, XFREE_CLEAR(eval_result); } } else { - int prev_can_f_submatch = can_f_submatch; + const bool prev_can_f_submatch = can_f_submatch; regsubmatch_T rsm_save; xfree(eval_result); @@ -6669,14 +6708,14 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, argv[0].vval.v_list = &matchList.sl_list; if (expr->v_type == VAR_FUNC) { s = expr->vval.v_string; - call_func(s, (int)STRLEN(s), &rettv, 1, argv, + call_func(s, -1, &rettv, 1, argv, fill_submatch_list, 0L, 0L, &dummy, true, NULL, NULL); } else if (expr->v_type == VAR_PARTIAL) { partial_T *partial = expr->vval.v_partial; s = partial_name(partial); - call_func(s, (int)STRLEN(s), &rettv, 1, argv, + call_func(s, -1, &rettv, 1, argv, fill_submatch_list, 0L, 0L, &dummy, true, partial, NULL); } @@ -6700,7 +6739,7 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, } if (eval_result != NULL) { - int had_backslash = FALSE; + int had_backslash = false; for (s = eval_result; *s != NUL; MB_PTR_ADV(s)) { // Change NL to CR, so that it becomes a line break, @@ -6778,22 +6817,24 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, } if (c == '\\' && *src != NUL) { - /* Check for abbreviations -- webb */ + // Check for abbreviations -- webb switch (*src) { case 'r': c = CAR; ++src; break; case 'n': c = NL; ++src; break; case 't': c = TAB; ++src; break; - /* Oh no! \e already has meaning in subst pat :-( */ - /* case 'e': c = ESC; ++src; break; */ + // Oh no! \e already has meaning in subst pat :-( + // case 'e': c = ESC; ++src; break; case 'b': c = Ctrl_H; ++src; break; - /* If "backslash" is TRUE the backslash will be removed - * later. Used to insert a literal CR. */ - default: if (backslash) { - if (copy) + // If "backslash" is true the backslash will be removed + // later. Used to insert a literal CR. + default: + if (backslash) { + if (copy) { *dst = '\\'; - ++dst; - } + } + dst++; + } c = *src++; } } else { @@ -6871,7 +6912,7 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, } } else if (*s == NUL) { // we hit NUL. if (copy) { - EMSG(_(e_re_damg)); + IEMSG(_(e_re_damg)); } goto exit; } else { @@ -7163,8 +7204,10 @@ regprog_T *vim_regcomp(char_u *expr_arg, int re_flags) regexp_engine = AUTOMATIC_ENGINE; } } +#ifdef REGEXP_DEBUG bt_regengine.expr = expr; nfa_regengine.expr = expr; +#endif // reg_iswordc() uses rex.reg_buf rex.reg_buf = curbuf; @@ -7245,24 +7288,33 @@ static void report_re_switch(char_u *pat) /// @param col the column to start looking for match /// @param nl /// -/// @return TRUE if there is a match, FALSE if not. -static int vim_regexec_string(regmatch_T *rmp, char_u *line, colnr_T col, - bool nl) +/// @return true if there is a match, false if not. +static bool vim_regexec_string(regmatch_T *rmp, char_u *line, colnr_T col, + bool nl) { regexec_T rex_save; bool rex_in_use_save = rex_in_use; + // Cannot use the same prog recursively, it contains state. + if (rmp->regprog->re_in_use) { + EMSG(_(e_recursive)); + return false; + } + rmp->regprog->re_in_use = true; + if (rex_in_use) { // Being called recursively, save the state. rex_save = rex; } rex_in_use = true; + rex.reg_startp = NULL; rex.reg_endp = NULL; rex.reg_startpos = NULL; rex.reg_endpos = NULL; int result = rmp->regprog->engine->regexec_nl(rmp, line, col, nl); + rmp->regprog->re_in_use = false; // NFA engine aborted because it's very slow, use backtracking engine instead. if (rmp->regprog->re_engine == AUTOMATIC_ENGINE @@ -7276,7 +7328,9 @@ static int vim_regexec_string(regmatch_T *rmp, char_u *line, colnr_T col, report_re_switch(pat); rmp->regprog = vim_regcomp(pat, re_flags); if (rmp->regprog != NULL) { + rmp->regprog->re_in_use = true; result = rmp->regprog->engine->regexec_nl(rmp, line, col, nl); + rmp->regprog->re_in_use = false; } xfree(pat); @@ -7292,27 +7346,27 @@ static int vim_regexec_string(regmatch_T *rmp, char_u *line, colnr_T col, } // Note: "*prog" may be freed and changed. -// Return TRUE if there is a match, FALSE if not. -int vim_regexec_prog(regprog_T **prog, bool ignore_case, char_u *line, +// Return true if there is a match, false if not. +bool vim_regexec_prog(regprog_T **prog, bool ignore_case, char_u *line, colnr_T col) { regmatch_T regmatch = { .regprog = *prog, .rm_ic = ignore_case }; - int r = vim_regexec_string(®match, line, col, false); + bool r = vim_regexec_string(®match, line, col, false); *prog = regmatch.regprog; return r; } // Note: "rmp->regprog" may be freed and changed. -// Return TRUE if there is a match, FALSE if not. -int vim_regexec(regmatch_T *rmp, char_u *line, colnr_T col) +// Return true if there is a match, false if not. +bool vim_regexec(regmatch_T *rmp, char_u *line, colnr_T col) { return vim_regexec_string(rmp, line, col, false); } // Like vim_regexec(), but consider a "\n" in "line" to be a line break. // Note: "rmp->regprog" may be freed and changed. -// Return TRUE if there is a match, FALSE if not. -int vim_regexec_nl(regmatch_T *rmp, char_u *line, colnr_T col) +// Return true if there is a match, false if not. +bool vim_regexec_nl(regmatch_T *rmp, char_u *line, colnr_T col) { return vim_regexec_string(rmp, line, col, true); } @@ -7333,10 +7387,18 @@ long vim_regexec_multi( proftime_T *tm, // timeout limit or NULL int *timed_out // flag is set when timeout limit reached ) + FUNC_ATTR_NONNULL_ARG(1) { regexec_T rex_save; bool rex_in_use_save = rex_in_use; + // Cannot use the same prog recursively, it contains state. + if (rmp->regprog->re_in_use) { + EMSG(_(e_recursive)); + return false; + } + rmp->regprog->re_in_use = true; + if (rex_in_use) { // Being called recursively, save the state. rex_save = rex; @@ -7345,6 +7407,7 @@ long vim_regexec_multi( int result = rmp->regprog->engine->regexec_multi(rmp, win, buf, lnum, col, tm, timed_out); + rmp->regprog->re_in_use = false; // NFA engine aborted because it's very slow, use backtracking engine instead. if (rmp->regprog->re_engine == AUTOMATIC_ENGINE @@ -7363,8 +7426,10 @@ long vim_regexec_multi( reg_do_extmatch = 0; if (rmp->regprog != NULL) { + rmp->regprog->re_in_use = true; result = rmp->regprog->engine->regexec_multi(rmp, win, buf, lnum, col, tm, timed_out); + rmp->regprog->re_in_use = false; } xfree(pat); diff --git a/src/nvim/regexp_defs.h b/src/nvim/regexp_defs.h index 116bfee91e..a729a91555 100644 --- a/src/nvim/regexp_defs.h +++ b/src/nvim/regexp_defs.h @@ -72,6 +72,7 @@ struct regprog { unsigned regflags; unsigned re_engine; ///< Automatic, backtracking or NFA engine. unsigned re_flags; ///< Second argument for vim_regcomp(). + bool re_in_use; ///< prog is being executed }; /* @@ -84,7 +85,8 @@ typedef struct { regengine_T *engine; unsigned regflags; unsigned re_engine; - unsigned re_flags; ///< Second argument for vim_regcomp(). + unsigned re_flags; + bool re_in_use; int regstart; char_u reganch; @@ -114,7 +116,8 @@ typedef struct { regengine_T *engine; unsigned regflags; unsigned re_engine; - unsigned re_flags; ///< Second argument for vim_regcomp(). + unsigned re_flags; + bool re_in_use; nfa_state_T *start; // points into state[] diff --git a/src/nvim/regexp_nfa.c b/src/nvim/regexp_nfa.c index 387732fdee..7cd1ae93d2 100644 --- a/src/nvim/regexp_nfa.c +++ b/src/nvim/regexp_nfa.c @@ -230,7 +230,10 @@ enum { NFA_CLASS_TAB, NFA_CLASS_RETURN, NFA_CLASS_BACKSPACE, - NFA_CLASS_ESCAPE + NFA_CLASS_ESCAPE, + NFA_CLASS_IDENT, + NFA_CLASS_KEYWORD, + NFA_CLASS_FNAME, }; /* Keep in sync with classchars. */ @@ -267,9 +270,9 @@ struct Frag { typedef struct Frag Frag_T; typedef struct { - int in_use; /* number of subexpr with useful info */ + int in_use; ///< number of subexpr with useful info - /* When REG_MULTI is TRUE list.multi is used, otherwise list.line. */ + // When REG_MULTI is true list.multi is used, otherwise list.line. union { struct multipos { linenr_T start_lnum; @@ -310,48 +313,27 @@ typedef struct { regsubs_T subs; /* submatch info, only party used */ } nfa_thread_T; -/* nfa_list_T contains the alternative NFA execution states. */ +// nfa_list_T contains the alternative NFA execution states. typedef struct { - nfa_thread_T *t; /* allocated array of states */ - int n; /* nr of states currently in "t" */ - int len; /* max nr of states in "t" */ - int id; /* ID of the list */ - int has_pim; /* TRUE when any state has a PIM */ + nfa_thread_T *t; ///< allocated array of states + int n; ///< nr of states currently in "t" + int len; ///< max nr of states in "t" + int id; ///< ID of the list + int has_pim; ///< true when any state has a PIM } nfa_list_T; -/// re_flags passed to nfa_regcomp(). -static int nfa_re_flags; - -/* NFA regexp \ze operator encountered. */ -static int nfa_has_zend; - -/* NFA regexp \1 .. \9 encountered. */ -static int nfa_has_backref; - -/* NFA regexp has \z( ), set zsubexpr. */ -static int nfa_has_zsubexpr; - -/* Number of sub expressions actually being used during execution. 1 if only - * the whole match (subexpr 0) is used. */ -static int nfa_nsubexpr; - -static int *post_start; /* holds the postfix form of r.e. */ +// Variables only used in nfa_regcomp() and descendants. +static int nfa_re_flags; ///< re_flags passed to nfa_regcomp(). +static int *post_start; ///< holds the postfix form of r.e. static int *post_end; static int *post_ptr; -static int nstate; /* Number of states in the NFA. Also used when - * executing. */ -static int istate; /* Index in the state vector, used in alloc_state() */ +static int nstate; ///< Number of states in the NFA. Also used when executing. +static int istate; ///< Index in the state vector, used in alloc_state() /* If not NULL match must end at this position */ static save_se_T *nfa_endp = NULL; -/* listid is global, so that it increases on recursive calls to - * nfa_regmatch(), which means we don't have to clear the lastlist field of - * all the states. */ -static int nfa_listid; -static int nfa_alt_listid; - /* 0 for first call to nfa_regmatch(), 1 for recursive call. */ static int nfa_ll_index = 0; @@ -395,8 +377,8 @@ nfa_regcomp_start ( post_start = (int *)xmalloc(postfix_size); post_ptr = post_start; post_end = post_start + nstate_max; - nfa_has_zend = FALSE; - nfa_has_backref = FALSE; + rex.nfa_has_zend = false; + rex.nfa_has_backref = false; /* shared with BT engine */ regcomp_start(expr, re_flags); @@ -605,12 +587,10 @@ static int nfa_recognize_char_class(char_u *start, char_u *end, int extra_newl) # define CLASS_o9 0x02 # define CLASS_underscore 0x01 - int newl = FALSE; char_u *p; int config = 0; - if (extra_newl == TRUE) - newl = TRUE; + bool newl = extra_newl == true; if (*end != ']') return FAIL; @@ -655,13 +635,13 @@ static int nfa_recognize_char_class(char_u *start, char_u *end, int extra_newl) } p += 3; } else if (p + 1 < end && *p == '\\' && *(p + 1) == 'n') { - newl = TRUE; + newl = true; p += 2; } else if (*p == '_') { config |= CLASS_underscore; p++; } else if (*p == '\n') { - newl = TRUE; + newl = true; p++; } else return FAIL; @@ -670,8 +650,9 @@ static int nfa_recognize_char_class(char_u *start, char_u *end, int extra_newl) if (p != end) return FAIL; - if (newl == TRUE) + if (newl == true) { extra_newl = NFA_ADD_NL; + } switch (config) { case CLASS_o9: @@ -1188,7 +1169,7 @@ static int nfa_regatom(void) case Magic('$'): EMIT(NFA_EOL); - had_eol = TRUE; + had_eol = true; break; case Magic('<'): @@ -1210,7 +1191,7 @@ static int nfa_regatom(void) } if (c == '$') { /* "\_$" is end-of-line */ EMIT(NFA_EOL); - had_eol = TRUE; + had_eol = true; break; } @@ -1257,7 +1238,7 @@ static int nfa_regatom(void) if (p == NULL) { if (extra == NFA_ADD_NL) { EMSGN(_(e_ill_char_class), c); - rc_did_emsg = TRUE; + rc_did_emsg = true; return FAIL; } IEMSGN("INTERNAL: Unknown character class char: %" PRId64, c); @@ -1346,7 +1327,7 @@ static int nfa_regatom(void) return FAIL; } EMIT(NFA_BACKREF1 + refnum); - nfa_has_backref = true; + rex.nfa_has_backref = true; } break; @@ -1361,7 +1342,7 @@ static int nfa_regatom(void) break; case 'e': EMIT(NFA_ZEND); - nfa_has_zend = true; + rex.nfa_has_zend = true; if (!re_mult_next("\\zs")) { return false; } @@ -1380,8 +1361,8 @@ static int nfa_regatom(void) EMSG_RET_FAIL(_(e_z1_not_allowed)); } EMIT(NFA_ZREF1 + (no_Magic(c) - '1')); - /* No need to set nfa_has_backref, the sub-matches don't - * change when \z1 .. \z9 matches or not. */ + // No need to set rex.nfa_has_backref, the sub-matches don't + // change when \z1 .. \z9 matches or not. re_has_z = REX_USE; break; case '(': @@ -1598,12 +1579,12 @@ collection: EMIT(NFA_CONCAT); MB_PTR_ADV(regparse); } - /* Emit the OR branches for each character in the [] */ - emit_range = FALSE; + // Emit the OR branches for each character in the [] + emit_range = false; while (regparse < endp) { oldstartc = startc; startc = -1; - got_coll_char = FALSE; + got_coll_char = false; if (*regparse == '[') { /* Check for [: :], [= =], [. .] */ equiclass = collclass = 0; @@ -1665,6 +1646,15 @@ collection: case CLASS_ESCAPE: EMIT(NFA_CLASS_ESCAPE); break; + case CLASS_IDENT: + EMIT(NFA_CLASS_IDENT); + break; + case CLASS_KEYWORD: + EMIT(NFA_CLASS_KEYWORD); + break; + case CLASS_FNAME: + EMIT(NFA_CLASS_FNAME); + break; } EMIT(NFA_CONCAT); continue; @@ -1684,7 +1674,7 @@ collection: /* Try a range like 'a-x' or '\t-z'. Also allows '-' as a * start character. */ if (*regparse == '-' && oldstartc != -1) { - emit_range = TRUE; + emit_range = true; startc = oldstartc; MB_PTR_ADV(regparse); continue; // reading the end of the range @@ -1764,7 +1754,7 @@ collection: EMIT(NFA_CONCAT); } } - emit_range = FALSE; + emit_range = false; startc = -1; } else { /* This char (startc) is not part of a range. Just @@ -1781,10 +1771,11 @@ collection: if (!negated) extra = NFA_ADD_NL; } else { - if (got_coll_char == TRUE && startc == 0) + if (got_coll_char == true && startc == 0) { EMIT(0x0a); - else + } else { EMIT(startc); + } EMIT(NFA_CONCAT); } } @@ -1802,13 +1793,14 @@ collection: regparse = endp; MB_PTR_ADV(regparse); - /* Mark end of the collection. */ - if (negated == TRUE) + // Mark end of the collection. + if (negated == true) { EMIT(NFA_END_NEG_COLL); - else + } else { EMIT(NFA_END_COLL); + } - /* \_[] also matches \n but it's not negated */ + // \_[] also matches \n but it's not negated if (extra == NFA_ADD_NL) { EMIT(reg_string ? NL : NFA_NEWL); EMIT(NFA_OR); @@ -1877,7 +1869,7 @@ static int nfa_regpiece(void) int op; int ret; long minval, maxval; - int greedy = TRUE; /* Braces are prefixed with '-' ? */ + bool greedy = true; // Braces are prefixed with '-' ? parse_state_T old_state; parse_state_T new_state; int64_t c2; @@ -1977,11 +1969,11 @@ static int nfa_regpiece(void) * parenthesis have the same id */ - greedy = TRUE; + greedy = true; c2 = peekchr(); if (c2 == '-' || c2 == Magic('-')) { skipchr(); - greedy = FALSE; + greedy = false; } if (!read_limits(&minval, &maxval)) EMSG_RET_FAIL(_("E870: (NFA regexp) Error reading repetition limits")); @@ -2019,7 +2011,7 @@ static int nfa_regpiece(void) /* Save parse state after the repeated atom and the \{} */ save_parse_state(&new_state); - quest = (greedy == TRUE ? NFA_QUEST : NFA_QUEST_NONGREEDY); + quest = (greedy == true ? NFA_QUEST : NFA_QUEST_NONGREEDY); for (i = 0; i < maxval; i++) { /* Goto beginning of the repeated atom */ restore_parse_state(&old_state); @@ -2073,8 +2065,8 @@ static int nfa_regpiece(void) */ static int nfa_regconcat(void) { - int cont = TRUE; - int first = TRUE; + bool cont = true; + bool first = true; while (cont) { switch (peekchr()) { @@ -2082,7 +2074,7 @@ static int nfa_regconcat(void) case Magic('|'): case Magic('&'): case Magic(')'): - cont = FALSE; + cont = false; break; case Magic('Z'): @@ -2119,12 +2111,14 @@ static int nfa_regconcat(void) break; default: - if (nfa_regpiece() == FAIL) + if (nfa_regpiece() == FAIL) { return FAIL; - if (first == FALSE) + } + if (first == false) { EMIT(NFA_CONCAT); - else - first = FALSE; + } else { + first = false; + } break; } } @@ -2230,15 +2224,14 @@ nfa_reg ( else EMSG_RET_FAIL(_("E873: (NFA regexp) proper termination error")); } - /* - * Here we set the flag allowing back references to this set of - * parentheses. - */ + // Here we set the flag allowing back references to this set of + // parentheses. if (paren == REG_PAREN) { - had_endbrace[parno] = TRUE; /* have seen the close paren */ + had_endbrace[parno] = true; // have seen the close paren EMIT(NFA_MOPEN + parno); - } else if (paren == REG_ZPAREN) + } else if (paren == REG_ZPAREN) { EMIT(NFA_ZOPEN + parno); + } return OK; } @@ -2248,10 +2241,10 @@ static char_u code[50]; static void nfa_set_code(int c) { - int addnl = FALSE; + int addnl = false; if (c >= NFA_FIRST_NL && c <= NFA_LAST_NL) { - addnl = TRUE; + addnl = true; c -= NFA_ADD_NL; } @@ -2426,6 +2419,9 @@ static void nfa_set_code(int c) case NFA_CLASS_RETURN: STRCPY(code, "NFA_CLASS_RETURN"); break; case NFA_CLASS_BACKSPACE: STRCPY(code, "NFA_CLASS_BACKSPACE"); break; case NFA_CLASS_ESCAPE: STRCPY(code, "NFA_CLASS_ESCAPE"); break; + case NFA_CLASS_IDENT: STRCPY(code, "NFA_CLASS_IDENT"); break; + case NFA_CLASS_KEYWORD: STRCPY(code, "NFA_CLASS_KEYWORD"); break; + case NFA_CLASS_FNAME: STRCPY(code, "NFA_CLASS_FNAME"); break; case NFA_ANY: STRCPY(code, "NFA_ANY"); break; case NFA_IDENT: STRCPY(code, "NFA_IDENT"); break; @@ -2464,9 +2460,9 @@ static void nfa_set_code(int c) code[5] = c; } - if (addnl == TRUE) + if (addnl == true) { STRCAT(code, " + NEWLINE "); - + } } static FILE *log_fd; @@ -2848,11 +2844,8 @@ static int nfa_max_width(nfa_state_T *startstate, int depth) case NFA_UPPER_IC: case NFA_NUPPER_IC: case NFA_ANY_COMPOSING: - /* possibly non-ascii */ - if (has_mbyte) - len += 3; - else - ++len; + // possibly non-ascii + len += 3; break; case NFA_START_INVISIBLE: @@ -3019,12 +3012,12 @@ static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size) for (p = postfix; p < end; ++p) { switch (*p) { case NFA_CONCAT: - /* Concatenation. - * Pay attention: this operator does not exist in the r.e. itself - * (it is implicit, really). It is added when r.e. is translated - * to postfix form in re2post(). */ - if (nfa_calc_size == TRUE) { - /* nstate += 0; */ + // Concatenation. + // Pay attention: this operator does not exist in the r.e. itself + // (it is implicit, really). It is added when r.e. is translated + // to postfix form in re2post(). + if (nfa_calc_size == true) { + // nstate += 0; break; } e2 = POP(); @@ -3034,8 +3027,8 @@ static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size) break; case NFA_OR: - /* Alternation */ - if (nfa_calc_size == TRUE) { + // Alternation + if (nfa_calc_size == true) { nstate++; break; } @@ -3048,8 +3041,8 @@ static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size) break; case NFA_STAR: - /* Zero or more, prefer more */ - if (nfa_calc_size == TRUE) { + // Zero or more, prefer more + if (nfa_calc_size == true) { nstate++; break; } @@ -3062,8 +3055,8 @@ static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size) break; case NFA_STAR_NONGREEDY: - /* Zero or more, prefer zero */ - if (nfa_calc_size == TRUE) { + // Zero or more, prefer zero + if (nfa_calc_size == true) { nstate++; break; } @@ -3076,8 +3069,8 @@ static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size) break; case NFA_QUEST: - /* one or zero atoms=> greedy match */ - if (nfa_calc_size == TRUE) { + // one or zero atoms=> greedy match + if (nfa_calc_size == true) { nstate++; break; } @@ -3089,8 +3082,8 @@ static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size) break; case NFA_QUEST_NONGREEDY: - /* zero or one atoms => non-greedy match */ - if (nfa_calc_size == TRUE) { + // zero or one atoms => non-greedy match + if (nfa_calc_size == true) { nstate++; break; } @@ -3106,7 +3099,7 @@ static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size) /* On the stack is the sequence starting with NFA_START_COLL or * NFA_START_NEG_COLL and all possible characters. Patch it to * add the output to the start. */ - if (nfa_calc_size == TRUE) { + if (nfa_calc_size == true) { nstate++; break; } @@ -3120,10 +3113,10 @@ static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size) break; case NFA_RANGE: - /* Before this are two characters, the low and high end of a - * range. Turn them into two states with MIN and MAX. */ - if (nfa_calc_size == TRUE) { - /* nstate += 0; */ + // Before this are two characters, the low and high end of a + // range. Turn them into two states with MIN and MAX. + if (nfa_calc_size == true) { + // nstate += 0; break; } e2 = POP(); @@ -3137,8 +3130,8 @@ static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size) break; case NFA_EMPTY: - /* 0-length, used in a repetition with max/min count of 0 */ - if (nfa_calc_size == TRUE) { + // 0-length, used in a repetition with max/min count of 0 + if (nfa_calc_size == true) { nstate++; break; } @@ -3152,20 +3145,19 @@ static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size) { int n; - /* \%[abc] implemented as: - * NFA_SPLIT - * +-CHAR(a) - * | +-NFA_SPLIT - * | +-CHAR(b) - * | | +-NFA_SPLIT - * | | +-CHAR(c) - * | | | +-next - * | | +- next - * | +- next - * +- next - */ - n = *++p; /* get number of characters */ - if (nfa_calc_size == TRUE) { + // \%[abc] implemented as: + // NFA_SPLIT + // +-CHAR(a) + // | +-NFA_SPLIT + // | +-CHAR(b) + // | | +-NFA_SPLIT + // | | +-CHAR(c) + // | | | +-next + // | | +- next + // | +- next + // +- next + n = *++p; // get number of characters + if (nfa_calc_size == true) { nstate += n; break; } @@ -3235,7 +3227,7 @@ static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size) * Surrounds the preceding atom with START_INVISIBLE and * END_INVISIBLE, similarly to MOPEN. */ - if (nfa_calc_size == TRUE) { + if (nfa_calc_size == true) { nstate += pattern ? 4 : 2; break; } @@ -3297,8 +3289,8 @@ static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size) case NFA_ZOPEN7: case NFA_ZOPEN8: case NFA_ZOPEN9: - case NFA_NOPEN: /* \%( \) "Invisible Submatch" */ - if (nfa_calc_size == TRUE) { + case NFA_NOPEN: // \%( \) "Invisible Submatch" + if (nfa_calc_size == true) { nstate += 2; break; } @@ -3376,7 +3368,7 @@ static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size) case NFA_ZREF7: case NFA_ZREF8: case NFA_ZREF9: - if (nfa_calc_size == TRUE) { + if (nfa_calc_size == true) { nstate += 2; break; } @@ -3405,7 +3397,7 @@ static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size) { int n = *++p; /* lnum, col or mark name */ - if (nfa_calc_size == TRUE) { + if (nfa_calc_size == true) { nstate += 1; break; } @@ -3420,8 +3412,8 @@ static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size) case NFA_ZSTART: case NFA_ZEND: default: - /* Operands */ - if (nfa_calc_size == TRUE) { + // Operands + if (nfa_calc_size == true) { nstate++; break; } @@ -3435,7 +3427,7 @@ static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size) } /* for(p = postfix; *p; ++p) */ - if (nfa_calc_size == TRUE) { + if (nfa_calc_size == true) { nstate++; goto theend; /* Return value when counting size is ignored anyway */ } @@ -3489,11 +3481,11 @@ static void nfa_postprocess(nfa_regprog_T *prog) || c == NFA_START_INVISIBLE_BEFORE_NEG) { int directly; - /* Do it directly when what follows is possibly the end of the - * match. */ - if (match_follows(prog->state[i].out1->out, 0)) - directly = TRUE; - else { + // Do it directly when what follows is possibly the end of the + // match. + if (match_follows(prog->state[i].out1->out, 0)) { + directly = true; + } else { int ch_invisible = failure_chance(prog->state[i].out, 0); int ch_follows = failure_chance(prog->state[i].out1->out, 0); @@ -3505,10 +3497,11 @@ static void nfa_postprocess(nfa_regprog_T *prog) * unbounded, always prefer what follows then, * unless what follows will always match. * Otherwise strongly prefer what follows. */ - if (prog->state[i].val <= 0 && ch_follows > 0) - directly = FALSE; - else + if (prog->state[i].val <= 0 && ch_follows > 0) { + directly = false; + } else { directly = ch_follows * 10 < ch_invisible; + } } else { /* normal invisible, first do the one with the * highest failure chance */ @@ -3537,8 +3530,9 @@ static void nfa_postprocess(nfa_regprog_T *prog) static void log_subsexpr(regsubs_T *subs) { log_subexpr(&subs->norm); - if (nfa_has_zsubexpr) + if (rex.nfa_has_zsubexpr) { log_subexpr(&subs->synt); + } } static void log_subexpr(regsub_T *sub) @@ -3564,15 +3558,17 @@ static void log_subexpr(regsub_T *sub) } } -static char *pim_info(nfa_pim_T *pim) +static char *pim_info(const nfa_pim_T *pim) { static char buf[30]; - if (pim == NULL || pim->result == NFA_PIM_UNUSED) + if (pim == NULL || pim->result == NFA_PIM_UNUSED) { buf[0] = NUL; - else { - sprintf(buf, " PIM col %d", REG_MULTI ? (int)pim->end.pos.col - : (int)(pim->end.ptr - reginput)); + } else { + snprintf(buf, sizeof(buf), " PIM col %d", + REG_MULTI + ? (int)pim->end.pos.col + : (int)(pim->end.ptr - rex.input)); } return buf; } @@ -3591,19 +3587,21 @@ static void copy_pim(nfa_pim_T *to, nfa_pim_T *from) to->result = from->result; to->state = from->state; copy_sub(&to->subs.norm, &from->subs.norm); - if (nfa_has_zsubexpr) + if (rex.nfa_has_zsubexpr) { copy_sub(&to->subs.synt, &from->subs.synt); + } to->end = from->end; } static void clear_sub(regsub_T *sub) { - if (REG_MULTI) - /* Use 0xff to set lnum to -1 */ + if (REG_MULTI) { + // Use 0xff to set lnum to -1 memset(sub->list.multi, 0xff, - sizeof(struct multipos) * nfa_nsubexpr); - else - memset(sub->list.line, 0, sizeof(struct linepos) * nfa_nsubexpr); + sizeof(struct multipos) * rex.nfa_nsubexpr); + } else { + memset(sub->list.line, 0, sizeof(struct linepos) * rex.nfa_nsubexpr); + } sub->in_use = 0; } @@ -3651,7 +3649,7 @@ static void copy_sub_off(regsub_T *to, regsub_T *from) */ static void copy_ze_off(regsub_T *to, regsub_T *from) { - if (nfa_has_zend) { + if (rex.nfa_has_zend) { if (REG_MULTI) { if (from->list.multi[0].end_lnum >= 0){ to->list.multi[0].end_lnum = from->list.multi[0].end_lnum; @@ -3664,9 +3662,9 @@ static void copy_ze_off(regsub_T *to, regsub_T *from) } } -// Return TRUE if "sub1" and "sub2" have the same start positions. +// Return true if "sub1" and "sub2" have the same start positions. // When using back-references also check the end position. -static int sub_equal(regsub_T *sub1, regsub_T *sub2) +static bool sub_equal(regsub_T *sub1, regsub_T *sub2) { int i; int todo; @@ -3677,22 +3675,25 @@ static int sub_equal(regsub_T *sub1, regsub_T *sub2) todo = sub1->in_use > sub2->in_use ? sub1->in_use : sub2->in_use; if (REG_MULTI) { - for (i = 0; i < todo; ++i) { - if (i < sub1->in_use) + for (i = 0; i < todo; i++) { + if (i < sub1->in_use) { s1 = sub1->list.multi[i].start_lnum; - else + } else { s1 = -1; - if (i < sub2->in_use) + } + if (i < sub2->in_use) { s2 = sub2->list.multi[i].start_lnum; - else + } else { s2 = -1; - if (s1 != s2) - return FALSE; + } + if (s1 != s2) { + return false; + } if (s1 != -1 && sub1->list.multi[i].start_col - != sub2->list.multi[i].start_col) - return FALSE; - - if (nfa_has_backref) { + != sub2->list.multi[i].start_col) { + return false; + } + if (rex.nfa_has_backref) { if (i < sub1->in_use) { s1 = sub1->list.multi[i].end_lnum; } else { @@ -3704,28 +3705,30 @@ static int sub_equal(regsub_T *sub1, regsub_T *sub2) s2 = -1; } if (s1 != s2) { - return FALSE; + return false; } if (s1 != -1 && sub1->list.multi[i].end_col != sub2->list.multi[i].end_col) { - return FALSE; + return false; } } } } else { - for (i = 0; i < todo; ++i) { - if (i < sub1->in_use) + for (i = 0; i < todo; i++) { + if (i < sub1->in_use) { sp1 = sub1->list.line[i].start; - else + } else { sp1 = NULL; - if (i < sub2->in_use) + } + if (i < sub2->in_use) { sp2 = sub2->list.line[i].start; - else + } else { sp2 = NULL; - if (sp1 != sp2) - return FALSE; - - if (nfa_has_backref) { + } + if (sp1 != sp2) { + return false; + } + if (rex.nfa_has_backref) { if (i < sub1->in_use) { sp1 = sub1->list.line[i].end; } else { @@ -3737,13 +3740,13 @@ static int sub_equal(regsub_T *sub1, regsub_T *sub2) sp2 = NULL; } if (sp1 != sp2) { - return FALSE; + return false; } } } } - return TRUE; + return true; } #ifdef REGEXP_DEBUG @@ -3754,83 +3757,81 @@ static void report_state(char *action, nfa_pim_T *pim) { int col; - if (sub->in_use <= 0) + if (sub->in_use <= 0) { col = -1; - else if (REG_MULTI) + } else if (REG_MULTI) { col = sub->list.multi[0].start_col; - else - col = (int)(sub->list.line[0].start - regline); + } else { + col = (int)(sub->list.line[0].start - rex.line); + } nfa_set_code(state->c); fprintf(log_fd, "> %s state %d to list %d. char %d: %s (start col %d)%s\n", - action, abs(state->id), lid, state->c, code, col, - pim_info(pim)); + action, abs(state->id), lid, state->c, code, col, + pim_info(pim)); } #endif -/* - * Return TRUE if the same state is already in list "l" with the same - * positions as "subs". - */ -static int -has_state_with_pos ( - nfa_list_T *l, /* runtime state list */ - nfa_state_T *state, /* state to update */ - regsubs_T *subs, /* pointers to subexpressions */ - nfa_pim_T *pim /* postponed match or NULL */ +// Return true if the same state is already in list "l" with the same +// positions as "subs". +static bool has_state_with_pos( + nfa_list_T *l, // runtime state list + nfa_state_T *state, // state to update + regsubs_T *subs, // pointers to subexpressions + nfa_pim_T *pim // postponed match or NULL ) + FUNC_ATTR_NONNULL_ARG(1, 2, 3) { - nfa_thread_T *thread; - int i; - - for (i = 0; i < l->n; ++i) { - thread = &l->t[i]; + for (int i = 0; i < l->n; i++) { + nfa_thread_T *thread = &l->t[i]; if (thread->state->id == state->id && sub_equal(&thread->subs.norm, &subs->norm) - && (!nfa_has_zsubexpr + && (!rex.nfa_has_zsubexpr || sub_equal(&thread->subs.synt, &subs->synt)) - && pim_equal(&thread->pim, pim)) - return TRUE; + && pim_equal(&thread->pim, pim)) { + return true; + } } - return FALSE; + return false; } -/* - * Return TRUE if "one" and "two" are equal. That includes when both are not - * set. - */ -static int pim_equal(nfa_pim_T *one, nfa_pim_T *two) +// Return true if "one" and "two" are equal. That includes when both are not +// set. +static bool pim_equal(const nfa_pim_T *one, const nfa_pim_T *two) { - int one_unused = (one == NULL || one->result == NFA_PIM_UNUSED); - int two_unused = (two == NULL || two->result == NFA_PIM_UNUSED); + const bool one_unused = (one == NULL || one->result == NFA_PIM_UNUSED); + const bool two_unused = (two == NULL || two->result == NFA_PIM_UNUSED); - if (one_unused) - /* one is unused: equal when two is also unused */ + if (one_unused) { + // one is unused: equal when two is also unused return two_unused; - if (two_unused) - /* one is used and two is not: not equal */ - return FALSE; - /* compare the state id */ - if (one->state->id != two->state->id) - return FALSE; - /* compare the position */ - if (REG_MULTI) + } + if (two_unused) { + // one is used and two is not: not equal + return false; + } + // compare the state id + if (one->state->id != two->state->id) { + return false; + } + // compare the position + if (REG_MULTI) { return one->end.pos.lnum == two->end.pos.lnum && one->end.pos.col == two->end.pos.col; + } return one->end.ptr == two->end.ptr; } -/* - * Return TRUE if "state" leads to a NFA_MATCH without advancing the input. - */ -static int match_follows(nfa_state_T *startstate, int depth) +// Return true if "state" leads to a NFA_MATCH without advancing the input. +static bool match_follows(const nfa_state_T *startstate, int depth) + FUNC_ATTR_NONNULL_ALL { - nfa_state_T *state = startstate; - - /* avoid too much recursion */ - if (depth > 10) - return FALSE; + const nfa_state_T *state = startstate; + // avoid too much recursion + if (depth > 10) { + return false; + } while (state != NULL) { switch (state->c) { case NFA_MATCH: @@ -3838,7 +3839,7 @@ static int match_follows(nfa_state_T *startstate, int depth) case NFA_END_INVISIBLE: case NFA_END_INVISIBLE_NEG: case NFA_END_PATTERN: - return TRUE; + return true; case NFA_SPLIT: return match_follows(state->out, depth + 1) @@ -3892,39 +3893,38 @@ static int match_follows(nfa_state_T *startstate, int depth) case NFA_START_COLL: case NFA_START_NEG_COLL: case NFA_NEWL: - /* state will advance input */ - return FALSE; + // state will advance input + return false; default: - if (state->c > 0) - /* state will advance input */ - return FALSE; - - /* Others: zero-width or possibly zero-width, might still find - * a match at the same position, keep looking. */ + if (state->c > 0) { + // state will advance input + return false; + } + // Others: zero-width or possibly zero-width, might still find + // a match at the same position, keep looking. break; } state = state->out; } - return FALSE; + return false; } -/* - * Return TRUE if "state" is already in list "l". - */ -static int -state_in_list ( - nfa_list_T *l, /* runtime state list */ - nfa_state_T *state, /* state to update */ - regsubs_T *subs /* pointers to subexpressions */ +// Return true if "state" is already in list "l". +static bool state_in_list( + nfa_list_T *l, // runtime state list + nfa_state_T *state, // state to update + regsubs_T *subs // pointers to subexpressions ) + FUNC_ATTR_NONNULL_ALL { if (state->lastlist[nfa_ll_index] == l->id) { - if (!nfa_has_backref || has_state_with_pos(l, state, subs, NULL)) - return TRUE; + if (!rex.nfa_has_backref || has_state_with_pos(l, state, subs, NULL)) { + return true; + } } - return FALSE; + return false; } // Offset used for "off" by addstate_here(). @@ -3943,10 +3943,10 @@ static regsubs_T *addstate( { int subidx; int off = off_arg; - int add_here = FALSE; + int add_here = false; int listindex = 0; int k; - int found = FALSE; + int found = false; nfa_thread_T *thread; struct multipos save_multipos; int save_in_use; @@ -3956,7 +3956,7 @@ static regsubs_T *addstate( regsubs_T *subs = subs_arg; static regsubs_T temp_subs; #ifdef REGEXP_DEBUG - int did_print = FALSE; + int did_print = false; #endif static int depth = 0; @@ -4005,15 +4005,16 @@ static regsubs_T *addstate( case NFA_BOL: case NFA_BOF: - /* "^" won't match past end-of-line, don't bother trying. - * Except when at the end of the line, or when we are going to the - * next line for a look-behind match. */ - if (reginput > regline - && *reginput != NUL + // "^" won't match past end-of-line, don't bother trying. + // Except when at the end of the line, or when we are going to the + // next line for a look-behind match. + if (rex.input > rex.line + && *rex.input != NUL && (nfa_endp == NULL || !REG_MULTI - || reglnum == nfa_endp->se_u.pos.lnum)) + || rex.lnum == nfa_endp->se_u.pos.lnum)) { goto skip_add; + } FALLTHROUGH; case NFA_MOPEN1: @@ -4047,7 +4048,7 @@ static regsubs_T *addstate( * unless it is an MOPEN that is used for a backreference or * when there is a PIM. For NFA_MATCH check the position, * lower position is preferred. */ - if (!nfa_has_backref && pim == NULL && !l->has_pim + if (!rex.nfa_has_backref && pim == NULL && !l->has_pim && state->c != NFA_MATCH) { /* When called from addstate_here() do insert before @@ -4055,7 +4056,7 @@ static regsubs_T *addstate( if (add_here) { for (k = 0; k < l->n && k < listindex; ++k) { if (l->t[k].state->id == state->id) { - found = TRUE; + found = true; break; } } @@ -4092,11 +4093,12 @@ skip_add: return NULL; } if (subs != &temp_subs) { - /* "subs" may point into the current array, need to make a - * copy before it becomes invalid. */ + // "subs" may point into the current array, need to make a + // copy before it becomes invalid. copy_sub(&temp_subs.norm, &subs->norm); - if (nfa_has_zsubexpr) + if (rex.nfa_has_zsubexpr) { copy_sub(&temp_subs.synt, &subs->synt); + } subs = &temp_subs; } @@ -4113,14 +4115,15 @@ skip_add: thread->pim.result = NFA_PIM_UNUSED; else { copy_pim(&thread->pim, pim); - l->has_pim = TRUE; + l->has_pim = true; } copy_sub(&thread->subs.norm, &subs->norm); - if (nfa_has_zsubexpr) + if (rex.nfa_has_zsubexpr) { copy_sub(&thread->subs.synt, &subs->synt); + } #ifdef REGEXP_DEBUG report_state("Adding", &thread->subs.norm, state, l->id, pim); - did_print = TRUE; + did_print = true; #endif } @@ -4195,13 +4198,12 @@ skip_add: sub->in_use = subidx + 1; } if (off == -1) { - sub->list.multi[subidx].start_lnum = reglnum + 1; + sub->list.multi[subidx].start_lnum = rex.lnum + 1; sub->list.multi[subidx].start_col = 0; } else { - - sub->list.multi[subidx].start_lnum = reglnum; + sub->list.multi[subidx].start_lnum = rex.lnum; sub->list.multi[subidx].start_col = - (colnr_T)(reginput - regline + off); + (colnr_T)(rex.input - rex.line + off); } sub->list.multi[subidx].end_lnum = -1; } else { @@ -4216,7 +4218,7 @@ skip_add: } sub->in_use = subidx + 1; } - sub->list.line[subidx].start = reginput + off; + sub->list.line[subidx].start = rex.input + off; } subs = addstate(l, state->out, subs, pim, off_arg); @@ -4241,9 +4243,10 @@ skip_add: break; case NFA_MCLOSE: - if (nfa_has_zend && (REG_MULTI - ? subs->norm.list.multi[0].end_lnum >= 0 - : subs->norm.list.line[0].end != NULL)) { + if (rex.nfa_has_zend + && (REG_MULTI + ? subs->norm.list.multi[0].end_lnum >= 0 + : subs->norm.list.line[0].end != NULL)) { // Do not overwrite the position set by \ze. subs = addstate(l, state->out, subs, pim, off_arg); break; @@ -4288,18 +4291,18 @@ skip_add: if (REG_MULTI) { save_multipos = sub->list.multi[subidx]; if (off == -1) { - sub->list.multi[subidx].end_lnum = reglnum + 1; + sub->list.multi[subidx].end_lnum = rex.lnum + 1; sub->list.multi[subidx].end_col = 0; } else { - sub->list.multi[subidx].end_lnum = reglnum; + sub->list.multi[subidx].end_lnum = rex.lnum; sub->list.multi[subidx].end_col = - (colnr_T)(reginput - regline + off); + (colnr_T)(rex.input - rex.line + off); } /* avoid compiler warnings */ save_ptr = NULL; } else { save_ptr = sub->list.line[subidx].end; - sub->list.line[subidx].end = reginput + off; + sub->list.line[subidx].end = rex.input + off; // avoid compiler warnings memset(&save_multipos, 0, sizeof(save_multipos)); } @@ -4486,6 +4489,21 @@ static int check_char_class(int class, int c) return OK; } break; + case NFA_CLASS_IDENT: + if (vim_isIDc(c)) { + return OK; + } + break; + case NFA_CLASS_KEYWORD: + if (reg_iswordc(c)) { + return OK; + } + break; + case NFA_CLASS_FNAME: + if (vim_isfilec(c)) { + return OK; + } + break; default: // should not be here :P @@ -4497,7 +4515,7 @@ static int check_char_class(int class, int c) /* * Check for a match with subexpression "subidx". - * Return TRUE if it matches. + * Return true if it matches. */ static int match_backref ( @@ -4512,49 +4530,49 @@ match_backref ( retempty: /* backref was not set, match an empty string */ *bytelen = 0; - return TRUE; + return true; } if (REG_MULTI) { if (sub->list.multi[subidx].start_lnum < 0 || sub->list.multi[subidx].end_lnum < 0) goto retempty; - if (sub->list.multi[subidx].start_lnum == reglnum - && sub->list.multi[subidx].end_lnum == reglnum) { + if (sub->list.multi[subidx].start_lnum == rex.lnum + && sub->list.multi[subidx].end_lnum == rex.lnum) { len = sub->list.multi[subidx].end_col - sub->list.multi[subidx].start_col; - if (cstrncmp(regline + sub->list.multi[subidx].start_col, - reginput, &len) == 0) { + if (cstrncmp(rex.line + sub->list.multi[subidx].start_col, + rex.input, &len) == 0) { *bytelen = len; - return TRUE; + return true; } } else { - if (match_with_backref( - sub->list.multi[subidx].start_lnum, - sub->list.multi[subidx].start_col, - sub->list.multi[subidx].end_lnum, - sub->list.multi[subidx].end_col, - bytelen) == RA_MATCH) - return TRUE; + if (match_with_backref(sub->list.multi[subidx].start_lnum, + sub->list.multi[subidx].start_col, + sub->list.multi[subidx].end_lnum, + sub->list.multi[subidx].end_col, + bytelen) == RA_MATCH) { + return true; + } } } else { if (sub->list.line[subidx].start == NULL || sub->list.line[subidx].end == NULL) goto retempty; len = (int)(sub->list.line[subidx].end - sub->list.line[subidx].start); - if (cstrncmp(sub->list.line[subidx].start, reginput, &len) == 0) { + if (cstrncmp(sub->list.line[subidx].start, rex.input, &len) == 0) { *bytelen = len; - return TRUE; + return true; } } - return FALSE; + return false; } /* * Check for a match with \z subexpression "subidx". - * Return TRUE if it matches. + * Return true if it matches. */ static int match_zref ( @@ -4568,15 +4586,15 @@ match_zref ( if (re_extmatch_in == NULL || re_extmatch_in->matches[subidx] == NULL) { /* backref was not set, match an empty string */ *bytelen = 0; - return TRUE; + return true; } len = (int)STRLEN(re_extmatch_in->matches[subidx]); - if (cstrncmp(re_extmatch_in->matches[subidx], reginput, &len) == 0) { + if (cstrncmp(re_extmatch_in->matches[subidx], rex.input, &len) == 0) { *bytelen = len; - return TRUE; + return true; } - return FALSE; + return false; } /* @@ -4629,74 +4647,79 @@ static bool nfa_re_num_cmp(uintmax_t val, int op, uintmax_t pos) static int recursive_regmatch( nfa_state_T *state, nfa_pim_T *pim, nfa_regprog_T *prog, regsubs_T *submatch, regsubs_T *m, int **listids, int *listids_len) + FUNC_ATTR_NONNULL_ARG(1, 3, 5, 6, 7) { - int save_reginput_col = (int)(reginput - regline); - int save_reglnum = reglnum; - int save_nfa_match = nfa_match; - int save_nfa_listid = nfa_listid; - save_se_T *save_nfa_endp = nfa_endp; + const int save_reginput_col = (int)(rex.input - rex.line); + const int save_reglnum = rex.lnum; + const int save_nfa_match = nfa_match; + const int save_nfa_listid = rex.nfa_listid; + save_se_T *const save_nfa_endp = nfa_endp; save_se_T endpos; save_se_T *endposp = NULL; - int result; - int need_restore = FALSE; + int need_restore = false; if (pim != NULL) { - /* start at the position where the postponed match was */ - if (REG_MULTI) - reginput = regline + pim->end.pos.col; - else - reginput = pim->end.ptr; + // start at the position where the postponed match was + if (REG_MULTI) { + rex.input = rex.line + pim->end.pos.col; + } else { + rex.input = pim->end.ptr; + } } if (state->c == NFA_START_INVISIBLE_BEFORE || state->c == NFA_START_INVISIBLE_BEFORE_FIRST || state->c == NFA_START_INVISIBLE_BEFORE_NEG || state->c == NFA_START_INVISIBLE_BEFORE_NEG_FIRST) { - /* The recursive match must end at the current position. When "pim" is - * not NULL it specifies the current position. */ + // The recursive match must end at the current position. When "pim" is + // not NULL it specifies the current position. endposp = &endpos; if (REG_MULTI) { if (pim == NULL) { - endpos.se_u.pos.col = (int)(reginput - regline); - endpos.se_u.pos.lnum = reglnum; - } else + endpos.se_u.pos.col = (int)(rex.input - rex.line); + endpos.se_u.pos.lnum = rex.lnum; + } else { endpos.se_u.pos = pim->end.pos; + } } else { - if (pim == NULL) - endpos.se_u.ptr = reginput; - else + if (pim == NULL) { + endpos.se_u.ptr = rex.input; + } else { endpos.se_u.ptr = pim->end.ptr; + } } - /* Go back the specified number of bytes, or as far as the - * start of the previous line, to try matching "\@<=" or - * not matching "\@<!". This is very inefficient, limit the number of - * bytes if possible. */ + // Go back the specified number of bytes, or as far as the + // start of the previous line, to try matching "\@<=" or + // not matching "\@<!". This is very inefficient, limit the number of + // bytes if possible. if (state->val <= 0) { if (REG_MULTI) { - regline = reg_getline(--reglnum); - if (regline == NULL) - /* can't go before the first line */ - regline = reg_getline(++reglnum); + rex.line = reg_getline(--rex.lnum); + if (rex.line == NULL) { + // can't go before the first line + rex.line = reg_getline(++rex.lnum); + } } - reginput = regline; + rex.input = rex.line; } else { - if (REG_MULTI && (int)(reginput - regline) < state->val) { - /* Not enough bytes in this line, go to end of - * previous line. */ - regline = reg_getline(--reglnum); - if (regline == NULL) { - /* can't go before the first line */ - regline = reg_getline(++reglnum); - reginput = regline; - } else - reginput = regline + STRLEN(regline); + if (REG_MULTI && (int)(rex.input - rex.line) < state->val) { + // Not enough bytes in this line, go to end of + // previous line. + rex.line = reg_getline(--rex.lnum); + if (rex.line == NULL) { + // can't go before the first line + rex.line = reg_getline(++rex.lnum); + rex.input = rex.line; + } else { + rex.input = rex.line + STRLEN(rex.line); + } } - if ((int)(reginput - regline) >= state->val) { - reginput -= state->val; - reginput -= utf_head_off(regline, reginput); + if ((int)(rex.input - rex.line) >= state->val) { + rex.input -= state->val; + rex.input -= utf_head_off(rex.line, rex.input); } else { - reginput = regline; + rex.input = rex.line; } } } @@ -4706,48 +4729,50 @@ static int recursive_regmatch( fclose(log_fd); log_fd = NULL; #endif - /* Have to clear the lastlist field of the NFA nodes, so that - * nfa_regmatch() and addstate() can run properly after recursion. */ + // Have to clear the lastlist field of the NFA nodes, so that + // nfa_regmatch() and addstate() can run properly after recursion. if (nfa_ll_index == 1) { - /* Already calling nfa_regmatch() recursively. Save the lastlist[1] - * values and clear them. */ - if (*listids == NULL || *listids_len < nstate) { + // Already calling nfa_regmatch() recursively. Save the lastlist[1] + // values and clear them. + if (*listids == NULL || *listids_len < prog->nstate) { xfree(*listids); - *listids = xmalloc(sizeof(**listids) * nstate); - *listids_len = nstate; + *listids = xmalloc(sizeof(**listids) * prog->nstate); + *listids_len = prog->nstate; } nfa_save_listids(prog, *listids); - need_restore = TRUE; - /* any value of nfa_listid will do */ + need_restore = true; + // any value of rex.nfa_listid will do } else { - /* First recursive nfa_regmatch() call, switch to the second lastlist - * entry. Make sure nfa_listid is different from a previous recursive - * call, because some states may still have this ID. */ - ++nfa_ll_index; - if (nfa_listid <= nfa_alt_listid) - nfa_listid = nfa_alt_listid; + // First recursive nfa_regmatch() call, switch to the second lastlist + // entry. Make sure rex.nfa_listid is different from a previous + // recursive call, because some states may still have this ID. + nfa_ll_index++; + if (rex.nfa_listid <= rex.nfa_alt_listid) { + rex.nfa_listid = rex.nfa_alt_listid; + } } - /* Call nfa_regmatch() to check if the current concat matches at this - * position. The concat ends with the node NFA_END_INVISIBLE */ + // Call nfa_regmatch() to check if the current concat matches at this + // position. The concat ends with the node NFA_END_INVISIBLE nfa_endp = endposp; - result = nfa_regmatch(prog, state->out, submatch, m); + const int result = nfa_regmatch(prog, state->out, submatch, m); - if (need_restore) + if (need_restore) { nfa_restore_listids(prog, *listids); - else { - --nfa_ll_index; - nfa_alt_listid = nfa_listid; + } else { + nfa_ll_index--; + rex.nfa_alt_listid = rex.nfa_listid; } - /* restore position in input text */ - reglnum = save_reglnum; - if (REG_MULTI) - regline = reg_getline(reglnum); - reginput = regline + save_reginput_col; + // restore position in input text + rex.lnum = save_reglnum; + if (REG_MULTI) { + rex.line = reg_getline(rex.lnum); + } + rex.input = rex.line + save_reginput_col; if (result != NFA_TOO_EXPENSIVE) { nfa_match = save_nfa_match; - nfa_listid = save_nfa_listid; + rex.nfa_listid = save_nfa_listid; } nfa_endp = save_nfa_endp; @@ -4756,7 +4781,7 @@ static int recursive_regmatch( if (log_fd != NULL) { fprintf(log_fd, "****************************\n"); fprintf(log_fd, "FINISHED RUNNING nfa_regmatch() recursively\n"); - fprintf(log_fd, "MATCH = %s\n", !result ? "FALSE" : "OK"); + fprintf(log_fd, "MATCH = %s\n", !result ? "false" : "OK"); fprintf(log_fd, "****************************\n"); } else { EMSG(_(e_log_open_failed)); @@ -4930,11 +4955,11 @@ static int failure_chance(nfa_state_T *state, int depth) */ static int skip_to_start(int c, colnr_T *colp) { - const char_u *const s = cstrchr(regline + *colp, c); + const char_u *const s = cstrchr(rex.line + *colp, c); if (s == NULL) { return FAIL; } - *colp = (int)(s - regline); + *colp = (int)(s - rex.line); return OK; } @@ -4948,12 +4973,12 @@ static long find_match_text(colnr_T startcol, int regstart, char_u *match_text) #define PTR2LEN(x) utf_ptr2len(x) colnr_T col = startcol; - int regstart_len = PTR2LEN(regline + startcol); + int regstart_len = PTR2LEN(rex.line + startcol); for (;;) { bool match = true; char_u *s1 = match_text; - char_u *s2 = regline + col + regstart_len; // skip regstart + char_u *s2 = rex.line + col + regstart_len; // skip regstart while (*s1) { int c1_len = PTR2LEN(s1); int c1 = PTR2CHAR(s1); @@ -4973,12 +4998,12 @@ static long find_match_text(colnr_T startcol, int regstart, char_u *match_text) && !(enc_utf8 && utf_iscomposing(PTR2CHAR(s2)))) { cleanup_subexpr(); if (REG_MULTI) { - rex.reg_startpos[0].lnum = reglnum; + rex.reg_startpos[0].lnum = rex.lnum; rex.reg_startpos[0].col = col; - rex.reg_endpos[0].lnum = reglnum; - rex.reg_endpos[0].col = s2 - regline; + rex.reg_endpos[0].lnum = rex.lnum; + rex.reg_endpos[0].col = s2 - rex.line; } else { - rex.reg_startp[0] = regline + col; + rex.reg_startp[0] = rex.line + col; rex.reg_endp[0] = s2; } return 1L; @@ -5008,17 +5033,18 @@ static int nfa_did_time_out(void) /// Main matching routine. /// -/// Run NFA to determine whether it matches reginput. +/// Run NFA to determine whether it matches rex.input. /// /// When "nfa_endp" is not NULL it is a required end-of-match position. /// -/// Return TRUE if there is a match, FALSE if there is no match, +/// Return true if there is a match, false if there is no match, /// NFA_TOO_EXPENSIVE if we end up with too many states. /// When there is a match "submatch" contains the positions. /// /// Note: Caller must ensure that: start != NULL. static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *submatch, regsubs_T *m) + FUNC_ATTR_NONNULL_ARG(1, 2, 4) { int result = false; int flag = 0; @@ -5063,11 +5089,11 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, nfa_match = false; // Allocate memory for the lists of nodes. - size_t size = (nstate + 1) * sizeof(nfa_thread_T); + size_t size = (prog->nstate + 1) * sizeof(nfa_thread_T); list[0].t = xmalloc(size); - list[0].len = nstate + 1; + list[0].len = prog->nstate + 1; list[1].t = xmalloc(size); - list[1].len = nstate + 1; + list[1].len = prog->nstate + 1; #ifdef REGEXP_DEBUG log_fd = fopen(NFA_REGEXP_RUN_LOG, "a"); @@ -5085,23 +5111,24 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, thislist = &list[0]; thislist->n = 0; - thislist->has_pim = FALSE; + thislist->has_pim = false; nextlist = &list[1]; nextlist->n = 0; - nextlist->has_pim = FALSE; + nextlist->has_pim = false; #ifdef REGEXP_DEBUG fprintf(log_fd, "(---) STARTSTATE first\n"); #endif - thislist->id = nfa_listid + 1; + thislist->id = rex.nfa_listid + 1; - /* Inline optimized code for addstate(thislist, start, m, 0) if we know - * it's the first MOPEN. */ + // Inline optimized code for addstate(thislist, start, m, 0) if we know + // it's the first MOPEN. if (toplevel) { if (REG_MULTI) { - m->norm.list.multi[0].start_lnum = reglnum; - m->norm.list.multi[0].start_col = (colnr_T)(reginput - regline); - } else - m->norm.list.line[0].start = reginput; + m->norm.list.multi[0].start_lnum = rex.lnum; + m->norm.list.multi[0].start_col = (colnr_T)(rex.input - rex.line); + } else { + m->norm.list.line[0].start = rex.input; + } m->norm.in_use = 1; r = addstate(thislist, start->out, m, NULL, 0); } else { @@ -5122,8 +5149,8 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, * Run for each character. */ for (;; ) { - int curc = utf_ptr2char(reginput); - int clen = utfc_ptr2len(reginput); + int curc = utf_ptr2char(rex.input); + int clen = utfc_ptr2len(rex.input); if (curc == NUL) { clen = 0; go_to_nextline = false; @@ -5134,20 +5161,20 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, nextlist = &list[flag ^= 1]; nextlist->n = 0; // clear nextlist nextlist->has_pim = false; - nfa_listid++; + rex.nfa_listid++; if (prog->re_engine == AUTOMATIC_ENGINE - && (nfa_listid >= NFA_MAX_STATES)) { + && (rex.nfa_listid >= NFA_MAX_STATES)) { // Too many states, retry with old engine. nfa_match = NFA_TOO_EXPENSIVE; goto theend; } - thislist->id = nfa_listid; - nextlist->id = nfa_listid + 1; + thislist->id = rex.nfa_listid; + nextlist->id = rex.nfa_listid + 1; #ifdef REGEXP_DEBUG fprintf(log_fd, "------------------------------------------\n"); - fprintf(log_fd, ">>> Reginput is \"%s\"\n", reginput); + fprintf(log_fd, ">>> Reginput is \"%s\"\n", rex.input); fprintf(log_fd, ">>> Advanced one character... Current char is %c (code %d) \n", curc, @@ -5200,7 +5227,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, } else if (REG_MULTI) { col = t->subs.norm.list.multi[0].start_col; } else { - col = (int)(t->subs.norm.list.line[0].start - regline); + col = (int)(t->subs.norm.list.line[0].start - rex.line); } nfa_set_code(t->state->c); fprintf(log_fd, "(%d) char %d %s (start col %d)%s... \n", @@ -5226,64 +5253,66 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, } nfa_match = true; copy_sub(&submatch->norm, &t->subs.norm); - if (nfa_has_zsubexpr) + if (rex.nfa_has_zsubexpr) { copy_sub(&submatch->synt, &t->subs.synt); + } #ifdef REGEXP_DEBUG log_subsexpr(&t->subs); #endif - /* Found the left-most longest match, do not look at any other - * states at this position. When the list of states is going - * to be empty quit without advancing, so that "reginput" is - * correct. */ - if (nextlist->n == 0) + // Found the left-most longest match, do not look at any other + // states at this position. When the list of states is going + // to be empty quit without advancing, so that "rex.input" is + // correct. + if (nextlist->n == 0) { clen = 0; + } goto nextchar; } case NFA_END_INVISIBLE: case NFA_END_INVISIBLE_NEG: case NFA_END_PATTERN: - /* - * This is only encountered after a NFA_START_INVISIBLE or - * NFA_START_INVISIBLE_BEFORE node. - * They surround a zero-width group, used with "\@=", "\&", - * "\@!", "\@<=" and "\@<!". - * If we got here, it means that the current "invisible" group - * finished successfully, so return control to the parent - * nfa_regmatch(). For a look-behind match only when it ends - * in the position in "nfa_endp". - * Submatches are stored in *m, and used in the parent call. - */ + // This is only encountered after a NFA_START_INVISIBLE or + // NFA_START_INVISIBLE_BEFORE node. + // They surround a zero-width group, used with "\@=", "\&", + // "\@!", "\@<=" and "\@<!". + // If we got here, it means that the current "invisible" group + // finished successfully, so return control to the parent + // nfa_regmatch(). For a look-behind match only when it ends + // in the position in "nfa_endp". + // Submatches are stored in *m, and used in the parent call. #ifdef REGEXP_DEBUG if (nfa_endp != NULL) { - if (REG_MULTI) - fprintf( - log_fd, - "Current lnum: %d, endp lnum: %d; current col: %d, endp col: %d\n", - (int)reglnum, - (int)nfa_endp->se_u.pos.lnum, - (int)(reginput - regline), - nfa_endp->se_u.pos.col); - else + if (REG_MULTI) { + fprintf(log_fd, + "Current lnum: %d, endp lnum: %d;" + " current col: %d, endp col: %d\n", + (int)rex.lnum, + (int)nfa_endp->se_u.pos.lnum, + (int)(rex.input - rex.line), + nfa_endp->se_u.pos.col); + } else { fprintf(log_fd, "Current col: %d, endp col: %d\n", - (int)(reginput - regline), - (int)(nfa_endp->se_u.ptr - reginput)); + (int)(rex.input - rex.line), + (int)(nfa_endp->se_u.ptr - rex.input)); + } } #endif - /* If "nfa_endp" is set it's only a match if it ends at - * "nfa_endp" */ - if (nfa_endp != NULL && (REG_MULTI - ? (reglnum != nfa_endp->se_u.pos.lnum - || (int)(reginput - regline) - != nfa_endp->se_u.pos.col) - : reginput != nfa_endp->se_u.ptr)) + // If "nfa_endp" is set it's only a match if it ends at + // "nfa_endp" + if (nfa_endp != NULL + && (REG_MULTI + ? (rex.lnum != nfa_endp->se_u.pos.lnum + || (int)(rex.input - rex.line) != nfa_endp->se_u.pos.col) + : rex.input != nfa_endp->se_u.ptr)) { break; - - /* do not set submatches for \@! */ + } + // do not set submatches for \@! if (t->state->c != NFA_END_INVISIBLE_NEG) { copy_sub(&m->norm, &t->subs.norm); - if (nfa_has_zsubexpr) + if (rex.nfa_has_zsubexpr) { copy_sub(&m->synt, &t->subs.synt); + } } #ifdef REGEXP_DEBUG fprintf(log_fd, "Match found:\n"); @@ -5322,9 +5351,9 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, // Copy submatch info for the recursive call, opposite // of what happens on success below. copy_sub_off(&m->norm, &t->subs.norm); - if (nfa_has_zsubexpr) + if (rex.nfa_has_zsubexpr) { copy_sub_off(&m->synt, &t->subs.synt); - + } // First try matching the invisible match, then what // follows. result = recursive_regmatch(t->state, NULL, prog, submatch, m, @@ -5335,7 +5364,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, } // for \@! and \@<! it is a match when the result is - // FALSE + // false if (result != (t->state->c == NFA_START_INVISIBLE_NEG || t->state->c == NFA_START_INVISIBLE_NEG_FIRST || t->state->c @@ -5344,8 +5373,9 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, == NFA_START_INVISIBLE_BEFORE_NEG_FIRST)) { // Copy submatch info from the recursive call copy_sub_off(&t->subs.norm, &m->norm); - if (nfa_has_zsubexpr) + if (rex.nfa_has_zsubexpr) { copy_sub_off(&t->subs.synt, &m->synt); + } // If the pattern has \ze and it matched in the // sub pattern, use it. copy_ze_off(&t->subs.norm, &m->norm); @@ -5369,11 +5399,11 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, pim.subs.norm.in_use = 0; pim.subs.synt.in_use = 0; if (REG_MULTI) { - pim.end.pos.col = (int)(reginput - regline); - pim.end.pos.lnum = reglnum; - } else - pim.end.ptr = reginput; - + pim.end.pos.col = (int)(rex.input - rex.line); + pim.end.pos.lnum = rex.lnum; + } else { + pim.end.ptr = rex.input; + } // t->state->out1 is the corresponding END_INVISIBLE // node; Add its out to the current list (zero-width // match). @@ -5426,7 +5456,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, // Copy submatch info to the recursive call, opposite of what // happens afterwards. copy_sub_off(&m->norm, &t->subs.norm); - if (nfa_has_zsubexpr) { + if (rex.nfa_has_zsubexpr) { copy_sub_off(&m->synt, &t->subs.synt); } @@ -5446,7 +5476,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, #endif // Copy submatch info from the recursive call copy_sub_off(&t->subs.norm, &m->norm); - if (nfa_has_zsubexpr) { + if (rex.nfa_has_zsubexpr) { copy_sub_off(&t->subs.synt, &m->synt); } // Now we need to skip over the matched text and then @@ -5454,9 +5484,9 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, if (REG_MULTI) { // TODO(RE): multi-line match bytelen = m->norm.list.multi[0].end_col - - (int)(reginput - regline); + - (int)(rex.input - rex.line); } else { - bytelen = (int)(m->norm.list.line[0].end - reginput); + bytelen = (int)(m->norm.list.line[0].end - rex.input); } #ifdef REGEXP_DEBUG @@ -5485,7 +5515,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, } case NFA_BOL: - if (reginput == regline) { + if (rex.input == rex.line) { add_here = true; add_state = t->state->out; } @@ -5503,20 +5533,16 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, if (curc == NUL) { result = false; - } else if (has_mbyte) { + } else { int this_class; // Get class of current and previous char (if it exists). - this_class = mb_get_class_tab(reginput, rex.reg_buf->b_chartab); + this_class = mb_get_class_tab(rex.input, rex.reg_buf->b_chartab); if (this_class <= 1) { result = false; } else if (reg_prev_class() == this_class) { result = false; } - } else if (!vim_iswordc_buf(curc, rex.reg_buf) - || (reginput > regline - && vim_iswordc_buf(reginput[-1], rex.reg_buf))) { - result = false; } if (result) { add_here = true; @@ -5526,22 +5552,18 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, case NFA_EOW: result = true; - if (reginput == regline) { + if (rex.input == rex.line) { result = false; - } else if (has_mbyte) { + } else { int this_class, prev_class; // Get class of current and previous char (if it exists). - this_class = mb_get_class_tab(reginput, rex.reg_buf->b_chartab); + this_class = mb_get_class_tab(rex.input, rex.reg_buf->b_chartab); prev_class = reg_prev_class(); if (this_class == prev_class || prev_class == 0 || prev_class == 1) { result = false; } - } else if (!vim_iswordc_buf(reginput[-1], rex.reg_buf) - || (reginput[0] != NUL - && vim_iswordc_buf(curc, rex.reg_buf))) { - result = false; } if (result) { add_here = true; @@ -5550,7 +5572,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, break; case NFA_BOF: - if (reglnum == 0 && reginput == regline + if (rex.lnum == 0 && rex.input == rex.line && (!REG_MULTI || rex.reg_firstlnum == 1)) { add_here = true; add_state = t->state->out; @@ -5558,7 +5580,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, break; case NFA_EOF: - if (reglnum == rex.reg_maxline && curc == NUL) { + if (rex.lnum == rex.reg_maxline && curc == NUL) { add_here = true; add_state = t->state->out; } @@ -5603,7 +5625,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, // We don't care about the order of composing characters. // Get them into cchars[] first. while (len < clen) { - mc = utf_ptr2char(reginput + len); + mc = utf_ptr2char(rex.input + len); cchars[ccount++] = mc; len += mb_char2len(mc); if (ccount == MAX_MCO) @@ -5634,7 +5656,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, case NFA_NEWL: if (curc == NUL && !rex.reg_line_lbr && REG_MULTI - && reglnum <= rex.reg_maxline) { + && rex.lnum <= rex.reg_maxline) { go_to_nextline = true; // Pass -1 for the offset, which means taking the position // at the start of the next line. @@ -5688,7 +5710,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, for (; c1 <= c2; c1++) { if (utf_fold(c1) == curc_low) { result = result_if_matched; - done = TRUE; + done = true; break; } } @@ -5746,13 +5768,13 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, break; case NFA_KWORD: // \k - result = vim_iswordp_buf(reginput, rex.reg_buf); + result = vim_iswordp_buf(rex.input, rex.reg_buf); ADD_STATE_IF_MATCH(t->state); break; case NFA_SKWORD: // \K result = !ascii_isdigit(curc) - && vim_iswordp_buf(reginput, rex.reg_buf); + && vim_iswordp_buf(rex.input, rex.reg_buf); ADD_STATE_IF_MATCH(t->state); break; @@ -5767,12 +5789,12 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, break; case NFA_PRINT: // \p - result = vim_isprintc(PTR2CHAR(reginput)); + result = vim_isprintc(PTR2CHAR(rex.input)); ADD_STATE_IF_MATCH(t->state); break; case NFA_SPRINT: // \P - result = !ascii_isdigit(curc) && vim_isprintc(PTR2CHAR(reginput)); + result = !ascii_isdigit(curc) && vim_isprintc(PTR2CHAR(rex.input)); ADD_STATE_IF_MATCH(t->state); break; @@ -5959,14 +5981,14 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, case NFA_LNUM_LT: assert(t->state->val >= 0 && !((rex.reg_firstlnum > 0 - && reglnum > LONG_MAX - rex.reg_firstlnum) + && rex.lnum > LONG_MAX - rex.reg_firstlnum) || (rex.reg_firstlnum < 0 - && reglnum < LONG_MIN + rex.reg_firstlnum)) - && reglnum + rex.reg_firstlnum >= 0); + && rex.lnum < LONG_MIN + rex.reg_firstlnum)) + && rex.lnum + rex.reg_firstlnum >= 0); result = (REG_MULTI && nfa_re_num_cmp((uintmax_t)t->state->val, t->state->c - NFA_LNUM, - (uintmax_t)(reglnum + rex.reg_firstlnum))); + (uintmax_t)(rex.lnum + rex.reg_firstlnum))); if (result) { add_here = true; add_state = t->state->out; @@ -5977,11 +5999,11 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, case NFA_COL_GT: case NFA_COL_LT: assert(t->state->val >= 0 - && reginput >= regline - && (uintmax_t)(reginput - regline) <= UINTMAX_MAX - 1); + && rex.input >= rex.line + && (uintmax_t)(rex.input - rex.line) <= UINTMAX_MAX - 1); result = nfa_re_num_cmp((uintmax_t)t->state->val, t->state->c - NFA_COL, - (uintmax_t)(reginput - regline + 1)); + (uintmax_t)(rex.input - rex.line + 1)); if (result) { add_here = true; add_state = t->state->out; @@ -5993,7 +6015,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, case NFA_VCOL_LT: { int op = t->state->c - NFA_VCOL; - colnr_T col = (colnr_T)(reginput - regline); + colnr_T col = (colnr_T)(rex.input - rex.line); // Bail out quickly when there can't be a match, avoid the overhead of // win_linetabsize() on long lines. @@ -6014,7 +6036,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, result = col > t->state->val * ts; } if (!result) { - uintmax_t lts = win_linetabsize(wp, regline, col); + uintmax_t lts = win_linetabsize(wp, rex.line, col); assert(t->state->val >= 0); result = nfa_re_num_cmp((uintmax_t)t->state->val, op, lts + 1); } @@ -6034,13 +6056,13 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, // Compare the mark position to the match position. result = (pos != NULL // mark doesn't exist && pos->lnum > 0 // mark isn't set in reg_buf - && (pos->lnum == reglnum + rex.reg_firstlnum - ? (pos->col == (colnr_T)(reginput - regline) + && (pos->lnum == rex.lnum + rex.reg_firstlnum + ? (pos->col == (colnr_T)(rex.input - rex.line) ? t->state->c == NFA_MARK - : (pos->col < (colnr_T)(reginput - regline) + : (pos->col < (colnr_T)(rex.input - rex.line) ? t->state->c == NFA_MARK_GT : t->state->c == NFA_MARK_LT)) - : (pos->lnum < reglnum + rex.reg_firstlnum + : (pos->lnum < rex.lnum + rex.reg_firstlnum ? t->state->c == NFA_MARK_GT : t->state->c == NFA_MARK_LT))); if (result) { @@ -6051,10 +6073,9 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, } case NFA_CURSOR: - result = (rex.reg_win != NULL - && (reglnum + rex.reg_firstlnum == rex.reg_win->w_cursor.lnum) - && ((colnr_T)(reginput - regline) - == rex.reg_win->w_cursor.col)); + result = rex.reg_win != NULL + && (rex.lnum + rex.reg_firstlnum == rex.reg_win->w_cursor.lnum) + && ((colnr_T)(rex.input - rex.line) == rex.reg_win->w_cursor.col); if (result) { add_here = true; add_state = t->state->out; @@ -6112,7 +6133,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, // If rex.reg_icombine is not set only skip over the character // itself. When it is set skip over composing characters. if (result && enc_utf8 && !rex.reg_icombine) { - clen = utf_ptr2len(reginput); + clen = utf_ptr2len(rex.input); } ADD_STATE_IF_MATCH(t->state); @@ -6143,7 +6164,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, &listids, &listids_len); pim->result = result ? NFA_PIM_MATCH : NFA_PIM_NOMATCH; // for \@! and \@<! it is a match when the result is - // FALSE + // false if (result != (pim->state->c == NFA_START_INVISIBLE_NEG || pim->state->c == NFA_START_INVISIBLE_NEG_FIRST || pim->state->c @@ -6152,8 +6173,9 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, == NFA_START_INVISIBLE_BEFORE_NEG_FIRST)) { // Copy submatch info from the recursive call copy_sub_off(&pim->subs.norm, &m->norm); - if (nfa_has_zsubexpr) + if (rex.nfa_has_zsubexpr) { copy_sub_off(&pim->subs.synt, &m->synt); + } } } else { result = (pim->result == NFA_PIM_MATCH); @@ -6163,12 +6185,12 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, log_fd, "Using previous recursive nfa_regmatch() result, result == %d\n", pim->result); - fprintf(log_fd, "MATCH = %s\n", result ? "OK" : "FALSE"); + fprintf(log_fd, "MATCH = %s\n", result ? "OK" : "false"); fprintf(log_fd, "\n"); #endif } - // for \@! and \@<! it is a match when result is FALSE + // for \@! and \@<! it is a match when result is false if (result != (pim->state->c == NFA_START_INVISIBLE_NEG || pim->state->c == NFA_START_INVISIBLE_NEG_FIRST || pim->state->c @@ -6177,8 +6199,9 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, == NFA_START_INVISIBLE_BEFORE_NEG_FIRST)) { // Copy submatch info from the recursive call copy_sub_off(&t->subs.norm, &pim->subs.norm); - if (nfa_has_zsubexpr) + if (rex.nfa_has_zsubexpr) { copy_sub_off(&t->subs.synt, &pim->subs.synt); + } } else { // look-behind match failed, don't add the state continue; @@ -6222,29 +6245,28 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, // Also don't start a match past the first line. if (!nfa_match && ((toplevel - && reglnum == 0 + && rex.lnum == 0 && clen != 0 && (rex.reg_maxcol == 0 - || (colnr_T)(reginput - regline) < rex.reg_maxcol)) + || (colnr_T)(rex.input - rex.line) < rex.reg_maxcol)) || (nfa_endp != NULL && (REG_MULTI - ? (reglnum < nfa_endp->se_u.pos.lnum - || (reglnum == nfa_endp->se_u.pos.lnum - && (int)(reginput - regline) + ? (rex.lnum < nfa_endp->se_u.pos.lnum + || (rex.lnum == nfa_endp->se_u.pos.lnum + && (int)(rex.input - rex.line) < nfa_endp->se_u.pos.col)) - : reginput < nfa_endp->se_u.ptr)))) { + : rex.input < nfa_endp->se_u.ptr)))) { #ifdef REGEXP_DEBUG fprintf(log_fd, "(---) STARTSTATE\n"); #endif // Inline optimized code for addstate() if we know the state is // the first MOPEN. if (toplevel) { - int add = TRUE; - int c; + int add = true; if (prog->regstart != NUL && clen != 0) { if (nextlist->n == 0) { - colnr_T col = (colnr_T)(reginput - regline) + clen; + colnr_T col = (colnr_T)(rex.input - rex.line) + clen; // Nextlist is empty, we can skip ahead to the // character that must appear at the start. @@ -6253,13 +6275,13 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, } #ifdef REGEXP_DEBUG fprintf(log_fd, " Skipping ahead %d bytes to regstart\n", - col - ((colnr_T)(reginput - regline) + clen)); + col - ((colnr_T)(rex.input - rex.line) + clen)); #endif - reginput = regline + col - clen; + rex.input = rex.line + col - clen; } else { // Checking if the required start character matches is // cheaper than adding a state that won't match. - c = PTR2CHAR(reginput + clen); + const int c = PTR2CHAR(rex.input + clen); if (c != prog->regstart && (!rex.reg_ic || utf_fold(c) != utf_fold(prog->regstart))) { @@ -6267,17 +6289,18 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, fprintf(log_fd, " Skipping start state, regstart does not match\n"); #endif - add = FALSE; + add = false; } } } if (add) { - if (REG_MULTI) + if (REG_MULTI) { m->norm.list.multi[0].start_col = - (colnr_T)(reginput - regline) + clen; - else - m->norm.list.line[0].start = reginput + clen; + (colnr_T)(rex.input - rex.line) + clen; + } else { + m->norm.list.line[0].start = rex.input + clen; + } if (addstate(nextlist, start->out, m, NULL, clen) == NULL) { nfa_match = NFA_TOO_EXPENSIVE; goto theend; @@ -6306,9 +6329,9 @@ nextchar: // Advance to the next character, or advance to the next line, or // finish. if (clen != 0) { - reginput += clen; + rex.input += clen; } else if (go_to_nextline || (nfa_endp != NULL && REG_MULTI - && reglnum < nfa_endp->se_u.pos.lnum)) { + && rex.lnum < nfa_endp->se_u.pos.lnum)) { reg_nextline(); } else { break; @@ -6347,7 +6370,7 @@ theend: return nfa_match; } -// Try match of "prog" with at regline["col"]. +// Try match of "prog" with at rex.line["col"]. // Returns <= 0 for failure, number of lines contained in the match otherwise. static long nfa_regtry(nfa_regprog_T *prog, colnr_T col, @@ -6361,7 +6384,7 @@ static long nfa_regtry(nfa_regprog_T *prog, FILE *f; #endif - reginput = regline + col; + rex.input = rex.line + col; nfa_time_limit = tm; nfa_timed_out = timed_out; nfa_time_count = 0; @@ -6374,7 +6397,7 @@ static long nfa_regtry(nfa_regprog_T *prog, #ifdef REGEXP_DEBUG fprintf(f, "\tRegexp is \"%s\"\n", nfa_regengine.expr); #endif - fprintf(f, "\tInput text is \"%s\" \n", reginput); + fprintf(f, "\tInput text is \"%s\" \n", rex.input); fprintf(f, "\t=======================================================\n\n"); nfa_print_state(f, start); fprintf(f, "\n\n"); @@ -6412,11 +6435,11 @@ static long nfa_regtry(nfa_regprog_T *prog, } if (rex.reg_endpos[0].lnum < 0) { // pattern has a \ze but it didn't match, use current end - rex.reg_endpos[0].lnum = reglnum; - rex.reg_endpos[0].col = (int)(reginput - regline); + rex.reg_endpos[0].lnum = rex.lnum; + rex.reg_endpos[0].col = (int)(rex.input - rex.line); } else { // Use line number of "\ze". - reglnum = rex.reg_endpos[0].lnum; + rex.lnum = rex.reg_endpos[0].lnum; } } else { for (i = 0; i < subs.norm.in_use; i++) { @@ -6425,10 +6448,10 @@ static long nfa_regtry(nfa_regprog_T *prog, } if (rex.reg_startp[0] == NULL) { - rex.reg_startp[0] = regline + col; + rex.reg_startp[0] = rex.line + col; } if (rex.reg_endp[0] == NULL) { - rex.reg_endp[0] = reginput; + rex.reg_endp[0] = rex.input; } } @@ -6463,7 +6486,7 @@ static long nfa_regtry(nfa_regprog_T *prog, } } - return 1 + reglnum; + return 1 + rex.lnum; } /// Match a regexp against a string ("line" points to the string) or multiple @@ -6481,7 +6504,6 @@ static long nfa_regexec_both(char_u *line, colnr_T startcol, { nfa_regprog_T *prog; long retval = 0L; - int i; colnr_T col = startcol; if (REG_MULTI) { @@ -6497,7 +6519,7 @@ static long nfa_regexec_both(char_u *line, colnr_T startcol, /* Be paranoid... */ if (prog == NULL || line == NULL) { - EMSG(_(e_null)); + IEMSG(_(e_null)); goto theend; } @@ -6513,26 +6535,30 @@ static long nfa_regexec_both(char_u *line, colnr_T startcol, rex.reg_icombine = true; } - regline = line; - reglnum = 0; /* relative to line */ + rex.line = line; + rex.lnum = 0; // relative to line - nfa_has_zend = prog->has_zend; - nfa_has_backref = prog->has_backref; - nfa_nsubexpr = prog->nsubexp; - nfa_listid = 1; - nfa_alt_listid = 2; + rex.nfa_has_zend = prog->has_zend; + rex.nfa_has_backref = prog->has_backref; + rex.nfa_nsubexpr = prog->nsubexp; + rex.nfa_listid = 1; + rex.nfa_alt_listid = 2; +#ifdef REGEXP_DEBUG nfa_regengine.expr = prog->pattern; +#endif if (prog->reganch && col > 0) return 0L; - need_clear_subexpr = TRUE; - /* Clear the external match subpointers if necessary. */ + rex.need_clear_subexpr = true; + // Clear the external match subpointers if necessary. if (prog->reghasz == REX_SET) { - nfa_has_zsubexpr = TRUE; - need_clear_zsubexpr = TRUE; - } else - nfa_has_zsubexpr = FALSE; + rex.nfa_has_zsubexpr = true; + rex.need_clear_zsubexpr = true; + } else { + rex.nfa_has_zsubexpr = false; + rex.need_clear_zsubexpr = false; + } if (prog->regstart != NUL) { /* Skip ahead until a character we know the match must start with. @@ -6552,8 +6578,10 @@ static long nfa_regexec_both(char_u *line, colnr_T startcol, goto theend; } - nstate = prog->nstate; - for (i = 0; i < nstate; ++i) { + // Set the "nstate" used by nfa_regcomp() to zero to trigger an error when + // it's accidentally used during execution. + nstate = 0; + for (int i = 0; i < prog->nstate; i++) { prog->state[i].id = i; prog->state[i].lastlist[0] = 0; prog->state[i].lastlist[1] = 0; @@ -6561,7 +6589,9 @@ static long nfa_regexec_both(char_u *line, colnr_T startcol, retval = nfa_regtry(prog, col, tm, timed_out); +#ifdef REGEXP_DEBUG nfa_regengine.expr = NULL; +#endif theend: return retval; @@ -6579,7 +6609,9 @@ static regprog_T *nfa_regcomp(char_u *expr, int re_flags) if (expr == NULL) return NULL; +#ifdef REGEXP_DEBUG nfa_regengine.expr = expr; +#endif nfa_re_flags = re_flags; init_class_tab(); @@ -6616,26 +6648,27 @@ static regprog_T *nfa_regcomp(char_u *expr, int re_flags) * PASS 1 * Count number of NFA states in "nstate". Do not build the NFA. */ - post2nfa(postfix, post_ptr, TRUE); + post2nfa(postfix, post_ptr, true); /* allocate the regprog with space for the compiled regexp */ size_t prog_size = sizeof(nfa_regprog_T) + sizeof(nfa_state_T) * (nstate - 1); prog = xmalloc(prog_size); state_ptr = prog->state; + prog->re_in_use = false; /* * PASS 2 * Build the NFA */ - prog->start = post2nfa(postfix, post_ptr, FALSE); - if (prog->start == NULL) + prog->start = post2nfa(postfix, post_ptr, false); + if (prog->start == NULL) { goto fail; - + } prog->regflags = regflags; prog->engine = &nfa_regengine; prog->nstate = nstate; - prog->has_zend = nfa_has_zend; - prog->has_backref = nfa_has_backref; + prog->has_zend = rex.nfa_has_zend; + prog->has_backref = rex.nfa_has_backref; prog->nsubexp = regnpar; nfa_postprocess(prog); @@ -6651,7 +6684,9 @@ static regprog_T *nfa_regcomp(char_u *expr, int re_flags) /* Remember whether this pattern has any \z specials in it. */ prog->reghasz = re_has_z; prog->pattern = vim_strsave(expr); +#ifdef REGEXP_DEBUG nfa_regengine.expr = NULL; +#endif out: xfree(post_start); @@ -6663,8 +6698,8 @@ fail: XFREE_CLEAR(prog); #ifdef REGEXP_DEBUG nfa_postfix_dump(expr, FAIL); -#endif nfa_regengine.expr = NULL; +#endif goto out; } diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 7bed747e9a..8998f9037e 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -25,7 +25,7 @@ // // 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(VALID) to have the window displayed by update_screen() +// 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 @@ -37,7 +37,7 @@ // // 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(NOT_VALID) to have the whole window +// 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') @@ -45,11 +45,11 @@ // buffer redisplayed by update_screen() later. // // Commands that change highlighting and possibly cause a scroll too must call -// redraw_later(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. +// 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(NOT_VALID). +// 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 @@ -88,6 +88,7 @@ #include "nvim/main.h" #include "nvim/mark.h" #include "nvim/extmark.h" +#include "nvim/decoration.h" #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" @@ -119,10 +120,12 @@ #include "nvim/api/private/helpers.h" #include "nvim/api/vim.h" #include "nvim/lua/executor.h" +#include "nvim/lib/kvec.h" #define MB_FILLER_CHAR '<' /* character used when a double-width character * doesn't fit. */ +typedef kvec_withinit_t(DecorProvider *, 4) Providers; // temporary buffer for rendering a single screenline, so it can be // compared with previous contents to calculate smallest delta. @@ -133,8 +136,6 @@ static sattr_T *linebuf_attr = NULL; static match_T search_hl; /* used for 'hlsearch' highlight matching */ -static foldinfo_T win_foldinfo; /* info for 'foldcolumn' */ - StlClickDefinition *tab_page_click_defs = NULL; long tab_page_click_defs_size = 0; @@ -158,22 +159,48 @@ static bool msg_grid_invalid = false; static bool resizing = false; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "screen.c.generated.h" #endif #define SEARCH_HL_PRIORITY 0 -/* - * Redraw the current 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(int type) +static char * provider_first_error = NULL; + +static bool provider_invoke(NS ns_id, const char *name, LuaRef ref, + Array args, bool default_true) { - redraw_win_later(curwin, type); + Error err = ERROR_INIT; + + textlock++; + Object ret = nlua_call_ref(ref, name, args, true, &err); + textlock--; + + if (!ERROR_SET(&err) + && api_object_to_bool(ret, "provider %s retval", default_true, &err)) { + return true; + } + + if (ERROR_SET(&err)) { + const char *ns_name = describe_ns(ns_id); + ELOG("error in provider %s:%s: %s", ns_name, name, err.msg); + bool verbose_errs = true; // TODO(bfredl): + if (verbose_errs && provider_first_error == NULL) { + static char errbuf[IOSIZE]; + snprintf(errbuf, sizeof errbuf, "%s: %s", ns_name, err.msg); + provider_first_error = xstrdup(errbuf); + } + } + + api_free_object(ret); + return false; } -void redraw_win_later(win_T *wp, int type) +/// 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) { @@ -191,7 +218,7 @@ void redraw_win_later(win_T *wp, int type) void redraw_all_later(int type) { FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - redraw_win_later(wp, type); + redraw_later(wp, type); } // This may be needed when switching tabs. if (must_redraw < type) { @@ -202,7 +229,7 @@ void redraw_all_later(int type) void screen_invalidate_highlights(void) { FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - redraw_win_later(wp, NOT_VALID); + redraw_later(wp, NOT_VALID); wp->w_grid.valid = false; } } @@ -219,7 +246,7 @@ void redraw_buf_later(buf_T *buf, int type) { FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { if (wp->w_buffer == buf) { - redraw_win_later(wp, type); + redraw_later(wp, type); } } } @@ -245,7 +272,7 @@ void redraw_buf_range_later(buf_T *buf, linenr_T firstline, linenr_T lastline) if (wp->w_redraw_bot == 0 || wp->w_redraw_bot < lastline) { wp->w_redraw_bot = lastline; } - redraw_win_later(wp, VALID); + redraw_later(wp, VALID); } } } @@ -273,7 +300,7 @@ redrawWinline( if (wp->w_redraw_bot == 0 || wp->w_redraw_bot < lnum) { wp->w_redraw_bot = lnum; } - redraw_win_later(wp, VALID); + redraw_later(wp, VALID); } } @@ -327,7 +354,6 @@ int update_screen(int type) /* Postpone the redrawing when it's not needed and when being called * recursively. */ if (!redrawing() || updating_screen) { - redraw_later(type); /* remember type for next time */ must_redraw = type; if (type > INVERTED_ALL) { curwin->w_lines_valid = 0; // don't use w_lines[].wl_size now @@ -446,6 +472,35 @@ int update_screen(int type) ui_comp_set_screen_valid(true); + Providers providers; + kvi_init(providers); + for (size_t i = 0; i < kv_size(decor_providers); i++) { + DecorProvider *p = &kv_A(decor_providers, i); + if (!p->active) { + continue; + } + + bool active; + if (p->redraw_start != LUA_NOREF) { + FIXED_TEMP_ARRAY(args, 2); + args.items[0] = INTEGER_OBJ(display_tick); + args.items[1] = INTEGER_OBJ(type); + active = provider_invoke(p->ns_id, "start", p->redraw_start, args, true); + } else { + active = true; + } + + if (active) { + kvi_push(providers, p); + } + } + + // "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); @@ -494,30 +549,24 @@ int update_screen(int type) FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { update_window_hl(wp, type >= NOT_VALID); - if (wp->w_buffer->b_mod_set) { - win_T *wwp; - - // Check if we already did this buffer. - for (wwp = firstwin; wwp != wp; wwp = wwp->w_next) { - if (wwp->w_buffer == wp->w_buffer) { - break; - } - } - if (wwp == wp && syntax_present(wp)) { - syn_stack_apply_changes(wp->w_buffer); - } - - buf_T *buf = wp->w_buffer; - if (buf->b_luahl && buf->b_luahl_window != LUA_NOREF) { - Error err = ERROR_INIT; - FIXED_TEMP_ARRAY(args, 2); - args.items[0] = BUFFER_OBJ(buf->handle); - args.items[1] = INTEGER_OBJ(display_tick); - executor_exec_lua_cb(buf->b_luahl_start, "start", args, false, &err); - if (ERROR_SET(&err)) { - ELOG("error in luahl start: %s", err.msg); - api_clear_error(&err); + 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) { + for (size_t i = 0; i < kv_size(providers); i++) { + DecorProvider *p = kv_A(providers, i); + if (p && p->redraw_buf != LUA_NOREF) { + FIXED_TEMP_ARRAY(args, 1); + args.items[0] = BUFFER_OBJ(buf->handle); + provider_invoke(p->ns_id, "buf", p->redraw_buf, args, true); + } } + buf->b_mod_tick_decor = display_tick; } } } @@ -541,7 +590,7 @@ int update_screen(int type) did_one = TRUE; start_search_hl(); } - win_update(wp); + win_update(wp, &providers); } /* redraw status line after the window to minimize cursor movement */ @@ -578,6 +627,21 @@ int update_screen(int type) maybe_intro_message(); did_intro = TRUE; + for (size_t i = 0; i < kv_size(providers); i++) { + DecorProvider *p = kv_A(providers, i); + if (!p->active) { + continue; + } + + if (p->redraw_end != LUA_NOREF) { + FIXED_TEMP_ARRAY(args, 1); + args.items[0] = INTEGER_OBJ(display_tick); + provider_invoke(p->ns_id, "end", p->redraw_end, args, true); + } + } + kvi_destroy(providers); + + // either cmdline is cleared, not drawn or mode is last drawn cmdline_was_last_drawn = false; return OK; @@ -634,17 +698,6 @@ bool win_cursorline_standout(const win_T *wp) || (wp->w_p_cole > 0 && (VIsual_active || !conceal_cursor_line(wp))); } -static DecorationRedrawState decorations; -bool decorations_active = false; - -void decorations_add_luahl_attr(int attr_id, - int start_row, int start_col, - int end_row, int end_col) -{ - kv_push(decorations.active, - ((HlRange){ start_row, start_col, end_row, end_col, attr_id, NULL })); -} - /* * Update a single window. * @@ -672,7 +725,7 @@ void decorations_add_luahl_attr(int attr_id, * 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) +static void win_update(win_T *wp, Providers *providers) { buf_T *buf = wp->w_buffer; int type; @@ -697,9 +750,10 @@ static void win_update(win_T *wp) int didline = FALSE; /* if TRUE, we finished the last line */ int i; long j; - static int recursive = FALSE; /* being called recursively */ - int old_botline = wp->w_botline; - long fold_count; + static bool recursive = false; // being called recursively + const linenr_T old_botline = wp->w_botline; + const int old_wrow = wp->w_wrow; + const int old_wcol = wp->w_wcol; // Remember what happened to the previous line. #define DID_NONE 1 // didn't update a line #define DID_LINE 2 // updated a normal line @@ -710,6 +764,7 @@ static void win_update(win_T *wp) linenr_T mod_bot = 0; int save_got_int; + // If we can compute a change in the automatic sizing of the sign column // under 'signcolumn=auto:X' and signs currently placed in the buffer, better // figuring it out here so we can redraw the entire screen for it. @@ -898,11 +953,12 @@ static void win_update(win_T *wp) || type == INVERTED || type == INVERTED_ALL) && !wp->w_botfill && !wp->w_old_botfill ) { - if (mod_top != 0 && wp->w_topline == mod_top) { - /* - * w_topline is the first changed line, the scrolling will be done - * further down. - */ + 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 @@ -1226,7 +1282,6 @@ static void win_update(win_T *wp) // Set the time limit to 'redrawtime'. proftime_T syntax_tm = profile_setlimit(p_rdt); syn_set_timeout(&syntax_tm); - win_foldinfo.fi_level = 0; /* * Update all the window rows. @@ -1236,28 +1291,32 @@ static void win_update(win_T *wp) srow = 0; lnum = wp->w_topline; // first line shown in window - decorations_active = decorations_redraw_reset(buf, &decorations); + decor_redraw_reset(buf, &decor_state); - if (buf->b_luahl && buf->b_luahl_window != LUA_NOREF) { - Error err = ERROR_INIT; - FIXED_TEMP_ARRAY(args, 4); - linenr_T knownmax = ((wp->w_valid & VALID_BOTLINE) - ? wp->w_botline - : (wp->w_topline + wp->w_height_inner)); - args.items[0] = WINDOW_OBJ(wp->handle); - args.items[1] = BUFFER_OBJ(buf->handle); - // TODO(bfredl): we are not using this, but should be first drawn line? - args.items[2] = INTEGER_OBJ(wp->w_topline-1); - args.items[3] = INTEGER_OBJ(knownmax); - // TODO(bfredl): we could allow this callback to change mod_top, mod_bot. - // For now the "start" callback is expected to use nvim__buf_redraw_range. - executor_exec_lua_cb(buf->b_luahl_window, "window", args, false, &err); - if (ERROR_SET(&err)) { - ELOG("error in luahl window: %s", err.msg); - api_clear_error(&err); + Providers line_providers; + kvi_init(line_providers); + + linenr_T knownmax = ((wp->w_valid & VALID_BOTLINE) + ? wp->w_botline + : (wp->w_topline + wp->w_height_inner)); + + for (size_t k = 0; k < kv_size(*providers); k++) { + DecorProvider *p = kv_A(*providers, k); + if (p && p->redraw_win != LUA_NOREF) { + FIXED_TEMP_ARRAY(args, 4); + args.items[0] = WINDOW_OBJ(wp->handle); + args.items[1] = BUFFER_OBJ(buf->handle); + // TODO(bfredl): we are not using this, but should be first drawn line? + args.items[2] = INTEGER_OBJ(wp->w_topline-1); + args.items[3] = INTEGER_OBJ(knownmax); + if (provider_invoke(p->ns_id, "win", p->redraw_win, args, true)) { + kvi_push(line_providers, p); + } } } + win_check_ns_hl(wp); + for (;; ) { /* stop updating when reached the end of the window (check for _past_ @@ -1448,24 +1507,19 @@ static void win_update(win_T *wp) * Otherwise, display normally (can be several display lines when * 'wrap' is on). */ - fold_count = foldedCount(wp, lnum, &win_foldinfo); - if (fold_count != 0) { - fold_line(wp, fold_count, &win_foldinfo, lnum, row); - ++row; - --fold_count; - wp->w_lines[idx].wl_folded = TRUE; - wp->w_lines[idx].wl_lastlnum = lnum + fold_count; - did_update = DID_FOLD; - } else if (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 - && diff_check_fill(wp, lnum) == 0 - ) { - /* This line is not going to fit. Don't draw anything here, - * will draw "@ " lines below. */ + 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 + && diff_check_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, lnum); @@ -1474,14 +1528,21 @@ static void win_update(win_T *wp) && syntax_present(wp)) syntax_end_parsing(syntax_last_parsed + 1); - /* - * Display one line. - */ - row = win_line(wp, lnum, srow, wp->w_grid.Rows, mod_top == 0, false); + // Display one line + row = win_line(wp, lnum, srow, + foldinfo.fi_lines ? srow : wp->w_grid.Rows, + mod_top == 0, false, foldinfo, &line_providers); - wp->w_lines[idx].wl_folded = FALSE; + wp->w_lines[idx].wl_folded = foldinfo.fi_lines != 0; wp->w_lines[idx].wl_lastlnum = lnum; did_update = DID_LINE; + + if (foldinfo.fi_lines > 0) { + did_update = DID_FOLD; + foldinfo.fi_lines--; + wp->w_lines[idx].wl_lastlnum = lnum + foldinfo.fi_lines; + } + syntax_last_parsed = lnum; } @@ -1496,20 +1557,18 @@ static void win_update(win_T *wp) idx++; break; } - if (dollar_vcol == -1) + if (dollar_vcol == -1) { wp->w_lines[idx].wl_size = row - srow; - ++idx; - lnum += fold_count + 1; + } + idx++; + lnum += foldinfo.fi_lines + 1; } else { if (wp->w_p_rnu) { // 'relativenumber' set: The text doesn't need to be drawn, but // the number column nearly always does. - fold_count = foldedCount(wp, lnum, &win_foldinfo); - if (fold_count != 0) { - fold_line(wp, fold_count, &win_foldinfo, lnum, row); - } else { - (void)win_line(wp, lnum, srow, wp->w_grid.Rows, true, true); - } + 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. @@ -1605,6 +1664,8 @@ static void win_update(win_T *wp) HLF_EOB); } + kvi_destroy(line_providers); + if (wp->w_redr_type >= REDRAW_TOP) { draw_vsep_win(wp, 0); } @@ -1631,21 +1692,55 @@ static void win_update(win_T *wp) wp->w_valid |= VALID_BOTLINE; wp->w_viewport_invalid = true; if (wp == curwin && wp->w_botline != old_botline && !recursive) { - recursive = TRUE; + const linenr_T old_topline = wp->w_topline; + const int new_wcol = wp->w_wcol; + recursive = true; curwin->w_valid &= ~VALID_TOPLINE; - update_topline(); /* may invalidate w_botline again */ - if (must_redraw != 0) { - /* Don't update for changes in buffer again. */ + update_topline(); // may invalidate w_botline again + + if (old_wcol != new_wcol + && (wp->w_valid & (VALID_WCOL|VALID_WROW)) + != (VALID_WCOL|VALID_WROW)) { + // A win_line() call applied a fix to screen cursor column to + // accomodate concealment of cursor line, but in this call to + // update_topline() the cursor's row or column got invalidated. + // If they are left invalid, setcursor() will recompute them + // but there won't be any further win_line() call to re-fix the + // column and the cursor will end up misplaced. So we call + // cursor validation now and reapply the fix again (or call + // win_line() to do it for us). + validate_cursor(); + if (wp->w_wcol == old_wcol + && wp->w_wrow == old_wrow + && old_topline == wp->w_topline) { + wp->w_wcol = new_wcol; + } else { + redrawWinline(wp, wp->w_cursor.lnum); + } + } + // New redraw either due to updated topline or due to wcol fix. + if (wp->w_redr_type != 0) { + // Don't update for changes in buffer again. i = curbuf->b_mod_set; curbuf->b_mod_set = false; - win_update(curwin); - must_redraw = 0; + j = curbuf->b_mod_xlines; + curbuf->b_mod_xlines = 0; + win_update(curwin, providers); curbuf->b_mod_set = i; + curbuf->b_mod_xlines = j; + } + // Other windows might have w_redr_type raised in update_topline(). + must_redraw = 0; + FOR_ALL_WINDOWS_IN_TAB(wwp, curtab) { + if (wwp->w_redr_type > must_redraw) { + must_redraw = wwp->w_redr_type; + } } - recursive = FALSE; + recursive = false; } } + /* restore got_int, unless CTRL-C was hit while redrawing */ if (!got_int) got_int = save_got_int; @@ -1741,31 +1836,6 @@ static int advance_color_col(int vcol, int **color_cols) return **color_cols >= 0; } -// Returns the next grid column. -static int text_to_screenline(win_T *wp, char_u *text, int col, int off) - FUNC_ATTR_NONNULL_ALL -{ - int idx = wp->w_p_rl ? off : off + col; - LineState s = LINE_STATE(text); - - while (*s.p != NUL) { - // TODO(bfredl): cargo-culted from the old Vim code: - // if(col + cells > wp->w_width - (wp->w_p_rl ? col : 0)) { break; } - // This is obvious wrong. If Vim ever fixes this, solve for "cells" again - // in the correct condition. - const int maxcells = wp->w_grid.Columns - col - (wp->w_p_rl ? col : 0); - const int cells = line_putchar(&s, &linebuf_char[idx], maxcells, - wp->w_p_rl); - if (cells == -1) { - break; - } - col += cells; - idx += cells; - } - - return col; -} - // 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) @@ -1830,271 +1900,6 @@ static int line_putchar(LineState *s, schar_T *dest, int maxcells, bool rl) return cells; } -/* - * Display one folded line. - */ -static void fold_line(win_T *wp, long fold_count, foldinfo_T *foldinfo, linenr_T lnum, int row) -{ - char_u buf[FOLD_TEXT_LEN]; - pos_T *top, *bot; - linenr_T lnume = lnum + fold_count - 1; - int len; - char_u *text; - int fdc; - int col; - int txtcol; - int off; - - /* Build the fold line: - * 1. Add the cmdwin_type for the command-line window - * 2. Add the 'foldcolumn' - * 3. Add the 'number' or 'relativenumber' column - * 4. Compose the text - * 5. Add the text - * 6. set highlighting for the Visual area an other text - */ - col = 0; - off = 0; - - /* - * 1. Add the cmdwin_type for the command-line window - * Ignores 'rightleft', this window is never right-left. - */ - if (cmdwin_type != 0 && wp == curwin) { - schar_from_ascii(linebuf_char[off], cmdwin_type); - linebuf_attr[off] = win_hl_attr(wp, HLF_AT); - col++; - } - -# define RL_MEMSET(p, v, l) \ - do { \ - if (wp->w_p_rl) { \ - for (int ri = 0; ri < l; ri++) { \ - linebuf_attr[off + (wp->w_grid.Columns - (p) - (l)) + ri] = v; \ - } \ - } else { \ - for (int ri = 0; ri < l; ri++) { \ - linebuf_attr[off + (p) + ri] = v; \ - } \ - } \ - } while (0) - - // 2. Add the 'foldcolumn' - // Reduce the width when there is not enough space. - fdc = compute_foldcolumn(wp, col); - if (fdc > 0) { - fill_foldcolumn(buf, wp, true, lnum); - const char_u *it = &buf[0]; - for (int i = 0; i < fdc; i++) { - int mb_c = mb_ptr2char_adv(&it); - if (wp->w_p_rl) { - schar_from_char(linebuf_char[off + wp->w_grid.Columns - i - 1 - col], - mb_c); - } else { - schar_from_char(linebuf_char[off + col + i], mb_c); - } - } - RL_MEMSET(col, win_hl_attr(wp, HLF_FC), fdc); - col += fdc; - } - - /* Set all attributes of the 'number' or 'relativenumber' column and the - * text */ - RL_MEMSET(col, win_hl_attr(wp, HLF_FL), wp->w_grid.Columns - col); - - // If signs are being displayed, add spaces. - if (win_signcol_count(wp) > 0) { - len = wp->w_grid.Columns - col; - if (len > 0) { - int len_max = win_signcol_width(wp) * win_signcol_count(wp); - if (len > len_max) { - len = len_max; - } - char_u space_buf[18] = " "; - assert((size_t)len_max <= sizeof(space_buf)); - copy_text_attr(off + col, space_buf, len, - win_hl_attr(wp, HLF_FL)); - col += len; - } - } - - /* - * 3. Add the 'number' or 'relativenumber' column - */ - if (wp->w_p_nu || wp->w_p_rnu) { - len = wp->w_grid.Columns - col; - if (len > 0) { - int w = number_width(wp); - long num; - char *fmt = "%*ld "; - - if (len > w + 1) - len = w + 1; - - 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': cursor line shows absolute - * line number */ - num = lnum; - fmt = "%-*ld "; - } - } - - snprintf((char *)buf, FOLD_TEXT_LEN, fmt, w, num); - if (wp->w_p_rl) { - // the line number isn't reversed - copy_text_attr(off + wp->w_grid.Columns - len - col, buf, len, - win_hl_attr(wp, HLF_FL)); - } else { - copy_text_attr(off + col, buf, len, win_hl_attr(wp, HLF_FL)); - } - col += len; - } - } - - /* - * 4. Compose the folded-line string with 'foldtext', if set. - */ - text = get_foldtext(wp, lnum, lnume, foldinfo, buf); - - txtcol = col; /* remember where text starts */ - - // 5. move the text to linebuf_char[off]. Fill up with "fold". - // Right-left text is put in columns 0 - number-col, normal text is put - // in columns number-col - window-width. - col = text_to_screenline(wp, text, col, off); - - /* Fill the rest of the line with the fold filler */ - if (wp->w_p_rl) - col -= txtcol; - - schar_T sc; - schar_from_char(sc, wp->w_p_fcs_chars.fold); - while (col < wp->w_grid.Columns - - (wp->w_p_rl ? txtcol : 0) - ) { - schar_copy(linebuf_char[off+col++], sc); - } - - if (text != buf) - xfree(text); - - /* - * 6. set highlighting for the Visual area an other text. - * If all folded lines are in the Visual area, highlight the line. - */ - if (VIsual_active && wp->w_buffer == curwin->w_buffer) { - 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; - } - if (lnum >= top->lnum - && lnume <= bot->lnum - && (VIsual_mode != 'v' - || ((lnum > top->lnum - || (lnum == top->lnum - && top->col == 0)) - && (lnume < bot->lnum - || (lnume == bot->lnum - && (bot->col - (*p_sel == 'e')) - >= (colnr_T)STRLEN(ml_get_buf(wp->w_buffer, lnume, - FALSE))))))) { - if (VIsual_mode == Ctrl_V) { - // Visual block mode: highlight the chars part of the block - if (wp->w_old_cursor_fcol + txtcol < (colnr_T)wp->w_grid.Columns) { - if (wp->w_old_cursor_lcol != MAXCOL - && wp->w_old_cursor_lcol + txtcol - < (colnr_T)wp->w_grid.Columns) { - len = wp->w_old_cursor_lcol; - } else { - len = wp->w_grid.Columns - txtcol; - } - RL_MEMSET(wp->w_old_cursor_fcol + txtcol, win_hl_attr(wp, HLF_V), - len - (int)wp->w_old_cursor_fcol); - } - } else { - // Set all attributes of the text - RL_MEMSET(txtcol, win_hl_attr(wp, HLF_V), wp->w_grid.Columns - txtcol); - } - } - } - - // Show colorcolumn in the fold line, but let cursorcolumn override it. - if (wp->w_p_cc_cols) { - int i = 0; - int j = wp->w_p_cc_cols[i]; - int old_txtcol = txtcol; - - while (j > -1) { - txtcol += j; - if (wp->w_p_wrap) { - txtcol -= wp->w_skipcol; - } else { - txtcol -= wp->w_leftcol; - } - if (txtcol >= 0 && txtcol < wp->w_grid.Columns) { - linebuf_attr[off + txtcol] = - hl_combine_attr(linebuf_attr[off + txtcol], win_hl_attr(wp, HLF_MC)); - } - txtcol = old_txtcol; - j = wp->w_p_cc_cols[++i]; - } - } - - /* Show 'cursorcolumn' in the fold line. */ - if (wp->w_p_cuc) { - txtcol += wp->w_virtcol; - if (wp->w_p_wrap) - txtcol -= wp->w_skipcol; - else - txtcol -= wp->w_leftcol; - if (txtcol >= 0 && txtcol < wp->w_grid.Columns) { - linebuf_attr[off + txtcol] = hl_combine_attr( - linebuf_attr[off + txtcol], win_hl_attr(wp, HLF_CUC)); - } - } - - grid_put_linebuf(&wp->w_grid, row, 0, wp->w_grid.Columns, wp->w_grid.Columns, - false, wp, wp->w_hl_attr_normal, false); - - /* - * Update w_cline_height and w_cline_folded if the cursor line was - * updated (saves a call to plines() later). - */ - if (wp == curwin - && lnum <= curwin->w_cursor.lnum - && lnume >= curwin->w_cursor.lnum) { - curwin->w_cline_row = row; - curwin->w_cline_height = 1; - curwin->w_cline_folded = true; - curwin->w_valid |= (VALID_CHEIGHT|VALID_CROW); - conceal_cursor_used = conceal_cursor_line(curwin); - } -} - - -/// Copy "buf[len]" to linebuf_char["off"] and set attributes to "attr". -/// -/// Only works for ASCII text! -static void copy_text_attr(int off, char_u *buf, int len, int attr) -{ - int i; - - for (i = 0; i < len; i++) { - schar_from_ascii(linebuf_char[off + i], buf[i]); - linebuf_attr[off + i] = attr; - } -} /// Fills the foldcolumn at "p" for window "wp". /// Only to be called when 'foldcolumn' > 0. @@ -2109,7 +1914,7 @@ static size_t fill_foldcolumn( char_u *p, win_T *wp, - int closed, + foldinfo_T foldinfo, linenr_T lnum ) { @@ -2120,10 +1925,11 @@ fill_foldcolumn( size_t char_counter = 0; int symbol = 0; int len = 0; + bool closed = foldinfo.fi_lines > 0; // Init to all spaces. memset(p, ' ', MAX_MCO * fdc + 1); - level = win_foldinfo.fi_level; + level = foldinfo.fi_level; // If the column is too narrow, we start at the lowest level that // fits and use numbers to indicated the depth. @@ -2133,8 +1939,8 @@ fill_foldcolumn( } for (i = 0; i < MIN(fdc, level); i++) { - if (win_foldinfo.fi_lnum == lnum - && first_level + i >= win_foldinfo.fi_low_level) { + if (foldinfo.fi_lnum == lnum + && first_level + i >= foldinfo.fi_low_level) { symbol = wp->w_p_fcs_chars.foldopen; } else if (first_level == 1) { symbol = wp->w_p_fcs_chars.foldsep; @@ -2165,22 +1971,23 @@ fill_foldcolumn( return MAX(char_counter + (fdc-i), (size_t)fdc); } -/* - * Display line "lnum" of window 'wp' on the screen. - * Start at row "startrow", stop when "endrow" is reached. - * wp->w_virtcol needs to be valid. - * - * 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, // not updating for changed text - bool number_only // only update the number column -) +/// 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, + Providers *providers) { int c = 0; // init for GCC long vcol = 0; // virtual column (for tabs) @@ -2281,9 +2088,11 @@ win_line ( int prev_c1 = 0; // first composing char for prev_c bool search_attr_from_match = false; // if search_attr is from :match - bool has_decorations = false; // this buffer has decorations + bool has_decor = false; // this buffer has decoration bool do_virttext = false; // draw virtual text for this line + char_u buf_fold[FOLD_TEXT_LEN + 1]; // Hold value returned by get_foldtext + /* draw_state: items that are drawn in sequence: */ #define WL_START 0 /* nothing done yet */ # define WL_CMDLINE WL_START + 1 /* cmdline window column */ @@ -2322,7 +2131,7 @@ win_line ( row = startrow; - char *luatext = NULL; + char *err_text = NULL; buf_T *buf = wp->w_buffer; @@ -2347,37 +2156,37 @@ win_line ( } } - if (decorations_active) { - if (buf->b_luahl && buf->b_luahl_line != LUA_NOREF) { - Error err = ERROR_INIT; + has_decor = decor_redraw_line(wp->w_buffer, lnum-1, + &decor_state); + + for (size_t k = 0; k < kv_size(*providers); k++) { + DecorProvider *p = kv_A(*providers, k); + if (p && p->redraw_line != LUA_NOREF) { FIXED_TEMP_ARRAY(args, 3); args.items[0] = WINDOW_OBJ(wp->handle); args.items[1] = BUFFER_OBJ(buf->handle); args.items[2] = INTEGER_OBJ(lnum-1); - lua_attr_active = true; - extra_check = true; - Object o = executor_exec_lua_cb(buf->b_luahl_line, "line", - args, true, &err); - lua_attr_active = false; - if (o.type == kObjectTypeString) { - // TODO(bfredl): this is a bit of a hack. A final API should use an - // "unified" interface where luahl can add both bufhl and virttext - luatext = o.data.string.data; - do_virttext = true; - } else if (ERROR_SET(&err)) { - ELOG("error in luahl line: %s", err.msg); - luatext = err.msg; - do_virttext = true; + if (provider_invoke(p->ns_id, "line", p->redraw_line, args, true)) { + has_decor = true; + } else { + // return 'false' or error: skip rest of this window + kv_A(*providers, k) = NULL; } - } - has_decorations = decorations_redraw_line(wp->w_buffer, lnum-1, - &decorations); - if (has_decorations) { - extra_check = true; + win_check_ns_hl(wp); } } + if (has_decor) { + extra_check = true; + } + + if (provider_first_error) { + err_text = provider_first_error; + provider_first_error = NULL; + do_virttext = true; + } + // Check for columns to display for 'colorcolumn'. color_cols = wp->w_buffer->terminal ? NULL : wp->w_p_cc_cols; if (color_cols != NULL) { @@ -2554,6 +2363,7 @@ win_line ( } // If this line has a sign with line highlighting set line_attr. + // TODO(bfredl, vigoux): this should not take priority over decoration! v = buf_getsigntype(wp->w_buffer, lnum, SIGN_LINEHL, 0, 1); if (v != 0) { line_attr = sign_get_attr((int)v, SIGN_LINEHL); @@ -2816,6 +2626,7 @@ win_line ( for (;; ) { int has_match_conc = 0; ///< match wants to conceal bool did_decrement_ptr = false; + // Skip this quickly when working on the text. if (draw_state != WL_LINE) { if (draw_state == WL_CMDLINE - 1 && n_extra == 0) { @@ -2838,7 +2649,7 @@ win_line ( // already be in use. xfree(p_extra_free); p_extra_free = xmalloc(MAX_MCO * fdc + 1); - n_extra = fill_foldcolumn(p_extra_free, wp, false, lnum); + n_extra = fill_foldcolumn(p_extra_free, wp, foldinfo, lnum); p_extra_free[n_extra] = NUL; p_extra = p_extra_free; c_extra = NUL; @@ -2854,47 +2665,12 @@ win_line ( * buffer or when using Netbeans. */ int count = win_signcol_count(wp); if (count > 0) { - int text_sign; - // Draw cells with the sign value or blank. - c_extra = ' '; - c_final = NUL; - char_attr = win_hl_attr(wp, HLF_SC); - n_extra = win_signcol_width(wp); - - if (row == startrow + filler_lines && filler_todo <= 0) { - text_sign = buf_getsigntype(wp->w_buffer, lnum, SIGN_TEXT, - sign_idx, count); - if (text_sign != 0) { - p_extra = sign_get_text(text_sign); - if (p_extra != NULL) { - int symbol_blen = (int)STRLEN(p_extra); - - c_extra = NUL; - c_final = NUL; - - // TODO(oni-link): Is sign text already extended to - // full cell width? - assert((size_t)win_signcol_width(wp) - >= mb_string2cells(p_extra)); - // symbol(s) bytes + (filling spaces) (one byte each) - n_extra = symbol_blen + - (win_signcol_width(wp) - mb_string2cells(p_extra)); - - assert(sizeof(extra) > (size_t)symbol_blen); - memset(extra, ' ', sizeof(extra)); - memcpy(extra, p_extra, symbol_blen); - - p_extra = extra; - p_extra[n_extra] = NUL; - } - char_attr = sign_get_attr(text_sign, SIGN_TEXT); - } - } - - sign_idx++; - if (sign_idx < count) { - draw_state = WL_SIGN - 1; - } + get_sign_display_info( + false, wp, lnum, row, + startrow, filler_lines, filler_todo, count, + &c_extra, &c_final, extra, sizeof(extra), + &p_extra, &n_extra, + &char_attr, &draw_state, &sign_idx); } } @@ -2903,65 +2679,78 @@ win_line ( /* 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 + && (row == startrow + filler_lines || vim_strchr(p_cpo, CPO_NUMCOL) == NULL)) { - /* Draw the line number (empty space after wrapping). */ - if (row == startrow - + filler_lines - ) { - 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 "; + // 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' + && buf_findsign_id(wp->w_buffer, lnum, (char_u *)"*") != 0) { + int count = win_signcol_count(wp); + get_sign_display_info( + true, wp, lnum, row, + startrow, filler_lines, filler_todo, count, + &c_extra, &c_final, extra, sizeof(extra), + &p_extra, &n_extra, + &char_attr, &draw_state, &sign_idx); + } else { + if (row == startrow + filler_lines) { + // Draw the line number (empty space after wrapping). */ + long num; + char *fmt = "%*ld "; + + if (wp->w_p_nu && !wp->w_p_rnu) { + // 'number' + 'norelativenumber' + num = (long)lnum; + } else { + // 'relativenumber', don't use negative numbers + num = labs((long)get_cursor_rel_lnum(wp, lnum)); + if (num == 0 && wp->w_p_nu && wp->w_p_rnu) { + // 'number' + 'relativenumber' + num = lnum; + fmt = "%-*ld "; + } } - } - sprintf((char *)extra, fmt, - number_width(wp), num); - 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 = skiptowhite(extra) - 1; - for (char_u *p1 = extra; p1 < p2; p1++, p2--) { - const int t = *p1; - *p1 = *p2; - *p2 = t; + snprintf((char *)extra, sizeof(extra), + fmt, number_width(wp), num); + 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 = skiptowhite(extra) - 1; + for (char_u *p1 = extra; p1 < p2; p1++, p2--) { + const int t = *p1; + *p1 = *p2; + *p2 = t; + } } + p_extra = extra; + c_extra = NUL; + c_final = NUL; + } else { + c_extra = ' '; + c_final = NUL; + } + n_extra = number_width(wp) + 1; + char_attr = win_hl_attr(wp, HLF_N); + + int num_sign = buf_getsigntype( + wp->w_buffer, lnum, SIGN_NUMHL, 0, 1); + if (num_sign != 0) { + // :sign defined with "numhl" highlight. + char_attr = sign_get_attr(num_sign, SIGN_NUMHL); + } else if ((wp->w_p_cul || wp->w_p_rnu) + && lnum == wp->w_cursor.lnum) { + // When 'cursorline' is set highlight the line number of + // the current line differently. + // TODO(vim): Can we use CursorLine instead of CursorLineNr + // when CursorLineNr isn't set? + char_attr = win_hl_attr(wp, HLF_CLN); } - p_extra = extra; - c_extra = NUL; - c_final = NUL; - } else { - c_extra = ' '; - c_final = NUL; - } - n_extra = number_width(wp) + 1; - char_attr = win_hl_attr(wp, HLF_N); - - int num_sign = buf_getsigntype(wp->w_buffer, lnum, SIGN_NUMHL, - 0, 1); - if (num_sign != 0) { - // :sign defined with "numhl" highlight. - char_attr = sign_get_attr(num_sign, SIGN_NUMHL); - } else if ((wp->w_p_cul || wp->w_p_rnu) - && lnum == wp->w_cursor.lnum) { - // When 'cursorline' is set highlight the line number of - // the current line differently. - // TODO(vim): Can we use CursorLine instead of CursorLineNr - // when CursorLineNr isn't set? - char_attr = win_hl_attr(wp, HLF_CLN); } } } @@ -3068,10 +2857,12 @@ win_line ( } // 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 - && filler_todo <= 0) - || (number_only && draw_state > WL_NR)) { + 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) { grid_put_linebuf(grid, row, 0, col, -grid->Columns, wp->w_p_rl, wp, wp->w_hl_attr_normal, false); // Pretend we have finished updating the window. Except when @@ -3084,6 +2875,51 @@ win_line ( break; } + if (draw_state == WL_LINE + && foldinfo.fi_level != 0 + && foldinfo.fi_lines > 0 + && vcol == 0 + && 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 = 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 + && foldinfo.fi_level != 0 + && foldinfo.fi_lines > 0 + && col < grid->Columns + && 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->Columns - col); + } + + if (draw_state == WL_LINE + && foldinfo.fi_level != 0 + && foldinfo.fi_lines > 0 + && col >= grid->Columns + && 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 @@ -3312,6 +3148,10 @@ win_line ( 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; @@ -3476,6 +3316,7 @@ win_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 = (long)(ptr - line); if (has_spell && v >= word_end && v > cur_checked_col) { spell_attr = 0; if (!attr_pri) { @@ -3547,9 +3388,9 @@ win_line ( char_attr = hl_combine_attr(spell_attr, char_attr); } - if (has_decorations && v > 0) { - int extmark_attr = decorations_redraw_col(wp->w_buffer, (colnr_T)v-1, - &decorations); + if (has_decor && v > 0) { + int extmark_attr = decor_redraw_col(wp->w_buffer, (colnr_T)v-1, + &decor_state); if (extmark_attr != 0) { if (!attr_pri) { char_attr = hl_combine_attr(char_attr, extmark_attr); @@ -3782,7 +3623,7 @@ win_line ( mb_utf8 = false; // don't draw as UTF-8 } } else if (c != NUL) { - p_extra = transchar(c); + p_extra = transchar_buf(wp->w_buffer, c); if (n_extra == 0) { n_extra = byte2cells(c) - 1; } @@ -3879,7 +3720,7 @@ win_line ( // 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. */ @@ -3936,7 +3777,7 @@ win_line ( } // At end of the text line or just after the last character. - if (c == NUL) { + if (c == NUL && eol_hl_off == 0) { long prevcol = (long)(ptr - line) - 1; // we're not really at that column when skipping some text @@ -4046,11 +3887,13 @@ win_line ( draw_color_col = advance_color_col(VCOL_HLC, &color_cols); VirtText virt_text = KV_INITIAL_VALUE; - if (luatext) { - kv_push(virt_text, ((VirtTextChunk){ .text = luatext, .hl_id = 0 })); + if (err_text) { + int hl_err = syn_check_group((char_u *)S_LEN("ErrorMsg")); + kv_push(virt_text, ((VirtTextChunk){ .text = err_text, + .hl_id = hl_err })); do_virttext = true; - } else if (has_decorations) { - VirtText *vp = decorations_redraw_virt_text(wp->w_buffer, &decorations); + } else if (has_decor) { + VirtText *vp = decor_redraw_virt_text(wp->w_buffer, &decor_state); if (vp) { virt_text = *vp; do_virttext = true; @@ -4189,11 +4032,10 @@ win_line ( if (wp == curwin && lnum == curwin->w_cursor.lnum) { curwin->w_cline_row = startrow; curwin->w_cline_height = row - startrow; - curwin->w_cline_folded = false; + curwin->w_cline_folded = foldinfo.fi_lines > 0; curwin->w_valid |= (VALID_CHEIGHT|VALID_CROW); conceal_cursor_used = conceal_cursor_line(curwin); } - break; } @@ -4382,6 +4224,7 @@ win_line ( * so far. If there is no more to display it is caught above. */ if ((wp->w_p_rl ? (col < 0) : (col >= grid->Columns)) + && foldinfo.fi_lines == 0 && (*ptr != NUL || filler_todo > 0 || (wp->w_p_list && wp->w_p_lcs_chars.eol != NUL @@ -4467,7 +4310,7 @@ win_line ( } xfree(p_extra_free); - xfree(luatext); + xfree(err_text); return row; } @@ -4494,6 +4337,88 @@ void screen_adjust_grid(ScreenGrid **grid, int *row_off, int *col_off) } } +// 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. +static void get_sign_display_info( + bool nrcol, + win_T *wp, + linenr_T lnum, + int row, + int startrow, + int filler_lines, + int filler_todo, + int count, + int *c_extrap, + int *c_finalp, + char_u *extra, + size_t extra_size, + char_u **pp_extra, + int *n_extrap, + int *char_attrp, + int *draw_statep, + int *sign_idxp +) +{ + int text_sign; + + // Draw cells with the sign value or blank. + *c_extrap = ' '; + *c_finalp = NUL; + if (nrcol) { + *n_extrap = number_width(wp) + 1; + } else { + *char_attrp = win_hl_attr(wp, HLF_SC); + *n_extrap = win_signcol_width(wp); + } + + if (row == startrow + filler_lines && filler_todo <= 0) { + text_sign = buf_getsigntype(wp->w_buffer, lnum, SIGN_TEXT, + *sign_idxp, count); + if (text_sign != 0) { + *pp_extra = sign_get_text(text_sign); + 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(*pp_extra)); + // symbol(s) bytes + (filling spaces) (one byte each) + *n_extrap = symbol_blen + + (win_signcol_width(wp) - mb_string2cells(*pp_extra)); + + assert(extra_size > (size_t)symbol_blen); + memset(extra, ' ', extra_size); + memcpy(extra, *pp_extra, symbol_blen); + + *pp_extra = extra; + (*pp_extra)[*n_extrap] = NUL; + } + } + *char_attrp = sign_get_attr(text_sign, SIGN_TEXT); + } + } + + (*sign_idxp)++; + if (*sign_idxp < count) { + *draw_statep = WL_SIGN - 1; + } +} + /* * Check whether the given character needs redrawing: @@ -4713,8 +4638,8 @@ void status_redraw_all(void) FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { if (wp->w_status_height) { - wp->w_redr_status = TRUE; - redraw_later(VALID); + wp->w_redr_status = true; + redraw_later(wp, VALID); } } } @@ -4731,7 +4656,7 @@ void status_redraw_buf(buf_T *buf) FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { if (wp->w_status_height != 0 && wp->w_buffer == buf) { wp->w_redr_status = true; - redraw_later(VALID); + redraw_later(wp, VALID); } } } @@ -5255,8 +5180,8 @@ win_redr_custom ( char_u buf[MAXPATHL]; char_u *stl; char_u *p; - struct stl_hlrec hltab[STL_MAX_ITEM]; - StlClickRecord tabtab[STL_MAX_ITEM]; + stl_hlrec_t *hltab; + StlClickRecord *tabtab; int use_sandbox = false; win_T *ewp; int p_crb_save; @@ -5334,9 +5259,9 @@ win_redr_custom ( /* 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), - stl, use_sandbox, - fillchar, maxwidth, hltab, tabtab); + width = + build_stl_str_hl(ewp, buf, sizeof(buf), stl, use_sandbox, + fillchar, maxwidth, &hltab, &tabtab); xfree(stl); ewp->w_p_crb = p_crb_save; @@ -5854,6 +5779,12 @@ next_search_hl ( long nmatched = 0; int save_called_emsg = called_emsg; + // for :{range}s/pat only highlight inside the range + if (lnum < search_first_line || lnum > search_last_line) { + shl->lnum = 0; + return; + } + if (shl->lnum != 0) { // Check for three situations: // 1. If the "lnum" is below a previous match, start a new search. @@ -6146,7 +6077,7 @@ void win_grid_alloc(win_T *wp) || grid->Rows != rows || grid->Columns != cols) { if (want_allocation) { - grid_alloc(grid, rows, cols, wp->w_grid.valid, wp->w_grid.valid); + grid_alloc(grid, rows, cols, wp->w_grid.valid, false); grid->valid = true; } else { // Single grid mode, all rendering will be redirected to default_grid. @@ -7344,9 +7275,17 @@ int number_width(win_T *wp) ++n; } while (lnum > 0); - /* 'numberwidth' gives the minimal width plus one */ - if (n < wp->w_p_nuw - 1) + // 'numberwidth' gives the minimal width plus one + if (n < wp->w_p_nuw - 1) { n = wp->w_p_nuw - 1; + } + + // If 'signcolumn' is set to 'number' and there is a sign to display, then + // the minimal width for the number column is 2. + if (n < 2 && (wp->w_buffer->b_signlist != NULL) + && (*wp->w_p_scl == 'n' && *(wp->w_p_scl + 1) == 'u')) { + n = 2; + } wp->w_nrwidth_width = n; return n; @@ -7508,3 +7447,5 @@ win_T *get_win_by_grid_handle(handle_T handle) } return NULL; } + + diff --git a/src/nvim/search.c b/src/nvim/search.c index b105d99d7c..b25333c9fa 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -96,11 +96,8 @@ static int lastc_bytelen = 1; // >1 for multi-byte char // copy of spats[], for keeping the search patterns while executing autocmds static struct spat saved_spats[2]; -// copy of spats[RE_SEARCH], for keeping the search patterns while incremental -// searching -static struct spat saved_last_search_spat; -static int saved_last_idx = 0; -static bool saved_no_hlsearch = false; +static int saved_spats_last_idx = 0; +static bool saved_spats_no_hlsearch = false; static char_u *mr_pattern = NULL; // pattern used by search_regcomp() static int mr_pattern_alloced = false; // mr_pattern was allocated @@ -267,8 +264,8 @@ void save_search_patterns(void) saved_spats[1] = spats[1]; if (spats[1].pat != NULL) saved_spats[1].pat = vim_strsave(spats[1].pat); - saved_last_idx = last_idx; - saved_no_hlsearch = no_hlsearch; + saved_spats_last_idx = last_idx; + saved_spats_no_hlsearch = no_hlsearch; } } @@ -280,8 +277,8 @@ void restore_search_patterns(void) set_vv_searchforward(); free_spat(&spats[1]); spats[1] = saved_spats[1]; - last_idx = saved_last_idx; - set_no_hlsearch(saved_no_hlsearch); + last_idx = saved_spats_last_idx; + set_no_hlsearch(saved_spats_no_hlsearch); } } @@ -308,6 +305,13 @@ void free_search_patterns(void) #endif +// copy of spats[RE_SEARCH], for keeping the search patterns while incremental +// searching +static struct spat saved_last_search_spat; +static int did_save_last_search_spat = 0; +static int saved_last_idx = 0; +static bool saved_no_hlsearch = false; + /// Save and restore the search pattern for incremental highlight search /// feature. /// @@ -316,6 +320,11 @@ void free_search_patterns(void) /// cancelling incremental searching even if it's called inside user functions. void save_last_search_pattern(void) { + if (++did_save_last_search_spat != 1) { + // nested call, nothing to do + return; + } + saved_last_search_spat = spats[RE_SEARCH]; if (spats[RE_SEARCH].pat != NULL) { saved_last_search_spat.pat = vim_strsave(spats[RE_SEARCH].pat); @@ -326,8 +335,19 @@ void save_last_search_pattern(void) void restore_last_search_pattern(void) { + if (--did_save_last_search_spat > 0) { + // nested call, nothing to do + return; + } + if (did_save_last_search_spat != 0) { + iemsg("restore_last_search_pattern() called more often than" + " save_last_search_pattern()"); + return; + } + xfree(spats[RE_SEARCH].pat); spats[RE_SEARCH] = saved_last_search_spat; + saved_last_search_spat.pat = NULL; set_vv_searchforward(); last_idx = saved_last_idx; set_no_hlsearch(saved_no_hlsearch); @@ -474,7 +494,7 @@ void set_last_search_pat(const char_u *s, int idx, int magic, int setlast) saved_spats[idx].pat = NULL; else saved_spats[idx].pat = vim_strsave(spats[idx].pat); - saved_last_idx = last_idx; + saved_spats_last_idx = last_idx; } /* If 'hlsearch' set and search pat changed: need redraw. */ if (p_hls && idx == last_idx && !no_hlsearch) @@ -631,6 +651,10 @@ int searchit( colnr_T col = at_first_line && (options & SEARCH_COL) ? pos->col : 0; nmatched = vim_regexec_multi(®match, win, buf, lnum, col, tm, timed_out); + // vim_regexec_multi() may clear "regprog" + if (regmatch.regprog == NULL) { + break; + } // Abort searching on an error (e.g., out of stack). if (called_emsg || (timed_out != NULL && *timed_out)) { break; @@ -702,6 +726,10 @@ int searchit( match_ok = false; break; } + // vim_regexec_multi() may clear "regprog" + if (regmatch.regprog == NULL) { + break; + } matchpos = regmatch.startpos[0]; endpos = regmatch.endpos[0]; submatch = first_submatch(®match); @@ -791,10 +819,13 @@ int searchit( } break; } - - /* Need to get the line pointer again, a - * multi-line search may have made it invalid. */ - ptr = ml_get_buf(buf, lnum + matchpos.lnum, FALSE); + // vim_regexec_multi() may clear "regprog" + if (regmatch.regprog == NULL) { + break; + } + // Need to get the line pointer again, a + // multi-line search may have made it invalid. + ptr = ml_get_buf(buf, lnum + matchpos.lnum, false); } /* @@ -871,6 +902,11 @@ int searchit( } at_first_line = FALSE; + // vim_regexec_multi() may clear "regprog" + if (regmatch.regprog == NULL) { + break; + } + // Stop the search if wrapscan isn't set, "stop_lnum" is // specified, after an interrupt, after a match and after looping // twice. @@ -1135,8 +1171,8 @@ int do_search( pat = p; /* put pat after search command */ } - if ((options & SEARCH_ECHO) && messaging() - && !cmd_silent && msg_silent == 0) { + if ((options & SEARCH_ECHO) && messaging() && !msg_silent + && (!cmd_silent || !shortmess(SHM_SEARCHCOUNT))) { char_u *trunc; char_u off_buf[40]; size_t off_len = 0; @@ -1145,7 +1181,8 @@ int do_search( msg_start(); // Get the offset, so we know how long it is. - if (spats[0].off.line || spats[0].off.end || spats[0].off.off) { + if (!cmd_silent + && (spats[0].off.line || spats[0].off.end || spats[0].off.off)) { p = off_buf; // -V507 *p++ = dirc; if (spats[0].off.end) { @@ -1165,19 +1202,19 @@ int do_search( } if (*searchstr == NUL) { - p = spats[last_idx].pat; + p = spats[0].pat; } else { p = searchstr; } - if (!shortmess(SHM_SEARCHCOUNT)) { + if (!shortmess(SHM_SEARCHCOUNT) || cmd_silent) { // Reserve enough space for the search pattern + offset + // search stat. Use all the space available, so that the // search state is right aligned. If there is not enough space // msg_strtrunc() will shorten in the middle. if (ui_has(kUIMessages)) { len = 0; // adjusted below - } else if (msg_scrolled != 0) { + } else if (msg_scrolled != 0 && !cmd_silent) { // Use all the columns. len = (Rows - msg_row) * Columns - 1; } else { @@ -1194,11 +1231,13 @@ int do_search( xfree(msgbuf); msgbuf = xmalloc(len); - { - memset(msgbuf, ' ', len); - msgbuf[0] = dirc; - msgbuf[len - 1] = NUL; + memset(msgbuf, ' ', len); + msgbuf[len - 1] = NUL; + // do not fill the msgbuf buffer, if cmd_silent is set, leave it + // empty for the search_stat feature. + if (!cmd_silent) { + msgbuf[0] = dirc; if (utf_iscomposing(utf_ptr2char(p))) { // Use a space to draw the composing char on. msgbuf[1] = ' '; @@ -1342,12 +1381,15 @@ int do_search( // Show [1/15] if 'S' is not in 'shortmess'. if ((options & SEARCH_ECHO) && messaging() - && !(cmd_silent + msg_silent) + && !msg_silent && c != FAIL && !shortmess(SHM_SEARCHCOUNT) && msgbuf != NULL) { search_stat(dirc, &pos, show_top_bot_msg, msgbuf, - (count != 1 || has_offset)); + (count != 1 + || has_offset + || (!(fdo_flags & FDO_SEARCH) + && hasFolding(curwin->w_cursor.lnum, NULL, NULL)))); } // The search command can be followed by a ';' to do another search. @@ -1612,8 +1654,9 @@ static bool find_rawstring_end(char_u *linep, pos_T *startpos, pos_T *endpos) if (lnum == endpos->lnum && (colnr_T)(p - line) >= endpos->col) { break; } - if (*p == ')' && p[delim_len + 1] == '"' - && STRNCMP(delim_copy, p + 1, delim_len) == 0) { + if (*p == ')' + && STRNCMP(delim_copy, p + 1, delim_len) == 0 + && p[delim_len + 1] == '"') { found = true; break; } @@ -3393,7 +3436,6 @@ current_tagblock( pos_T start_pos; pos_T end_pos; pos_T old_start, old_end; - char_u *spat, *epat; char_u *p; char_u *cp; int len; @@ -3447,9 +3489,9 @@ again: */ for (long n = 0; n < count; n++) { if (do_searchpair( - (char_u *)"<[^ \t>/!]\\+\\%(\\_s\\_[^>]\\{-}[^/]>\\|$\\|\\_s\\=>\\)", - (char_u *)"", - (char_u *)"</[^>]*>", BACKWARD, NULL, 0, + "<[^ \t>/!]\\+\\%(\\_s\\_[^>]\\{-}[^/]>\\|$\\|\\_s\\=>\\)", + "", + "</[^>]*>", BACKWARD, NULL, 0, NULL, (linenr_T)0, 0L) <= 0) { curwin->w_cursor = old_pos; goto theend; @@ -3471,12 +3513,15 @@ again: curwin->w_cursor = old_pos; goto theend; } - spat = xmalloc(len + 31); - epat = xmalloc(len + 9); - sprintf((char *)spat, "<%.*s\\>\\%%(\\s\\_[^>]\\{-}[^/]>\\|>\\)\\c", len, p); - sprintf((char *)epat, "</%.*s>\\c", len, p); - - const int r = do_searchpair(spat, (char_u *)"", epat, FORWARD, NULL, + const size_t spat_len = len + 39; + char *const spat = xmalloc(spat_len); + const size_t epat_len = len + 9; + char *const epat = xmalloc(epat_len); + snprintf(spat, spat_len, + "<%.*s\\>\\%%(\\_s\\_[^>]\\{-}\\_[^/]>\\|\\_s\\?>\\)\\c", len, p); + snprintf(epat, epat_len, "</%.*s>\\c", len, p); + + const int r = do_searchpair(spat, "", epat, FORWARD, NULL, 0, NULL, (linenr_T)0, 0L); xfree(spat); @@ -4054,7 +4099,7 @@ abort_search: int current_search( long count, - int forward // true for forward, false for backward + bool forward // true for forward, false for backward ) { bool old_p_ws = p_ws; @@ -4069,6 +4114,11 @@ current_search( pos_T pos; // position after the pattern int result; // result of various function calls + // When searching forward and the cursor is at the start of the Visual + // area, skip the first search backward, otherwise it doesn't move. + const bool skip_first_backward = forward && VIsual_active + && lt(curwin->w_cursor, VIsual); + orig_pos = pos = curwin->w_cursor; if (VIsual_active) { // Searching further will extend the match. @@ -4086,13 +4136,20 @@ current_search( return FAIL; // pattern not found } - /* - * The trick is to first search backwards and then search forward again, - * so that a match at the current cursor position will be correctly - * captured. - */ + // The trick is to first search backwards and then search forward again, + // so that a match at the current cursor position will be correctly + // captured. When "forward" is false do it the other way around. for (int i = 0; i < 2; i++) { - int dir = forward ? i : !i; + int dir; + if (forward) { + if (i == 0 && skip_first_backward) { + continue; + } + dir = i; + } else { + dir = !i; + } + int flags = 0; if (!dir && !zero_width) { @@ -4139,11 +4196,17 @@ current_search( VIsual = start_pos; } - // put cursor on last character of match + // put the cursor after the match curwin->w_cursor = end_pos; if (lt(VIsual, end_pos) && forward) { - dec_cursor(); - } else if (VIsual_active && lt(curwin->w_cursor, VIsual)) { + if (skip_first_backward) { + // put the cursor on the start of the match + curwin->w_cursor = pos; + } else { + // put the cursor on last character of match + dec_cursor(); + } + } else if (VIsual_active && lt(curwin->w_cursor, VIsual) && forward) { curwin->w_cursor = pos; // put the cursor on the start of the match } VIsual_active = true; @@ -4216,7 +4279,8 @@ is_zero_width(char_u *pattern, int move, pos_T *cur, Direction direction) if (nmatched != 0) { break; } - } while (direction == FORWARD + } while (regmatch.regprog != NULL + && direction == FORWARD ? regmatch.startpos[0].col < pos.col : regmatch.startpos[0].col > pos.col); @@ -4335,7 +4399,9 @@ static void search_stat(int dirc, pos_T *pos, len = STRLEN(t); if (show_top_bot_msg && len + 2 < SEARCH_STAT_BUF_LEN) { - STRCPY(t + len, " W"); + memmove(t + 2, t, len); + t[0] = 'W'; + t[1] = ' '; len += 2; } @@ -4857,7 +4923,7 @@ search_line: && curwin != curwin_save && win_valid(curwin_save)) { /* Return cursor to where we were */ validate_cursor(); - redraw_later(VALID); + redraw_later(curwin, VALID); win_enter(curwin_save, true); } break; diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 95257fe945..2444910bb3 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -2207,8 +2207,12 @@ static inline ShaDaWriteResult shada_read_when_writing( shada_free_shada_entry(&entry); break; } - hms_insert(&wms->hms[entry.data.history_item.histtype], entry, true, - true); + if (wms->hms[entry.data.history_item.histtype].hmll.size != 0) { + hms_insert(&wms->hms[entry.data.history_item.histtype], entry, true, + true); + } else { + shada_free_shada_entry(&entry); + } break; } case kSDItemRegister: { @@ -4144,7 +4148,7 @@ static inline size_t shada_init_jumps( } const char *const fname = (char *) (fm.fmark.fnum == 0 ? (fm.fname == NULL ? NULL : fm.fname) - : buf->b_ffname); + : buf ? buf->b_ffname : NULL); if (fname == NULL) { continue; } diff --git a/src/nvim/sign.c b/src/nvim/sign.c index ab5d04d39b..ffe51287c5 100644 --- a/src/nvim/sign.c +++ b/src/nvim/sign.c @@ -881,6 +881,17 @@ int sign_undefine_by_name(const char_u *name) return OK; } +static void may_force_numberwidth_recompute(buf_T *buf, int unplace) +{ + FOR_ALL_TAB_WINDOWS(tp, wp) + if (wp->w_buffer == buf + && (wp->w_p_nu || wp->w_p_rnu) + && (unplace || wp->w_nrwidth_width < 2) + && (*wp->w_p_scl == 'n' && *(wp->w_p_scl + 1) == 'u')) { + wp->w_nrwidth_line_count = 0; + } +} + /// List the signs matching 'name' static void sign_list_by_name(char_u *name) { @@ -935,6 +946,10 @@ int sign_place( } if (lnum > 0) { redraw_buf_line_later(buf, lnum); + + // When displaying signs in the 'number' column, if the width of the + // number column is less than 2, then force recomputing the width. + may_force_numberwidth_recompute(buf, false); } else { EMSG2(_("E885: Not possible to change sign %s"), sign_name); return FAIL; @@ -964,6 +979,13 @@ int sign_unplace(int sign_id, char_u *sign_group, buf_T *buf, linenr_T atlnum) redraw_buf_line_later(buf, lnum); } + // When all the signs in a buffer are removed, force recomputing the + // number column width (if enabled) in all the windows displaying the + // buffer if 'signcolumn' is set to 'number' in that window. + if (buf->b_signlist == NULL) { + may_force_numberwidth_recompute(buf, true); + } + return OK; } diff --git a/src/nvim/spell.c b/src/nvim/spell.c index 95948dac78..636c71657d 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -362,6 +362,8 @@ size_t spell_check( size_t wrongcaplen = 0; int lpi; bool count_word = docount; + bool use_camel_case = *wp->w_s->b_p_spo != NUL; + bool camel_case = false; // A word never starts at a space or a control character. Return quickly // then, skipping over the character. @@ -394,9 +396,23 @@ size_t spell_check( mi.mi_word = ptr; mi.mi_fend = ptr; if (spell_iswordp(mi.mi_fend, wp)) { + bool this_upper = false; // init for gcc + + if (use_camel_case) { + c = PTR2CHAR(mi.mi_fend); + this_upper = SPELL_ISUPPER(c); + } + do { MB_PTR_ADV(mi.mi_fend); - } while (*mi.mi_fend != NUL && spell_iswordp(mi.mi_fend, wp)); + if (use_camel_case) { + const bool prev_upper = this_upper; + c = PTR2CHAR(mi.mi_fend); + this_upper = SPELL_ISUPPER(c); + camel_case = !prev_upper && this_upper; + } + } while (*mi.mi_fend != NUL && spell_iswordp(mi.mi_fend, wp) + && !camel_case); if (capcol != NULL && *capcol == 0 && wp->w_s->b_cap_prog != NULL) { // Check word starting with capital letter. @@ -428,6 +444,11 @@ size_t spell_check( (void)spell_casefold(ptr, (int)(mi.mi_fend - ptr), mi.mi_fword, MAXWLEN + 1); mi.mi_fwordlen = (int)STRLEN(mi.mi_fword); + if (camel_case) { + // introduce a fake word end space into the folded word. + mi.mi_fword[mi.mi_fwordlen - 1] = ' '; + } + // The word is bad unless we recognize it. mi.mi_result = SP_BAD; mi.mi_result2 = SP_BAD; @@ -2237,7 +2258,7 @@ char_u *did_set_spelllang(win_T *wp) theend: xfree(spl_copy); recursive = false; - redraw_win_later(wp, NOT_VALID); + redraw_later(wp, NOT_VALID); return ret_msg; } @@ -4405,8 +4426,6 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so } break; - FALLTHROUGH; - case STATE_INS: // Insert one byte. Repeat this for each possible byte at this // node. @@ -5663,6 +5682,9 @@ check_suggestions ( 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". @@ -5765,14 +5787,14 @@ cleanup_suggestions ( ) FUNC_ATTR_NONNULL_ALL { - suggest_T *stp = &SUG(*gap, 0); - 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); } @@ -6855,7 +6877,7 @@ void ex_spelldump(exarg_T *eap) if (curbuf->b_ml.ml_line_count > 1) { ml_delete(curbuf->b_ml.ml_line_count, false); } - redraw_later(NOT_VALID); + redraw_later(curwin, NOT_VALID); } // Go through all possible words and: diff --git a/src/nvim/spell_defs.h b/src/nvim/spell_defs.h index 034c580b3e..05667f060e 100644 --- a/src/nvim/spell_defs.h +++ b/src/nvim/spell_defs.h @@ -119,6 +119,7 @@ struct slang_S { bool sl_add; // true if it's a .add file. char_u *sl_fbyts; // case-folded word bytes + long sl_fbyts_len; // length of sl_fbyts idx_T *sl_fidxs; // case-folded word indexes char_u *sl_kbyts; // keep-case word bytes idx_T *sl_kidxs; // keep-case word indexes diff --git a/src/nvim/spellfile.c b/src/nvim/spellfile.c index 6b9348e55d..b415a4635b 100644 --- a/src/nvim/spellfile.c +++ b/src/nvim/spellfile.c @@ -628,6 +628,7 @@ spell_load_file ( case SP_OTHERERROR: { emsgf(_("E5042: Failed to read spell file %s: %s"), fname, strerror(ferror(fd))); + goto endFAIL; } case 0: { break; @@ -763,20 +764,24 @@ truncerr: } // <LWORDTREE> - res = spell_read_tree(fd, &lp->sl_fbyts, &lp->sl_fidxs, false, 0); - if (res != 0) + res = spell_read_tree(fd, &lp->sl_fbyts, &lp->sl_fbyts_len, + &lp->sl_fidxs, false, 0); + if (res != 0) { goto someerror; + } // <KWORDTREE> - res = spell_read_tree(fd, &lp->sl_kbyts, &lp->sl_kidxs, false, 0); - if (res != 0) + res = spell_read_tree(fd, &lp->sl_kbyts, NULL, &lp->sl_kidxs, false, 0); + if (res != 0) { goto someerror; + } // <PREFIXTREE> - res = spell_read_tree(fd, &lp->sl_pbyts, &lp->sl_pidxs, true, - lp->sl_prefixcnt); - if (res != 0) + res = spell_read_tree(fd, &lp->sl_pbyts, NULL, &lp->sl_pidxs, true, + lp->sl_prefixcnt); + if (res != 0) { goto someerror; + } // For a new file link it in the list of spell files. if (old_lp == NULL && lang != NULL) { @@ -919,8 +924,8 @@ void suggest_load_files(void) // <SUGWORDTREE>: <wordtree> // Read the trie with the soundfolded words. - if (spell_read_tree(fd, &slang->sl_sbyts, &slang->sl_sidxs, - false, 0) != 0) { + if (spell_read_tree(fd, &slang->sl_sbyts, NULL, &slang->sl_sidxs, + false, 0) != 0) { someerror: EMSG2(_("E782: error while reading .sug file: %s"), slang->sl_fname); @@ -983,15 +988,17 @@ nextone: static char_u *read_cnt_string(FILE *fd, int cnt_bytes, int *cntp) { int cnt = 0; - int i; char_u *str; // read the length bytes, MSB first - for (i = 0; i < cnt_bytes; ++i) - cnt = (cnt << 8) + getc(fd); - if (cnt < 0) { - *cntp = SP_TRUNCERROR; - return NULL; + for (int i = 0; i < cnt_bytes; i++) { + const int c = getc(fd); + + if (c == EOF) { + *cntp = SP_TRUNCERROR; + return NULL; + } + cnt = (cnt << 8) + (unsigned)c; } *cntp = cnt; if (cnt == 0) @@ -1627,10 +1634,12 @@ static int spell_read_tree ( FILE *fd, char_u **bytsp, + long *bytsp_len, idx_T **idxsp, bool prefixtree, // true for the prefix tree int prefixcnt // when "prefixtree" is true: prefix count ) + FUNC_ATTR_NONNULL_ARG(1, 2, 4) { int idx; char_u *bp; @@ -1650,6 +1659,9 @@ spell_read_tree ( // Allocate the byte array. bp = xmalloc(len); *bytsp = bp; + if (bytsp_len != NULL) { + *bytsp_len = len; + } // Allocate the index array. ip = xcalloc(len, sizeof(*ip)); @@ -3037,9 +3049,9 @@ static int spell_read_dic(spellinfo_T *spin, char_u *fname, afffile_T *affile) spin->si_msg_count = 999999; // Read and ignore the first line: word count. - (void)vim_fgets(line, MAXLINELEN, fd); - if (!ascii_isdigit(*skipwhite(line))) + if (vim_fgets(line, MAXLINELEN, fd) || !ascii_isdigit(*skipwhite(line))) { EMSG2(_("E760: No word count in %s"), fname); + } // Read all the lines in the file one by one. // The words are converted to 'encoding' here, before being added to @@ -4847,10 +4859,10 @@ static int sug_filltree(spellinfo_T *spin, slang_T *slang) spin->si_blocks_cnt = 0; // Skip over any other NUL bytes (same word with different - // flags). - while (byts[n + 1] == 0) { - ++n; - ++curi[depth]; + // flags). But don't go over the end. + while (n + 1 < slang->sl_fbyts_len && byts[n + 1] == 0) { + n++; + curi[depth]++; } } else { // Normal char, go one level deeper. diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index 4aa7c21ce4..5ce126a593 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -73,9 +73,9 @@ struct hl_group { RgbValue sg_rgb_fg; ///< RGB foreground color RgbValue sg_rgb_bg; ///< RGB background color RgbValue sg_rgb_sp; ///< RGB special color - uint8_t *sg_rgb_fg_name; ///< RGB foreground color name - uint8_t *sg_rgb_bg_name; ///< RGB background color name - uint8_t *sg_rgb_sp_name; ///< RGB special color name + char *sg_rgb_fg_name; ///< RGB foreground color name + char *sg_rgb_bg_name; ///< RGB background color name + char *sg_rgb_sp_name; ///< RGB special color name int sg_blend; ///< blend level (0-100 inclusive), -1 if unset }; @@ -3147,7 +3147,7 @@ static void syn_cmd_spell(exarg_T *eap, int syncing) } // assume spell checking changed, force a redraw - redraw_win_later(curwin, NOT_VALID); + redraw_later(curwin, NOT_VALID); } /// Handle ":syntax iskeyword" command. @@ -3187,7 +3187,7 @@ static void syn_cmd_iskeyword(exarg_T *eap, int syncing) curbuf->b_p_isk = save_isk; } } - redraw_win_later(curwin, NOT_VALID); + redraw_later(curwin, NOT_VALID); } /* @@ -4296,8 +4296,9 @@ static void syn_cmd_include(exarg_T *eap, int syncing) current_syn_inc_tag = ++running_syn_inc_tag; prev_toplvl_grp = curwin->w_s->b_syn_topgrp; curwin->w_s->b_syn_topgrp = sgl_id; - if (source ? do_source(eap->arg, false, DOSO_NONE) == FAIL - : source_runtime(eap->arg, DIP_ALL) == FAIL) { + if (source + ? do_source(eap->arg, false, DOSO_NONE) == FAIL + : source_in_path(p_rtp, eap->arg, DIP_ALL) == FAIL) { EMSG2(_(e_notopen), eap->arg); } curwin->w_s->b_syn_topgrp = prev_toplvl_grp; @@ -5588,9 +5589,11 @@ void ex_ownsyntax(exarg_T *eap) hash_init(&curwin->w_s->b_keywtab_ic); // TODO: Keep the spell checking as it was. NOLINT(readability/todo) curwin->w_p_spell = false; // No spell checking + // make sure option values are "empty_option" instead of NULL clear_string_option(&curwin->w_s->b_p_spc); clear_string_option(&curwin->w_s->b_p_spf); clear_string_option(&curwin->w_s->b_p_spl); + clear_string_option(&curwin->w_s->b_p_spo); clear_string_option(&curwin->w_s->b_syn_isk); } @@ -6897,7 +6900,7 @@ void do_highlight(const char *line, const bool forceit, const bool init) } } } else if (strcmp(key, "GUIFG") == 0) { - char_u **const namep = &HL_TABLE()[idx].sg_rgb_fg_name; + char **namep = &HL_TABLE()[idx].sg_rgb_fg_name; if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI)) { if (!init) { @@ -6907,8 +6910,8 @@ void do_highlight(const char *line, const bool forceit, const bool init) if (*namep == NULL || STRCMP(*namep, arg) != 0) { xfree(*namep); if (strcmp(arg, "NONE") != 0) { - *namep = (char_u *)xstrdup(arg); - HL_TABLE()[idx].sg_rgb_fg = name_to_color((char_u *)arg); + *namep = xstrdup(arg); + HL_TABLE()[idx].sg_rgb_fg = name_to_color(arg); } else { *namep = NULL; HL_TABLE()[idx].sg_rgb_fg = -1; @@ -6921,7 +6924,7 @@ void do_highlight(const char *line, const bool forceit, const bool init) normal_fg = HL_TABLE()[idx].sg_rgb_fg; } } else if (STRCMP(key, "GUIBG") == 0) { - char_u **const namep = &HL_TABLE()[idx].sg_rgb_bg_name; + char **const namep = &HL_TABLE()[idx].sg_rgb_bg_name; if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI)) { if (!init) { @@ -6931,8 +6934,8 @@ void do_highlight(const char *line, const bool forceit, const bool init) if (*namep == NULL || STRCMP(*namep, arg) != 0) { xfree(*namep); if (STRCMP(arg, "NONE") != 0) { - *namep = (char_u *)xstrdup(arg); - HL_TABLE()[idx].sg_rgb_bg = name_to_color((char_u *)arg); + *namep = xstrdup(arg); + HL_TABLE()[idx].sg_rgb_bg = name_to_color(arg); } else { *namep = NULL; HL_TABLE()[idx].sg_rgb_bg = -1; @@ -6945,7 +6948,7 @@ void do_highlight(const char *line, const bool forceit, const bool init) normal_bg = HL_TABLE()[idx].sg_rgb_bg; } } else if (strcmp(key, "GUISP") == 0) { - char_u **const namep = &HL_TABLE()[idx].sg_rgb_sp_name; + char **const namep = &HL_TABLE()[idx].sg_rgb_sp_name; if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI)) { if (!init) { @@ -6955,8 +6958,8 @@ void do_highlight(const char *line, const bool forceit, const bool init) if (*namep == NULL || STRCMP(*namep, arg) != 0) { xfree(*namep); if (strcmp(arg, "NONE") != 0) { - *namep = (char_u *)xstrdup(arg); - HL_TABLE()[idx].sg_rgb_sp = name_to_color((char_u *)arg); + *namep = xstrdup(arg); + HL_TABLE()[idx].sg_rgb_sp = name_to_color(arg); } else { *namep = NULL; HL_TABLE()[idx].sg_rgb_sp = -1; @@ -7150,13 +7153,32 @@ static void highlight_list_one(const int id) msg_outtrans(HL_TABLE()[HL_TABLE()[id - 1].sg_link - 1].sg_name); } - if (!didh) - highlight_list_arg(id, didh, LIST_STRING, 0, (char_u *)"cleared", ""); + if (!didh) { + highlight_list_arg(id, didh, LIST_STRING, 0, "cleared", ""); + } if (p_verbose > 0) { last_set_msg(sgp->sg_script_ctx); } } +Dictionary get_global_hl_defs(void) +{ + Dictionary rv = ARRAY_DICT_INIT; + for (int i = 1; i <= highlight_ga.ga_len && !got_int; i++) { + Dictionary attrs = ARRAY_DICT_INIT; + struct hl_group *h = &HL_TABLE()[i - 1]; + if (h->sg_attr > 0) { + attrs = hlattrs2dict(syn_attr2entry(h->sg_attr), true); + } else if (h->sg_link > 0) { + const char *link = (const char *)HL_TABLE()[h->sg_link - 1].sg_name; + PUT(attrs, "link", STRING_OBJ(cstr_to_string(link))); + } + PUT(rv, (const char *)h->sg_name, DICTIONARY_OBJ(attrs)); + } + + return rv; +} + /// Outputs a highlight when doing ":hi MyHighlight" /// /// @param type one of \ref LIST_XXX @@ -7164,15 +7186,15 @@ static void highlight_list_one(const int id) /// @param sarg string used if \p type == LIST_STRING static bool highlight_list_arg( const int id, bool didh, const int type, int iarg, - char_u *const sarg, const char *const name) + char *const sarg, const char *const name) { - char_u buf[100]; + char buf[100]; if (got_int) { return false; } if (type == LIST_STRING ? (sarg != NULL) : (iarg != 0)) { - char_u *ts = buf; + char *ts = buf; if (type == LIST_INT) { snprintf((char *)buf, sizeof(buf), "%d", iarg - 1); } else if (type == LIST_STRING) { @@ -7189,15 +7211,15 @@ static bool highlight_list_arg( } } - (void)syn_list_header(didh, (int)(vim_strsize(ts) + STRLEN(name) + 1), id, - false); + (void)syn_list_header(didh, (int)(vim_strsize((char_u *)ts) + STRLEN(name) + + 1), id, false); didh = true; if (!got_int) { if (*name != NUL) { MSG_PUTS_ATTR(name, HL_ATTR(HLF_D)); MSG_PUTS_ATTR("=", HL_ATTR(HLF_D)); } - msg_outtrans(ts); + msg_outtrans((char_u *)ts); } } return didh; @@ -7376,7 +7398,7 @@ static void set_hl_attr(int idx) at_en.rgb_sp_color = sgp->sg_rgb_sp_name ? sgp->sg_rgb_sp : -1; at_en.hl_blend = sgp->sg_blend; - sgp->sg_attr = hl_get_syn_attr(idx+1, at_en); + sgp->sg_attr = hl_get_syn_attr(0, idx+1, at_en); // a cursor style uses this syn_id, make sure its attribute is updated. if (cursor_mode_uses_syn_id(idx+1)) { @@ -7539,11 +7561,17 @@ int syn_id2attr(int hl_id) struct hl_group *sgp; hl_id = syn_get_final_id(hl_id); - sgp = &HL_TABLE()[hl_id - 1]; /* index is ID minus one */ + int attr = ns_get_hl(-1, hl_id, false); + if (attr >= 0) { + return attr; + } + sgp = &HL_TABLE()[hl_id - 1]; // index is ID minus one return sgp->sg_attr; } + + /* * Translate a group ID to the final group ID (following links). */ @@ -7560,9 +7588,22 @@ int syn_get_final_id(int hl_id) * Look out for loops! Break after 100 links. */ for (count = 100; --count >= 0; ) { - sgp = &HL_TABLE()[hl_id - 1]; /* index is ID minus one */ - if (sgp->sg_link == 0 || sgp->sg_link > highlight_ga.ga_len) + // ACHTUNG: when using "tmp" attribute (no link) the function might be + // called twice. it needs be smart enough to remember attr only to + // syn_id2attr time + int check = ns_get_hl(-1, hl_id, true); + if (check == 0) { + return 0; // how dare! it broke the link! + } else if (check > 0) { + hl_id = check; + continue; + } + + + sgp = &HL_TABLE()[hl_id - 1]; // index is ID minus one + if (sgp->sg_link == 0 || sgp->sg_link > highlight_ga.ga_len) { break; + } hl_id = sgp->sg_link; } @@ -8492,7 +8533,7 @@ color_name_table_T color_name_table[] = { /// /// @param[in] name string value to convert to RGB /// return the hex value or -1 if could not find a correct value -RgbValue name_to_color(const char_u *name) +RgbValue name_to_color(const char *name) { if (name[0] == '#' && isxdigit(name[1]) && isxdigit(name[2]) diff --git a/src/nvim/tag.c b/src/nvim/tag.c index 81d1ef4c9f..c6b1a0d04c 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -2903,7 +2903,7 @@ static int jumpto_tag( && curwin != curwin_save && win_valid(curwin_save)) { /* Return cursor to where we were */ validate_cursor(); - redraw_later(VALID); + redraw_later(curwin, VALID); win_enter(curwin_save, true); } diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index 52d3eef810..39e2ca6171 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -233,7 +233,7 @@ Terminal *terminal_open(TerminalOptions opts) snprintf(var, sizeof(var), "terminal_color_%d", i); char *name = get_config_string(var); if (name) { - color_val = name_to_color((uint8_t *)name); + color_val = name_to_color(name); xfree(name); if (color_val != -1) { @@ -1060,7 +1060,7 @@ static bool send_mouse_event(Terminal *term, int c) curwin->w_redr_status = true; curwin = save_curwin; curbuf = curwin->w_buffer; - redraw_win_later(mouse_win, NOT_VALID); + redraw_later(mouse_win, NOT_VALID); invalidate_terminal(term, -1, -1); // Only need to exit focus if the scrolled window is the terminal window return mouse_win == curwin; diff --git a/src/nvim/testdir/check.vim b/src/nvim/testdir/check.vim new file mode 100644 index 0000000000..7f6b7dcfec --- /dev/null +++ b/src/nvim/testdir/check.vim @@ -0,0 +1,99 @@ +source shared.vim +source term_util.vim + +" Command to check for the presence of a feature. +command -nargs=1 CheckFeature call CheckFeature(<f-args>) +func CheckFeature(name) + if !has(a:name) + throw 'Skipped: ' .. a:name .. ' feature missing' + endif +endfunc + +" Command to check for the presence of a working option. +command -nargs=1 CheckOption call CheckOption(<f-args>) +func CheckOption(name) + if !exists('+' .. a:name) + throw 'Skipped: ' .. a:name .. ' option not supported' + endif +endfunc + +" Command to check for the presence of a function. +command -nargs=1 CheckFunction call CheckFunction(<f-args>) +func CheckFunction(name) + if !exists('*' .. a:name) + throw 'Skipped: ' .. a:name .. ' function missing' + endif +endfunc + +" Command to check for the presence of python. Argument should have been +" obtained with PythonProg() +func CheckPython(name) + if a:name == '' + throw 'Skipped: python command not available' + endif +endfunc + +" Command to check for running on MS-Windows +command CheckMSWindows call CheckMSWindows() +func CheckMSWindows() + if !has('win32') + throw 'Skipped: only works on MS-Windows' + endif +endfunc + +" Command to check for running on Unix +command CheckUnix call CheckUnix() +func CheckUnix() + if !has('unix') + throw 'Skipped: only works on Unix' + endif +endfunc + +" Command to check that making screendumps is supported. +" Caller must source screendump.vim +command CheckScreendump call CheckScreendump() +func CheckScreendump() + if !CanRunVimInTerminal() + throw 'Skipped: cannot make screendumps' + endif +endfunc + +" Command to check that we can Run Vim in a terminal window +command CheckRunVimInTerminal call CheckRunVimInTerminal() +func CheckRunVimInTerminal() + if !CanRunVimInTerminal() + throw 'Skipped: cannot run Vim in a terminal window' + endif +endfunc + +" Command to check that we can run the GUI +command CheckCanRunGui call CheckCanRunGui() +func CheckCanRunGui() + if !has('gui') || ($DISPLAY == "" && !has('gui_running')) + throw 'Skipped: cannot start the GUI' + endif +endfunc + +" Command to check that not currently using the GUI +command CheckNotGui call CheckNotGui() +func CheckNotGui() + if has('gui_running') + throw 'Skipped: only works in the terminal' + endif +endfunc + +" Command to check that the current language is English +command CheckEnglish call CheckEnglish() +func CheckEnglish() + if v:lang != "C" && v:lang !~ '^[Ee]n' + throw 'Skipped: only works in English language environment' + endif +endfunc + +" Command to check for NOT running on MS-Windows +command CheckNotMSWindows call CheckNotMSWindows() +func CheckNotMSWindows() + if has('win32') + throw 'Skipped: does not work on MS-Windows' + endif +endfunc diff --git a/src/nvim/testdir/runnvim.sh b/src/nvim/testdir/runnvim.sh index 72f9254635..25cb8437b4 100755 --- a/src/nvim/testdir/runnvim.sh +++ b/src/nvim/testdir/runnvim.sh @@ -66,7 +66,7 @@ main() {( fi fi if test "$FAILED" = 1 ; then - travis_fold start "$NVIM_TEST_CURRENT_SUITE/$test_name" + ci_fold start "$NVIM_TEST_CURRENT_SUITE/$test_name" fi valgrind_check . if test -n "$LOG_DIR" ; then @@ -78,7 +78,7 @@ main() {( fi rm -f "$tlog" if test "$FAILED" = 1 ; then - travis_fold end "$NVIM_TEST_CURRENT_SUITE/$test_name" + ci_fold end "$NVIM_TEST_CURRENT_SUITE/$test_name" fi if test "$FAILED" = 1 ; then echo "Test $test_name failed, see output above and summary for more details" >> test.log diff --git a/src/nvim/testdir/runtest.vim b/src/nvim/testdir/runtest.vim index e249d499c4..b02514143c 100644 --- a/src/nvim/testdir/runtest.vim +++ b/src/nvim/testdir/runtest.vim @@ -7,6 +7,19 @@ " ../vim -u NONE -S runtest.vim test_channel.vim open_delay " The output can be found in the "messages" file. " +" If the environment variable $TEST_FILTER is set then only test functions +" matching this pattern are executed. E.g. for sh/bash: +" export TEST_FILTER=Test_channel +" For csh: +" setenv TEST_FILTER Test_channel +" +" To ignore failure for tests that are known to fail in a certain environment, +" set $TEST_MAY_FAIL to a comma separated list of function names. E.g. for +" sh/bash: +" export TEST_MAY_FAIL=Test_channel_one,Test_channel_other +" The failure report will then not be included in the test.log file and +" "make test" will not fail. +" " The test script may contain anything, only functions that start with " "Test_" are special. These will be invoked and should contain assert " functions. See test_assert.vim for an example. @@ -65,10 +78,17 @@ set encoding=utf-8 let s:test_script_fname = expand('%') au! SwapExists * call HandleSwapExists() func HandleSwapExists() - " Only ignore finding a swap file for the test script (the user might be + if exists('g:ignoreSwapExists') + return + endif + " Ignore finding a swap file for the test script (the user might be " editing it and do ":make test_name") and the output file. + " Report finding another swap file and chose 'q' to avoid getting stuck. if expand('<afile>') == 'messages' || expand('<afile>') =~ s:test_script_fname let v:swapchoice = 'e' + else + call assert_report('Unexpected swap file: ' .. v:swapname) + let v:swapchoice = 'q' endif endfunc @@ -84,6 +104,13 @@ let &runtimepath .= ','.expand($BUILD_DIR).'/runtime/' " Always use forward slashes. set shellslash +let s:t_bold = &t_md +let s:t_normal = &t_me +if has('win32') + " avoid prompt that is long or contains a line break + let $PROMPT = '$P$G' +endif + " Prepare for calling test_garbagecollect_now(). let v:testing = 1 @@ -131,13 +158,6 @@ func RunTheTest(test) endtry endif - let message = 'Executed ' . a:test - if has('reltime') - let message ..= ' in ' .. reltimestr(reltime(func_start)) .. ' seconds' - endif - call add(s:messages, message) - let s:done += 1 - if a:test =~ 'Test_nocatch_' " Function handles errors itself. This avoids skipping commands after the " error. @@ -191,13 +211,34 @@ func RunTheTest(test) endwhile exe 'cd ' . save_cwd + + let message = 'Executed ' . a:test + if has('reltime') + let message ..= repeat(' ', 50 - len(message)) + let time = reltime(func_start) + if has('float') && reltimefloat(time) > 0.1 + let message = s:t_bold .. message + endif + let message ..= ' in ' .. reltimestr(time) .. ' seconds' + if has('float') && reltimefloat(time) > 0.1 + let message ..= s:t_normal + endif + endif + call add(s:messages, message) + let s:done += 1 endfunc -func AfterTheTest() +func AfterTheTest(func_name) if len(v:errors) > 0 - let s:fail += 1 - call add(s:errors, 'Found errors in ' . s:test . ':') - call extend(s:errors, v:errors) + if match(s:may_fail_list, '^' .. a:func_name) >= 0 + let s:fail_expected += 1 + call add(s:errors_expected, 'Found errors in ' . s:test . ':') + call extend(s:errors_expected, v:errors) + else + let s:fail += 1 + call add(s:errors, 'Found errors in ' . s:test . ':') + call extend(s:errors, v:errors) + endif let v:errors = [] endif endfunc @@ -213,7 +254,7 @@ endfunc " This function can be called by a test if it wants to abort testing. func FinishTesting() - call AfterTheTest() + call AfterTheTest('') " Don't write viminfo on exit. set viminfo= @@ -221,7 +262,7 @@ func FinishTesting() " Clean up files created by setup.vim call delete('XfakeHOME', 'rf') - if s:fail == 0 + if s:fail == 0 && s:fail_expected == 0 " Success, create the .res file so that make knows it's done. exe 'split ' . fnamemodify(g:testname, ':r') . '.res' write @@ -237,12 +278,21 @@ func FinishTesting() endif if s:done == 0 - let message = 'NO tests executed' + if s:filtered > 0 + let message = "NO tests match $TEST_FILTER: '" .. $TEST_FILTER .. "'" + else + let message = 'NO tests executed' + endif else + if s:filtered > 0 + call add(s:messages, "Filtered " .. s:filtered .. " tests with $TEST_FILTER") + endif let message = 'Executed ' . s:done . (s:done > 1 ? ' tests' : ' test') endif - if has('reltime') + if s:done > 0 && has('reltime') + let message = s:t_bold .. message .. repeat(' ', 40 - len(message)) let message ..= ' in ' .. reltimestr(reltime(s:start_time)) .. ' seconds' + let message ..= s:t_normal endif echo message call add(s:messages, message) @@ -252,6 +302,12 @@ func FinishTesting() call add(s:messages, message) call extend(s:messages, s:errors) endif + if s:fail_expected > 0 + let message = s:fail_expected . ' FAILED (matching $TEST_MAY_FAIL):' + echo message + call add(s:messages, message) + call extend(s:messages, s:errors_expected) + endif " Add SKIPPED messages call extend(s:messages, s:skipped) @@ -271,11 +327,13 @@ endfunc let g:testname = expand('%') let s:done = 0 let s:fail = 0 +let s:fail_expected = 0 let s:errors = [] +let s:errors_expected = [] let s:messages = [] let s:skipped = [] if expand('%') =~ 'test_vimscript.vim' - " this test has intentional s:errors, don't use try/catch. + " this test has intentional errors, don't use try/catch. source % else try @@ -306,11 +364,12 @@ let s:flaky_tests = [ \ 'Test_repeat_three()', \ 'Test_state()', \ 'Test_stop_all_in_callback()', - \ 'Test_term_mouse_double_click_to_create_tab', + \ 'Test_term_mouse_double_click_to_create_tab()', \ 'Test_term_mouse_multiple_clicks_to_visually_select()', \ 'Test_terminal_composing_unicode()', \ 'Test_terminal_redir_file()', \ 'Test_terminal_tmap()', + \ 'Test_termwinscroll()', \ 'Test_with_partial_callback()', \ ] @@ -330,8 +389,17 @@ endif " If the environment variable $TEST_FILTER is set then filter the function " names against it. +let s:filtered = 0 if $TEST_FILTER != '' + let s:filtered = len(s:tests) let s:tests = filter(s:tests, 'v:val =~ $TEST_FILTER') + let s:filtered -= len(s:tests) +endif + +let s:may_fail_list = [] +if $TEST_MAY_FAIL != '' + " Split the list at commas and add () to make it match s:test. + let s:may_fail_list = split($TEST_MAY_FAIL, ',')->map({i, v -> v .. '()'}) endif " Execute the tests in alphabetical order. @@ -383,7 +451,7 @@ for s:test in sort(s:tests) endwhile endif - call AfterTheTest() + call AfterTheTest(s:test) endfor call FinishTesting() diff --git a/src/nvim/testdir/shared.vim b/src/nvim/testdir/shared.vim index 41ff9b2bd6..0e20ac1593 100644 --- a/src/nvim/testdir/shared.vim +++ b/src/nvim/testdir/shared.vim @@ -56,6 +56,9 @@ endfunc " Run "cmd". Returns the job if using a job. func RunCommand(cmd) + " Running an external command can occasionally be slow or fail. + let g:test_is_flaky = 1 + let job = 0 if has('job') let job = job_start(a:cmd, {"stoponexit": "hup"}) @@ -240,7 +243,7 @@ func s:feedkeys(timer) call feedkeys('x', 'nt') endfunc -" Get $VIMPROG to run Vim executable. +" Get $VIMPROG to run the Vim executable. " The Makefile writes it as the first line in the "vimcmd" file. " Nvim: uses $NVIM_TEST_ARG0. func GetVimProg() @@ -271,7 +274,7 @@ func GetVimCommand(...) let cmd = cmd . ' -u ' . name endif let cmd .= ' --headless -i NONE' - let cmd = substitute(cmd, 'VIMRUNTIME=.*VIMRUNTIME;', '', '') + let cmd = substitute(cmd, 'VIMRUNTIME=\S\+', '', '') " If using valgrind, make sure every run uses a different log file. if cmd =~ 'valgrind.*--log-file=' @@ -288,12 +291,22 @@ func GetVimCommandClean() let cmd = substitute(cmd, '-u NONE', '--clean', '') let cmd = substitute(cmd, '--headless', '', '') + " Force using utf-8, Vim may pick up something else from the environment. + " let cmd ..= ' --cmd "set enc=utf8" ' + " Optionally run Vim under valgrind " let cmd = 'valgrind --tool=memcheck --leak-check=yes --num-callers=25 --log-file=valgrind ' . cmd return cmd endfunc +" Get the command to run Vim, with --clean, and force to run in terminal so it +" won't start a new GUI. +func GetVimCommandCleanTerm() + " Add -v to have gvim run in the terminal (if possible) + return GetVimCommandClean() .. ' -v ' +endfunc + " Run Vim, using the "vimcmd" file and "-u NORC". " "before" is a list of Vim commands to be executed before loading plugins. " "after" is a list of Vim commands to be executed after loading plugins. @@ -330,6 +343,16 @@ func RunVimPiped(before, after, arguments, pipecmd) return 1 endfunc -func CanRunGui() - return has('gui') && ($DISPLAY != "" || has('gui_running')) +" Get all messages but drop the maintainer entry. +func GetMessages() + redir => result + redraw | messages + redir END + let msg_list = split(result, "\n") + " if msg_list->len() > 0 && msg_list[0] =~ 'Messages maintainer:' + " return msg_list[1:] + " endif + return msg_list endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/summarize.vim b/src/nvim/testdir/summarize.vim index 7f8f758a71..da5856a2e7 100644 --- a/src/nvim/testdir/summarize.vim +++ b/src/nvim/testdir/summarize.vim @@ -1,3 +1,4 @@ +set cpo&vim if 1 " This is executed only with the eval feature set nocompatible diff --git a/src/nvim/testdir/test_alot.vim b/src/nvim/testdir/test_alot.vim index 5668f45dea..7647475427 100644 --- a/src/nvim/testdir/test_alot.vim +++ b/src/nvim/testdir/test_alot.vim @@ -13,7 +13,6 @@ source test_ex_undo.vim source test_ex_z.vim source test_execute_func.vim source test_expand_func.vim -source test_expr.vim source test_feedkeys.vim source test_filter_cmd.vim source test_filter_map.vim @@ -50,6 +49,7 @@ source test_tagjump.vim source test_taglist.vim source test_true_false.vim source test_unlet.vim +source test_version.vim source test_virtualedit.vim source test_window_cmd.vim source test_wnext.vim diff --git a/src/nvim/testdir/test_arglist.vim b/src/nvim/testdir/test_arglist.vim index 6e7583ade3..92fedf9bfb 100644 --- a/src/nvim/testdir/test_arglist.vim +++ b/src/nvim/testdir/test_arglist.vim @@ -397,9 +397,15 @@ func Test_argdelete() last argdelete % call assert_equal(['b'], argv()) - call assert_fails('argdelete', 'E471:') + call assert_fails('argdelete', 'E610:') call assert_fails('1,100argdelete', 'E16:') - %argd + + call Reset_arglist() + args a b c d + next + argdel + call Assert_argc(['a', 'c', 'd']) + %argdel endfunc func Test_argdelete_completion() diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim index d116246ef3..04a678eeb8 100644 --- a/src/nvim/testdir/test_autocmd.vim +++ b/src/nvim/testdir/test_autocmd.vim @@ -1,6 +1,8 @@ " Tests for autocommands source shared.vim +source check.vim +source term_util.vim func! s:cleanup_buffers() abort for bnr in range(1, bufnr('$')) @@ -1125,7 +1127,7 @@ func Test_change_mark_in_autocmds() write au! BufWritePre - if executable('cat') + if has('unix') write XtestFilter write >> XtestFilter @@ -1735,6 +1737,35 @@ func Test_throw_in_BufWritePre() au! throwing endfunc +func Test_autocmd_CmdWinEnter() + CheckRunVimInTerminal + " There is not cmdwin switch, so + " test for cmdline_hist + " (both are available with small builds) + CheckFeature cmdline_hist + let lines =<< trim END + let b:dummy_var = 'This is a dummy' + autocmd CmdWinEnter * quit + let winnr = winnr('$') + END + let filename='XCmdWinEnter' + call writefile(lines, filename) + let buf = RunVimInTerminal('-S '.filename, #{rows: 6}) + + call term_sendkeys(buf, "q:") + call term_wait(buf) + call term_sendkeys(buf, ":echo b:dummy_var\<cr>") + call WaitForAssert({-> assert_match('^This is a dummy', term_getline(buf, 6))}, 1000) + call term_sendkeys(buf, ":echo &buftype\<cr>") + call WaitForAssert({-> assert_notmatch('^nofile', term_getline(buf, 6))}, 1000) + call term_sendkeys(buf, ":echo winnr\<cr>") + call WaitForAssert({-> assert_match('^1', term_getline(buf, 6))}, 1000) + + " clean up + call StopVimInTerminal(buf) + call delete(filename) +endfunc + func Test_FileChangedShell_reload() if !has('unix') return @@ -1866,4 +1897,17 @@ func Test_autocmd_FileReadCmd() delfunc ReadFileCmd endfunc +" Tests for SigUSR1 autocmd event, which is only available on posix systems. +func Test_autocmd_sigusr1() + CheckUnix + + let g:sigusr1_passed = 0 + au Signal SIGUSR1 let g:sigusr1_passed = 1 + call system('/bin/kill -s usr1 ' . getpid()) + call WaitForAssert({-> assert_true(g:sigusr1_passed)}) + + au! Signal + unlet g:sigusr1_passed +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_backup.vim b/src/nvim/testdir/test_backup.vim index fa10430613..ce2bfe72bc 100644 --- a/src/nvim/testdir/test_backup.vim +++ b/src/nvim/testdir/test_backup.vim @@ -1,7 +1,7 @@ " Tests for the backup function func Test_backup() - set backup backupdir=. + set backup backupdir=. backupskip= new call setline(1, ['line1', 'line2']) :f Xbackup.txt @@ -12,13 +12,13 @@ func Test_backup() let l = readfile('Xbackup.txt~') call assert_equal(['line1', 'line2'], l) bw! - set backup&vim backupdir&vim + set backup&vim backupdir&vim backupskip&vim call delete('Xbackup.txt') call delete('Xbackup.txt~') endfunc func Test_backup2() - set backup backupdir=.// + set backup backupdir=.// backupskip= new call setline(1, ['line1', 'line2', 'line3']) :f Xbackup.txt @@ -29,16 +29,16 @@ func Test_backup2() sp *Xbackup.txt~ call assert_equal(['line1', 'line2', 'line3'], getline(1,'$')) let f=expand('%') - call assert_match('src%nvim%testdir%Xbackup.txt\~', f) + call assert_match('%testdir%Xbackup.txt\~', f) bw! bw! call delete('Xbackup.txt') call delete(f) - set backup&vim backupdir&vim + set backup&vim backupdir&vim backupskip&vim endfunc func Test_backup2_backupcopy() - set backup backupdir=.// backupcopy=yes + set backup backupdir=.// backupcopy=yes backupskip= new call setline(1, ['line1', 'line2', 'line3']) :f Xbackup.txt @@ -49,10 +49,10 @@ func Test_backup2_backupcopy() sp *Xbackup.txt~ call assert_equal(['line1', 'line2', 'line3'], getline(1,'$')) let f=expand('%') - call assert_match('src%nvim%testdir%Xbackup.txt\~', f) + call assert_match('%testdir%Xbackup.txt\~', f) bw! bw! call delete('Xbackup.txt') call delete(f) - set backup&vim backupdir&vim backupcopy&vim + set backup&vim backupdir&vim backupcopy&vim backupskip&vim endfunc diff --git a/src/nvim/testdir/test_cindent.vim b/src/nvim/testdir/test_cindent.vim index debc9da46d..b6c2d1467e 100644 --- a/src/nvim/testdir/test_cindent.vim +++ b/src/nvim/testdir/test_cindent.vim @@ -127,4 +127,40 @@ func Test_cindent_case() bwipe! endfunc +func Test_cindent_pragma() + new + setl cindent ts=4 sw=4 + setl cino=Ps + + let code =<< trim [CODE] + { + #pragma omp parallel + { + #pragma omp task + foo(); + # pragma omp taskwait + } + } + [CODE] + + call append(0, code) + normal gg + normal =G + + let expected =<< trim [CODE] + { + #pragma omp parallel + { + #pragma omp task + foo(); + # pragma omp taskwait + } + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_cjk_linebreak.vim b/src/nvim/testdir/test_cjk_linebreak.vim new file mode 100644 index 0000000000..dfaa8fa1af --- /dev/null +++ b/src/nvim/testdir/test_cjk_linebreak.vim @@ -0,0 +1,97 @@ +scriptencoding utf-8 + +func Run_cjk_linebreak_after(rigorous) + set textwidth=12 + for punct in [ + \ '!', '%', ')', ',', ':', ';', '>', '?', ']', '}', 'โ', 'โ', 'โ ', 'โก', + \ 'โฆ', 'โฐ', 'โฑ', 'โผ', 'โ', 'โ', 'โ', 'โ', 'โ', 'ใ', 'ใ', 'ใ', 'ใ', + \ 'ใ', 'ใ', 'ใ', 'ใ', 'ใ', 'ใ', 'ใ', '๏ผ', '๏ผ', '๏ผ', '๏ผ', '๏ผ', + \ '๏ผ', '๏ผ', '๏ผฝ', '๏ฝ'] + call setline('.', '่ฟๆฏไธไธชๆต่ฏ' .. punct.'่ฏ่ฏ CJK ่ก็ฆๅ่กฅไธใ') + normal gqq + if a:rigorous + call assert_equal('่ฟๆฏไธไธชๆต', getline(1)) + else + call assert_equal('่ฟๆฏไธไธชๆต่ฏ' .. punct, getline(1)) + endif + %d_ + endfor +endfunc + +func Test_cjk_linebreak_after() + set formatoptions=croqn2mB1j + call Run_cjk_linebreak_after(0) +endfunc + +func Test_cjk_linebreak_after_rigorous() + set formatoptions=croqn2mB1j] + call Run_cjk_linebreak_after(1) +endfunc + +func Run_cjk_linebreak_before() + set textwidth=12 + for punct in [ + \ '(', '<', '[', '`', '{', 'โ', 'โ', 'ใ', 'ใ', 'ใ', 'ใ', 'ใ', 'ใ', + \ 'ใ', 'ใ', 'ใ', '๏ผ', '๏ผป', '๏ฝ'] + call setline('.', '่ฟๆฏไธชๆต่ฏ' .. punct.'่ฏ่ฏ CJK ่ก็ฆๅ่กฅไธใ') + normal gqq + call assert_equal('่ฟๆฏไธชๆต่ฏ', getline(1)) + %d_ + endfor +endfunc + +func Test_cjk_linebreak_before() + set formatoptions=croqn2mB1j + call Run_cjk_linebreak_before() +endfunc + +func Test_cjk_linebreak_before_rigorous() + set formatoptions=croqn2mB1j] + call Run_cjk_linebreak_before() +endfunc + +func Run_cjk_linebreak_nobetween(rigorous) + " โฆโฆ must not start a line + call setline('.', '่ฟๆฏไธชๆต่ฏโฆโฆ่ฏ่ฏ CJK ่ก็ฆๅ่กฅไธใ') + set textwidth=12 ambiwidth=double + normal gqq + if a:rigorous + call assert_equal('่ฟๆฏไธชๆต', getline(1)) + else + call assert_equal('่ฟๆฏไธชๆต่ฏโฆโฆ', getline(1)) + endif + %d_ + + call setline('.', '่ฟๆฏไธไธชๆต่ฏโฆโฆ่ฏ่ฏ CJK ่ก็ฆๅ่กฅไธใ') + set textwidth=12 ambiwidth=double + normal gqq + call assert_equal('่ฟๆฏไธไธชๆต', getline(1)) + %d_ + + " but โโ can + call setline('.', '่ฟๆฏไธชๆต่ฏโโ่ฏ่ฏ CJK ่ก็ฆๅ่กฅไธใ') + set textwidth=12 ambiwidth=double + normal gqq + call assert_equal('่ฟๆฏไธชๆต่ฏ', getline(1)) +endfunc + +func Test_cjk_linebreak_nobetween() + set formatoptions=croqn2mB1j + call Run_cjk_linebreak_nobetween(0) +endfunc + +func Test_cjk_linebreak_nobetween_rigorous() + set formatoptions=croqn2mB1j] + call Run_cjk_linebreak_nobetween(1) +endfunc + +func Test_cjk_linebreak_join_punct() + for punct in ['โโ', 'ใ', '๏ผ', 'ใ', 'โฆโฆ'] + call setline(1, 'ๆๆฌๆๆฌ' .. punct) + call setline(2, 'English') + set formatoptions=croqn2mB1j + normal ggJ + call assert_equal('ๆๆฌๆๆฌ' .. punct.'English', getline(1)) + %d_ + endfor +endfunc diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim index f8d84f1a49..e3c42a4fe3 100644 --- a/src/nvim/testdir/test_cmdline.vim +++ b/src/nvim/testdir/test_cmdline.vim @@ -1,5 +1,8 @@ " Tests for editing the command line. +source check.vim +source screendump.vim + func Test_complete_tab() call writefile(['testfile'], 'Xtestfile') call feedkeys(":e Xtestf\t\r", "tx") @@ -718,6 +721,27 @@ func Test_verbosefile() call delete('Xlog') endfunc +func Test_verbose_option() + " See test/functional/legacy/cmdline_spec.lua + CheckScreendump + + let lines =<< trim [SCRIPT] + command DoSomething echo 'hello' |set ts=4 |let v = '123' |echo v + call feedkeys("\r", 't') " for the hit-enter prompt + set verbose=20 + [SCRIPT] + call writefile(lines, 'XTest_verbose') + + let buf = RunVimInTerminal('-S XTest_verbose', {'rows': 12}) + call term_wait(buf, 100) + call term_sendkeys(buf, ":DoSomething\<CR>") + call VerifyScreenDump(buf, 'Test_verbose_option_1', {}) + + " clean up + call StopVimInTerminal(buf) + call delete('XTest_verbose') +endfunc + func Test_setcmdpos() func InsertTextAtPos(text, pos) call assert_equal(0, setcmdpos(a:pos)) @@ -818,6 +842,25 @@ func Test_buffers_lastused() bwipeout bufc endfunc +func Test_cmdlineclear_tabenter() + " See test/functional/legacy/cmdline_spec.lua + CheckScreendump + + let lines =<< trim [SCRIPT] + call setline(1, range(30)) + [SCRIPT] + + call writefile(lines, 'XtestCmdlineClearTabenter') + let buf = RunVimInTerminal('-S XtestCmdlineClearTabenter', #{rows: 10}) + call term_wait(buf, 50) + " in one tab make the command line higher with CTRL-W - + call term_sendkeys(buf, ":tabnew\<cr>\<C-w>-\<C-w>-gtgt") + call VerifyScreenDump(buf, 'Test_cmdlineclear_tabenter', {}) + + call StopVimInTerminal(buf) + call delete('XtestCmdlineClearTabenter') +endfunc + " test that ";" works to find a match at the start of the first line func Test_zero_line_search() new diff --git a/src/nvim/testdir/test_compiler.vim b/src/nvim/testdir/test_compiler.vim index 6bb602717f..9101f8cfa0 100644 --- a/src/nvim/testdir/test_compiler.vim +++ b/src/nvim/testdir/test_compiler.vim @@ -42,12 +42,12 @@ func Test_compiler_without_arg() let a = split(execute('compiler')) call assert_match(runtime .. '/compiler/ant.vim$', a[0]) call assert_match(runtime .. '/compiler/bcc.vim$', a[1]) - call assert_match(runtime .. '/compiler/xmlwf.vim$', a[-1]) + call assert_match(runtime .. '/compiler/xo.vim$', a[-1]) endfunc func Test_compiler_completion() call feedkeys(":compiler \<C-A>\<C-B>\"\<CR>", 'tx') - call assert_match('^"compiler ant bcc .* xmlwf$', @:) + call assert_match('^"compiler ant bcc .* xmlwf xo$', @:) call feedkeys(":compiler p\<C-A>\<C-B>\"\<CR>", 'tx') call assert_equal('"compiler pbx perl php pylint pyunit', @:) diff --git a/src/nvim/testdir/test_const.vim b/src/nvim/testdir/test_const.vim index eaf200e9bb..fc7ea71f6e 100644 --- a/src/nvim/testdir/test_const.vim +++ b/src/nvim/testdir/test_const.vim @@ -36,6 +36,7 @@ func Test_define_var_with_lock() call assert_fails('let s = "vim"', 'E741:') call assert_fails('let F = funcref("s:noop")', 'E741:') call assert_fails('let l = [1, 2, 3]', 'E741:') + call assert_fails('call filter(l, "v:val % 2 == 0")', 'E741:') call assert_fails('let d = {"foo": 10}', 'E741:') if has('channel') call assert_fails('let j = test_null_job()', 'E741:') @@ -247,11 +248,14 @@ func Test_lock_depth_is_1() const l = [1, 2, 3] const d = {'foo': 10} - " Modify list - call add(l, 4) + " Modify list - setting item is OK, adding/removing items not let l[0] = 42 + call assert_fails('call add(l, 4)', 'E741:') + call assert_fails('unlet l[1]', 'E741:') - " Modify dict - let d['bar'] = 'hello' + " Modify dict - changing item is OK, adding/removing items not + let d['foo'] = 'hello' let d.foo = 44 + call assert_fails("let d['bar'] = 'hello'", 'E741:') + call assert_fails("unlet d['foo']", 'E741:') endfunc diff --git a/src/nvim/testdir/test_debugger.vim b/src/nvim/testdir/test_debugger.vim index 811717208e..59d51b855b 100644 --- a/src/nvim/testdir/test_debugger.vim +++ b/src/nvim/testdir/test_debugger.vim @@ -316,3 +316,128 @@ func Test_Debugger() call delete('Xtest.vim') endfunc + +" Test for setting a breakpoint on a :endif where the :if condition is false +" and then quit the script. This should generate an interrupt. +func Test_breakpt_endif_intr() + func F() + let g:Xpath ..= 'a' + if v:false + let g:Xpath ..= 'b' + endif + invalid_command + endfunc + + let g:Xpath = '' + breakadd func 4 F + try + let caught_intr = 0 + debuggreedy + call feedkeys(":call F()\<CR>quit\<CR>", "xt") + call F() + catch /^Vim:Interrupt$/ + call assert_match('\.F, line 4', v:throwpoint) + let caught_intr = 1 + endtry + 0debuggreedy + call assert_equal(1, caught_intr) + call assert_equal('a', g:Xpath) + breakdel * + delfunc F +endfunc + +" Test for setting a breakpoint on a :else where the :if condition is false +" and then quit the script. This should generate an interrupt. +func Test_breakpt_else_intr() + func F() + let g:Xpath ..= 'a' + if v:false + let g:Xpath ..= 'b' + else + invalid_command + endif + invalid_command + endfunc + + let g:Xpath = '' + breakadd func 4 F + try + let caught_intr = 0 + debuggreedy + call feedkeys(":call F()\<CR>quit\<CR>", "xt") + call F() + catch /^Vim:Interrupt$/ + call assert_match('\.F, line 4', v:throwpoint) + let caught_intr = 1 + endtry + 0debuggreedy + call assert_equal(1, caught_intr) + call assert_equal('a', g:Xpath) + breakdel * + delfunc F +endfunc + +" Test for setting a breakpoint on a :endwhile where the :while condition is +" false and then quit the script. This should generate an interrupt. +func Test_breakpt_endwhile_intr() + func F() + let g:Xpath ..= 'a' + while v:false + let g:Xpath ..= 'b' + endwhile + invalid_command + endfunc + + let g:Xpath = '' + breakadd func 4 F + try + let caught_intr = 0 + debuggreedy + call feedkeys(":call F()\<CR>quit\<CR>", "xt") + call F() + catch /^Vim:Interrupt$/ + call assert_match('\.F, line 4', v:throwpoint) + let caught_intr = 1 + endtry + 0debuggreedy + call assert_equal(1, caught_intr) + call assert_equal('a', g:Xpath) + breakdel * + delfunc F +endfunc + +" Test for setting a breakpoint on an :endtry where an exception is pending to +" be processed and then quit the script. This should generate an interrupt and +" the thrown exception should be ignored. +func Test_breakpt_endtry_intr() + func F() + try + let g:Xpath ..= 'a' + throw "abc" + endtry + invalid_command + endfunc + + let g:Xpath = '' + breakadd func 4 F + try + let caught_intr = 0 + let caught_abc = 0 + debuggreedy + call feedkeys(":call F()\<CR>quit\<CR>", "xt") + call F() + catch /abc/ + let caught_abc = 1 + catch /^Vim:Interrupt$/ + call assert_match('\.F, line 4', v:throwpoint) + let caught_intr = 1 + endtry + 0debuggreedy + call assert_equal(1, caught_intr) + call assert_equal(0, caught_abc) + call assert_equal('a', g:Xpath) + breakdel * + delfunc F +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_diffmode.vim b/src/nvim/testdir/test_diffmode.vim index 49bbe84869..f09a64c329 100644 --- a/src/nvim/testdir/test_diffmode.vim +++ b/src/nvim/testdir/test_diffmode.vim @@ -1,6 +1,7 @@ " Tests for diff mode source shared.vim source screendump.vim +source check.vim func Test_diff_fold_sync() enew! @@ -723,10 +724,140 @@ func Test_diff_lastline() bwipe! endfunc +func Test_diff_screen() + CheckScreendump + CheckFeature menu + + " clean up already existing swap files, just in case + call delete('.Xfile1.swp') + call delete('.Xfile2.swp') + + " Test 1: Add a line in beginning of file 2 + call WriteDiffFiles(0, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) + let buf = RunVimInTerminal('-d Xfile1 Xfile2', {}) + " Set autoread mode, so that Vim won't complain once we re-write the test + " files + call term_sendkeys(buf, ":set autoread\<CR>\<c-w>w:set autoread\<CR>\<c-w>w") + + call VerifyBoth(buf, 'Test_diff_01', '') + + " Test 2: Add a line in beginning of file 1 + call WriteDiffFiles(buf, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) + call VerifyBoth(buf, 'Test_diff_02', '') + + " Test 3: Add a line at the end of file 2 + call WriteDiffFiles(buf, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]) + call VerifyBoth(buf, 'Test_diff_03', '') + + " Test 4: Add a line at the end of file 1 + call WriteDiffFiles(buf, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) + call VerifyBoth(buf, 'Test_diff_04', '') + + " Test 5: Add a line in the middle of file 2, remove on at the end of file 1 + call WriteDiffFiles(buf, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], [1, 2, 3, 4, 4, 5, 6, 7, 8, 9, 10]) + call VerifyBoth(buf, 'Test_diff_05', '') + + " Test 6: Add a line in the middle of file 1, remove on at the end of file 2 + call WriteDiffFiles(buf, [1, 2, 3, 4, 4, 5, 6, 7, 8, 9, 10], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]) + call VerifyBoth(buf, 'Test_diff_06', '') + + " Variants on test 6 with different context settings + call term_sendkeys(buf, ":set diffopt+=context:2\<cr>") + call VerifyScreenDump(buf, 'Test_diff_06.2', {}) + call term_sendkeys(buf, ":set diffopt-=context:2\<cr>") + call term_sendkeys(buf, ":set diffopt+=context:1\<cr>") + call VerifyScreenDump(buf, 'Test_diff_06.1', {}) + call term_sendkeys(buf, ":set diffopt-=context:1\<cr>") + call term_sendkeys(buf, ":set diffopt+=context:0\<cr>") + call VerifyScreenDump(buf, 'Test_diff_06.0', {}) + call term_sendkeys(buf, ":set diffopt-=context:0\<cr>") + + " Test 7 - 9: Test normal/patience/histogram diff algorithm + call WriteDiffFiles(buf, ['#include <stdio.h>', '', '// Frobs foo heartily', 'int frobnitz(int foo)', '{', + \ ' int i;', ' for(i = 0; i < 10; i++)', ' {', ' printf("Your answer is: ");', + \ ' printf("%d\n", foo);', ' }', '}', '', 'int fact(int n)', '{', ' if(n > 1)', ' {', + \ ' return fact(n-1) * n;', ' }', ' return 1;', '}', '', 'int main(int argc, char **argv)', + \ '{', ' frobnitz(fact(10));', '}'], + \ ['#include <stdio.h>', '', 'int fib(int n)', '{', ' if(n > 2)', ' {', + \ ' return fib(n-1) + fib(n-2);', ' }', ' return 1;', '}', '', '// Frobs foo heartily', + \ 'int frobnitz(int foo)', '{', ' int i;', ' for(i = 0; i < 10; i++)', ' {', + \ ' printf("%d\n", foo);', ' }', '}', '', + \ 'int main(int argc, char **argv)', '{', ' frobnitz(fib(10));', '}']) + call term_sendkeys(buf, ":diffupdate!\<cr>") + call term_sendkeys(buf, ":set diffopt+=internal\<cr>") + call VerifyScreenDump(buf, 'Test_diff_07', {}) + + call term_sendkeys(buf, ":set diffopt+=algorithm:patience\<cr>") + call VerifyScreenDump(buf, 'Test_diff_08', {}) + + call term_sendkeys(buf, ":set diffopt+=algorithm:histogram\<cr>") + call VerifyScreenDump(buf, 'Test_diff_09', {}) + + " Test 10-11: normal/indent-heuristic + call term_sendkeys(buf, ":set diffopt&vim\<cr>") + call WriteDiffFiles(buf, ['', ' def finalize(values)', '', ' values.each do |v|', ' v.finalize', ' end'], + \ ['', ' def finalize(values)', '', ' values.each do |v|', ' v.prepare', ' end', '', + \ ' values.each do |v|', ' v.finalize', ' end']) + call term_sendkeys(buf, ":diffupdate!\<cr>") + call term_sendkeys(buf, ":set diffopt+=internal\<cr>") + call VerifyScreenDump(buf, 'Test_diff_10', {}) + + " Leave trailing : at commandline! + call term_sendkeys(buf, ":set diffopt+=indent-heuristic\<cr>:\<cr>") + call VerifyScreenDump(buf, 'Test_diff_11', {}, 'one') + " shouldn't matter, if indent-algorithm comes before or after the algorithm + call term_sendkeys(buf, ":set diffopt&\<cr>") + call term_sendkeys(buf, ":set diffopt+=indent-heuristic,algorithm:patience\<cr>:\<cr>") + call VerifyScreenDump(buf, 'Test_diff_11', {}, 'two') + call term_sendkeys(buf, ":set diffopt&\<cr>") + call term_sendkeys(buf, ":set diffopt+=algorithm:patience,indent-heuristic\<cr>:\<cr>") + call VerifyScreenDump(buf, 'Test_diff_11', {}, 'three') + + " Test 12: diff the same file + call WriteDiffFiles(buf, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) + call VerifyBoth(buf, 'Test_diff_12', '') + + " Test 13: diff an empty file + call WriteDiffFiles(buf, [], []) + call VerifyBoth(buf, 'Test_diff_13', '') + + " Test 14: test diffopt+=icase + call WriteDiffFiles(buf, ['a', 'b', 'cd'], ['A', 'b', 'cDe']) + call VerifyBoth(buf, 'Test_diff_14', " diffopt+=filler diffopt+=icase") + + " Test 15-16: test diffopt+=iwhite + call WriteDiffFiles(buf, ['int main()', '{', ' printf("Hello, World!");', ' return 0;', '}'], + \ ['int main()', '{', ' if (0)', ' {', ' printf("Hello, World!");', ' return 0;', ' }', '}']) + call term_sendkeys(buf, ":diffupdate!\<cr>") + call term_sendkeys(buf, ":set diffopt&vim diffopt+=filler diffopt+=iwhite\<cr>") + call VerifyScreenDump(buf, 'Test_diff_15', {}) + call term_sendkeys(buf, ":set diffopt+=internal\<cr>") + call VerifyScreenDump(buf, 'Test_diff_16', {}) + + " Test 17: test diffopt+=iblank + call WriteDiffFiles(buf, ['a', ' ', 'cd', 'ef', 'xxx'], ['a', 'cd', '', 'ef', 'yyy']) + call VerifyInternal(buf, 'Test_diff_17', " diffopt+=iblank") + + " Test 18: test diffopt+=iblank,iwhite / iwhiteall / iwhiteeol + call VerifyInternal(buf, 'Test_diff_18', " diffopt+=iblank,iwhite") + call VerifyInternal(buf, 'Test_diff_18', " diffopt+=iblank,iwhiteall") + call VerifyInternal(buf, 'Test_diff_18', " diffopt+=iblank,iwhiteeol") + + " Test 19: test diffopt+=iwhiteeol + call WriteDiffFiles(buf, ['a ', 'x', 'cd', 'ef', 'xx xx', 'foo', 'bar'], ['a', 'x', 'c d', ' ef', 'xx xx', 'foo', '', 'bar']) + call VerifyInternal(buf, 'Test_diff_19', " diffopt+=iwhiteeol") + + " Test 19: test diffopt+=iwhiteall + call VerifyInternal(buf, 'Test_diff_20', " diffopt+=iwhiteall") + + " clean up + call StopVimInTerminal(buf) + call delete('Xfile1') + call delete('Xfile2') +endfunc + func Test_diff_with_cursorline() - if !CanRunVimInTerminal() - throw 'Skipped: cannot run Vim in a terminal window' - endif + CheckScreendump call writefile([ \ 'hi CursorLine ctermbg=red ctermfg=white', @@ -750,13 +881,45 @@ func Test_diff_with_cursorline() call delete('Xtest_diff_cursorline') endfunc +func Test_diff_with_syntax() + CheckScreendump + + let lines =<< trim END + void doNothing() { + int x = 0; + char *s = "hello"; + return 5; + } + END + call writefile(lines, 'Xprogram1.c') + let lines =<< trim END + void doSomething() { + int x = 0; + char *s = "there"; + return 5; + } + END + call writefile(lines, 'Xprogram2.c') + + let lines =<< trim END + edit Xprogram1.c + diffsplit Xprogram2.c + END + call writefile(lines, 'Xtest_diff_syntax') + let buf = RunVimInTerminal('-S Xtest_diff_syntax', {}) + + call VerifyScreenDump(buf, 'Test_diff_syntax_1', {}) + + " clean up + call StopVimInTerminal(buf) + call delete('Xtest_diff_syntax') + call delete('Xprogram1.c') + call delete('Xprogram2.c') +endfunc + func Test_diff_of_diff() - if !CanRunVimInTerminal() - throw 'Skipped: cannot run Vim in a terminal window' - endif - if !has("rightleft") - throw 'Skipped: rightleft not supported' - endif + CheckScreendump + CheckFeature rightleft call writefile([ \ 'call setline(1, ["aa","bb","cc","@@ -3,2 +5,7 @@","dd","ee","ff"])', @@ -801,6 +964,34 @@ func Test_diff_closeoff() enew! endfunc +func Test_diff_rnu() + CheckScreendump + + let content =<< trim END + call setline(1, ['a', 'a', 'a', 'y', 'b', 'b', 'b', 'b', 'b']) + vnew + call setline(1, ['a', 'a', 'a', 'x', 'x', 'x', 'b', 'b', 'b', 'b', 'b']) + call setline(1, ['a', 'a', 'a', 'y', 'b', 'b', 'b', 'b', 'b']) + vnew + call setline(1, ['a', 'a', 'a', 'x', 'x', 'x', 'b', 'b', 'b', 'b', 'b']) + windo diffthis + setlocal number rnu foldcolumn=0 + END + call writefile(content, 'Xtest_diff_rnu') + let buf = RunVimInTerminal('-S Xtest_diff_rnu', {}) + + call VerifyScreenDump(buf, 'Test_diff_rnu_01', {}) + + call term_sendkeys(buf, "j") + call VerifyScreenDump(buf, 'Test_diff_rnu_02', {}) + call term_sendkeys(buf, "j") + call VerifyScreenDump(buf, 'Test_diff_rnu_03', {}) + + " clean up + call StopVimInTerminal(buf) + call delete('Xtest_diff_rnu') +endfunc + func Test_diff_and_scroll() " this was causing an ml_get error set ls=2 diff --git a/src/nvim/testdir/test_display.vim b/src/nvim/testdir/test_display.vim index 1c2f5a05ff..c702b44b88 100644 --- a/src/nvim/testdir/test_display.vim +++ b/src/nvim/testdir/test_display.vim @@ -6,11 +6,12 @@ " endif source view_util.vim +source check.vim +source screendump.vim + +func Test_display_foldcolumn() + CheckFeature folding -func! Test_display_foldcolumn() - if !has("folding") - return - endif new vnew vert resize 25 @@ -26,10 +27,10 @@ func! Test_display_foldcolumn() call cursor(2, 1) norm! zt - let lines=ScreenLines([1,2], winwidth(0)) + let lines = ScreenLines([1,2], winwidth(0)) call assert_equal(expect, lines) set fdc=2 - let lines=ScreenLines([1,2], winwidth(0)) + let lines = ScreenLines([1,2], winwidth(0)) let expect = [ \ " e more noise blah blah<", \ " 82> more stuff here " @@ -41,9 +42,8 @@ func! Test_display_foldcolumn() endfunc func! Test_display_foldtext_mbyte() - if !has("folding") - return - endif + CheckFeature folding + call NewWindow(10, 40) call append(0, range(1,20)) exe "set foldmethod=manual foldtext=foldtext() fillchars=fold:\u2500,vert:\u2502 fdc=2" @@ -70,6 +70,42 @@ func! Test_display_foldtext_mbyte() bw! endfunc +" check that win_ins_lines() and win_del_lines() work when t_cs is empty. +func Test_scroll_without_region() + CheckScreendump + + let lines =<< trim END + call setline(1, range(1, 20)) + set t_cs= + set laststatus=2 + END + call writefile(lines, 'Xtestscroll') + let buf = RunVimInTerminal('-S Xtestscroll', #{rows: 10}) + + call VerifyScreenDump(buf, 'Test_scroll_no_region_1', {}) + + call term_sendkeys(buf, ":3delete\<cr>") + call VerifyScreenDump(buf, 'Test_scroll_no_region_2', {}) + + call term_sendkeys(buf, ":4put\<cr>") + call VerifyScreenDump(buf, 'Test_scroll_no_region_3', {}) + + call term_sendkeys(buf, ":undo\<cr>") + call term_sendkeys(buf, ":undo\<cr>") + call term_sendkeys(buf, ":set laststatus=0\<cr>") + call VerifyScreenDump(buf, 'Test_scroll_no_region_4', {}) + + call term_sendkeys(buf, ":3delete\<cr>") + call VerifyScreenDump(buf, 'Test_scroll_no_region_5', {}) + + call term_sendkeys(buf, ":4put\<cr>") + call VerifyScreenDump(buf, 'Test_scroll_no_region_6', {}) + + " clean up + call StopVimInTerminal(buf) + call delete('Xtestscroll') +endfunc + func Test_display_listchars_precedes() set fillchars+=vert:\| call NewWindow(10, 10) @@ -125,3 +161,104 @@ func Test_display_listchars_precedes() set list& listchars& wrap& bw! endfunc + +" Check that win_lines() works correctly with the number_only parameter=TRUE +" should break early to optimize cost of drawing, but needs to make sure +" that the number column is correctly highlighted. +func Test_scroll_CursorLineNr_update() + CheckScreendump + + let lines =<< trim END + hi CursorLineNr ctermfg=73 ctermbg=236 + set nu rnu cursorline cursorlineopt=number + exe ":norm! o\<esc>110ia\<esc>" + END + let filename = 'Xdrawscreen' + call writefile(lines, filename) + let buf = RunVimInTerminal('-S '.filename, #{rows: 5, cols: 50}) + call term_sendkeys(buf, "k") + call term_wait(buf) + call VerifyScreenDump(buf, 'Test_winline_rnu', {}) + + " clean up + call StopVimInTerminal(buf) + call delete(filename) +endfunc + +" check a long file name does not result in the hit-enter prompt +func Test_edit_long_file_name() + CheckScreendump + + let longName = 'x'->repeat(min([&columns, 255])) + call writefile([], longName) + let buf = RunVimInTerminal('-N -u NONE ' .. longName, #{rows: 8}) + + call VerifyScreenDump(buf, 'Test_long_file_name_1', {}) + + call term_sendkeys(buf, ":q\<cr>") + + " clean up + call StopVimInTerminal(buf) + call delete(longName) +endfunc + +func Test_unprintable_fileformats() + CheckScreendump + + call writefile(["unix\r", "two"], 'Xunix.txt') + call writefile(["mac\r", "two"], 'Xmac.txt') + let lines =<< trim END + edit Xunix.txt + split Xmac.txt + edit ++ff=mac + END + let filename = 'Xunprintable' + call writefile(lines, filename) + let buf = RunVimInTerminal('-S '.filename, #{rows: 9, cols: 50}) + call VerifyScreenDump(buf, 'Test_display_unprintable_01', {}) + call term_sendkeys(buf, "\<C-W>\<C-W>\<C-L>") + call VerifyScreenDump(buf, 'Test_display_unprintable_02', {}) + + " clean up + call StopVimInTerminal(buf) + call delete('Xunix.txt') + call delete('Xmac.txt') + call delete(filename) +endfunc + +" Test for scrolling that modifies buffer during visual block +func Test_visual_block_scroll() + " See test/functional/legacy/visual_mode_spec.lua + CheckScreendump + + let lines =<< trim END + source $VIMRUNTIME/plugin/matchparen.vim + set scrolloff=1 + call setline(1, ['a', 'b', 'c', 'd', 'e', '', '{', '}', '{', 'f', 'g', '}']) + call cursor(5, 1) + END + + let filename = 'Xvisualblockmodifiedscroll' + call writefile(lines, filename) + + let buf = RunVimInTerminal('-S '.filename, #{rows: 7}) + call term_sendkeys(buf, "V\<C-D>\<C-D>") + + call VerifyScreenDump(buf, 'Test_display_visual_block_scroll', {}) + + call StopVimInTerminal(buf) + call delete(filename) +endfunc + +func Test_display_scroll_at_topline() + " See test/functional/legacy/display_spec.lua + CheckScreendump + + let buf = RunVimInTerminal('', #{cols: 20}) + call term_sendkeys(buf, ":call setline(1, repeat('a', 21))\<CR>") + call term_wait(buf) + call term_sendkeys(buf, "O\<Esc>") + call VerifyScreenDump(buf, 'Test_display_scroll_at_topline', #{rows: 4}) + + call StopVimInTerminal(buf) +endfunc diff --git a/src/nvim/testdir/test_edit.vim b/src/nvim/testdir/test_edit.vim index 12d5d9790e..abad6983dc 100644 --- a/src/nvim/testdir/test_edit.vim +++ b/src/nvim/testdir/test_edit.vim @@ -1,9 +1,11 @@ " Test for edit functions -" + if exists("+t_kD") let &t_kD="[3;*~" endif +source check.vim + " Needed for testing basic rightleft: Test_edit_rightleft source view_util.vim @@ -733,17 +735,16 @@ func! Test_edit_CTRL_O() endfunc func! Test_edit_CTRL_R() - throw 'skipped: Nvim does not support test_override()' " Insert Register new - call test_override("ALL", 1) + " call test_override("ALL", 1) set showcmd call feedkeys("AFOOBAR eins zwei\<esc>", 'tnix') call feedkeys("O\<c-r>.", 'tnix') call feedkeys("O\<c-r>=10*500\<cr>\<esc>", 'tnix') call feedkeys("O\<c-r>=getreg('=', 1)\<cr>\<esc>", 'tnix') call assert_equal(["getreg('=', 1)", '5000', "FOOBAR eins zwei", "FOOBAR eins zwei"], getline(1, '$')) - call test_override("ALL", 0) + " call test_override("ALL", 0) set noshowcmd bw! endfunc @@ -955,7 +956,6 @@ func! Test_edit_DROP() endfunc func! Test_edit_CTRL_V() - throw 'skipped: Nvim does not support test_override()' if has("ebcdic") return endif @@ -965,7 +965,7 @@ func! Test_edit_CTRL_V() " force some redraws set showmode showcmd "call test_override_char_avail(1) - call test_override('ALL', 1) + " call test_override('ALL', 1) call feedkeys("A\<c-v>\<c-n>\<c-v>\<c-l>\<c-v>\<c-b>\<esc>", 'tnix') call assert_equal(["abc\x0e\x0c\x02"], getline(1, '$')) @@ -978,7 +978,7 @@ func! Test_edit_CTRL_V() set norl endif - call test_override('ALL', 0) + " call test_override('ALL', 0) set noshowmode showcmd bw! endfunc @@ -1441,31 +1441,40 @@ endfunc func Test_edit_InsertLeave() new + au InsertLeavePre * let g:did_au_pre = 1 au InsertLeave * let g:did_au = 1 + let g:did_au_pre = 0 let g:did_au = 0 call feedkeys("afoo\<Esc>", 'tx') + call assert_equal(1, g:did_au_pre) call assert_equal(1, g:did_au) call assert_equal('foo', getline(1)) + let g:did_au_pre = 0 let g:did_au = 0 call feedkeys("Sbar\<C-C>", 'tx') + call assert_equal(1, g:did_au_pre) call assert_equal(0, g:did_au) call assert_equal('bar', getline(1)) inoremap x xx<Esc> + let g:did_au_pre = 0 let g:did_au = 0 call feedkeys("Saax", 'tx') + call assert_equal(1, g:did_au_pre) call assert_equal(1, g:did_au) call assert_equal('aaxx', getline(1)) inoremap x xx<C-C> + let g:did_au_pre = 0 let g:did_au = 0 call feedkeys("Sbbx", 'tx') + call assert_equal(1, g:did_au_pre) call assert_equal(0, g:did_au) call assert_equal('bbxx', getline(1)) bwipe! - au! InsertLeave + au! InsertLeave InsertLeavePre iunmap x endfunc @@ -1514,3 +1523,75 @@ func Test_edit_startinsert() set backspace& bwipe! endfunc + +func Test_edit_noesckeys() + CheckNotGui + new + + " <Left> moves cursor when 'esckeys' is set + exe "set t_kl=\<Esc>OD" + " set esckeys + call feedkeys("axyz\<Esc>ODX", "xt") + " call assert_equal("xyXz", getline(1)) + + " <Left> exits Insert mode when 'esckeys' is off + " set noesckeys + call setline(1, '') + call feedkeys("axyz\<Esc>ODX", "xt") + call assert_equal(["DX", "xyz"], getline(1, 2)) + + bwipe! + " set esckeys +endfunc + +" Test for editing a directory +func Test_edit_is_a_directory() + CheckEnglish + let dirname = getcwd() . "/Xdir" + call mkdir(dirname, 'p') + + new + redir => msg + exe 'edit' dirname + redir END + call assert_match("is a directory$", split(msg, "\n")[0]) + bwipe! + + let dirname .= '/' + + new + redir => msg + exe 'edit' dirname + redir END + call assert_match("is a directory$", split(msg, "\n")[0]) + bwipe! + + call delete(dirname, 'rf') +endfunc + +func Test_edit_browse() + " in the GUI this opens a file picker, we only test the terminal behavior + CheckNotGui + + " ":browse xxx" checks for the FileExplorer augroup and assumes editing "." + " works then. + augroup FileExplorer + au! + augroup END + + " When the USE_FNAME_CASE is defined this used to cause a crash. + browse enew + bwipe! + + browse split + bwipe! +endfunc + +func Test_read_invalid() + " set encoding=latin1 + " This was not properly checking for going past the end. + call assert_fails('r`=', 'E484') + set encoding=utf-8 +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_environ.vim b/src/nvim/testdir/test_environ.vim index 21bb09a690..a25d83753c 100644 --- a/src/nvim/testdir/test_environ.vim +++ b/src/nvim/testdir/test_environ.vim @@ -1,5 +1,9 @@ +" Test for environment variables. + scriptencoding utf-8 +source check.vim + func Test_environ() unlet! $TESTENV call assert_equal(0, has_key(environ(), 'TESTENV')) @@ -42,3 +46,24 @@ func Test_external_env() endif call assert_equal('', result) endfunc + +func Test_mac_locale() + CheckFeature osxdarwin + + " If $LANG is not set then the system locale will be used. + " Run Vim after unsetting all the locale environmental vars, and capture the + " output of :lang. + let lang_results = system("unset LANG; unset LC_MESSAGES; " .. + \ shellescape(v:progpath) .. + \ " --clean -esX -c 'redir @a' -c 'lang' -c 'put a' -c 'print' -c 'qa!' ") + + " Check that: + " 1. The locale is the form of <locale>.UTF-8. + " 2. Check that fourth item (LC_NUMERIC) is properly set to "C". + " Example match: "en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8" + call assert_match('"\([a-zA-Z_]\+\.UTF-8/\)\{3}C\(/[a-zA-Z_]\+\.UTF-8\)\{2}"', + \ lang_results, + \ "Default locale should have UTF-8 encoding set, and LC_NUMERIC set to 'C'") +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_eval_stuff.vim b/src/nvim/testdir/test_eval_stuff.vim index 4b54a0d39f..061364fb73 100644 --- a/src/nvim/testdir/test_eval_stuff.vim +++ b/src/nvim/testdir/test_eval_stuff.vim @@ -108,3 +108,27 @@ func Test_skip_after_throw() catch /something/ endtry endfunc + +func Test_curly_assignment() + let s:svar = 'svar' + let g:gvar = 'gvar' + let lname = 'gvar' + let gname = 'gvar' + let {'s:'.lname} = {'g:'.gname} + call assert_equal('gvar', s:gvar) + let s:gvar = '' + let { 's:'.lname } = { 'g:'.gname } + call assert_equal('gvar', s:gvar) + let s:gvar = '' + let { 's:' . lname } = { 'g:' . gname } + call assert_equal('gvar', s:gvar) + let s:gvar = '' + let { 's:' .. lname } = { 'g:' .. gname } + call assert_equal('gvar', s:gvar) + + unlet s:svar + unlet s:gvar + unlet g:gvar +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_expand_func.vim b/src/nvim/testdir/test_expand_func.vim index fb29c3eb7a..9588d3b89d 100644 --- a/src/nvim/testdir/test_expand_func.vim +++ b/src/nvim/testdir/test_expand_func.vim @@ -1,5 +1,7 @@ " Tests for expand() +source shared.vim + let s:sfile = expand('<sfile>') let s:slnum = str2nr(expand('<slnum>')) let s:sflnum = str2nr(expand('<sflnum>')) @@ -16,6 +18,25 @@ func s:expand_sflnum() return str2nr(expand('<sflnum>')) endfunc +" This test depends on the location in the test file, put it first. +func Test_expand_sflnum() + call assert_equal(7, s:sflnum) + call assert_equal(24, str2nr(expand('<sflnum>'))) + + " Line-continuation + call assert_equal( + \ 27, + \ str2nr(expand('<sflnum>'))) + + " Call in script-local function + call assert_equal(18, s:expand_sflnum()) + + " Call in command + command Flnum echo expand('<sflnum>') + call assert_equal(36, str2nr(trim(execute('Flnum')))) + delcommand Flnum +endfunc + func Test_expand_sfile() call assert_match('test_expand_func\.vim$', s:sfile) call assert_match('^function .*\.\.Test_expand_sfile$', expand('<sfile>')) @@ -30,7 +51,7 @@ func Test_expand_sfile() endfunc func Test_expand_slnum() - call assert_equal(4, s:slnum) + call assert_equal(6, s:slnum) call assert_equal(2, str2nr(expand('<slnum>'))) " Line-continuation @@ -47,20 +68,14 @@ func Test_expand_slnum() delcommand Slnum endfunc -func Test_expand_sflnum() - call assert_equal(5, s:sflnum) - call assert_equal(52, str2nr(expand('<sflnum>'))) - - " Line-continuation - call assert_equal( - \ 55, - \ str2nr(expand('<sflnum>'))) - - " Call in script-local function - call assert_equal(16, s:expand_sflnum()) +func s:sid_test() + return 'works' +endfunc - " Call in command - command Flnum echo expand('<sflnum>') - call assert_equal(64, str2nr(trim(execute('Flnum')))) - delcommand Flnum +func Test_expand_SID() + let sid = expand('<SID>') + execute 'let g:sid_result = ' .. sid .. 'sid_test()' + call assert_equal('works', g:sid_result) endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_expr.vim b/src/nvim/testdir/test_expr.vim index 264d8b000f..b8d6f5aa7d 100644 --- a/src/nvim/testdir/test_expr.vim +++ b/src/nvim/testdir/test_expr.vim @@ -49,6 +49,9 @@ func Test_dict() let d['a'] = 'aaa' call assert_equal('none', d['']) call assert_equal('aaa', d['a']) + + let d[ 'b' ] = 'bbb' + call assert_equal('bbb', d[ 'b' ]) endfunc func Test_strgetchar() diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim index d440bdcb1e..9f7e153955 100644 --- a/src/nvim/testdir/test_filetype.vim +++ b/src/nvim/testdir/test_filetype.vim @@ -54,6 +54,7 @@ let s:filename_checks = { \ 'acedb': ['file.wrm'], \ 'ada': ['file.adb', 'file.ads', 'file.ada', 'file.gpr'], \ 'ahdl': ['file.tdf'], + \ 'aidl': ['file.aidl'], \ 'alsaconf': ['.asoundrc', '/usr/share/alsa/alsa.conf', '/etc/asound.conf'], \ 'aml': ['file.aml'], \ 'ampl': ['file.run'], @@ -72,8 +73,9 @@ let s:filename_checks = { \ 'autoit': ['file.au3'], \ 'automake': ['GNUmakefile.am'], \ 'ave': ['file.ave'], - \ 'awk': ['file.awk'], + \ 'awk': ['file.awk', 'file.gawk'], \ 'b': ['file.mch', 'file.ref', 'file.imp'], + \ 'bzl': ['file.bazel', 'file.bzl', 'WORKSPACE'], \ 'bc': ['file.bc'], \ 'bdf': ['file.bdf'], \ 'bib': ['file.bib'], @@ -107,7 +109,7 @@ let s:filename_checks = { \ 'conaryrecipe': ['file.recipe'], \ 'conf': ['auto.master'], \ 'config': ['configure.in', 'configure.ac', 'Pipfile'], - \ 'context': ['tex/context/any/file.tex', 'file.mkii', 'file.mkiv', 'file.mkvi'], + \ 'context': ['tex/context/any/file.tex', 'file.mkii', 'file.mkiv', 'file.mkvi', 'file.mkxl', 'file.mklx'], \ 'cpp': ['file.cxx', 'file.c++', 'file.hh', 'file.hxx', 'file.hpp', 'file.ipp', 'file.moc', 'file.tcc', 'file.inl', 'file.tlh'], \ 'crm': ['file.crm'], \ 'cs': ['file.cs'], @@ -126,6 +128,7 @@ let s:filename_checks = { \ 'dart': ['file.dart', 'file.drt'], \ 'datascript': ['file.ds'], \ 'dcd': ['file.dcd'], + \ 'debchangelog': ['changelog.Debian', 'changelog.dch', 'NEWS.Debian', 'NEWS.dch', '/debian/changelog'], \ 'debcontrol': ['/debian/control'], \ 'debsources': ['/etc/apt/sources.list', '/etc/apt/sources.list.d/file.list'], \ 'def': ['file.def'], @@ -139,7 +142,7 @@ let s:filename_checks = { \ 'dnsmasq': ['/etc/dnsmasq.conf'], \ 'dockerfile': ['Dockerfile', 'file.Dockerfile'], \ 'dosbatch': ['file.bat', 'file.sys'], - \ 'dosini': ['.editorconfig', '/etc/pacman.conf', '/etc/yum.conf', 'file.ini'], + \ 'dosini': ['.editorconfig', '/etc/pacman.conf', '/etc/yum.conf', 'file.ini', 'npmrc', '.npmrc', 'php.ini', 'php.ini-5'], \ 'dot': ['file.dot', 'file.gv'], \ 'dracula': ['file.drac', 'file.drc', 'filelvs', 'filelpe'], \ 'dsl': ['file.dsl'], @@ -324,7 +327,7 @@ let s:filename_checks = { \ 'pamconf': ['/etc/pam.conf'], \ 'pamenv': ['/etc/security/pam_env.conf', '/home/user/.pam_environment'], \ 'papp': ['file.papp', 'file.pxml', 'file.pxsl'], - \ 'pascal': ['file.pas', 'file.dpr'], + \ 'pascal': ['file.pas', 'file.pp', 'file.dpr', 'file.lpr'], \ 'passwd': ['any/etc/passwd', 'any/etc/passwd-', 'any/etc/passwd.edit', 'any/etc/shadow', 'any/etc/shadow-', 'any/etc/shadow.edit', 'any/var/backups/passwd.bak', 'any/var/backups/shadow.bak'], \ 'pccts': ['file.g'], \ 'pdf': ['file.pdf'], @@ -453,7 +456,7 @@ let s:filename_checks = { \ 'texmf': ['texmf.cnf'], \ 'text': ['file.text', 'README'], \ 'tf': ['file.tf', '.tfrc', 'tfrc'], - \ 'tidy': ['.tidyrc', 'tidyrc'], + \ 'tidy': ['.tidyrc', 'tidyrc', 'tidy.conf'], \ 'tilde': ['file.t.html'], \ 'tli': ['file.tli'], \ 'tmux': ['tmuxfile.conf', '.tmuxfile.conf'], @@ -470,6 +473,7 @@ let s:filename_checks = { \ 'uc': ['file.uc'], \ 'udevconf': ['/etc/udev/udev.conf'], \ 'udevperm': ['/etc/udev/permissions.d/file.permissions'], + \ 'udevrules': ['/etc/udev/rules.d/file.rules', '/usr/lib/udev/rules.d/file.rules', '/lib/udev/rules.d/file.rules'], \ 'uil': ['file.uit', 'file.uil'], \ 'updatedb': ['/etc/updatedb.conf'], \ 'upstart': ['/usr/share/upstart/file.conf', '/usr/share/upstart/file.override', '/etc/init/file.conf', '/etc/init/file.override', '/.init/file.conf', '/.init/file.override', '/.config/upstart/file.conf', '/.config/upstart/file.override'], @@ -524,9 +528,11 @@ let s:filename_checks = { let s:filename_case_checks = { \ 'modula2': ['file.DEF', 'file.MOD'], + \ 'bzl': ['file.BUILD', 'BUILD'], \ } func CheckItems(checks) + set noswapfile for [ft, names] in items(a:checks) for i in range(0, len(names) - 1) new @@ -543,6 +549,7 @@ func CheckItems(checks) bwipe! endfor endfor + set swapfile& endfunc func Test_filetype_detection() @@ -595,7 +602,8 @@ let s:script_checks = { \ 'bc': [['#!/path/bc']], \ 'sed': [['#!/path/sed']], \ 'ocaml': [['#!/path/ocaml']], - \ 'awk': [['#!/path/awk']], + \ 'awk': [['#!/path/awk'], + \ ['#!/path/gawk']], \ 'wml': [['#!/path/wml']], \ 'scheme': [['#!/path/scheme']], \ 'cfengine': [['#!/path/cfengine']], diff --git a/src/nvim/testdir/test_filter_map.vim b/src/nvim/testdir/test_filter_map.vim index 1dd3a5b29f..a15567bcf2 100644 --- a/src/nvim/testdir/test_filter_map.vim +++ b/src/nvim/testdir/test_filter_map.vim @@ -11,6 +11,7 @@ func Test_filter_map_list_expr_string() call assert_equal([2, 4, 6, 8], map([1, 2, 3, 4], 'v:val * 2')) call assert_equal([0, 2, 4, 6], map([1, 2, 3, 4], 'v:key * 2')) call assert_equal([9, 9, 9, 9], map([1, 2, 3, 4], 9)) + call assert_equal([7, 7, 7], map([1, 2, 3], ' 7 ')) endfunc " dict with expression string diff --git a/src/nvim/testdir/test_fold.vim b/src/nvim/testdir/test_fold.vim index 692f6e4780..3c90c45952 100644 --- a/src/nvim/testdir/test_fold.vim +++ b/src/nvim/testdir/test_fold.vim @@ -795,3 +795,24 @@ func Test_fold_delete_first_line() bwipe! set foldmethod& endfunc + +" this was crashing +func Test_move_no_folds() + new + fold + setlocal fdm=expr + normal zj + bwipe! +endfunc + +" this was crashing +func Test_fold_create_delete_create() + new + fold + fold + normal zd + fold + bwipe! +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_functions.vim b/src/nvim/testdir/test_functions.vim index 51689db9c4..917a5e8eca 100644 --- a/src/nvim/testdir/test_functions.vim +++ b/src/nvim/testdir/test_functions.vim @@ -214,7 +214,7 @@ func Test_strftime() endif endfunc -func Test_resolve() +func Test_resolve_unix() if !has('unix') return endif @@ -258,6 +258,8 @@ func Test_resolve() call assert_equal('Xlink2', resolve('Xlink1')) call assert_equal('./Xlink2', resolve('./Xlink1')) call delete('Xlink1') + + call assert_equal('/', resolve('/')) endfunc func Test_simplify() @@ -334,6 +336,10 @@ func Test_strpart() call assert_equal('lรฉp', strpart('รฉlรฉphant', 2, 4)) call assert_equal('lรฉphant', strpart('รฉlรฉphant', 2)) + + call assert_equal('รฉ', strpart('รฉlรฉphant', 0, 1, 1)) + call assert_equal('รฉp', strpart('รฉlรฉphant', 3, 2, v:true)) + call assert_equal('oฬ', strpart('coฬmposed', 1, 1, 1)) endfunc func Test_tolower() @@ -1079,6 +1085,12 @@ func Test_trim() call assert_equal("", trim("", "")) call assert_equal("a", trim("a", "")) call assert_equal("", trim("", "a")) + call assert_equal("vim", trim(" vim ", " ", 0)) + call assert_equal("vim ", trim(" vim ", " ", 1)) + call assert_equal(" vim", trim(" vim ", " ", 2)) + call assert_fails('call trim(" vim ", " ", [])', 'E745:') + call assert_fails('call trim(" vim ", " ", -1)', 'E475:') + call assert_fails('call trim(" vim ", " ", 3)', 'E475:') let chars = join(map(range(1, 0x20) + [0xa0], {n -> nr2char(n)}), '') call assert_equal("x", trim(chars . "x" . chars)) @@ -1217,6 +1229,24 @@ func Test_reg_executing_and_recording() unlet s:reg_stat endfunc +func Test_getchar() + call feedkeys('a', '') + call assert_equal(char2nr('a'), getchar()) + + " call test_setmouse(1, 3) + " let v:mouse_win = 9 + " let v:mouse_winid = 9 + " let v:mouse_lnum = 9 + " let v:mouse_col = 9 + " call feedkeys("\<S-LeftMouse>", '') + call nvim_input_mouse('left', 'press', 'S', 0, 0, 2) + call assert_equal("\<S-LeftMouse>", getchar()) + call assert_equal(1, v:mouse_win) + call assert_equal(win_getid(1), v:mouse_winid) + call assert_equal(1, v:mouse_lnum) + call assert_equal(3, v:mouse_col) +endfunc + func Test_libcall_libcallnr() if !has('libcall') return @@ -1337,3 +1367,22 @@ func Test_readdir() call delete('Xdir', 'rf') endfunc + +" Test for the eval() function +func Test_eval() + call assert_fails("call eval('5 a')", 'E488:') +endfunc + +" Test for the nr2char() function +func Test_nr2char() + " set encoding=latin1 + call assert_equal('@', nr2char(64)) + set encoding=utf8 + call assert_equal('a', nr2char(97, 1)) + call assert_equal('a', nr2char(97, 0)) + + call assert_equal("\x80\xfc\b\xf4\x80\xfeX\x80\xfeX\x80\xfeX", eval('"\<M-' .. nr2char(0x100000) .. '>"')) + call assert_equal("\x80\xfc\b\xfd\x80\xfeX\x80\xfeX\x80\xfeX\x80\xfeX\x80\xfeX", eval('"\<M-' .. nr2char(0x40000000) .. '>"')) +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_gf.vim b/src/nvim/testdir/test_gf.vim index 4a4ffcefa1..ee548037ba 100644 --- a/src/nvim/testdir/test_gf.vim +++ b/src/nvim/testdir/test_gf.vim @@ -1,3 +1,4 @@ +" Test for the gf and gF (goto file) commands " This is a test if a URL is recognized by "gf", with the cursor before and " after the "://". Also test ":\\". @@ -109,7 +110,7 @@ func Test_gf() endfunc func Test_gf_visual() - call writefile([], "Xtest_gf_visual") + call writefile(['one', 'two', 'three', 'four'], "Xtest_gf_visual") new call setline(1, 'XXXtest_gf_visualXXX') set hidden @@ -118,6 +119,30 @@ func Test_gf_visual() norm! ttvtXgf call assert_equal('Xtest_gf_visual', bufname('%')) + " if multiple lines are selected, then gf should fail + call setline(1, ["one", "two"]) + normal VGgf + call assert_equal('Xtest_gf_visual', @%) + + " following line number is used for gF + bwipe! + new + call setline(1, 'XXXtest_gf_visual:3XXX') + norm! 0ttvt:gF + call assert_equal('Xtest_gf_visual', bufname('%')) + call assert_equal(3, getcurpos()[1]) + + " line number in visual area is used for file name + if has('unix') + bwipe! + call writefile([], "Xtest_gf_visual:3") + new + call setline(1, 'XXXtest_gf_visual:3XXX') + norm! 0ttvtXgF + call assert_equal('Xtest_gf_visual:3', bufname('%')) + call delete('Xtest_gf_visual:3') + endif + bwipe! call delete('Xtest_gf_visual') set hidden& diff --git a/src/nvim/testdir/test_gn.vim b/src/nvim/testdir/test_gn.vim index d41675be0c..9acec51913 100644 --- a/src/nvim/testdir/test_gn.vim +++ b/src/nvim/testdir/test_gn.vim @@ -158,7 +158,32 @@ func Test_gn_command() set wrapscan&vim set belloff&vim -endfu +endfunc + +func Test_gN_repeat() + new + call setline(1, 'this list is a list with a list of a list.') + /list + normal $gNgNgNx + call assert_equal('list with a list of a list', @") + bwipe! +endfunc + +func Test_gN_then_gn() + new + + call setline(1, 'this list is a list with a list of a last.') + /l.st + normal $gNgNgnx + call assert_equal('last', @") + + call setline(1, 'this list is a list with a lust of a last.') + /l.st + normal $gNgNgNgnx + call assert_equal('lust of a last', @") + + bwipe! +endfunc func Test_gn_multi_line() new diff --git a/src/nvim/testdir/test_highlight.vim b/src/nvim/testdir/test_highlight.vim index 6aa187b17e..00e42733a7 100644 --- a/src/nvim/testdir/test_highlight.vim +++ b/src/nvim/testdir/test_highlight.vim @@ -596,9 +596,17 @@ endfunc " This test must come before the Test_cursorline test, as it appears this " defines the Normal highlighting group anyway. func Test_1_highlight_Normalgroup_exists() - " MS-Windows GUI sets the font - if !has('win32') || !has('gui_running') - let hlNormal = HighlightArgs('Normal') + let hlNormal = HighlightArgs('Normal') + if !has('gui_running') call assert_match('hi Normal\s*clear', hlNormal) + elseif has('gui_gtk2') || has('gui_gnome') || has('gui_gtk3') + " expect is DEFAULT_FONT of gui_gtk_x11.c + call assert_match('hi Normal\s*font=Monospace 10', hlNormal) + elseif has('gui_motif') || has('gui_athena') + " expect is DEFAULT_FONT of gui_x11.c + call assert_match('hi Normal\s*font=7x13', hlNormal) + elseif has('win32') + " expect any font + call assert_match('hi Normal\s*font=.*', hlNormal) endif endfunc diff --git a/src/nvim/testdir/test_ins_complete.vim b/src/nvim/testdir/test_ins_complete.vim index 1c275d5bd1..57a0a7aaf4 100644 --- a/src/nvim/testdir/test_ins_complete.vim +++ b/src/nvim/testdir/test_ins_complete.vim @@ -1,3 +1,5 @@ +source screendump.vim +source check.vim " Test for insert expansion func Test_ins_complete() @@ -325,7 +327,10 @@ func Test_compl_in_cmdwin() set wildmenu wildchar=<Tab> com! -nargs=1 -complete=command GetInput let input = <q-args> com! -buffer TestCommand echo 'TestCommand' + let w:test_winvar = 'winvar' + let b:test_bufvar = 'bufvar' + " User-defined commands let input = '' call feedkeys("q:iGetInput T\<C-x>\<C-v>\<CR>", 'tx!') call assert_equal('TestCommand', input) @@ -334,7 +339,114 @@ func Test_compl_in_cmdwin() call feedkeys("q::GetInput T\<Tab>\<CR>:q\<CR>", 'tx!') call assert_equal('T', input) + com! -nargs=1 -complete=var GetInput let input = <q-args> + " Window-local variables + let input = '' + call feedkeys("q:iGetInput w:test_\<C-x>\<C-v>\<CR>", 'tx!') + call assert_equal('w:test_winvar', input) + + let input = '' + call feedkeys("q::GetInput w:test_\<Tab>\<CR>:q\<CR>", 'tx!') + call assert_equal('w:test_', input) + + " Buffer-local variables + let input = '' + call feedkeys("q:iGetInput b:test_\<C-x>\<C-v>\<CR>", 'tx!') + call assert_equal('b:test_bufvar', input) + + let input = '' + call feedkeys("q::GetInput b:test_\<Tab>\<CR>:q\<CR>", 'tx!') + call assert_equal('b:test_', input) + delcom TestCommand delcom GetInput + unlet w:test_winvar + unlet b:test_bufvar set wildmenu& wildchar& endfunc + +" Test for insert path completion with completeslash option +func Test_ins_completeslash() + CheckMSWindows + + call mkdir('Xdir') + + let orig_shellslash = &shellslash + set cpt& + + new + + set noshellslash + + set completeslash= + exe "normal oXd\<C-X>\<C-F>" + call assert_equal('Xdir\', getline('.')) + + set completeslash=backslash + exe "normal oXd\<C-X>\<C-F>" + call assert_equal('Xdir\', getline('.')) + + set completeslash=slash + exe "normal oXd\<C-X>\<C-F>" + call assert_equal('Xdir/', getline('.')) + + set shellslash + + set completeslash= + exe "normal oXd\<C-X>\<C-F>" + call assert_equal('Xdir/', getline('.')) + + set completeslash=backslash + exe "normal oXd\<C-X>\<C-F>" + call assert_equal('Xdir\', getline('.')) + + set completeslash=slash + exe "normal oXd\<C-X>\<C-F>" + call assert_equal('Xdir/', getline('.')) + %bw! + call delete('Xdir', 'rf') + + set noshellslash + set completeslash=slash + call assert_true(stridx(globpath(&rtp, 'syntax/*.vim', 1, 1)[0], '\') != -1) + + let &shellslash = orig_shellslash + set completeslash= +endfunc + +func Test_issue_7021() + CheckMSWindows + + let orig_shellslash = &shellslash + set noshellslash + + set completeslash=slash + call assert_false(expand('~') =~ '/') + + let &shellslash = orig_shellslash + set completeslash= +endfunc + +func Test_pum_with_folds_two_tabs() + CheckScreendump + + let lines =<< trim END + set fdm=marker + call setline(1, ['" x {{{1', '" a some text']) + call setline(3, range(&lines)->map({_, val -> '" a' .. val})) + norm! zm + tab sp + call feedkeys('2Gzv', 'xt') + call feedkeys("0fa", 'xt') + END + + call writefile(lines, 'Xpumscript') + let buf = RunVimInTerminal('-S Xpumscript', #{rows: 10}) + call term_wait(buf, 100) + call term_sendkeys(buf, "a\<C-N>") + call VerifyScreenDump(buf, 'Test_pum_with_folds_two_tabs', {}) + + call term_sendkeys(buf, "\<Esc>") + call StopVimInTerminal(buf) + call delete('Xpumscript') +endfunc diff --git a/src/nvim/testdir/test_interrupt.vim b/src/nvim/testdir/test_interrupt.vim new file mode 100644 index 0000000000..111752d16a --- /dev/null +++ b/src/nvim/testdir/test_interrupt.vim @@ -0,0 +1,27 @@ +" Test behavior of interrupt() + +let s:bufwritepre_called = 0 +let s:bufwritepost_called = 0 + +func s:bufwritepre() + let s:bufwritepre_called = 1 + call interrupt() +endfunction + +func s:bufwritepost() + let s:bufwritepost_called = 1 +endfunction + +func Test_interrupt() + new Xfile + let n = 0 + try + au BufWritePre Xfile call s:bufwritepre() + au BufWritePost Xfile call s:bufwritepost() + w! + catch /^Vim:Interrupt$/ + endtry + call assert_equal(1, s:bufwritepre_called) + call assert_equal(0, s:bufwritepost_called) + call assert_equal(0, filereadable('Xfile')) +endfunc diff --git a/src/nvim/testdir/test_lambda.vim b/src/nvim/testdir/test_lambda.vim index bfbb3e5c5b..f026c8a55f 100644 --- a/src/nvim/testdir/test_lambda.vim +++ b/src/nvim/testdir/test_lambda.vim @@ -181,7 +181,7 @@ function! Test_lambda_scope() let l:D = s:NewCounter2() call assert_equal(1, l:C()) - call assert_fails(':call l:D()', 'E15:') " E121: then E15: + call assert_fails(':call l:D()', 'E121:') call assert_equal(2, l:C()) endfunction diff --git a/src/nvim/testdir/test_listdict.vim b/src/nvim/testdir/test_listdict.vim index bea62cb0ad..8e2a987e74 100644 --- a/src/nvim/testdir/test_listdict.vim +++ b/src/nvim/testdir/test_listdict.vim @@ -199,9 +199,9 @@ func Test_dict_big() try let n = d[1500] catch - let str=substitute(v:exception, '\v(.{14}).*( \d{4}).*', '\1\2', '') + let str = substitute(v:exception, '\v(.{14}).*( "\d{4}").*', '\1\2', '') endtry - call assert_equal('Vim(let):E716: 1500', str) + call assert_equal('Vim(let):E716: "1500"', str) " lookup each items for i in range(1500) @@ -280,6 +280,14 @@ func Test_dict_func_remove_in_use() call assert_equal(expected, d.func(string(remove(d, 'func')))) endfunc +func Test_dict_literal_keys() + call assert_equal({'one': 1, 'two2': 2, '3three': 3, '44': 4}, #{one: 1, two2: 2, 3three: 3, 44: 4},) + + " why *{} cannot be used + let blue = 'blue' + call assert_equal('6', trim(execute('echo 2 *{blue: 3}.blue'))) +endfunc + " Nasty: deepcopy() dict that refers to itself (fails when noref used) func Test_dict_deepcopy() let d = {1:1, 2:2} diff --git a/src/nvim/testdir/test_mapping.vim b/src/nvim/testdir/test_mapping.vim index 82562339f6..152afb4b9d 100644 --- a/src/nvim/testdir/test_mapping.vim +++ b/src/nvim/testdir/test_mapping.vim @@ -391,6 +391,42 @@ func Test_motionforce_omap() delfunc GetCommand endfunc +func Test_error_in_map_expr() + if !has('terminal') || (has('win32') && has('gui_running')) + throw 'Skipped: cannot run Vim in a terminal window' + endif + + let lines =<< trim [CODE] + func Func() + " fail to create list + let x = [ + endfunc + nmap <expr> ! Func() + set updatetime=50 + [CODE] + call writefile(lines, 'Xtest.vim') + + let buf = term_start(GetVimCommandCleanTerm() .. ' -S Xtest.vim', {'term_rows': 8}) + let job = term_getjob(buf) + call WaitForAssert({-> assert_notequal('', term_getline(buf, 8))}) + + " GC must not run during map-expr processing, which can make Vim crash. + call term_sendkeys(buf, '!') + call term_wait(buf, 100) + call term_sendkeys(buf, "\<CR>") + call term_wait(buf, 100) + call assert_equal('run', job_status(job)) + + call term_sendkeys(buf, ":qall!\<CR>") + call WaitFor({-> job_status(job) ==# 'dead'}) + if has('unix') + call assert_equal('', job_info(job).termsig) + endif + + call delete('Xtest.vim') + exe buf .. 'bwipe!' +endfunc + " Test for mapping errors func Test_map_error() call assert_fails('unmap', 'E474:') diff --git a/src/nvim/testdir/test_marks.vim b/src/nvim/testdir/test_marks.vim index 06b9dc9dab..66df57ea39 100644 --- a/src/nvim/testdir/test_marks.vim +++ b/src/nvim/testdir/test_marks.vim @@ -94,33 +94,43 @@ func Test_marks_cmd() new Xtwo call setline(1, ['ccc', 'ddd']) norm! $mcGmD + exe "norm! GVgg\<Esc>G" w! b Xone let a = split(execute('marks'), "\n") call assert_equal(9, len(a)) - call assert_equal('mark line col file/text', a[0]) - call assert_equal(" ' 2 0 bbb", a[1]) - call assert_equal(' a 1 0 aaa', a[2]) - call assert_equal(' B 2 2 bbb', a[3]) - call assert_equal(' D 2 0 Xtwo', a[4]) - call assert_equal(' " 1 0 aaa', a[5]) - call assert_equal(' [ 1 0 aaa', a[6]) - call assert_equal(' ] 2 0 bbb', a[7]) - call assert_equal(' . 2 0 bbb', a[8]) + call assert_equal(['mark line col file/text', + \ " ' 2 0 bbb", + \ ' a 1 0 aaa', + \ ' B 2 2 bbb', + \ ' D 2 0 Xtwo', + \ ' " 1 0 aaa', + \ ' [ 1 0 aaa', + \ ' ] 2 0 bbb', + \ ' . 2 0 bbb'], a) b Xtwo let a = split(execute('marks'), "\n") - call assert_equal(9, len(a)) - call assert_equal('mark line col file/text', a[0]) - call assert_equal(" ' 1 0 ccc", a[1]) - call assert_equal(' c 1 2 ccc', a[2]) - call assert_equal(' B 2 2 Xone', a[3]) - call assert_equal(' D 2 0 ddd', a[4]) - call assert_equal(' " 2 0 ddd', a[5]) - call assert_equal(' [ 1 0 ccc', a[6]) - call assert_equal(' ] 2 0 ddd', a[7]) - call assert_equal(' . 2 0 ddd', a[8]) + call assert_equal(11, len(a)) + call assert_equal(['mark line col file/text', + \ " ' 1 0 ccc", + \ ' c 1 2 ccc', + \ ' B 2 2 Xone', + \ ' D 2 0 ddd', + \ ' " 2 0 ddd', + \ ' [ 1 0 ccc', + \ ' ] 2 0 ddd', + \ ' . 2 0 ddd', + \ ' < 1 0 ccc', + \ ' > 2 0 ddd'], a) + norm! Gdd + w! + let a = split(execute('marks <>'), "\n") + call assert_equal(3, len(a)) + call assert_equal(['mark line col file/text', + \ ' < 1 0 ccc', + \ ' > 2 0 -invalid-'], a) b Xone delmarks aB diff --git a/src/nvim/testdir/test_matchadd_conceal.vim b/src/nvim/testdir/test_matchadd_conceal.vim index b918525dbc..393e183ddb 100644 --- a/src/nvim/testdir/test_matchadd_conceal.vim +++ b/src/nvim/testdir/test_matchadd_conceal.vim @@ -1,9 +1,11 @@ " Test for matchadd() and conceal feature -if !has('conceal') - finish -endif + +source check.vim +CheckFeature conceal source shared.vim +source term_util.vim +source view_util.vim function! Test_simple_matchadd() new @@ -273,3 +275,70 @@ function! Test_matchadd_and_syn_conceal() call assert_notequal(screenattr(1, 11) , screenattr(1, 12)) call assert_equal(screenattr(1, 11) , screenattr(1, 32)) endfunction + +func Test_cursor_column_in_concealed_line_after_window_scroll() + CheckRunVimInTerminal + + " Test for issue #5012 fix. + " For a concealed line with cursor, there should be no window's cursor + " position invalidation during win_update() after scrolling attempt that is + " not successful and no real topline change happens. The invalidation would + " cause a window's cursor position recalc outside of win_line() where it's + " not possible to take conceal into account. + let lines =<< trim END + 3split + let m = matchadd('Conceal', '=') + setl conceallevel=2 concealcursor=nc + normal gg + "==expr== + END + call writefile(lines, 'Xcolesearch') + let buf = RunVimInTerminal('Xcolesearch', {}) + call term_wait(buf, 100) + + " Jump to something that is beyond the bottom of the window, + " so there's a scroll down. + call term_sendkeys(buf, ":so %\<CR>") + call term_wait(buf, 100) + call term_sendkeys(buf, "/expr\<CR>") + call term_wait(buf, 100) + + " Are the concealed parts of the current line really hidden? + let cursor_row = term_scrape(buf, '.')->map({_, e -> e.chars})->join('') + call assert_equal('"expr', cursor_row) + + " BugFix check: Is the window's cursor column properly updated for hidden + " parts of the current line? + call assert_equal(2, term_getcursor(buf)[1]) + + call StopVimInTerminal(buf) + call delete('Xcolesearch') +endfunc + +func Test_cursor_column_in_concealed_line_after_leftcol_change() + CheckRunVimInTerminal + + " Test for issue #5214 fix. + let lines =<< trim END + 0put = 'ab' .. repeat('-', &columns) .. 'c' + call matchadd('Conceal', '-') + set nowrap ss=0 cole=3 cocu=n + END + call writefile(lines, 'Xcurs-columns') + let buf = RunVimInTerminal('-S Xcurs-columns', {}) + + " Go to the end of the line (3 columns beyond the end of the screen). + " Horizontal scroll would center the cursor in the screen line, but conceal + " makes it go to screen column 1. + call term_sendkeys(buf, "$") + call term_wait(buf) + + " Are the concealed parts of the current line really hidden? + call WaitForAssert({-> assert_equal('c', term_getline(buf, '.'))}) + + " BugFix check: Is the window's cursor column properly updated for conceal? + call assert_equal(1, term_getcursor(buf)[1]) + + call StopVimInTerminal(buf) + call delete('Xcurs-columns') +endfunc diff --git a/src/nvim/testdir/test_messages.vim b/src/nvim/testdir/test_messages.vim index 7fbf04311d..30239a90c2 100644 --- a/src/nvim/testdir/test_messages.vim +++ b/src/nvim/testdir/test_messages.vim @@ -1,20 +1,16 @@ " Tests for :messages, :echomsg, :echoerr -function Test_messages() +source shared.vim + +func Test_messages() let oldmore = &more try set nomore - " Avoid the "message maintainer" line. - let $LANG = '' - let $LC_ALL = '' - let $LC_MESSAGES = '' - let $LC_COLLATE = '' let arr = map(range(10), '"hello" . v:val') for s in arr echomsg s | redraw endfor - let result = '' " get last two messages redir => result @@ -25,22 +21,17 @@ function Test_messages() " clear messages without last one 1messages clear - redir => result - redraw | messages - redir END - let msg_list = split(result, "\n") + let msg_list = GetMessages() call assert_equal(['hello9'], msg_list) " clear all messages messages clear - redir => result - redraw | messages - redir END - call assert_equal('', result) + let msg_list = GetMessages() + call assert_equal([], msg_list) finally let &more = oldmore endtry -endfunction +endfunc " Patch 7.4.1696 defined the "clearmode()" command for clearing the mode " indicator (e.g., "-- INSERT --") when ":stopinsert" is invoked. Message @@ -74,6 +65,7 @@ func Test_echomsg() call assert_equal("\n12345", execute(':echomsg 12345')) call assert_equal("\n[]", execute(':echomsg []')) call assert_equal("\n[1, 2, 3]", execute(':echomsg [1, 2, 3]')) + call assert_equal("\n[1, 2, []]", execute(':echomsg [1, 2, v:_null_list]')) call assert_equal("\n{}", execute(':echomsg {}')) call assert_equal("\n{'a': 1, 'b': 2}", execute(':echomsg {"a": 1, "b": 2}')) if has('float') diff --git a/src/nvim/testdir/test_mksession.vim b/src/nvim/testdir/test_mksession.vim index 9c9e04be07..215065f941 100644 --- a/src/nvim/testdir/test_mksession.vim +++ b/src/nvim/testdir/test_mksession.vim @@ -9,6 +9,29 @@ endif source shared.vim source term_util.vim +" Test for storing global and local argument list in a session file +" This one must be done first. +func Test__mksession_arglocal() + enew | only + n a b c + new + arglocal + mksession! Xtest_mks.out + + %bwipe! + %argdelete + argglobal + source Xtest_mks.out + call assert_equal(2, winnr('$')) + call assert_equal(2, arglistid(1)) + call assert_equal(0, arglistid(2)) + + %bwipe! + %argdelete + argglobal + call delete('Xtest_mks.out') +endfunc + func Test_mksession() tabnew let wrap_save = &wrap @@ -155,7 +178,7 @@ endfunc " Verify that arglist is stored correctly to the session file. func Test_mksession_arglist() - argdel * + %argdel next file1 file2 file3 file4 mksession! Xtest_mks.out source Xtest_mks.out @@ -307,6 +330,104 @@ func Test_mksession_quote_in_filename() call delete('Xtest_mks_quoted.out') endfunc +" Test for storing global variables in a session file +func Test_mksession_globals() + set sessionoptions+=globals + + " create different global variables + let g:Global_string = "Sun is shining" + let g:Global_count = 100 + let g:Global_pi = 3.14 + + mksession! Xtest_mks.out + + unlet g:Global_string + unlet g:Global_count + unlet g:Global_pi + + source Xtest_mks.out + call assert_equal("Sun is shining", g:Global_string) + call assert_equal(100, g:Global_count) + call assert_equal(3.14, g:Global_pi) + + unlet g:Global_string + unlet g:Global_count + unlet g:Global_pi + call delete('Xtest_mks.out') + set sessionoptions& +endfunc + +" Test for changing backslash to forward slash in filenames +func Test_mksession_slash() + if exists('+shellslash') + throw 'Skipped: cannot use backslash in file name' + endif + enew + %bwipe! + e a\\b\\c + mksession! Xtest_mks1.out + set sessionoptions+=slash + mksession! Xtest_mks2.out + + %bwipe! + source Xtest_mks1.out + call assert_equal('a/b/c', bufname('')) + %bwipe! + source Xtest_mks2.out + call assert_equal('a/b/c', bufname('')) + + %bwipe! + call delete('Xtest_mks1.out') + call delete('Xtest_mks2.out') + set sessionoptions& +endfunc + +" Test for changing directory to the session file directory +func Test_mksession_sesdir() + call mkdir('Xproj') + mksession! Xproj/Xtest_mks1.out + set sessionoptions-=curdir + set sessionoptions+=sesdir + mksession! Xproj/Xtest_mks2.out + + source Xproj/Xtest_mks1.out + call assert_equal('testdir', fnamemodify(getcwd(), ':t')) + source Xproj/Xtest_mks2.out + call assert_equal('Xproj', fnamemodify(getcwd(), ':t')) + cd .. + + set sessionoptions& + call delete('Xproj', 'rf') +endfunc + +" Test for storing the 'lines' and 'columns' settings +func Test_mksession_resize() + mksession! Xtest_mks1.out + set sessionoptions+=resize + mksession! Xtest_mks2.out + + let lines = readfile('Xtest_mks1.out') + let found_resize = v:false + for line in lines + if line =~ '^set lines=' + let found_resize = v:true + endif + endfor + call assert_equal(v:false, found_resize) + let lines = readfile('Xtest_mks2.out') + let found_resize = v:false + for line in lines + if line =~ '^set lines=' + let found_resize = v:true + endif + endfor + call assert_equal(v:true, found_resize) + + call delete('Xtest_mks1.out') + call delete('Xtest_mks2.out') + set sessionoptions& +endfunc + func s:ClearMappings() mapclear omapclear @@ -349,4 +470,17 @@ func Test_mkvimrc() call delete('Xtestvimrc') endfunc +func Test_scrolloff() + set sessionoptions+=localoptions + setlocal so=1 siso=1 + mksession! Xtest_mks.out + setlocal so=-1 siso=-1 + source Xtest_mks.out + call assert_equal(1, &l:so) + call assert_equal(1, &l:siso) + call delete('Xtest_mks.out') + setlocal so& siso& + set sessionoptions& +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_options.vim b/src/nvim/testdir/test_options.vim index 04a5c62f66..10e16f4198 100644 --- a/src/nvim/testdir/test_options.vim +++ b/src/nvim/testdir/test_options.vim @@ -267,7 +267,6 @@ func Test_set_errors() call assert_fails('set commentstring=x', 'E537:') call assert_fails('set complete=x', 'E539:') call assert_fails('set statusline=%{', 'E540:') - call assert_fails('set statusline=' . repeat("%p", 81), 'E541:') call assert_fails('set statusline=%(', 'E542:') if has('cursorshape') " This invalid value for 'guicursor' used to cause Vim to crash. @@ -576,3 +575,13 @@ func Test_opt_boolean() set number& endfunc +" Test for setting option value containing spaces with isfname+=32 +func Test_isfname_with_options() + set isfname+=32 + setlocal keywordprg=:term\ help.exe + call assert_equal(':term help.exe', &keywordprg) + set isfname& + setlocal keywordprg& +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_perl.vim b/src/nvim/testdir/test_perl.vim new file mode 100644 index 0000000000..872194a804 --- /dev/null +++ b/src/nvim/testdir/test_perl.vim @@ -0,0 +1,311 @@ +" Tests for Perl interface + +if !has('perl') || has('win32') + finish +endif + +" FIXME: RunTest don't see any error when Perl abort... +perl $SIG{__WARN__} = sub { die "Unexpected warnings from perl: @_" }; + +func Test_change_buffer() + call setline(line('$'), ['1 line 1']) + perl VIM::DoCommand("normal /^1\n") + perl $curline = VIM::Eval("line('.')") + perl $curbuf->Set($curline, "1 changed line 1") + call assert_equal('1 changed line 1', getline('$')) +endfunc + +func Test_evaluate_list() + call setline(line('$'), ['2 line 2']) + perl VIM::DoCommand("normal /^2\n") + perl $curline = VIM::Eval("line('.')") + let l = ["abc", "def"] + perl << EOF + $l = VIM::Eval("l"); + $curbuf->Append($curline, $l); +EOF + normal j + .perldo s|\n|/|g + " call assert_equal('abc/def/', getline('$')) + call assert_equal('def', getline('$')) +endfunc + +funct Test_VIM_Blob() + call assert_equal('0z', perleval('VIM::Blob("")')) + call assert_equal('0z31326162', perleval('VIM::Blob("12ab")')) + call assert_equal('0z00010203', perleval('VIM::Blob("\x00\x01\x02\x03")')) + call assert_equal('0z8081FEFF', perleval('VIM::Blob("\x80\x81\xfe\xff")')) +endfunc + +func Test_buffer_Delete() + new + call setline(1, ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']) + perl $curbuf->Delete(7) + perl $curbuf->Delete(2, 5) + perl $curbuf->Delete(10) + call assert_equal(['a', 'f', 'h'], getline(1, '$')) + bwipe! +endfunc + +func Test_buffer_Append() + new + perl $curbuf->Append(1, '1') + perl $curbuf->Append(2, '2', '3', '4') + perl @l = ('5' ..'7') + perl $curbuf->Append(0, @l) + call assert_equal(['5', '6', '7', '', '1', '2', '3', '4'], getline(1, '$')) + bwipe! +endfunc + +func Test_buffer_Set() + new + call setline(1, ['1', '2', '3', '4', '5']) + perl $curbuf->Set(2, 'a', 'b', 'c') + perl $curbuf->Set(4, 'A', 'B', 'C') + call assert_equal(['1', 'a', 'b', 'A', 'B'], getline(1, '$')) + bwipe! +endfunc + +func Test_buffer_Get() + new + call setline(1, ['1', '2', '3', '4']) + call assert_equal('2:3', perleval('join(":", $curbuf->Get(2, 3))')) + bwipe! +endfunc + +func Test_buffer_Count() + new + call setline(1, ['a', 'b', 'c']) + call assert_equal(3, perleval('$curbuf->Count()')) + bwipe! +endfunc + +func Test_buffer_Name() + new + call assert_equal('', perleval('$curbuf->Name()')) + bwipe! + new Xfoo + call assert_equal('Xfoo', perleval('$curbuf->Name()')) + bwipe! +endfunc + +func Test_buffer_Number() + call assert_equal(bufnr('%'), perleval('$curbuf->Number()')) +endfunc + +func Test_window_Cursor() + new + call setline(1, ['line1', 'line2']) + perl $curwin->Cursor(2, 3) + call assert_equal('2:3', perleval('join(":", $curwin->Cursor())')) + " Col is numbered from 0 in Perl, and from 1 in Vim script. + call assert_equal([0, 2, 4, 0], getpos('.')) + bwipe! +endfunc + +func Test_window_SetHeight() + new + perl $curwin->SetHeight(2) + call assert_equal(2, winheight(0)) + bwipe! +endfunc + +func Test_VIM_Windows() + new + " VIM::Windows() without argument in scalar and list context. + perl $winnr = VIM::Windows() + perl @winlist = VIM::Windows() + perl $curbuf->Append(0, $winnr, scalar(@winlist)) + call assert_equal(['2', '2', ''], getline(1, '$')) + + " VIM::Windows() with window number argument. + perl (VIM::Windows(VIM::Eval('winnr()')))[0]->Buffer()->Set(1, 'bar') + call assert_equal('bar', getline(1)) + bwipe! +endfunc + +func Test_VIM_Buffers() + new Xbar + " VIM::Buffers() without argument in scalar and list context. + perl $nbuf = VIM::Buffers() + perl @buflist = VIM::Buffers() + + " VIM::Buffers() with argument. + perl $curbuf = (VIM::Buffers('Xbar'))[0] + perl $curbuf->Append(0, $nbuf, scalar(@buflist)) + call assert_equal(['2', '2', ''], getline(1, '$')) + bwipe! +endfunc + +func <SID>catch_peval(expr) + try + call perleval(a:expr) + catch + return v:exception + endtry + call assert_report('no exception for `perleval("'.a:expr.'")`') + return '' +endfunc + +func Test_perleval() + call assert_false(perleval('undef')) + + " scalar + call assert_equal(0, perleval('0')) + call assert_equal(2, perleval('2')) + call assert_equal(-2, perleval('-2')) + if has('float') + call assert_equal(2.5, perleval('2.5')) + else + call assert_equal(2, perleval('2.5')) + end + + " sandbox call assert_equal(2, perleval('2')) + + call assert_equal('abc', perleval('"abc"')) + " call assert_equal("abc\ndef", perleval('"abc\0def"')) + + " ref + call assert_equal([], perleval('[]')) + call assert_equal(['word', 42, [42],{}], perleval('["word", 42, [42], {}]')) + + call assert_equal({}, perleval('{}')) + call assert_equal({'foo': 'bar'}, perleval('{foo => "bar"}')) + + perl our %h; our @a; + let a = perleval('[\(%h, %h, @a, @a)]') + " call assert_true((a[0] is a[1])) + call assert_equal(a[0], a[1]) + " call assert_true((a[2] is a[3])) + call assert_equal(a[2], a[3]) + perl undef %h; undef @a; + + " call assert_true(<SID>catch_peval('{"" , 0}') =~ 'Malformed key Dictionary') + " call assert_true(<SID>catch_peval('{"\0" , 0}') =~ 'Malformed key Dictionary') + " call assert_true(<SID>catch_peval('{"foo\0bar" , 0}') =~ 'Malformed key Dictionary') + + call assert_equal('*VIM', perleval('"*VIM"')) + " call assert_true(perleval('\\0') =~ 'SCALAR(0x\x\+)') +endfunc + +func Test_perldo() + sp __TEST__ + exe 'read ' g:testname + perldo s/perl/vieux_chameau/g + 1 + call assert_false(search('\Cperl')) + bw! + + " Check deleting lines does not trigger ml_get error. + new + call setline(1, ['one', 'two', 'three']) + perldo VIM::DoCommand("%d_") + bwipe! + + " Check switching to another buffer does not trigger ml_get error. + new + let wincount = winnr('$') + call setline(1, ['one', 'two', 'three']) + perldo VIM::DoCommand("new") + call assert_equal(wincount + 1, winnr('$')) + bwipe! + bwipe! +endfunc + +func Test_VIM_package() + perl VIM::DoCommand('let l:var = "foo"') + call assert_equal(l:var, 'foo') + + set noet + perl VIM::SetOption('et') + call assert_true(&et) +endfunc + +func Test_stdio() + throw 'skipped: TODO: ' + redir =>l:out + perl <<EOF + VIM::Msg("&VIM::Msg"); + print "STDOUT"; + print STDERR "STDERR"; +EOF + redir END + call assert_equal(['&VIM::Msg', 'STDOUT', 'STDERR'], split(l:out, "\n")) +endfunc + +" Run first to get a clean namespace +func Test_000_SvREFCNT() + throw 'skipped: TODO: ' + for i in range(8) + exec 'new X'.i + endfor + new t + perl <<--perl +#line 5 "Test_000_SvREFCNT()" + my ($b, $w); + + my $num = 0; + for ( 0 .. 100 ) { + if ( ++$num >= 8 ) { $num = 0 } + VIM::DoCommand("buffer X$num"); + $b = $curbuf; + } + + VIM::DoCommand("buffer t"); + + $b = $curbuf for 0 .. 100; + $w = $curwin for 0 .. 100; + () = VIM::Buffers for 0 .. 100; + () = VIM::Windows for 0 .. 100; + + VIM::DoCommand('bw! t'); + if (exists &Internals::SvREFCNT) { + my $cb = Internals::SvREFCNT($$b); + my $cw = Internals::SvREFCNT($$w); + VIM::Eval("assert_equal(2, $cb, 'T1')"); + VIM::Eval("assert_equal(2, $cw, 'T2')"); + my $strongref; + foreach ( VIM::Buffers, VIM::Windows ) { + VIM::DoCommand("%bw!"); + my $c = Internals::SvREFCNT($_); + VIM::Eval("assert_equal(2, $c, 'T3')"); + $c = Internals::SvREFCNT($$_); + next if $c == 2 && !$strongref++; + VIM::Eval("assert_equal(1, $c, 'T4')"); + } + $cb = Internals::SvREFCNT($$curbuf); + $cw = Internals::SvREFCNT($$curwin); + VIM::Eval("assert_equal(3, $cb, 'T5')"); + VIM::Eval("assert_equal(3, $cw, 'T6')"); + } + VIM::Eval("assert_false($$b)"); + VIM::Eval("assert_false($$w)"); +--perl + %bw! +endfunc + +func Test_set_cursor() + " Check that setting the cursor position works. + new + call setline(1, ['first line', 'second line']) + normal gg + perldo $curwin->Cursor(1, 5) + call assert_equal([1, 6], [line('.'), col('.')]) + + " Check that movement after setting cursor position keeps current column. + normal j + call assert_equal([2, 6], [line('.'), col('.')]) +endfunc + +" Test for various heredoc syntax +func Test_perl_heredoc() + perl << END +VIM::DoCommand('let s = "A"') +END + perl << +VIM::DoCommand('let s ..= "B"') +. + call assert_equal('AB', s) +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_popup.vim b/src/nvim/testdir/test_popup.vim index bb0ed6e00c..fb464d95ea 100644 --- a/src/nvim/testdir/test_popup.vim +++ b/src/nvim/testdir/test_popup.vim @@ -2,6 +2,7 @@ source shared.vim source screendump.vim +source check.vim let g:months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'] let g:setting = '' @@ -755,6 +756,52 @@ func Test_popup_and_previewwindow_dump() call delete('Xscript') endfunc +func Test_balloon_split() + CheckFunction balloon_split + + call assert_equal([ + \ 'tempname: 0x555555e380a0 "/home/mool/.viminfz.tmp"', + \ ], balloon_split( + \ 'tempname: 0x555555e380a0 "/home/mool/.viminfz.tmp"')) + call assert_equal([ + \ 'one two three four one two three four one two thre', + \ 'e four', + \ ], balloon_split( + \ 'one two three four one two three four one two three four')) + + eval 'struct = {one = 1, two = 2, three = 3}' + \ ->balloon_split() + \ ->assert_equal([ + \ 'struct = {', + \ ' one = 1,', + \ ' two = 2,', + \ ' three = 3}', + \ ]) + + call assert_equal([ + \ 'struct = {', + \ ' one = 1,', + \ ' nested = {', + \ ' n1 = "yes",', + \ ' n2 = "no"}', + \ ' two = 2}', + \ ], balloon_split( + \ 'struct = {one = 1, nested = {n1 = "yes", n2 = "no"} two = 2}')) + call assert_equal([ + \ 'struct = 0x234 {', + \ ' long = 2343 "\\"some long string that will be wr', + \ 'apped in two\\"",', + \ ' next = 123}', + \ ], balloon_split( + \ 'struct = 0x234 {long = 2343 "\\"some long string that will be wrapped in two\\"", next = 123}')) + call assert_equal([ + \ 'Some comment', + \ '', + \ 'typedef this that;', + \ ], balloon_split( + \ "Some comment\n\ntypedef this that;")) +endfunc + func Test_popup_position() if !CanRunVimInTerminal() return diff --git a/src/nvim/testdir/test_put.vim b/src/nvim/testdir/test_put.vim index 884ada7e88..15745d5619 100644 --- a/src/nvim/testdir/test_put.vim +++ b/src/nvim/testdir/test_put.vim @@ -22,12 +22,21 @@ endfunc func Test_put_char_block2() new - let a = [ getreg('a'), getregtype('a') ] call setreg('a', ' one ', 'v') call setline(1, ['Line 1', '', 'Line 3', '']) " visually select the first 3 lines and put register a over it exe "norm! ggl\<c-v>2j2l\"ap" - call assert_equal(['L one 1', '', 'L one 3', ''], getline(1,4)) + call assert_equal(['L one 1', '', 'L one 3', ''], getline(1, 4)) + " clean up + bw! +endfunc + +func Test_put_lines() + new + let a = [ getreg('a'), getregtype('a') ] + call setline(1, ['Line 1', 'Line2', 'Line 3', '']) + exe 'norm! gg"add"AddG""p' + call assert_equal(['Line 3', '', 'Line 1', 'Line2'], getline(1, '$')) " clean up bw! call setreg('a', a[0], a[1]) @@ -42,21 +51,10 @@ func Test_put_expr() exec "4norm! \"=\<cr>P" norm! j0. norm! j0. - call assert_equal(['A1','A2','A3','4A','5A','6A'], getline(1,'$')) + call assert_equal(['A1','A2','A3','4A','5A','6A'], getline(1, '$')) bw! endfunc -func Test_put_lines() - new - let a = [ getreg('a'), getregtype('a') ] - call setline(1, ['Line 1', 'Line2', 'Line 3', '']) - exe 'norm! gg"add"AddG""p' - call assert_equal(['Line 3', '', 'Line 1', 'Line2'], getline(1,'$')) - " clean up - bw! - call setreg('a', a[0], a[1]) -endfunc - func Test_put_fails_when_nomodifiable() new setlocal nomodifiable diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index 35555ca9d3..049163890e 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -1,8 +1,7 @@ " Test for the quickfix commands. -if !has('quickfix') - finish -endif +source check.vim +CheckFeature quickfix set encoding=utf-8 @@ -95,7 +94,7 @@ func XlistTests(cchar) " Populate the list and then try Xgetexpr ['non-error 1', 'Xtestfile1:1:3:Line1', \ 'non-error 2', 'Xtestfile2:2:2:Line2', - \ 'non-error 3', 'Xtestfile3:3:1:Line3'] + \ 'non-error| 3', 'Xtestfile3:3:1:Line3'] " List only valid entries let l = split(execute('Xlist', ''), "\n") @@ -107,7 +106,7 @@ func XlistTests(cchar) let l = split(execute('Xlist!', ''), "\n") call assert_equal([' 1: non-error 1', ' 2 Xtestfile1:1 col 3: Line1', \ ' 3: non-error 2', ' 4 Xtestfile2:2 col 2: Line2', - \ ' 5: non-error 3', ' 6 Xtestfile3:3 col 1: Line3'], l) + \ ' 5: non-error| 3', ' 6 Xtestfile3:3 col 1: Line3'], l) " List a range of errors let l = split(execute('Xlist 3,6', ''), "\n") @@ -507,7 +506,7 @@ func Xtest_browse(cchar) Xexpr "" call assert_equal(0, g:Xgetlist({'idx' : 0}).idx) call assert_equal(0, g:Xgetlist({'size' : 0}).size) - Xaddexpr ['foo', 'bar', 'baz', 'quux', 'shmoo'] + Xaddexpr ['foo', 'bar', 'baz', 'quux', 'sh|moo'] call assert_equal(5, g:Xgetlist({'size' : 0}).size) Xlast call assert_equal(5, g:Xgetlist({'idx' : 0}).idx) @@ -554,6 +553,33 @@ func s:test_xhelpgrep(cchar) " This wipes out the buffer, make sure that doesn't cause trouble. Xclose + " When the current window is vertically split, jumping to a help match + " should open the help window at the top. + only | enew + let w1 = win_getid() + vert new + let w2 = win_getid() + Xnext + let w3 = win_getid() + call assert_true(&buftype == 'help') + call assert_true(winnr() == 1) + " See jump_to_help_window() for details + let w2_width = winwidth(w2) + if w2_width != &columns && w2_width < 80 + call assert_equal(['col', [['leaf', w3], + \ ['row', [['leaf', w2], ['leaf', w1]]]]], winlayout()) + else + call assert_equal(['row', [['col', [['leaf', w3], ['leaf', w2]]], + \ ['leaf', w1]]] , winlayout()) + endif + + new | only + set buftype=help + set modified + call assert_fails('Xnext', 'E37:') + set nomodified + new | only + if a:cchar == 'l' " When a help window is present, running :lhelpgrep should reuse the " help window and not the current window @@ -1257,6 +1283,30 @@ func Test_quickfix_was_changed_by_autocmd() call XquickfixChangedByAutocmd('l') endfunc +func Test_setloclist_in_autocommand() + call writefile(['test1', 'test2'], 'Xfile') + edit Xfile + let s:bufnr = bufnr() + call setloclist(1, + \ [{'bufnr' : s:bufnr, 'lnum' : 1, 'text' : 'test1'}, + \ {'bufnr' : s:bufnr, 'lnum' : 2, 'text' : 'test2'}]) + + augroup Test_LocList + au! + autocmd BufEnter * call setloclist(1, + \ [{'bufnr' : s:bufnr, 'lnum' : 1, 'text' : 'test1'}, + \ {'bufnr' : s:bufnr, 'lnum' : 2, 'text' : 'test2'}], 'r') + augroup END + + lopen + call assert_fails('exe "normal j\<CR>"', 'E926:') + + augroup Test_LocList + au! + augroup END + call delete('Xfile') +endfunc + func Test_caddbuffer_to_empty() helpgr quickfix call setqflist([], 'r') @@ -1570,6 +1620,24 @@ func Test_long_lines() call s:long_lines_tests('l') endfunc +func Test_cgetfile_on_long_lines() + " Problematic values if the line is longer than 4096 bytes. Then 1024 bytes + " are read at a time. + for len in [4078, 4079, 4080, 5102, 5103, 5104, 6126, 6127, 6128, 7150, 7151, 7152] + let lines = [ + \ '/tmp/file1:1:1:aaa', + \ '/tmp/file2:1:1:%s', + \ '/tmp/file3:1:1:bbb', + \ '/tmp/file4:1:1:ccc', + \ ] + let lines[1] = substitute(lines[1], '%s', repeat('x', len), '') + call writefile(lines, 'Xcqetfile.txt') + cgetfile Xcqetfile.txt + call assert_equal(4, getqflist(#{size: v:true}).size, 'with length ' .. len) + endfor + call delete('Xcqetfile.txt') +endfunc + func s:create_test_file(filename) let l = [] for i in range(1, 20) @@ -1847,6 +1915,13 @@ func HistoryTest(cchar) call g:Xsetlist([], 'f') let l = split(execute(a:cchar . 'hist'), "\n") call assert_equal('No entries', l[0]) + + " An empty list should still show the stack history + call g:Xsetlist([]) + let res = split(execute(a:cchar . 'hist'), "\n") + call assert_equal('> error list 1 of 1; 0 ' . common, res[0]) + + call g:Xsetlist([], 'f') endfunc func Test_history() @@ -2097,6 +2172,9 @@ func Xproperty_tests(cchar) call assert_equal(['Colors'], newl2.context) call assert_equal('Line10', newl2.items[0].text) call g:Xsetlist([], 'f') + + " Cannot specify both a non-empty list argument and a dict argument + call assert_fails("call g:Xsetlist([{}], ' ', {})", 'E475:') endfunc func Test_qf_property() @@ -2104,6 +2182,56 @@ func Test_qf_property() call Xproperty_tests('l') endfunc +" Test for setting the current index in the location/quickfix list +func Xtest_setqfidx(cchar) + call s:setup_commands(a:cchar) + + Xgetexpr "F1:10:1:Line1\nF2:20:2:Line2\nF3:30:3:Line3" + Xgetexpr "F4:10:1:Line1\nF5:20:2:Line2\nF6:30:3:Line3" + Xgetexpr "F7:10:1:Line1\nF8:20:2:Line2\nF9:30:3:Line3" + + call g:Xsetlist([], 'a', {'nr' : 3, 'idx' : 2}) + call g:Xsetlist([], 'a', {'nr' : 2, 'idx' : 2}) + call g:Xsetlist([], 'a', {'nr' : 1, 'idx' : 3}) + Xolder 2 + Xopen + call assert_equal(3, line('.')) + Xnewer + call assert_equal(2, line('.')) + Xnewer + call assert_equal(2, line('.')) + " Update the current index with the quickfix window open + wincmd w + call g:Xsetlist([], 'a', {'nr' : 3, 'idx' : 3}) + Xopen + call assert_equal(3, line('.')) + Xclose + + " Set the current index to the last entry + call g:Xsetlist([], 'a', {'nr' : 1, 'idx' : '$'}) + call assert_equal(3, g:Xgetlist({'nr' : 1, 'idx' : 0}).idx) + " A large value should set the index to the last index + call g:Xsetlist([], 'a', {'nr' : 1, 'idx' : 1}) + call g:Xsetlist([], 'a', {'nr' : 1, 'idx' : 999}) + call assert_equal(3, g:Xgetlist({'nr' : 1, 'idx' : 0}).idx) + " Invalid index values + call g:Xsetlist([], 'a', {'nr' : 1, 'idx' : -1}) + call assert_equal(3, g:Xgetlist({'nr' : 1, 'idx' : 0}).idx) + call g:Xsetlist([], 'a', {'nr' : 1, 'idx' : 0}) + call assert_equal(3, g:Xgetlist({'nr' : 1, 'idx' : 0}).idx) + call g:Xsetlist([], 'a', {'nr' : 1, 'idx' : 'xx'}) + call assert_equal(3, g:Xgetlist({'nr' : 1, 'idx' : 0}).idx) + call assert_fails("call g:Xsetlist([], 'a', {'nr':1, 'idx':[]})", 'E745:') + + call g:Xsetlist([], 'f') + new | only +endfunc + +func Test_setqfidx() + call Xtest_setqfidx('c') + call Xtest_setqfidx('l') +endfunc + " Tests for the QuickFixCmdPre/QuickFixCmdPost autocommands func QfAutoCmdHandler(loc, cmd) call add(g:acmds, a:loc . a:cmd) @@ -2423,6 +2551,57 @@ func Test_vimgrep() call XvimgrepTests('l') endfunc +" Test for incsearch highlighting of the :vimgrep pattern +" This test used to cause "E315: ml_get: invalid lnum" errors. +func Test_vimgrep_incsearch() + throw 'skipped: Nvim does not support test_override()' + enew + set incsearch + call test_override("char_avail", 1) + + call feedkeys(":2vimgrep assert test_quickfix.vim test_cdo.vim\<CR>", "ntx") + let l = getqflist() + call assert_equal(2, len(l)) + + call test_override("ALL", 0) + set noincsearch +endfunc + +" Test vimgrep without swap file +func Test_vimgrep_without_swap_file() + let lines =<< trim [SCRIPT] + vimgrep grep test_c* + call writefile(['done'], 'Xresult') + qall! + [SCRIPT] + call writefile(lines, 'Xscript') + if RunVim([], [], '--clean -n -S Xscript Xscript') + call assert_equal(['done'], readfile('Xresult')) + endif + call delete('Xscript') + call delete('Xresult') +endfunc + +func Test_vimgrep_existing_swapfile() + call writefile(['match apple with apple'], 'Xapple') + call writefile(['swapfile'], '.Xapple.swp') + let g:foundSwap = 0 + let g:ignoreSwapExists = 1 + augroup grep + au SwapExists * let foundSwap = 1 | let v:swapchoice = 'e' + augroup END + vimgrep apple Xapple + call assert_equal(1, g:foundSwap) + call assert_match('.Xapple.swo', swapname('')) + + call delete('Xapple') + call delete('Xapple.swp') + augroup grep + au! SwapExists + augroup END + unlet g:ignoreSwapExists +endfunc + func XfreeTests(cchar) call s:setup_commands(a:cchar) @@ -3315,6 +3494,17 @@ func Test_lvimgrep_crash() augroup QF_Test au! augroup END + + new | only + augroup QF_Test + au! + au BufEnter * call setloclist(0, [], 'r') + augroup END + call assert_fails('lvimgrep Test_lvimgrep_crash *', 'E926:') + augroup QF_Test + au! + augroup END + enew | only endfunc @@ -3405,6 +3595,41 @@ func Test_lhelpgrep_autocmd() call assert_equal('help', &filetype) call assert_equal(1, getloclist(0, {'nr' : '$'}).nr) au! QuickFixCmdPost + + new | only + augroup QF_Test + au! + au BufEnter * call setqflist([], 'f') + augroup END + call assert_fails('helpgrep quickfix', 'E925:') + " run the test with a help window already open + help + wincmd w + call assert_fails('helpgrep quickfix', 'E925:') + augroup QF_Test + au! BufEnter + augroup END + + new | only + augroup QF_Test + au! + au BufEnter * call setqflist([], 'r') + augroup END + call assert_fails('helpgrep quickfix', 'E925:') + augroup QF_Test + au! BufEnter + augroup END + + new | only + augroup QF_Test + au! + au BufEnter * call setloclist(0, [], 'r') + augroup END + call assert_fails('lhelpgrep quickfix', 'E926:') + augroup QF_Test + au! BufEnter + augroup END + new | only endfunc @@ -3430,6 +3655,18 @@ func Test_shorten_fname() " Displaying the quickfix list should simplify the file path silent! clist call assert_equal('test_quickfix.vim', bufname('test_quickfix.vim')) + " Add a few entries for the same file with different paths and check whether + " the buffer name is shortened + %bwipe + call setqflist([], 'f') + call setqflist([{'filename' : 'test_quickfix.vim', 'lnum' : 10}, + \ {'filename' : '../testdir/test_quickfix.vim', 'lnum' : 20}, + \ {'filename' : fname, 'lnum' : 30}], ' ') + copen + call assert_equal(['test_quickfix.vim|10| ', + \ 'test_quickfix.vim|20| ', + \ 'test_quickfix.vim|30| '], getline(1, '$')) + cclose endfunc " Quickfix title tests @@ -3730,6 +3967,52 @@ func Test_curswant() cclose | helpclose endfunc +" Test for opening a file from the quickfix window using CTRL-W <Enter> +" doesn't leave an empty buffer around. +func Test_splitview() + call s:create_test_file('Xtestfile1') + call s:create_test_file('Xtestfile2') + new | only + let last_bufnr = bufnr('Test_sv_1', 1) + let l = ['Xtestfile1:2:Line2', 'Xtestfile2:4:Line4'] + cgetexpr l + copen + let numbufs = len(getbufinfo()) + exe "normal \<C-W>\<CR>" + copen + exe "normal j\<C-W>\<CR>" + " Make sure new empty buffers are not created + call assert_equal(numbufs, len(getbufinfo())) + " Creating a new buffer should use the next available buffer number + call assert_equal(last_bufnr + 4, bufnr("Test_sv_2", 1)) + bwipe Test_sv_1 + bwipe Test_sv_2 + new | only + + " When split opening files from location list window, make sure that two + " windows doesn't refer to the same location list + lgetexpr l + let locid = getloclist(0, {'id' : 0}).id + lopen + exe "normal \<C-W>\<CR>" + call assert_notequal(locid, getloclist(0, {'id' : 0}).id) + call assert_equal(0, getloclist(0, {'winid' : 0}).winid) + new | only + + " When split opening files from a helpgrep location list window, a new help + " window should be opend with a copy of the location list. + lhelpgrep window + let locid = getloclist(0, {'id' : 0}).id + lwindow + exe "normal j\<C-W>\<CR>" + call assert_notequal(locid, getloclist(0, {'id' : 0}).id) + call assert_equal(0, getloclist(0, {'winid' : 0}).winid) + new | only + + call delete('Xtestfile1') + call delete('Xtestfile2') +endfunc + " Test for parsing entries using visual screen column func Test_viscol() enew @@ -3781,11 +4064,102 @@ func Test_viscol() cnext call assert_equal([16, 25], [col('.'), virtcol('.')]) + " Use screen column number with a multi-line error message + enew + call writefile(["ร test"], 'Xfile1') + set efm=%E===\ %f\ ===,%C%l:%v,%Z%m + cexpr ["=== Xfile1 ===", "1:3", "errormsg"] + call assert_equal('Xfile1', @%) + call assert_equal([0, 1, 4, 0], getpos('.')) + + " Repeat previous test with byte offset %c: ensure that fix to issue #7145 + " does not break this + set efm=%E===\ %f\ ===,%C%l:%c,%Z%m + cexpr ["=== Xfile1 ===", "1:3", "errormsg"] + call assert_equal('Xfile1', @%) + call assert_equal([0, 1, 3, 0], getpos('.')) + enew | only set efm& call delete('Xfile1') endfunc +" Test for the quickfix window buffer +func Xqfbuf_test(cchar) + call s:setup_commands(a:cchar) + + " Quickfix buffer should be reused across closing and opening a quickfix + " window + Xexpr "F1:10:Line10" + Xopen + let qfbnum = bufnr('') + Xclose + " Even after the quickfix window is closed, the buffer should be loaded + call assert_true(bufloaded(qfbnum)) + Xopen + " Buffer should be reused when opening the window again + call assert_equal(qfbnum, bufnr('')) + Xclose + + if a:cchar == 'l' + %bwipe + " For a location list, when both the file window and the location list + " window for the list are closed, then the buffer should be freed. + new | only + lexpr "F1:10:Line10" + let wid = win_getid() + lopen + let qfbnum = bufnr('') + call assert_match(qfbnum . ' %a- "\[Location List]"', execute('ls')) + close + " When the location list window is closed, the buffer name should not + " change to 'Quickfix List' + call assert_match(qfbnum . ' h- "\[Location List]"', execute('ls')) + call assert_true(bufloaded(qfbnum)) + + " After deleting a location list buffer using ":bdelete", opening the + " location list window should mark the buffer as a location list buffer. + exe "bdelete " . qfbnum + lopen + call assert_equal("quickfix", &buftype) + call assert_equal(1, getwininfo(win_getid(winnr()))[0].loclist) + call assert_equal(wid, getloclist(0, {'filewinid' : 0}).filewinid) + call assert_false(&swapfile) + lclose + + " When the location list is cleared for the window, the buffer should be + " removed + call setloclist(0, [], 'f') + call assert_false(bufexists(qfbnum)) + + " When the location list is freed with the location list window open, the + " location list buffer should not be lost. It should be reused when the + " location list is again populated. + lexpr "F1:10:Line10" + lopen + let wid = win_getid() + let qfbnum = bufnr('') + wincmd p + call setloclist(0, [], 'f') + lexpr "F1:10:Line10" + lopen + call assert_equal(wid, win_getid()) + call assert_equal(qfbnum, bufnr('')) + lclose + + " When the window with the location list is closed, the buffer should be + " removed + new | only + call assert_false(bufexists(qfbnum)) + endif +endfunc + +func Test_qfbuf() + throw 'skipped: enable after porting patch 8.1.0877' + call Xqfbuf_test('c') + call Xqfbuf_test('l') +endfunc + " Test to make sure that an empty quickfix buffer is not reused for loading " a normal buffer. func Test_empty_qfbuf() @@ -3994,4 +4368,46 @@ func Test_qfcmd_abort() augroup END endfunc +" Test for using a file in one of the parent directories. +func Test_search_in_dirstack() + call mkdir('Xtestdir/a/b/c', 'p') + let save_cwd = getcwd() + call writefile(["X1_L1", "X1_L2"], 'Xtestdir/Xfile1') + call writefile(["X2_L1", "X2_L2"], 'Xtestdir/a/Xfile2') + call writefile(["X3_L1", "X3_L2"], 'Xtestdir/a/b/Xfile3') + call writefile(["X4_L1", "X4_L2"], 'Xtestdir/a/b/c/Xfile4') + + let lines = "Entering dir Xtestdir\n" . + \ "Entering dir a\n" . + \ "Entering dir b\n" . + \ "Xfile2:2:X2_L2\n" . + \ "Leaving dir a\n" . + \ "Xfile1:2:X1_L2\n" . + \ "Xfile3:1:X3_L1\n" . + \ "Entering dir c\n" . + \ "Xfile4:2:X4_L2\n" . + \ "Leaving dir c\n" + set efm=%DEntering\ dir\ %f,%XLeaving\ dir\ %f,%f:%l:%m + cexpr lines .. "Leaving dir Xtestdir|\n" | let next = 1 + call assert_equal(11, getqflist({'size' : 0}).size) + call assert_equal(4, getqflist({'idx' : 0}).idx) + call assert_equal('X2_L2', getline('.')) + call assert_equal(1, next) + cnext + call assert_equal(6, getqflist({'idx' : 0}).idx) + call assert_equal('X1_L2', getline('.')) + cnext + call assert_equal(7, getqflist({'idx' : 0}).idx) + call assert_equal(1, line('$')) + call assert_equal('', getline(1)) + cnext + call assert_equal(9, getqflist({'idx' : 0}).idx) + call assert_equal(1, line('$')) + call assert_equal('', getline(1)) + + set efm& + exe 'cd ' . save_cwd + call delete('Xtestdir', 'rf') +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_regexp_utf8.vim b/src/nvim/testdir/test_regexp_utf8.vim index f48458566b..4466ad436a 100644 --- a/src/nvim/testdir/test_regexp_utf8.vim +++ b/src/nvim/testdir/test_regexp_utf8.vim @@ -32,6 +32,9 @@ func Test_equivalence_re2() endfunc func s:classes_test() + if has('win32') + set iskeyword=@,48-57,_,192-255 + endif set isprint=@,161-255 call assert_equal('Motรถrhead', matchstr('Motรถrhead', '[[:print:]]\+')) @@ -51,6 +54,12 @@ func s:classes_test() let tabchar = '' let upperchars = '' let xdigitchars = '' + let identchars = '' + let identchars1 = '' + let kwordchars = '' + let kwordchars1 = '' + let fnamechars = '' + let fnamechars1 = '' let i = 1 while i <= 255 let c = nr2char(i) @@ -102,6 +111,24 @@ func s:classes_test() if c =~ '[[:xdigit:]]' let xdigitchars .= c endif + if c =~ '[[:ident:]]' + let identchars .= c + endif + if c =~ '\i' + let identchars1 .= c + endif + if c =~ '[[:keyword:]]' + let kwordchars .= c + endif + if c =~ '\k' + let kwordchars1 .= c + endif + if c =~ '[[:fname:]]' + let fnamechars .= c + endif + if c =~ '\f' + let fnamechars1 .= c + endif let i += 1 endwhile @@ -121,6 +148,37 @@ func s:classes_test() call assert_equal("\t\n\x0b\f\r ", spacechars) call assert_equal("\t", tabchar) call assert_equal('0123456789ABCDEFabcdef', xdigitchars) + + if has('win32') + let identchars_ok = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyzยยยยยย
ยยยยยยยยยยยยยยยยยยยยยยยยยยย ยกยขยฃยคยฅยฆยงยตรรรรรร
รรรรรรรรรรรรรรรรรรรรรรรรรร รกรขรฃรครฅรฆรงรจรฉรชรซรฌรญรฎรฏรฐรฑรฒรณรดรตรถรธรนรบรปรผรฝรพรฟ' + let kwordchars_ok = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyzยตรรรรรร
รรรรรรรรรรรรรรรรรรรรรรรรรรร รกรขรฃรครฅรฆรงรจรฉรชรซรฌรญรฎรฏรฐรฑรฒรณรดรตรถรทรธรนรบรปรผรฝรพรฟ' + elseif has('ebcdic') + let identchars_ok = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyzยยยยยยยฌยฎยตยบยฟรรรรรร
รรรรรรรรรรรรรรรรรรรรรรรรรร รกรขรฃรครฅรฆรงรจรฉรชรซรฌรญรฎรฏรฐรฑรฒรณรดรตรถรทรธรนรบรปรผรฝรพรฟ' + let kwordchars_ok = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyzยยยยยยยฌยฎยตยบยฟรรรรรร
รรรรรรรรรรรรรรรรรรรรรรรรรร รกรขรฃรครฅรฆรงรจรฉรชรซรฌรญรฎรฏรฐรฑรฒรณรดรตรถรทรธรนรบรปรผรฝรพรฟ' + else + let identchars_ok = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyzยตรรรรรร
รรรรรรรรรรรรรรรรรรรรรรรรรรร รกรขรฃรครฅรฆรงรจรฉรชรซรฌรญรฎรฏรฐรฑรฒรณรดรตรถรทรธรนรบรปรผรฝรพรฟ' + let kwordchars_ok = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyzยตรรรรรร
รรรรรรรรรรรรรรรรรรรรรรรรรรร รกรขรฃรครฅรฆรงรจรฉรชรซรฌรญรฎรฏรฐรฑรฒรณรดรตรถรทรธรนรบรปรผรฝรพรฟ' + endif + + if has('win32') + let fnamechars_ok = '!#$%+,-./0123456789:=@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]_abcdefghijklmnopqrstuvwxyz{}~ย ยกยขยฃยคยฅยฆยงยจยฉยชยซยฌยญยฎยฏยฐยฑยฒยณยดยตยถยทยธยนยบยปยผยฝยพยฟรรรรรร
รรรรรรรรรรรรรรรรรรรรรรรรรรร รกรขรฃรครฅรฆรงรจรฉรชรซรฌรญรฎรฏรฐรฑรฒรณรดรตรถรทรธรนรบรปรผรฝรพรฟ' + elseif has('amiga') + let fnamechars_ok = '$+,-./0123456789:ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~ย ยกยขยฃยคยฅยฆยงยจยฉยชยซยฌยญยฎยฏยฐยฑยฒยณยดยตยถยทยธยนยบยปยผยฝยพยฟรรรรรร
รรรรรรรรรรรรรรรรรรรรรรรรรรร รกรขรฃรครฅรฆรงรจรฉรชรซรฌรญรฎรฏรฐรฑรฒรณรดรตรถรทรธรนรบรปรผรฝรพรฟ' + elseif has('vms') + let fnamechars_ok = '#$%+,-./0123456789:;<>ABCDEFGHIJKLMNOPQRSTUVWXYZ[]_abcdefghijklmnopqrstuvwxyz~ย ยกยขยฃยคยฅยฆยงยจยฉยชยซยฌยญยฎยฏยฐยฑยฒยณยดยตยถยทยธยนยบยปยผยฝยพยฟรรรรรร
รรรรรรรรรรรรรรรรรรรรรรรรรรร รกรขรฃรครฅรฆรงรจรฉรชรซรฌรญรฎรฏรฐรฑรฒรณรดรตรถรทรธรนรบรปรผรฝรพรฟ' + elseif has('ebcdic') + let fnamechars_ok = '#$%+,-./=ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~ย ยกยขยฃยคยฅยฆยงยจยฉยชยซยฌยญยฎยฏยฐยฑยฒยณยดยตยถยทยธยนยบยปยผยฝยพยฟรรรรรร
รรรรรรรรรรรรรรรรรรรรรรรรรรร รกรขรฃรครฅรฆรงรจรฉรชรซรฌรญรฎรฏรฐรฑรฒรณรดรตรถรทรธรนรบรปรผรฝรพรฟ' + else + let fnamechars_ok = '#$%+,-./0123456789=ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~ย ยกยขยฃยคยฅยฆยงยจยฉยชยซยฌยญยฎยฏยฐยฑยฒยณยดยตยถยทยธยนยบยปยผยฝยพยฟรรรรรร
รรรรรรรรรรรรรรรรรรรรรรรรรรร รกรขรฃรครฅรฆรงรจรฉรชรซรฌรญรฎรฏรฐรฑรฒรณรดรตรถรทรธรนรบรปรผรฝรพรฟ' + endif + + call assert_equal(identchars_ok, identchars) + call assert_equal(kwordchars_ok, kwordchars) + call assert_equal(fnamechars_ok, fnamechars) + + call assert_equal(identchars1, identchars) + call assert_equal(kwordchars1, kwordchars) + call assert_equal(fnamechars1, fnamechars) endfunc func Test_classes_re1() @@ -351,4 +409,128 @@ func Test_regexp_ignore_case() set regexpengine& endfunc +" Tests for regexp with multi-byte encoding and various magic settings +func Run_regexp_multibyte_magic() + let text =<< trim END + 1 a aa abb abbccc + 2 d dd dee deefff + 3 g gg ghh ghhiii + 4 j jj jkk jkklll + 5 m mm mnn mnnooo + 6 x ^aa$ x + 7 (a)(b) abbaa + 8 axx [ab]xx + 9 เธซเธกเนx เธญเธกx + a เธญเธกx เธซเธกเนx + b ใกใซใจใฏ + c x ยฌโฌx + d ๅคฉไฝฟx + e
ธy + f ฏz + g aๅทbb + j 0123โคx + k combinations + l รครถ รผฮฑฬฬฬ + END + + new + call setline(1, text) + exe 'normal /a*b\{2}c\+/e' .. "\<CR>x" + call assert_equal('1 a aa abb abbcc', getline('.')) + exe 'normal /\Md\*e\{2}f\+/e' .. "\<CR>x" + call assert_equal('2 d dd dee deeff', getline('.')) + set nomagic + exe 'normal /g\*h\{2}i\+/e' .. "\<CR>x" + call assert_equal('3 g gg ghh ghhii', getline('.')) + exe 'normal /\mj*k\{2}l\+/e' .. "\<CR>x" + call assert_equal('4 j jj jkk jkkll', getline('.')) + exe 'normal /\vm*n{2}o+/e' .. "\<CR>x" + call assert_equal('5 m mm mnn mnnoo', getline('.')) + exe 'normal /\V^aa$/' .. "\<CR>x" + call assert_equal('6 x aa$ x', getline('.')) + set magic + exe 'normal /\v(a)(b)\2\1\1/e' .. "\<CR>x" + call assert_equal('7 (a)(b) abba', getline('.')) + exe 'normal /\V[ab]\(\[xy]\)\1' .. "\<CR>x" + call assert_equal('8 axx ab]xx', getline('.')) + + " search for multi-byte without composing char + exe 'normal /เธก' .. "\<CR>x" + call assert_equal('9 เธซเธกเนx เธญx', getline('.')) + + " search for multi-byte with composing char + exe 'normal /เธกเน' .. "\<CR>x" + call assert_equal('a เธญเธกx เธซx', getline('.')) + + " find word by change of word class + exe 'normal /ใก\<ใซใจ\>ใฏ' .. "\<CR>x" + call assert_equal('b ใซใจใฏ', getline('.')) + + " Test \%u, [\u] and friends + " c + exe 'normal /\%u20ac' .. "\<CR>x" + call assert_equal('c x ยฌx', getline('.')) + " d + exe 'normal /[\u4f7f\u5929]\+' .. "\<CR>x" + call assert_equal('d ไฝฟx', getline('.')) + " e + exe 'normal /\%U12345678' .. "\<CR>x" + call assert_equal('e y', getline('.')) + " f + exe 'normal /[\U1234abcd\u1234\uabcd]' .. "\<CR>x" + call assert_equal('f z', getline('.')) + " g + exe 'normal /\%d21879b' .. "\<CR>x" + call assert_equal('g abb', getline('.')) + + " j Test backwards search from a multi-byte char + exe "normal /x\<CR>x?.\<CR>x" + call assert_equal('j 012โค', getline('.')) + " k + let @w=':%s#comb[i]nations#ลฬแนฃฬmฬฅฬฮฑฬฬฬ#g' + @w + call assert_equal('k ลฬแนฃฬmฬฅฬฮฑฬฬฬ', getline(18)) + + close! +endfunc + +func Test_regexp_multibyte_magic() + set regexpengine=1 + call Run_regexp_multibyte_magic() + set regexpengine=2 + call Run_regexp_multibyte_magic() + set regexpengine& +endfunc + +" Test for 7.3.192 +" command ":s/ \?/ /g" splits multi-byte characters into bytes +func Test_split_multibyte_to_bytes() + new + call setline(1, 'l รครถ รผฮฑฬฬฬ') + s/ \?/ /g + call assert_equal(' l รค รถ รผ ฮฑฬฬฬ', getline(1)) + close! +endfunc + +" Test for matchstr() with multibyte characters +func Test_matchstr_multibyte() + new + call assert_equal('ื', matchstr("ืืืื", ".", 0, 2)) + call assert_equal('ืื', matchstr("ืืืื", "..", 0, 2)) + call assert_equal('ื', matchstr("ืืืื", ".", 0, 0)) + call assert_equal('ื', matchstr("ืืืื", ".", 4, -1)) + close! +endfunc + +" Test for 7.4.636 +" A search with end offset gets stuck at end of file. +func Test_search_with_end_offset() + new + call setline(1, ['', 'dog(a', 'cat(']) + exe "normal /(/e+" .. "\<CR>" + normal "ayn + call assert_equal("a\ncat(", @a) + close! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_registers.vim b/src/nvim/testdir/test_registers.vim index d20f8d1eef..19a7c6c9d0 100644 --- a/src/nvim/testdir/test_registers.vim +++ b/src/nvim/testdir/test_registers.vim @@ -167,6 +167,75 @@ func Test_set_register() enew! endfunc +func Test_v_register() + enew + call setline(1, 'nothing') + + func s:Put() + let s:register = v:register + exec 'normal! "' .. v:register .. 'P' + endfunc + nnoremap <buffer> <plug>(test) :<c-u>call s:Put()<cr> + nmap <buffer> S <plug>(test) + + let @z = "testz\n" + let @" = "test@\n" + + let s:register = '' + call feedkeys('"_ddS', 'mx') + call assert_equal('test@', getline('.')) " fails before 8.2.0929 + call assert_equal('"', s:register) " fails before 8.2.0929 + + let s:register = '' + call feedkeys('"zS', 'mx') + call assert_equal('z', s:register) + + let s:register = '' + call feedkeys('"zSS', 'mx') + call assert_equal('"', s:register) + + let s:register = '' + call feedkeys('"_S', 'mx') + call assert_equal('_', s:register) + + let s:register = '' + normal "_ddS + call assert_equal('"', s:register) " fails before 8.2.0929 + call assert_equal('test@', getline('.')) " fails before 8.2.0929 + + let s:register = '' + execute 'normal "z:call' "s:Put()\n" + call assert_equal('z', s:register) + call assert_equal('testz', getline('.')) + + " Test operator and omap + let @b = 'testb' + func s:OpFunc(...) + let s:register2 = v:register + endfunc + set opfunc=s:OpFunc + + normal "bg@l + normal S + call assert_equal('"', s:register) " fails before 8.2.0929 + call assert_equal('b', s:register2) + + func s:Motion() + let s:register1 = v:register + normal! l + endfunc + onoremap <buffer> Q :<c-u>call s:Motion()<cr> + + normal "bg@Q + normal S + call assert_equal('"', s:register) + call assert_equal('b', s:register1) + call assert_equal('"', s:register2) + + set opfunc& + bwipe! +endfunc + func Test_ve_blockpaste() new set ve=all diff --git a/src/nvim/testdir/test_restricted.vim b/src/nvim/testdir/test_restricted.vim deleted file mode 100644 index a29f7c33d3..0000000000 --- a/src/nvim/testdir/test_restricted.vim +++ /dev/null @@ -1,103 +0,0 @@ -" Test for "rvim" or "vim -Z" - -source shared.vim - -"if has('win32') && has('gui') -" " Win32 GUI shows a dialog instead of displaying the error in the last line. -" finish -"endif - -func Test_restricted() - call Run_restricted_test('!ls', 'E145:') -endfunc - -func Run_restricted_test(ex_cmd, error) - let cmd = GetVimCommand('Xrestricted') - if cmd == '' - return - endif - - " Use a VimEnter autocommand to avoid that the error message is displayed in - " a dialog with an OK button. - call writefile([ - \ "func Init()", - \ " silent! " . a:ex_cmd, - \ " call writefile([v:errmsg], 'Xrestrout')", - \ " qa!", - \ "endfunc", - \ "au VimEnter * call Init()", - \ ], 'Xrestricted') - call system(cmd . ' -Z') - call assert_match(a:error, join(readfile('Xrestrout'))) - - call delete('Xrestricted') - call delete('Xrestrout') -endfunc - -func Test_restricted_lua() - if !has('lua') - throw 'Skipped: Lua is not supported' - endif - call Run_restricted_test('lua print("Hello, Vim!")', 'E981:') - call Run_restricted_test('luado return "hello"', 'E981:') - call Run_restricted_test('luafile somefile', 'E981:') - call Run_restricted_test('call luaeval("expression")', 'E145:') -endfunc - -func Test_restricted_mzscheme() - if !has('mzscheme') - throw 'Skipped: MzScheme is not supported' - endif - call Run_restricted_test('mzscheme statement', 'E981:') - call Run_restricted_test('mzfile somefile', 'E981:') - call Run_restricted_test('call mzeval("expression")', 'E145:') -endfunc - -func Test_restricted_perl() - if !has('perl') - throw 'Skipped: Perl is not supported' - endif - " TODO: how to make Safe mode fail? - " call Run_restricted_test('perl system("ls")', 'E981:') - " call Run_restricted_test('perldo system("hello")', 'E981:') - " call Run_restricted_test('perlfile somefile', 'E981:') - " call Run_restricted_test('call perleval("system(\"ls\")")', 'E145:') -endfunc - -func Test_restricted_python() - if !has('python') - throw 'Skipped: Python is not supported' - endif - call Run_restricted_test('python print "hello"', 'E981:') - call Run_restricted_test('pydo return "hello"', 'E981:') - call Run_restricted_test('pyfile somefile', 'E981:') - call Run_restricted_test('call pyeval("expression")', 'E145:') -endfunc - -func Test_restricted_python3() - if !has('python3') - throw 'Skipped: Python3 is not supported' - endif - call Run_restricted_test('py3 print "hello"', 'E981:') - call Run_restricted_test('py3do return "hello"', 'E981:') - call Run_restricted_test('py3file somefile', 'E981:') - call Run_restricted_test('call py3eval("expression")', 'E145:') -endfunc - -func Test_restricted_ruby() - if !has('ruby') - throw 'Skipped: Ruby is not supported' - endif - call Run_restricted_test('ruby print "Hello"', 'E981:') - call Run_restricted_test('rubydo print "Hello"', 'E981:') - call Run_restricted_test('rubyfile somefile', 'E981:') -endfunc - -func Test_restricted_tcl() - if !has('tcl') - throw 'Skipped: Tcl is not supported' - endif - call Run_restricted_test('tcl puts "Hello"', 'E981:') - call Run_restricted_test('tcldo puts "Hello"', 'E981:') - call Run_restricted_test('tclfile somefile', 'E981:') -endfunc diff --git a/src/nvim/testdir/test_ruby.vim b/src/nvim/testdir/test_ruby.vim index 64199570a9..9c74c35049 100644 --- a/src/nvim/testdir/test_ruby.vim +++ b/src/nvim/testdir/test_ruby.vim @@ -1,8 +1,7 @@ " Tests for ruby interface -if !has('ruby') - finish -end +source check.vim +CheckFeature ruby func Test_ruby_change_buffer() call setline(line('$'), ['1 line 1']) @@ -11,37 +10,6 @@ func Test_ruby_change_buffer() call assert_equal('1 changed line 1', getline('$')) endfunc -func Test_ruby_evaluate_list() - throw 'skipped: TODO: ' - call setline(line('$'), ['2 line 2']) - ruby Vim.command("normal /^2\n") - let l = ["abc", "def"] - ruby << EOF - curline = $curbuf.line_number - l = Vim.evaluate("l"); - $curbuf.append(curline, l.join("\n")) -EOF - normal j - .rubydo $_ = $_.gsub(/\n/, '/') - call assert_equal('abc/def', getline('$')) -endfunc - -func Test_ruby_evaluate_dict() - let d = {'a': 'foo', 'b': 123} - redir => l:out - ruby d = Vim.evaluate("d"); print d - redir END - call assert_equal(['{"a"=>"foo", "b"=>123}'], split(l:out, "\n")) -endfunc - -func Test_ruby_evaluate_special_var() - let l = [v:true, v:false, v:null] - redir => l:out - ruby d = Vim.evaluate("l"); print d - redir END - call assert_equal(['[true, false, nil]'], split(l:out, "\n")) -endfunc - func Test_rubydo() throw 'skipped: TODO: ' " Check deleting lines does not trigger ml_get error. @@ -56,8 +24,7 @@ func Test_rubydo() call setline(1, ['one', 'two', 'three']) rubydo Vim.command("new") call assert_equal(wincount + 1, winnr('$')) - bwipe! - bwipe! + %bwipe! endfunc func Test_rubyfile() @@ -68,15 +35,359 @@ func Test_rubyfile() call delete(tempfile) endfunc -func Test_set_cursor() +func Test_ruby_set_cursor() " Check that setting the cursor position works. new call setline(1, ['first line', 'second line']) normal gg rubydo $curwin.cursor = [1, 5] call assert_equal([1, 6], [line('.'), col('.')]) + call assert_equal([1, 5], rubyeval('$curwin.cursor')) " Check that movement after setting cursor position keeps current column. normal j call assert_equal([2, 6], [line('.'), col('.')]) + call assert_equal([2, 5], rubyeval('$curwin.cursor')) + + " call assert_fails('ruby $curwin.cursor = [1]', + " \ 'ArgumentError: array length must be 2') + bwipe! +endfunc + +" Test buffer.count and buffer.length (number of lines in buffer) +func Test_ruby_buffer_count() + new + call setline(1, ['one', 'two', 'three']) + call assert_equal(3, rubyeval('$curbuf.count')) + call assert_equal(3, rubyeval('$curbuf.length')) + bwipe! +endfunc + +" Test buffer.name (buffer name) +func Test_ruby_buffer_name() + new Xfoo + call assert_equal(expand('%:p'), rubyeval('$curbuf.name')) + bwipe + call assert_equal('', rubyeval('$curbuf.name')) +endfunc + +" Test buffer.number (number of the buffer). +func Test_ruby_buffer_number() + new + call assert_equal(bufnr('%'), rubyeval('$curbuf.number')) + new + call assert_equal(bufnr('%'), rubyeval('$curbuf.number')) + + %bwipe +endfunc + +" Test buffer.delete({n}) (delete line {n}) +func Test_ruby_buffer_delete() + new + call setline(1, ['one', 'two', 'three']) + ruby $curbuf.delete(2) + call assert_equal(['one', 'three'], getline(1, '$')) + + " call assert_fails('ruby $curbuf.delete(0)', 'IndexError: line number 0 out of range') + " call assert_fails('ruby $curbuf.delete(3)', 'IndexError: line number 3 out of range') + call assert_fails('ruby $curbuf.delete(3)', 'RuntimeError: Index out of bounds') + + bwipe! +endfunc + +" Test buffer.append({str}, str) (append line {str} after line {n}) +func Test_ruby_buffer_append() + new + ruby $curbuf.append(0, 'one') + ruby $curbuf.append(1, 'three') + ruby $curbuf.append(1, 'two') + ruby $curbuf.append(4, 'four') + + call assert_equal(['one', 'two', 'three', '', 'four'], getline(1, '$')) + + " call assert_fails('ruby $curbuf.append(-1, "x")', + " \ 'IndexError: line number -1 out of range') + call assert_fails('ruby $curbuf.append(-1, "x")', + \ 'ArgumentError: Index out of bounds') + call assert_fails('ruby $curbuf.append(6, "x")', + \ 'RuntimeError: Index out of bounds') + + bwipe! +endfunc + +" Test buffer.line (get or set the current line) +func Test_ruby_buffer_line() + new + call setline(1, ['one', 'two', 'three']) + 2 + call assert_equal('two', rubyeval('$curbuf.line')) + + ruby $curbuf.line = 'TWO' + call assert_equal(['one', 'TWO', 'three'], getline(1, '$')) + + bwipe! +endfunc + +" Test buffer.line_number (get current line number) +func Test_ruby_buffer_line_number() + new + call setline(1, ['one', 'two', 'three']) + 2 + call assert_equal(2, rubyeval('$curbuf.line_number')) + + bwipe! +endfunc + +func Test_ruby_buffer_get() + new + call setline(1, ['one', 'two']) + call assert_equal('one', rubyeval('$curbuf[1]')) + call assert_equal('two', rubyeval('$curbuf[2]')) + + " call assert_fails('ruby $curbuf[0]', + " \ 'IndexError: line number 0 out of range') + call assert_fails('ruby $curbuf[3]', + \ 'RuntimeError: Index out of bounds') + + bwipe! +endfunc + +func Test_ruby_buffer_set() + new + call setline(1, ['one', 'two']) + ruby $curbuf[2] = 'TWO' + ruby $curbuf[1] = 'ONE' + + " call assert_fails('ruby $curbuf[0] = "ZERO"', + " \ 'IndexError: line number 0 out of range') + " call assert_fails('ruby $curbuf[3] = "THREE"', + " \ 'IndexError: line number 3 out of range') + call assert_fails('ruby $curbuf[3] = "THREE"', + \ 'RuntimeError: Index out of bounds') + bwipe! +endfunc + +" Test window.width (get or set window height). +func Test_ruby_window_height() + new + + " Test setting window height + ruby $curwin.height = 2 + call assert_equal(2, winheight(0)) + + " Test getting window height + call assert_equal(2, rubyeval('$curwin.height')) + + bwipe +endfunc + +" Test window.width (get or set window width). +func Test_ruby_window_width() + vnew + + " Test setting window width + ruby $curwin.width = 2 + call assert_equal(2, winwidth(0)) + + " Test getting window width + call assert_equal(2, rubyeval('$curwin.width')) + + bwipe +endfunc + +" Test window.buffer (get buffer object of a window object). +func Test_ruby_window_buffer() + new Xfoo1 + new Xfoo2 + ruby $b2 = $curwin.buffer + ruby $w2 = $curwin + wincmd j + ruby $b1 = $curwin.buffer + ruby $w1 = $curwin + + " call assert_equal(rubyeval('$b1'), rubyeval('$w1.buffer')) + " call assert_equal(rubyeval('$b2'), rubyeval('$w2.buffer')) + call assert_equal(bufnr('Xfoo1'), rubyeval('$w1.buffer.number')) + call assert_equal(bufnr('Xfoo2'), rubyeval('$w2.buffer.number')) + + ruby $b1, $w1, $b2, $w2 = nil + %bwipe +endfunc + +" Test Vim::Window.current (get current window object) +func Test_ruby_Vim_window_current() + let cw = rubyeval('$curwin.to_s') + " call assert_equal(cw, rubyeval('Vim::Window.current')) + call assert_match('^#<Neovim::Window:0x\x\+>$', cw) +endfunc + +" Test Vim::Window.count (number of windows) +func Test_ruby_Vim_window_count() + new Xfoo1 + new Xfoo2 + split + call assert_equal(4, rubyeval('Vim::Window.count')) + %bwipe + call assert_equal(1, rubyeval('Vim::Window.count')) +endfunc + +" Test Vim::Window[n] (get window object of window n) +func Test_ruby_Vim_window_get() + new Xfoo1 + new Xfoo2 + call assert_match('Xfoo2$', rubyeval('Vim::Window[0].buffer.name')) + wincmd j + call assert_match('Xfoo1$', rubyeval('Vim::Window[1].buffer.name')) + wincmd j + call assert_equal('', rubyeval('Vim::Window[2].buffer.name')) + %bwipe +endfunc + +" Test Vim::Buffer.current (return the buffer object of current buffer) +func Test_ruby_Vim_buffer_current() + let cb = rubyeval('$curbuf.to_s') + " call assert_equal(cb, rubyeval('Vim::Buffer.current')) + call assert_match('^#<Neovim::Buffer:0x\x\+>$', cb) +endfunc + +" Test Vim::Buffer:.count (return the number of buffers) +func Test_ruby_Vim_buffer_count() + new Xfoo1 + new Xfoo2 + call assert_equal(3, rubyeval('Vim::Buffer.count')) + %bwipe + call assert_equal(1, rubyeval('Vim::Buffer.count')) +endfunc + +" Test Vim::buffer[n] (return the buffer object of buffer number n) +func Test_ruby_Vim_buffer_get() + new Xfoo1 + new Xfoo2 + + " Index of Vim::Buffer[n] goes from 0 to the number of buffers. + call assert_equal('', rubyeval('Vim::Buffer[0].name')) + call assert_match('Xfoo1$', rubyeval('Vim::Buffer[1].name')) + call assert_match('Xfoo2$', rubyeval('Vim::Buffer[2].name')) + call assert_fails('ruby print Vim::Buffer[3].name', + \ "NoMethodError: undefined method `name' for nil:NilClass") + %bwipe +endfunc + +" Test Vim::command({cmd}) (execute a Ex command)) +" Test Vim::command({cmd}) +func Test_ruby_Vim_command() + new + call setline(1, ['one', 'two', 'three', 'four']) + ruby Vim::command('2,3d') + call assert_equal(['one', 'four'], getline(1, '$')) + bwipe! +endfunc + +" Test Vim::set_option (set a vim option) +func Test_ruby_Vim_set_option() + call assert_equal(0, &number) + ruby Vim::set_option('number') + call assert_equal(1, &number) + ruby Vim::set_option('nonumber') + call assert_equal(0, &number) +endfunc + +func Test_ruby_Vim_evaluate() + call assert_equal(123, rubyeval('Vim::evaluate("123")')) + " Vim::evaluate("123").class gives Integer or Fixnum depending + " on versions of Ruby. + call assert_match('^Integer\|Fixnum$', rubyeval('Vim::evaluate("123").class')) + + if has('float') + call assert_equal(1.23, rubyeval('Vim::evaluate("1.23")')) + call assert_equal('Float', rubyeval('Vim::evaluate("1.23").class')) + endif + + call assert_equal('foo', rubyeval('Vim::evaluate("\"foo\"")')) + call assert_equal('String', rubyeval('Vim::evaluate("\"foo\"").class')) + + call assert_equal([1, 2], rubyeval('Vim::evaluate("[1, 2]")')) + call assert_equal('Array', rubyeval('Vim::evaluate("[1, 2]").class')) + + call assert_equal({'1': 2}, rubyeval('Vim::evaluate("{1:2}")')) + call assert_equal('Hash', rubyeval('Vim::evaluate("{1:2}").class')) + + call assert_equal(v:null, rubyeval('Vim::evaluate("v:null")')) + call assert_equal('NilClass', rubyeval('Vim::evaluate("v:null").class')) + + " call assert_equal(v:null, rubyeval('Vim::evaluate("v:none")')) + " call assert_equal('NilClass', rubyeval('Vim::evaluate("v:none").class')) + + call assert_equal(v:true, rubyeval('Vim::evaluate("v:true")')) + call assert_equal('TrueClass', rubyeval('Vim::evaluate("v:true").class')) + call assert_equal(v:false, rubyeval('Vim::evaluate("v:false")')) + call assert_equal('FalseClass',rubyeval('Vim::evaluate("v:false").class')) +endfunc + +func Test_ruby_Vim_evaluate_list() + call setline(line('$'), ['2 line 2']) + ruby Vim.command("normal /^2\n") + let l = ["abc", "def"] + ruby << EOF + curline = $curbuf.line_number + l = Vim.evaluate("l"); + $curbuf.append(curline, l.join("|")) +EOF + normal j + .rubydo $_ = $_.gsub(/\|/, '/') + call assert_equal('abc/def', getline('$')) +endfunc + +func Test_ruby_Vim_evaluate_dict() + let d = {'a': 'foo', 'b': 123} + redir => l:out + ruby d = Vim.evaluate("d"); print d + redir END + call assert_equal(['{"a"=>"foo", "b"=>123}'], split(l:out, "\n")) +endfunc + +" Test Vim::message({msg}) (display message {msg}) +func Test_ruby_Vim_message() + throw 'skipped: TODO: ' + ruby Vim::message('A message') + let messages = split(execute('message'), "\n") + call assert_equal('A message', messages[-1]) +endfunc + +func Test_ruby_print() + func RubyPrint(expr) + return trim(execute('ruby print ' . a:expr)) + endfunc + + call assert_equal('123', RubyPrint('123')) + call assert_equal('1.23', RubyPrint('1.23')) + call assert_equal('Hello World!', RubyPrint('"Hello World!"')) + call assert_equal('[1, 2]', RubyPrint('[1, 2]')) + call assert_equal('{"k1"=>"v1", "k2"=>"v2"}', RubyPrint('({"k1" => "v1", "k2" => "v2"})')) + call assert_equal('true', RubyPrint('true')) + call assert_equal('false', RubyPrint('false')) + call assert_equal('', RubyPrint('nil')) + call assert_match('Vim', RubyPrint('Vim')) + call assert_match('Module', RubyPrint('Vim.class')) + + delfunc RubyPrint +endfunc + +func Test_ruby_p() + ruby p 'Just a test' + let messages = GetMessages() + call assert_equal('"Just a test"', messages[-1]) + + " Check return values of p method + + call assert_equal(123, rubyeval('p(123)')) + call assert_equal([1, 2, 3], rubyeval('p(1, 2, 3)')) + + " Avoid the "message maintainer" line. + let $LANG = '' + messages clear + call assert_equal(v:true, rubyeval('p() == nil')) + + let messages = GetMessages() + call assert_equal(0, len(messages)) endfunc diff --git a/src/nvim/testdir/test_search.vim b/src/nvim/testdir/test_search.vim index 8036dea29f..5db23c22a8 100644 --- a/src/nvim/testdir/test_search.vim +++ b/src/nvim/testdir/test_search.vim @@ -244,6 +244,10 @@ func Test_search_cmdline2() " go to previous match (on line 2) call feedkeys("/the\<C-G>\<C-G>\<C-G>\<C-T>\<C-T>\<C-T>\<cr>", 'tx') call assert_equal(' 2 these', getline('.')) + 1 + " go to previous match (on line 2) + call feedkeys("/the\<C-G>\<C-R>\<C-W>\<cr>", 'tx') + call assert_equal('theother', @/) " Test 2: keep the view, " after deleting a character from the search cmd @@ -255,7 +259,7 @@ func Test_search_cmdline2() call assert_equal({'lnum': 10, 'leftcol': 0, 'col': 4, 'topfill': 0, 'topline': 6, 'coladd': 0, 'skipcol': 0, 'curswant': 4}, winsaveview()) " remove all history entries - for i in range(10) + for i in range(11) call histdel('/') endfor @@ -346,25 +350,104 @@ func Test_searchc() bw! endfunc -func Test_search_cmdline3() +func Cmdline3_prep() throw 'skipped: Nvim does not support test_override()' - if !exists('+incsearch') - return - endif " need to disable char_avail, " so that expansion of commandline works call test_override("char_avail", 1) new call setline(1, [' 1', ' 2 the~e', ' 3 the theother']) set incsearch +endfunc + +func Incsearch_cleanup() + throw 'skipped: Nvim does not support test_override()' + set noincsearch + call test_override("char_avail", 0) + bw! +endfunc + +func Test_search_cmdline3() + throw 'skipped: Nvim does not support test_override()' + if !exists('+incsearch') + return + endif + call Cmdline3_prep() 1 " first match call feedkeys("/the\<c-l>\<cr>", 'tx') call assert_equal(' 2 the~e', getline('.')) - " clean up - set noincsearch - call test_override("char_avail", 0) - bw! + + call Incsearch_cleanup() +endfunc + +func Test_search_cmdline3s() + throw 'skipped: Nvim does not support test_override()' + if !exists('+incsearch') + return + endif + call Cmdline3_prep() + 1 + call feedkeys(":%s/the\<c-l>/xxx\<cr>", 'tx') + call assert_equal(' 2 xxxe', getline('.')) + undo + call feedkeys(":%subs/the\<c-l>/xxx\<cr>", 'tx') + call assert_equal(' 2 xxxe', getline('.')) + undo + call feedkeys(":%substitute/the\<c-l>/xxx\<cr>", 'tx') + call assert_equal(' 2 xxxe', getline('.')) + undo + call feedkeys(":%smagic/the.e/xxx\<cr>", 'tx') + call assert_equal(' 2 xxx', getline('.')) + undo + call assert_fails(":%snomagic/the.e/xxx\<cr>", 'E486') + " + call feedkeys(":%snomagic/the\\.e/xxx\<cr>", 'tx') + call assert_equal(' 2 xxx', getline('.')) + + call Incsearch_cleanup() +endfunc + +func Test_search_cmdline3g() + throw 'skipped: Nvim does not support test_override()' + if !exists('+incsearch') + return + endif + call Cmdline3_prep() + 1 + call feedkeys(":g/the\<c-l>/d\<cr>", 'tx') + call assert_equal(' 3 the theother', getline(2)) + undo + call feedkeys(":global/the\<c-l>/d\<cr>", 'tx') + call assert_equal(' 3 the theother', getline(2)) + undo + call feedkeys(":g!/the\<c-l>/d\<cr>", 'tx') + call assert_equal(1, line('$')) + call assert_equal(' 2 the~e', getline(1)) + undo + call feedkeys(":global!/the\<c-l>/d\<cr>", 'tx') + call assert_equal(1, line('$')) + call assert_equal(' 2 the~e', getline(1)) + + call Incsearch_cleanup() +endfunc + +func Test_search_cmdline3v() + throw 'skipped: Nvim does not support test_override()' + if !exists('+incsearch') + return + endif + call Cmdline3_prep() + 1 + call feedkeys(":v/the\<c-l>/d\<cr>", 'tx') + call assert_equal(1, line('$')) + call assert_equal(' 2 the~e', getline(1)) + undo + call feedkeys(":vglobal/the\<c-l>/d\<cr>", 'tx') + call assert_equal(1, line('$')) + call assert_equal(' 2 the~e', getline(1)) + + call Incsearch_cleanup() endfunc func Test_search_cmdline4() @@ -410,19 +493,58 @@ func Test_search_cmdline5() " Do not call test_override("char_avail", 1) so that <C-g> and <C-t> work " regardless char_avail. new - call setline(1, [' 1 the first', ' 2 the second', ' 3 the third']) + call setline(1, [' 1 the first', ' 2 the second', ' 3 the third', '']) set incsearch 1 call feedkeys("/the\<c-g>\<c-g>\<cr>", 'tx') call assert_equal(' 3 the third', getline('.')) $ call feedkeys("?the\<c-t>\<c-t>\<c-t>\<cr>", 'tx') - call assert_equal(' 2 the second', getline('.')) + call assert_equal(' 1 the first', getline('.')) " clean up set noincsearch bw! endfunc +func Test_search_cmdline7() + throw 'skipped: Nvim does not support test_override()' + " Test that an pressing <c-g> in an empty command line + " does not move the cursor + if !exists('+incsearch') + return + endif + " need to disable char_avail, + " so that expansion of commandline works + call test_override("char_avail", 1) + new + let @/ = 'b' + call setline(1, [' bbvimb', '']) + set incsearch + " first match + norm! gg0 + " moves to next match of previous search pattern, just like /<cr> + call feedkeys("/\<c-g>\<cr>", 'tx') + call assert_equal([0,1,2,0], getpos('.')) + " moves to next match of previous search pattern, just like /<cr> + call feedkeys("/\<cr>", 'tx') + call assert_equal([0,1,3,0], getpos('.')) + " moves to next match of previous search pattern, just like /<cr> + call feedkeys("/\<c-t>\<cr>", 'tx') + call assert_equal([0,1,7,0], getpos('.')) + + " using an offset uses the last search pattern + call cursor(1, 1) + call setline(1, ['1 bbvimb', ' 2 bbvimb']) + let @/ = 'b' + call feedkeys("//e\<c-g>\<cr>", 'tx') + call assert_equal('1 bbvimb', getline('.')) + call assert_equal(4, col('.')) + + set noincsearch + call test_override("char_avail", 0) + bw! +endfunc + " Tests for regexp with various magic settings func Test_search_regexp() enew! @@ -504,6 +626,7 @@ func Test_incsearch_substitute_dump() \ 'for n in range(1, 10)', \ ' call setline(n, "foo " . n)', \ 'endfor', + \ 'call setline(11, "bar 11")', \ '3', \ ], 'Xis_subst_script') let buf = RunVimInTerminal('-S Xis_subst_script', {'rows': 9, 'cols': 70}) @@ -512,6 +635,7 @@ func Test_incsearch_substitute_dump() sleep 100m " Need to send one key at a time to force a redraw. + " Select three lines at the cursor with typed pattern. call term_sendkeys(buf, ':.,.+2s/') sleep 100m call term_sendkeys(buf, 'f') @@ -520,12 +644,219 @@ func Test_incsearch_substitute_dump() sleep 100m call term_sendkeys(buf, 'o') call VerifyScreenDump(buf, 'Test_incsearch_substitute_01', {}) + call term_sendkeys(buf, "\<Esc>") + + " Select three lines at the cursor using previous pattern. + call term_sendkeys(buf, "/foo\<CR>") + sleep 100m + call term_sendkeys(buf, ':.,.+2s//') + call VerifyScreenDump(buf, 'Test_incsearch_substitute_02', {}) + + " Deleting last slash should remove the match. + call term_sendkeys(buf, "\<BS>") + call VerifyScreenDump(buf, 'Test_incsearch_substitute_03', {}) + call term_sendkeys(buf, "\<Esc>") + + " Reverse range is accepted + call term_sendkeys(buf, ':5,2s/foo') + call VerifyScreenDump(buf, 'Test_incsearch_substitute_04', {}) + call term_sendkeys(buf, "\<Esc>") + + " White space after the command is skipped + call term_sendkeys(buf, ':2,3sub /fo') + call VerifyScreenDump(buf, 'Test_incsearch_substitute_05', {}) + call term_sendkeys(buf, "\<Esc>") + + " Command modifiers are skipped + call term_sendkeys(buf, ':above below browse botr confirm keepmar keepalt keeppat keepjum filter xxx hide lockm leftabove noau noswap rightbel sandbox silent silent! $tab top unsil vert verbose 4,5s/fo.') + call VerifyScreenDump(buf, 'Test_incsearch_substitute_06', {}) + call term_sendkeys(buf, "\<Esc>") + + " Cursorline highlighting at match + call term_sendkeys(buf, ":set cursorline\<CR>") + call term_sendkeys(buf, 'G9G') + call term_sendkeys(buf, ':9,11s/bar') + call VerifyScreenDump(buf, 'Test_incsearch_substitute_07', {}) + call term_sendkeys(buf, "\<Esc>") + " Cursorline highlighting at cursor when no match + call term_sendkeys(buf, ':9,10s/bar') + call VerifyScreenDump(buf, 'Test_incsearch_substitute_08', {}) call term_sendkeys(buf, "\<Esc>") + + " Only \v handled as empty pattern, does not move cursor + call term_sendkeys(buf, '3G4G') + call term_sendkeys(buf, ":nohlsearch\<CR>") + call term_sendkeys(buf, ':6,7s/\v') + call VerifyScreenDump(buf, 'Test_incsearch_substitute_09', {}) + call term_sendkeys(buf, "\<Esc>") + + call term_sendkeys(buf, ":set nocursorline\<CR>") + + " All matches are highlighted for 'hlsearch' after the incsearch canceled + call term_sendkeys(buf, "1G*") + call term_sendkeys(buf, ":2,5s/foo") + sleep 100m + call term_sendkeys(buf, "\<Esc>") + call VerifyScreenDump(buf, 'Test_incsearch_substitute_10', {}) + + call term_sendkeys(buf, ":split\<CR>") + call term_sendkeys(buf, ":let @/ = 'xyz'\<CR>") + call term_sendkeys(buf, ":%s/.") + call VerifyScreenDump(buf, 'Test_incsearch_substitute_11', {}) + call term_sendkeys(buf, "\<BS>") + call VerifyScreenDump(buf, 'Test_incsearch_substitute_12', {}) + call term_sendkeys(buf, "\<Esc>") + call VerifyScreenDump(buf, 'Test_incsearch_substitute_13', {}) + call term_sendkeys(buf, ":%bwipe!\<CR>") + call term_sendkeys(buf, ":only!\<CR>") + + " get :'<,'>s command in history + call term_sendkeys(buf, ":set cmdheight=2\<CR>") + call term_sendkeys(buf, "aasdfasdf\<Esc>") + call term_sendkeys(buf, "V:s/a/b/g\<CR>") + " Using '<,'> does not give E20 + call term_sendkeys(buf, ":new\<CR>") + call term_sendkeys(buf, "aasdfasdf\<Esc>") + call term_sendkeys(buf, ":\<Up>\<Up>") + call VerifyScreenDump(buf, 'Test_incsearch_substitute_14', {}) + call term_sendkeys(buf, "<Esc>") + call StopVimInTerminal(buf) call delete('Xis_subst_script') endfunc +" Similar to Test_incsearch_substitute_dump() for :sort +func Test_incsearch_sort_dump() + if !exists('+incsearch') + return + endif + if !CanRunVimInTerminal() + throw 'Skipped: cannot make screendumps' + endif + call writefile([ + \ 'set incsearch hlsearch scrolloff=0', + \ 'call setline(1, ["another one 2", "that one 3", "the one 1"])', + \ ], 'Xis_sort_script') + let buf = RunVimInTerminal('-S Xis_sort_script', {'rows': 9, 'cols': 70}) + " Give Vim a chance to redraw to get rid of the spaces in line 2 caused by + " the 'ambiwidth' check. + sleep 100m + + call term_sendkeys(buf, ':sort ni u /on') + call VerifyScreenDump(buf, 'Test_incsearch_sort_01', {}) + call term_sendkeys(buf, "\<Esc>") + + call term_sendkeys(buf, ':sort! /on') + call VerifyScreenDump(buf, 'Test_incsearch_sort_02', {}) + call term_sendkeys(buf, "\<Esc>") + + call StopVimInTerminal(buf) + call delete('Xis_sort_script') +endfunc + +" Similar to Test_incsearch_substitute_dump() for :vimgrep famiry +func Test_incsearch_vimgrep_dump() + if !exists('+incsearch') + return + endif + if !CanRunVimInTerminal() + throw 'Skipped: cannot make screendumps' + endif + call writefile([ + \ 'set incsearch hlsearch scrolloff=0', + \ 'call setline(1, ["another one 2", "that one 3", "the one 1"])', + \ ], 'Xis_vimgrep_script') + let buf = RunVimInTerminal('-S Xis_vimgrep_script', {'rows': 9, 'cols': 70}) + " Give Vim a chance to redraw to get rid of the spaces in line 2 caused by + " the 'ambiwidth' check. + sleep 100m + + " Need to send one key at a time to force a redraw. + call term_sendkeys(buf, ':vimgrep on') + call VerifyScreenDump(buf, 'Test_incsearch_vimgrep_01', {}) + call term_sendkeys(buf, "\<Esc>") + + call term_sendkeys(buf, ':vimg /on/ *.txt') + call VerifyScreenDump(buf, 'Test_incsearch_vimgrep_02', {}) + call term_sendkeys(buf, "\<Esc>") + + call term_sendkeys(buf, ':vimgrepadd "\<on') + call VerifyScreenDump(buf, 'Test_incsearch_vimgrep_03', {}) + call term_sendkeys(buf, "\<Esc>") + + call term_sendkeys(buf, ':lv "tha') + call VerifyScreenDump(buf, 'Test_incsearch_vimgrep_04', {}) + call term_sendkeys(buf, "\<Esc>") + + call term_sendkeys(buf, ':lvimgrepa "the" **/*.txt') + call VerifyScreenDump(buf, 'Test_incsearch_vimgrep_05', {}) + call term_sendkeys(buf, "\<Esc>") + + call StopVimInTerminal(buf) + call delete('Xis_vimgrep_script') +endfunc + +func Test_keep_last_search_pattern() + throw 'skipped: Nvim does not support test_override()' + if !exists('+incsearch') + return + endif + new + call setline(1, ['foo', 'foo', 'foo']) + set incsearch + call test_override("char_avail", 1) + let @/ = 'bar' + call feedkeys(":/foo/s//\<Esc>", 'ntx') + call assert_equal('bar', @/) + + " no error message if pattern not found + call feedkeys(":/xyz/s//\<Esc>", 'ntx') + call assert_equal('bar', @/) + + bwipe! + call test_override("ALL", 0) + set noincsearch +endfunc + +func Test_word_under_cursor_after_match() + throw 'skipped: Nvim does not support test_override()' + if !exists('+incsearch') + return + endif + new + call setline(1, 'foo bar') + set incsearch + call test_override("char_avail", 1) + try + call feedkeys("/foo\<C-R>\<C-W>\<CR>", 'ntx') + catch /E486:/ + endtry + call assert_equal('foobar', @/) + + bwipe! + call test_override("ALL", 0) + set noincsearch +endfunc + +func Test_subst_word_under_cursor() + throw 'skipped: Nvim does not support test_override()' + if !exists('+incsearch') + return + endif + new + call setline(1, ['int SomeLongName;', 'for (xxx = 1; xxx < len; ++xxx)']) + set incsearch + call test_override("char_avail", 1) + call feedkeys("/LongName\<CR>", 'ntx') + call feedkeys(":%s/xxx/\<C-R>\<C-W>/g\<CR>", 'ntx') + call assert_equal('for (SomeLongName = 1; SomeLongName < len; ++SomeLongName)', getline(2)) + + bwipe! + call test_override("ALL", 0) + set noincsearch +endfunc + func Test_incsearch_with_change() if !has('timers') || !exists('+incsearch') || !CanRunVimInTerminal() throw 'Skipped: cannot make screendumps and/or timers feature and/or incsearch option missing' @@ -550,6 +881,21 @@ func Test_incsearch_with_change() call delete('Xis_change_script') endfunc +func Test_incsearch_cmdline_modifier() + throw 'skipped: Nvim does not support test_override()' + if !exists('+incsearch') + return + endif + call test_override("char_avail", 1) + new + call setline(1, ['foo']) + set incsearch + " Test that error E14 does not occur in parsing command modifier. + call feedkeys("V:tab", 'tx') + + call Incsearch_cleanup() +endfunc + func Test_incsearch_scrolling() if !CanRunVimInTerminal() return @@ -580,6 +926,76 @@ func Test_incsearch_scrolling() call delete('Xscript') endfunc +func Test_incsearch_search_dump() + if !exists('+incsearch') + return + endif + if !CanRunVimInTerminal() + return + endif + call writefile([ + \ 'set incsearch hlsearch scrolloff=0', + \ 'for n in range(1, 8)', + \ ' call setline(n, "foo " . n)', + \ 'endfor', + \ '3', + \ ], 'Xis_search_script') + let buf = RunVimInTerminal('-S Xis_search_script', {'rows': 9, 'cols': 70}) + " Give Vim a chance to redraw to get rid of the spaces in line 2 caused by + " the 'ambiwidth' check. + sleep 100m + + " Need to send one key at a time to force a redraw. + call term_sendkeys(buf, '/fo') + call VerifyScreenDump(buf, 'Test_incsearch_search_01', {}) + call term_sendkeys(buf, "\<Esc>") + sleep 100m + + call term_sendkeys(buf, '/\v') + call VerifyScreenDump(buf, 'Test_incsearch_search_02', {}) + call term_sendkeys(buf, "\<Esc>") + + call StopVimInTerminal(buf) + call delete('Xis_search_script') +endfunc + +func Test_incsearch_substitute() + throw 'skipped: Nvim does not support test_override()' + if !exists('+incsearch') + return + endif + call test_override("char_avail", 1) + new + set incsearch + for n in range(1, 10) + call setline(n, 'foo ' . n) + endfor + 4 + call feedkeys(":.,.+2s/foo\<BS>o\<BS>o/xxx\<cr>", 'tx') + call assert_equal('foo 3', getline(3)) + call assert_equal('xxx 4', getline(4)) + call assert_equal('xxx 5', getline(5)) + call assert_equal('xxx 6', getline(6)) + call assert_equal('foo 7', getline(7)) + + call Incsearch_cleanup() +endfunc + +func Test_incsearch_substitute_long_line() + throw 'skipped: Nvim does not support test_override()' + new + call test_override("char_avail", 1) + set incsearch + + call repeat('x', 100000)->setline(1) + call feedkeys(':s/\%c', 'xt') + redraw + call feedkeys("\<Esc>", 'xt') + + call Incsearch_cleanup() + bwipe! +endfunc + func Test_search_undefined_behaviour() if !has("terminal") return @@ -616,6 +1032,40 @@ func Test_search_sentence() / endfunc +" Test that there is no crash when there is a last search pattern but no last +" substitute pattern. +func Test_no_last_substitute_pat() + " Use viminfo to set the last search pattern to a string and make the last + " substitute pattern the most recent used and make it empty (NULL). + call writefile(['~MSle0/bar', '~MSle0~&'], 'Xviminfo') + rviminfo! Xviminfo + call assert_fails('normal n', 'E35:') + + call delete('Xviminfo') +endfunc + +func Test_search_Ctrl_L_combining() + " Make sure, that Ctrl-L works correctly with combining characters. + " It uses an artificial example of an 'a' with 4 combining chars: + " 'a' U+0061 Dec:97 LATIN SMALL LETTER A a /\%u61\Z "\u0061" + " ' ฬ' U+0300 Dec:768 COMBINING GRAVE ACCENT ̀ /\%u300\Z "\u0300" + " ' ฬ' U+0301 Dec:769 COMBINING ACUTE ACCENT ́ /\%u301\Z "\u0301" + " ' ฬ' U+0307 Dec:775 COMBINING DOT ABOVE ̇ /\%u307\Z "\u0307" + " ' ฬฃ' U+0323 Dec:803 COMBINING DOT BELOW ̣ /\%u323 "\u0323" + " Those should also appear on the commandline + if !has('multi_byte') || !exists('+incsearch') + return + endif + call Cmdline3_prep() + 1 + let bufcontent = ['', 'Miaฬฬฬฬฃm'] + call append('$', bufcontent) + call feedkeys("/Mi\<c-l>\<c-l>\<cr>", 'tx') + call assert_equal(5, line('.')) + call assert_equal(bufcontent[1], @/) + call Incsearch_cleanup() +endfunc + func Test_large_hex_chars1() " This used to cause a crash, the character becomes an NFA state. try @@ -653,6 +1103,24 @@ func Test_one_error_msg() call assert_fails('call search(" \\((\\v[[=P=]]){185}+ ")', 'E871:') endfunc +func Test_incsearch_add_char_under_cursor() + throw 'skipped: Nvim does not support test_override()' + if !exists('+incsearch') + return + endif + set incsearch + new + call setline(1, ['find match', 'anything']) + 1 + call test_override('char_avail', 1) + call feedkeys("fc/m\<C-L>\<C-L>\<C-L>\<C-L>\<C-L>\<CR>", 'tx') + call assert_equal('match', @/) + call test_override('char_avail', 0) + + set incsearch& + bwipe! +endfunc + " Test for the search() function with match at the cursor position func Test_search_match_at_curpos() new diff --git a/src/nvim/testdir/test_search_stat.vim b/src/nvim/testdir/test_search_stat.vim index cf36f3214a..11c6489ca2 100644 --- a/src/nvim/testdir/test_search_stat.vim +++ b/src/nvim/testdir/test_search_stat.vim @@ -1,11 +1,9 @@ " Tests for search_stats, when "S" is not in 'shortmess' -" -" This test is fragile, it might not work interactively, but it works when run -" as test! -source shared.vim +source screendump.vim +source check.vim -func! Test_search_stat() +func Test_search_stat() new set shortmess-=S " Append 50 lines with text to search for, "foobar" appears 20 times @@ -45,7 +43,7 @@ func! Test_search_stat() call assert_match(pat .. stat, g:a) call cursor(line('$'), 1) let g:a = execute(':unsilent :norm! n') - let stat = '\[1/>99\] W' + let stat = 'W \[1/>99\]' call assert_match(pat .. stat, g:a) " Many matches @@ -55,7 +53,7 @@ func! Test_search_stat() call assert_match(pat .. stat, g:a) call cursor(1, 1) let g:a = execute(':unsilent :norm! N') - let stat = '\[>99/>99\] W' + let stat = 'W \[>99/>99\]' call assert_match(pat .. stat, g:a) " right-left @@ -87,7 +85,7 @@ func! Test_search_stat() call cursor('$',1) let pat = 'raboof/\s\+' let g:a = execute(':unsilent :norm! n') - let stat = '\[20/1\]' + let stat = 'W \[20/1\]' call assert_match(pat .. stat, g:a) call assert_match('search hit BOTTOM, continuing at TOP', g:a) set norl @@ -98,10 +96,10 @@ func! Test_search_stat() let @/ = 'foobar' let pat = '?foobar\s\+' let g:a = execute(':unsilent :norm! N') - let stat = '\[20/20\]' + let stat = 'W \[20/20\]' call assert_match(pat .. stat, g:a) call assert_match('search hit TOP, continuing at BOTTOM', g:a) - call assert_match('\[20/20\] W', Screenline(&lines)) + call assert_match('W \[20/20\]', Screenline(&lines)) " normal, no match call cursor(1,1) @@ -160,7 +158,115 @@ func! Test_search_stat() let stat = '\[1/2\]' call assert_notmatch(pat .. stat, g:a) - " close the window + " normal, n comes from a silent mapping + " First test a normal mapping, then a silent mapping + call cursor(1,1) + nnoremap n n + let @/ = 'find this' + let pat = '/find this\s\+' + let g:a = execute(':unsilent :norm n') + let g:b = split(g:a, "\n")[-1] + let stat = '\[1/2\]' + call assert_match(pat .. stat, g:b) + nnoremap <silent> n n + call cursor(1,1) + let g:a = execute(':unsilent :norm n') + let g:b = split(g:a, "\n")[-1] + let stat = '\[1/2\]' + call assert_notmatch(pat .. stat, g:b) + call assert_match(stat, g:b) + " Test that the message is not truncated + " it would insert '...' into the output. + call assert_match('^\s\+' .. stat, g:b) + unmap n + + " Clean up set shortmess+=S + " close the window bwipe! endfunc + +func Test_search_stat_foldopen() + CheckScreendump + + let lines =<< trim END + set shortmess-=S + setl foldenable foldmethod=indent foldopen-=search + call append(0, ['if', "\tfoo", "\tfoo", 'endif']) + let @/ = 'foo' + call cursor(1,1) + norm n + END + call writefile(lines, 'Xsearchstat1') + + let buf = RunVimInTerminal('-S Xsearchstat1', #{rows: 10}) + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_searchstat_3', {}) + + call term_sendkeys(buf, "n") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_searchstat_3', {}) + + call term_sendkeys(buf, "n") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_searchstat_3', {}) + + call StopVimInTerminal(buf) + call delete('Xsearchstat1') +endfunc + +func! Test_search_stat_screendump() + CheckScreendump + + let lines =<< trim END + set shortmess-=S + " Append 50 lines with text to search for, "foobar" appears 20 times + call append(0, repeat(['foobar', 'foo', 'fooooobar', 'foba', 'foobar'], 20)) + call setline(2, 'find this') + call setline(70, 'find this') + nnoremap n n + let @/ = 'find this' + call cursor(1,1) + norm n + END + call writefile(lines, 'Xsearchstat') + let buf = RunVimInTerminal('-S Xsearchstat', #{rows: 10}) + call term_wait(buf) + call VerifyScreenDump(buf, 'Test_searchstat_1', {}) + + call term_sendkeys(buf, ":nnoremap <silent> n n\<cr>") + call term_sendkeys(buf, "gg0n") + call term_wait(buf) + call VerifyScreenDump(buf, 'Test_searchstat_2', {}) + + call StopVimInTerminal(buf) + call delete('Xsearchstat') +endfunc + +func Test_searchcount_in_statusline() + CheckScreendump + + let lines =<< trim END + set shortmess-=S + call append(0, 'this is something') + function TestSearchCount() abort + let search_count = searchcount() + if !empty(search_count) + return '[' . search_count.current . '/' . search_count.total . ']' + else + return '' + endif + endfunction + set hlsearch + set laststatus=2 statusline+=%{TestSearchCount()} + END + call writefile(lines, 'Xsearchstatusline') + let buf = RunVimInTerminal('-S Xsearchstatusline', #{rows: 10}) + call TermWait(buf) + call term_sendkeys(buf, "/something") + call VerifyScreenDump(buf, 'Test_searchstat_4', {}) + + call term_sendkeys(buf, "\<Esc>") + call StopVimInTerminal(buf) + call delete('Xsearchstatusline') +endfunc diff --git a/src/nvim/testdir/test_signs.vim b/src/nvim/testdir/test_signs.vim index 8b1927e4f0..2b7672f1af 100644 --- a/src/nvim/testdir/test_signs.vim +++ b/src/nvim/testdir/test_signs.vim @@ -1742,3 +1742,117 @@ func Test_sign_cursor_position() call StopVimInTerminal(buf) call delete('XtestSigncolumn') endfunc + +" Return the 'len' characters in screen starting from (row,col) +func s:ScreenLine(row, col, len) + let s = '' + for i in range(a:len) + let s .= nr2char(screenchar(a:row, a:col + i)) + endfor + return s +endfunc + +" Test for 'signcolumn' set to 'number'. +func Test_sign_numcol() + new + call append(0, "01234") + " With 'signcolumn' set to 'number', make sure sign is displayed in the + " number column and line number is not displayed. + set numberwidth=2 + set number + set signcolumn=number + sign define sign1 text==> + sign place 10 line=1 name=sign1 + sign define sign2 text=๏ผถ + redraw! + call assert_equal("=> 01234", s:ScreenLine(1, 1, 8)) + + " With 'signcolumn' set to 'number', when there is no sign, make sure line + " number is displayed in the number column + sign unplace 10 + redraw! + call assert_equal("1 01234", s:ScreenLine(1, 1, 7)) + + " Disable number column. Check whether sign is displayed in the sign column + set numberwidth=4 + set nonumber + sign place 10 line=1 name=sign1 + redraw! + call assert_equal("=>01234", s:ScreenLine(1, 1, 7)) + + " Enable number column. Check whether sign is displayed in the number column + set number + redraw! + call assert_equal(" => 01234", s:ScreenLine(1, 1, 9)) + + " Disable sign column. Make sure line number is displayed + set signcolumn=no + redraw! + call assert_equal(" 1 01234", s:ScreenLine(1, 1, 9)) + + " Enable auto sign column. Make sure both sign and line number are displayed + set signcolumn=auto + redraw! + call assert_equal("=> 1 01234", s:ScreenLine(1, 1, 11)) + + " Test displaying signs in the number column with width 1 + call sign_unplace('*') + call append(1, "abcde") + call append(2, "01234") + " Enable number column with width 1 + set number numberwidth=1 signcolumn=auto + redraw! + call assert_equal("3 01234", s:ScreenLine(3, 1, 7)) + " Place a sign and make sure number column width remains the same + sign place 20 line=2 name=sign1 + redraw! + call assert_equal("=>2 abcde", s:ScreenLine(2, 1, 9)) + call assert_equal(" 3 01234", s:ScreenLine(3, 1, 9)) + " Set 'signcolumn' to 'number', make sure the number column width increases + set signcolumn=number + redraw! + call assert_equal("=> abcde", s:ScreenLine(2, 1, 8)) + call assert_equal(" 3 01234", s:ScreenLine(3, 1, 8)) + " Set 'signcolumn' to 'auto', make sure the number column width is 1. + set signcolumn=auto + redraw! + call assert_equal("=>2 abcde", s:ScreenLine(2, 1, 9)) + call assert_equal(" 3 01234", s:ScreenLine(3, 1, 9)) + " Set 'signcolumn' to 'number', make sure the number column width is 2. + set signcolumn=number + redraw! + call assert_equal("=> abcde", s:ScreenLine(2, 1, 8)) + call assert_equal(" 3 01234", s:ScreenLine(3, 1, 8)) + " Disable 'number' column + set nonumber + redraw! + call assert_equal("=>abcde", s:ScreenLine(2, 1, 7)) + call assert_equal(" 01234", s:ScreenLine(3, 1, 7)) + " Enable 'number' column + set number + redraw! + call assert_equal("=> abcde", s:ScreenLine(2, 1, 8)) + call assert_equal(" 3 01234", s:ScreenLine(3, 1, 8)) + " Remove the sign and make sure the width of the number column is 1. + call sign_unplace('', {'id' : 20}) + redraw! + call assert_equal("3 01234", s:ScreenLine(3, 1, 7)) + " When the first sign is placed with 'signcolumn' set to number, verify that + " the number column width increases + sign place 30 line=1 name=sign1 + redraw! + call assert_equal("=> 01234", s:ScreenLine(1, 1, 8)) + call assert_equal(" 2 abcde", s:ScreenLine(2, 1, 8)) + " Add sign with multi-byte text + set numberwidth=4 + sign place 40 line=2 name=sign2 + redraw! + call assert_equal(" => 01234", s:ScreenLine(1, 1, 9)) + call assert_equal(" ๏ผถ abcde", s:ScreenLine(2, 1, 9)) + + sign unplace * group=* + sign undefine sign1 + set signcolumn& + set number& + enew! | close +endfunc diff --git a/src/nvim/testdir/test_spell.vim b/src/nvim/testdir/test_spell.vim index e5eaa01e92..ab8a998bb8 100644 --- a/src/nvim/testdir/test_spell.vim +++ b/src/nvim/testdir/test_spell.vim @@ -1,10 +1,13 @@ " Test spell checking " Note: this file uses latin1 encoding, but is used with utf-8 encoding. +source check.vim if !has('spell') finish endif +source screendump.vim + func TearDown() set nospell call delete('Xtest.aff') @@ -76,6 +79,11 @@ func Test_spellbadword() call assert_equal(['bycycle', 'bad'], spellbadword('My bycycle.')) call assert_equal(['another', 'caps'], spellbadword('A sentence. another sentence')) + call assert_equal(['TheCamelWord', 'bad'], spellbadword('TheCamelWord asdf')) + set spelloptions=camel + call assert_equal(['asdf', 'bad'], spellbadword('TheCamelWord asdf')) + set spelloptions= + set spelllang=en call assert_equal(['', ''], spellbadword('centre')) call assert_equal(['', ''], spellbadword('center')) @@ -110,6 +118,43 @@ foobar/? set spell& endfunc +func Test_spelllang_inv_region() + set spell spelllang=en_xx + let messages = GetMessages() + call assert_equal('Warning: region xx not supported', messages[-1]) + set spell& spelllang& +endfunc + +func Test_compl_with_CTRL_X_CTRL_K_using_spell() + " When spell checking is enabled and 'dictionary' is empty, + " CTRL-X CTRL-K in insert mode completes using the spelling dictionary. + new + set spell spelllang=en dictionary= + + set ignorecase + call feedkeys("Senglis\<c-x>\<c-k>\<esc>", 'tnx') + call assert_equal(['English'], getline(1, '$')) + call feedkeys("SEnglis\<c-x>\<c-k>\<esc>", 'tnx') + call assert_equal(['English'], getline(1, '$')) + + set noignorecase + call feedkeys("Senglis\<c-x>\<c-k>\<esc>", 'tnx') + call assert_equal(['englis'], getline(1, '$')) + call feedkeys("SEnglis\<c-x>\<c-k>\<esc>", 'tnx') + call assert_equal(['English'], getline(1, '$')) + + set spelllang=en_us + call feedkeys("Stheat\<c-x>\<c-k>\<esc>", 'tnx') + call assert_equal(['theater'], getline(1, '$')) + set spelllang=en_gb + call feedkeys("Stheat\<c-x>\<c-k>\<esc>", 'tnx') + " FIXME: commented out, expected theatre bug got theater. See issue #7025. + " call assert_equal(['theatre'], getline(1, '$')) + + bwipe! + set spell& spelllang& dictionary& ignorecase& +endfunc + func Test_spellreall() new set spell @@ -477,6 +522,44 @@ func RunGoodBad(good, bad, expected_words, expected_bad_words) bwipe! endfunc +func Test_spell_screendump() + CheckScreendump + + let lines =<< trim END + call setline(1, [ + \ "This is some text without any spell errors. Everything", + \ "should just be black, nothing wrong here.", + \ "", + \ "This line has a sepll error. and missing caps.", + \ "And and this is the the duplication.", + \ "with missing caps here.", + \ ]) + set spell spelllang=en_nz + END + call writefile(lines, 'XtestSpell') + let buf = RunVimInTerminal('-S XtestSpell', {'rows': 8}) + call VerifyScreenDump(buf, 'Test_spell_1', {}) + + let lines =<< trim END + call setline(1, [ + \ "This is some text without any spell errors. Everything", + \ "should just be black, nothing wrong here.", + \ "", + \ "This line has a sepll error. and missing caps.", + \ "And and this is the the duplication.", + \ "with missing caps here.", + \ ]) + set spell spelllang=en_nz + END + call writefile(lines, 'XtestSpell') + let buf = RunVimInTerminal('-S XtestSpell', {'rows': 8}) + call VerifyScreenDump(buf, 'Test_spell_1', {}) + + " clean up + call StopVimInTerminal(buf) + call delete('XtestSpell') +endfunc + let g:test_data_aff1 = [ \"SET ISO8859-1", \"TRY esianrtolcdugmphbyfvkwjkqxz-\xEB\xE9\xE8\xEA\xEF\xEE\xE4\xE0\xE2\xF6\xFC\xFB'ESIANRTOLCDUGMPHBYFVKWJKQXZ", diff --git a/src/nvim/testdir/test_startup.vim b/src/nvim/testdir/test_startup.vim index 9abaca5957..12bec745a8 100644 --- a/src/nvim/testdir/test_startup.vim +++ b/src/nvim/testdir/test_startup.vim @@ -369,12 +369,11 @@ func Test_invalid_args() endfor if has('clientserver') - " FIXME: need to add --servername to this list - " but it causes vim-8.1.1282 to crash! for opt in ['--remote', '--remote-send', '--remote-silent', '--remote-expr', \ '--remote-tab', '--remote-tab-wait', \ '--remote-tab-wait-silent', '--remote-tab-silent', \ '--remote-wait', '--remote-wait-silent', + \ '--servername', \ ] let out = split(system(GetVimCommand() .. ' ' .. opt), "\n") call assert_equal(1, v:shell_error) @@ -384,14 +383,21 @@ func Test_invalid_args() endfor endif - " FIXME: commented out as this causes vim-8.1.1282 to crash! - "if has('clipboard') - " let out = split(system(GetVimCommand() .. ' --display'), "\n") - " call assert_equal(1, v:shell_error) - " call assert_match('^VIM - Vi IMproved .* (.*)$', out[0]) - " call assert_equal('Argument missing after: "--display"', out[1]) - " call assert_equal('More info with: "vim -h"', out[2]) - "endif + if has('gui_gtk') + let out = split(system(GetVimCommand() .. ' --display'), "\n") + call assert_equal(1, v:shell_error) + call assert_match('^VIM - Vi IMproved .* (.*)$', out[0]) + call assert_equal('Argument missing after: "--display"', out[1]) + call assert_equal('More info with: "vim -h"', out[2]) + endif + + if has('xterm_clipboard') + let out = split(system(GetVimCommand() .. ' -display'), "\n") + call assert_equal(1, v:shell_error) + call assert_match('^VIM - Vi IMproved .* (.*)$', out[0]) + call assert_equal('Argument missing after: "-display"', out[1]) + call assert_equal('More info with: "vim -h"', out[2]) + endif let out = split(system(GetVimCommand() .. ' -ix'), "\n") call assert_equal(1, v:shell_error) diff --git a/src/nvim/testdir/test_statusline.vim b/src/nvim/testdir/test_statusline.vim index 8c81ec3431..4e38f7ebd8 100644 --- a/src/nvim/testdir/test_statusline.vim +++ b/src/nvim/testdir/test_statusline.vim @@ -354,6 +354,21 @@ func Test_statusline() delfunc GetNested delfunc GetStatusLine + " Test statusline works with 80+ items + function! StatusLabel() + redrawstatus + return '[label]' + endfunc + let statusline = '%{StatusLabel()}' + for i in range(150) + let statusline .= '%#TabLine' . (i % 2 == 0 ? 'Fill' : 'Sel') . '#' . string(i)[0] + endfor + let &statusline = statusline + redrawstatus + set statusline& + delfunc StatusLabel + + " Check statusline in current and non-current window " with the 'fillchars' option. set fillchars=stl:^,stlnc:=,vert:\|,fold:-,diff:- @@ -412,3 +427,22 @@ func Test_statusline_removed_group() call StopVimInTerminal(buf) call delete('XTest_statusline') endfunc + +func Test_statusline_after_split_vsplit() + only + + " Make the status line of each window show the window number. + set ls=2 stl=%{winnr()} + + split | redraw + vsplit | redraw + + " The status line of the third window should read '3' here. + call assert_equal('3', nr2char(screenchar(&lines - 1, 1))) + + only + set ls& stl& +endfunc + + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_swap.vim b/src/nvim/testdir/test_swap.vim index e072e9ed7f..f27920d20f 100644 --- a/src/nvim/testdir/test_swap.vim +++ b/src/nvim/testdir/test_swap.vim @@ -1,5 +1,7 @@ " Tests for the swap feature +source check.vim + func s:swapname() return trim(execute('swapname')) endfunc @@ -305,3 +307,61 @@ func Test_swap_recover_ext() augroup END augroup! test_swap_recover_ext endfunc + +" Test for selecting 'q' in the attention prompt +func Test_swap_prompt_splitwin() + CheckRunVimInTerminal + + call writefile(['foo bar'], 'Xfile1') + edit Xfile1 + preserve " should help to make sure the swap file exists + + let buf = RunVimInTerminal('', {'rows': 20}) + call term_sendkeys(buf, ":set nomore\n") + call term_sendkeys(buf, ":set noruler\n") + call term_sendkeys(buf, ":split Xfile1\n") + call term_wait(buf) + call WaitForAssert({-> assert_match('^\[O\]pen Read-Only, (E)dit anyway, (R)ecover, (Q)uit, (A)bort: $', term_getline(buf, 20))}) + call term_sendkeys(buf, "q") + call term_wait(buf) + call term_sendkeys(buf, ":\<CR>") + call WaitForAssert({-> assert_match('^:$', term_getline(buf, 20))}) + call term_sendkeys(buf, ":echomsg winnr('$')\<CR>") + call term_wait(buf) + call WaitForAssert({-> assert_match('^1$', term_getline(buf, 20))}) + call StopVimInTerminal(buf) + %bwipe! + call delete('Xfile1') +endfunc + +func Test_swap_symlink() + if !has("unix") + return + endif + + call writefile(['text'], 'Xtestfile') + silent !ln -s -f Xtestfile Xtestlink + + set dir=. + + " Test that swap file uses the name of the file when editing through a + " symbolic link (so that editing the file twice is detected) + edit Xtestlink + call assert_match('Xtestfile\.swp$', s:swapname()) + bwipe! + + call mkdir('Xswapdir') + exe 'set dir=' . getcwd() . '/Xswapdir//' + + " Check that this also works when 'directory' ends with '//' + edit Xtestlink + call assert_match('Xtestfile\.swp$', s:swapname()) + bwipe! + + set dir& + call delete('Xtestfile') + call delete('Xtestlink') + call delete('Xswapdir', 'rf') +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_syntax.vim b/src/nvim/testdir/test_syntax.vim index 85ee42420e..2617aa3945 100644 --- a/src/nvim/testdir/test_syntax.vim +++ b/src/nvim/testdir/test_syntax.vim @@ -369,7 +369,11 @@ func Test_ownsyntax() call setline(1, '#define FOO') syntax on set filetype=c + ownsyntax perl + " this should not crash + set + call assert_equal('perlComment', synIDattr(synID(line('.'), col('.'), 1), 'name')) call assert_equal('c', b:current_syntax) call assert_equal('perl', w:current_syntax) @@ -471,6 +475,40 @@ func Test_bg_detection() hi Normal ctermbg=NONE endfunc +func Test_syntax_hangs() + if !has('reltime') || !has('float') || !has('syntax') + return + endif + + " This pattern takes a long time to match, it should timeout. + new + call setline(1, ['aaa', repeat('abc ', 1000), 'ccc']) + let start = reltime() + set nolazyredraw redrawtime=101 + syn match Error /\%#=1a*.*X\@<=b*/ + redraw + let elapsed = reltimefloat(reltime(start)) + call assert_true(elapsed > 0.1) + call assert_true(elapsed < 1.0) + + " second time syntax HL is disabled + let start = reltime() + redraw + let elapsed = reltimefloat(reltime(start)) + call assert_true(elapsed < 0.1) + + " after CTRL-L the timeout flag is reset + let start = reltime() + exe "normal \<C-L>" + redraw + let elapsed = reltimefloat(reltime(start)) + call assert_true(elapsed > 0.1) + call assert_true(elapsed < 1.0) + + set redrawtime& + bwipe! +endfunc + func Test_synstack_synIDtrans() new setfiletype c @@ -546,38 +584,42 @@ func Test_syn_wrong_z_one() bwipe! endfunc -func Test_syntax_hangs() - if !has('reltime') || !has('float') || !has('syntax') - return - endif - - " This pattern takes a long time to match, it should timeout. - new - call setline(1, ['aaa', repeat('abc ', 1000), 'ccc']) - let start = reltime() - set nolazyredraw redrawtime=101 - syn match Error /\%#=1a*.*X\@<=b*/ - redraw - let elapsed = reltimefloat(reltime(start)) - call assert_true(elapsed > 0.1) - call assert_true(elapsed < 1.0) - - " second time syntax HL is disabled - let start = reltime() - redraw - let elapsed = reltimefloat(reltime(start)) - call assert_true(elapsed < 0.1) +func Test_syntax_after_bufdo() + call writefile(['/* aaa comment */'], 'Xaaa.c') + call writefile(['/* bbb comment */'], 'Xbbb.c') + call writefile(['/* ccc comment */'], 'Xccc.c') + call writefile(['/* ddd comment */'], 'Xddd.c') + + let bnr = bufnr('%') + new Xaaa.c + badd Xbbb.c + badd Xccc.c + badd Xddd.c + exe "bwipe " . bnr + let l = [] + bufdo call add(l, bufnr('%')) + call assert_equal(4, len(l)) - " after CTRL-L the timeout flag is reset - let start = reltime() - exe "normal \<C-L>" - redraw - let elapsed = reltimefloat(reltime(start)) - call assert_true(elapsed > 0.1) - call assert_true(elapsed < 1.0) + syntax on - set redrawtime& - bwipe! + " This used to only enable syntax HL in the last buffer. + bufdo tab split + tabrewind + for tab in range(1, 4) + norm fm + call assert_equal(['cComment'], map(synstack(line("."), col(".")), 'synIDattr(v:val, "name")')) + tabnext + endfor + + bwipe! Xaaa.c + bwipe! Xbbb.c + bwipe! Xccc.c + bwipe! Xddd.c + syntax off + call delete('Xaaa.c') + call delete('Xbbb.c') + call delete('Xccc.c') + call delete('Xddd.c') endfunc func Test_syntax_foldlevel() diff --git a/src/nvim/testdir/test_tabline.vim b/src/nvim/testdir/test_tabline.vim index f24552088b..117d962d08 100644 --- a/src/nvim/testdir/test_tabline.vim +++ b/src/nvim/testdir/test_tabline.vim @@ -64,3 +64,28 @@ func Test_redrawtabline() let &showtabline = showtabline_save au! Bufadd endfunc + +function EmptyTabname() + return "" +endfunction + +function MakeTabLine() abort + let titles = map(range(1, tabpagenr('$')), '"%( %" . v:val . "T%{EmptyTabname()}%T %)"') + let sep = 'ใ' + let tabpages = join(titles, sep) + return tabpages .. sep .. '%=%999X X' +endfunction + +func Test_tabline_empty_group() + " this was reading invalid memory + set tabline=%!MakeTabLine() + tabnew + redraw! + + tabclose + set tabline= +endfunc + + + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_tagjump.vim b/src/nvim/testdir/test_tagjump.vim index 6abe5b7c89..7057cdefb2 100644 --- a/src/nvim/testdir/test_tagjump.vim +++ b/src/nvim/testdir/test_tagjump.vim @@ -1,5 +1,8 @@ " Tests for tagjump (tags and special searches) +source check.vim +source screendump.vim + " SEGV occurs in older versions. (At least 7.4.1748 or older) func Test_ptag_with_notagstack() set notagstack @@ -551,6 +554,37 @@ func Test_tag_line_toolong() let &verbose = old_vbs endfunc +" Check that using :tselect does not run into the hit-enter prompt. +" Requires a terminal to trigger that prompt. +func Test_tselect() + CheckScreendump + + call writefile([ + \ 'main Xtest.h /^void test();$/;" f', + \ 'main Xtest.c /^int main()$/;" f', + \ 'main Xtest.x /^void test()$/;" f', + \ ], 'Xtags') + cal writefile([ + \ 'int main()', + \ 'void test()', + \ ], 'Xtest.c') + + let lines =<< trim [SCRIPT] + set tags=Xtags + [SCRIPT] + call writefile(lines, 'XTest_tselect') + let buf = RunVimInTerminal('-S XTest_tselect', {'rows': 10, 'cols': 50}) + + call term_wait(buf, 100) + call term_sendkeys(buf, ":tselect main\<CR>2\<CR>") + call VerifyScreenDump(buf, 'Test_tselect_1', {}) + + call StopVimInTerminal(buf) + call delete('Xtags') + call delete('Xtest.c') + call delete('XTest_tselect') +endfunc + func Test_tagline() call writefile([ \ 'provision Xtest.py /^ def provision(self, **kwargs):$/;" m line:1 language:Python class:Foo', diff --git a/src/nvim/testdir/test_textformat.vim b/src/nvim/testdir/test_textformat.vim index 75673adf0a..2223be952c 100644 --- a/src/nvim/testdir/test_textformat.vim +++ b/src/nvim/testdir/test_textformat.vim @@ -1,4 +1,7 @@ " Tests for the various 'formatoptions' settings + +source check.vim + func Test_text_format() enew! @@ -490,6 +493,23 @@ func Test_format_list_auto() set fo& ai& bs& endfunc +func Test_crash_github_issue_5095() + CheckFeature autocmd + + " This used to segfault, see https://github.com/vim/vim/issues/5095 + augroup testing + au BufNew x center + augroup END + + next! x + + bw + augroup testing + au! + augroup END + augroup! testing +endfunc + " Test for formatting multi-byte text with 'fo=t' func Test_tw_2_fo_t() new diff --git a/src/nvim/testdir/test_textobjects.vim b/src/nvim/testdir/test_textobjects.vim index 7863317eb0..f70cc1f70a 100644 --- a/src/nvim/testdir/test_textobjects.vim +++ b/src/nvim/testdir/test_textobjects.vim @@ -152,6 +152,36 @@ func Test_string_html_objects() normal! dit call assert_equal('-<b></b>', getline('.')) + " copy the tag block from leading indentation before the start tag + let t = " <b>\ntext\n</b>" + $put =t + normal! 2kvaty + call assert_equal("<b>\ntext\n</b>", @") + + " copy the tag block from the end tag + let t = "<title>\nwelcome\n</title>" + $put =t + normal! $vaty + call assert_equal("<title>\nwelcome\n</title>", @") + + " copy the outer tag block from a tag without an end tag + let t = "<html>\n<title>welcome\n</html>" + $put =t + normal! k$vaty + call assert_equal("<html>\n<title>welcome\n</html>", @") + + " nested tag that has < in a different line from > + let t = "<div><div\n></div></div>" + $put =t + normal! k0vaty + call assert_equal("<div><div\n></div></div>", @") + + " nested tag with attribute that has < in a different line from > + let t = "<div><div\nattr=\"attr\"\n></div></div>" + $put =t + normal! 2k0vaty + call assert_equal("<div><div\nattr=\"attr\"\n></div></div>", @") + set quoteescape& enew! endfunc diff --git a/src/nvim/testdir/test_timers.vim b/src/nvim/testdir/test_timers.vim index cffd80ff4f..13971a918d 100644 --- a/src/nvim/testdir/test_timers.vim +++ b/src/nvim/testdir/test_timers.vim @@ -233,16 +233,17 @@ func Test_timer_catch_error() endfunc func FeedAndPeek(timer) - call test_feedinput('a') + " call test_feedinput('a') + call nvim_input('a') call getchar(1) endfunc func Interrupt(timer) - call test_feedinput("\<C-C>") + " call test_feedinput("\<C-C>") + call nvim_input("\<C-C>") endfunc func Test_peek_and_get_char() - throw 'skipped: Nvim does not support test_feedinput()' if !has('unix') && !has('gui_running') return endif @@ -339,6 +340,41 @@ func Test_nocatch_garbage_collect() delfunc FeedChar endfunc +func Test_error_in_timer_callback() + if !has('terminal') || (has('win32') && has('gui_running')) + throw 'Skipped: cannot run Vim in a terminal window' + endif + + let lines =<< trim [CODE] + func Func(timer) + " fail to create list + let x = [ + endfunc + set updatetime=50 + call timer_start(1, 'Func') + [CODE] + call writefile(lines, 'Xtest.vim') + + let buf = term_start(GetVimCommandCleanTerm() .. ' -S Xtest.vim', {'term_rows': 8}) + let job = term_getjob(buf) + call WaitForAssert({-> assert_notequal('', term_getline(buf, 8))}) + + " GC must not run during timer callback, which can make Vim crash. + call term_wait(buf, 100) + call term_sendkeys(buf, "\<CR>") + call term_wait(buf, 100) + call assert_equal('run', job_status(job)) + + call term_sendkeys(buf, ":qall!\<CR>") + call WaitFor({-> job_status(job) ==# 'dead'}) + if has('unix') + call assert_equal('', job_info(job).termsig) + endif + + call delete('Xtest.vim') + exe buf .. 'bwipe!' +endfunc + func Test_timer_invalid_callback() call assert_fails('call timer_start(0, "0")', 'E921') endfunc diff --git a/src/nvim/testdir/test_undo.vim b/src/nvim/testdir/test_undo.vim index adcdcb1cd9..3b66071d6d 100644 --- a/src/nvim/testdir/test_undo.vim +++ b/src/nvim/testdir/test_undo.vim @@ -364,6 +364,25 @@ func Test_wundo_errors() bwipe! endfunc +" Check that reading a truncted undo file doesn't hang. +func Test_undofile_truncated() + throw 'skipped: TODO: ' + new + call setline(1, 'hello') + set ul=100 + wundo Xundofile + let contents = readfile('Xundofile', 'B') + + " try several sizes + for size in range(20, 500, 33) + call writefile(contents[0:size], 'Xundofile') + call assert_fails('rundo Xundofile', 'E825:') + endfor + + bwipe! + call delete('Xundofile') +endfunc + func Test_rundo_errors() call assert_fails('rundo XfileDoesNotExist', 'E822:') @@ -373,6 +392,26 @@ func Test_rundo_errors() call delete('Xundofile') endfunc +func Test_undofile_next() + set undofile + new Xfoo.txt + execute "norm ix\<c-g>uy\<c-g>uz\<Esc>" + write + bwipe + + next Xfoo.txt + call assert_equal('xyz', getline(1)) + silent undo + call assert_equal('xy', getline(1)) + silent undo + call assert_equal('x', getline(1)) + bwipe! + + call delete('Xfoo.txt') + call delete('.Xfoo.txt.un~') + set undofile& +endfunc + " Test for undo working properly when executing commands from a register. " Also test this in an empty buffer. func Test_cmd_in_reg_undo() diff --git a/src/nvim/testdir/test_usercommands.vim b/src/nvim/testdir/test_usercommands.vim index 2c7cb7bab7..0a89066a2b 100644 --- a/src/nvim/testdir/test_usercommands.vim +++ b/src/nvim/testdir/test_usercommands.vim @@ -184,6 +184,34 @@ func Test_Ambiguous() call assert_fails("\x4ei\041", 'E492: Not an editor command: Ni!') endfunc +func Test_redefine_on_reload() + call writefile(['command ExistingCommand echo "yes"'], 'Xcommandexists') + call assert_equal(0, exists(':ExistingCommand')) + source Xcommandexists + call assert_equal(2, exists(':ExistingCommand')) + " Redefining a command when reloading a script is OK. + source Xcommandexists + call assert_equal(2, exists(':ExistingCommand')) + + " But redefining in another script is not OK. + call writefile(['command ExistingCommand echo "yes"'], 'Xcommandexists2') + call assert_fails('source Xcommandexists2', 'E174:') + call delete('Xcommandexists2') + + " And defining twice in one script is not OK. + delcommand ExistingCommand + call assert_equal(0, exists(':ExistingCommand')) + call writefile([ + \ 'command ExistingCommand echo "yes"', + \ 'command ExistingCommand echo "no"', + \ ], 'Xcommandexists') + call assert_fails('source Xcommandexists', 'E174:') + call assert_equal(2, exists(':ExistingCommand')) + + call delete('Xcommandexists') + delcommand ExistingCommand +endfunc + func Test_CmdUndefined() call assert_fails('Doit', 'E492:') au CmdUndefined Doit :command Doit let g:didit = 'yes' @@ -248,7 +276,7 @@ func Test_CmdCompletion() call assert_equal('"com -nargs=* + 0 1 ?', @:) call feedkeys(":com -addr=\<C-A>\<C-B>\"\<CR>", 'tx') - call assert_equal('"com -addr=arguments buffers lines loaded_buffers quickfix tabs windows', @:) + call assert_equal('"com -addr=arguments buffers lines loaded_buffers other quickfix tabs windows', @:) call feedkeys(":com -complete=co\<C-A>\<C-B>\"\<CR>", 'tx') call assert_equal('"com -complete=color command compiler', @:) @@ -312,3 +340,202 @@ func Test_use_execute_in_completion() call assert_equal('"DoExec hi', @:) delcommand DoExec endfunc + +func Test_addr_all() + throw 'skipped: requires patch v8.1.0341 to pass' + command! -addr=lines DoSomething let g:a1 = <line1> | let g:a2 = <line2> + %DoSomething + call assert_equal(1, g:a1) + call assert_equal(line('$'), g:a2) + + command! -addr=arguments DoSomething let g:a1 = <line1> | let g:a2 = <line2> + args one two three + %DoSomething + call assert_equal(1, g:a1) + call assert_equal(3, g:a2) + + command! -addr=buffers DoSomething let g:a1 = <line1> | let g:a2 = <line2> + %DoSomething + for low in range(1, bufnr('$')) + if buflisted(low) + break + endif + endfor + call assert_equal(low, g:a1) + call assert_equal(bufnr('$'), g:a2) + + command! -addr=loaded_buffers DoSomething let g:a1 = <line1> | let g:a2 = <line2> + %DoSomething + for low in range(1, bufnr('$')) + if bufloaded(low) + break + endif + endfor + call assert_equal(low, g:a1) + for up in range(bufnr('$'), 1, -1) + if bufloaded(up) + break + endif + endfor + call assert_equal(up, g:a2) + + command! -addr=windows DoSomething let g:a1 = <line1> | let g:a2 = <line2> + new + %DoSomething + call assert_equal(1, g:a1) + call assert_equal(winnr('$'), g:a2) + bwipe + + command! -addr=tabs DoSomething let g:a1 = <line1> | let g:a2 = <line2> + tabnew + %DoSomething + call assert_equal(1, g:a1) + call assert_equal(len(gettabinfo()), g:a2) + bwipe + + command! -addr=other DoSomething echo 'nothing' + DoSomething + call assert_fails('%DoSomething') + + delcommand DoSomething +endfunc + +func Test_command_list() + command! DoCmd : + call assert_equal("\n Name Args Address Complete Definition" + \ .. "\n DoCmd 0 :", + \ execute('command DoCmd')) + + " Test with various -range= and -count= argument values. + command! -range DoCmd : + call assert_equal("\n Name Args Address Complete Definition" + \ .. "\n DoCmd 0 . :", + \ execute('command DoCmd')) + command! -range=% DoCmd : + call assert_equal("\n Name Args Address Complete Definition" + \ .. "\n DoCmd 0 % :", + \ execute('command! DoCmd')) + command! -range=2 DoCmd : + call assert_equal("\n Name Args Address Complete Definition" + \ .. "\n DoCmd 0 2 :", + \ execute('command DoCmd')) + command! -count=2 DoCmd : + call assert_equal("\n Name Args Address Complete Definition" + \ .. "\n DoCmd 0 2c :", + \ execute('command DoCmd')) + + " Test with various -addr= argument values. + command! -addr=lines DoCmd : + call assert_equal("\n Name Args Address Complete Definition" + \ .. "\n DoCmd 0 . :", + \ execute('command DoCmd')) + command! -addr=arguments DoCmd : + call assert_equal("\n Name Args Address Complete Definition" + \ .. "\n DoCmd 0 . arg :", + \ execute('command DoCmd')) + command! -addr=buffers DoCmd : + call assert_equal("\n Name Args Address Complete Definition" + \ .. "\n DoCmd 0 . buf :", + \ execute('command DoCmd')) + command! -addr=loaded_buffers DoCmd : + call assert_equal("\n Name Args Address Complete Definition" + \ .. "\n DoCmd 0 . load :", + \ execute('command DoCmd')) + command! -addr=windows DoCmd : + call assert_equal("\n Name Args Address Complete Definition" + \ .. "\n DoCmd 0 . win :", + \ execute('command DoCmd')) + command! -addr=tabs DoCmd : + call assert_equal("\n Name Args Address Complete Definition" + \ .. "\n DoCmd 0 . tab :", + \ execute('command DoCmd')) + command! -addr=other DoCmd : + call assert_equal("\n Name Args Address Complete Definition" + \ .. "\n DoCmd 0 . ? :", + \ execute('command DoCmd')) + + " Test with various -complete= argument values (non-exhaustive list) + command! -complete=arglist DoCmd : + call assert_equal("\n Name Args Address Complete Definition" + \ .. "\n DoCmd 0 arglist :", + \ execute('command DoCmd')) + command! -complete=augroup DoCmd : + call assert_equal("\n Name Args Address Complete Definition" + \ .. "\n DoCmd 0 augroup :", + \ execute('command DoCmd')) + command! -complete=custom,CustomComplete DoCmd : + call assert_equal("\n Name Args Address Complete Definition" + \ .. "\n DoCmd 0 custom :", + \ execute('command DoCmd')) + command! -complete=customlist,CustomComplete DoCmd : + call assert_equal("\n Name Args Address Complete Definition" + \ .. "\n DoCmd 0 customlist :", + \ execute('command DoCmd')) + + " Test with various -narg= argument values. + command! -nargs=0 DoCmd : + call assert_equal("\n Name Args Address Complete Definition" + \ .. "\n DoCmd 0 :", + \ execute('command DoCmd')) + command! -nargs=1 DoCmd : + call assert_equal("\n Name Args Address Complete Definition" + \ .. "\n DoCmd 1 :", + \ execute('command DoCmd')) + command! -nargs=* DoCmd : + call assert_equal("\n Name Args Address Complete Definition" + \ .. "\n DoCmd * :", + \ execute('command DoCmd')) + command! -nargs=? DoCmd : + call assert_equal("\n Name Args Address Complete Definition" + \ .. "\n DoCmd ? :", + \ execute('command DoCmd')) + command! -nargs=+ DoCmd : + call assert_equal("\n Name Args Address Complete Definition" + \ .. "\n DoCmd + :", + \ execute('command DoCmd')) + + " Test with other arguments. + command! -bang DoCmd : + call assert_equal("\n Name Args Address Complete Definition" + \ .. "\n! DoCmd 0 :", + \ execute('command DoCmd')) + command! -bar DoCmd : + call assert_equal("\n Name Args Address Complete Definition" + \ .. "\n| DoCmd 0 :", + \ execute('command DoCmd')) + command! -register DoCmd : + call assert_equal("\n Name Args Address Complete Definition" + \ .. "\n\" DoCmd 0 :", + \ execute('command DoCmd')) + command! -buffer DoCmd : + call assert_equal("\n Name Args Address Complete Definition" + \ .. "\nb DoCmd 0 :" + \ .. "\n\" DoCmd 0 :", + \ execute('command DoCmd')) + comclear + + " Test with many args. + command! -bang -bar -register -buffer -nargs=+ -complete=environment -addr=windows -count=3 DoCmd : + call assert_equal("\n Name Args Address Complete Definition" + \ .. "\n!\"b|DoCmd + 3c win environment :", + \ execute('command DoCmd')) + comclear + + " Test with special characters in command definition. + command! DoCmd :<cr><tab><c-d> + call assert_equal("\n Name Args Address Complete Definition" + \ .. "\n DoCmd 0 :<CR><Tab><C-D>", + \ execute('command DoCmd')) + + " Test output in verbose mode. + command! DoCmd : + call assert_match("^\n" + \ .. " Name Args Address Complete Definition\n" + \ .. " DoCmd 0 :\n" + \ .. "\tLast set from .*/test_usercommands.vim line \\d\\+$", + \ execute('verbose command DoCmd')) + + comclear + call assert_equal("\nNo user-defined commands found", execute(':command Xxx')) + call assert_equal("\nNo user-defined commands found", execute('command')) +endfunc diff --git a/src/nvim/testdir/test_version.vim b/src/nvim/testdir/test_version.vim new file mode 100644 index 0000000000..46cf34979f --- /dev/null +++ b/src/nvim/testdir/test_version.vim @@ -0,0 +1,12 @@ +" Test :version Ex command + +func Test_version() + " version should always return the same string. + let v1 = execute('version') + let v2 = execute('version') + call assert_equal(v1, v2) + + call assert_match("^\n\nNVIM v[0-9]\\+\\.[0-9]\\+\\.[0-9]\\+.*", v1) +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_vimscript.vim b/src/nvim/testdir/test_vimscript.vim index d2f13ff072..cb81997d39 100644 --- a/src/nvim/testdir/test_vimscript.vim +++ b/src/nvim/testdir/test_vimscript.vim @@ -1409,6 +1409,17 @@ func Test_compound_assignment_operators() let @/ = '' endfunc +func Test_funccall_garbage_collect() + func Func(x, ...) + call add(a:x, a:000) + endfunc + call Func([], []) + " Must not crash cause by invalid freeing + call test_garbagecollect_now() + call assert_true(v:true) + delfunc Func +endfunc + func Test_function_defined_line() if has('gui_running') " Can't catch the output of gvim. diff --git a/src/nvim/testdir/test_visual.vim b/src/nvim/testdir/test_visual.vim index 7fc8cdd7f4..734f264672 100644 --- a/src/nvim/testdir/test_visual.vim +++ b/src/nvim/testdir/test_visual.vim @@ -432,3 +432,14 @@ func Test_Visual_Block() close! endfunc + +func Test_visual_put_in_block() + new + call setline(1, ['xxxx', 'yโyy', 'zzzz']) + normal 1G2yl + exe "normal 1G2l\<C-V>jjlp" + call assert_equal(['xxxx', 'yโxx', 'zzxx'], getline(1, 3)) + bwipe! +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_window_cmd.vim b/src/nvim/testdir/test_window_cmd.vim index aaa291f87d..500e3ff088 100644 --- a/src/nvim/testdir/test_window_cmd.vim +++ b/src/nvim/testdir/test_window_cmd.vim @@ -841,4 +841,50 @@ func Test_winnr() only | tabonly endfunc +func Test_window_resize() + " Vertical :resize (absolute, relative, min and max size). + vsplit + vert resize 8 + call assert_equal(8, winwidth(0)) + vert resize +2 + call assert_equal(10, winwidth(0)) + vert resize -2 + call assert_equal(8, winwidth(0)) + vert resize + call assert_equal(&columns - 2, winwidth(0)) + vert resize 0 + call assert_equal(1, winwidth(0)) + vert resize 99999 + call assert_equal(&columns - 2, winwidth(0)) + + %bwipe! + + " Horizontal :resize (with absolute, relative size, min and max size). + split + resize 8 + call assert_equal(8, winheight(0)) + resize +2 + call assert_equal(10, winheight(0)) + resize -2 + call assert_equal(8, winheight(0)) + resize + call assert_equal(&lines - 4, winheight(0)) + resize 0 + call assert_equal(1, winheight(0)) + resize 99999 + call assert_equal(&lines - 4, winheight(0)) + + " :resize with explicit window number. + let other_winnr = winnr('j') + exe other_winnr .. 'resize 10' + call assert_equal(10, winheight(other_winnr)) + call assert_equal(&lines - 10 - 3, winheight(0)) + exe other_winnr .. 'resize +1' + exe other_winnr .. 'resize +1' + call assert_equal(12, winheight(other_winnr)) + call assert_equal(&lines - 10 - 3 -2, winheight(0)) + + %bwipe! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_windows_home.vim b/src/nvim/testdir/test_windows_home.vim index 2e311b9aa5..3c2db01444 100644 --- a/src/nvim/testdir/test_windows_home.vim +++ b/src/nvim/testdir/test_windows_home.vim @@ -1,8 +1,7 @@ " Test for $HOME on Windows. -if !has('win32') - finish -endif +source check.vim +CheckMSWindows let s:env = {} diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index bfd9435c49..62d7dc8b18 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -52,15 +52,10 @@ #define OUTBUF_SIZE 0xffff #define TOO_MANY_EVENTS 1000000 -#define STARTS_WITH(str, prefix) \ - (strlen(str) >= (sizeof(prefix) - 1) && 0 == memcmp((str), (prefix), \ - sizeof(prefix) - 1)) -#define SCREEN_WRAP(is_screen, seq) ((is_screen) \ - ? DCS_STR seq STERM_STR : seq) -#define SCREEN_TMUX_WRAP(is_screen, is_tmux, seq) \ - ((is_screen) \ - ? DCS_STR seq STERM_STR : (is_tmux) \ - ? DCS_STR "tmux;\x1b" seq STERM_STR : seq) +#define STARTS_WITH(str, prefix) (strlen(str) >= (sizeof(prefix) - 1) \ + && 0 == memcmp((str), (prefix), sizeof(prefix) - 1)) +#define TMUX_WRAP(is_tmux, seq) ((is_tmux) \ + ? "\x1bPtmux;\x1b" seq "\x1b\\" : seq) #define LINUXSET0C "\x1b[?0c" #define LINUXSET1C "\x1b[?1c" @@ -113,6 +108,7 @@ typedef struct { bool cork, overflow; bool cursor_color_changed; bool is_starting; + FILE *screenshot; cursorentry_T cursor_shapes[SHAPE_IDX_COUNT]; HlAttrs clear_attrs; kvec_t(HlAttrs) attrs; @@ -172,6 +168,7 @@ UI *tui_start(void) ui->suspend = tui_suspend; ui->set_title = tui_set_title; ui->set_icon = tui_set_icon; + ui->screenshot = tui_screenshot; ui->option_set= tui_option_set; ui->raw_line = tui_raw_line; @@ -319,7 +316,13 @@ static void terminfo_start(UI *ui) #ifdef WIN32 uv_tty_set_mode(&data->output_handle.tty, UV_TTY_MODE_RAW); #else - uv_tty_set_mode(&data->output_handle.tty, UV_TTY_MODE_IO); + int retry_count = 10; + // A signal may cause uv_tty_set_mode() to fail (e.g., SIGCONT). Retry a + // few times. #12322 + while (uv_tty_set_mode(&data->output_handle.tty, UV_TTY_MODE_IO) == UV_EINTR + && retry_count > 0) { + retry_count--; + } #endif } else { uv_pipe_init(&data->write_loop, &data->output_handle.pipe, 0); @@ -417,6 +420,7 @@ static void tui_main(UIBridgeData *bridge, UI *ui) data->bridge = bridge; data->loop = &tui_loop; data->is_starting = true; + data->screenshot = NULL; kv_init(data->invalid_regions); signal_watcher_init(data->loop, &data->winch_handle, ui); signal_watcher_init(data->loop, &data->cont_handle, data); @@ -1085,6 +1089,7 @@ static void tui_set_mode(UI *ui, ModeShape mode) } } else if (c.id == 0) { // No cursor color for this mode; reset to default. + data->want_invisible = false; unibi_out_ext(ui, data->unibi_ext.reset_cursor_color); } @@ -1103,6 +1108,15 @@ static void tui_set_mode(UI *ui, ModeShape mode) static void tui_mode_change(UI *ui, String mode, Integer mode_idx) { TUIData *data = ui->data; +#ifdef UNIX + // If stdin is not a TTY, the LHS of pipe may change the state of the TTY + // after calling uv_tty_set_mode. So, set the mode of the TTY again here. + // #13073 + if (data->is_starting && data->input.in_fd == STDERR_FILENO) { + uv_tty_set_mode(&data->output_handle.tty, UV_TTY_MODE_NORMAL); + uv_tty_set_mode(&data->output_handle.tty, UV_TTY_MODE_IO); + } +#endif tui_set_mode(ui, (ModeShape)mode_idx); data->is_starting = false; // mode entered, no longer starting data->showing_mode = (ModeShape)mode_idx; @@ -1322,6 +1336,31 @@ static void tui_set_icon(UI *ui, String icon) { } +static void tui_screenshot(UI *ui, String path) +{ + TUIData *data = ui->data; + UGrid *grid = &data->grid; + flush_buf(ui); + grid->row = 0; + grid->col = 0; + + FILE *f = fopen(path.data, "w"); + data->screenshot = f; + fprintf(f, "%d,%d\n", grid->height, grid->width); + unibi_out(ui, unibi_clear_screen); + for (int i = 0; i < grid->height; i++) { + cursor_goto(ui, i, 0); + for (int j = 0; j < grid->width; j++) { + print_cell(ui, &grid->cells[i][j]); + } + } + flush_buf(ui); + data->screenshot = NULL; + + fclose(f); +} + + static void tui_option_set(UI *ui, String name, Object value) { TUIData *data = ui->data; @@ -1591,10 +1630,6 @@ static void patch_terminfo_bugs(TUIData *data, const char *term, bool mate_pretending_xterm = xterm && colorterm && strstr(colorterm, "mate-terminal"); bool true_xterm = xterm && !!xterm_version && !bsdvt; - bool true_screen = screen && !os_getenv("TMUX"); - bool screen_host_linuxvt = - terminfo_is_term_family(true_screen && term[6] == '.' - ? term + 7 : NULL, "linux"); bool cygwin = terminfo_is_term_family(term, "cygwin"); char *fix_normal = (char *)unibi_get_str(ut, unibi_cursor_normal); @@ -1737,10 +1772,8 @@ static void patch_terminfo_bugs(TUIData *data, const char *term, #define XTERM_SETAB_16 \ "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e39%;m" - data->unibi_ext.get_bg = - (int)unibi_add_ext_str(ut, "ext.get_bg", - SCREEN_TMUX_WRAP(true_screen, - tmux, "\x1b]11;?\x07")); + data->unibi_ext.get_bg = (int)unibi_add_ext_str(ut, "ext.get_bg", + "\x1b]11;?\x07"); // Terminals with 256-colour SGR support despite what terminfo says. if (unibi_get_num(ut, unibi_max_colors) < 256) { @@ -1775,32 +1808,6 @@ static void patch_terminfo_bugs(TUIData *data, const char *term, data->unibi_ext.set_cursor_style = unibi_find_ext_str(ut, "Ss"); } - // GNU Screen does not have Ss/Se. When terminfo has Ss/Se, it is wrapped with - // DCS because it is inherited from the host terminal. - if (true_screen) { - size_t len; - size_t dcs_st_len = strlen(DCS_STR) + strlen(STERM_STR); - if (-1 != data->unibi_ext.set_cursor_style) { - const char *orig_ss = - unibi_get_ext_str(data->ut, (size_t)data->unibi_ext.reset_cursor_style); - len = STRLEN(orig_ss) + dcs_st_len + 1; - char *ss = xmalloc(len); - snprintf(ss, len, "%s%s%s", DCS_STR, orig_ss, STERM_STR); - unibi_set_ext_str(data->ut, (size_t)data->unibi_ext.set_cursor_style, ss); - xfree(ss); - } - if (-1 != data->unibi_ext.reset_cursor_style) { - const char *orig_se = - unibi_get_ext_str(data->ut, (size_t)data->unibi_ext.reset_cursor_style); - len = strlen(orig_se) + dcs_st_len + 1; - char *se = xmalloc(len); - snprintf(se, len, "%s%s%s", DCS_STR, orig_se, STERM_STR); - unibi_set_ext_str(data->ut, - (size_t)data->unibi_ext.reset_cursor_style, se); - xfree(se); - } - } - // Dickey ncurses terminfo includes Ss/Se capabilities since 2011-07-14. So // adding them to terminal types, that have such control sequences but lack // the correct terminfo entries, is a fixup, not an augmentation. @@ -1816,12 +1823,7 @@ static void patch_terminfo_bugs(TUIData *data, const char *term, || (konsolev >= 180770) // #9364 || tmux // per tmux manual page // https://lists.gnu.org/archive/html/screen-devel/2013-03/msg00000.html - || (true_screen - && (!screen_host_linuxvt - || (screen_host_linuxvt - && (xterm_version || (vte_version > 0) || colorterm)))) - // Since GNU Screen does not support DECSCUSR, DECSCUSR is wrapped - // in DCS and output to the host terminal. + || screen || st // #7641 || rxvt // per command.C // per analysis of VT100Terminal.m @@ -1834,72 +1836,58 @@ static void patch_terminfo_bugs(TUIData *data, const char *term, || (linuxvt && (xterm_version || (vte_version > 0) || colorterm)))) { data->unibi_ext.set_cursor_style = - (int)unibi_add_ext_str(ut, "Ss", - SCREEN_WRAP(true_screen, "\x1b[%p1%d q")); + (int)unibi_add_ext_str(ut, "Ss", "\x1b[%p1%d q"); if (-1 == data->unibi_ext.reset_cursor_style) { data->unibi_ext.reset_cursor_style = (int)unibi_add_ext_str(ut, "Se", ""); } unibi_set_ext_str(ut, (size_t)data->unibi_ext.reset_cursor_style, - SCREEN_WRAP(true_screen, "\x1b[ q")); - } else if (linuxvt || screen_host_linuxvt) { + "\x1b[ q"); + } else if (linuxvt) { // Linux uses an idiosyncratic escape code to set the cursor shape and // does not support DECSCUSR. // See http://linuxgazette.net/137/anonymous.html for more info - // - // Since gnu Screen does not have Ss/Se, if the host terminal is a linux - // console that does not support xterm extensions, it will wraps the - // linux-specific sequence in DCS and outputs it. - data->unibi_ext.set_cursor_style = (int)unibi_add_ext_str( - ut, "Ss", - SCREEN_WRAP(true_screen, - "\x1b[?" - "%?" - // The parameter passed to Ss is the DECSCUSR parameter, - // so the - // terminal capability has to translate into the Linux - // idiosyncratic parameter. - // - // linuxvt only supports block and underline. It is also - // only possible to have a steady block (no steady - // underline) - "%p1%{2}%<" "%t%{8}" // blink block - "%e%p1%{2}%=" "%t%{112}" // steady block - "%e%p1%{3}%=" "%t%{4}" // blink underline (set to half - // block) - "%e%p1%{4}%=" "%t%{4}" // steady underline - "%e%p1%{5}%=" "%t%{2}" // blink bar (set to underline) - "%e%p1%{6}%=" "%t%{2}" // steady bar - "%e%{0}" // anything else - "%;" "%dc")); + data->unibi_ext.set_cursor_style = (int)unibi_add_ext_str(ut, "Ss", + "\x1b[?" + "%?" + // The parameter passed to Ss is the DECSCUSR parameter, so the + // terminal capability has to translate into the Linux idiosyncratic + // parameter. + // + // linuxvt only supports block and underline. It is also only + // possible to have a steady block (no steady underline) + "%p1%{2}%<" "%t%{8}" // blink block + "%e%p1%{2}%=" "%t%{112}" // steady block + "%e%p1%{3}%=" "%t%{4}" // blink underline (set to half block) + "%e%p1%{4}%=" "%t%{4}" // steady underline + "%e%p1%{5}%=" "%t%{2}" // blink bar (set to underline) + "%e%p1%{6}%=" "%t%{2}" // steady bar + "%e%{0}" // anything else + "%;" "%dc"); if (-1 == data->unibi_ext.reset_cursor_style) { data->unibi_ext.reset_cursor_style = (int)unibi_add_ext_str(ut, "Se", ""); } unibi_set_ext_str(ut, (size_t)data->unibi_ext.reset_cursor_style, - SCREEN_WRAP(true_screen, "\x1b[?c")); + "\x1b[?c"); } else if (konsolev > 0 && konsolev < 180770) { // Konsole before version 18.07.70: set up a nonce profile. This has // side-effects on temporary font resizing. #6798 - data->unibi_ext.set_cursor_style = (int)unibi_add_ext_str( - ut, "Ss", - SCREEN_TMUX_WRAP(true_screen, tmux, - "\x1b]50;CursorShape=%?" - "%p1%{3}%<" "%t%{0}" // block - "%e%p1%{5}%<" "%t%{2}" // underline - "%e%{1}" // everything else is bar - "%;%d;BlinkingCursorEnabled=%?" - "%p1%{1}%<" "%t%{1}" // Fortunately if we exclude - // zero as special, - "%e%p1%{1}%&" // in all other c2ses we can treat bit - // #0 as a flag. - "%;%d\x07")); + data->unibi_ext.set_cursor_style = (int)unibi_add_ext_str(ut, "Ss", + TMUX_WRAP(tmux, "\x1b]50;CursorShape=%?" + "%p1%{3}%<" "%t%{0}" // block + "%e%p1%{5}%<" "%t%{2}" // underline + "%e%{1}" // everything else is bar + "%;%d;BlinkingCursorEnabled=%?" + "%p1%{1}%<" "%t%{1}" // Fortunately if we exclude zero as special, + "%e%p1%{1}%&" // in all other cases we can treat bit #0 as a flag. + "%;%d\x07")); if (-1 == data->unibi_ext.reset_cursor_style) { data->unibi_ext.reset_cursor_style = (int)unibi_add_ext_str(ut, "Se", ""); } unibi_set_ext_str(ut, (size_t)data->unibi_ext.reset_cursor_style, - SCREEN_TMUX_WRAP(true_screen, tmux, "\x1b]50;\x07")); + "\x1b]50;\x07"); } } } @@ -1931,10 +1919,6 @@ static void augment_terminfo(TUIData *data, const char *term, const char *xterm_version = os_getenv("XTERM_VERSION"); bool true_xterm = xterm && !!xterm_version && !bsdvt; - bool true_screen = screen && !os_getenv("TMUX"); - bool screen_host_rxvt = - terminfo_is_term_family(true_screen - && term[6] == '.' ? term + 7 : NULL, "rxvt"); // Only define this capability for terminal types that we know understand it. if (dtterm // originated this extension @@ -2001,7 +1985,7 @@ static void augment_terminfo(TUIData *data, const char *term, // all panes, which is not particularly desirable. A better approach // would use a tmux control sequence and an extra if(screen) test. data->unibi_ext.set_cursor_color = (int)unibi_add_ext_str( - ut, NULL, SCREEN_TMUX_WRAP(true_screen, tmux, "\033]Pl%p1%06x\033\\")); + ut, NULL, TMUX_WRAP(tmux, "\033]Pl%p1%06x\033\\")); } else if ((xterm || rxvt || tmux || alacritty) && (vte_version == 0 || vte_version >= 3900)) { // Supported in urxvt, newer VTE. @@ -2021,27 +2005,21 @@ static void augment_terminfo(TUIData *data, const char *term, /// Terminals usually ignore unrecognized private modes, and there is no /// known ambiguity with these. So we just set them unconditionally. - /// If the DECSET is not supported by GNU Screen, it is wrapped with DCS and - /// sent to the host terminal. data->unibi_ext.enable_lr_margin = (int)unibi_add_ext_str( ut, "ext.enable_lr_margin", "\x1b[?69h"); data->unibi_ext.disable_lr_margin = (int)unibi_add_ext_str( ut, "ext.disable_lr_margin", "\x1b[?69l"); data->unibi_ext.enable_bracketed_paste = (int)unibi_add_ext_str( - ut, "ext.enable_bpaste", SCREEN_WRAP(true_screen, "\x1b[?2004h")); + ut, "ext.enable_bpaste", "\x1b[?2004h"); data->unibi_ext.disable_bracketed_paste = (int)unibi_add_ext_str( - ut, "ext.disable_bpaste", SCREEN_WRAP(true_screen, "\x1b[?2004l")); + ut, "ext.disable_bpaste", "\x1b[?2004l"); // For urxvt send BOTH xterm and old urxvt sequences. #8695 data->unibi_ext.enable_focus_reporting = (int)unibi_add_ext_str( ut, "ext.enable_focus", - (rxvt || screen_host_rxvt) - ? SCREEN_WRAP(true_screen, "\x1b[?1004h\x1b]777;focus;on\x7") - : SCREEN_WRAP(true_screen, "\x1b[?1004h")); + rxvt ? "\x1b[?1004h\x1b]777;focus;on\x7" : "\x1b[?1004h"); data->unibi_ext.disable_focus_reporting = (int)unibi_add_ext_str( ut, "ext.disable_focus", - (rxvt || screen_host_rxvt) - ? SCREEN_WRAP(true_screen, "\x1b[?1004l\x1b]777;focus;off\x7") - : SCREEN_WRAP(true_screen, "\x1b[?1004l")); + rxvt ? "\x1b[?1004l\x1b]777;focus;off\x7" : "\x1b[?1004l"); data->unibi_ext.enable_mouse = (int)unibi_add_ext_str( ut, "ext.enable_mouse", "\x1b[?1002h\x1b[?1006h"); data->unibi_ext.disable_mouse = (int)unibi_add_ext_str( @@ -2120,9 +2098,15 @@ static void flush_buf(UI *ui) } } - uv_write(&req, STRUCT_CAST(uv_stream_t, &data->output_handle), - bufs, (unsigned)(bufp - bufs), NULL); - uv_run(&data->write_loop, UV_RUN_DEFAULT); + if (data->screenshot) { + for (size_t i = 0; i < (size_t)(bufp - bufs); i++) { + fwrite(bufs[i].base, bufs[i].len, 1, data->screenshot); + } + } else { + uv_write(&req, STRUCT_CAST(uv_stream_t, &data->output_handle), + bufs, (unsigned)(bufp - bufs), NULL); + uv_run(&data->write_loop, UV_RUN_DEFAULT); + } data->bufpos = 0; data->overflow = false; } diff --git a/src/nvim/types.h b/src/nvim/types.h index 87560a43da..17f7e16740 100644 --- a/src/nvim/types.h +++ b/src/nvim/types.h @@ -16,11 +16,13 @@ typedef uint32_t u8char_T; // Opaque handle used by API clients to refer to various objects in vim typedef int handle_T; -// Opaque handle to a lua value. Must be free with `executor_free_luaref` when +// Opaque handle to a lua value. Must be free with `api_free_luaref` when // not needed anymore! LUA_NOREF represents missing reference, i e to indicate // absent callback etc. typedef int LuaRef; +typedef handle_T NS; + typedef struct expand expand_T; typedef enum { diff --git a/src/nvim/ui_bridge.c b/src/nvim/ui_bridge.c index 9a1988739c..25f45b8fe6 100644 --- a/src/nvim/ui_bridge.c +++ b/src/nvim/ui_bridge.c @@ -61,6 +61,7 @@ UI *ui_bridge_attach(UI *ui, ui_main_fn ui_main, event_scheduler scheduler) rv->bridge.suspend = ui_bridge_suspend; rv->bridge.set_title = ui_bridge_set_title; rv->bridge.set_icon = ui_bridge_set_icon; + rv->bridge.screenshot = ui_bridge_screenshot; rv->bridge.option_set = ui_bridge_option_set; rv->bridge.raw_line = ui_bridge_raw_line; rv->bridge.inspect = ui_bridge_inspect; diff --git a/src/nvim/ui_compositor.c b/src/nvim/ui_compositor.c index bc7fee7e96..9d3ec21949 100644 --- a/src/nvim/ui_compositor.c +++ b/src/nvim/ui_compositor.c @@ -26,6 +26,7 @@ #include "nvim/screen.h" #include "nvim/syntax.h" #include "nvim/api/private/helpers.h" +#include "nvim/lua/executor.h" #include "nvim/os/os.h" #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/undo.c b/src/nvim/undo.c index 97018f6c02..a8b8f7aa50 100644 --- a/src/nvim/undo.c +++ b/src/nvim/undo.c @@ -878,7 +878,12 @@ static u_header_T *unserialize_uhp(bufinfo_T *bi, for (;; ) { int len = undo_read_byte(bi); - if (len == 0 || len == EOF) { + if (len == EOF) { + corruption_error("truncated", file_name); + u_free_uhp(uhp); + return NULL; + } + if (len == 0) { break; } int what = undo_read_byte(bi); @@ -2450,7 +2455,7 @@ static void u_undo_end( { FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { if (wp->w_buffer == curbuf && wp->w_p_cole > 0) { - redraw_win_later(wp, NOT_VALID); + redraw_later(wp, NOT_VALID); } } } @@ -3029,8 +3034,6 @@ u_header_T *u_force_get_undo_header(buf_T *buf) curbuf = buf; // Args are tricky: this means replace empty range by empty range.. u_savecommon(0, 1, 1, true); - curbuf = save_curbuf; - uhp = buf->b_u_curhead; if (!uhp) { uhp = buf->b_u_newhead; @@ -3038,6 +3041,7 @@ u_header_T *u_force_get_undo_header(buf_T *buf) abort(); } } + curbuf = save_curbuf; } return uhp; } diff --git a/src/nvim/version.c b/src/nvim/version.c index 190f13e74b..7296c74109 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -144,7 +144,7 @@ static const int included_patches[] = { 1777, 1776, 1775, - // 1774, + 1774, 1773, 1772, 1771, @@ -171,9 +171,9 @@ static const int included_patches[] = { 1750, 1749, 1748, - // 1747, + 1747, 1746, - // 1745, + 1745, // 1744, // 1743, 1742, @@ -206,7 +206,7 @@ static const int included_patches[] = { 1715, 1714, 1713, - // 1712, + 1712, 1711, 1710, 1709, @@ -327,9 +327,9 @@ static const int included_patches[] = { 1594, 1593, // 1592, - // 1591, + 1591, 1590, - // 1589, + 1589, // 1588, 1587, 1586, @@ -374,7 +374,7 @@ static const int included_patches[] = { 1547, 1546, 1545, - // 1544, + 1544, 1543, 1542, 1541, @@ -387,7 +387,7 @@ static const int included_patches[] = { 1534, 1533, 1532, - // 1531, + 1531, 1530, 1529, 1528, @@ -464,7 +464,7 @@ static const int included_patches[] = { 1457, 1456, // 1455, - // 1454, + 1454, 1453, 1452, 1451, @@ -1970,11 +1970,21 @@ bool has_nvim_version(const char *const version_str) /// /// @return true if patch `n` has been included. bool has_vim_patch(int n) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { - for (int i = 0; included_patches[i] != 0; i++) { - if (included_patches[i] == n) { + // Perform a binary search. + int l = 0; + int h = (int)(ARRAY_SIZE(included_patches)) - 1; + while (l < h) { + const int m = (l + h) / 2; + if (included_patches[m] == n) { return true; } + if (included_patches[m] < n) { + h = m; + } else { + l = m + 1; + } } return false; } @@ -2119,13 +2129,13 @@ void list_in_columns(char_u **items, int size, int current) void list_lua_version(void) { - typval_T luaver_tv; - typval_T arg = { .v_type = VAR_UNKNOWN }; // No args. - char *luaver_expr = "((jit and jit.version) and jit.version or _VERSION)"; - executor_eval_lua(cstr_as_string(luaver_expr), &arg, &luaver_tv); - assert(luaver_tv.v_type == VAR_STRING); - MSG(luaver_tv.vval.v_string); - xfree(luaver_tv.vval.v_string); + char *code = "return ((jit and jit.version) and jit.version or _VERSION)"; + Error err = ERROR_INIT; + Object ret = nlua_exec(cstr_as_string(code), (Array)ARRAY_DICT_INIT, &err); + assert(!ERROR_SET(&err)); + assert(ret.type == kObjectTypeString); + MSG(ret.data.string.data); + api_free_object(ret); } void list_version(void) diff --git a/src/nvim/vim.h b/src/nvim/vim.h index 832703e83d..900f2acd81 100644 --- a/src/nvim/vim.h +++ b/src/nvim/vim.h @@ -259,7 +259,6 @@ enum { FOLD_TEXT_LEN = 51 }; //!< buffer size for get_foldtext() #define PERROR(msg) (void) emsgf("%s: %s", msg, strerror(errno)) #define SHOWCMD_COLS 10 // columns needed by shown command -#define STL_MAX_ITEM 80 // max nr of %<flag> in statusline #include "nvim/path.h" @@ -316,7 +315,8 @@ enum { FOLD_TEXT_LEN = 51 }; //!< buffer size for get_foldtext() #define LOWEST_WIN_ID 1000 // BSD is supposed to cover FreeBSD and similar systems. -#if (defined(BSD) || defined(__FreeBSD_kernel__)) && defined(S_ISCHR) +#if (defined(BSD) || defined(__FreeBSD_kernel__)) \ + && (defined(S_ISCHR) || defined(S_IFCHR)) # define OPEN_CHR_FILES #endif diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index b77b80a5f3..44b6ab5f5a 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -1431,7 +1431,7 @@ static inline void east_set_error(const ParserState *const pstate, const ParserLine pline = pstate->reader.lines.items[start.line]; ret_ast_err->msg = msg; ret_ast_err->arg_len = (int)(pline.size - start.col); - ret_ast_err->arg = pline.data + start.col; + ret_ast_err->arg = pline.data ? pline.data + start.col : NULL; } /// Set error from the given token and given message diff --git a/src/nvim/window.c b/src/nvim/window.c index 0fff93d984..3429e3df70 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -606,7 +606,7 @@ win_T *win_new_float(win_T *wp, FloatConfig fconfig, Error *err) win_config_float(wp, fconfig); wp->w_pos_changed = true; - redraw_win_later(wp, VALID); + redraw_later(wp, VALID); return wp; } @@ -679,7 +679,7 @@ void win_config_float(win_T *wp, FloatConfig fconfig) wp->w_pos_changed = true; if (change_external) { wp->w_hl_needs_update = true; - redraw_win_later(wp, NOT_VALID); + redraw_later(wp, NOT_VALID); } } @@ -764,7 +764,7 @@ static void ui_ext_win_position(win_T *wp) wp->w_grid.focusable = wp->w_float_config.focusable; if (!valid) { wp->w_grid.valid = false; - redraw_win_later(wp, NOT_VALID); + redraw_later(wp, NOT_VALID); } } } else { @@ -1490,13 +1490,11 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) if (flags & (WSP_TOP | WSP_BOT)) (void)win_comp_pos(); - /* - * Both windows need redrawing - */ - redraw_win_later(wp, NOT_VALID); - wp->w_redr_status = TRUE; - redraw_win_later(oldwin, NOT_VALID); - oldwin->w_redr_status = TRUE; + // Both windows need redrawing. Update all status lines, in case they + // show something related to the window count or position. + redraw_later(wp, NOT_VALID); + redraw_later(oldwin, NOT_VALID); + status_redraw_all(); if (need_status) { msg_row = Rows - 1; @@ -1825,8 +1823,8 @@ static void win_exchange(long Prenum) (void)win_comp_pos(); /* recompute window positions */ win_enter(wp, true); - redraw_later(NOT_VALID); - redraw_win_later(wp, NOT_VALID); + redraw_later(curwin, NOT_VALID); + redraw_later(wp, NOT_VALID); } // rotate windows: if upwards true the second window becomes the first one @@ -1998,8 +1996,8 @@ void win_move_after(win_T *win1, win_T *win2) win_append(win2, win1); frame_append(win2->w_frame, win1->w_frame); - (void)win_comp_pos(); /* recompute w_winrow for all windows */ - redraw_later(NOT_VALID); + (void)win_comp_pos(); // recompute w_winrow for all windows + redraw_later(curwin, NOT_VALID); } win_enter(win1, false); @@ -2580,9 +2578,10 @@ int win_close(win_T *win, bool free_buf) return OK; } - /* Free independent synblock before the buffer is freed. */ - if (win->w_buffer != NULL) + // Free independent synblock before the buffer is freed. + if (win->w_buffer != NULL) { reset_synblock(win); + } /* * Close the link to the buffer. @@ -3636,7 +3635,7 @@ void curwin_init(void) void win_init_empty(win_T *wp) { - redraw_win_later(wp, NOT_VALID); + redraw_later(wp, NOT_VALID); wp->w_lines_valid = 0; wp->w_cursor.lnum = 1; wp->w_curswant = wp->w_cursor.col = 0; @@ -4050,7 +4049,7 @@ static void enter_tabpage(tabpage_T *tp, buf_T *old_curbuf, int trigger_enter_au prevwin = next_prevwin; last_status(false); // status line may appear or disappear - (void)win_comp_pos(); // recompute w_winrow for all windows + const int row = win_comp_pos(); // recompute w_winrow for all windows diff_need_scrollbind = true; /* The tabpage line may have appeared or disappeared, may need to resize @@ -4061,11 +4060,20 @@ static void enter_tabpage(tabpage_T *tp, buf_T *old_curbuf, int trigger_enter_au clear_cmdline = true; } p_ch = curtab->tp_ch_used; - if (curtab->tp_old_Rows != Rows || (old_off != firstwin->w_winrow - )) + + // When cmdheight is changed in a tab page with '<C-w>-', cmdline_row is + // changed but p_ch and tp_ch_used are not changed. Thus we also need to + // check cmdline_row. + if ((row < cmdline_row) && (cmdline_row <= Rows - p_ch)) { + clear_cmdline = true; + } + + if (curtab->tp_old_Rows != Rows || (old_off != firstwin->w_winrow)) { shell_new_rows(); - if (curtab->tp_old_Columns != Columns && starting == 0) - shell_new_columns(); /* update window widths */ + } + if (curtab->tp_old_Columns != Columns && starting == 0) { + shell_new_columns(); // update window widths + } lastused_tabpage = old_curtab; @@ -4559,7 +4567,7 @@ static void win_enter_ext(win_T *wp, bool undo_sync, int curwin_invalid, if (os_chdir(new_dir) == 0) { if (!p_acd && !strequal(new_dir, cwd)) { do_autocmd_dirchanged(new_dir, curwin->w_localdir - ? kCdScopeWindow : kCdScopeTab); + ? kCdScopeWindow : kCdScopeTab, true); } shorten_fnames(true); } @@ -4568,7 +4576,7 @@ static void win_enter_ext(win_T *wp, bool undo_sync, int curwin_invalid, // directory: Change to the global directory. if (os_chdir((char *)globaldir) == 0) { if (!p_acd && !strequal((char *)globaldir, cwd)) { - do_autocmd_dirchanged((char *)globaldir, kCdScopeGlobal); + do_autocmd_dirchanged((char *)globaldir, kCdScopeGlobal, true); } } XFREE_CLEAR(globaldir); @@ -4588,10 +4596,11 @@ static void win_enter_ext(win_T *wp, bool undo_sync, int curwin_invalid, } maketitle(); - curwin->w_redr_status = TRUE; - redraw_tabline = TRUE; - if (restart_edit) - redraw_later(VALID); /* causes status line redraw */ + curwin->w_redr_status = true; + redraw_tabline = true; + if (restart_edit) { + redraw_later(curwin, VALID); // causes status line redraw + } if (HL_ATTR(HLF_INACTIVE) || (prevwin && prevwin->w_hl_ids[HLF_INACTIVE]) @@ -4966,6 +4975,27 @@ void shell_new_columns(void) win_reconfig_floats(); // The size of floats might change } +/// Check if "wp" has scrolled since last time it was checked +/// @param wp the window to check +bool win_did_scroll(win_T *wp) +{ + return (curwin->w_last_topline != curwin->w_topline + || curwin->w_last_leftcol != curwin->w_leftcol + || curwin->w_last_width != curwin->w_width + || curwin->w_last_height != curwin->w_height); +} + +/// Trigger WinScrolled autocmd +void do_autocmd_winscrolled(win_T *wp) +{ + apply_autocmds(EVENT_WINSCROLLED, NULL, NULL, false, curbuf); + + wp->w_last_topline = wp->w_topline; + wp->w_last_leftcol = wp->w_leftcol; + wp->w_last_width = wp->w_width; + wp->w_last_height = wp->w_height; +} + /* * Save the size of all windows in "gap". */ @@ -5057,7 +5087,7 @@ static void frame_comp_pos(frame_T *topfrp, int *row, int *col) /* position changed, redraw */ wp->w_winrow = *row; wp->w_wincol = *col; - redraw_win_later(wp, NOT_VALID); + redraw_later(wp, NOT_VALID); wp->w_redr_status = true; wp->w_pos_changed = true; } @@ -5110,7 +5140,7 @@ void win_setheight_win(int height, win_T *win) if (win->w_floating) { win->w_float_config.height = height; win_config_float(win, win->w_float_config); - redraw_win_later(win, NOT_VALID); + redraw_later(win, NOT_VALID); } else { frame_setheight(win->w_frame, height + win->w_status_height); @@ -5313,7 +5343,7 @@ void win_setwidth_win(int width, win_T *wp) if (wp->w_floating) { wp->w_float_config.width = width; win_config_float(wp, wp->w_float_config); - redraw_win_later(wp, NOT_VALID); + redraw_later(wp, NOT_VALID); } else { frame_setwidth(wp->w_frame, width + wp->w_vsep_width); @@ -5846,8 +5876,8 @@ void scroll_to_fraction(win_T *wp, int prev_height) } win_comp_scroll(wp); - redraw_win_later(wp, SOME_VALID); - wp->w_redr_status = TRUE; + redraw_later(wp, SOME_VALID); + wp->w_redr_status = true; invalidate_botline_win(wp); } @@ -5886,7 +5916,7 @@ void win_set_inner_size(win_T *wp) if (!exiting) { scroll_to_fraction(wp, prev_height); } - redraw_win_later(wp, NOT_VALID); // SOME_VALID?? + redraw_later(wp, NOT_VALID); // SOME_VALID?? } if (width != wp->w_width_inner) { @@ -5898,7 +5928,7 @@ void win_set_inner_size(win_T *wp) update_topline(); curs_columns(true); // validate w_wrow } - redraw_win_later(wp, NOT_VALID); + redraw_later(wp, NOT_VALID); } if (wp->w_buffer->terminal) { @@ -6019,6 +6049,12 @@ char_u *grab_file_name(long count, linenr_T *file_lnum) char_u *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_u *p = ptr + len + 1; + + *file_lnum = getdigits_long(&p, false, 0); + } return find_file_name_in_path(ptr, len, options, count, curbuf->b_ffname); } return file_name_at_cursor(options | FNAME_HYP, count, file_lnum); @@ -6736,7 +6772,7 @@ int match_add(win_T *wp, const char *const grp, const char *const pat, prev->next = m; m->next = cur; - redraw_win_later(wp, rtype); + redraw_later(wp, rtype); return id; fail: @@ -6794,7 +6830,7 @@ int match_delete(win_T *wp, int id, int perr) rtype = VALID; } xfree(cur); - redraw_win_later(wp, rtype); + redraw_later(wp, rtype); return 0; } @@ -6812,7 +6848,7 @@ void clear_matches(win_T *wp) xfree(wp->w_match_head); wp->w_match_head = m; } - redraw_win_later(wp, SOME_VALID); + redraw_later(wp, SOME_VALID); } /* @@ -6986,7 +7022,7 @@ void win_findbuf(typval_T *argvars, list_T *list) int bufnr = tv_get_number(&argvars[0]); FOR_ALL_TAB_WINDOWS(tp, wp) { - if (wp->w_buffer->b_fnum == bufnr) { + if (!wp->w_closing && wp->w_buffer->b_fnum == bufnr) { tv_list_append_number(list, wp->handle); } } diff --git a/src/tree_sitter/LICENSE b/src/tree_sitter/LICENSE deleted file mode 100644 index 971b81f9a8..0000000000 --- a/src/tree_sitter/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2018 Max Brunsfeld - -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. diff --git a/src/tree_sitter/README.md b/src/tree_sitter/README.md deleted file mode 100644 index 20cb35e7c3..0000000000 --- a/src/tree_sitter/README.md +++ /dev/null @@ -1,16 +0,0 @@ -Tree-sitter vendor runtime -========================== - -This is the vendor runtime code for treesitter. - -The original code can be found [here](https://github.com/tree-sitter/tree-sitter). - -As this code is not ours, if you find any bugs, feel free to open an issue, so that we can -investigate and determine if this should go upstream. - -# Updating - -To update the treesitter runtime, use the `update-ts-runtime.sh` script in the `scripts` directory: -```sh -./scripts/update-ts-runtime.sh <commit you want to update to> -``` diff --git a/src/tree_sitter/alloc.h b/src/tree_sitter/alloc.h deleted file mode 100644 index d3c6b5eca8..0000000000 --- a/src/tree_sitter/alloc.h +++ /dev/null @@ -1,95 +0,0 @@ -#ifndef TREE_SITTER_ALLOC_H_ -#define TREE_SITTER_ALLOC_H_ - -#ifdef __cplusplus -extern "C" { -#endif - -#include <stdlib.h> -#include <stdbool.h> -#include <stdio.h> - -#include "nvim/memory.h" - -#if 1 - -static inline bool ts_toggle_allocation_recording(bool value) { - return false; -} - -#define ts_malloc xmalloc -#define ts_calloc xcalloc -#define ts_realloc xrealloc -#define ts_free xfree - -#elif defined(TREE_SITTER_TEST) - -void *ts_record_malloc(size_t); -void *ts_record_calloc(size_t, size_t); -void *ts_record_realloc(void *, size_t); -void ts_record_free(void *); -bool ts_toggle_allocation_recording(bool); - -static inline void *ts_malloc(size_t size) { - return ts_record_malloc(size); -} - -static inline void *ts_calloc(size_t count, size_t size) { - return ts_record_calloc(count, size); -} - -static inline void *ts_realloc(void *buffer, size_t size) { - return ts_record_realloc(buffer, size); -} - -static inline void ts_free(void *buffer) { - ts_record_free(buffer); -} - -#else - -#include <stdlib.h> - -static inline bool ts_toggle_allocation_recording(bool value) { - (void)value; - return false; -} - -static inline void *ts_malloc(size_t size) { - void *result = malloc(size); - if (size > 0 && !result) { - fprintf(stderr, "tree-sitter failed to allocate %lu bytes", size); - exit(1); - } - return result; -} - -static inline void *ts_calloc(size_t count, size_t size) { - void *result = calloc(count, size); - if (count > 0 && !result) { - fprintf(stderr, "tree-sitter failed to allocate %lu bytes", count * size); - exit(1); - } - return result; -} - -static inline void *ts_realloc(void *buffer, size_t size) { - void *result = realloc(buffer, size); - if (size > 0 && !result) { - fprintf(stderr, "tree-sitter failed to reallocate %lu bytes", size); - exit(1); - } - return result; -} - -static inline void ts_free(void *buffer) { - free(buffer); -} - -#endif - -#ifdef __cplusplus -} -#endif - -#endif // TREE_SITTER_ALLOC_H_ diff --git a/src/tree_sitter/api.h b/src/tree_sitter/api.h deleted file mode 100644 index 9d832e6ec4..0000000000 --- a/src/tree_sitter/api.h +++ /dev/null @@ -1,876 +0,0 @@ -#ifndef TREE_SITTER_API_H_ -#define TREE_SITTER_API_H_ - -#ifdef __cplusplus -extern "C" { -#endif - -#include <stdio.h> -#include <stdlib.h> -#include <stdint.h> -#include <stdbool.h> - -/****************************/ -/* Section - ABI Versioning */ -/****************************/ - -/** - * The latest ABI version that is supported by the current version of the - * library. When Languages are generated by the Tree-sitter CLI, they are - * assigned an ABI version number that corresponds to the current CLI version. - * The Tree-sitter library is generally backwards-compatible with languages - * generated using older CLI versions, but is not forwards-compatible. - */ -#define TREE_SITTER_LANGUAGE_VERSION 11 - -/** - * The earliest ABI version that is supported by the current version of the - * library. - */ -#define TREE_SITTER_MIN_COMPATIBLE_LANGUAGE_VERSION 9 - -/*******************/ -/* Section - Types */ -/*******************/ - -typedef uint16_t TSSymbol; -typedef uint16_t TSFieldId; -typedef struct TSLanguage TSLanguage; -typedef struct TSParser TSParser; -typedef struct TSTree TSTree; -typedef struct TSQuery TSQuery; -typedef struct TSQueryCursor TSQueryCursor; - -typedef enum { - TSInputEncodingUTF8, - TSInputEncodingUTF16, -} TSInputEncoding; - -typedef enum { - TSSymbolTypeRegular, - TSSymbolTypeAnonymous, - TSSymbolTypeAuxiliary, -} TSSymbolType; - -typedef struct { - uint32_t row; - uint32_t column; -} TSPoint; - -typedef struct { - TSPoint start_point; - TSPoint end_point; - uint32_t start_byte; - uint32_t end_byte; -} TSRange; - -typedef struct { - void *payload; - const char *(*read)(void *payload, uint32_t byte_index, TSPoint position, uint32_t *bytes_read); - TSInputEncoding encoding; -} TSInput; - -typedef enum { - TSLogTypeParse, - TSLogTypeLex, -} TSLogType; - -typedef struct { - void *payload; - void (*log)(void *payload, TSLogType, const char *); -} TSLogger; - -typedef struct { - uint32_t start_byte; - uint32_t old_end_byte; - uint32_t new_end_byte; - TSPoint start_point; - TSPoint old_end_point; - TSPoint new_end_point; -} TSInputEdit; - -typedef struct { - uint32_t context[4]; - const void *id; - const TSTree *tree; -} TSNode; - -typedef struct { - const void *tree; - const void *id; - uint32_t context[2]; -} TSTreeCursor; - -typedef struct { - TSNode node; - uint32_t index; -} TSQueryCapture; - -typedef struct { - uint32_t id; - uint16_t pattern_index; - uint16_t capture_count; - const TSQueryCapture *captures; -} TSQueryMatch; - -typedef enum { - TSQueryPredicateStepTypeDone, - TSQueryPredicateStepTypeCapture, - TSQueryPredicateStepTypeString, -} TSQueryPredicateStepType; - -typedef struct { - TSQueryPredicateStepType type; - uint32_t value_id; -} TSQueryPredicateStep; - -typedef enum { - TSQueryErrorNone = 0, - TSQueryErrorSyntax, - TSQueryErrorNodeType, - TSQueryErrorField, - TSQueryErrorCapture, -} TSQueryError; - -/********************/ -/* Section - Parser */ -/********************/ - -/** - * Create a new parser. - */ -TSParser *ts_parser_new(void); - -/** - * Delete the parser, freeing all of the memory that it used. - */ -void ts_parser_delete(TSParser *parser); - -/** - * Set the language that the parser should use for parsing. - * - * Returns a boolean indicating whether or not the language was successfully - * assigned. True means assignment succeeded. False means there was a version - * mismatch: the language was generated with an incompatible version of the - * Tree-sitter CLI. Check the language's version using `ts_language_version` - * and compare it to this library's `TREE_SITTER_LANGUAGE_VERSION` and - * `TREE_SITTER_MIN_COMPATIBLE_LANGUAGE_VERSION` constants. - */ -bool ts_parser_set_language(TSParser *self, const TSLanguage *language); - -/** - * Get the parser's current language. - */ -const TSLanguage *ts_parser_language(const TSParser *self); - -/** - * Set the ranges of text that the parser should include when parsing. - * - * By default, the parser will always include entire documents. This function - * allows you to parse only a *portion* of a document but still return a syntax - * tree whose ranges match up with the document as a whole. You can also pass - * multiple disjoint ranges. - * - * The second and third parameters specify the location and length of an array - * of ranges. The parser does *not* take ownership of these ranges; it copies - * the data, so it doesn't matter how these ranges are allocated. - * - * If `length` is zero, then the entire document will be parsed. Otherwise, - * the given ranges must be ordered from earliest to latest in the document, - * and they must not overlap. That is, the following must hold for all - * `i` < `length - 1`: - * - * ranges[i].end_byte <= ranges[i + 1].start_byte - * - * If this requirement is not satisfied, the operation will fail, the ranges - * will not be assigned, and this function will return `false`. On success, - * this function returns `true` - */ -bool ts_parser_set_included_ranges( - TSParser *self, - const TSRange *ranges, - uint32_t length -); - -/** - * Get the ranges of text that the parser will include when parsing. - * - * The returned pointer is owned by the parser. The caller should not free it - * or write to it. The length of the array will be written to the given - * `length` pointer. - */ -const TSRange *ts_parser_included_ranges( - const TSParser *self, - uint32_t *length -); - -/** - * Use the parser to parse some source code and create a syntax tree. - * - * If you are parsing this document for the first time, pass `NULL` for the - * `old_tree` parameter. Otherwise, if you have already parsed an earlier - * version of this document and the document has since been edited, pass the - * previous syntax tree so that the unchanged parts of it can be reused. - * This will save time and memory. For this to work correctly, you must have - * already edited the old syntax tree using the `ts_tree_edit` function in a - * way that exactly matches the source code changes. - * - * The `TSInput` parameter lets you specify how to read the text. It has the - * following three fields: - * 1. `read`: A function to retrieve a chunk of text at a given byte offset - * and (row, column) position. The function should return a pointer to the - * text and write its length to the the `bytes_read` pointer. The parser - * does not take ownership of this buffer; it just borrows it until it has - * finished reading it. The function should write a zero value to the - * `bytes_read` pointer to indicate the end of the document. - * 2. `payload`: An arbitrary pointer that will be passed to each invocation - * of the `read` function. - * 3. `encoding`: An indication of how the text is encoded. Either - * `TSInputEncodingUTF8` or `TSInputEncodingUTF16`. - * - * This function returns a syntax tree on success, and `NULL` on failure. There - * are three possible reasons for failure: - * 1. The parser does not have a language assigned. Check for this using the - `ts_parser_language` function. - * 2. Parsing was cancelled due to a timeout that was set by an earlier call to - * the `ts_parser_set_timeout_micros` function. You can resume parsing from - * where the parser left out by calling `ts_parser_parse` again with the - * same arguments. Or you can start parsing from scratch by first calling - * `ts_parser_reset`. - * 3. Parsing was cancelled using a cancellation flag that was set by an - * earlier call to `ts_parser_set_cancellation_flag`. You can resume parsing - * from where the parser left out by calling `ts_parser_parse` again with - * the same arguments. - */ -TSTree *ts_parser_parse( - TSParser *self, - const TSTree *old_tree, - TSInput input -); - -/** - * Use the parser to parse some source code stored in one contiguous buffer. - * The first two parameters are the same as in the `ts_parser_parse` function - * above. The second two parameters indicate the location of the buffer and its - * length in bytes. - */ -TSTree *ts_parser_parse_string( - TSParser *self, - const TSTree *old_tree, - const char *string, - uint32_t length -); - -/** - * Use the parser to parse some source code stored in one contiguous buffer with - * a given encoding. The first four parameters work the same as in the - * `ts_parser_parse_string` method above. The final parameter indicates whether - * the text is encoded as UTF8 or UTF16. - */ -TSTree *ts_parser_parse_string_encoding( - TSParser *self, - const TSTree *old_tree, - const char *string, - uint32_t length, - TSInputEncoding encoding -); - -/** - * Instruct the parser to start the next parse from the beginning. - * - * If the parser previously failed because of a timeout or a cancellation, then - * by default, it will resume where it left off on the next call to - * `ts_parser_parse` or other parsing functions. If you don't want to resume, - * and instead intend to use this parser to parse some other document, you must - * call `ts_parser_reset` first. - */ -void ts_parser_reset(TSParser *self); - -/** - * Set the maximum duration in microseconds that parsing should be allowed to - * take before halting. - * - * If parsing takes longer than this, it will halt early, returning NULL. - * See `ts_parser_parse` for more information. - */ -void ts_parser_set_timeout_micros(TSParser *self, uint64_t timeout); - -/** - * Get the duration in microseconds that parsing is allowed to take. - */ -uint64_t ts_parser_timeout_micros(const TSParser *self); - -/** - * Set the parser's current cancellation flag pointer. - * - * If a non-null pointer is assigned, then the parser will periodically read - * from this pointer during parsing. If it reads a non-zero value, it will - * halt early, returning NULL. See `ts_parser_parse` for more information. - */ -void ts_parser_set_cancellation_flag(TSParser *self, const size_t *flag); - -/** - * Get the parser's current cancellation flag pointer. - */ -const size_t *ts_parser_cancellation_flag(const TSParser *self); - -/** - * Set the logger that a parser should use during parsing. - * - * The parser does not take ownership over the logger payload. If a logger was - * previously assigned, the caller is responsible for releasing any memory - * owned by the previous logger. - */ -void ts_parser_set_logger(TSParser *self, TSLogger logger); - -/** - * Get the parser's current logger. - */ -TSLogger ts_parser_logger(const TSParser *self); - -/** - * Set the file descriptor to which the parser should write debugging graphs - * during parsing. The graphs are formatted in the DOT language. You may want - * to pipe these graphs directly to a `dot(1)` process in order to generate - * SVG output. You can turn off this logging by passing a negative number. - */ -void ts_parser_print_dot_graphs(TSParser *self, int file); - -/******************/ -/* Section - Tree */ -/******************/ - -/** - * Create a shallow copy of the syntax tree. This is very fast. - * - * You need to copy a syntax tree in order to use it on more than one thread at - * a time, as syntax trees are not thread safe. - */ -TSTree *ts_tree_copy(const TSTree *self); - -/** - * Delete the syntax tree, freeing all of the memory that it used. - */ -void ts_tree_delete(TSTree *self); - -/** - * Get the root node of the syntax tree. - */ -TSNode ts_tree_root_node(const TSTree *self); - -/** - * Get the language that was used to parse the syntax tree. - */ -const TSLanguage *ts_tree_language(const TSTree *); - -/** - * Edit the syntax tree to keep it in sync with source code that has been - * edited. - * - * You must describe the edit both in terms of byte offsets and in terms of - * (row, column) coordinates. - */ -void ts_tree_edit(TSTree *self, const TSInputEdit *edit); - -/** - * Compare an old edited syntax tree to a new syntax tree representing the same - * document, returning an array of ranges whose syntactic structure has changed. - * - * For this to work correctly, the old syntax tree must have been edited such - * that its ranges match up to the new tree. Generally, you'll want to call - * this function right after calling one of the `ts_parser_parse` functions. - * You need to pass the old tree that was passed to parse, as well as the new - * tree that was returned from that function. - * - * The returned array is allocated using `malloc` and the caller is responsible - * for freeing it using `free`. The length of the array will be written to the - * given `length` pointer. - */ -TSRange *ts_tree_get_changed_ranges( - const TSTree *old_tree, - const TSTree *new_tree, - uint32_t *length -); - -/** - * Write a DOT graph describing the syntax tree to the given file. - */ -void ts_tree_print_dot_graph(const TSTree *, FILE *); - -/******************/ -/* Section - Node */ -/******************/ - -/** - * Get the node's type as a null-terminated string. - */ -const char *ts_node_type(TSNode); - -/** - * Get the node's type as a numerical id. - */ -TSSymbol ts_node_symbol(TSNode); - -/** - * Get the node's start byte. - */ -uint32_t ts_node_start_byte(TSNode); - -/** - * Get the node's start position in terms of rows and columns. - */ -TSPoint ts_node_start_point(TSNode); - -/** - * Get the node's end byte. - */ -uint32_t ts_node_end_byte(TSNode); - -/** - * Get the node's end position in terms of rows and columns. - */ -TSPoint ts_node_end_point(TSNode); - -/** - * Get an S-expression representing the node as a string. - * - * This string is allocated with `malloc` and the caller is responsible for - * freeing it using `free`. - */ -char *ts_node_string(TSNode); - -/** - * Check if the node is null. Functions like `ts_node_child` and - * `ts_node_next_sibling` will return a null node to indicate that no such node - * was found. - */ -bool ts_node_is_null(TSNode); - -/** - * 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. - */ -bool ts_node_is_named(TSNode); - -/** - * Check if the node is *missing*. Missing nodes are inserted by the parser in - * order to recover from certain kinds of syntax errors. - */ -bool ts_node_is_missing(TSNode); - -/** - * Check if the node is *extra*. Extra nodes represent things like comments, - * which are not required the grammar, but can appear anywhere. - */ -bool ts_node_is_extra(TSNode); - -/** - * Check if a syntax node has been edited. - */ -bool ts_node_has_changes(TSNode); - -/** - * Check if the node is a syntax error or contains any syntax errors. - */ -bool ts_node_has_error(TSNode); - -/** - * Get the node's immediate parent. - */ -TSNode ts_node_parent(TSNode); - -/** - * Get the node's child at the given index, where zero represents the first - * child. - */ -TSNode ts_node_child(TSNode, uint32_t); - -/** - * Get the node's number of children. - */ -uint32_t ts_node_child_count(TSNode); - -/** - * Get the node's *named* child at the given index. - * - * See also `ts_node_is_named`. - */ -TSNode ts_node_named_child(TSNode, uint32_t); - -/** - * Get the node's number of *named* children. - * - * See also `ts_node_is_named`. - */ -uint32_t ts_node_named_child_count(TSNode); - -/** - * Get the node's child with the given field name. - */ -TSNode ts_node_child_by_field_name( - TSNode self, - const char *field_name, - uint32_t field_name_length -); - -/** - * Get the node's child with the given numerical field id. - * - * You can convert a field name to an id using the - * `ts_language_field_id_for_name` function. - */ -TSNode ts_node_child_by_field_id(TSNode, TSFieldId); - -/** - * Get the node's next / previous sibling. - */ -TSNode ts_node_next_sibling(TSNode); -TSNode ts_node_prev_sibling(TSNode); - -/** - * Get the node's next / previous *named* sibling. - */ -TSNode ts_node_next_named_sibling(TSNode); -TSNode ts_node_prev_named_sibling(TSNode); - -/** - * Get the node's first child that extends beyond the given byte offset. - */ -TSNode ts_node_first_child_for_byte(TSNode, uint32_t); - -/** - * Get the node's first named child that extends beyond the given byte offset. - */ -TSNode ts_node_first_named_child_for_byte(TSNode, uint32_t); - -/** - * Get the smallest node within this node that spans the given range of bytes - * or (row, column) positions. - */ -TSNode ts_node_descendant_for_byte_range(TSNode, uint32_t, uint32_t); -TSNode ts_node_descendant_for_point_range(TSNode, TSPoint, TSPoint); - -/** - * Get the smallest named node within this node that spans the given range of - * bytes or (row, column) positions. - */ -TSNode ts_node_named_descendant_for_byte_range(TSNode, uint32_t, uint32_t); -TSNode ts_node_named_descendant_for_point_range(TSNode, TSPoint, TSPoint); - -/** - * Edit the node to keep it in-sync with source code that has been edited. - * - * This function is only rarely needed. When you edit a syntax tree with the - * `ts_tree_edit` function, all of the nodes that you retrieve from the tree - * afterward will already reflect the edit. You only need to use `ts_node_edit` - * when you have a `TSNode` instance that you want to keep and continue to use - * after an edit. - */ -void ts_node_edit(TSNode *, const TSInputEdit *); - -/** - * Check if two nodes are identical. - */ -bool ts_node_eq(TSNode, TSNode); - -/************************/ -/* Section - TreeCursor */ -/************************/ - -/** - * Create a new tree cursor starting from the given node. - * - * A tree cursor allows you to walk a syntax tree more efficiently than is - * possible using the `TSNode` functions. It is a mutable object that is always - * on a certain syntax node, and can be moved imperatively to different nodes. - */ -TSTreeCursor ts_tree_cursor_new(TSNode); - -/** - * Delete a tree cursor, freeing all of the memory that it used. - */ -void ts_tree_cursor_delete(TSTreeCursor *); - -/** - * Re-initialize a tree cursor to start at a different node. - */ -void ts_tree_cursor_reset(TSTreeCursor *, TSNode); - -/** - * Get the tree cursor's current node. - */ -TSNode ts_tree_cursor_current_node(const TSTreeCursor *); - -/** - * Get the field name of the tree cursor's current node. - * - * This returns `NULL` if the current node doesn't have a field. - * See also `ts_node_child_by_field_name`. - */ -const char *ts_tree_cursor_current_field_name(const TSTreeCursor *); - -/** - * Get the field name of the tree cursor's current node. - * - * This returns zero if the current node doesn't have a field. - * See also `ts_node_child_by_field_id`, `ts_language_field_id_for_name`. - */ -TSFieldId ts_tree_cursor_current_field_id(const TSTreeCursor *); - -/** - * Move the cursor to the parent of its current node. - * - * This returns `true` if the cursor successfully moved, and returns `false` - * if there was no parent node (the cursor was already on the root node). - */ -bool ts_tree_cursor_goto_parent(TSTreeCursor *); - -/** - * Move the cursor to the next sibling of its current node. - * - * This returns `true` if the cursor successfully moved, and returns `false` - * if there was no next sibling node. - */ -bool ts_tree_cursor_goto_next_sibling(TSTreeCursor *); - -/** - * Move the cursor to the first child of its current node. - * - * This returns `true` if the cursor successfully moved, and returns `false` - * if there were no children. - */ -bool ts_tree_cursor_goto_first_child(TSTreeCursor *); - -/** - * Move the cursor to the first child of its current node that extends beyond - * the given byte offset. - * - * This returns the index of the child node if one was found, and returns -1 - * if no such child was found. - */ -int64_t ts_tree_cursor_goto_first_child_for_byte(TSTreeCursor *, uint32_t); - -TSTreeCursor ts_tree_cursor_copy(const TSTreeCursor *); - -/*******************/ -/* Section - Query */ -/*******************/ - -/** - * Create a new query from a string containing one or more S-expression - * patterns. The query is associated with a particular language, and can - * only be run on syntax nodes parsed with that language. - * - * If all of the given patterns are valid, this returns a `TSQuery`. - * If a pattern is invalid, this returns `NULL`, and provides two pieces - * of information about the problem: - * 1. The byte offset of the error is written to the `error_offset` parameter. - * 2. The type of error is written to the `error_type` parameter. - */ -TSQuery *ts_query_new( - const TSLanguage *language, - const char *source, - uint32_t source_len, - uint32_t *error_offset, - TSQueryError *error_type -); - -/** - * Delete a query, freeing all of the memory that it used. - */ -void ts_query_delete(TSQuery *); - -/** - * Get the number of patterns, captures, or string literals in the query. - */ -uint32_t ts_query_pattern_count(const TSQuery *); -uint32_t ts_query_capture_count(const TSQuery *); -uint32_t ts_query_string_count(const TSQuery *); - -/** - * Get the byte offset where the given pattern starts in the query's source. - * - * This can be useful when combining queries by concatenating their source - * code strings. - */ -uint32_t ts_query_start_byte_for_pattern(const TSQuery *, uint32_t); - -/** - * Get all of the predicates for the given pattern in the query. - * - * The predicates are represented as a single array of steps. There are three - * types of steps in this array, which correspond to the three legal values for - * the `type` field: - * - `TSQueryPredicateStepTypeCapture` - Steps with this type represent names - * of captures. Their `value_id` can be used with the - * `ts_query_capture_name_for_id` function to obtain the name of the capture. - * - `TSQueryPredicateStepTypeString` - Steps with this type represent literal - * strings. Their `value_id` can be used with the - * `ts_query_string_value_for_id` function to obtain their string value. - * - `TSQueryPredicateStepTypeDone` - Steps with this type are *sentinels* - * that represent the end of an individual predicate. If a pattern has two - * predicates, then there will be two steps with this `type` in the array. - */ -const TSQueryPredicateStep *ts_query_predicates_for_pattern( - const TSQuery *self, - uint32_t pattern_index, - uint32_t *length -); - -/** - * Get the name and length of one of the query's captures, or one of the - * query's string literals. Each capture and string is associated with a - * numeric id based on the order that it appeared in the query's source. - */ -const char *ts_query_capture_name_for_id( - const TSQuery *, - uint32_t id, - uint32_t *length -); -const char *ts_query_string_value_for_id( - const TSQuery *, - uint32_t id, - uint32_t *length -); - -/** - * Disable a certain capture within a query. - * - * This prevents the capture from being returned in matches, and also avoids - * any resource usage associated with recording the capture. Currently, there - * is no way to undo this. - */ -void ts_query_disable_capture(TSQuery *, const char *, uint32_t); - -/** - * Disable a certain pattern within a query. - * - * This prevents the pattern from matching and removes most of the overhead - * associated with the pattern. Currently, there is no way to undo this. - */ -void ts_query_disable_pattern(TSQuery *, uint32_t); - -/** - * Create a new cursor for executing a given query. - * - * The cursor stores the state that is needed to iteratively search - * for matches. To use the query cursor, first call `ts_query_cursor_exec` - * to start running a given query on a given syntax node. Then, there are - * two options for consuming the results of the query: - * 1. Repeatedly call `ts_query_cursor_next_match` to iterate over all of the - * the *matches* in the order that they were found. Each match contains the - * index of the pattern that matched, and an array of captures. Because - * multiple patterns can match the same set of nodes, one match may contain - * captures that appear *before* some of the captures from a previous match. - * 2. Repeatedly call `ts_query_cursor_next_capture` to iterate over all of the - * individual *captures* in the order that they appear. This is useful if - * don't care about which pattern matched, and just want a single ordered - * sequence of captures. - * - * If you don't care about consuming all of the results, you can stop calling - * `ts_query_cursor_next_match` or `ts_query_cursor_next_capture` at any point. - * You can then start executing another query on another node by calling - * `ts_query_cursor_exec` again. - */ -TSQueryCursor *ts_query_cursor_new(void); - -/** - * Delete a query cursor, freeing all of the memory that it used. - */ -void ts_query_cursor_delete(TSQueryCursor *); - -/** - * Start running a given query on a given node. - */ -void ts_query_cursor_exec(TSQueryCursor *, const TSQuery *, TSNode); - -/** - * Set the range of bytes or (row, column) positions in which the query - * will be executed. - */ -void ts_query_cursor_set_byte_range(TSQueryCursor *, uint32_t, uint32_t); -void ts_query_cursor_set_point_range(TSQueryCursor *, TSPoint, TSPoint); - -/** - * Advance to the next match of the currently running query. - * - * If there is a match, write it to `*match` and return `true`. - * Otherwise, return `false`. - */ -bool ts_query_cursor_next_match(TSQueryCursor *, TSQueryMatch *match); -void ts_query_cursor_remove_match(TSQueryCursor *, uint32_t id); - -/** - * Advance to the next capture of the currently running query. - * - * If there is a capture, write its match to `*match` and its index within - * the matche's capture list to `*capture_index`. Otherwise, return `false`. - */ -bool ts_query_cursor_next_capture( - TSQueryCursor *, - TSQueryMatch *match, - uint32_t *capture_index -); - -/**********************/ -/* Section - Language */ -/**********************/ - -/** - * Get the number of distinct node types in the language. - */ -uint32_t ts_language_symbol_count(const TSLanguage *); - -/** - * Get a node type string for the given numerical id. - */ -const char *ts_language_symbol_name(const TSLanguage *, TSSymbol); - -/** - * Get the numerical id for the given node type string. - */ -TSSymbol ts_language_symbol_for_name( - const TSLanguage *self, - const char *string, - uint32_t length, - bool is_named -); - -/** - * Get the number of distinct field names in the language. - */ -uint32_t ts_language_field_count(const TSLanguage *); - -/** - * Get the field name string for the given numerical id. - */ -const char *ts_language_field_name_for_id(const TSLanguage *, TSFieldId); - -/** - * Get the numerical id for the given field name string. - */ -TSFieldId ts_language_field_id_for_name(const TSLanguage *, const char *, uint32_t); - -/** - * Check whether the given node type id belongs to named nodes, anonymous nodes, - * or a hidden nodes. - * - * See also `ts_node_is_named`. Hidden nodes are never returned from the API. - */ -TSSymbolType ts_language_symbol_type(const TSLanguage *, TSSymbol); - -/** - * Get the ABI version number for this language. This version number is used - * to ensure that languages were generated by a compatible version of - * Tree-sitter. - * - * See also `ts_parser_set_language`. - */ -uint32_t ts_language_version(const TSLanguage *); - -#ifdef __cplusplus -} -#endif - -#endif // TREE_SITTER_API_H_ diff --git a/src/tree_sitter/array.h b/src/tree_sitter/array.h deleted file mode 100644 index 26cb8448f1..0000000000 --- a/src/tree_sitter/array.h +++ /dev/null @@ -1,158 +0,0 @@ -#ifndef TREE_SITTER_ARRAY_H_ -#define TREE_SITTER_ARRAY_H_ - -#ifdef __cplusplus -extern "C" { -#endif - -#include <string.h> -#include <stdlib.h> -#include <stdint.h> -#include <assert.h> -#include <stdbool.h> -#include "./alloc.h" - -#define Array(T) \ - struct { \ - T *contents; \ - uint32_t size; \ - uint32_t capacity; \ - } - -#define array_init(self) \ - ((self)->size = 0, (self)->capacity = 0, (self)->contents = NULL) - -#define array_new() \ - { NULL, 0, 0 } - -#define array_get(self, index) \ - (assert((uint32_t)index < (self)->size), &(self)->contents[index]) - -#define array_front(self) array_get(self, 0) - -#define array_back(self) array_get(self, (self)->size - 1) - -#define array_clear(self) ((self)->size = 0) - -#define array_reserve(self, new_capacity) \ - array__reserve((VoidArray *)(self), array__elem_size(self), new_capacity) - -#define array_erase(self, index) \ - array__erase((VoidArray *)(self), array__elem_size(self), index) - -#define array_delete(self) array__delete((VoidArray *)self) - -#define array_push(self, element) \ - (array__grow((VoidArray *)(self), 1, array__elem_size(self)), \ - (self)->contents[(self)->size++] = (element)) - -#define array_grow_by(self, count) \ - (array__grow((VoidArray *)(self), count, array__elem_size(self)), \ - memset((self)->contents + (self)->size, 0, (count) * array__elem_size(self)), \ - (self)->size += (count)) - -#define array_push_all(self, other) \ - array_splice((self), (self)->size, 0, (other)->size, (other)->contents) - -#define array_splice(self, index, old_count, new_count, new_contents) \ - array__splice((VoidArray *)(self), array__elem_size(self), index, old_count, \ - new_count, new_contents) - -#define array_insert(self, index, element) \ - array__splice((VoidArray *)(self), array__elem_size(self), index, 0, 1, &element) - -#define array_pop(self) ((self)->contents[--(self)->size]) - -#define array_assign(self, other) \ - array__assign((VoidArray *)(self), (const VoidArray *)(other), array__elem_size(self)) - -// Private - -typedef Array(void) VoidArray; - -#define array__elem_size(self) sizeof(*(self)->contents) - -static inline void array__delete(VoidArray *self) { - ts_free(self->contents); - self->contents = NULL; - self->size = 0; - self->capacity = 0; -} - -static inline void array__erase(VoidArray *self, size_t element_size, - uint32_t index) { - assert(index < self->size); - char *contents = (char *)self->contents; - memmove(contents + index * element_size, contents + (index + 1) * element_size, - (self->size - index - 1) * element_size); - self->size--; -} - -static inline void array__reserve(VoidArray *self, size_t element_size, uint32_t new_capacity) { - if (new_capacity > self->capacity) { - if (self->contents) { - self->contents = ts_realloc(self->contents, new_capacity * element_size); - } else { - self->contents = ts_calloc(new_capacity, element_size); - } - self->capacity = new_capacity; - } -} - -static inline void array__assign(VoidArray *self, const VoidArray *other, size_t element_size) { - array__reserve(self, element_size, other->size); - self->size = other->size; - memcpy(self->contents, other->contents, self->size * element_size); -} - -static inline void array__grow(VoidArray *self, size_t count, size_t element_size) { - size_t new_size = self->size + count; - if (new_size > self->capacity) { - size_t new_capacity = self->capacity * 2; - if (new_capacity < 8) new_capacity = 8; - if (new_capacity < new_size) new_capacity = new_size; - array__reserve(self, element_size, new_capacity); - } -} - -static inline void array__splice(VoidArray *self, size_t element_size, - uint32_t index, uint32_t old_count, - uint32_t new_count, const void *elements) { - uint32_t new_size = self->size + new_count - old_count; - uint32_t old_end = index + old_count; - uint32_t new_end = index + new_count; - assert(old_end <= self->size); - - array__reserve(self, element_size, new_size); - - char *contents = (char *)self->contents; - if (self->size > old_end) { - memmove( - contents + new_end * element_size, - contents + old_end * element_size, - (self->size - old_end) * element_size - ); - } - if (new_count > 0) { - if (elements) { - memcpy( - (contents + index * element_size), - elements, - new_count * element_size - ); - } else { - memset( - (contents + index * element_size), - 0, - new_count * element_size - ); - } - } - self->size += new_count - old_count; -} - -#ifdef __cplusplus -} -#endif - -#endif // TREE_SITTER_ARRAY_H_ diff --git a/src/tree_sitter/atomic.h b/src/tree_sitter/atomic.h deleted file mode 100644 index 7bd0e850a9..0000000000 --- a/src/tree_sitter/atomic.h +++ /dev/null @@ -1,42 +0,0 @@ -#ifndef TREE_SITTER_ATOMIC_H_ -#define TREE_SITTER_ATOMIC_H_ - -#include <stdint.h> - -#ifdef _WIN32 - -#include <windows.h> - -static inline size_t atomic_load(const volatile size_t *p) { - return *p; -} - -static inline uint32_t atomic_inc(volatile uint32_t *p) { - return InterlockedIncrement((long volatile *)p); -} - -static inline uint32_t atomic_dec(volatile uint32_t *p) { - return InterlockedDecrement((long volatile *)p); -} - -#else - -static inline size_t atomic_load(const volatile size_t *p) { -#ifdef __ATOMIC_RELAXED - return __atomic_load_n(p, __ATOMIC_RELAXED); -#else - return __sync_fetch_and_add((volatile size_t *)p, 0); -#endif -} - -static inline uint32_t atomic_inc(volatile uint32_t *p) { - return __sync_add_and_fetch(p, 1u); -} - -static inline uint32_t atomic_dec(volatile uint32_t *p) { - return __sync_sub_and_fetch(p, 1u); -} - -#endif - -#endif // TREE_SITTER_ATOMIC_H_ diff --git a/src/tree_sitter/bits.h b/src/tree_sitter/bits.h deleted file mode 100644 index ce7a715567..0000000000 --- a/src/tree_sitter/bits.h +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef TREE_SITTER_BITS_H_ -#define TREE_SITTER_BITS_H_ - -#include <stdint.h> - -static inline uint32_t bitmask_for_index(uint16_t id) { - return (1u << (31 - id)); -} - -#if defined _WIN32 && !defined __GNUC__ - -#include <intrin.h> - -static inline uint32_t count_leading_zeros(uint32_t x) { - if (x == 0) return 32; - uint32_t result; - _BitScanReverse(&result, x); - return 31 - result; -} - -#else - -static inline uint32_t count_leading_zeros(uint32_t x) { - if (x == 0) return 32; - return __builtin_clz(x); -} - -#endif -#endif // TREE_SITTER_BITS_H_ diff --git a/src/tree_sitter/clock.h b/src/tree_sitter/clock.h deleted file mode 100644 index 94545f3566..0000000000 --- a/src/tree_sitter/clock.h +++ /dev/null @@ -1,141 +0,0 @@ -#ifndef TREE_SITTER_CLOCK_H_ -#define TREE_SITTER_CLOCK_H_ - -#include <stdint.h> - -typedef uint64_t TSDuration; - -#ifdef _WIN32 - -// Windows: -// * Represent a time as a performance counter value. -// * Represent a duration as a number of performance counter ticks. - -#include <windows.h> -typedef uint64_t TSClock; - -static inline TSDuration duration_from_micros(uint64_t micros) { - LARGE_INTEGER frequency; - QueryPerformanceFrequency(&frequency); - return micros * (uint64_t)frequency.QuadPart / 1000000; -} - -static inline uint64_t duration_to_micros(TSDuration self) { - LARGE_INTEGER frequency; - QueryPerformanceFrequency(&frequency); - return self * 1000000 / (uint64_t)frequency.QuadPart; -} - -static inline TSClock clock_null(void) { - return 0; -} - -static inline TSClock clock_now(void) { - LARGE_INTEGER result; - QueryPerformanceCounter(&result); - return (uint64_t)result.QuadPart; -} - -static inline TSClock clock_after(TSClock base, TSDuration duration) { - return base + duration; -} - -static inline bool clock_is_null(TSClock self) { - return !self; -} - -static inline bool clock_is_gt(TSClock self, TSClock other) { - return self > other; -} - -#elif defined(CLOCK_MONOTONIC) && !defined(__APPLE__) - -// POSIX with monotonic clock support (Linux) -// * Represent a time as a monotonic (seconds, nanoseconds) pair. -// * Represent a duration as a number of microseconds. -// -// On these platforms, parse timeouts will correspond accurately to -// real time, regardless of what other processes are running. - -#include <time.h> -typedef struct timespec TSClock; - -static inline TSDuration duration_from_micros(uint64_t micros) { - return micros; -} - -static inline uint64_t duration_to_micros(TSDuration self) { - return self; -} - -static inline TSClock clock_now(void) { - TSClock result; - clock_gettime(CLOCK_MONOTONIC, &result); - return result; -} - -static inline TSClock clock_null(void) { - return (TSClock) {0, 0}; -} - -static inline TSClock clock_after(TSClock base, TSDuration duration) { - TSClock result = base; - result.tv_sec += duration / 1000000; - result.tv_nsec += (duration % 1000000) * 1000; - return result; -} - -static inline bool clock_is_null(TSClock self) { - return !self.tv_sec; -} - -static inline bool clock_is_gt(TSClock self, TSClock other) { - if (self.tv_sec > other.tv_sec) return true; - if (self.tv_sec < other.tv_sec) return false; - return self.tv_nsec > other.tv_nsec; -} - -#else - -// macOS or POSIX without monotonic clock support -// * Represent a time as a process clock value. -// * Represent a duration as a number of process clock ticks. -// -// On these platforms, parse timeouts may be affected by other processes, -// which is not ideal, but is better than using a non-monotonic time API -// like `gettimeofday`. - -#include <time.h> -typedef uint64_t TSClock; - -static inline TSDuration duration_from_micros(uint64_t micros) { - return micros * (uint64_t)CLOCKS_PER_SEC / 1000000; -} - -static inline uint64_t duration_to_micros(TSDuration self) { - return self * 1000000 / (uint64_t)CLOCKS_PER_SEC; -} - -static inline TSClock clock_null(void) { - return 0; -} - -static inline TSClock clock_now(void) { - return (uint64_t)clock(); -} - -static inline TSClock clock_after(TSClock base, TSDuration duration) { - return base + duration; -} - -static inline bool clock_is_null(TSClock self) { - return !self; -} - -static inline bool clock_is_gt(TSClock self, TSClock other) { - return self > other; -} - -#endif - -#endif // TREE_SITTER_CLOCK_H_ diff --git a/src/tree_sitter/error_costs.h b/src/tree_sitter/error_costs.h deleted file mode 100644 index 32d3666a66..0000000000 --- a/src/tree_sitter/error_costs.h +++ /dev/null @@ -1,11 +0,0 @@ -#ifndef TREE_SITTER_ERROR_COSTS_H_ -#define TREE_SITTER_ERROR_COSTS_H_ - -#define ERROR_STATE 0 -#define ERROR_COST_PER_RECOVERY 500 -#define ERROR_COST_PER_MISSING_TREE 110 -#define ERROR_COST_PER_SKIPPED_TREE 100 -#define ERROR_COST_PER_SKIPPED_LINE 30 -#define ERROR_COST_PER_SKIPPED_CHAR 1 - -#endif diff --git a/src/tree_sitter/get_changed_ranges.c b/src/tree_sitter/get_changed_ranges.c deleted file mode 100644 index 5bd1d814bd..0000000000 --- a/src/tree_sitter/get_changed_ranges.c +++ /dev/null @@ -1,482 +0,0 @@ -#include "./get_changed_ranges.h" -#include "./subtree.h" -#include "./language.h" -#include "./error_costs.h" -#include "./tree_cursor.h" -#include <assert.h> - -// #define DEBUG_GET_CHANGED_RANGES - -static void ts_range_array_add(TSRangeArray *self, Length start, Length end) { - if (self->size > 0) { - TSRange *last_range = array_back(self); - if (start.bytes <= last_range->end_byte) { - last_range->end_byte = end.bytes; - last_range->end_point = end.extent; - return; - } - } - - if (start.bytes < end.bytes) { - TSRange range = { start.extent, end.extent, start.bytes, end.bytes }; - array_push(self, range); - } -} - -bool ts_range_array_intersects(const TSRangeArray *self, unsigned start_index, - uint32_t start_byte, uint32_t end_byte) { - for (unsigned i = start_index; i < self->size; i++) { - TSRange *range = &self->contents[i]; - if (range->end_byte > start_byte) { - if (range->start_byte >= end_byte) break; - return true; - } - } - return false; -} - -void ts_range_array_get_changed_ranges( - const TSRange *old_ranges, unsigned old_range_count, - const TSRange *new_ranges, unsigned new_range_count, - TSRangeArray *differences -) { - unsigned new_index = 0; - unsigned old_index = 0; - Length current_position = length_zero(); - bool in_old_range = false; - bool in_new_range = false; - - while (old_index < old_range_count || new_index < new_range_count) { - const TSRange *old_range = &old_ranges[old_index]; - const TSRange *new_range = &new_ranges[new_index]; - - Length next_old_position; - if (in_old_range) { - next_old_position = (Length) {old_range->end_byte, old_range->end_point}; - } else if (old_index < old_range_count) { - next_old_position = (Length) {old_range->start_byte, old_range->start_point}; - } else { - next_old_position = LENGTH_MAX; - } - - Length next_new_position; - if (in_new_range) { - next_new_position = (Length) {new_range->end_byte, new_range->end_point}; - } else if (new_index < new_range_count) { - next_new_position = (Length) {new_range->start_byte, new_range->start_point}; - } else { - next_new_position = LENGTH_MAX; - } - - if (next_old_position.bytes < next_new_position.bytes) { - if (in_old_range != in_new_range) { - ts_range_array_add(differences, current_position, next_old_position); - } - if (in_old_range) old_index++; - current_position = next_old_position; - in_old_range = !in_old_range; - } else if (next_new_position.bytes < next_old_position.bytes) { - if (in_old_range != in_new_range) { - ts_range_array_add(differences, current_position, next_new_position); - } - if (in_new_range) new_index++; - current_position = next_new_position; - in_new_range = !in_new_range; - } else { - if (in_old_range != in_new_range) { - ts_range_array_add(differences, current_position, next_new_position); - } - if (in_old_range) old_index++; - if (in_new_range) new_index++; - in_old_range = !in_old_range; - in_new_range = !in_new_range; - current_position = next_new_position; - } - } -} - -typedef struct { - TreeCursor cursor; - const TSLanguage *language; - unsigned visible_depth; - bool in_padding; -} Iterator; - -static Iterator iterator_new(TreeCursor *cursor, const Subtree *tree, const TSLanguage *language) { - array_clear(&cursor->stack); - array_push(&cursor->stack, ((TreeCursorEntry){ - .subtree = tree, - .position = length_zero(), - .child_index = 0, - .structural_child_index = 0, - })); - return (Iterator) { - .cursor = *cursor, - .language = language, - .visible_depth = 1, - .in_padding = false, - }; -} - -static bool iterator_done(Iterator *self) { - return self->cursor.stack.size == 0; -} - -static Length iterator_start_position(Iterator *self) { - TreeCursorEntry entry = *array_back(&self->cursor.stack); - if (self->in_padding) { - return entry.position; - } else { - return length_add(entry.position, ts_subtree_padding(*entry.subtree)); - } -} - -static Length iterator_end_position(Iterator *self) { - TreeCursorEntry entry = *array_back(&self->cursor.stack); - Length result = length_add(entry.position, ts_subtree_padding(*entry.subtree)); - if (self->in_padding) { - return result; - } else { - return length_add(result, ts_subtree_size(*entry.subtree)); - } -} - -static bool iterator_tree_is_visible(const Iterator *self) { - TreeCursorEntry entry = *array_back(&self->cursor.stack); - if (ts_subtree_visible(*entry.subtree)) return true; - if (self->cursor.stack.size > 1) { - Subtree parent = *self->cursor.stack.contents[self->cursor.stack.size - 2].subtree; - const TSSymbol *alias_sequence = ts_language_alias_sequence( - self->language, - parent.ptr->production_id - ); - return alias_sequence && alias_sequence[entry.structural_child_index] != 0; - } - return false; -} - -static void iterator_get_visible_state(const Iterator *self, Subtree *tree, - TSSymbol *alias_symbol, uint32_t *start_byte) { - uint32_t i = self->cursor.stack.size - 1; - - if (self->in_padding) { - if (i == 0) return; - i--; - } - - for (; i + 1 > 0; i--) { - TreeCursorEntry entry = self->cursor.stack.contents[i]; - - if (i > 0) { - const Subtree *parent = self->cursor.stack.contents[i - 1].subtree; - const TSSymbol *alias_sequence = ts_language_alias_sequence( - self->language, - parent->ptr->production_id - ); - if (alias_sequence) { - *alias_symbol = alias_sequence[entry.structural_child_index]; - } - } - - if (ts_subtree_visible(*entry.subtree) || *alias_symbol) { - *tree = *entry.subtree; - *start_byte = entry.position.bytes; - break; - } - } -} - -static void iterator_ascend(Iterator *self) { - if (iterator_done(self)) return; - if (iterator_tree_is_visible(self) && !self->in_padding) self->visible_depth--; - if (array_back(&self->cursor.stack)->child_index > 0) self->in_padding = false; - self->cursor.stack.size--; -} - -static bool iterator_descend(Iterator *self, uint32_t goal_position) { - if (self->in_padding) return false; - - bool did_descend; - do { - did_descend = false; - TreeCursorEntry entry = *array_back(&self->cursor.stack); - Length position = entry.position; - uint32_t structural_child_index = 0; - for (uint32_t i = 0, n = ts_subtree_child_count(*entry.subtree); i < n; i++) { - const Subtree *child = &entry.subtree->ptr->children[i]; - Length child_left = length_add(position, ts_subtree_padding(*child)); - Length child_right = length_add(child_left, ts_subtree_size(*child)); - - if (child_right.bytes > goal_position) { - array_push(&self->cursor.stack, ((TreeCursorEntry){ - .subtree = child, - .position = position, - .child_index = i, - .structural_child_index = structural_child_index, - })); - - if (iterator_tree_is_visible(self)) { - if (child_left.bytes > goal_position) { - self->in_padding = true; - } else { - self->visible_depth++; - } - return true; - } - - did_descend = true; - break; - } - - position = child_right; - if (!ts_subtree_extra(*child)) structural_child_index++; - } - } while (did_descend); - - return false; -} - -static void iterator_advance(Iterator *self) { - if (self->in_padding) { - self->in_padding = false; - if (iterator_tree_is_visible(self)) { - self->visible_depth++; - } else { - iterator_descend(self, 0); - } - return; - } - - for (;;) { - if (iterator_tree_is_visible(self)) self->visible_depth--; - TreeCursorEntry entry = array_pop(&self->cursor.stack); - if (iterator_done(self)) return; - - const Subtree *parent = array_back(&self->cursor.stack)->subtree; - uint32_t child_index = entry.child_index + 1; - if (ts_subtree_child_count(*parent) > child_index) { - Length position = length_add(entry.position, ts_subtree_total_size(*entry.subtree)); - uint32_t structural_child_index = entry.structural_child_index; - if (!ts_subtree_extra(*entry.subtree)) structural_child_index++; - const Subtree *next_child = &parent->ptr->children[child_index]; - - array_push(&self->cursor.stack, ((TreeCursorEntry){ - .subtree = next_child, - .position = position, - .child_index = child_index, - .structural_child_index = structural_child_index, - })); - - if (iterator_tree_is_visible(self)) { - if (ts_subtree_padding(*next_child).bytes > 0) { - self->in_padding = true; - } else { - self->visible_depth++; - } - } else { - iterator_descend(self, 0); - } - break; - } - } -} - -typedef enum { - IteratorDiffers, - IteratorMayDiffer, - IteratorMatches, -} IteratorComparison; - -static IteratorComparison iterator_compare(const Iterator *old_iter, const Iterator *new_iter) { - Subtree old_tree = NULL_SUBTREE; - Subtree new_tree = NULL_SUBTREE; - uint32_t old_start = 0; - uint32_t new_start = 0; - TSSymbol old_alias_symbol = 0; - TSSymbol new_alias_symbol = 0; - iterator_get_visible_state(old_iter, &old_tree, &old_alias_symbol, &old_start); - iterator_get_visible_state(new_iter, &new_tree, &new_alias_symbol, &new_start); - - if (!old_tree.ptr && !new_tree.ptr) return IteratorMatches; - if (!old_tree.ptr || !new_tree.ptr) return IteratorDiffers; - - if ( - old_alias_symbol == new_alias_symbol && - ts_subtree_symbol(old_tree) == ts_subtree_symbol(new_tree) - ) { - if (old_start == new_start && - !ts_subtree_has_changes(old_tree) && - ts_subtree_symbol(old_tree) != ts_builtin_sym_error && - ts_subtree_size(old_tree).bytes == ts_subtree_size(new_tree).bytes && - ts_subtree_parse_state(old_tree) != TS_TREE_STATE_NONE && - ts_subtree_parse_state(new_tree) != TS_TREE_STATE_NONE && - (ts_subtree_parse_state(old_tree) == ERROR_STATE) == - (ts_subtree_parse_state(new_tree) == ERROR_STATE)) { - return IteratorMatches; - } else { - return IteratorMayDiffer; - } - } - - return IteratorDiffers; -} - -#ifdef DEBUG_GET_CHANGED_RANGES -static inline void iterator_print_state(Iterator *self) { - TreeCursorEntry entry = *array_back(&self->cursor.stack); - TSPoint start = iterator_start_position(self).extent; - TSPoint end = iterator_end_position(self).extent; - const char *name = ts_language_symbol_name(self->language, ts_subtree_symbol(*entry.subtree)); - printf( - "(%-25s %s\t depth:%u [%u, %u] - [%u, %u])", - name, self->in_padding ? "(p)" : " ", - self->visible_depth, - start.row + 1, start.column, - end.row + 1, end.column - ); -} -#endif - -unsigned ts_subtree_get_changed_ranges(const Subtree *old_tree, const Subtree *new_tree, - TreeCursor *cursor1, TreeCursor *cursor2, - const TSLanguage *language, - const TSRangeArray *included_range_differences, - TSRange **ranges) { - TSRangeArray results = array_new(); - - Iterator old_iter = iterator_new(cursor1, old_tree, language); - Iterator new_iter = iterator_new(cursor2, new_tree, language); - - unsigned included_range_difference_index = 0; - - Length position = iterator_start_position(&old_iter); - Length next_position = iterator_start_position(&new_iter); - if (position.bytes < next_position.bytes) { - ts_range_array_add(&results, position, next_position); - position = next_position; - } else if (position.bytes > next_position.bytes) { - ts_range_array_add(&results, next_position, position); - next_position = position; - } - - do { - #ifdef DEBUG_GET_CHANGED_RANGES - printf("At [%-2u, %-2u] Compare ", position.extent.row + 1, position.extent.column); - iterator_print_state(&old_iter); - printf("\tvs\t"); - iterator_print_state(&new_iter); - puts(""); - #endif - - // Compare the old and new subtrees. - IteratorComparison comparison = iterator_compare(&old_iter, &new_iter); - - // Even if the two subtrees appear to be identical, they could differ - // internally if they contain a range of text that was previously - // excluded from the parse, and is now included, or vice-versa. - if (comparison == IteratorMatches && ts_range_array_intersects( - included_range_differences, - included_range_difference_index, - position.bytes, - iterator_end_position(&old_iter).bytes - )) { - comparison = IteratorMayDiffer; - } - - bool is_changed = false; - switch (comparison) { - // If the subtrees are definitely identical, move to the end - // of both subtrees. - case IteratorMatches: - next_position = iterator_end_position(&old_iter); - break; - - // If the subtrees might differ internally, descend into both - // subtrees, finding the first child that spans the current position. - case IteratorMayDiffer: - if (iterator_descend(&old_iter, position.bytes)) { - if (!iterator_descend(&new_iter, position.bytes)) { - is_changed = true; - next_position = iterator_end_position(&old_iter); - } - } else if (iterator_descend(&new_iter, position.bytes)) { - is_changed = true; - next_position = iterator_end_position(&new_iter); - } else { - next_position = length_min( - iterator_end_position(&old_iter), - iterator_end_position(&new_iter) - ); - } - break; - - // If the subtrees are different, record a change and then move - // to the end of both subtrees. - case IteratorDiffers: - is_changed = true; - next_position = length_min( - iterator_end_position(&old_iter), - iterator_end_position(&new_iter) - ); - break; - } - - // Ensure that both iterators are caught up to the current position. - while ( - !iterator_done(&old_iter) && - iterator_end_position(&old_iter).bytes <= next_position.bytes - ) iterator_advance(&old_iter); - while ( - !iterator_done(&new_iter) && - iterator_end_position(&new_iter).bytes <= next_position.bytes - ) iterator_advance(&new_iter); - - // Ensure that both iterators are at the same depth in the tree. - while (old_iter.visible_depth > new_iter.visible_depth) { - iterator_ascend(&old_iter); - } - while (new_iter.visible_depth > old_iter.visible_depth) { - iterator_ascend(&new_iter); - } - - if (is_changed) { - #ifdef DEBUG_GET_CHANGED_RANGES - printf( - " change: [[%u, %u] - [%u, %u]]\n", - position.extent.row + 1, position.extent.column, - next_position.extent.row + 1, next_position.extent.column - ); - #endif - - ts_range_array_add(&results, position, next_position); - } - - position = next_position; - - // Keep track of the current position in the included range differences - // array in order to avoid scanning the entire array on each iteration. - while (included_range_difference_index < included_range_differences->size) { - const TSRange *range = &included_range_differences->contents[ - included_range_difference_index - ]; - if (range->end_byte <= position.bytes) { - included_range_difference_index++; - } else { - break; - } - } - } while (!iterator_done(&old_iter) && !iterator_done(&new_iter)); - - Length old_size = ts_subtree_total_size(*old_tree); - Length new_size = ts_subtree_total_size(*new_tree); - if (old_size.bytes < new_size.bytes) { - ts_range_array_add(&results, old_size, new_size); - } else if (new_size.bytes < old_size.bytes) { - ts_range_array_add(&results, new_size, old_size); - } - - *cursor1 = old_iter.cursor; - *cursor2 = new_iter.cursor; - *ranges = results.contents; - return results.size; -} diff --git a/src/tree_sitter/get_changed_ranges.h b/src/tree_sitter/get_changed_ranges.h deleted file mode 100644 index a1f1dbb430..0000000000 --- a/src/tree_sitter/get_changed_ranges.h +++ /dev/null @@ -1,36 +0,0 @@ -#ifndef TREE_SITTER_GET_CHANGED_RANGES_H_ -#define TREE_SITTER_GET_CHANGED_RANGES_H_ - -#ifdef __cplusplus -extern "C" { -#endif - -#include "./tree_cursor.h" -#include "./subtree.h" - -typedef Array(TSRange) TSRangeArray; - -void ts_range_array_get_changed_ranges( - const TSRange *old_ranges, unsigned old_range_count, - const TSRange *new_ranges, unsigned new_range_count, - TSRangeArray *differences -); - -bool ts_range_array_intersects( - const TSRangeArray *self, unsigned start_index, - uint32_t start_byte, uint32_t end_byte -); - -unsigned ts_subtree_get_changed_ranges( - const Subtree *old_tree, const Subtree *new_tree, - TreeCursor *cursor1, TreeCursor *cursor2, - const TSLanguage *language, - const TSRangeArray *included_range_differences, - TSRange **ranges -); - -#ifdef __cplusplus -} -#endif - -#endif // TREE_SITTER_GET_CHANGED_RANGES_H_ diff --git a/src/tree_sitter/language.c b/src/tree_sitter/language.c deleted file mode 100644 index c00c49e3c0..0000000000 --- a/src/tree_sitter/language.c +++ /dev/null @@ -1,149 +0,0 @@ -#include "./language.h" -#include "./subtree.h" -#include "./error_costs.h" -#include <string.h> - -uint32_t ts_language_symbol_count(const TSLanguage *self) { - return self->symbol_count + self->alias_count; -} - -uint32_t ts_language_version(const TSLanguage *self) { - return self->version; -} - -uint32_t ts_language_field_count(const TSLanguage *self) { - if (self->version >= TREE_SITTER_LANGUAGE_VERSION_WITH_FIELDS) { - return self->field_count; - } else { - return 0; - } -} - -void ts_language_table_entry( - const TSLanguage *self, - TSStateId state, - TSSymbol symbol, - TableEntry *result -) { - if (symbol == ts_builtin_sym_error || symbol == ts_builtin_sym_error_repeat) { - result->action_count = 0; - result->is_reusable = false; - result->actions = NULL; - } else { - assert(symbol < self->token_count); - uint32_t action_index = ts_language_lookup(self, state, symbol); - const TSParseActionEntry *entry = &self->parse_actions[action_index]; - result->action_count = entry->entry.count; - result->is_reusable = entry->entry.reusable; - result->actions = (const TSParseAction *)(entry + 1); - } -} - -TSSymbolMetadata ts_language_symbol_metadata( - const TSLanguage *self, - TSSymbol symbol -) { - if (symbol == ts_builtin_sym_error) { - return (TSSymbolMetadata){.visible = true, .named = true}; - } else if (symbol == ts_builtin_sym_error_repeat) { - return (TSSymbolMetadata){.visible = false, .named = false}; - } else { - return self->symbol_metadata[symbol]; - } -} - -TSSymbol ts_language_public_symbol( - const TSLanguage *self, - TSSymbol symbol -) { - if (symbol == ts_builtin_sym_error) return symbol; - if (self->version >= TREE_SITTER_LANGUAGE_VERSION_WITH_SYMBOL_DEDUPING) { - return self->public_symbol_map[symbol]; - } else { - return symbol; - } -} - -const char *ts_language_symbol_name( - const TSLanguage *self, - TSSymbol symbol -) { - if (symbol == ts_builtin_sym_error) { - return "ERROR"; - } else if (symbol == ts_builtin_sym_error_repeat) { - return "_ERROR"; - } else if (symbol < ts_language_symbol_count(self)) { - return self->symbol_names[symbol]; - } else { - return NULL; - } -} - -TSSymbol ts_language_symbol_for_name( - const TSLanguage *self, - const char *string, - uint32_t length, - bool is_named -) { - if (!strncmp(string, "ERROR", length)) return ts_builtin_sym_error; - uint32_t count = ts_language_symbol_count(self); - for (TSSymbol i = 0; i < count; i++) { - TSSymbolMetadata metadata = ts_language_symbol_metadata(self, i); - if (!metadata.visible || metadata.named != is_named) continue; - const char *symbol_name = self->symbol_names[i]; - if (!strncmp(symbol_name, string, length) && !symbol_name[length]) { - if (self->version >= TREE_SITTER_LANGUAGE_VERSION_WITH_SYMBOL_DEDUPING) { - return self->public_symbol_map[i]; - } else { - return i; - } - } - } - return 0; -} - -TSSymbolType ts_language_symbol_type( - const TSLanguage *self, - TSSymbol symbol -) { - TSSymbolMetadata metadata = ts_language_symbol_metadata(self, symbol); - if (metadata.named) { - return TSSymbolTypeRegular; - } else if (metadata.visible) { - return TSSymbolTypeAnonymous; - } else { - return TSSymbolTypeAuxiliary; - } -} - -const char *ts_language_field_name_for_id( - const TSLanguage *self, - TSFieldId id -) { - uint32_t count = ts_language_field_count(self); - if (count && id <= count) { - return self->field_names[id]; - } else { - return NULL; - } -} - -TSFieldId ts_language_field_id_for_name( - const TSLanguage *self, - const char *name, - uint32_t name_length -) { - uint32_t count = ts_language_field_count(self); - for (TSSymbol i = 1; i < count + 1; i++) { - switch (strncmp(name, self->field_names[i], name_length)) { - case 0: - if (self->field_names[i][name_length] == 0) return i; - break; - case -1: - return 0; - default: - break; - } - } - return 0; -} diff --git a/src/tree_sitter/language.h b/src/tree_sitter/language.h deleted file mode 100644 index 341f0f85af..0000000000 --- a/src/tree_sitter/language.h +++ /dev/null @@ -1,143 +0,0 @@ -#ifndef TREE_SITTER_LANGUAGE_H_ -#define TREE_SITTER_LANGUAGE_H_ - -#ifdef __cplusplus -extern "C" { -#endif - -#include "./subtree.h" -#include "tree_sitter/parser.h" - -#define ts_builtin_sym_error_repeat (ts_builtin_sym_error - 1) -#define TREE_SITTER_LANGUAGE_VERSION_WITH_FIELDS 10 -#define TREE_SITTER_LANGUAGE_VERSION_WITH_SYMBOL_DEDUPING 11 -#define TREE_SITTER_LANGUAGE_VERSION_WITH_SMALL_STATES 11 - -typedef struct { - const TSParseAction *actions; - uint32_t action_count; - bool is_reusable; -} TableEntry; - -void ts_language_table_entry(const TSLanguage *, TSStateId, TSSymbol, TableEntry *); - -TSSymbolMetadata ts_language_symbol_metadata(const TSLanguage *, TSSymbol); - -TSSymbol ts_language_public_symbol(const TSLanguage *, TSSymbol); - -static inline bool ts_language_is_symbol_external(const TSLanguage *self, TSSymbol symbol) { - return 0 < symbol && symbol < self->external_token_count + 1; -} - -static inline const TSParseAction *ts_language_actions( - const TSLanguage *self, - TSStateId state, - TSSymbol symbol, - uint32_t *count -) { - TableEntry entry; - ts_language_table_entry(self, state, symbol, &entry); - *count = entry.action_count; - return entry.actions; -} - -static inline bool ts_language_has_actions(const TSLanguage *self, - TSStateId state, - TSSymbol symbol) { - TableEntry entry; - ts_language_table_entry(self, state, symbol, &entry); - return entry.action_count > 0; -} - -static inline bool ts_language_has_reduce_action(const TSLanguage *self, - TSStateId state, - TSSymbol symbol) { - TableEntry entry; - ts_language_table_entry(self, state, symbol, &entry); - return entry.action_count > 0 && entry.actions[0].type == TSParseActionTypeReduce; -} - -static inline uint16_t ts_language_lookup( - const TSLanguage *self, - TSStateId state, - TSSymbol symbol -) { - if ( - self->version >= TREE_SITTER_LANGUAGE_VERSION_WITH_SMALL_STATES && - state >= self->large_state_count - ) { - uint32_t index = self->small_parse_table_map[state - self->large_state_count]; - const uint16_t *data = &self->small_parse_table[index]; - uint16_t section_count = *(data++); - for (unsigned i = 0; i < section_count; i++) { - uint16_t section_value = *(data++); - uint16_t symbol_count = *(data++); - for (unsigned i = 0; i < symbol_count; i++) { - if (*(data++) == symbol) return section_value; - } - } - return 0; - } else { - return self->parse_table[state * self->symbol_count + symbol]; - } -} - -static inline TSStateId ts_language_next_state(const TSLanguage *self, - TSStateId state, - TSSymbol symbol) { - if (symbol == ts_builtin_sym_error || symbol == ts_builtin_sym_error_repeat) { - return 0; - } else if (symbol < self->token_count) { - uint32_t count; - const TSParseAction *actions = ts_language_actions(self, state, symbol, &count); - if (count > 0) { - TSParseAction action = actions[count - 1]; - if (action.type == TSParseActionTypeShift) { - return action.params.shift.extra ? state : action.params.shift.state; - } - } - return 0; - } else { - return ts_language_lookup(self, state, symbol); - } -} - -static inline const bool * -ts_language_enabled_external_tokens(const TSLanguage *self, - unsigned external_scanner_state) { - if (external_scanner_state == 0) { - return NULL; - } else { - return self->external_scanner.states + self->external_token_count * external_scanner_state; - } -} - -static inline const TSSymbol * -ts_language_alias_sequence(const TSLanguage *self, uint32_t production_id) { - return production_id > 0 ? - self->alias_sequences + production_id * self->max_alias_sequence_length : - NULL; -} - -static inline void ts_language_field_map( - const TSLanguage *self, - uint32_t production_id, - const TSFieldMapEntry **start, - const TSFieldMapEntry **end -) { - if (self->version < TREE_SITTER_LANGUAGE_VERSION_WITH_FIELDS || self->field_count == 0) { - *start = NULL; - *end = NULL; - return; - } - - TSFieldMapSlice slice = self->field_map_slices[production_id]; - *start = &self->field_map_entries[slice.index]; - *end = &self->field_map_entries[slice.index] + slice.length; -} - -#ifdef __cplusplus -} -#endif - -#endif // TREE_SITTER_LANGUAGE_H_ diff --git a/src/tree_sitter/length.h b/src/tree_sitter/length.h deleted file mode 100644 index 61de9fc1d5..0000000000 --- a/src/tree_sitter/length.h +++ /dev/null @@ -1,44 +0,0 @@ -#ifndef TREE_SITTER_LENGTH_H_ -#define TREE_SITTER_LENGTH_H_ - -#include <stdlib.h> -#include <stdbool.h> -#include "./point.h" -#include "tree_sitter/api.h" - -typedef struct { - uint32_t bytes; - TSPoint extent; -} Length; - -static const Length LENGTH_UNDEFINED = {0, {0, 1}}; -static const Length LENGTH_MAX = {UINT32_MAX, {UINT32_MAX, UINT32_MAX}}; - -static inline bool length_is_undefined(Length length) { - return length.bytes == 0 && length.extent.column != 0; -} - -static inline Length length_min(Length len1, Length len2) { - return (len1.bytes < len2.bytes) ? len1 : len2; -} - -static inline Length length_add(Length len1, Length len2) { - Length result; - result.bytes = len1.bytes + len2.bytes; - result.extent = point_add(len1.extent, len2.extent); - return result; -} - -static inline Length length_sub(Length len1, Length len2) { - Length result; - result.bytes = len1.bytes - len2.bytes; - result.extent = point_sub(len1.extent, len2.extent); - return result; -} - -static inline Length length_zero(void) { - Length result = {0, {0, 0}}; - return result; -} - -#endif diff --git a/src/tree_sitter/lexer.c b/src/tree_sitter/lexer.c deleted file mode 100644 index 3f8a4c0ae8..0000000000 --- a/src/tree_sitter/lexer.c +++ /dev/null @@ -1,391 +0,0 @@ -#include <stdio.h> -#include "./lexer.h" -#include "./subtree.h" -#include "./length.h" -#include "./unicode.h" - -#define LOG(message, character) \ - if (self->logger.log) { \ - snprintf( \ - self->debug_buffer, \ - TREE_SITTER_SERIALIZATION_BUFFER_SIZE, \ - 32 <= character && character < 127 ? \ - message " character:'%c'" : \ - message " character:%d", \ - character \ - ); \ - self->logger.log( \ - self->logger.payload, \ - TSLogTypeLex, \ - self->debug_buffer \ - ); \ - } - -static const int32_t BYTE_ORDER_MARK = 0xFEFF; - -static const TSRange DEFAULT_RANGE = { - .start_point = { - .row = 0, - .column = 0, - }, - .end_point = { - .row = UINT32_MAX, - .column = UINT32_MAX, - }, - .start_byte = 0, - .end_byte = UINT32_MAX -}; - -// Check if the lexer has reached EOF. This state is stored -// by setting the lexer's `current_included_range_index` such that -// it has consumed all of its available ranges. -static bool ts_lexer__eof(const TSLexer *_self) { - Lexer *self = (Lexer *)_self; - return self->current_included_range_index == self->included_range_count; -} - -// Clear the currently stored chunk of source code, because the lexer's -// position has changed. -static void ts_lexer__clear_chunk(Lexer *self) { - self->chunk = NULL; - self->chunk_size = 0; - self->chunk_start = 0; -} - -// Call the lexer's input callback to obtain a new chunk of source code -// for the current position. -static void ts_lexer__get_chunk(Lexer *self) { - self->chunk_start = self->current_position.bytes; - self->chunk = self->input.read( - self->input.payload, - self->current_position.bytes, - self->current_position.extent, - &self->chunk_size - ); - if (!self->chunk_size) { - self->current_included_range_index = self->included_range_count; - self->chunk = NULL; - } -} - -// Decode the next unicode character in the current chunk of source code. -// This assumes that the lexer has already retrieved a chunk of source -// code that spans the current position. -static void ts_lexer__get_lookahead(Lexer *self) { - uint32_t position_in_chunk = self->current_position.bytes - self->chunk_start; - const uint8_t *chunk = (const uint8_t *)self->chunk + position_in_chunk; - uint32_t size = self->chunk_size - position_in_chunk; - - if (size == 0) { - self->lookahead_size = 1; - self->data.lookahead = '\0'; - return; - } - - UnicodeDecodeFunction decode = self->input.encoding == TSInputEncodingUTF8 - ? ts_decode_utf8 - : ts_decode_utf16; - - self->lookahead_size = decode(chunk, size, &self->data.lookahead); - - // If this chunk ended in the middle of a multi-byte character, - // try again with a fresh chunk. - if (self->data.lookahead == TS_DECODE_ERROR && size < 4) { - ts_lexer__get_chunk(self); - chunk = (const uint8_t *)self->chunk; - size = self->chunk_size; - self->lookahead_size = decode(chunk, size, &self->data.lookahead); - } - - if (self->data.lookahead == TS_DECODE_ERROR) { - self->lookahead_size = 1; - } -} - -// Advance to the next character in the source code, retrieving a new -// chunk of source code if needed. -static void ts_lexer__advance(TSLexer *_self, bool skip) { - Lexer *self = (Lexer *)_self; - if (!self->chunk) return; - - if (skip) { - LOG("skip", self->data.lookahead); - } else { - LOG("consume", self->data.lookahead); - } - - if (self->lookahead_size) { - self->current_position.bytes += self->lookahead_size; - if (self->data.lookahead == '\n') { - self->current_position.extent.row++; - self->current_position.extent.column = 0; - } else { - self->current_position.extent.column += self->lookahead_size; - } - } - - const TSRange *current_range = NULL; - if (self->current_included_range_index < self->included_range_count) { - current_range = &self->included_ranges[self->current_included_range_index]; - if (self->current_position.bytes == current_range->end_byte) { - self->current_included_range_index++; - if (self->current_included_range_index < self->included_range_count) { - current_range++; - self->current_position = (Length) { - current_range->start_byte, - current_range->start_point, - }; - } else { - current_range = NULL; - } - } - } - - if (skip) self->token_start_position = self->current_position; - - if (current_range) { - if (self->current_position.bytes >= self->chunk_start + self->chunk_size) { - ts_lexer__get_chunk(self); - } - ts_lexer__get_lookahead(self); - } else { - ts_lexer__clear_chunk(self); - self->data.lookahead = '\0'; - self->lookahead_size = 1; - } -} - -// Mark that a token match has completed. This can be called multiple -// times if a longer match is found later. -static void ts_lexer__mark_end(TSLexer *_self) { - Lexer *self = (Lexer *)_self; - if (!ts_lexer__eof(&self->data)) { - // If the lexer is right at the beginning of included range, - // then the token should be considered to end at the *end* of the - // previous included range, rather than here. - TSRange *current_included_range = &self->included_ranges[ - self->current_included_range_index - ]; - if ( - self->current_included_range_index > 0 && - self->current_position.bytes == current_included_range->start_byte - ) { - TSRange *previous_included_range = current_included_range - 1; - self->token_end_position = (Length) { - previous_included_range->end_byte, - previous_included_range->end_point, - }; - return; - } - } - self->token_end_position = self->current_position; -} - -static uint32_t ts_lexer__get_column(TSLexer *_self) { - Lexer *self = (Lexer *)_self; - uint32_t goal_byte = self->current_position.bytes; - - self->current_position.bytes -= self->current_position.extent.column; - self->current_position.extent.column = 0; - - if (self->current_position.bytes < self->chunk_start) { - ts_lexer__get_chunk(self); - } - - uint32_t result = 0; - while (self->current_position.bytes < goal_byte) { - ts_lexer__advance(&self->data, false); - result++; - } - - return result; -} - -// Is the lexer at a boundary between two disjoint included ranges of -// source code? This is exposed as an API because some languages' external -// scanners need to perform custom actions at these bounaries. -static bool ts_lexer__is_at_included_range_start(const TSLexer *_self) { - const Lexer *self = (const Lexer *)_self; - if (self->current_included_range_index < self->included_range_count) { - TSRange *current_range = &self->included_ranges[self->current_included_range_index]; - return self->current_position.bytes == current_range->start_byte; - } else { - return false; - } -} - -void ts_lexer_init(Lexer *self) { - *self = (Lexer) { - .data = { - // The lexer's methods are stored as struct fields so that generated - // parsers can call them without needing to be linked against this - // library. - .advance = ts_lexer__advance, - .mark_end = ts_lexer__mark_end, - .get_column = ts_lexer__get_column, - .is_at_included_range_start = ts_lexer__is_at_included_range_start, - .eof = ts_lexer__eof, - .lookahead = 0, - .result_symbol = 0, - }, - .chunk = NULL, - .chunk_size = 0, - .chunk_start = 0, - .current_position = {0, {0, 0}}, - .logger = { - .payload = NULL, - .log = NULL - }, - .included_ranges = NULL, - .included_range_count = 0, - .current_included_range_index = 0, - }; - ts_lexer_set_included_ranges(self, NULL, 0); -} - -void ts_lexer_delete(Lexer *self) { - ts_free(self->included_ranges); -} - -static void ts_lexer_goto(Lexer *self, Length position) { - self->current_position = position; - bool found_included_range = false; - - // Move to the first valid position at or after the given position. - for (unsigned i = 0; i < self->included_range_count; i++) { - TSRange *included_range = &self->included_ranges[i]; - if (included_range->end_byte > position.bytes) { - if (included_range->start_byte > position.bytes) { - self->current_position = (Length) { - .bytes = included_range->start_byte, - .extent = included_range->start_point, - }; - } - - self->current_included_range_index = i; - found_included_range = true; - break; - } - } - - if (found_included_range) { - // If the current position is outside of the current chunk of text, - // then clear out the current chunk of text. - if (self->chunk && ( - position.bytes < self->chunk_start || - position.bytes >= self->chunk_start + self->chunk_size - )) { - ts_lexer__clear_chunk(self); - } - - self->lookahead_size = 0; - self->data.lookahead = '\0'; - } - - // If the given position is beyond any of included ranges, move to the EOF - // state - past the end of the included ranges. - else { - self->current_included_range_index = self->included_range_count; - TSRange *last_included_range = &self->included_ranges[self->included_range_count - 1]; - self->current_position = (Length) { - .bytes = last_included_range->end_byte, - .extent = last_included_range->end_point, - }; - ts_lexer__clear_chunk(self); - self->lookahead_size = 1; - self->data.lookahead = '\0'; - } -} - -void ts_lexer_set_input(Lexer *self, TSInput input) { - self->input = input; - ts_lexer__clear_chunk(self); - ts_lexer_goto(self, self->current_position); -} - -// Move the lexer to the given position. This doesn't do any work -// if the parser is already at the given position. -void ts_lexer_reset(Lexer *self, Length position) { - if (position.bytes != self->current_position.bytes) { - ts_lexer_goto(self, position); - } -} - -void ts_lexer_start(Lexer *self) { - self->token_start_position = self->current_position; - self->token_end_position = LENGTH_UNDEFINED; - self->data.result_symbol = 0; - if (!ts_lexer__eof(&self->data)) { - if (!self->chunk_size) ts_lexer__get_chunk(self); - if (!self->lookahead_size) ts_lexer__get_lookahead(self); - if ( - self->current_position.bytes == 0 && - self->data.lookahead == BYTE_ORDER_MARK - ) ts_lexer__advance(&self->data, true); - } -} - -void ts_lexer_finish(Lexer *self, uint32_t *lookahead_end_byte) { - if (length_is_undefined(self->token_end_position)) { - ts_lexer__mark_end(&self->data); - } - - uint32_t current_lookahead_end_byte = self->current_position.bytes + 1; - - // In order to determine that a byte sequence is invalid UTF8 or UTF16, - // the character decoding algorithm may have looked at the following byte. - // Therefore, the next byte *after* the current (invalid) character - // affects the interpretation of the current character. - if (self->data.lookahead == TS_DECODE_ERROR) { - current_lookahead_end_byte++; - } - - if (current_lookahead_end_byte > *lookahead_end_byte) { - *lookahead_end_byte = current_lookahead_end_byte; - } -} - -void ts_lexer_advance_to_end(Lexer *self) { - while (self->chunk) { - ts_lexer__advance(&self->data, false); - } -} - -void ts_lexer_mark_end(Lexer *self) { - ts_lexer__mark_end(&self->data); -} - -bool ts_lexer_set_included_ranges( - Lexer *self, - const TSRange *ranges, - uint32_t count -) { - if (count == 0 || !ranges) { - ranges = &DEFAULT_RANGE; - count = 1; - } else { - uint32_t previous_byte = 0; - for (unsigned i = 0; i < count; i++) { - const TSRange *range = &ranges[i]; - if ( - range->start_byte < previous_byte || - range->end_byte < range->start_byte - ) return false; - previous_byte = range->end_byte; - } - } - - size_t size = count * sizeof(TSRange); - self->included_ranges = ts_realloc(self->included_ranges, size); - memcpy(self->included_ranges, ranges, size); - self->included_range_count = count; - ts_lexer_goto(self, self->current_position); - return true; -} - -TSRange *ts_lexer_included_ranges(const Lexer *self, uint32_t *count) { - *count = self->included_range_count; - return self->included_ranges; -} - -#undef LOG diff --git a/src/tree_sitter/lexer.h b/src/tree_sitter/lexer.h deleted file mode 100644 index 5e39294529..0000000000 --- a/src/tree_sitter/lexer.h +++ /dev/null @@ -1,48 +0,0 @@ -#ifndef TREE_SITTER_LEXER_H_ -#define TREE_SITTER_LEXER_H_ - -#ifdef __cplusplus -extern "C" { -#endif - -#include "./length.h" -#include "./subtree.h" -#include "tree_sitter/api.h" -#include "tree_sitter/parser.h" - -typedef struct { - TSLexer data; - Length current_position; - Length token_start_position; - Length token_end_position; - - TSRange *included_ranges; - size_t included_range_count; - size_t current_included_range_index; - - const char *chunk; - uint32_t chunk_start; - uint32_t chunk_size; - uint32_t lookahead_size; - - TSInput input; - TSLogger logger; - char debug_buffer[TREE_SITTER_SERIALIZATION_BUFFER_SIZE]; -} Lexer; - -void ts_lexer_init(Lexer *); -void ts_lexer_delete(Lexer *); -void ts_lexer_set_input(Lexer *, TSInput); -void ts_lexer_reset(Lexer *, Length); -void ts_lexer_start(Lexer *); -void ts_lexer_finish(Lexer *, uint32_t *); -void ts_lexer_advance_to_end(Lexer *); -void ts_lexer_mark_end(Lexer *); -bool ts_lexer_set_included_ranges(Lexer *self, const TSRange *ranges, uint32_t count); -TSRange *ts_lexer_included_ranges(const Lexer *self, uint32_t *count); - -#ifdef __cplusplus -} -#endif - -#endif // TREE_SITTER_LEXER_H_ diff --git a/src/tree_sitter/lib.c b/src/tree_sitter/lib.c deleted file mode 100644 index 289d32f4c5..0000000000 --- a/src/tree_sitter/lib.c +++ /dev/null @@ -1,17 +0,0 @@ -// The Tree-sitter library can be built by compiling this one source file. -// -// The following directories must be added to the include path: -// - include - -#define _POSIX_C_SOURCE 200112L - -#include "./get_changed_ranges.c" -#include "./language.c" -#include "./lexer.c" -#include "./node.c" -#include "./parser.c" -#include "./query.c" -#include "./stack.c" -#include "./subtree.c" -#include "./tree_cursor.c" -#include "./tree.c" diff --git a/src/tree_sitter/node.c b/src/tree_sitter/node.c deleted file mode 100644 index 576f3ef38e..0000000000 --- a/src/tree_sitter/node.c +++ /dev/null @@ -1,677 +0,0 @@ -#include <stdbool.h> -#include "./subtree.h" -#include "./tree.h" -#include "./language.h" - -typedef struct { - Subtree parent; - const TSTree *tree; - Length position; - uint32_t child_index; - uint32_t structural_child_index; - const TSSymbol *alias_sequence; -} NodeChildIterator; - -// TSNode - constructors - -TSNode ts_node_new( - const TSTree *tree, - const Subtree *subtree, - Length position, - TSSymbol alias -) { - return (TSNode) { - {position.bytes, position.extent.row, position.extent.column, alias}, - subtree, - tree, - }; -} - -static inline TSNode ts_node__null(void) { - return ts_node_new(NULL, NULL, length_zero(), 0); -} - -// TSNode - accessors - -uint32_t ts_node_start_byte(TSNode self) { - return self.context[0]; -} - -TSPoint ts_node_start_point(TSNode self) { - return (TSPoint) {self.context[1], self.context[2]}; -} - -static inline uint32_t ts_node__alias(const TSNode *self) { - return self->context[3]; -} - -static inline Subtree ts_node__subtree(TSNode self) { - return *(const Subtree *)self.id; -} - -// NodeChildIterator - -static inline NodeChildIterator ts_node_iterate_children(const TSNode *node) { - Subtree subtree = ts_node__subtree(*node); - if (ts_subtree_child_count(subtree) == 0) { - return (NodeChildIterator) {NULL_SUBTREE, node->tree, length_zero(), 0, 0, NULL}; - } - const TSSymbol *alias_sequence = ts_language_alias_sequence( - node->tree->language, - subtree.ptr->production_id - ); - return (NodeChildIterator) { - .tree = node->tree, - .parent = subtree, - .position = {ts_node_start_byte(*node), ts_node_start_point(*node)}, - .child_index = 0, - .structural_child_index = 0, - .alias_sequence = alias_sequence, - }; -} - -static inline bool ts_node_child_iterator_done(NodeChildIterator *self) { - return self->child_index == self->parent.ptr->child_count; -} - -static inline bool ts_node_child_iterator_next( - NodeChildIterator *self, - TSNode *result -) { - if (!self->parent.ptr || ts_node_child_iterator_done(self)) return false; - const Subtree *child = &self->parent.ptr->children[self->child_index]; - TSSymbol alias_symbol = 0; - if (!ts_subtree_extra(*child)) { - if (self->alias_sequence) { - alias_symbol = self->alias_sequence[self->structural_child_index]; - } - self->structural_child_index++; - } - if (self->child_index > 0) { - self->position = length_add(self->position, ts_subtree_padding(*child)); - } - *result = ts_node_new( - self->tree, - child, - self->position, - alias_symbol - ); - self->position = length_add(self->position, ts_subtree_size(*child)); - self->child_index++; - return true; -} - -// TSNode - private - -static inline bool ts_node__is_relevant(TSNode self, bool include_anonymous) { - Subtree tree = ts_node__subtree(self); - if (include_anonymous) { - return ts_subtree_visible(tree) || ts_node__alias(&self); - } else { - TSSymbol alias = ts_node__alias(&self); - if (alias) { - return ts_language_symbol_metadata(self.tree->language, alias).named; - } else { - return ts_subtree_visible(tree) && ts_subtree_named(tree); - } - } -} - -static inline uint32_t ts_node__relevant_child_count( - TSNode self, - bool include_anonymous -) { - Subtree tree = ts_node__subtree(self); - if (ts_subtree_child_count(tree) > 0) { - if (include_anonymous) { - return tree.ptr->visible_child_count; - } else { - return tree.ptr->named_child_count; - } - } else { - return 0; - } -} - -static inline TSNode ts_node__child( - TSNode self, - uint32_t child_index, - bool include_anonymous -) { - TSNode result = self; - bool did_descend = true; - - while (did_descend) { - did_descend = false; - - TSNode child; - uint32_t index = 0; - NodeChildIterator iterator = ts_node_iterate_children(&result); - while (ts_node_child_iterator_next(&iterator, &child)) { - if (ts_node__is_relevant(child, include_anonymous)) { - if (index == child_index) { - if (ts_node__is_relevant(self, true)) { - ts_tree_set_cached_parent(self.tree, &child, &self); - } - return child; - } - index++; - } else { - uint32_t grandchild_index = child_index - index; - uint32_t grandchild_count = ts_node__relevant_child_count(child, include_anonymous); - if (grandchild_index < grandchild_count) { - did_descend = true; - result = child; - child_index = grandchild_index; - break; - } - index += grandchild_count; - } - } - } - - return ts_node__null(); -} - -static bool ts_subtree_has_trailing_empty_descendant( - Subtree self, - Subtree other -) { - for (unsigned i = ts_subtree_child_count(self) - 1; i + 1 > 0; i--) { - Subtree child = self.ptr->children[i]; - if (ts_subtree_total_bytes(child) > 0) break; - if (child.ptr == other.ptr || ts_subtree_has_trailing_empty_descendant(child, other)) { - return true; - } - } - return false; -} - -static inline TSNode ts_node__prev_sibling(TSNode self, bool include_anonymous) { - Subtree self_subtree = ts_node__subtree(self); - bool self_is_empty = ts_subtree_total_bytes(self_subtree) == 0; - uint32_t target_end_byte = ts_node_end_byte(self); - - TSNode node = ts_node_parent(self); - TSNode earlier_node = ts_node__null(); - bool earlier_node_is_relevant = false; - - while (!ts_node_is_null(node)) { - TSNode earlier_child = ts_node__null(); - bool earlier_child_is_relevant = false; - bool found_child_containing_target = false; - - TSNode child; - NodeChildIterator iterator = ts_node_iterate_children(&node); - while (ts_node_child_iterator_next(&iterator, &child)) { - if (child.id == self.id) break; - if (iterator.position.bytes > target_end_byte) { - found_child_containing_target = true; - break; - } - - if (iterator.position.bytes == target_end_byte && - (!self_is_empty || - ts_subtree_has_trailing_empty_descendant(ts_node__subtree(child), self_subtree))) { - found_child_containing_target = true; - break; - } - - if (ts_node__is_relevant(child, include_anonymous)) { - earlier_child = child; - earlier_child_is_relevant = true; - } else if (ts_node__relevant_child_count(child, include_anonymous) > 0) { - earlier_child = child; - earlier_child_is_relevant = false; - } - } - - if (found_child_containing_target) { - if (!ts_node_is_null(earlier_child)) { - earlier_node = earlier_child; - earlier_node_is_relevant = earlier_child_is_relevant; - } - node = child; - } else if (earlier_child_is_relevant) { - return earlier_child; - } else if (!ts_node_is_null(earlier_child)) { - node = earlier_child; - } else if (earlier_node_is_relevant) { - return earlier_node; - } else { - node = earlier_node; - } - } - - return ts_node__null(); -} - -static inline TSNode ts_node__next_sibling(TSNode self, bool include_anonymous) { - uint32_t target_end_byte = ts_node_end_byte(self); - - TSNode node = ts_node_parent(self); - TSNode later_node = ts_node__null(); - bool later_node_is_relevant = false; - - while (!ts_node_is_null(node)) { - TSNode later_child = ts_node__null(); - bool later_child_is_relevant = false; - TSNode child_containing_target = ts_node__null(); - - TSNode child; - NodeChildIterator iterator = ts_node_iterate_children(&node); - while (ts_node_child_iterator_next(&iterator, &child)) { - if (iterator.position.bytes < target_end_byte) continue; - if (ts_node_start_byte(child) <= ts_node_start_byte(self)) { - if (ts_node__subtree(child).ptr != ts_node__subtree(self).ptr) { - child_containing_target = child; - } - } else if (ts_node__is_relevant(child, include_anonymous)) { - later_child = child; - later_child_is_relevant = true; - break; - } else if (ts_node__relevant_child_count(child, include_anonymous) > 0) { - later_child = child; - later_child_is_relevant = false; - break; - } - } - - if (!ts_node_is_null(child_containing_target)) { - if (!ts_node_is_null(later_child)) { - later_node = later_child; - later_node_is_relevant = later_child_is_relevant; - } - node = child_containing_target; - } else if (later_child_is_relevant) { - return later_child; - } else if (!ts_node_is_null(later_child)) { - node = later_child; - } else if (later_node_is_relevant) { - return later_node; - } else { - node = later_node; - } - } - - return ts_node__null(); -} - -static inline TSNode ts_node__first_child_for_byte( - TSNode self, - uint32_t goal, - bool include_anonymous -) { - TSNode node = self; - bool did_descend = true; - - while (did_descend) { - did_descend = false; - - TSNode child; - NodeChildIterator iterator = ts_node_iterate_children(&node); - while (ts_node_child_iterator_next(&iterator, &child)) { - if (ts_node_end_byte(child) > goal) { - if (ts_node__is_relevant(child, include_anonymous)) { - return child; - } else if (ts_node_child_count(child) > 0) { - did_descend = true; - node = child; - break; - } - } - } - } - - return ts_node__null(); -} - -static inline TSNode ts_node__descendant_for_byte_range( - TSNode self, - uint32_t range_start, - uint32_t range_end, - bool include_anonymous -) { - TSNode node = self; - TSNode last_visible_node = self; - - bool did_descend = true; - while (did_descend) { - did_descend = false; - - TSNode child; - NodeChildIterator iterator = ts_node_iterate_children(&node); - while (ts_node_child_iterator_next(&iterator, &child)) { - uint32_t node_end = iterator.position.bytes; - - // The end of this node must extend far enough forward to touch - // the end of the range and exceed the start of the range. - if (node_end < range_end) continue; - if (node_end <= range_start) continue; - - // The start of this node must extend far enough backward to - // touch the start of the range. - if (range_start < ts_node_start_byte(child)) break; - - node = child; - if (ts_node__is_relevant(node, include_anonymous)) { - ts_tree_set_cached_parent(self.tree, &child, &last_visible_node); - last_visible_node = node; - } - did_descend = true; - break; - } - } - - return last_visible_node; -} - -static inline TSNode ts_node__descendant_for_point_range( - TSNode self, - TSPoint range_start, - TSPoint range_end, - bool include_anonymous -) { - TSNode node = self; - TSNode last_visible_node = self; - - bool did_descend = true; - while (did_descend) { - did_descend = false; - - TSNode child; - NodeChildIterator iterator = ts_node_iterate_children(&node); - while (ts_node_child_iterator_next(&iterator, &child)) { - TSPoint node_end = iterator.position.extent; - - // The end of this node must extend far enough forward to touch - // the end of the range and exceed the start of the range. - if (point_lt(node_end, range_end)) continue; - if (point_lte(node_end, range_start)) continue; - - // The start of this node must extend far enough backward to - // touch the start of the range. - if (point_lt(range_start, ts_node_start_point(child))) break; - - node = child; - if (ts_node__is_relevant(node, include_anonymous)) { - ts_tree_set_cached_parent(self.tree, &child, &last_visible_node); - last_visible_node = node; - } - did_descend = true; - break; - } - } - - return last_visible_node; -} - -// TSNode - public - -uint32_t ts_node_end_byte(TSNode self) { - return ts_node_start_byte(self) + ts_subtree_size(ts_node__subtree(self)).bytes; -} - -TSPoint ts_node_end_point(TSNode self) { - return point_add(ts_node_start_point(self), ts_subtree_size(ts_node__subtree(self)).extent); -} - -TSSymbol ts_node_symbol(TSNode self) { - TSSymbol symbol = ts_node__alias(&self); - if (!symbol) symbol = ts_subtree_symbol(ts_node__subtree(self)); - return ts_language_public_symbol(self.tree->language, symbol); -} - -const char *ts_node_type(TSNode self) { - TSSymbol symbol = ts_node__alias(&self); - if (!symbol) symbol = ts_subtree_symbol(ts_node__subtree(self)); - return ts_language_symbol_name(self.tree->language, symbol); -} - -char *ts_node_string(TSNode self) { - return ts_subtree_string(ts_node__subtree(self), self.tree->language, false); -} - -bool ts_node_eq(TSNode self, TSNode other) { - return self.tree == other.tree && self.id == other.id; -} - -bool ts_node_is_null(TSNode self) { - return self.id == 0; -} - -bool ts_node_is_extra(TSNode self) { - return ts_subtree_extra(ts_node__subtree(self)); -} - -bool ts_node_is_named(TSNode self) { - TSSymbol alias = ts_node__alias(&self); - return alias - ? ts_language_symbol_metadata(self.tree->language, alias).named - : ts_subtree_named(ts_node__subtree(self)); -} - -bool ts_node_is_missing(TSNode self) { - return ts_subtree_missing(ts_node__subtree(self)); -} - -bool ts_node_has_changes(TSNode self) { - return ts_subtree_has_changes(ts_node__subtree(self)); -} - -bool ts_node_has_error(TSNode self) { - return ts_subtree_error_cost(ts_node__subtree(self)) > 0; -} - -TSNode ts_node_parent(TSNode self) { - TSNode node = ts_tree_get_cached_parent(self.tree, &self); - if (node.id) return node; - - node = ts_tree_root_node(self.tree); - uint32_t end_byte = ts_node_end_byte(self); - if (node.id == self.id) return ts_node__null(); - - TSNode last_visible_node = node; - bool did_descend = true; - while (did_descend) { - did_descend = false; - - TSNode child; - NodeChildIterator iterator = ts_node_iterate_children(&node); - while (ts_node_child_iterator_next(&iterator, &child)) { - if ( - ts_node_start_byte(child) > ts_node_start_byte(self) || - child.id == self.id - ) break; - if (iterator.position.bytes >= end_byte) { - node = child; - if (ts_node__is_relevant(child, true)) { - ts_tree_set_cached_parent(self.tree, &node, &last_visible_node); - last_visible_node = node; - } - did_descend = true; - break; - } - } - } - - return last_visible_node; -} - -TSNode ts_node_child(TSNode self, uint32_t child_index) { - return ts_node__child(self, child_index, true); -} - -TSNode ts_node_named_child(TSNode self, uint32_t child_index) { - return ts_node__child(self, child_index, false); -} - -TSNode ts_node_child_by_field_id(TSNode self, TSFieldId field_id) { -recur: - if (!field_id || ts_node_child_count(self) == 0) return ts_node__null(); - - const TSFieldMapEntry *field_map, *field_map_end; - ts_language_field_map( - self.tree->language, - ts_node__subtree(self).ptr->production_id, - &field_map, - &field_map_end - ); - if (field_map == field_map_end) return ts_node__null(); - - // The field mappings are sorted by their field id. Scan all - // the mappings to find the ones for the given field id. - while (field_map->field_id < field_id) { - field_map++; - if (field_map == field_map_end) return ts_node__null(); - } - while (field_map_end[-1].field_id > field_id) { - field_map_end--; - if (field_map == field_map_end) return ts_node__null(); - } - - TSNode child; - NodeChildIterator iterator = ts_node_iterate_children(&self); - while (ts_node_child_iterator_next(&iterator, &child)) { - if (!ts_subtree_extra(ts_node__subtree(child))) { - uint32_t index = iterator.structural_child_index - 1; - if (index < field_map->child_index) continue; - - // Hidden nodes' fields are "inherited" by their visible parent. - if (field_map->inherited) { - - // If this is the *last* possible child node for this field, - // then perform a tail call to avoid recursion. - if (field_map + 1 == field_map_end) { - self = child; - goto recur; - } - - // Otherwise, descend into this child, but if it doesn't contain - // the field, continue searching subsequent children. - else { - TSNode result = ts_node_child_by_field_id(child, field_id); - if (result.id) return result; - field_map++; - if (field_map == field_map_end) return ts_node__null(); - } - } - - else if (ts_node__is_relevant(child, true)) { - return child; - } - - // If the field refers to a hidden node, return its first visible - // child. - else { - return ts_node_child(child, 0); - } - } - } - - return ts_node__null(); -} - -TSNode ts_node_child_by_field_name( - TSNode self, - const char *name, - uint32_t name_length -) { - TSFieldId field_id = ts_language_field_id_for_name( - self.tree->language, - name, - name_length - ); - return ts_node_child_by_field_id(self, field_id); -} - -uint32_t ts_node_child_count(TSNode self) { - Subtree tree = ts_node__subtree(self); - if (ts_subtree_child_count(tree) > 0) { - return tree.ptr->visible_child_count; - } else { - return 0; - } -} - -uint32_t ts_node_named_child_count(TSNode self) { - Subtree tree = ts_node__subtree(self); - if (ts_subtree_child_count(tree) > 0) { - return tree.ptr->named_child_count; - } else { - return 0; - } -} - -TSNode ts_node_next_sibling(TSNode self) { - return ts_node__next_sibling(self, true); -} - -TSNode ts_node_next_named_sibling(TSNode self) { - return ts_node__next_sibling(self, false); -} - -TSNode ts_node_prev_sibling(TSNode self) { - return ts_node__prev_sibling(self, true); -} - -TSNode ts_node_prev_named_sibling(TSNode self) { - return ts_node__prev_sibling(self, false); -} - -TSNode ts_node_first_child_for_byte(TSNode self, uint32_t byte) { - return ts_node__first_child_for_byte(self, byte, true); -} - -TSNode ts_node_first_named_child_for_byte(TSNode self, uint32_t byte) { - return ts_node__first_child_for_byte(self, byte, false); -} - -TSNode ts_node_descendant_for_byte_range( - TSNode self, - uint32_t start, - uint32_t end -) { - return ts_node__descendant_for_byte_range(self, start, end, true); -} - -TSNode ts_node_named_descendant_for_byte_range( - TSNode self, - uint32_t start, - uint32_t end -) { - return ts_node__descendant_for_byte_range(self, start, end, false); -} - -TSNode ts_node_descendant_for_point_range( - TSNode self, - TSPoint start, - TSPoint end -) { - return ts_node__descendant_for_point_range(self, start, end, true); -} - -TSNode ts_node_named_descendant_for_point_range( - TSNode self, - TSPoint start, - TSPoint end -) { - return ts_node__descendant_for_point_range(self, start, end, false); -} - -void ts_node_edit(TSNode *self, const TSInputEdit *edit) { - uint32_t start_byte = ts_node_start_byte(*self); - TSPoint start_point = ts_node_start_point(*self); - - if (start_byte >= edit->old_end_byte) { - start_byte = edit->new_end_byte + (start_byte - edit->old_end_byte); - start_point = point_add(edit->new_end_point, point_sub(start_point, edit->old_end_point)); - } else if (start_byte > edit->start_byte) { - start_byte = edit->new_end_byte; - start_point = edit->new_end_point; - } - - self->context[0] = start_byte; - self->context[1] = start_point.row; - self->context[2] = start_point.column; -} diff --git a/src/tree_sitter/parser.c b/src/tree_sitter/parser.c deleted file mode 100644 index dd222cd3c4..0000000000 --- a/src/tree_sitter/parser.c +++ /dev/null @@ -1,1879 +0,0 @@ -#include <time.h> -#include <assert.h> -#include <stdio.h> -#include <limits.h> -#include <stdbool.h> -#include "tree_sitter/api.h" -#include "./alloc.h" -#include "./array.h" -#include "./atomic.h" -#include "./clock.h" -#include "./error_costs.h" -#include "./get_changed_ranges.h" -#include "./language.h" -#include "./length.h" -#include "./lexer.h" -#include "./reduce_action.h" -#include "./reusable_node.h" -#include "./stack.h" -#include "./subtree.h" -#include "./tree.h" - -#define LOG(...) \ - if (self->lexer.logger.log || self->dot_graph_file) { \ - snprintf(self->lexer.debug_buffer, TREE_SITTER_SERIALIZATION_BUFFER_SIZE, __VA_ARGS__); \ - ts_parser__log(self); \ - } - -#define LOG_STACK() \ - if (self->dot_graph_file) { \ - ts_stack_print_dot_graph(self->stack, self->language, self->dot_graph_file); \ - fputs("\n\n", self->dot_graph_file); \ - } - -#define LOG_TREE(tree) \ - if (self->dot_graph_file) { \ - ts_subtree_print_dot_graph(tree, self->language, self->dot_graph_file); \ - fputs("\n", self->dot_graph_file); \ - } - -#define SYM_NAME(symbol) ts_language_symbol_name(self->language, symbol) - -#define TREE_NAME(tree) SYM_NAME(ts_subtree_symbol(tree)) - -static const unsigned MAX_VERSION_COUNT = 6; -static const unsigned MAX_VERSION_COUNT_OVERFLOW = 4; -static const unsigned MAX_SUMMARY_DEPTH = 16; -static const unsigned MAX_COST_DIFFERENCE = 16 * ERROR_COST_PER_SKIPPED_TREE; -static const unsigned OP_COUNT_PER_TIMEOUT_CHECK = 100; - -typedef struct { - Subtree token; - Subtree last_external_token; - uint32_t byte_index; -} TokenCache; - -struct TSParser { - Lexer lexer; - Stack *stack; - SubtreePool tree_pool; - const TSLanguage *language; - ReduceActionSet reduce_actions; - Subtree finished_tree; - SubtreeHeapData scratch_tree_data; - MutableSubtree scratch_tree; - TokenCache token_cache; - ReusableNode reusable_node; - void *external_scanner_payload; - FILE *dot_graph_file; - TSClock end_clock; - TSDuration timeout_duration; - unsigned accept_count; - unsigned operation_count; - const volatile size_t *cancellation_flag; - Subtree old_tree; - TSRangeArray included_range_differences; - unsigned included_range_difference_index; -}; - -typedef struct { - unsigned cost; - unsigned node_count; - int dynamic_precedence; - bool is_in_error; -} ErrorStatus; - -typedef enum { - ErrorComparisonTakeLeft, - ErrorComparisonPreferLeft, - ErrorComparisonNone, - ErrorComparisonPreferRight, - ErrorComparisonTakeRight, -} ErrorComparison; - -typedef struct { - const char *string; - uint32_t length; -} TSStringInput; - -// StringInput - -static const char *ts_string_input_read( - void *_self, - uint32_t byte, - TSPoint pt, - uint32_t *length -) { - (void)pt; - TSStringInput *self = (TSStringInput *)_self; - if (byte >= self->length) { - *length = 0; - return ""; - } else { - *length = self->length - byte; - return self->string + byte; - } -} - -// Parser - Private - -static void ts_parser__log(TSParser *self) { - if (self->lexer.logger.log) { - self->lexer.logger.log( - self->lexer.logger.payload, - TSLogTypeParse, - self->lexer.debug_buffer - ); - } - - if (self->dot_graph_file) { - fprintf(self->dot_graph_file, "graph {\nlabel=\""); - for (char *c = &self->lexer.debug_buffer[0]; *c != 0; c++) { - if (*c == '"') fputc('\\', self->dot_graph_file); - fputc(*c, self->dot_graph_file); - } - fprintf(self->dot_graph_file, "\"\n}\n\n"); - } -} - -static bool ts_parser__breakdown_top_of_stack( - TSParser *self, - StackVersion version -) { - bool did_break_down = false; - bool pending = false; - - do { - StackSliceArray pop = ts_stack_pop_pending(self->stack, version); - if (!pop.size) break; - - did_break_down = true; - pending = false; - for (uint32_t i = 0; i < pop.size; i++) { - StackSlice slice = pop.contents[i]; - TSStateId state = ts_stack_state(self->stack, slice.version); - Subtree parent = *array_front(&slice.subtrees); - - for (uint32_t j = 0, n = ts_subtree_child_count(parent); j < n; j++) { - Subtree child = parent.ptr->children[j]; - pending = ts_subtree_child_count(child) > 0; - - if (ts_subtree_is_error(child)) { - state = ERROR_STATE; - } else if (!ts_subtree_extra(child)) { - state = ts_language_next_state(self->language, state, ts_subtree_symbol(child)); - } - - ts_subtree_retain(child); - ts_stack_push(self->stack, slice.version, child, pending, state); - } - - for (uint32_t j = 1; j < slice.subtrees.size; j++) { - Subtree tree = slice.subtrees.contents[j]; - ts_stack_push(self->stack, slice.version, tree, false, state); - } - - ts_subtree_release(&self->tree_pool, parent); - array_delete(&slice.subtrees); - - LOG("breakdown_top_of_stack tree:%s", TREE_NAME(parent)); - LOG_STACK(); - } - } while (pending); - - return did_break_down; -} - -static void ts_parser__breakdown_lookahead( - TSParser *self, - Subtree *lookahead, - TSStateId state, - ReusableNode *reusable_node -) { - bool did_descend = false; - Subtree tree = reusable_node_tree(reusable_node); - while (ts_subtree_child_count(tree) > 0 && ts_subtree_parse_state(tree) != state) { - LOG("state_mismatch sym:%s", TREE_NAME(tree)); - reusable_node_descend(reusable_node); - tree = reusable_node_tree(reusable_node); - did_descend = true; - } - - if (did_descend) { - ts_subtree_release(&self->tree_pool, *lookahead); - *lookahead = tree; - ts_subtree_retain(*lookahead); - } -} - -static ErrorComparison ts_parser__compare_versions( - TSParser *self, - ErrorStatus a, - ErrorStatus b -) { - (void)self; - if (!a.is_in_error && b.is_in_error) { - if (a.cost < b.cost) { - return ErrorComparisonTakeLeft; - } else { - return ErrorComparisonPreferLeft; - } - } - - if (a.is_in_error && !b.is_in_error) { - if (b.cost < a.cost) { - return ErrorComparisonTakeRight; - } else { - return ErrorComparisonPreferRight; - } - } - - if (a.cost < b.cost) { - if ((b.cost - a.cost) * (1 + a.node_count) > MAX_COST_DIFFERENCE) { - return ErrorComparisonTakeLeft; - } else { - return ErrorComparisonPreferLeft; - } - } - - if (b.cost < a.cost) { - if ((a.cost - b.cost) * (1 + b.node_count) > MAX_COST_DIFFERENCE) { - return ErrorComparisonTakeRight; - } else { - return ErrorComparisonPreferRight; - } - } - - if (a.dynamic_precedence > b.dynamic_precedence) return ErrorComparisonPreferLeft; - if (b.dynamic_precedence > a.dynamic_precedence) return ErrorComparisonPreferRight; - return ErrorComparisonNone; -} - -static ErrorStatus ts_parser__version_status( - TSParser *self, - StackVersion version -) { - unsigned cost = ts_stack_error_cost(self->stack, version); - bool is_paused = ts_stack_is_paused(self->stack, version); - if (is_paused) cost += ERROR_COST_PER_SKIPPED_TREE; - return (ErrorStatus) { - .cost = cost, - .node_count = ts_stack_node_count_since_error(self->stack, version), - .dynamic_precedence = ts_stack_dynamic_precedence(self->stack, version), - .is_in_error = is_paused || ts_stack_state(self->stack, version) == ERROR_STATE - }; -} - -static bool ts_parser__better_version_exists( - TSParser *self, - StackVersion version, - bool is_in_error, - unsigned cost -) { - if (self->finished_tree.ptr && ts_subtree_error_cost(self->finished_tree) <= cost) { - return true; - } - - Length position = ts_stack_position(self->stack, version); - ErrorStatus status = { - .cost = cost, - .is_in_error = is_in_error, - .dynamic_precedence = ts_stack_dynamic_precedence(self->stack, version), - .node_count = ts_stack_node_count_since_error(self->stack, version), - }; - - for (StackVersion i = 0, n = ts_stack_version_count(self->stack); i < n; i++) { - if (i == version || - !ts_stack_is_active(self->stack, i) || - ts_stack_position(self->stack, i).bytes < position.bytes) continue; - ErrorStatus status_i = ts_parser__version_status(self, i); - switch (ts_parser__compare_versions(self, status, status_i)) { - case ErrorComparisonTakeRight: - return true; - case ErrorComparisonPreferRight: - if (ts_stack_can_merge(self->stack, i, version)) return true; - default: - break; - } - } - - return false; -} - -static void ts_parser__restore_external_scanner( - TSParser *self, - Subtree external_token -) { - if (external_token.ptr) { - self->language->external_scanner.deserialize( - self->external_scanner_payload, - ts_external_scanner_state_data(&external_token.ptr->external_scanner_state), - external_token.ptr->external_scanner_state.length - ); - } else { - self->language->external_scanner.deserialize(self->external_scanner_payload, NULL, 0); - } -} - -static bool ts_parser__can_reuse_first_leaf( - TSParser *self, - TSStateId state, - Subtree tree, - TableEntry *table_entry -) { - TSLexMode current_lex_mode = self->language->lex_modes[state]; - TSSymbol leaf_symbol = ts_subtree_leaf_symbol(tree); - TSStateId leaf_state = ts_subtree_leaf_parse_state(tree); - TSLexMode leaf_lex_mode = self->language->lex_modes[leaf_state]; - - // At the end of a non-terminal extra node, the lexer normally returns - // NULL, which indicates that the parser should look for a reduce action - // at symbol `0`. Avoid reusing tokens in this situation to ensure that - // the same thing happens when incrementally reparsing. - if (current_lex_mode.lex_state == (uint16_t)(-1)) return false; - - // If the token was created in a state with the same set of lookaheads, it is reusable. - if ( - table_entry->action_count > 0 && - memcmp(&leaf_lex_mode, ¤t_lex_mode, sizeof(TSLexMode)) == 0 && - ( - leaf_symbol != self->language->keyword_capture_token || - (!ts_subtree_is_keyword(tree) && ts_subtree_parse_state(tree) == state) - ) - ) return true; - - // Empty tokens are not reusable in states with different lookaheads. - if (ts_subtree_size(tree).bytes == 0 && leaf_symbol != ts_builtin_sym_end) return false; - - // If the current state allows external tokens or other tokens that conflict with this - // token, this token is not reusable. - return current_lex_mode.external_lex_state == 0 && table_entry->is_reusable; -} - -static Subtree ts_parser__lex( - TSParser *self, - StackVersion version, - TSStateId parse_state -) { - Length start_position = ts_stack_position(self->stack, version); - Subtree external_token = ts_stack_last_external_token(self->stack, version); - TSLexMode lex_mode = self->language->lex_modes[parse_state]; - if (lex_mode.lex_state == (uint16_t)-1) return NULL_SUBTREE; - const bool *valid_external_tokens = ts_language_enabled_external_tokens( - self->language, - lex_mode.external_lex_state - ); - - bool found_external_token = false; - bool error_mode = parse_state == ERROR_STATE; - bool skipped_error = false; - int32_t first_error_character = 0; - Length error_start_position = length_zero(); - Length error_end_position = length_zero(); - uint32_t lookahead_end_byte = 0; - ts_lexer_reset(&self->lexer, start_position); - - for (;;) { - Length current_position = self->lexer.current_position; - - if (valid_external_tokens) { - LOG( - "lex_external state:%d, row:%u, column:%u", - lex_mode.external_lex_state, - current_position.extent.row + 1, - current_position.extent.column - ); - ts_lexer_start(&self->lexer); - ts_parser__restore_external_scanner(self, external_token); - bool found_token = self->language->external_scanner.scan( - self->external_scanner_payload, - &self->lexer.data, - valid_external_tokens - ); - ts_lexer_finish(&self->lexer, &lookahead_end_byte); - - // Zero-length external tokens are generally allowed, but they're not - // allowed right after a syntax error. This is for two reasons: - // 1. After a syntax error, the lexer is looking for any possible token, - // as opposed to the specific set of tokens that are valid in some - // parse state. In this situation, it's very easy for an external - // scanner to produce unwanted zero-length tokens. - // 2. The parser sometimes inserts *missing* tokens to recover from - // errors. These tokens are also zero-length. If we allow more - // zero-length tokens to be created after missing tokens, it - // can lead to infinite loops. Forbidding zero-length tokens - // right at the point of error recovery is a conservative strategy - // for preventing this kind of infinite loop. - if (found_token && ( - self->lexer.token_end_position.bytes > current_position.bytes || - (!error_mode && ts_stack_has_advanced_since_error(self->stack, version)) - )) { - found_external_token = true; - break; - } - - ts_lexer_reset(&self->lexer, current_position); - } - - LOG( - "lex_internal state:%d, row:%u, column:%u", - lex_mode.lex_state, - current_position.extent.row + 1, - current_position.extent.column - ); - ts_lexer_start(&self->lexer); - bool found_token = self->language->lex_fn(&self->lexer.data, lex_mode.lex_state); - ts_lexer_finish(&self->lexer, &lookahead_end_byte); - if (found_token) break; - - if (!error_mode) { - error_mode = true; - lex_mode = self->language->lex_modes[ERROR_STATE]; - valid_external_tokens = ts_language_enabled_external_tokens( - self->language, - lex_mode.external_lex_state - ); - ts_lexer_reset(&self->lexer, start_position); - continue; - } - - if (!skipped_error) { - LOG("skip_unrecognized_character"); - skipped_error = true; - error_start_position = self->lexer.token_start_position; - error_end_position = self->lexer.token_start_position; - first_error_character = self->lexer.data.lookahead; - } - - if (self->lexer.current_position.bytes == error_end_position.bytes) { - if (self->lexer.data.eof(&self->lexer.data)) { - self->lexer.data.result_symbol = ts_builtin_sym_error; - break; - } - self->lexer.data.advance(&self->lexer.data, false); - } - - error_end_position = self->lexer.current_position; - } - - Subtree result; - if (skipped_error) { - Length padding = length_sub(error_start_position, start_position); - Length size = length_sub(error_end_position, error_start_position); - uint32_t lookahead_bytes = lookahead_end_byte - error_end_position.bytes; - result = ts_subtree_new_error( - &self->tree_pool, - first_error_character, - padding, - size, - lookahead_bytes, - parse_state, - self->language - ); - - LOG( - "lexed_lookahead sym:%s, size:%u, character:'%c'", - SYM_NAME(ts_subtree_symbol(result)), - ts_subtree_total_size(result).bytes, - first_error_character - ); - } else { - if (self->lexer.token_end_position.bytes < self->lexer.token_start_position.bytes) { - self->lexer.token_start_position = self->lexer.token_end_position; - } - - bool is_keyword = false; - TSSymbol symbol = self->lexer.data.result_symbol; - Length padding = length_sub(self->lexer.token_start_position, start_position); - Length size = length_sub(self->lexer.token_end_position, self->lexer.token_start_position); - uint32_t lookahead_bytes = lookahead_end_byte - self->lexer.token_end_position.bytes; - - if (found_external_token) { - symbol = self->language->external_scanner.symbol_map[symbol]; - } else if (symbol == self->language->keyword_capture_token && symbol != 0) { - uint32_t end_byte = self->lexer.token_end_position.bytes; - ts_lexer_reset(&self->lexer, self->lexer.token_start_position); - ts_lexer_start(&self->lexer); - if ( - self->language->keyword_lex_fn(&self->lexer.data, 0) && - self->lexer.token_end_position.bytes == end_byte && - ts_language_has_actions(self->language, parse_state, self->lexer.data.result_symbol) - ) { - is_keyword = true; - symbol = self->lexer.data.result_symbol; - } - } - - result = ts_subtree_new_leaf( - &self->tree_pool, - symbol, - padding, - size, - lookahead_bytes, - parse_state, - found_external_token, - is_keyword, - self->language - ); - - if (found_external_token) { - unsigned length = self->language->external_scanner.serialize( - self->external_scanner_payload, - self->lexer.debug_buffer - ); - ts_external_scanner_state_init( - &((SubtreeHeapData *)result.ptr)->external_scanner_state, - self->lexer.debug_buffer, - length - ); - } - - LOG( - "lexed_lookahead sym:%s, size:%u", - SYM_NAME(ts_subtree_symbol(result)), - ts_subtree_total_size(result).bytes - ); - } - - return result; -} - -static Subtree ts_parser__get_cached_token( - TSParser *self, - TSStateId state, - size_t position, - Subtree last_external_token, - TableEntry *table_entry -) { - TokenCache *cache = &self->token_cache; - if ( - cache->token.ptr && cache->byte_index == position && - ts_subtree_external_scanner_state_eq(cache->last_external_token, last_external_token) - ) { - ts_language_table_entry(self->language, state, ts_subtree_symbol(cache->token), table_entry); - if (ts_parser__can_reuse_first_leaf(self, state, cache->token, table_entry)) { - ts_subtree_retain(cache->token); - return cache->token; - } - } - return NULL_SUBTREE; -} - -static void ts_parser__set_cached_token( - TSParser *self, - size_t byte_index, - Subtree last_external_token, - Subtree token -) { - TokenCache *cache = &self->token_cache; - if (token.ptr) ts_subtree_retain(token); - if (last_external_token.ptr) ts_subtree_retain(last_external_token); - if (cache->token.ptr) ts_subtree_release(&self->tree_pool, cache->token); - if (cache->last_external_token.ptr) ts_subtree_release(&self->tree_pool, cache->last_external_token); - cache->token = token; - cache->byte_index = byte_index; - cache->last_external_token = last_external_token; -} - -static bool ts_parser__has_included_range_difference( - const TSParser *self, - uint32_t start_position, - uint32_t end_position -) { - return ts_range_array_intersects( - &self->included_range_differences, - self->included_range_difference_index, - start_position, - end_position - ); -} - -static Subtree ts_parser__reuse_node( - TSParser *self, - StackVersion version, - TSStateId *state, - uint32_t position, - Subtree last_external_token, - TableEntry *table_entry -) { - Subtree result; - while ((result = reusable_node_tree(&self->reusable_node)).ptr) { - uint32_t byte_offset = reusable_node_byte_offset(&self->reusable_node); - uint32_t end_byte_offset = byte_offset + ts_subtree_total_bytes(result); - - // Do not reuse an EOF node if the included ranges array has changes - // later on in the file. - if (ts_subtree_is_eof(result)) end_byte_offset = UINT32_MAX; - - if (byte_offset > position) { - LOG("before_reusable_node symbol:%s", TREE_NAME(result)); - break; - } - - if (byte_offset < position) { - LOG("past_reusable_node symbol:%s", TREE_NAME(result)); - if (end_byte_offset <= position || !reusable_node_descend(&self->reusable_node)) { - reusable_node_advance(&self->reusable_node); - } - continue; - } - - if (!ts_subtree_external_scanner_state_eq(self->reusable_node.last_external_token, last_external_token)) { - LOG("reusable_node_has_different_external_scanner_state symbol:%s", TREE_NAME(result)); - reusable_node_advance(&self->reusable_node); - continue; - } - - const char *reason = NULL; - if (ts_subtree_has_changes(result)) { - reason = "has_changes"; - } else if (ts_subtree_is_error(result)) { - reason = "is_error"; - } else if (ts_subtree_missing(result)) { - reason = "is_missing"; - } else if (ts_subtree_is_fragile(result)) { - reason = "is_fragile"; - } else if (ts_parser__has_included_range_difference(self, byte_offset, end_byte_offset)) { - reason = "contains_different_included_range"; - } - - if (reason) { - LOG("cant_reuse_node_%s tree:%s", reason, TREE_NAME(result)); - if (!reusable_node_descend(&self->reusable_node)) { - reusable_node_advance(&self->reusable_node); - ts_parser__breakdown_top_of_stack(self, version); - *state = ts_stack_state(self->stack, version); - } - continue; - } - - TSSymbol leaf_symbol = ts_subtree_leaf_symbol(result); - ts_language_table_entry(self->language, *state, leaf_symbol, table_entry); - if (!ts_parser__can_reuse_first_leaf(self, *state, result, table_entry)) { - LOG( - "cant_reuse_node symbol:%s, first_leaf_symbol:%s", - TREE_NAME(result), - SYM_NAME(leaf_symbol) - ); - reusable_node_advance_past_leaf(&self->reusable_node); - break; - } - - LOG("reuse_node symbol:%s", TREE_NAME(result)); - ts_subtree_retain(result); - return result; - } - - return NULL_SUBTREE; -} - -static bool ts_parser__select_tree(TSParser *self, Subtree left, Subtree right) { - if (!left.ptr) return true; - if (!right.ptr) return false; - - if (ts_subtree_error_cost(right) < ts_subtree_error_cost(left)) { - LOG("select_smaller_error symbol:%s, over_symbol:%s", TREE_NAME(right), TREE_NAME(left)); - return true; - } - - if (ts_subtree_error_cost(left) < ts_subtree_error_cost(right)) { - LOG("select_smaller_error symbol:%s, over_symbol:%s", TREE_NAME(left), TREE_NAME(right)); - return false; - } - - if (ts_subtree_dynamic_precedence(right) > ts_subtree_dynamic_precedence(left)) { - LOG("select_higher_precedence symbol:%s, prec:%u, over_symbol:%s, other_prec:%u", - TREE_NAME(right), ts_subtree_dynamic_precedence(right), TREE_NAME(left), - ts_subtree_dynamic_precedence(left)); - return true; - } - - if (ts_subtree_dynamic_precedence(left) > ts_subtree_dynamic_precedence(right)) { - LOG("select_higher_precedence symbol:%s, prec:%u, over_symbol:%s, other_prec:%u", - TREE_NAME(left), ts_subtree_dynamic_precedence(left), TREE_NAME(right), - ts_subtree_dynamic_precedence(right)); - return false; - } - - if (ts_subtree_error_cost(left) > 0) return true; - - int comparison = ts_subtree_compare(left, right); - switch (comparison) { - case -1: - LOG("select_earlier symbol:%s, over_symbol:%s", TREE_NAME(left), TREE_NAME(right)); - return false; - break; - case 1: - LOG("select_earlier symbol:%s, over_symbol:%s", TREE_NAME(right), TREE_NAME(left)); - return true; - default: - LOG("select_existing symbol:%s, over_symbol:%s", TREE_NAME(left), TREE_NAME(right)); - return false; - } -} - -static void ts_parser__shift( - TSParser *self, - StackVersion version, - TSStateId state, - Subtree lookahead, - bool extra -) { - Subtree subtree_to_push; - if (extra != ts_subtree_extra(lookahead)) { - MutableSubtree result = ts_subtree_make_mut(&self->tree_pool, lookahead); - ts_subtree_set_extra(&result); - subtree_to_push = ts_subtree_from_mut(result); - } else { - subtree_to_push = lookahead; - } - - bool is_pending = ts_subtree_child_count(subtree_to_push) > 0; - ts_stack_push(self->stack, version, subtree_to_push, is_pending, state); - if (ts_subtree_has_external_tokens(subtree_to_push)) { - ts_stack_set_last_external_token( - self->stack, version, ts_subtree_last_external_token(subtree_to_push) - ); - } -} - -static bool ts_parser__replace_children( - TSParser *self, - MutableSubtree *tree, - SubtreeArray *children -) { - *self->scratch_tree.ptr = *tree->ptr; - self->scratch_tree.ptr->child_count = 0; - ts_subtree_set_children(self->scratch_tree, children->contents, children->size, self->language); - if (ts_parser__select_tree(self, ts_subtree_from_mut(*tree), ts_subtree_from_mut(self->scratch_tree))) { - *tree->ptr = *self->scratch_tree.ptr; - return true; - } else { - return false; - } -} - -static StackVersion ts_parser__reduce( - TSParser *self, - StackVersion version, - TSSymbol symbol, - uint32_t count, - int dynamic_precedence, - uint16_t production_id, - bool is_fragile, - bool is_extra -) { - uint32_t initial_version_count = ts_stack_version_count(self->stack); - uint32_t removed_version_count = 0; - StackSliceArray pop = ts_stack_pop_count(self->stack, version, count); - - for (uint32_t i = 0; i < pop.size; i++) { - StackSlice slice = pop.contents[i]; - StackVersion slice_version = slice.version - removed_version_count; - - // Error recovery can sometimes cause lots of stack versions to merge, - // such that a single pop operation can produce a lots of slices. - // Avoid creating too many stack versions in that situation. - if (i > 0 && slice_version > MAX_VERSION_COUNT + MAX_VERSION_COUNT_OVERFLOW) { - ts_stack_remove_version(self->stack, slice_version); - ts_subtree_array_delete(&self->tree_pool, &slice.subtrees); - removed_version_count++; - while (i + 1 < pop.size) { - StackSlice next_slice = pop.contents[i + 1]; - if (next_slice.version != slice.version) break; - ts_subtree_array_delete(&self->tree_pool, &next_slice.subtrees); - i++; - } - continue; - } - - // Extra tokens on top of the stack should not be included in this new parent - // node. They will be re-pushed onto the stack after the parent node is - // created and pushed. - SubtreeArray children = slice.subtrees; - while (children.size > 0 && ts_subtree_extra(children.contents[children.size - 1])) { - children.size--; - } - - MutableSubtree parent = ts_subtree_new_node(&self->tree_pool, - symbol, &children, production_id, self->language - ); - - // This pop operation may have caused multiple stack versions to collapse - // into one, because they all diverged from a common state. In that case, - // choose one of the arrays of trees to be the parent node's children, and - // delete the rest of the tree arrays. - while (i + 1 < pop.size) { - StackSlice next_slice = pop.contents[i + 1]; - if (next_slice.version != slice.version) break; - i++; - - SubtreeArray children = next_slice.subtrees; - while (children.size > 0 && ts_subtree_extra(children.contents[children.size - 1])) { - children.size--; - } - - if (ts_parser__replace_children(self, &parent, &children)) { - ts_subtree_array_delete(&self->tree_pool, &slice.subtrees); - slice = next_slice; - } else { - ts_subtree_array_delete(&self->tree_pool, &next_slice.subtrees); - } - } - - parent.ptr->dynamic_precedence += dynamic_precedence; - parent.ptr->production_id = production_id; - - TSStateId state = ts_stack_state(self->stack, slice_version); - TSStateId next_state = ts_language_next_state(self->language, state, symbol); - if (is_extra) parent.ptr->extra = true; - if (is_fragile || pop.size > 1 || initial_version_count > 1) { - parent.ptr->fragile_left = true; - parent.ptr->fragile_right = true; - parent.ptr->parse_state = TS_TREE_STATE_NONE; - } else { - parent.ptr->parse_state = state; - } - - // Push the parent node onto the stack, along with any extra tokens that - // were previously on top of the stack. - ts_stack_push(self->stack, slice_version, ts_subtree_from_mut(parent), false, next_state); - for (uint32_t j = parent.ptr->child_count; j < slice.subtrees.size; j++) { - ts_stack_push(self->stack, slice_version, slice.subtrees.contents[j], false, next_state); - } - - for (StackVersion j = 0; j < slice_version; j++) { - if (j == version) continue; - if (ts_stack_merge(self->stack, j, slice_version)) { - removed_version_count++; - break; - } - } - } - - // Return the first new stack version that was created. - return ts_stack_version_count(self->stack) > initial_version_count - ? initial_version_count - : STACK_VERSION_NONE; -} - -static void ts_parser__accept( - TSParser *self, - StackVersion version, - Subtree lookahead -) { - assert(ts_subtree_is_eof(lookahead)); - ts_stack_push(self->stack, version, lookahead, false, 1); - - StackSliceArray pop = ts_stack_pop_all(self->stack, version); - for (uint32_t i = 0; i < pop.size; i++) { - SubtreeArray trees = pop.contents[i].subtrees; - - Subtree root = NULL_SUBTREE; - for (uint32_t j = trees.size - 1; j + 1 > 0; j--) { - Subtree child = trees.contents[j]; - if (!ts_subtree_extra(child)) { - assert(!child.data.is_inline); - uint32_t child_count = ts_subtree_child_count(child); - for (uint32_t k = 0; k < child_count; k++) { - ts_subtree_retain(child.ptr->children[k]); - } - array_splice(&trees, j, 1, child_count, child.ptr->children); - root = ts_subtree_from_mut(ts_subtree_new_node( - &self->tree_pool, - ts_subtree_symbol(child), - &trees, - child.ptr->production_id, - self->language - )); - ts_subtree_release(&self->tree_pool, child); - break; - } - } - - assert(root.ptr); - self->accept_count++; - - if (self->finished_tree.ptr) { - if (ts_parser__select_tree(self, self->finished_tree, root)) { - ts_subtree_release(&self->tree_pool, self->finished_tree); - self->finished_tree = root; - } else { - ts_subtree_release(&self->tree_pool, root); - } - } else { - self->finished_tree = root; - } - } - - ts_stack_remove_version(self->stack, pop.contents[0].version); - ts_stack_halt(self->stack, version); -} - -static bool ts_parser__do_all_potential_reductions( - TSParser *self, - StackVersion starting_version, - TSSymbol lookahead_symbol -) { - uint32_t initial_version_count = ts_stack_version_count(self->stack); - - bool can_shift_lookahead_symbol = false; - StackVersion version = starting_version; - for (unsigned i = 0; true; i++) { - uint32_t version_count = ts_stack_version_count(self->stack); - if (version >= version_count) break; - - bool merged = false; - for (StackVersion i = initial_version_count; i < version; i++) { - if (ts_stack_merge(self->stack, i, version)) { - merged = true; - break; - } - } - if (merged) continue; - - TSStateId state = ts_stack_state(self->stack, version); - bool has_shift_action = false; - array_clear(&self->reduce_actions); - - TSSymbol first_symbol, end_symbol; - if (lookahead_symbol != 0) { - first_symbol = lookahead_symbol; - end_symbol = lookahead_symbol + 1; - } else { - first_symbol = 1; - end_symbol = self->language->token_count; - } - - for (TSSymbol symbol = first_symbol; symbol < end_symbol; symbol++) { - TableEntry entry; - ts_language_table_entry(self->language, state, symbol, &entry); - for (uint32_t i = 0; i < entry.action_count; i++) { - TSParseAction action = entry.actions[i]; - switch (action.type) { - case TSParseActionTypeShift: - case TSParseActionTypeRecover: - if (!action.params.shift.extra && !action.params.shift.repetition) has_shift_action = true; - break; - case TSParseActionTypeReduce: - if (action.params.reduce.child_count > 0) - ts_reduce_action_set_add(&self->reduce_actions, (ReduceAction){ - .symbol = action.params.reduce.symbol, - .count = action.params.reduce.child_count, - .dynamic_precedence = action.params.reduce.dynamic_precedence, - .production_id = action.params.reduce.production_id, - }); - default: - break; - } - } - } - - StackVersion reduction_version = STACK_VERSION_NONE; - for (uint32_t i = 0; i < self->reduce_actions.size; i++) { - ReduceAction action = self->reduce_actions.contents[i]; - - reduction_version = ts_parser__reduce( - self, version, action.symbol, action.count, - action.dynamic_precedence, action.production_id, - true, false - ); - } - - if (has_shift_action) { - can_shift_lookahead_symbol = true; - } else if (reduction_version != STACK_VERSION_NONE && i < MAX_VERSION_COUNT) { - ts_stack_renumber_version(self->stack, reduction_version, version); - continue; - } else if (lookahead_symbol != 0) { - ts_stack_remove_version(self->stack, version); - } - - if (version == starting_version) { - version = version_count; - } else { - version++; - } - } - - return can_shift_lookahead_symbol; -} - -static void ts_parser__handle_error( - TSParser *self, - StackVersion version, - TSSymbol lookahead_symbol -) { - uint32_t previous_version_count = ts_stack_version_count(self->stack); - - // Perform any reductions that can happen in this state, regardless of the lookahead. After - // skipping one or more invalid tokens, the parser might find a token that would have allowed - // a reduction to take place. - ts_parser__do_all_potential_reductions(self, version, 0); - uint32_t version_count = ts_stack_version_count(self->stack); - Length position = ts_stack_position(self->stack, version); - - // Push a discontinuity onto the stack. Merge all of the stack versions that - // were created in the previous step. - bool did_insert_missing_token = false; - for (StackVersion v = version; v < version_count;) { - if (!did_insert_missing_token) { - TSStateId state = ts_stack_state(self->stack, v); - for (TSSymbol missing_symbol = 1; - missing_symbol < self->language->token_count; - missing_symbol++) { - TSStateId state_after_missing_symbol = ts_language_next_state( - self->language, state, missing_symbol - ); - if (state_after_missing_symbol == 0 || state_after_missing_symbol == state) { - continue; - } - - if (ts_language_has_reduce_action( - self->language, - state_after_missing_symbol, - lookahead_symbol - )) { - // In case the parser is currently outside of any included range, the lexer will - // snap to the beginning of the next included range. The missing token's padding - // must be assigned to position it within the next included range. - ts_lexer_reset(&self->lexer, position); - ts_lexer_mark_end(&self->lexer); - Length padding = length_sub(self->lexer.token_end_position, position); - - StackVersion version_with_missing_tree = ts_stack_copy_version(self->stack, v); - Subtree missing_tree = ts_subtree_new_missing_leaf( - &self->tree_pool, missing_symbol, padding, self->language - ); - ts_stack_push( - self->stack, version_with_missing_tree, - missing_tree, false, - state_after_missing_symbol - ); - - if (ts_parser__do_all_potential_reductions( - self, version_with_missing_tree, - lookahead_symbol - )) { - LOG( - "recover_with_missing symbol:%s, state:%u", - SYM_NAME(missing_symbol), - ts_stack_state(self->stack, version_with_missing_tree) - ); - did_insert_missing_token = true; - break; - } - } - } - } - - ts_stack_push(self->stack, v, NULL_SUBTREE, false, ERROR_STATE); - v = (v == version) ? previous_version_count : v + 1; - } - - for (unsigned i = previous_version_count; i < version_count; i++) { - bool did_merge = ts_stack_merge(self->stack, version, previous_version_count); - assert(did_merge); - } - - ts_stack_record_summary(self->stack, version, MAX_SUMMARY_DEPTH); - LOG_STACK(); -} - -static bool ts_parser__recover_to_state( - TSParser *self, - StackVersion version, - unsigned depth, - TSStateId goal_state -) { - StackSliceArray pop = ts_stack_pop_count(self->stack, version, depth); - StackVersion previous_version = STACK_VERSION_NONE; - - for (unsigned i = 0; i < pop.size; i++) { - StackSlice slice = pop.contents[i]; - - if (slice.version == previous_version) { - ts_subtree_array_delete(&self->tree_pool, &slice.subtrees); - array_erase(&pop, i--); - continue; - } - - if (ts_stack_state(self->stack, slice.version) != goal_state) { - ts_stack_halt(self->stack, slice.version); - ts_subtree_array_delete(&self->tree_pool, &slice.subtrees); - array_erase(&pop, i--); - continue; - } - - SubtreeArray error_trees = ts_stack_pop_error(self->stack, slice.version); - if (error_trees.size > 0) { - assert(error_trees.size == 1); - Subtree error_tree = error_trees.contents[0]; - uint32_t error_child_count = ts_subtree_child_count(error_tree); - if (error_child_count > 0) { - array_splice(&slice.subtrees, 0, 0, error_child_count, error_tree.ptr->children); - for (unsigned j = 0; j < error_child_count; j++) { - ts_subtree_retain(slice.subtrees.contents[j]); - } - } - ts_subtree_array_delete(&self->tree_pool, &error_trees); - } - - SubtreeArray trailing_extras = ts_subtree_array_remove_trailing_extras(&slice.subtrees); - - if (slice.subtrees.size > 0) { - Subtree error = ts_subtree_new_error_node(&self->tree_pool, &slice.subtrees, true, self->language); - ts_stack_push(self->stack, slice.version, error, false, goal_state); - } else { - array_delete(&slice.subtrees); - } - - for (unsigned j = 0; j < trailing_extras.size; j++) { - Subtree tree = trailing_extras.contents[j]; - ts_stack_push(self->stack, slice.version, tree, false, goal_state); - } - - previous_version = slice.version; - array_delete(&trailing_extras); - } - - return previous_version != STACK_VERSION_NONE; -} - -static void ts_parser__recover( - TSParser *self, - StackVersion version, - Subtree lookahead -) { - bool did_recover = false; - unsigned previous_version_count = ts_stack_version_count(self->stack); - Length position = ts_stack_position(self->stack, version); - StackSummary *summary = ts_stack_get_summary(self->stack, version); - unsigned node_count_since_error = ts_stack_node_count_since_error(self->stack, version); - unsigned current_error_cost = ts_stack_error_cost(self->stack, version); - - // When the parser is in the error state, there are two strategies for recovering with a - // given lookahead token: - // 1. Find a previous state on the stack in which that lookahead token would be valid. Then, - // create a new stack version that is in that state again. This entails popping all of the - // subtrees that have been pushed onto the stack since that previous state, and wrapping - // them in an ERROR node. - // 2. Wrap the lookahead token in an ERROR node, push that ERROR node onto the stack, and - // move on to the next lookahead token, remaining in the error state. - // - // First, try the strategy 1. Upon entering the error state, the parser recorded a summary - // of the previous parse states and their depths. Look at each state in the summary, to see - // if the current lookahead token would be valid in that state. - if (summary && !ts_subtree_is_error(lookahead)) { - for (unsigned i = 0; i < summary->size; i++) { - StackSummaryEntry entry = summary->contents[i]; - - if (entry.state == ERROR_STATE) continue; - if (entry.position.bytes == position.bytes) continue; - unsigned depth = entry.depth; - if (node_count_since_error > 0) depth++; - - // Do not recover in ways that create redundant stack versions. - bool would_merge = false; - for (unsigned j = 0; j < previous_version_count; j++) { - if ( - ts_stack_state(self->stack, j) == entry.state && - ts_stack_position(self->stack, j).bytes == position.bytes - ) { - would_merge = true; - break; - } - } - if (would_merge) continue; - - // Do not recover if the result would clearly be worse than some existing stack version. - unsigned new_cost = - current_error_cost + - entry.depth * ERROR_COST_PER_SKIPPED_TREE + - (position.bytes - entry.position.bytes) * ERROR_COST_PER_SKIPPED_CHAR + - (position.extent.row - entry.position.extent.row) * ERROR_COST_PER_SKIPPED_LINE; - if (ts_parser__better_version_exists(self, version, false, new_cost)) break; - - // If the current lookahead token is valid in some previous state, recover to that state. - // Then stop looking for further recoveries. - if (ts_language_has_actions(self->language, entry.state, ts_subtree_symbol(lookahead))) { - if (ts_parser__recover_to_state(self, version, depth, entry.state)) { - did_recover = true; - LOG("recover_to_previous state:%u, depth:%u", entry.state, depth); - LOG_STACK(); - break; - } - } - } - } - - // In the process of attemping to recover, some stack versions may have been created - // and subsequently halted. Remove those versions. - for (unsigned i = previous_version_count; i < ts_stack_version_count(self->stack); i++) { - if (!ts_stack_is_active(self->stack, i)) { - ts_stack_remove_version(self->stack, i--); - } - } - - // If strategy 1 succeeded, a new stack version will have been created which is able to handle - // the current lookahead token. Now, in addition, try strategy 2 described above: skip the - // current lookahead token by wrapping it in an ERROR node. - - // Don't pursue this additional strategy if there are already too many stack versions. - if (did_recover && ts_stack_version_count(self->stack) > MAX_VERSION_COUNT) { - ts_stack_halt(self->stack, version); - ts_subtree_release(&self->tree_pool, lookahead); - return; - } - - // If the parser is still in the error state at the end of the file, just wrap everything - // in an ERROR node and terminate. - if (ts_subtree_is_eof(lookahead)) { - LOG("recover_eof"); - SubtreeArray children = array_new(); - Subtree parent = ts_subtree_new_error_node(&self->tree_pool, &children, false, self->language); - ts_stack_push(self->stack, version, parent, false, 1); - ts_parser__accept(self, version, lookahead); - return; - } - - // Do not recover if the result would clearly be worse than some existing stack version. - unsigned new_cost = - current_error_cost + ERROR_COST_PER_SKIPPED_TREE + - ts_subtree_total_bytes(lookahead) * ERROR_COST_PER_SKIPPED_CHAR + - ts_subtree_total_size(lookahead).extent.row * ERROR_COST_PER_SKIPPED_LINE; - if (ts_parser__better_version_exists(self, version, false, new_cost)) { - ts_stack_halt(self->stack, version); - ts_subtree_release(&self->tree_pool, lookahead); - return; - } - - // If the current lookahead token is an extra token, mark it as extra. This means it won't - // be counted in error cost calculations. - unsigned n; - const TSParseAction *actions = ts_language_actions(self->language, 1, ts_subtree_symbol(lookahead), &n); - if (n > 0 && actions[n - 1].type == TSParseActionTypeShift && actions[n - 1].params.shift.extra) { - MutableSubtree mutable_lookahead = ts_subtree_make_mut(&self->tree_pool, lookahead); - ts_subtree_set_extra(&mutable_lookahead); - lookahead = ts_subtree_from_mut(mutable_lookahead); - } - - // Wrap the lookahead token in an ERROR. - LOG("skip_token symbol:%s", TREE_NAME(lookahead)); - SubtreeArray children = array_new(); - array_reserve(&children, 1); - array_push(&children, lookahead); - MutableSubtree error_repeat = ts_subtree_new_node( - &self->tree_pool, - ts_builtin_sym_error_repeat, - &children, - 0, - self->language - ); - - // If other tokens have already been skipped, so there is already an ERROR at the top of the - // stack, then pop that ERROR off the stack and wrap the two ERRORs together into one larger - // ERROR. - if (node_count_since_error > 0) { - StackSliceArray pop = ts_stack_pop_count(self->stack, version, 1); - - // TODO: Figure out how to make this condition occur. - // See https://github.com/atom/atom/issues/18450#issuecomment-439579778 - // If multiple stack versions have merged at this point, just pick one of the errors - // arbitrarily and discard the rest. - if (pop.size > 1) { - for (unsigned i = 1; i < pop.size; i++) { - ts_subtree_array_delete(&self->tree_pool, &pop.contents[i].subtrees); - } - while (ts_stack_version_count(self->stack) > pop.contents[0].version + 1) { - ts_stack_remove_version(self->stack, pop.contents[0].version + 1); - } - } - - ts_stack_renumber_version(self->stack, pop.contents[0].version, version); - array_push(&pop.contents[0].subtrees, ts_subtree_from_mut(error_repeat)); - error_repeat = ts_subtree_new_node( - &self->tree_pool, - ts_builtin_sym_error_repeat, - &pop.contents[0].subtrees, - 0, - self->language - ); - } - - // Push the new ERROR onto the stack. - ts_stack_push(self->stack, version, ts_subtree_from_mut(error_repeat), false, ERROR_STATE); - if (ts_subtree_has_external_tokens(lookahead)) { - ts_stack_set_last_external_token( - self->stack, version, ts_subtree_last_external_token(lookahead) - ); - } -} - -static bool ts_parser__advance( - TSParser *self, - StackVersion version, - bool allow_node_reuse -) { - TSStateId state = ts_stack_state(self->stack, version); - uint32_t position = ts_stack_position(self->stack, version).bytes; - Subtree last_external_token = ts_stack_last_external_token(self->stack, version); - - bool did_reuse = true; - Subtree lookahead = NULL_SUBTREE; - TableEntry table_entry = {.action_count = 0}; - - // If possible, reuse a node from the previous syntax tree. - if (allow_node_reuse) { - lookahead = ts_parser__reuse_node( - self, version, &state, position, last_external_token, &table_entry - ); - } - - // If no node from the previous syntax tree could be reused, then try to - // reuse the token previously returned by the lexer. - if (!lookahead.ptr) { - did_reuse = false; - lookahead = ts_parser__get_cached_token( - self, state, position, last_external_token, &table_entry - ); - } - - // Otherwise, re-run the lexer. - if (!lookahead.ptr) { - lookahead = ts_parser__lex(self, version, state); - if (lookahead.ptr) { - ts_parser__set_cached_token(self, position, last_external_token, lookahead); - ts_language_table_entry(self->language, state, ts_subtree_symbol(lookahead), &table_entry); - } - - // When parsing a non-terminal extra, a null lookahead indicates the - // end of the rule. The reduction is stored in the EOF table entry. - // After the reduction, the lexer needs to be run again. - else { - ts_language_table_entry(self->language, state, ts_builtin_sym_end, &table_entry); - } - } - - for (;;) { - // If a cancellation flag or a timeout was provided, then check every - // time a fixed number of parse actions has been processed. - if (++self->operation_count == OP_COUNT_PER_TIMEOUT_CHECK) { - self->operation_count = 0; - } - if ( - self->operation_count == 0 && - ((self->cancellation_flag && atomic_load(self->cancellation_flag)) || - (!clock_is_null(self->end_clock) && clock_is_gt(clock_now(), self->end_clock))) - ) { - ts_subtree_release(&self->tree_pool, lookahead); - return false; - } - - // Process each parse action for the current lookahead token in - // the current state. If there are multiple actions, then this is - // an ambiguous state. REDUCE actions always create a new stack - // version, whereas SHIFT actions update the existing stack version - // and terminate this loop. - StackVersion last_reduction_version = STACK_VERSION_NONE; - for (uint32_t i = 0; i < table_entry.action_count; i++) { - TSParseAction action = table_entry.actions[i]; - - switch (action.type) { - case TSParseActionTypeShift: { - if (action.params.shift.repetition) break; - TSStateId next_state; - if (action.params.shift.extra) { - - // TODO: remove when TREE_SITTER_LANGUAGE_VERSION 9 is out. - if (state == ERROR_STATE) continue; - - next_state = state; - LOG("shift_extra"); - } else { - next_state = action.params.shift.state; - LOG("shift state:%u", next_state); - } - - if (ts_subtree_child_count(lookahead) > 0) { - ts_parser__breakdown_lookahead(self, &lookahead, state, &self->reusable_node); - next_state = ts_language_next_state(self->language, state, ts_subtree_symbol(lookahead)); - } - - ts_parser__shift(self, version, next_state, lookahead, action.params.shift.extra); - if (did_reuse) reusable_node_advance(&self->reusable_node); - return true; - } - - case TSParseActionTypeReduce: { - bool is_fragile = table_entry.action_count > 1; - bool is_extra = lookahead.ptr == NULL; - LOG("reduce sym:%s, child_count:%u", SYM_NAME(action.params.reduce.symbol), action.params.reduce.child_count); - StackVersion reduction_version = ts_parser__reduce( - self, version, action.params.reduce.symbol, action.params.reduce.child_count, - action.params.reduce.dynamic_precedence, action.params.reduce.production_id, - is_fragile, is_extra - ); - if (reduction_version != STACK_VERSION_NONE) { - last_reduction_version = reduction_version; - } - break; - } - - case TSParseActionTypeAccept: { - LOG("accept"); - ts_parser__accept(self, version, lookahead); - return true; - } - - case TSParseActionTypeRecover: { - if (ts_subtree_child_count(lookahead) > 0) { - ts_parser__breakdown_lookahead(self, &lookahead, ERROR_STATE, &self->reusable_node); - } - - ts_parser__recover(self, version, lookahead); - if (did_reuse) reusable_node_advance(&self->reusable_node); - return true; - } - } - } - - // If a reduction was performed, then replace the current stack version - // with one of the stack versions created by a reduction, and continue - // processing this version of the stack with the same lookahead symbol. - if (last_reduction_version != STACK_VERSION_NONE) { - ts_stack_renumber_version(self->stack, last_reduction_version, version); - LOG_STACK(); - state = ts_stack_state(self->stack, version); - - // At the end of a non-terminal extra rule, the lexer will return a - // null subtree, because the parser needs to perform a fixed reduction - // regardless of the lookahead node. After performing that reduction, - // (and completing the non-terminal extra rule) run the lexer again based - // on the current parse state. - if (!lookahead.ptr) { - lookahead = ts_parser__lex(self, version, state); - } - ts_language_table_entry( - self->language, - state, - ts_subtree_leaf_symbol(lookahead), - &table_entry - ); - continue; - } - - // If there were no parse actions for the current lookahead token, then - // it is not valid in this state. If the current lookahead token is a - // keyword, then switch to treating it as the normal word token if that - // token is valid in this state. - if ( - ts_subtree_is_keyword(lookahead) && - ts_subtree_symbol(lookahead) != self->language->keyword_capture_token - ) { - ts_language_table_entry(self->language, state, self->language->keyword_capture_token, &table_entry); - if (table_entry.action_count > 0) { - LOG( - "switch from_keyword:%s, to_word_token:%s", - TREE_NAME(lookahead), - SYM_NAME(self->language->keyword_capture_token) - ); - - MutableSubtree mutable_lookahead = ts_subtree_make_mut(&self->tree_pool, lookahead); - ts_subtree_set_symbol(&mutable_lookahead, self->language->keyword_capture_token, self->language); - lookahead = ts_subtree_from_mut(mutable_lookahead); - continue; - } - } - - // If the current lookahead token is not valid and the parser is - // already in the error state, restart the error recovery process. - // TODO - can this be unified with the other `RECOVER` case above? - if (state == ERROR_STATE) { - ts_parser__recover(self, version, lookahead); - return true; - } - - // If the current lookahead token is not valid and the previous - // subtree on the stack was reused from an old tree, it isn't actually - // valid to reuse it. Remove it from the stack, and in its place, - // push each of its children. Then try again to process the current - // lookahead. - if (ts_parser__breakdown_top_of_stack(self, version)) { - continue; - } - - // At this point, the current lookahead token is definitely not valid - // for this parse stack version. Mark this version as paused and continue - // processing any other stack versions that might exist. If some other - // version advances successfully, then this version can simply be removed. - // But if all versions end up paused, then error recovery is needed. - LOG("detect_error"); - ts_stack_pause(self->stack, version, ts_subtree_leaf_symbol(lookahead)); - ts_subtree_release(&self->tree_pool, lookahead); - return true; - } -} - -static unsigned ts_parser__condense_stack(TSParser *self) { - bool made_changes = false; - unsigned min_error_cost = UINT_MAX; - for (StackVersion i = 0; i < ts_stack_version_count(self->stack); i++) { - // Prune any versions that have been marked for removal. - if (ts_stack_is_halted(self->stack, i)) { - ts_stack_remove_version(self->stack, i); - i--; - continue; - } - - // Keep track of the minimum error cost of any stack version so - // that it can be returned. - ErrorStatus status_i = ts_parser__version_status(self, i); - if (!status_i.is_in_error && status_i.cost < min_error_cost) { - min_error_cost = status_i.cost; - } - - // Examine each pair of stack versions, removing any versions that - // are clearly worse than another version. Ensure that the versions - // are ordered from most promising to least promising. - for (StackVersion j = 0; j < i; j++) { - ErrorStatus status_j = ts_parser__version_status(self, j); - - switch (ts_parser__compare_versions(self, status_j, status_i)) { - case ErrorComparisonTakeLeft: - made_changes = true; - ts_stack_remove_version(self->stack, i); - i--; - j = i; - break; - - case ErrorComparisonPreferLeft: - case ErrorComparisonNone: - if (ts_stack_merge(self->stack, j, i)) { - made_changes = true; - i--; - j = i; - } - break; - - case ErrorComparisonPreferRight: - made_changes = true; - if (ts_stack_merge(self->stack, j, i)) { - i--; - j = i; - } else { - ts_stack_swap_versions(self->stack, i, j); - } - break; - - case ErrorComparisonTakeRight: - made_changes = true; - ts_stack_remove_version(self->stack, j); - i--; - j--; - break; - } - } - } - - // Enfore a hard upper bound on the number of stack versions by - // discarding the least promising versions. - while (ts_stack_version_count(self->stack) > MAX_VERSION_COUNT) { - ts_stack_remove_version(self->stack, MAX_VERSION_COUNT); - made_changes = true; - } - - // If the best-performing stack version is currently paused, or all - // versions are paused, then resume the best paused version and begin - // the error recovery process. Otherwise, remove the paused versions. - if (ts_stack_version_count(self->stack) > 0) { - bool has_unpaused_version = false; - for (StackVersion i = 0, n = ts_stack_version_count(self->stack); i < n; i++) { - if (ts_stack_is_paused(self->stack, i)) { - if (!has_unpaused_version && self->accept_count < MAX_VERSION_COUNT) { - LOG("resume version:%u", i); - min_error_cost = ts_stack_error_cost(self->stack, i); - TSSymbol lookahead_symbol = ts_stack_resume(self->stack, i); - ts_parser__handle_error(self, i, lookahead_symbol); - has_unpaused_version = true; - } else { - ts_stack_remove_version(self->stack, i); - i--; - n--; - } - } else { - has_unpaused_version = true; - } - } - } - - if (made_changes) { - LOG("condense"); - LOG_STACK(); - } - - return min_error_cost; -} - -static bool ts_parser_has_outstanding_parse(TSParser *self) { - return ( - ts_stack_state(self->stack, 0) != 1 || - ts_stack_node_count_since_error(self->stack, 0) != 0 - ); -} - -// Parser - Public - -TSParser *ts_parser_new(void) { - TSParser *self = ts_calloc(1, sizeof(TSParser)); - ts_lexer_init(&self->lexer); - array_init(&self->reduce_actions); - array_reserve(&self->reduce_actions, 4); - self->tree_pool = ts_subtree_pool_new(32); - self->stack = ts_stack_new(&self->tree_pool); - self->finished_tree = NULL_SUBTREE; - self->reusable_node = reusable_node_new(); - self->dot_graph_file = NULL; - self->cancellation_flag = NULL; - self->timeout_duration = 0; - self->end_clock = clock_null(); - self->operation_count = 0; - self->old_tree = NULL_SUBTREE; - self->scratch_tree.ptr = &self->scratch_tree_data; - self->included_range_differences = (TSRangeArray) array_new(); - self->included_range_difference_index = 0; - ts_parser__set_cached_token(self, 0, NULL_SUBTREE, NULL_SUBTREE); - return self; -} - -void ts_parser_delete(TSParser *self) { - if (!self) return; - - ts_parser_set_language(self, NULL); - ts_stack_delete(self->stack); - if (self->reduce_actions.contents) { - array_delete(&self->reduce_actions); - } - if (self->included_range_differences.contents) { - array_delete(&self->included_range_differences); - } - if (self->old_tree.ptr) { - ts_subtree_release(&self->tree_pool, self->old_tree); - self->old_tree = NULL_SUBTREE; - } - ts_lexer_delete(&self->lexer); - ts_parser__set_cached_token(self, 0, NULL_SUBTREE, NULL_SUBTREE); - ts_subtree_pool_delete(&self->tree_pool); - reusable_node_delete(&self->reusable_node); - ts_free(self); -} - -const TSLanguage *ts_parser_language(const TSParser *self) { - return self->language; -} - -bool ts_parser_set_language(TSParser *self, const TSLanguage *language) { - if (language) { - if (language->version > TREE_SITTER_LANGUAGE_VERSION) return false; - if (language->version < TREE_SITTER_MIN_COMPATIBLE_LANGUAGE_VERSION) return false; - } - - if (self->external_scanner_payload && self->language->external_scanner.destroy) { - self->language->external_scanner.destroy(self->external_scanner_payload); - } - - if (language && language->external_scanner.create) { - self->external_scanner_payload = language->external_scanner.create(); - } else { - self->external_scanner_payload = NULL; - } - - self->language = language; - ts_parser_reset(self); - return true; -} - -TSLogger ts_parser_logger(const TSParser *self) { - return self->lexer.logger; -} - -void ts_parser_set_logger(TSParser *self, TSLogger logger) { - self->lexer.logger = logger; -} - -void ts_parser_print_dot_graphs(TSParser *self, int fd) { - if (self->dot_graph_file) { - fclose(self->dot_graph_file); - } - - if (fd >= 0) { - self->dot_graph_file = fdopen(fd, "a"); - } else { - self->dot_graph_file = NULL; - } -} - -const size_t *ts_parser_cancellation_flag(const TSParser *self) { - return (const size_t *)self->cancellation_flag; -} - -void ts_parser_set_cancellation_flag(TSParser *self, const size_t *flag) { - self->cancellation_flag = (const volatile size_t *)flag; -} - -uint64_t ts_parser_timeout_micros(const TSParser *self) { - return duration_to_micros(self->timeout_duration); -} - -void ts_parser_set_timeout_micros(TSParser *self, uint64_t timeout_micros) { - self->timeout_duration = duration_from_micros(timeout_micros); -} - -bool ts_parser_set_included_ranges( - TSParser *self, - const TSRange *ranges, - uint32_t count -) { - return ts_lexer_set_included_ranges(&self->lexer, ranges, count); -} - -const TSRange *ts_parser_included_ranges(const TSParser *self, uint32_t *count) { - return ts_lexer_included_ranges(&self->lexer, count); -} - -void ts_parser_reset(TSParser *self) { - if (self->language && self->language->external_scanner.deserialize) { - self->language->external_scanner.deserialize(self->external_scanner_payload, NULL, 0); - } - - if (self->old_tree.ptr) { - ts_subtree_release(&self->tree_pool, self->old_tree); - self->old_tree = NULL_SUBTREE; - } - - reusable_node_clear(&self->reusable_node); - ts_lexer_reset(&self->lexer, length_zero()); - ts_stack_clear(self->stack); - ts_parser__set_cached_token(self, 0, NULL_SUBTREE, NULL_SUBTREE); - if (self->finished_tree.ptr) { - ts_subtree_release(&self->tree_pool, self->finished_tree); - self->finished_tree = NULL_SUBTREE; - } - self->accept_count = 0; -} - -TSTree *ts_parser_parse( - TSParser *self, - const TSTree *old_tree, - TSInput input -) { - if (!self->language || !input.read) return NULL; - - ts_lexer_set_input(&self->lexer, input); - - array_clear(&self->included_range_differences); - self->included_range_difference_index = 0; - - if (ts_parser_has_outstanding_parse(self)) { - LOG("resume_parsing"); - } else if (old_tree) { - ts_subtree_retain(old_tree->root); - self->old_tree = old_tree->root; - ts_range_array_get_changed_ranges( - old_tree->included_ranges, old_tree->included_range_count, - self->lexer.included_ranges, self->lexer.included_range_count, - &self->included_range_differences - ); - reusable_node_reset(&self->reusable_node, old_tree->root); - LOG("parse_after_edit"); - LOG_TREE(self->old_tree); - for (unsigned i = 0; i < self->included_range_differences.size; i++) { - TSRange *range = &self->included_range_differences.contents[i]; - LOG("different_included_range %u - %u", range->start_byte, range->end_byte); - } - } else { - reusable_node_clear(&self->reusable_node); - LOG("new_parse"); - } - - uint32_t position = 0, last_position = 0, version_count = 0; - self->operation_count = 0; - if (self->timeout_duration) { - self->end_clock = clock_after(clock_now(), self->timeout_duration); - } else { - self->end_clock = clock_null(); - } - - do { - for (StackVersion version = 0; - version_count = ts_stack_version_count(self->stack), version < version_count; - version++) { - bool allow_node_reuse = version_count == 1; - while (ts_stack_is_active(self->stack, version)) { - LOG("process version:%d, version_count:%u, state:%d, row:%u, col:%u", - version, ts_stack_version_count(self->stack), - ts_stack_state(self->stack, version), - ts_stack_position(self->stack, version).extent.row + 1, - ts_stack_position(self->stack, version).extent.column); - - if (!ts_parser__advance(self, version, allow_node_reuse)) return NULL; - LOG_STACK(); - - position = ts_stack_position(self->stack, version).bytes; - if (position > last_position || (version > 0 && position == last_position)) { - last_position = position; - break; - } - } - } - - unsigned min_error_cost = ts_parser__condense_stack(self); - if (self->finished_tree.ptr && ts_subtree_error_cost(self->finished_tree) < min_error_cost) { - break; - } - - while (self->included_range_difference_index < self->included_range_differences.size) { - TSRange *range = &self->included_range_differences.contents[self->included_range_difference_index]; - if (range->end_byte <= position) { - self->included_range_difference_index++; - } else { - break; - } - } - } while (version_count != 0); - - ts_subtree_balance(self->finished_tree, &self->tree_pool, self->language); - LOG("done"); - LOG_TREE(self->finished_tree); - - TSTree *result = ts_tree_new( - self->finished_tree, - self->language, - self->lexer.included_ranges, - self->lexer.included_range_count - ); - self->finished_tree = NULL_SUBTREE; - ts_parser_reset(self); - return result; -} - -TSTree *ts_parser_parse_string( - TSParser *self, - const TSTree *old_tree, - const char *string, - uint32_t length -) { - return ts_parser_parse_string_encoding(self, old_tree, string, length, TSInputEncodingUTF8); -} - -TSTree *ts_parser_parse_string_encoding(TSParser *self, const TSTree *old_tree, - const char *string, uint32_t length, TSInputEncoding encoding) { - TSStringInput input = {string, length}; - return ts_parser_parse(self, old_tree, (TSInput) { - &input, - ts_string_input_read, - encoding, - }); -} - -#undef LOG diff --git a/src/tree_sitter/parser.h b/src/tree_sitter/parser.h deleted file mode 100644 index 11bf4fc42a..0000000000 --- a/src/tree_sitter/parser.h +++ /dev/null @@ -1,235 +0,0 @@ -#ifndef TREE_SITTER_PARSER_H_ -#define TREE_SITTER_PARSER_H_ - -#ifdef __cplusplus -extern "C" { -#endif - -#include <stdbool.h> -#include <stdint.h> -#include <stdlib.h> - -#define ts_builtin_sym_error ((TSSymbol)-1) -#define ts_builtin_sym_end 0 -#define TREE_SITTER_SERIALIZATION_BUFFER_SIZE 1024 - -#ifndef TREE_SITTER_API_H_ -typedef uint16_t TSSymbol; -typedef uint16_t TSFieldId; -typedef struct TSLanguage TSLanguage; -#endif - -typedef struct { - TSFieldId field_id; - uint8_t child_index; - bool inherited; -} TSFieldMapEntry; - -typedef struct { - uint16_t index; - uint16_t length; -} TSFieldMapSlice; - -typedef uint16_t TSStateId; - -typedef struct { - bool visible : 1; - bool named : 1; -} TSSymbolMetadata; - -typedef struct TSLexer TSLexer; - -struct TSLexer { - int32_t lookahead; - TSSymbol result_symbol; - void (*advance)(TSLexer *, bool); - void (*mark_end)(TSLexer *); - uint32_t (*get_column)(TSLexer *); - bool (*is_at_included_range_start)(const TSLexer *); - bool (*eof)(const TSLexer *); -}; - -typedef enum { - TSParseActionTypeShift, - TSParseActionTypeReduce, - TSParseActionTypeAccept, - TSParseActionTypeRecover, -} TSParseActionType; - -typedef struct { - union { - struct { - TSStateId state; - bool extra : 1; - bool repetition : 1; - } shift; - struct { - TSSymbol symbol; - int16_t dynamic_precedence; - uint8_t child_count; - uint8_t production_id; - } reduce; - } params; - TSParseActionType type : 4; -} TSParseAction; - -typedef struct { - uint16_t lex_state; - uint16_t external_lex_state; -} TSLexMode; - -typedef union { - TSParseAction action; - struct { - uint8_t count; - bool reusable : 1; - } entry; -} TSParseActionEntry; - -struct TSLanguage { - uint32_t version; - uint32_t symbol_count; - uint32_t alias_count; - uint32_t token_count; - uint32_t external_token_count; - const char **symbol_names; - const TSSymbolMetadata *symbol_metadata; - const uint16_t *parse_table; - const TSParseActionEntry *parse_actions; - const TSLexMode *lex_modes; - const TSSymbol *alias_sequences; - uint16_t max_alias_sequence_length; - bool (*lex_fn)(TSLexer *, TSStateId); - bool (*keyword_lex_fn)(TSLexer *, TSStateId); - TSSymbol keyword_capture_token; - struct { - const bool *states; - const TSSymbol *symbol_map; - void *(*create)(void); - void (*destroy)(void *); - bool (*scan)(void *, TSLexer *, const bool *symbol_whitelist); - unsigned (*serialize)(void *, char *); - void (*deserialize)(void *, const char *, unsigned); - } external_scanner; - uint32_t field_count; - const TSFieldMapSlice *field_map_slices; - const TSFieldMapEntry *field_map_entries; - const char **field_names; - uint32_t large_state_count; - const uint16_t *small_parse_table; - const uint32_t *small_parse_table_map; - const TSSymbol *public_symbol_map; -}; - -/* - * Lexer Macros - */ - -#define START_LEXER() \ - bool result = false; \ - bool skip = false; \ - bool eof = false; \ - int32_t lookahead; \ - goto start; \ - next_state: \ - lexer->advance(lexer, skip); \ - start: \ - skip = false; \ - lookahead = lexer->lookahead; - -#define ADVANCE(state_value) \ - { \ - state = state_value; \ - goto next_state; \ - } - -#define SKIP(state_value) \ - { \ - skip = true; \ - state = state_value; \ - goto next_state; \ - } - -#define ACCEPT_TOKEN(symbol_value) \ - result = true; \ - lexer->result_symbol = symbol_value; \ - lexer->mark_end(lexer); - -#define END_STATE() return result; - -/* - * Parse Table Macros - */ - -#define SMALL_STATE(id) id - LARGE_STATE_COUNT - -#define STATE(id) id - -#define ACTIONS(id) id - -#define SHIFT(state_value) \ - { \ - { \ - .params = { \ - .shift = { \ - .state = state_value \ - } \ - }, \ - .type = TSParseActionTypeShift \ - } \ - } - -#define SHIFT_REPEAT(state_value) \ - { \ - { \ - .params = { \ - .shift = { \ - .state = state_value, \ - .repetition = true \ - } \ - }, \ - .type = TSParseActionTypeShift \ - } \ - } - -#define RECOVER() \ - { \ - { .type = TSParseActionTypeRecover } \ - } - -#define SHIFT_EXTRA() \ - { \ - { \ - .params = { \ - .shift = { \ - .extra = true \ - } \ - }, \ - .type = TSParseActionTypeShift \ - } \ - } - -#define REDUCE(symbol_val, child_count_val, ...) \ - { \ - { \ - .params = { \ - .reduce = { \ - .symbol = symbol_val, \ - .child_count = child_count_val, \ - __VA_ARGS__ \ - }, \ - }, \ - .type = TSParseActionTypeReduce \ - } \ - } - -#define ACCEPT_INPUT() \ - { \ - { .type = TSParseActionTypeAccept } \ - } - -#ifdef __cplusplus -} -#endif - -#endif // TREE_SITTER_PARSER_H_ diff --git a/src/tree_sitter/point.h b/src/tree_sitter/point.h deleted file mode 100644 index a50d20214b..0000000000 --- a/src/tree_sitter/point.h +++ /dev/null @@ -1,54 +0,0 @@ -#ifndef TREE_SITTER_POINT_H_ -#define TREE_SITTER_POINT_H_ - -#include "tree_sitter/api.h" - -#define POINT_ZERO ((TSPoint) {0, 0}) -#define POINT_MAX ((TSPoint) {UINT32_MAX, UINT32_MAX}) - -static inline TSPoint point__new(unsigned row, unsigned column) { - TSPoint result = {row, column}; - return result; -} - -static inline TSPoint point_add(TSPoint a, TSPoint b) { - if (b.row > 0) - return point__new(a.row + b.row, b.column); - else - return point__new(a.row, a.column + b.column); -} - -static inline TSPoint point_sub(TSPoint a, TSPoint b) { - if (a.row > b.row) - return point__new(a.row - b.row, a.column); - else - return point__new(0, a.column - b.column); -} - -static inline bool point_lte(TSPoint a, TSPoint b) { - return (a.row < b.row) || (a.row == b.row && a.column <= b.column); -} - -static inline bool point_lt(TSPoint a, TSPoint b) { - return (a.row < b.row) || (a.row == b.row && a.column < b.column); -} - -static inline bool point_eq(TSPoint a, TSPoint b) { - return a.row == b.row && a.column == b.column; -} - -static inline TSPoint point_min(TSPoint a, TSPoint b) { - if (a.row < b.row || (a.row == b.row && a.column < b.column)) - return a; - else - return b; -} - -static inline TSPoint point_max(TSPoint a, TSPoint b) { - if (a.row > b.row || (a.row == b.row && a.column > b.column)) - return a; - else - return b; -} - -#endif diff --git a/src/tree_sitter/query.c b/src/tree_sitter/query.c deleted file mode 100644 index 59902dee3b..0000000000 --- a/src/tree_sitter/query.c +++ /dev/null @@ -1,2035 +0,0 @@ -#include "tree_sitter/api.h" -#include "./alloc.h" -#include "./array.h" -#include "./bits.h" -#include "./language.h" -#include "./point.h" -#include "./tree_cursor.h" -#include "./unicode.h" -#include <wctype.h> - -// #define LOG(...) fprintf(stderr, __VA_ARGS__) -#define LOG(...) - -#define MAX_STATE_COUNT 256 -#define MAX_CAPTURE_LIST_COUNT 32 -#define MAX_STEP_CAPTURE_COUNT 3 - -/* - * Stream - A sequence of unicode characters derived from a UTF8 string. - * This struct is used in parsing queries from S-expressions. - */ -typedef struct { - const char *input; - const char *end; - int32_t next; - uint8_t next_size; -} Stream; - -/* - * QueryStep - A step in the process of matching a query. Each node within - * a query S-expression maps to one of these steps. An entire pattern is - * represented as a sequence of these steps. Fields: - * - * - `symbol` - The grammar symbol to match. A zero value represents the - * wildcard symbol, '_'. - * - `field` - The field name to match. A zero value means that a field name - * was not specified. - * - `capture_ids` - An array of integers representing the names of captures - * associated with this node in the pattern, terminated by a `NONE` value. - * - `depth` - The depth where this node occurs in the pattern. The root node - * of the pattern has depth zero. - * - `alternative_index` - The index of a different query step that serves as - * an alternative to this step. - */ -typedef struct { - TSSymbol symbol; - TSFieldId field; - uint16_t capture_ids[MAX_STEP_CAPTURE_COUNT]; - uint16_t alternative_index; - uint16_t depth; - bool contains_captures: 1; - bool is_pattern_start: 1; - bool is_immediate: 1; - bool is_last_child: 1; - bool is_pass_through: 1; - bool is_dead_end: 1; - bool alternative_is_immediate: 1; -} QueryStep; - -/* - * Slice - A slice of an external array. Within a query, capture names, - * literal string values, and predicate step informations are stored in three - * contiguous arrays. Individual captures, string values, and predicates are - * represented as slices of these three arrays. - */ -typedef struct { - uint32_t offset; - uint32_t length; -} Slice; - -/* - * SymbolTable - a two-way mapping of strings to ids. - */ -typedef struct { - Array(char) characters; - Array(Slice) slices; -} SymbolTable; - -/* - * PatternEntry - Information about the starting point for matching a - * particular pattern, consisting of the index of the pattern within the query, - * and the index of the patter's first step in the shared `steps` array. These - * entries are stored in a 'pattern map' - a sorted array that makes it - * possible to efficiently lookup patterns based on the symbol for their first - * step. - */ -typedef struct { - uint16_t step_index; - uint16_t pattern_index; -} PatternEntry; - -/* - * QueryState - The state of an in-progress match of a particular pattern - * in a query. While executing, a `TSQueryCursor` must keep track of a number - * of possible in-progress matches. Each of those possible matches is - * represented as one of these states. Fields: - * - `id` - A numeric id that is exposed to the public API. This allows the - * caller to remove a given match, preventing any more of its captures - * from being returned. - * - `start_depth` - The depth in the tree where the first step of the state's - * pattern was matched. - * - `pattern_index` - The pattern that the state is matching. - * - `consumed_capture_count` - The number of captures from this match that - * have already been returned. - * - `capture_list_id` - A numeric id that can be used to retrieve the state's - * list of captures from the `CaptureListPool`. - * - `seeking_immediate_match` - A flag that indicates that the state's next - * step must be matched by the very next sibling. This is used when - * processing repetitions. - * - `has_in_progress_alternatives` - A flag that indicates that there is are - * other states that have the same captures as this state, but are at - * different steps in their pattern. This means that in order to obey the - * 'longest-match' rule, this state should not be returned as a match until - * it is clear that there can be no longer match. - */ -typedef struct { - uint32_t id; - uint16_t start_depth; - uint16_t step_index; - uint16_t pattern_index; - uint16_t capture_list_id; - uint16_t consumed_capture_count: 14; - bool seeking_immediate_match: 1; - bool has_in_progress_alternatives: 1; -} QueryState; - -typedef Array(TSQueryCapture) CaptureList; - -/* - * CaptureListPool - A collection of *lists* of captures. Each QueryState - * needs to maintain its own list of captures. To avoid repeated allocations, - * the reuses a fixed set of capture lists, and keeps track of which ones - * are currently in use. - */ -typedef struct { - CaptureList list[MAX_CAPTURE_LIST_COUNT]; - CaptureList empty_list; - uint32_t usage_map; -} CaptureListPool; - -/* - * TSQuery - A tree query, compiled from a string of S-expressions. The query - * itself is immutable. The mutable state used in the process of executing the - * query is stored in a `TSQueryCursor`. - */ -struct TSQuery { - SymbolTable captures; - SymbolTable predicate_values; - Array(QueryStep) steps; - Array(PatternEntry) pattern_map; - Array(TSQueryPredicateStep) predicate_steps; - Array(Slice) predicates_by_pattern; - Array(uint32_t) start_bytes_by_pattern; - const TSLanguage *language; - uint16_t wildcard_root_pattern_count; - TSSymbol *symbol_map; -}; - -/* - * TSQueryCursor - A stateful struct used to execute a query on a tree. - */ -struct TSQueryCursor { - const TSQuery *query; - TSTreeCursor cursor; - Array(QueryState) states; - Array(QueryState) finished_states; - CaptureListPool capture_list_pool; - uint32_t depth; - uint32_t start_byte; - uint32_t end_byte; - uint32_t next_state_id; - TSPoint start_point; - TSPoint end_point; - bool ascending; -}; - -static const TSQueryError PARENT_DONE = -1; -static const uint16_t PATTERN_DONE_MARKER = UINT16_MAX; -static const uint16_t NONE = UINT16_MAX; -static const TSSymbol WILDCARD_SYMBOL = 0; -static const TSSymbol NAMED_WILDCARD_SYMBOL = UINT16_MAX - 1; - -/********** - * Stream - **********/ - -// Advance to the next unicode code point in the stream. -static bool stream_advance(Stream *self) { - self->input += self->next_size; - if (self->input < self->end) { - uint32_t size = ts_decode_utf8( - (const uint8_t *)self->input, - self->end - self->input, - &self->next - ); - if (size > 0) { - self->next_size = size; - return true; - } - } else { - self->next_size = 0; - self->next = '\0'; - } - return false; -} - -// Reset the stream to the given input position, represented as a pointer -// into the input string. -static void stream_reset(Stream *self, const char *input) { - self->input = input; - self->next_size = 0; - stream_advance(self); -} - -static Stream stream_new(const char *string, uint32_t length) { - Stream self = { - .next = 0, - .input = string, - .end = string + length, - }; - stream_advance(&self); - return self; -} - -static void stream_skip_whitespace(Stream *stream) { - for (;;) { - if (iswspace(stream->next)) { - stream_advance(stream); - } else if (stream->next == ';') { - // skip over comments - stream_advance(stream); - while (stream->next && stream->next != '\n') { - if (!stream_advance(stream)) break; - } - } else { - break; - } - } -} - -static bool stream_is_ident_start(Stream *stream) { - return iswalnum(stream->next) || stream->next == '_' || stream->next == '-'; -} - -static void stream_scan_identifier(Stream *stream) { - do { - stream_advance(stream); - } while ( - iswalnum(stream->next) || - stream->next == '_' || - stream->next == '-' || - stream->next == '.' || - stream->next == '?' || - stream->next == '!' - ); -} - -/****************** - * CaptureListPool - ******************/ - -static CaptureListPool capture_list_pool_new(void) { - return (CaptureListPool) { - .empty_list = array_new(), - .usage_map = UINT32_MAX, - }; -} - -static void capture_list_pool_reset(CaptureListPool *self) { - self->usage_map = UINT32_MAX; - for (unsigned i = 0; i < MAX_CAPTURE_LIST_COUNT; i++) { - array_clear(&self->list[i]); - } -} - -static void capture_list_pool_delete(CaptureListPool *self) { - for (unsigned i = 0; i < MAX_CAPTURE_LIST_COUNT; i++) { - array_delete(&self->list[i]); - } -} - -static const CaptureList *capture_list_pool_get(const CaptureListPool *self, uint16_t id) { - if (id >= MAX_CAPTURE_LIST_COUNT) return &self->empty_list; - return &self->list[id]; -} - -static CaptureList *capture_list_pool_get_mut(CaptureListPool *self, uint16_t id) { - assert(id < MAX_CAPTURE_LIST_COUNT); - return &self->list[id]; -} - -static bool capture_list_pool_is_empty(const CaptureListPool *self) { - return self->usage_map == 0; -} - -static uint16_t capture_list_pool_acquire(CaptureListPool *self) { - // In the usage_map bitmask, ones represent free lists, and zeros represent - // lists that are in use. A free list id can quickly be found by counting - // the leading zeros in the usage map. An id of zero corresponds to the - // highest-order bit in the bitmask. - uint16_t id = count_leading_zeros(self->usage_map); - if (id >= MAX_CAPTURE_LIST_COUNT) return NONE; - self->usage_map &= ~bitmask_for_index(id); - array_clear(&self->list[id]); - return id; -} - -static void capture_list_pool_release(CaptureListPool *self, uint16_t id) { - if (id >= MAX_CAPTURE_LIST_COUNT) return; - array_clear(&self->list[id]); - self->usage_map |= bitmask_for_index(id); -} - -/************** - * SymbolTable - **************/ - -static SymbolTable symbol_table_new(void) { - return (SymbolTable) { - .characters = array_new(), - .slices = array_new(), - }; -} - -static void symbol_table_delete(SymbolTable *self) { - array_delete(&self->characters); - array_delete(&self->slices); -} - -static int symbol_table_id_for_name( - const SymbolTable *self, - const char *name, - uint32_t length -) { - for (unsigned i = 0; i < self->slices.size; i++) { - Slice slice = self->slices.contents[i]; - if ( - slice.length == length && - !strncmp(&self->characters.contents[slice.offset], name, length) - ) return i; - } - return -1; -} - -static const char *symbol_table_name_for_id( - const SymbolTable *self, - uint16_t id, - uint32_t *length -) { - Slice slice = self->slices.contents[id]; - *length = slice.length; - return &self->characters.contents[slice.offset]; -} - -static uint16_t symbol_table_insert_name( - SymbolTable *self, - const char *name, - uint32_t length -) { - int id = symbol_table_id_for_name(self, name, length); - if (id >= 0) return (uint16_t)id; - Slice slice = { - .offset = self->characters.size, - .length = length, - }; - array_grow_by(&self->characters, length + 1); - memcpy(&self->characters.contents[slice.offset], name, length); - self->characters.contents[self->characters.size - 1] = 0; - array_push(&self->slices, slice); - return self->slices.size - 1; -} - -static uint16_t symbol_table_insert_name_with_escapes( - SymbolTable *self, - const char *escaped_name, - uint32_t escaped_length -) { - Slice slice = { - .offset = self->characters.size, - .length = 0, - }; - array_grow_by(&self->characters, escaped_length + 1); - - // Copy the contents of the literal into the characters buffer, processing escape - // sequences like \n and \". This needs to be done before checking if the literal - // is already present, in order to do the string comparison. - bool is_escaped = false; - for (unsigned i = 0; i < escaped_length; i++) { - const char *src = &escaped_name[i]; - char *dest = &self->characters.contents[slice.offset + slice.length]; - if (is_escaped) { - switch (*src) { - case 'n': - *dest = '\n'; - break; - case 'r': - *dest = '\r'; - break; - case 't': - *dest = '\t'; - break; - case '0': - *dest = '\0'; - break; - default: - *dest = *src; - break; - } - is_escaped = false; - slice.length++; - } else { - if (*src == '\\') { - is_escaped = true; - } else { - *dest = *src; - slice.length++; - } - } - } - - // If the string is already present, remove the redundant content from the characters - // buffer and return the existing id. - int id = symbol_table_id_for_name(self, &self->characters.contents[slice.offset], slice.length); - if (id >= 0) { - self->characters.size -= (escaped_length + 1); - return id; - } - - self->characters.contents[slice.offset + slice.length] = 0; - array_push(&self->slices, slice); - return self->slices.size - 1; -} - -/************ - * QueryStep - ************/ - -static QueryStep query_step__new( - TSSymbol symbol, - uint16_t depth, - bool is_immediate -) { - return (QueryStep) { - .symbol = symbol, - .depth = depth, - .field = 0, - .capture_ids = {NONE, NONE, NONE}, - .alternative_index = NONE, - .contains_captures = false, - .is_last_child = false, - .is_pattern_start = false, - .is_pass_through = false, - .is_dead_end = false, - .is_immediate = is_immediate, - .alternative_is_immediate = false, - }; -} - -static void query_step__add_capture(QueryStep *self, uint16_t capture_id) { - for (unsigned i = 0; i < MAX_STEP_CAPTURE_COUNT; i++) { - if (self->capture_ids[i] == NONE) { - self->capture_ids[i] = capture_id; - break; - } - } -} - -static void query_step__remove_capture(QueryStep *self, uint16_t capture_id) { - for (unsigned i = 0; i < MAX_STEP_CAPTURE_COUNT; i++) { - if (self->capture_ids[i] == capture_id) { - self->capture_ids[i] = NONE; - while (i + 1 < MAX_STEP_CAPTURE_COUNT) { - if (self->capture_ids[i + 1] == NONE) break; - self->capture_ids[i] = self->capture_ids[i + 1]; - self->capture_ids[i + 1] = NONE; - i++; - } - break; - } - } -} - -/********* - * Query - *********/ - -// The `pattern_map` contains a mapping from TSSymbol values to indices in the -// `steps` array. For a given syntax node, the `pattern_map` makes it possible -// to quickly find the starting steps of all of the patterns whose root matches -// that node. Each entry has two fields: a `pattern_index`, which identifies one -// of the patterns in the query, and a `step_index`, which indicates the start -// offset of that pattern's steps within the `steps` array. -// -// The entries are sorted by the patterns' root symbols, and lookups use a -// binary search. This ensures that the cost of this initial lookup step -// scales logarithmically with the number of patterns in the query. -// -// This returns `true` if the symbol is present and `false` otherwise. -// If the symbol is not present `*result` is set to the index where the -// symbol should be inserted. -static inline bool ts_query__pattern_map_search( - const TSQuery *self, - TSSymbol needle, - uint32_t *result -) { - uint32_t base_index = self->wildcard_root_pattern_count; - uint32_t size = self->pattern_map.size - base_index; - if (size == 0) { - *result = base_index; - return false; - } - while (size > 1) { - uint32_t half_size = size / 2; - uint32_t mid_index = base_index + half_size; - TSSymbol mid_symbol = self->steps.contents[ - self->pattern_map.contents[mid_index].step_index - ].symbol; - if (needle > mid_symbol) base_index = mid_index; - size -= half_size; - } - - TSSymbol symbol = self->steps.contents[ - self->pattern_map.contents[base_index].step_index - ].symbol; - - if (needle > symbol) { - base_index++; - if (base_index < self->pattern_map.size) { - symbol = self->steps.contents[ - self->pattern_map.contents[base_index].step_index - ].symbol; - } - } - - *result = base_index; - return needle == symbol; -} - -// Insert a new pattern's start index into the pattern map, maintaining -// the pattern map's ordering invariant. -static inline void ts_query__pattern_map_insert( - TSQuery *self, - TSSymbol symbol, - uint32_t start_step_index, - uint32_t pattern_index -) { - uint32_t index; - ts_query__pattern_map_search(self, symbol, &index); - array_insert(&self->pattern_map, index, ((PatternEntry) { - .step_index = start_step_index, - .pattern_index = pattern_index, - })); -} - -static void ts_query__finalize_steps(TSQuery *self) { - for (unsigned i = 0; i < self->steps.size; i++) { - QueryStep *step = &self->steps.contents[i]; - uint32_t depth = step->depth; - if (step->capture_ids[0] != NONE) { - step->contains_captures = true; - } else { - step->contains_captures = false; - for (unsigned j = i + 1; j < self->steps.size; j++) { - QueryStep *s = &self->steps.contents[j]; - if (s->depth == PATTERN_DONE_MARKER || s->depth <= depth) break; - if (s->capture_ids[0] != NONE) step->contains_captures = true; - } - } - } -} - -// Parse a single predicate associated with a pattern, adding it to the -// query's internal `predicate_steps` array. Predicates are arbitrary -// S-expressions associated with a pattern which are meant to be handled at -// a higher level of abstraction, such as the Rust/JavaScript bindings. They -// can contain '@'-prefixed capture names, double-quoted strings, and bare -// symbols, which also represent strings. -static TSQueryError ts_query__parse_predicate( - TSQuery *self, - Stream *stream -) { - if (!stream_is_ident_start(stream)) return TSQueryErrorSyntax; - const char *predicate_name = stream->input; - stream_scan_identifier(stream); - uint32_t length = stream->input - predicate_name; - uint16_t id = symbol_table_insert_name( - &self->predicate_values, - predicate_name, - length - ); - array_back(&self->predicates_by_pattern)->length++; - array_push(&self->predicate_steps, ((TSQueryPredicateStep) { - .type = TSQueryPredicateStepTypeString, - .value_id = id, - })); - stream_skip_whitespace(stream); - - for (;;) { - if (stream->next == ')') { - stream_advance(stream); - stream_skip_whitespace(stream); - array_back(&self->predicates_by_pattern)->length++; - array_push(&self->predicate_steps, ((TSQueryPredicateStep) { - .type = TSQueryPredicateStepTypeDone, - .value_id = 0, - })); - break; - } - - // Parse an '@'-prefixed capture name - else if (stream->next == '@') { - stream_advance(stream); - - // Parse the capture name - if (!stream_is_ident_start(stream)) return TSQueryErrorSyntax; - const char *capture_name = stream->input; - stream_scan_identifier(stream); - uint32_t length = stream->input - capture_name; - - // Add the capture id to the first step of the pattern - int capture_id = symbol_table_id_for_name( - &self->captures, - capture_name, - length - ); - if (capture_id == -1) { - stream_reset(stream, capture_name); - return TSQueryErrorCapture; - } - - array_back(&self->predicates_by_pattern)->length++; - array_push(&self->predicate_steps, ((TSQueryPredicateStep) { - .type = TSQueryPredicateStepTypeCapture, - .value_id = capture_id, - })); - } - - // Parse a string literal - else if (stream->next == '"') { - stream_advance(stream); - - // Parse the string content - bool is_escaped = false; - const char *string_content = stream->input; - for (;;) { - if (is_escaped) { - is_escaped = false; - } else { - if (stream->next == '\\') { - is_escaped = true; - } else if (stream->next == '"') { - break; - } else if (stream->next == '\n') { - stream_reset(stream, string_content - 1); - return TSQueryErrorSyntax; - } - } - if (!stream_advance(stream)) { - stream_reset(stream, string_content - 1); - return TSQueryErrorSyntax; - } - } - uint32_t length = stream->input - string_content; - - // Add a step for the node - uint16_t id = symbol_table_insert_name_with_escapes( - &self->predicate_values, - string_content, - length - ); - array_back(&self->predicates_by_pattern)->length++; - array_push(&self->predicate_steps, ((TSQueryPredicateStep) { - .type = TSQueryPredicateStepTypeString, - .value_id = id, - })); - - if (stream->next != '"') return TSQueryErrorSyntax; - stream_advance(stream); - } - - // Parse a bare symbol - else if (stream_is_ident_start(stream)) { - const char *symbol_start = stream->input; - stream_scan_identifier(stream); - uint32_t length = stream->input - symbol_start; - uint16_t id = symbol_table_insert_name( - &self->predicate_values, - symbol_start, - length - ); - array_back(&self->predicates_by_pattern)->length++; - array_push(&self->predicate_steps, ((TSQueryPredicateStep) { - .type = TSQueryPredicateStepTypeString, - .value_id = id, - })); - } - - else { - return TSQueryErrorSyntax; - } - - stream_skip_whitespace(stream); - } - - return 0; -} - -// Read one S-expression pattern from the stream, and incorporate it into -// the query's internal state machine representation. For nested patterns, -// this function calls itself recursively. -static TSQueryError ts_query__parse_pattern( - TSQuery *self, - Stream *stream, - uint32_t depth, - uint32_t *capture_count, - bool is_immediate -) { - uint32_t starting_step_index = self->steps.size; - - if (stream->next == 0) return TSQueryErrorSyntax; - - // Finish the parent S-expression. - if (stream->next == ')' || stream->next == ']') { - return PARENT_DONE; - } - - // An open bracket is the start of an alternation. - else if (stream->next == '[') { - stream_advance(stream); - stream_skip_whitespace(stream); - - // Parse each branch, and add a placeholder step in between the branches. - Array(uint32_t) branch_step_indices = array_new(); - for (;;) { - uint32_t start_index = self->steps.size; - TSQueryError e = ts_query__parse_pattern( - self, - stream, - depth, - capture_count, - is_immediate - ); - - if (e == PARENT_DONE && stream->next == ']' && branch_step_indices.size > 0) { - stream_advance(stream); - break; - } else if (e) { - array_delete(&branch_step_indices); - return e; - } - - array_push(&branch_step_indices, start_index); - array_push(&self->steps, query_step__new(0, depth, false)); - } - (void)array_pop(&self->steps); - - // For all of the branches except for the last one, add the subsequent branch as an - // alternative, and link the end of the branch to the current end of the steps. - for (unsigned i = 0; i < branch_step_indices.size - 1; i++) { - uint32_t step_index = branch_step_indices.contents[i]; - uint32_t next_step_index = branch_step_indices.contents[i + 1]; - QueryStep *start_step = &self->steps.contents[step_index]; - QueryStep *end_step = &self->steps.contents[next_step_index - 1]; - start_step->alternative_index = next_step_index; - end_step->alternative_index = self->steps.size; - end_step->is_dead_end = true; - } - - array_delete(&branch_step_indices); - } - - // An open parenthesis can be the start of three possible constructs: - // * A grouped sequence - // * A predicate - // * A named node - else if (stream->next == '(') { - stream_advance(stream); - stream_skip_whitespace(stream); - - // If this parenthesis is followed by a node, then it represents a grouped sequence. - if (stream->next == '(' || stream->next == '"' || stream->next == '[') { - bool child_is_immediate = false; - for (;;) { - if (stream->next == '.') { - child_is_immediate = true; - stream_advance(stream); - stream_skip_whitespace(stream); - } - TSQueryError e = ts_query__parse_pattern( - self, - stream, - depth, - capture_count, - child_is_immediate - ); - if (e == PARENT_DONE && stream->next == ')') { - stream_advance(stream); - break; - } else if (e) { - return e; - } - - child_is_immediate = false; - } - } - - // A pound character indicates the start of a predicate. - else if (stream->next == '#') { - stream_advance(stream); - return ts_query__parse_predicate(self, stream); - } - - // Otherwise, this parenthesis is the start of a named node. - else { - TSSymbol symbol; - - // Parse the wildcard symbol - if ( - stream->next == '_' || - - // TODO - remove. - // For temporary backward compatibility, handle '*' as a wildcard. - stream->next == '*' - ) { - symbol = depth > 0 ? NAMED_WILDCARD_SYMBOL : WILDCARD_SYMBOL; - stream_advance(stream); - } - - // Parse a normal node name - else if (stream_is_ident_start(stream)) { - const char *node_name = stream->input; - stream_scan_identifier(stream); - uint32_t length = stream->input - node_name; - - // TODO - remove. - // For temporary backward compatibility, handle predicates without the leading '#' sign. - if (length > 0 && (node_name[length - 1] == '!' || node_name[length - 1] == '?')) { - stream_reset(stream, node_name); - return ts_query__parse_predicate(self, stream); - } - - symbol = ts_language_symbol_for_name( - self->language, - node_name, - length, - true - ); - if (!symbol) { - stream_reset(stream, node_name); - return TSQueryErrorNodeType; - } - } else { - return TSQueryErrorSyntax; - } - - // Add a step for the node. - array_push(&self->steps, query_step__new(symbol, depth, is_immediate)); - - // Parse the child patterns - stream_skip_whitespace(stream); - bool child_is_immediate = false; - uint16_t child_start_step_index = self->steps.size; - for (;;) { - if (stream->next == '.') { - child_is_immediate = true; - stream_advance(stream); - stream_skip_whitespace(stream); - } - - TSQueryError e = ts_query__parse_pattern( - self, - stream, - depth + 1, - capture_count, - child_is_immediate - ); - if (e == PARENT_DONE && stream->next == ')') { - if (child_is_immediate) { - self->steps.contents[child_start_step_index].is_last_child = true; - } - stream_advance(stream); - break; - } else if (e) { - return e; - } - - child_is_immediate = false; - } - } - } - - // Parse a wildcard pattern - else if ( - stream->next == '_' || - - // TODO remove. - // For temporary backward compatibility, handle '*' as a wildcard. - stream->next == '*' - ) { - stream_advance(stream); - stream_skip_whitespace(stream); - - // Add a step that matches any kind of node - array_push(&self->steps, query_step__new(WILDCARD_SYMBOL, depth, is_immediate)); - } - - // Parse a double-quoted anonymous leaf node expression - else if (stream->next == '"') { - stream_advance(stream); - - // Parse the string content - const char *string_content = stream->input; - while (stream->next != '"') { - if (!stream_advance(stream)) { - stream_reset(stream, string_content - 1); - return TSQueryErrorSyntax; - } - } - uint32_t length = stream->input - string_content; - - // Add a step for the node - TSSymbol symbol = ts_language_symbol_for_name( - self->language, - string_content, - length, - false - ); - if (!symbol) { - stream_reset(stream, string_content); - return TSQueryErrorNodeType; - } - array_push(&self->steps, query_step__new(symbol, depth, is_immediate)); - - if (stream->next != '"') return TSQueryErrorSyntax; - stream_advance(stream); - } - - // Parse a field-prefixed pattern - else if (stream_is_ident_start(stream)) { - // Parse the field name - const char *field_name = stream->input; - stream_scan_identifier(stream); - uint32_t length = stream->input - field_name; - stream_skip_whitespace(stream); - - if (stream->next != ':') { - stream_reset(stream, field_name); - return TSQueryErrorSyntax; - } - stream_advance(stream); - stream_skip_whitespace(stream); - - // Parse the pattern - uint32_t step_index = self->steps.size; - TSQueryError e = ts_query__parse_pattern( - self, - stream, - depth, - capture_count, - is_immediate - ); - if (e == PARENT_DONE) return TSQueryErrorSyntax; - if (e) return e; - - // Add the field name to the first step of the pattern - TSFieldId field_id = ts_language_field_id_for_name( - self->language, - field_name, - length - ); - if (!field_id) { - stream->input = field_name; - return TSQueryErrorField; - } - self->steps.contents[step_index].field = field_id; - } - - else { - return TSQueryErrorSyntax; - } - - stream_skip_whitespace(stream); - - // Parse suffixes modifiers for this pattern - for (;;) { - QueryStep *step = &self->steps.contents[starting_step_index]; - - // Parse the one-or-more operator. - if (stream->next == '+') { - stream_advance(stream); - stream_skip_whitespace(stream); - - QueryStep repeat_step = query_step__new(WILDCARD_SYMBOL, depth, false); - repeat_step.alternative_index = starting_step_index; - repeat_step.is_pass_through = true; - repeat_step.alternative_is_immediate = true; - array_push(&self->steps, repeat_step); - } - - // Parse the zero-or-more repetition operator. - else if (stream->next == '*') { - stream_advance(stream); - stream_skip_whitespace(stream); - - QueryStep repeat_step = query_step__new(WILDCARD_SYMBOL, depth, false); - repeat_step.alternative_index = starting_step_index; - repeat_step.is_pass_through = true; - repeat_step.alternative_is_immediate = true; - array_push(&self->steps, repeat_step); - - while (step->alternative_index != NONE) { - step = &self->steps.contents[step->alternative_index]; - } - step->alternative_index = self->steps.size; - } - - // Parse the optional operator. - else if (stream->next == '?') { - stream_advance(stream); - stream_skip_whitespace(stream); - - while (step->alternative_index != NONE) { - step = &self->steps.contents[step->alternative_index]; - } - step->alternative_index = self->steps.size; - } - - // Parse an '@'-prefixed capture pattern - else if (stream->next == '@') { - stream_advance(stream); - if (!stream_is_ident_start(stream)) return TSQueryErrorSyntax; - const char *capture_name = stream->input; - stream_scan_identifier(stream); - uint32_t length = stream->input - capture_name; - stream_skip_whitespace(stream); - - // Add the capture id to the first step of the pattern - uint16_t capture_id = symbol_table_insert_name( - &self->captures, - capture_name, - length - ); - - for (;;) { - query_step__add_capture(step, capture_id); - if ( - step->alternative_index != NONE && - step->alternative_index > starting_step_index && - step->alternative_index < self->steps.size - ) { - starting_step_index = step->alternative_index; - step = &self->steps.contents[starting_step_index]; - } else { - break; - } - } - - (*capture_count)++; - } - - // No more suffix modifiers - else { - break; - } - } - - return 0; -} - -TSQuery *ts_query_new( - const TSLanguage *language, - const char *source, - uint32_t source_len, - uint32_t *error_offset, - TSQueryError *error_type -) { - TSSymbol *symbol_map; - if (ts_language_version(language) >= TREE_SITTER_LANGUAGE_VERSION_WITH_SYMBOL_DEDUPING) { - symbol_map = NULL; - } else { - // Work around the fact that multiple symbols can currently be - // associated with the same name, due to "simple aliases". - // In the next language ABI version, this map will be contained - // in the language's `public_symbol_map` field. - uint32_t symbol_count = ts_language_symbol_count(language); - symbol_map = ts_malloc(sizeof(TSSymbol) * symbol_count); - for (unsigned i = 0; i < symbol_count; i++) { - const char *name = ts_language_symbol_name(language, i); - const TSSymbolType symbol_type = ts_language_symbol_type(language, i); - - symbol_map[i] = i; - - for (unsigned j = 0; j < i; j++) { - if (ts_language_symbol_type(language, j) == symbol_type) { - if (!strcmp(name, ts_language_symbol_name(language, j))) { - symbol_map[i] = j; - break; - } - } - } - } - } - - TSQuery *self = ts_malloc(sizeof(TSQuery)); - *self = (TSQuery) { - .steps = array_new(), - .pattern_map = array_new(), - .captures = symbol_table_new(), - .predicate_values = symbol_table_new(), - .predicate_steps = array_new(), - .predicates_by_pattern = array_new(), - .symbol_map = symbol_map, - .wildcard_root_pattern_count = 0, - .language = language, - }; - - // Parse all of the S-expressions in the given string. - Stream stream = stream_new(source, source_len); - stream_skip_whitespace(&stream); - while (stream.input < stream.end) { - uint32_t pattern_index = self->predicates_by_pattern.size; - uint32_t start_step_index = self->steps.size; - uint32_t capture_count = 0; - array_push(&self->start_bytes_by_pattern, stream.input - source); - array_push(&self->predicates_by_pattern, ((Slice) { - .offset = self->predicate_steps.size, - .length = 0, - })); - *error_type = ts_query__parse_pattern(self, &stream, 0, &capture_count, false); - array_push(&self->steps, query_step__new(0, PATTERN_DONE_MARKER, false)); - - // If any pattern could not be parsed, then report the error information - // and terminate. - if (*error_type) { - if (*error_type == PARENT_DONE) *error_type = TSQueryErrorSyntax; - *error_offset = stream.input - source; - ts_query_delete(self); - return NULL; - } - - // If a pattern has a wildcard at its root, optimize the matching process - // by skipping matching the wildcard. - if ( - self->steps.contents[start_step_index].symbol == WILDCARD_SYMBOL - ) { - QueryStep *second_step = &self->steps.contents[start_step_index + 1]; - if (second_step->symbol != WILDCARD_SYMBOL && second_step->depth != PATTERN_DONE_MARKER) { - start_step_index += 1; - } - } - - // Maintain a map that can look up patterns for a given root symbol. - for (;;) { - QueryStep *step = &self->steps.contents[start_step_index]; - step->is_pattern_start = true; - ts_query__pattern_map_insert(self, step->symbol, start_step_index, pattern_index); - if (step->symbol == WILDCARD_SYMBOL) { - self->wildcard_root_pattern_count++; - } - - // If there are alternatives or options at the root of the pattern, - // then add multiple entries to the pattern map. - if (step->alternative_index != NONE) { - start_step_index = step->alternative_index; - } else { - break; - } - } - } - - ts_query__finalize_steps(self); - return self; -} - -void ts_query_delete(TSQuery *self) { - if (self) { - array_delete(&self->steps); - array_delete(&self->pattern_map); - array_delete(&self->predicate_steps); - array_delete(&self->predicates_by_pattern); - array_delete(&self->start_bytes_by_pattern); - symbol_table_delete(&self->captures); - symbol_table_delete(&self->predicate_values); - ts_free(self->symbol_map); - ts_free(self); - } -} - -uint32_t ts_query_pattern_count(const TSQuery *self) { - return self->predicates_by_pattern.size; -} - -uint32_t ts_query_capture_count(const TSQuery *self) { - return self->captures.slices.size; -} - -uint32_t ts_query_string_count(const TSQuery *self) { - return self->predicate_values.slices.size; -} - -const char *ts_query_capture_name_for_id( - const TSQuery *self, - uint32_t index, - uint32_t *length -) { - return symbol_table_name_for_id(&self->captures, index, length); -} - -const char *ts_query_string_value_for_id( - const TSQuery *self, - uint32_t index, - uint32_t *length -) { - return symbol_table_name_for_id(&self->predicate_values, index, length); -} - -const TSQueryPredicateStep *ts_query_predicates_for_pattern( - const TSQuery *self, - uint32_t pattern_index, - uint32_t *step_count -) { - Slice slice = self->predicates_by_pattern.contents[pattern_index]; - *step_count = slice.length; - return &self->predicate_steps.contents[slice.offset]; -} - -uint32_t ts_query_start_byte_for_pattern( - const TSQuery *self, - uint32_t pattern_index -) { - return self->start_bytes_by_pattern.contents[pattern_index]; -} - -void ts_query_disable_capture( - TSQuery *self, - const char *name, - uint32_t length -) { - // Remove capture information for any pattern step that previously - // captured with the given name. - int id = symbol_table_id_for_name(&self->captures, name, length); - if (id != -1) { - for (unsigned i = 0; i < self->steps.size; i++) { - QueryStep *step = &self->steps.contents[i]; - query_step__remove_capture(step, id); - } - ts_query__finalize_steps(self); - } -} - -void ts_query_disable_pattern( - TSQuery *self, - uint32_t pattern_index -) { - // Remove the given pattern from the pattern map. Its steps will still - // be in the `steps` array, but they will never be read. - for (unsigned i = 0; i < self->pattern_map.size; i++) { - PatternEntry *pattern = &self->pattern_map.contents[i]; - if (pattern->pattern_index == pattern_index) { - array_erase(&self->pattern_map, i); - i--; - } - } -} - -/*************** - * QueryCursor - ***************/ - -TSQueryCursor *ts_query_cursor_new(void) { - TSQueryCursor *self = ts_malloc(sizeof(TSQueryCursor)); - *self = (TSQueryCursor) { - .ascending = false, - .states = array_new(), - .finished_states = array_new(), - .capture_list_pool = capture_list_pool_new(), - .start_byte = 0, - .end_byte = UINT32_MAX, - .start_point = {0, 0}, - .end_point = POINT_MAX, - }; - array_reserve(&self->states, MAX_STATE_COUNT); - array_reserve(&self->finished_states, MAX_CAPTURE_LIST_COUNT); - return self; -} - -void ts_query_cursor_delete(TSQueryCursor *self) { - array_delete(&self->states); - array_delete(&self->finished_states); - ts_tree_cursor_delete(&self->cursor); - capture_list_pool_delete(&self->capture_list_pool); - ts_free(self); -} - -void ts_query_cursor_exec( - TSQueryCursor *self, - const TSQuery *query, - TSNode node -) { - array_clear(&self->states); - array_clear(&self->finished_states); - ts_tree_cursor_reset(&self->cursor, node); - capture_list_pool_reset(&self->capture_list_pool); - self->next_state_id = 0; - self->depth = 0; - self->ascending = false; - self->query = query; -} - -void ts_query_cursor_set_byte_range( - TSQueryCursor *self, - uint32_t start_byte, - uint32_t end_byte -) { - if (end_byte == 0) { - start_byte = 0; - end_byte = UINT32_MAX; - } - self->start_byte = start_byte; - self->end_byte = end_byte; -} - -void ts_query_cursor_set_point_range( - TSQueryCursor *self, - TSPoint start_point, - TSPoint end_point -) { - if (end_point.row == 0 && end_point.column == 0) { - start_point = POINT_ZERO; - end_point = POINT_MAX; - } - self->start_point = start_point; - self->end_point = end_point; -} - -// Search through all of the in-progress states, and find the captured -// node that occurs earliest in the document. -static bool ts_query_cursor__first_in_progress_capture( - TSQueryCursor *self, - uint32_t *state_index, - uint32_t *byte_offset, - uint32_t *pattern_index -) { - bool result = false; - *state_index = UINT32_MAX; - *byte_offset = UINT32_MAX; - *pattern_index = UINT32_MAX; - for (unsigned i = 0; i < self->states.size; i++) { - const QueryState *state = &self->states.contents[i]; - const CaptureList *captures = capture_list_pool_get( - &self->capture_list_pool, - state->capture_list_id - ); - if (captures->size > 0) { - uint32_t capture_byte = ts_node_start_byte(captures->contents[0].node); - if ( - !result || - capture_byte < *byte_offset || - (capture_byte == *byte_offset && state->pattern_index < *pattern_index) - ) { - result = true; - *state_index = i; - *byte_offset = capture_byte; - *pattern_index = state->pattern_index; - } - } - } - return result; -} - -// Determine which node is first in a depth-first traversal -int ts_query_cursor__compare_nodes(TSNode left, TSNode right) { - if (left.id != right.id) { - uint32_t left_start = ts_node_start_byte(left); - uint32_t right_start = ts_node_start_byte(right); - if (left_start < right_start) return -1; - if (left_start > right_start) return 1; - uint32_t left_node_count = ts_node_end_byte(left); - uint32_t right_node_count = ts_node_end_byte(right); - if (left_node_count > right_node_count) return -1; - if (left_node_count < right_node_count) return 1; - } - return 0; -} - -// Determine if either state contains a superset of the other state's captures. -void ts_query_cursor__compare_captures( - TSQueryCursor *self, - QueryState *left_state, - QueryState *right_state, - bool *left_contains_right, - bool *right_contains_left -) { - const CaptureList *left_captures = capture_list_pool_get( - &self->capture_list_pool, - left_state->capture_list_id - ); - const CaptureList *right_captures = capture_list_pool_get( - &self->capture_list_pool, - right_state->capture_list_id - ); - *left_contains_right = true; - *right_contains_left = true; - unsigned i = 0, j = 0; - for (;;) { - if (i < left_captures->size) { - if (j < right_captures->size) { - TSQueryCapture *left = &left_captures->contents[i]; - TSQueryCapture *right = &right_captures->contents[j]; - if (left->node.id == right->node.id && left->index == right->index) { - i++; - j++; - } else { - switch (ts_query_cursor__compare_nodes(left->node, right->node)) { - case -1: - *right_contains_left = false; - i++; - break; - case 1: - *left_contains_right = false; - j++; - break; - default: - *right_contains_left = false; - *left_contains_right = false; - i++; - j++; - break; - } - } - } else { - *right_contains_left = false; - break; - } - } else { - if (j < right_captures->size) { - *left_contains_right = false; - } - break; - } - } -} - -static bool ts_query_cursor__add_state( - TSQueryCursor *self, - const PatternEntry *pattern -) { - if (self->states.size >= MAX_STATE_COUNT) { - LOG(" too many states"); - return false; - } - LOG( - " start state. pattern:%u, step:%u\n", - pattern->pattern_index, - pattern->step_index - ); - QueryStep *step = &self->query->steps.contents[pattern->step_index]; - array_push(&self->states, ((QueryState) { - .capture_list_id = NONE, - .step_index = pattern->step_index, - .pattern_index = pattern->pattern_index, - .start_depth = self->depth - step->depth, - .consumed_capture_count = 0, - .seeking_immediate_match = false, - })); - return true; -} - -// Duplicate the given state and insert the newly-created state immediately after -// the given state in the `states` array. -static QueryState *ts_query__cursor_copy_state( - TSQueryCursor *self, - const QueryState *state -) { - if (self->states.size >= MAX_STATE_COUNT) { - LOG(" too many states"); - return NULL; - } - - // If the state has captures, copy its capture list. - QueryState copy = *state; - copy.capture_list_id = state->capture_list_id; - if (state->capture_list_id != NONE) { - copy.capture_list_id = capture_list_pool_acquire(&self->capture_list_pool); - if (copy.capture_list_id == NONE) { - LOG(" too many capture lists"); - return NULL; - } - const CaptureList *old_captures = capture_list_pool_get( - &self->capture_list_pool, - state->capture_list_id - ); - CaptureList *new_captures = capture_list_pool_get_mut( - &self->capture_list_pool, - copy.capture_list_id - ); - array_push_all(new_captures, old_captures); - } - - uint32_t index = (state - self->states.contents) + 1; - array_insert(&self->states, index, copy); - return &self->states.contents[index]; -} - -// Walk the tree, processing patterns until at least one pattern finishes, -// If one or more patterns finish, return `true` and store their states in the -// `finished_states` array. Multiple patterns can finish on the same node. If -// there are no more matches, return `false`. -static inline bool ts_query_cursor__advance(TSQueryCursor *self) { - do { - if (self->ascending) { - LOG("leave node. type:%s\n", ts_node_type(ts_tree_cursor_current_node(&self->cursor))); - - // Leave this node by stepping to its next sibling or to its parent. - bool did_move = true; - if (ts_tree_cursor_goto_next_sibling(&self->cursor)) { - self->ascending = false; - } else if (ts_tree_cursor_goto_parent(&self->cursor)) { - self->depth--; - } else { - did_move = false; - } - - // After leaving a node, remove any states that cannot make further progress. - uint32_t deleted_count = 0; - for (unsigned i = 0, n = self->states.size; i < n; i++) { - QueryState *state = &self->states.contents[i]; - QueryStep *step = &self->query->steps.contents[state->step_index]; - - // If a state completed its pattern inside of this node, but was deferred from finishing - // in order to search for longer matches, mark it as finished. - if (step->depth == PATTERN_DONE_MARKER) { - if (state->start_depth > self->depth || !did_move) { - LOG(" finish pattern %u\n", state->pattern_index); - state->id = self->next_state_id++; - array_push(&self->finished_states, *state); - deleted_count++; - continue; - } - } - - // If a state needed to match something within this node, then remove that state - // as it has failed to match. - else if ((uint32_t)state->start_depth + (uint32_t)step->depth > self->depth) { - LOG( - " failed to match. pattern:%u, step:%u\n", - state->pattern_index, - state->step_index - ); - capture_list_pool_release( - &self->capture_list_pool, - state->capture_list_id - ); - deleted_count++; - continue; - } - - if (deleted_count > 0) { - self->states.contents[i - deleted_count] = *state; - } - } - self->states.size -= deleted_count; - - if (!did_move) { - return self->finished_states.size > 0; - } - } else { - // If this node is before the selected range, then avoid descending into it. - TSNode node = ts_tree_cursor_current_node(&self->cursor); - if ( - ts_node_end_byte(node) <= self->start_byte || - point_lte(ts_node_end_point(node), self->start_point) - ) { - if (!ts_tree_cursor_goto_next_sibling(&self->cursor)) { - self->ascending = true; - } - continue; - } - - // If this node is after the selected range, then stop walking. - if ( - self->end_byte <= ts_node_start_byte(node) || - point_lte(self->end_point, ts_node_start_point(node)) - ) return false; - - // Get the properties of the current node. - TSSymbol symbol = ts_node_symbol(node); - bool is_named = ts_node_is_named(node); - if (symbol != ts_builtin_sym_error && self->query->symbol_map) { - symbol = self->query->symbol_map[symbol]; - } - bool can_have_later_siblings; - bool can_have_later_siblings_with_this_field; - TSFieldId field_id = ts_tree_cursor_current_status( - &self->cursor, - &can_have_later_siblings, - &can_have_later_siblings_with_this_field - ); - LOG( - "enter node. type:%s, field:%s, row:%u state_count:%u, finished_state_count:%u\n", - ts_node_type(node), - ts_language_field_name_for_id(self->query->language, field_id), - ts_node_start_point(node).row, - self->states.size, - self->finished_states.size - ); - - // Add new states for any patterns whose root node is a wildcard. - for (unsigned i = 0; i < self->query->wildcard_root_pattern_count; i++) { - PatternEntry *pattern = &self->query->pattern_map.contents[i]; - QueryStep *step = &self->query->steps.contents[pattern->step_index]; - - // If this node matches the first step of the pattern, then add a new - // state at the start of this pattern. - if (step->field && field_id != step->field) continue; - if (!ts_query_cursor__add_state(self, pattern)) break; - } - - // Add new states for any patterns whose root node matches this node. - unsigned i; - if (ts_query__pattern_map_search(self->query, symbol, &i)) { - PatternEntry *pattern = &self->query->pattern_map.contents[i]; - QueryStep *step = &self->query->steps.contents[pattern->step_index]; - do { - // If this node matches the first step of the pattern, then add a new - // state at the start of this pattern. - if (step->field && field_id != step->field) continue; - if (!ts_query_cursor__add_state(self, pattern)) break; - - // Advance to the next pattern whose root node matches this node. - i++; - if (i == self->query->pattern_map.size) break; - pattern = &self->query->pattern_map.contents[i]; - step = &self->query->steps.contents[pattern->step_index]; - } while (step->symbol == symbol); - } - - // Update all of the in-progress states with current node. - for (unsigned i = 0, copy_count = 0; i < self->states.size; i += 1 + copy_count) { - QueryState *state = &self->states.contents[i]; - QueryStep *step = &self->query->steps.contents[state->step_index]; - state->has_in_progress_alternatives = false; - copy_count = 0; - - // Check that the node matches all of the criteria for the next - // step of the pattern. - if ((uint32_t)state->start_depth + (uint32_t)step->depth != self->depth) continue; - - // Determine if this node matches this step of the pattern, and also - // if this node can have later siblings that match this step of the - // pattern. - bool node_does_match = - step->symbol == symbol || - step->symbol == WILDCARD_SYMBOL || - (step->symbol == NAMED_WILDCARD_SYMBOL && is_named); - bool later_sibling_can_match = can_have_later_siblings; - if ((step->is_immediate && is_named) || state->seeking_immediate_match) { - later_sibling_can_match = false; - } - if (step->is_last_child && can_have_later_siblings) { - node_does_match = false; - } - if (step->field) { - if (step->field == field_id) { - if (!can_have_later_siblings_with_this_field) { - later_sibling_can_match = false; - } - } else { - node_does_match = false; - } - } - - // Remove states immediately if it is ever clear that they cannot match. - if (!node_does_match) { - if (!later_sibling_can_match) { - LOG( - " discard state. pattern:%u, step:%u\n", - state->pattern_index, - state->step_index - ); - capture_list_pool_release( - &self->capture_list_pool, - state->capture_list_id - ); - array_erase(&self->states, i); - i--; - } - continue; - } - - // Some patterns can match their root node in multiple ways, capturing different - // children. If this pattern step could match later children within the same - // parent, then this query state cannot simply be updated in place. It must be - // split into two states: one that matches this node, and one which skips over - // this node, to preserve the possibility of matching later siblings. - if ( - later_sibling_can_match && - !step->is_pattern_start && - step->contains_captures - ) { - if (ts_query__cursor_copy_state(self, state)) { - LOG( - " split state for capture. pattern:%u, step:%u\n", - state->pattern_index, - state->step_index - ); - copy_count++; - } - } - - // If the current node is captured in this pattern, add it to the capture list. - // For the first capture in a pattern, lazily acquire a capture list. - if (step->capture_ids[0] != NONE) { - if (state->capture_list_id == NONE) { - state->capture_list_id = capture_list_pool_acquire(&self->capture_list_pool); - - // If there are no capture lists left in the pool, then terminate whichever - // state has captured the earliest node in the document, and steal its - // capture list. - if (state->capture_list_id == NONE) { - uint32_t state_index, byte_offset, pattern_index; - if (ts_query_cursor__first_in_progress_capture( - self, - &state_index, - &byte_offset, - &pattern_index - )) { - LOG( - " abandon state. index:%u, pattern:%u, offset:%u.\n", - state_index, pattern_index, byte_offset - ); - state->capture_list_id = self->states.contents[state_index].capture_list_id; - array_erase(&self->states, state_index); - if (state_index < i) { - i--; - state--; - } - } else { - LOG(" too many finished states.\n"); - array_erase(&self->states, i); - i--; - continue; - } - } - } - - CaptureList *capture_list = capture_list_pool_get_mut( - &self->capture_list_pool, - state->capture_list_id - ); - for (unsigned j = 0; j < MAX_STEP_CAPTURE_COUNT; j++) { - uint16_t capture_id = step->capture_ids[j]; - if (step->capture_ids[j] == NONE) break; - array_push(capture_list, ((TSQueryCapture) { node, capture_id })); - LOG( - " capture node. pattern:%u, capture_id:%u, capture_count:%u\n", - state->pattern_index, - capture_id, - capture_list->size - ); - } - } - - // Advance this state to the next step of its pattern. - state->step_index++; - state->seeking_immediate_match = false; - LOG( - " advance state. pattern:%u, step:%u\n", - state->pattern_index, - state->step_index - ); - - // If this state's next step has an 'alternative' step (the step is either optional, - // or is the end of a repetition), then copy the state in order to pursue both - // alternatives. The alternative step itself may have an alternative, so this is - // an interative process. - unsigned end_index = i + 1; - for (unsigned j = i; j < end_index; j++) { - QueryState *state = &self->states.contents[j]; - QueryStep *next_step = &self->query->steps.contents[state->step_index]; - if (next_step->alternative_index != NONE) { - if (next_step->is_dead_end) { - state->step_index = next_step->alternative_index; - j--; - continue; - } - - QueryState *copy = ts_query__cursor_copy_state(self, state); - if (next_step->is_pass_through) { - state->step_index++; - j--; - } - if (copy) { - copy_count++; - end_index++; - copy->step_index = next_step->alternative_index; - if (next_step->alternative_is_immediate) { - copy->seeking_immediate_match = true; - } - LOG( - " split state for branch. pattern:%u, step:%u, step:%u, immediate:%d\n", - copy->pattern_index, - state->step_index, - copy->step_index, - copy->seeking_immediate_match - ); - } - } - } - } - - for (unsigned i = 0; i < self->states.size; i++) { - QueryState *state = &self->states.contents[i]; - bool did_remove = false; - - // Enfore the longest-match criteria. When a query pattern contains optional or - // repeated nodes, this is necesssary to avoid multiple redundant states, where - // one state has a strict subset of another state's captures. - for (unsigned j = i + 1; j < self->states.size; j++) { - QueryState *other_state = &self->states.contents[j]; - if ( - state->pattern_index == other_state->pattern_index && - state->start_depth == other_state->start_depth - ) { - bool left_contains_right, right_contains_left; - ts_query_cursor__compare_captures( - self, - state, - other_state, - &left_contains_right, - &right_contains_left - ); - if (left_contains_right) { - if (state->step_index == other_state->step_index) { - LOG( - " drop shorter state. pattern: %u, step_index: %u\n", - state->pattern_index, - state->step_index - ); - capture_list_pool_release(&self->capture_list_pool, other_state->capture_list_id); - array_erase(&self->states, j); - j--; - continue; - } - other_state->has_in_progress_alternatives = true; - } - if (right_contains_left) { - if (state->step_index == other_state->step_index) { - LOG( - " drop shorter state. pattern: %u, step_index: %u\n", - state->pattern_index, - state->step_index - ); - capture_list_pool_release(&self->capture_list_pool, state->capture_list_id); - array_erase(&self->states, i); - did_remove = true; - break; - } - state->has_in_progress_alternatives = true; - } - } - } - - // If there the state is at the end of its pattern, remove it from the list - // of in-progress states and add it to the list of finished states. - if (!did_remove) { - QueryStep *next_step = &self->query->steps.contents[state->step_index]; - if (next_step->depth == PATTERN_DONE_MARKER) { - if (state->has_in_progress_alternatives) { - LOG(" defer finishing pattern %u\n", state->pattern_index); - } else { - LOG(" finish pattern %u\n", state->pattern_index); - state->id = self->next_state_id++; - array_push(&self->finished_states, *state); - array_erase(&self->states, state - self->states.contents); - i--; - } - } - } - } - - // Continue descending if possible. - if (ts_tree_cursor_goto_first_child(&self->cursor)) { - self->depth++; - } else { - self->ascending = true; - } - } - } while (self->finished_states.size == 0); - - return true; -} - -bool ts_query_cursor_next_match( - TSQueryCursor *self, - TSQueryMatch *match -) { - if (self->finished_states.size == 0) { - if (!ts_query_cursor__advance(self)) { - return false; - } - } - - QueryState *state = &self->finished_states.contents[0]; - match->id = state->id; - match->pattern_index = state->pattern_index; - const CaptureList *captures = capture_list_pool_get( - &self->capture_list_pool, - state->capture_list_id - ); - match->captures = captures->contents; - match->capture_count = captures->size; - capture_list_pool_release(&self->capture_list_pool, state->capture_list_id); - array_erase(&self->finished_states, 0); - return true; -} - -void ts_query_cursor_remove_match( - TSQueryCursor *self, - uint32_t match_id -) { - for (unsigned i = 0; i < self->finished_states.size; i++) { - const QueryState *state = &self->finished_states.contents[i]; - if (state->id == match_id) { - capture_list_pool_release( - &self->capture_list_pool, - state->capture_list_id - ); - array_erase(&self->finished_states, i); - return; - } - } -} - -bool ts_query_cursor_next_capture( - TSQueryCursor *self, - TSQueryMatch *match, - uint32_t *capture_index -) { - for (;;) { - // The goal here is to return captures in order, even though they may not - // be discovered in order, because patterns can overlap. If there are any - // finished patterns, then try to find one that contains a capture that - // is *definitely* before any capture in an *unfinished* pattern. - if (self->finished_states.size > 0) { - // First, identify the position of the earliest capture in an unfinished - // match. For a finished capture to be returned, it must be *before* - // this position. - uint32_t first_unfinished_capture_byte; - uint32_t first_unfinished_pattern_index; - uint32_t first_unfinished_state_index; - ts_query_cursor__first_in_progress_capture( - self, - &first_unfinished_state_index, - &first_unfinished_capture_byte, - &first_unfinished_pattern_index - ); - - // Find the earliest capture in a finished match. - int first_finished_state_index = -1; - uint32_t first_finished_capture_byte = first_unfinished_capture_byte; - uint32_t first_finished_pattern_index = first_unfinished_pattern_index; - for (unsigned i = 0; i < self->finished_states.size; i++) { - const QueryState *state = &self->finished_states.contents[i]; - const CaptureList *captures = capture_list_pool_get( - &self->capture_list_pool, - state->capture_list_id - ); - if (captures->size > state->consumed_capture_count) { - uint32_t capture_byte = ts_node_start_byte( - captures->contents[state->consumed_capture_count].node - ); - if ( - capture_byte < first_finished_capture_byte || - ( - capture_byte == first_finished_capture_byte && - state->pattern_index < first_finished_pattern_index - ) - ) { - first_finished_state_index = i; - first_finished_capture_byte = capture_byte; - first_finished_pattern_index = state->pattern_index; - } - } else { - capture_list_pool_release( - &self->capture_list_pool, - state->capture_list_id - ); - array_erase(&self->finished_states, i); - i--; - } - } - - // If there is finished capture that is clearly before any unfinished - // capture, then return its match, and its capture index. Internally - // record the fact that the capture has been 'consumed'. - if (first_finished_state_index != -1) { - QueryState *state = &self->finished_states.contents[ - first_finished_state_index - ]; - match->id = state->id; - match->pattern_index = state->pattern_index; - const CaptureList *captures = capture_list_pool_get( - &self->capture_list_pool, - state->capture_list_id - ); - match->captures = captures->contents; - match->capture_count = captures->size; - *capture_index = state->consumed_capture_count; - state->consumed_capture_count++; - return true; - } - - if (capture_list_pool_is_empty(&self->capture_list_pool)) { - LOG( - " abandon state. index:%u, pattern:%u, offset:%u.\n", - first_unfinished_state_index, - first_unfinished_pattern_index, - first_unfinished_capture_byte - ); - capture_list_pool_release( - &self->capture_list_pool, - self->states.contents[first_unfinished_state_index].capture_list_id - ); - array_erase(&self->states, first_unfinished_state_index); - } - } - - // If there are no finished matches that are ready to be returned, then - // continue finding more matches. - if (!ts_query_cursor__advance(self)) return false; - } -} - -#undef LOG diff --git a/src/tree_sitter/reduce_action.h b/src/tree_sitter/reduce_action.h deleted file mode 100644 index 72aff08d73..0000000000 --- a/src/tree_sitter/reduce_action.h +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef TREE_SITTER_REDUCE_ACTION_H_ -#define TREE_SITTER_REDUCE_ACTION_H_ - -#ifdef __cplusplus -extern "C" { -#endif - -#include "./array.h" -#include "tree_sitter/api.h" - -typedef struct { - uint32_t count; - TSSymbol symbol; - int dynamic_precedence; - unsigned short production_id; -} ReduceAction; - -typedef Array(ReduceAction) ReduceActionSet; - -static inline void ts_reduce_action_set_add(ReduceActionSet *self, - ReduceAction new_action) { - for (uint32_t i = 0; i < self->size; i++) { - ReduceAction action = self->contents[i]; - if (action.symbol == new_action.symbol && action.count == new_action.count) - return; - } - array_push(self, new_action); -} - -#ifdef __cplusplus -} -#endif - -#endif // TREE_SITTER_REDUCE_ACTION_H_ diff --git a/src/tree_sitter/reusable_node.h b/src/tree_sitter/reusable_node.h deleted file mode 100644 index 9cba951909..0000000000 --- a/src/tree_sitter/reusable_node.h +++ /dev/null @@ -1,88 +0,0 @@ -#include "./subtree.h" - -typedef struct { - Subtree tree; - uint32_t child_index; - uint32_t byte_offset; -} StackEntry; - -typedef struct { - Array(StackEntry) stack; - Subtree last_external_token; -} ReusableNode; - -static inline ReusableNode reusable_node_new(void) { - return (ReusableNode) {array_new(), NULL_SUBTREE}; -} - -static inline void reusable_node_clear(ReusableNode *self) { - array_clear(&self->stack); - self->last_external_token = NULL_SUBTREE; -} - -static inline void reusable_node_reset(ReusableNode *self, Subtree tree) { - reusable_node_clear(self); - array_push(&self->stack, ((StackEntry) { - .tree = tree, - .child_index = 0, - .byte_offset = 0, - })); -} - -static inline Subtree reusable_node_tree(ReusableNode *self) { - return self->stack.size > 0 - ? self->stack.contents[self->stack.size - 1].tree - : NULL_SUBTREE; -} - -static inline uint32_t reusable_node_byte_offset(ReusableNode *self) { - return self->stack.size > 0 - ? self->stack.contents[self->stack.size - 1].byte_offset - : UINT32_MAX; -} - -static inline void reusable_node_delete(ReusableNode *self) { - array_delete(&self->stack); -} - -static inline void reusable_node_advance(ReusableNode *self) { - StackEntry last_entry = *array_back(&self->stack); - uint32_t byte_offset = last_entry.byte_offset + ts_subtree_total_bytes(last_entry.tree); - if (ts_subtree_has_external_tokens(last_entry.tree)) { - self->last_external_token = ts_subtree_last_external_token(last_entry.tree); - } - - Subtree tree; - uint32_t next_index; - do { - StackEntry popped_entry = array_pop(&self->stack); - next_index = popped_entry.child_index + 1; - if (self->stack.size == 0) return; - tree = array_back(&self->stack)->tree; - } while (ts_subtree_child_count(tree) <= next_index); - - array_push(&self->stack, ((StackEntry) { - .tree = tree.ptr->children[next_index], - .child_index = next_index, - .byte_offset = byte_offset, - })); -} - -static inline bool reusable_node_descend(ReusableNode *self) { - StackEntry last_entry = *array_back(&self->stack); - if (ts_subtree_child_count(last_entry.tree) > 0) { - array_push(&self->stack, ((StackEntry) { - .tree = last_entry.tree.ptr->children[0], - .child_index = 0, - .byte_offset = last_entry.byte_offset, - })); - return true; - } else { - return false; - } -} - -static inline void reusable_node_advance_past_leaf(ReusableNode *self) { - while (reusable_node_descend(self)) {} - reusable_node_advance(self); -} diff --git a/src/tree_sitter/stack.c b/src/tree_sitter/stack.c deleted file mode 100644 index 6ceee2577f..0000000000 --- a/src/tree_sitter/stack.c +++ /dev/null @@ -1,848 +0,0 @@ -#include "./alloc.h" -#include "./language.h" -#include "./subtree.h" -#include "./array.h" -#include "./stack.h" -#include "./length.h" -#include <assert.h> -#include <stdio.h> - -#define MAX_LINK_COUNT 8 -#define MAX_NODE_POOL_SIZE 50 -#define MAX_ITERATOR_COUNT 64 - -#if defined _WIN32 && !defined __GNUC__ -#define inline __forceinline -#else -#define inline static inline __attribute__((always_inline)) -#endif - -typedef struct StackNode StackNode; - -typedef struct { - StackNode *node; - Subtree subtree; - bool is_pending; -} StackLink; - -struct StackNode { - TSStateId state; - Length position; - StackLink links[MAX_LINK_COUNT]; - short unsigned int link_count; - uint32_t ref_count; - unsigned error_cost; - unsigned node_count; - int dynamic_precedence; -}; - -typedef struct { - StackNode *node; - SubtreeArray subtrees; - uint32_t subtree_count; - bool is_pending; -} StackIterator; - -typedef struct { - void *payload; - StackIterateCallback callback; -} StackIterateSession; - -typedef Array(StackNode *) StackNodeArray; - -typedef enum { - StackStatusActive, - StackStatusPaused, - StackStatusHalted, -} StackStatus; - -typedef struct { - StackNode *node; - Subtree last_external_token; - StackSummary *summary; - unsigned node_count_at_last_error; - TSSymbol lookahead_when_paused; - StackStatus status; -} StackHead; - -struct Stack { - Array(StackHead) heads; - StackSliceArray slices; - Array(StackIterator) iterators; - StackNodeArray node_pool; - StackNode *base_node; - SubtreePool *subtree_pool; -}; - -typedef unsigned StackAction; -enum { - StackActionNone, - StackActionStop = 1, - StackActionPop = 2, -}; - -typedef StackAction (*StackCallback)(void *, const StackIterator *); - -static void stack_node_retain(StackNode *self) { - if (!self) - return; - assert(self->ref_count > 0); - self->ref_count++; - assert(self->ref_count != 0); -} - -static void stack_node_release(StackNode *self, StackNodeArray *pool, SubtreePool *subtree_pool) { -recur: - assert(self->ref_count != 0); - self->ref_count--; - if (self->ref_count > 0) return; - - StackNode *first_predecessor = NULL; - if (self->link_count > 0) { - for (unsigned i = self->link_count - 1; i > 0; i--) { - StackLink link = self->links[i]; - if (link.subtree.ptr) ts_subtree_release(subtree_pool, link.subtree); - stack_node_release(link.node, pool, subtree_pool); - } - StackLink link = self->links[0]; - if (link.subtree.ptr) ts_subtree_release(subtree_pool, link.subtree); - first_predecessor = self->links[0].node; - } - - if (pool->size < MAX_NODE_POOL_SIZE) { - array_push(pool, self); - } else { - ts_free(self); - } - - if (first_predecessor) { - self = first_predecessor; - goto recur; - } -} - -static StackNode *stack_node_new(StackNode *previous_node, Subtree subtree, - bool is_pending, TSStateId state, StackNodeArray *pool) { - StackNode *node = pool->size > 0 ? - array_pop(pool) : - ts_malloc(sizeof(StackNode)); - *node = (StackNode){.ref_count = 1, .link_count = 0, .state = state}; - - if (previous_node) { - node->link_count = 1; - node->links[0] = (StackLink){ - .node = previous_node, - .subtree = subtree, - .is_pending = is_pending, - }; - - node->position = previous_node->position; - node->error_cost = previous_node->error_cost; - node->dynamic_precedence = previous_node->dynamic_precedence; - node->node_count = previous_node->node_count; - - if (subtree.ptr) { - node->error_cost += ts_subtree_error_cost(subtree); - node->position = length_add(node->position, ts_subtree_total_size(subtree)); - node->node_count += ts_subtree_node_count(subtree); - node->dynamic_precedence += ts_subtree_dynamic_precedence(subtree); - } - } else { - node->position = length_zero(); - node->error_cost = 0; - } - - return node; -} - -static bool stack__subtree_is_equivalent(Subtree left, Subtree right) { - return - left.ptr == right.ptr || - (left.ptr && right.ptr && - ts_subtree_symbol(left) == ts_subtree_symbol(right) && - ((ts_subtree_error_cost(left) > 0 && ts_subtree_error_cost(right) > 0) || - (ts_subtree_padding(left).bytes == ts_subtree_padding(right).bytes && - ts_subtree_size(left).bytes == ts_subtree_size(right).bytes && - ts_subtree_child_count(left) == ts_subtree_child_count(right) && - ts_subtree_extra(left) == ts_subtree_extra(right) && - ts_subtree_external_scanner_state_eq(left, right)))); -} - -static void stack_node_add_link(StackNode *self, StackLink link, SubtreePool *subtree_pool) { - if (link.node == self) return; - - for (int i = 0; i < self->link_count; i++) { - StackLink *existing_link = &self->links[i]; - if (stack__subtree_is_equivalent(existing_link->subtree, link.subtree)) { - // In general, we preserve ambiguities until they are removed from the stack - // during a pop operation where multiple paths lead to the same node. But in - // the special case where two links directly connect the same pair of nodes, - // we can safely remove the ambiguity ahead of time without changing behavior. - if (existing_link->node == link.node) { - if ( - ts_subtree_dynamic_precedence(link.subtree) > - ts_subtree_dynamic_precedence(existing_link->subtree) - ) { - ts_subtree_retain(link.subtree); - ts_subtree_release(subtree_pool, existing_link->subtree); - existing_link->subtree = link.subtree; - self->dynamic_precedence = - link.node->dynamic_precedence + ts_subtree_dynamic_precedence(link.subtree); - } - return; - } - - // If the previous nodes are mergeable, merge them recursively. - if (existing_link->node->state == link.node->state && - existing_link->node->position.bytes == link.node->position.bytes) { - for (int j = 0; j < link.node->link_count; j++) { - stack_node_add_link(existing_link->node, link.node->links[j], subtree_pool); - } - int32_t dynamic_precedence = link.node->dynamic_precedence; - if (link.subtree.ptr) { - dynamic_precedence += ts_subtree_dynamic_precedence(link.subtree); - } - if (dynamic_precedence > self->dynamic_precedence) { - self->dynamic_precedence = dynamic_precedence; - } - return; - } - } - } - - if (self->link_count == MAX_LINK_COUNT) return; - - stack_node_retain(link.node); - unsigned node_count = link.node->node_count; - int dynamic_precedence = link.node->dynamic_precedence; - self->links[self->link_count++] = link; - - if (link.subtree.ptr) { - ts_subtree_retain(link.subtree); - node_count += ts_subtree_node_count(link.subtree); - dynamic_precedence += ts_subtree_dynamic_precedence(link.subtree); - } - - if (node_count > self->node_count) self->node_count = node_count; - if (dynamic_precedence > self->dynamic_precedence) self->dynamic_precedence = dynamic_precedence; -} - -static void stack_head_delete(StackHead *self, StackNodeArray *pool, SubtreePool *subtree_pool) { - if (self->node) { - if (self->last_external_token.ptr) { - ts_subtree_release(subtree_pool, self->last_external_token); - } - if (self->summary) { - array_delete(self->summary); - ts_free(self->summary); - } - stack_node_release(self->node, pool, subtree_pool); - } -} - -static StackVersion ts_stack__add_version(Stack *self, StackVersion original_version, - StackNode *node) { - StackHead head = { - .node = node, - .node_count_at_last_error = self->heads.contents[original_version].node_count_at_last_error, - .last_external_token = self->heads.contents[original_version].last_external_token, - .status = StackStatusActive, - .lookahead_when_paused = 0, - }; - array_push(&self->heads, head); - stack_node_retain(node); - if (head.last_external_token.ptr) ts_subtree_retain(head.last_external_token); - return (StackVersion)(self->heads.size - 1); -} - -static void ts_stack__add_slice(Stack *self, StackVersion original_version, - StackNode *node, SubtreeArray *subtrees) { - for (uint32_t i = self->slices.size - 1; i + 1 > 0; i--) { - StackVersion version = self->slices.contents[i].version; - if (self->heads.contents[version].node == node) { - StackSlice slice = {*subtrees, version}; - array_insert(&self->slices, i + 1, slice); - return; - } - } - - StackVersion version = ts_stack__add_version(self, original_version, node); - StackSlice slice = { *subtrees, version }; - array_push(&self->slices, slice); -} - -inline StackSliceArray stack__iter(Stack *self, StackVersion version, - StackCallback callback, void *payload, - int goal_subtree_count) { - array_clear(&self->slices); - array_clear(&self->iterators); - - StackHead *head = array_get(&self->heads, version); - StackIterator iterator = { - .node = head->node, - .subtrees = array_new(), - .subtree_count = 0, - .is_pending = true, - }; - - bool include_subtrees = false; - if (goal_subtree_count >= 0) { - include_subtrees = true; - array_reserve(&iterator.subtrees, goal_subtree_count); - } - - array_push(&self->iterators, iterator); - - while (self->iterators.size > 0) { - for (uint32_t i = 0, size = self->iterators.size; i < size; i++) { - StackIterator *iterator = &self->iterators.contents[i]; - StackNode *node = iterator->node; - - StackAction action = callback(payload, iterator); - bool should_pop = action & StackActionPop; - bool should_stop = action & StackActionStop || node->link_count == 0; - - if (should_pop) { - SubtreeArray subtrees = iterator->subtrees; - if (!should_stop) - ts_subtree_array_copy(subtrees, &subtrees); - ts_subtree_array_reverse(&subtrees); - ts_stack__add_slice( - self, - version, - node, - &subtrees - ); - } - - if (should_stop) { - if (!should_pop) - ts_subtree_array_delete(self->subtree_pool, &iterator->subtrees); - array_erase(&self->iterators, i); - i--, size--; - continue; - } - - for (uint32_t j = 1; j <= node->link_count; j++) { - StackIterator *next_iterator; - StackLink link; - if (j == node->link_count) { - link = node->links[0]; - next_iterator = &self->iterators.contents[i]; - } else { - if (self->iterators.size >= MAX_ITERATOR_COUNT) continue; - link = node->links[j]; - StackIterator current_iterator = self->iterators.contents[i]; - array_push(&self->iterators, current_iterator); - next_iterator = array_back(&self->iterators); - ts_subtree_array_copy(next_iterator->subtrees, &next_iterator->subtrees); - } - - next_iterator->node = link.node; - if (link.subtree.ptr) { - if (include_subtrees) { - array_push(&next_iterator->subtrees, link.subtree); - ts_subtree_retain(link.subtree); - } - - if (!ts_subtree_extra(link.subtree)) { - next_iterator->subtree_count++; - if (!link.is_pending) { - next_iterator->is_pending = false; - } - } - } else { - next_iterator->subtree_count++; - next_iterator->is_pending = false; - } - } - } - } - - return self->slices; -} - -Stack *ts_stack_new(SubtreePool *subtree_pool) { - Stack *self = ts_calloc(1, sizeof(Stack)); - - array_init(&self->heads); - array_init(&self->slices); - array_init(&self->iterators); - array_init(&self->node_pool); - array_reserve(&self->heads, 4); - array_reserve(&self->slices, 4); - array_reserve(&self->iterators, 4); - array_reserve(&self->node_pool, MAX_NODE_POOL_SIZE); - - self->subtree_pool = subtree_pool; - self->base_node = stack_node_new(NULL, NULL_SUBTREE, false, 1, &self->node_pool); - ts_stack_clear(self); - - return self; -} - -void ts_stack_delete(Stack *self) { - if (self->slices.contents) - array_delete(&self->slices); - if (self->iterators.contents) - array_delete(&self->iterators); - stack_node_release(self->base_node, &self->node_pool, self->subtree_pool); - for (uint32_t i = 0; i < self->heads.size; i++) { - stack_head_delete(&self->heads.contents[i], &self->node_pool, self->subtree_pool); - } - array_clear(&self->heads); - if (self->node_pool.contents) { - for (uint32_t i = 0; i < self->node_pool.size; i++) - ts_free(self->node_pool.contents[i]); - array_delete(&self->node_pool); - } - array_delete(&self->heads); - ts_free(self); -} - -uint32_t ts_stack_version_count(const Stack *self) { - return self->heads.size; -} - -TSStateId ts_stack_state(const Stack *self, StackVersion version) { - return array_get(&self->heads, version)->node->state; -} - -Length ts_stack_position(const Stack *self, StackVersion version) { - return array_get(&self->heads, version)->node->position; -} - -Subtree ts_stack_last_external_token(const Stack *self, StackVersion version) { - return array_get(&self->heads, version)->last_external_token; -} - -void ts_stack_set_last_external_token(Stack *self, StackVersion version, Subtree token) { - StackHead *head = array_get(&self->heads, version); - if (token.ptr) ts_subtree_retain(token); - if (head->last_external_token.ptr) ts_subtree_release(self->subtree_pool, head->last_external_token); - head->last_external_token = token; -} - -unsigned ts_stack_error_cost(const Stack *self, StackVersion version) { - StackHead *head = array_get(&self->heads, version); - unsigned result = head->node->error_cost; - if ( - head->status == StackStatusPaused || - (head->node->state == ERROR_STATE && !head->node->links[0].subtree.ptr)) { - result += ERROR_COST_PER_RECOVERY; - } - return result; -} - -unsigned ts_stack_node_count_since_error(const Stack *self, StackVersion version) { - StackHead *head = array_get(&self->heads, version); - if (head->node->node_count < head->node_count_at_last_error) { - head->node_count_at_last_error = head->node->node_count; - } - return head->node->node_count - head->node_count_at_last_error; -} - -void ts_stack_push(Stack *self, StackVersion version, Subtree subtree, - bool pending, TSStateId state) { - StackHead *head = array_get(&self->heads, version); - StackNode *new_node = stack_node_new(head->node, subtree, pending, state, &self->node_pool); - if (!subtree.ptr) head->node_count_at_last_error = new_node->node_count; - head->node = new_node; -} - -inline StackAction iterate_callback(void *payload, const StackIterator *iterator) { - StackIterateSession *session = payload; - session->callback( - session->payload, - iterator->node->state, - iterator->subtree_count - ); - return StackActionNone; -} - -void ts_stack_iterate(Stack *self, StackVersion version, - StackIterateCallback callback, void *payload) { - StackIterateSession session = {payload, callback}; - stack__iter(self, version, iterate_callback, &session, -1); -} - -inline StackAction pop_count_callback(void *payload, const StackIterator *iterator) { - unsigned *goal_subtree_count = payload; - if (iterator->subtree_count == *goal_subtree_count) { - return StackActionPop | StackActionStop; - } else { - return StackActionNone; - } -} - -StackSliceArray ts_stack_pop_count(Stack *self, StackVersion version, uint32_t count) { - return stack__iter(self, version, pop_count_callback, &count, count); -} - -inline StackAction pop_pending_callback(void *payload, const StackIterator *iterator) { - (void)payload; - if (iterator->subtree_count >= 1) { - if (iterator->is_pending) { - return StackActionPop | StackActionStop; - } else { - return StackActionStop; - } - } else { - return StackActionNone; - } -} - -StackSliceArray ts_stack_pop_pending(Stack *self, StackVersion version) { - StackSliceArray pop = stack__iter(self, version, pop_pending_callback, NULL, 0); - if (pop.size > 0) { - ts_stack_renumber_version(self, pop.contents[0].version, version); - pop.contents[0].version = version; - } - return pop; -} - -inline StackAction pop_error_callback(void *payload, const StackIterator *iterator) { - if (iterator->subtrees.size > 0) { - bool *found_error = payload; - if (!*found_error && ts_subtree_is_error(iterator->subtrees.contents[0])) { - *found_error = true; - return StackActionPop | StackActionStop; - } else { - return StackActionStop; - } - } else { - return StackActionNone; - } -} - -SubtreeArray ts_stack_pop_error(Stack *self, StackVersion version) { - StackNode *node = array_get(&self->heads, version)->node; - for (unsigned i = 0; i < node->link_count; i++) { - if (node->links[i].subtree.ptr && ts_subtree_is_error(node->links[i].subtree)) { - bool found_error = false; - StackSliceArray pop = stack__iter(self, version, pop_error_callback, &found_error, 1); - if (pop.size > 0) { - assert(pop.size == 1); - ts_stack_renumber_version(self, pop.contents[0].version, version); - return pop.contents[0].subtrees; - } - break; - } - } - return (SubtreeArray){.size = 0}; -} - -inline StackAction pop_all_callback(void *payload, const StackIterator *iterator) { - (void)payload; - return iterator->node->link_count == 0 ? StackActionPop : StackActionNone; -} - -StackSliceArray ts_stack_pop_all(Stack *self, StackVersion version) { - return stack__iter(self, version, pop_all_callback, NULL, 0); -} - -typedef struct { - StackSummary *summary; - unsigned max_depth; -} SummarizeStackSession; - -inline StackAction summarize_stack_callback(void *payload, const StackIterator *iterator) { - SummarizeStackSession *session = payload; - TSStateId state = iterator->node->state; - unsigned depth = iterator->subtree_count; - if (depth > session->max_depth) return StackActionStop; - for (unsigned i = session->summary->size - 1; i + 1 > 0; i--) { - StackSummaryEntry entry = session->summary->contents[i]; - if (entry.depth < depth) break; - if (entry.depth == depth && entry.state == state) return StackActionNone; - } - array_push(session->summary, ((StackSummaryEntry){ - .position = iterator->node->position, - .depth = depth, - .state = state, - })); - return StackActionNone; -} - -void ts_stack_record_summary(Stack *self, StackVersion version, unsigned max_depth) { - SummarizeStackSession session = { - .summary = ts_malloc(sizeof(StackSummary)), - .max_depth = max_depth - }; - array_init(session.summary); - stack__iter(self, version, summarize_stack_callback, &session, -1); - self->heads.contents[version].summary = session.summary; -} - -StackSummary *ts_stack_get_summary(Stack *self, StackVersion version) { - return array_get(&self->heads, version)->summary; -} - -int ts_stack_dynamic_precedence(Stack *self, StackVersion version) { - return array_get(&self->heads, version)->node->dynamic_precedence; -} - -bool ts_stack_has_advanced_since_error(const Stack *self, StackVersion version) { - const StackHead *head = array_get(&self->heads, version); - const StackNode *node = head->node; - if (node->error_cost == 0) return true; - while (node) { - if (node->link_count > 0) { - Subtree subtree = node->links[0].subtree; - if (subtree.ptr) { - if (ts_subtree_total_bytes(subtree) > 0) { - return true; - } else if ( - node->node_count > head->node_count_at_last_error && - ts_subtree_error_cost(subtree) == 0 - ) { - node = node->links[0].node; - continue; - } - } - } - break; - } - return false; -} - -void ts_stack_remove_version(Stack *self, StackVersion version) { - stack_head_delete(array_get(&self->heads, version), &self->node_pool, self->subtree_pool); - array_erase(&self->heads, version); -} - -void ts_stack_renumber_version(Stack *self, StackVersion v1, StackVersion v2) { - if (v1 == v2) return; - assert(v2 < v1); - assert((uint32_t)v1 < self->heads.size); - StackHead *source_head = &self->heads.contents[v1]; - StackHead *target_head = &self->heads.contents[v2]; - if (target_head->summary && !source_head->summary) { - source_head->summary = target_head->summary; - target_head->summary = NULL; - } - stack_head_delete(target_head, &self->node_pool, self->subtree_pool); - *target_head = *source_head; - array_erase(&self->heads, v1); -} - -void ts_stack_swap_versions(Stack *self, StackVersion v1, StackVersion v2) { - StackHead temporary_head = self->heads.contents[v1]; - self->heads.contents[v1] = self->heads.contents[v2]; - self->heads.contents[v2] = temporary_head; -} - -StackVersion ts_stack_copy_version(Stack *self, StackVersion version) { - assert(version < self->heads.size); - array_push(&self->heads, self->heads.contents[version]); - StackHead *head = array_back(&self->heads); - stack_node_retain(head->node); - if (head->last_external_token.ptr) ts_subtree_retain(head->last_external_token); - head->summary = NULL; - return self->heads.size - 1; -} - -bool ts_stack_merge(Stack *self, StackVersion version1, StackVersion version2) { - if (!ts_stack_can_merge(self, version1, version2)) return false; - StackHead *head1 = &self->heads.contents[version1]; - StackHead *head2 = &self->heads.contents[version2]; - for (uint32_t i = 0; i < head2->node->link_count; i++) { - stack_node_add_link(head1->node, head2->node->links[i], self->subtree_pool); - } - if (head1->node->state == ERROR_STATE) { - head1->node_count_at_last_error = head1->node->node_count; - } - ts_stack_remove_version(self, version2); - return true; -} - -bool ts_stack_can_merge(Stack *self, StackVersion version1, StackVersion version2) { - StackHead *head1 = &self->heads.contents[version1]; - StackHead *head2 = &self->heads.contents[version2]; - return - head1->status == StackStatusActive && - head2->status == StackStatusActive && - head1->node->state == head2->node->state && - head1->node->position.bytes == head2->node->position.bytes && - head1->node->error_cost == head2->node->error_cost && - ts_subtree_external_scanner_state_eq(head1->last_external_token, head2->last_external_token); -} - -void ts_stack_halt(Stack *self, StackVersion version) { - array_get(&self->heads, version)->status = StackStatusHalted; -} - -void ts_stack_pause(Stack *self, StackVersion version, TSSymbol lookahead) { - StackHead *head = array_get(&self->heads, version); - head->status = StackStatusPaused; - head->lookahead_when_paused = lookahead; - head->node_count_at_last_error = head->node->node_count; -} - -bool ts_stack_is_active(const Stack *self, StackVersion version) { - return array_get(&self->heads, version)->status == StackStatusActive; -} - -bool ts_stack_is_halted(const Stack *self, StackVersion version) { - return array_get(&self->heads, version)->status == StackStatusHalted; -} - -bool ts_stack_is_paused(const Stack *self, StackVersion version) { - return array_get(&self->heads, version)->status == StackStatusPaused; -} - -TSSymbol ts_stack_resume(Stack *self, StackVersion version) { - StackHead *head = array_get(&self->heads, version); - assert(head->status == StackStatusPaused); - TSSymbol result = head->lookahead_when_paused; - head->status = StackStatusActive; - head->lookahead_when_paused = 0; - return result; -} - -void ts_stack_clear(Stack *self) { - stack_node_retain(self->base_node); - for (uint32_t i = 0; i < self->heads.size; i++) { - stack_head_delete(&self->heads.contents[i], &self->node_pool, self->subtree_pool); - } - array_clear(&self->heads); - array_push(&self->heads, ((StackHead){ - .node = self->base_node, - .last_external_token = NULL_SUBTREE, - .status = StackStatusActive, - .lookahead_when_paused = 0, - })); -} - -bool ts_stack_print_dot_graph(Stack *self, const TSLanguage *language, FILE *f) { - array_reserve(&self->iterators, 32); - bool was_recording_allocations = ts_toggle_allocation_recording(false); - if (!f) f = stderr; - - fprintf(f, "digraph stack {\n"); - fprintf(f, "rankdir=\"RL\";\n"); - fprintf(f, "edge [arrowhead=none]\n"); - - Array(StackNode *) visited_nodes = array_new(); - - array_clear(&self->iterators); - for (uint32_t i = 0; i < self->heads.size; i++) { - StackHead *head = &self->heads.contents[i]; - if (head->status == StackStatusHalted) continue; - - fprintf(f, "node_head_%u [shape=none, label=\"\"]\n", i); - fprintf(f, "node_head_%u -> node_%p [", i, head->node); - - if (head->status == StackStatusPaused) { - fprintf(f, "color=red "); - } - fprintf(f, - "label=%u, fontcolor=blue, weight=10000, labeltooltip=\"node_count: %u\nerror_cost: %u", - i, - ts_stack_node_count_since_error(self, i), - ts_stack_error_cost(self, i) - ); - - if (head->last_external_token.ptr) { - const ExternalScannerState *state = &head->last_external_token.ptr->external_scanner_state; - const char *data = ts_external_scanner_state_data(state); - fprintf(f, "\nexternal_scanner_state:"); - for (uint32_t j = 0; j < state->length; j++) fprintf(f, " %2X", data[j]); - } - - fprintf(f, "\"]\n"); - array_push(&self->iterators, ((StackIterator){.node = head->node })); - } - - bool all_iterators_done = false; - while (!all_iterators_done) { - all_iterators_done = true; - - for (uint32_t i = 0; i < self->iterators.size; i++) { - StackIterator iterator = self->iterators.contents[i]; - StackNode *node = iterator.node; - - for (uint32_t j = 0; j < visited_nodes.size; j++) { - if (visited_nodes.contents[j] == node) { - node = NULL; - break; - } - } - - if (!node) continue; - all_iterators_done = false; - - fprintf(f, "node_%p [", node); - if (node->state == ERROR_STATE) { - fprintf(f, "label=\"?\""); - } else if ( - node->link_count == 1 && - node->links[0].subtree.ptr && - ts_subtree_extra(node->links[0].subtree) - ) { - fprintf(f, "shape=point margin=0 label=\"\""); - } else { - fprintf(f, "label=\"%d\"", node->state); - } - - fprintf( - f, - " tooltip=\"position: %u,%u\nnode_count:%u\nerror_cost: %u\ndynamic_precedence: %d\"];\n", - node->position.extent.row + 1, - node->position.extent.column, - node->node_count, - node->error_cost, - node->dynamic_precedence - ); - - for (int j = 0; j < node->link_count; j++) { - StackLink link = node->links[j]; - fprintf(f, "node_%p -> node_%p [", node, link.node); - if (link.is_pending) fprintf(f, "style=dashed "); - if (link.subtree.ptr && ts_subtree_extra(link.subtree)) fprintf(f, "fontcolor=gray "); - - if (!link.subtree.ptr) { - fprintf(f, "color=red"); - } else { - fprintf(f, "label=\""); - bool quoted = ts_subtree_visible(link.subtree) && !ts_subtree_named(link.subtree); - if (quoted) fprintf(f, "'"); - const char *name = ts_language_symbol_name(language, ts_subtree_symbol(link.subtree)); - for (const char *c = name; *c; c++) { - if (*c == '\"' || *c == '\\') fprintf(f, "\\"); - fprintf(f, "%c", *c); - } - if (quoted) fprintf(f, "'"); - fprintf(f, "\""); - fprintf( - f, - "labeltooltip=\"error_cost: %u\ndynamic_precedence: %u\"", - ts_subtree_error_cost(link.subtree), - ts_subtree_dynamic_precedence(link.subtree) - ); - } - - fprintf(f, "];\n"); - - StackIterator *next_iterator; - if (j == 0) { - next_iterator = &self->iterators.contents[i]; - } else { - array_push(&self->iterators, iterator); - next_iterator = array_back(&self->iterators); - } - next_iterator->node = link.node; - } - - array_push(&visited_nodes, node); - } - } - - fprintf(f, "}\n"); - - array_delete(&visited_nodes); - ts_toggle_allocation_recording(was_recording_allocations); - return true; -} - -#undef inline diff --git a/src/tree_sitter/stack.h b/src/tree_sitter/stack.h deleted file mode 100644 index ec7a69d2b4..0000000000 --- a/src/tree_sitter/stack.h +++ /dev/null @@ -1,135 +0,0 @@ -#ifndef TREE_SITTER_PARSE_STACK_H_ -#define TREE_SITTER_PARSE_STACK_H_ - -#ifdef __cplusplus -extern "C" { -#endif - -#include "./array.h" -#include "./subtree.h" -#include "./error_costs.h" -#include <stdio.h> - -typedef struct Stack Stack; - -typedef unsigned StackVersion; -#define STACK_VERSION_NONE ((StackVersion)-1) - -typedef struct { - SubtreeArray subtrees; - StackVersion version; -} StackSlice; -typedef Array(StackSlice) StackSliceArray; - -typedef struct { - Length position; - unsigned depth; - TSStateId state; -} StackSummaryEntry; -typedef Array(StackSummaryEntry) StackSummary; - -// Create a stack. -Stack *ts_stack_new(SubtreePool *); - -// Release the memory reserved for a given stack. -void ts_stack_delete(Stack *); - -// Get the stack's current number of versions. -uint32_t ts_stack_version_count(const Stack *); - -// Get the state at the top of the given version of the stack. If the stack is -// empty, this returns the initial state, 0. -TSStateId ts_stack_state(const Stack *, StackVersion); - -// Get the last external token associated with a given version of the stack. -Subtree ts_stack_last_external_token(const Stack *, StackVersion); - -// Set the last external token associated with a given version of the stack. -void ts_stack_set_last_external_token(Stack *, StackVersion, Subtree ); - -// Get the position of the given version of the stack within the document. -Length ts_stack_position(const Stack *, StackVersion); - -// Push a tree and state onto the given version of the stack. -// -// This transfers ownership of the tree to the Stack. Callers that -// need to retain ownership of the tree for their own purposes should -// first retain the tree. -void ts_stack_push(Stack *, StackVersion, Subtree , bool, TSStateId); - -// Pop the given number of entries from the given version of the stack. This -// operation can increase the number of stack versions by revealing multiple -// versions which had previously been merged. It returns an array that -// specifies the index of each revealed version and the trees that were -// removed from that version. -StackSliceArray ts_stack_pop_count(Stack *, StackVersion, uint32_t count); - -// Remove an error at the top of the given version of the stack. -SubtreeArray ts_stack_pop_error(Stack *, StackVersion); - -// Remove any pending trees from the top of the given version of the stack. -StackSliceArray ts_stack_pop_pending(Stack *, StackVersion); - -// Remove any all trees from the given version of the stack. -StackSliceArray ts_stack_pop_all(Stack *, StackVersion); - -// Get the maximum number of tree nodes reachable from this version of the stack -// since the last error was detected. -unsigned ts_stack_node_count_since_error(const Stack *, StackVersion); - -int ts_stack_dynamic_precedence(Stack *, StackVersion); - -bool ts_stack_has_advanced_since_error(const Stack *, StackVersion); - -// Compute a summary of all the parse states near the top of the given -// version of the stack and store the summary for later retrieval. -void ts_stack_record_summary(Stack *, StackVersion, unsigned max_depth); - -// Retrieve a summary of all the parse states near the top of the -// given version of the stack. -StackSummary *ts_stack_get_summary(Stack *, StackVersion); - -// Get the total cost of all errors on the given version of the stack. -unsigned ts_stack_error_cost(const Stack *, StackVersion version); - -// Merge the given two stack versions if possible, returning true -// if they were successfully merged and false otherwise. -bool ts_stack_merge(Stack *, StackVersion, StackVersion); - -// Determine whether the given two stack versions can be merged. -bool ts_stack_can_merge(Stack *, StackVersion, StackVersion); - -TSSymbol ts_stack_resume(Stack *, StackVersion); - -void ts_stack_pause(Stack *, StackVersion, TSSymbol); - -void ts_stack_halt(Stack *, StackVersion); - -bool ts_stack_is_active(const Stack *, StackVersion); - -bool ts_stack_is_paused(const Stack *, StackVersion); - -bool ts_stack_is_halted(const Stack *, StackVersion); - -void ts_stack_renumber_version(Stack *, StackVersion, StackVersion); - -void ts_stack_swap_versions(Stack *, StackVersion, StackVersion); - -StackVersion ts_stack_copy_version(Stack *, StackVersion); - -// Remove the given version from the stack. -void ts_stack_remove_version(Stack *, StackVersion); - -void ts_stack_clear(Stack *); - -bool ts_stack_print_dot_graph(Stack *, const TSLanguage *, FILE *); - -typedef void (*StackIterateCallback)(void *, TSStateId, uint32_t); - -void ts_stack_iterate(Stack *, StackVersion, StackIterateCallback, void *); - -#ifdef __cplusplus -} -#endif - -#endif // TREE_SITTER_PARSE_STACK_H_ diff --git a/src/tree_sitter/subtree.c b/src/tree_sitter/subtree.c deleted file mode 100644 index ef92a32fe4..0000000000 --- a/src/tree_sitter/subtree.c +++ /dev/null @@ -1,982 +0,0 @@ -#include <assert.h> -#include <ctype.h> -#include <limits.h> -#include <stdbool.h> -#include <string.h> -#include <stdio.h> -#include "./alloc.h" -#include "./atomic.h" -#include "./subtree.h" -#include "./length.h" -#include "./language.h" -#include "./error_costs.h" -#include <stddef.h> - -typedef struct { - Length start; - Length old_end; - Length new_end; -} Edit; - -#define TS_MAX_INLINE_TREE_LENGTH UINT8_MAX -#define TS_MAX_TREE_POOL_SIZE 32 - -static const ExternalScannerState empty_state = {{.short_data = {0}}, .length = 0}; - -// ExternalScannerState - -void ts_external_scanner_state_init(ExternalScannerState *self, const char *data, unsigned length) { - self->length = length; - if (length > sizeof(self->short_data)) { - self->long_data = ts_malloc(length); - memcpy(self->long_data, data, length); - } else { - memcpy(self->short_data, data, length); - } -} - -ExternalScannerState ts_external_scanner_state_copy(const ExternalScannerState *self) { - ExternalScannerState result = *self; - if (self->length > sizeof(self->short_data)) { - result.long_data = ts_malloc(self->length); - memcpy(result.long_data, self->long_data, self->length); - } - return result; -} - -void ts_external_scanner_state_delete(ExternalScannerState *self) { - if (self->length > sizeof(self->short_data)) { - ts_free(self->long_data); - } -} - -const char *ts_external_scanner_state_data(const ExternalScannerState *self) { - if (self->length > sizeof(self->short_data)) { - return self->long_data; - } else { - return self->short_data; - } -} - -bool ts_external_scanner_state_eq(const ExternalScannerState *a, const ExternalScannerState *b) { - return a == b || ( - a->length == b->length && - !memcmp(ts_external_scanner_state_data(a), ts_external_scanner_state_data(b), a->length) - ); -} - -// SubtreeArray - -void ts_subtree_array_copy(SubtreeArray self, SubtreeArray *dest) { - dest->size = self.size; - dest->capacity = self.capacity; - dest->contents = self.contents; - if (self.capacity > 0) { - dest->contents = ts_calloc(self.capacity, sizeof(Subtree)); - memcpy(dest->contents, self.contents, self.size * sizeof(Subtree)); - for (uint32_t i = 0; i < self.size; i++) { - ts_subtree_retain(dest->contents[i]); - } - } -} - -void ts_subtree_array_delete(SubtreePool *pool, SubtreeArray *self) { - for (uint32_t i = 0; i < self->size; i++) { - ts_subtree_release(pool, self->contents[i]); - } - array_delete(self); -} - -SubtreeArray ts_subtree_array_remove_trailing_extras(SubtreeArray *self) { - SubtreeArray result = array_new(); - - uint32_t i = self->size - 1; - for (; i + 1 > 0; i--) { - Subtree child = self->contents[i]; - if (!ts_subtree_extra(child)) break; - array_push(&result, child); - } - - self->size = i + 1; - ts_subtree_array_reverse(&result); - return result; -} - -void ts_subtree_array_reverse(SubtreeArray *self) { - for (uint32_t i = 0, limit = self->size / 2; i < limit; i++) { - size_t reverse_index = self->size - 1 - i; - Subtree swap = self->contents[i]; - self->contents[i] = self->contents[reverse_index]; - self->contents[reverse_index] = swap; - } -} - -// SubtreePool - -SubtreePool ts_subtree_pool_new(uint32_t capacity) { - SubtreePool self = {array_new(), array_new()}; - array_reserve(&self.free_trees, capacity); - return self; -} - -void ts_subtree_pool_delete(SubtreePool *self) { - if (self->free_trees.contents) { - for (unsigned i = 0; i < self->free_trees.size; i++) { - ts_free(self->free_trees.contents[i].ptr); - } - array_delete(&self->free_trees); - } - if (self->tree_stack.contents) array_delete(&self->tree_stack); -} - -static SubtreeHeapData *ts_subtree_pool_allocate(SubtreePool *self) { - if (self->free_trees.size > 0) { - return array_pop(&self->free_trees).ptr; - } else { - return ts_malloc(sizeof(SubtreeHeapData)); - } -} - -static void ts_subtree_pool_free(SubtreePool *self, SubtreeHeapData *tree) { - if (self->free_trees.capacity > 0 && self->free_trees.size + 1 <= TS_MAX_TREE_POOL_SIZE) { - array_push(&self->free_trees, (MutableSubtree) {.ptr = tree}); - } else { - ts_free(tree); - } -} - -// Subtree - -static inline bool ts_subtree_can_inline(Length padding, Length size, uint32_t lookahead_bytes) { - return - padding.bytes < TS_MAX_INLINE_TREE_LENGTH && - padding.extent.row < 16 && - padding.extent.column < TS_MAX_INLINE_TREE_LENGTH && - size.extent.row == 0 && - size.extent.column < TS_MAX_INLINE_TREE_LENGTH && - lookahead_bytes < 16; -} - -Subtree ts_subtree_new_leaf( - SubtreePool *pool, TSSymbol symbol, Length padding, Length size, - uint32_t lookahead_bytes, TSStateId parse_state, bool has_external_tokens, - bool is_keyword, const TSLanguage *language -) { - TSSymbolMetadata metadata = ts_language_symbol_metadata(language, symbol); - bool extra = symbol == ts_builtin_sym_end; - - bool is_inline = ( - symbol <= UINT8_MAX && - !has_external_tokens && - ts_subtree_can_inline(padding, size, lookahead_bytes) - ); - - if (is_inline) { - return (Subtree) {{ - .parse_state = parse_state, - .symbol = symbol, - .padding_bytes = padding.bytes, - .padding_rows = padding.extent.row, - .padding_columns = padding.extent.column, - .size_bytes = size.bytes, - .lookahead_bytes = lookahead_bytes, - .visible = metadata.visible, - .named = metadata.named, - .extra = extra, - .has_changes = false, - .is_missing = false, - .is_keyword = is_keyword, - .is_inline = true, - }}; - } else { - SubtreeHeapData *data = ts_subtree_pool_allocate(pool); - *data = (SubtreeHeapData) { - .ref_count = 1, - .padding = padding, - .size = size, - .lookahead_bytes = lookahead_bytes, - .error_cost = 0, - .child_count = 0, - .symbol = symbol, - .parse_state = parse_state, - .visible = metadata.visible, - .named = metadata.named, - .extra = extra, - .fragile_left = false, - .fragile_right = false, - .has_changes = false, - .has_external_tokens = has_external_tokens, - .is_missing = false, - .is_keyword = is_keyword, - {{.first_leaf = {.symbol = 0, .parse_state = 0}}} - }; - return (Subtree) {.ptr = data}; - } -} - -void ts_subtree_set_symbol( - MutableSubtree *self, - TSSymbol symbol, - const TSLanguage *language -) { - TSSymbolMetadata metadata = ts_language_symbol_metadata(language, symbol); - if (self->data.is_inline) { - assert(symbol < UINT8_MAX); - self->data.symbol = symbol; - self->data.named = metadata.named; - self->data.visible = metadata.visible; - } else { - self->ptr->symbol = symbol; - self->ptr->named = metadata.named; - self->ptr->visible = metadata.visible; - } -} - -Subtree ts_subtree_new_error( - SubtreePool *pool, int32_t lookahead_char, Length padding, Length size, - uint32_t bytes_scanned, TSStateId parse_state, const TSLanguage *language -) { - Subtree result = ts_subtree_new_leaf( - pool, ts_builtin_sym_error, padding, size, bytes_scanned, - parse_state, false, false, language - ); - SubtreeHeapData *data = (SubtreeHeapData *)result.ptr; - data->fragile_left = true; - data->fragile_right = true; - data->lookahead_char = lookahead_char; - return result; -} - -MutableSubtree ts_subtree_make_mut(SubtreePool *pool, Subtree self) { - if (self.data.is_inline) return (MutableSubtree) {self.data}; - if (self.ptr->ref_count == 1) return ts_subtree_to_mut_unsafe(self); - - SubtreeHeapData *result = ts_subtree_pool_allocate(pool); - memcpy(result, self.ptr, sizeof(SubtreeHeapData)); - if (result->child_count > 0) { - result->children = ts_calloc(self.ptr->child_count, sizeof(Subtree)); - memcpy(result->children, self.ptr->children, result->child_count * sizeof(Subtree)); - for (uint32_t i = 0; i < result->child_count; i++) { - ts_subtree_retain(result->children[i]); - } - } else if (result->has_external_tokens) { - result->external_scanner_state = ts_external_scanner_state_copy(&self.ptr->external_scanner_state); - } - result->ref_count = 1; - ts_subtree_release(pool, self); - return (MutableSubtree) {.ptr = result}; -} - -static void ts_subtree__compress(MutableSubtree self, unsigned count, const TSLanguage *language, - MutableSubtreeArray *stack) { - unsigned initial_stack_size = stack->size; - - MutableSubtree tree = self; - TSSymbol symbol = tree.ptr->symbol; - for (unsigned i = 0; i < count; i++) { - if (tree.ptr->ref_count > 1 || tree.ptr->child_count < 2) break; - - MutableSubtree child = ts_subtree_to_mut_unsafe(tree.ptr->children[0]); - if ( - child.data.is_inline || - child.ptr->child_count < 2 || - child.ptr->ref_count > 1 || - child.ptr->symbol != symbol - ) break; - - MutableSubtree grandchild = ts_subtree_to_mut_unsafe(child.ptr->children[0]); - if ( - grandchild.data.is_inline || - grandchild.ptr->child_count < 2 || - grandchild.ptr->ref_count > 1 || - grandchild.ptr->symbol != symbol - ) break; - - tree.ptr->children[0] = ts_subtree_from_mut(grandchild); - child.ptr->children[0] = grandchild.ptr->children[grandchild.ptr->child_count - 1]; - grandchild.ptr->children[grandchild.ptr->child_count - 1] = ts_subtree_from_mut(child); - array_push(stack, tree); - tree = grandchild; - } - - while (stack->size > initial_stack_size) { - tree = array_pop(stack); - MutableSubtree child = ts_subtree_to_mut_unsafe(tree.ptr->children[0]); - MutableSubtree grandchild = ts_subtree_to_mut_unsafe(child.ptr->children[child.ptr->child_count - 1]); - ts_subtree_set_children(grandchild, grandchild.ptr->children, grandchild.ptr->child_count, language); - ts_subtree_set_children(child, child.ptr->children, child.ptr->child_count, language); - ts_subtree_set_children(tree, tree.ptr->children, tree.ptr->child_count, language); - } -} - -void ts_subtree_balance(Subtree self, SubtreePool *pool, const TSLanguage *language) { - array_clear(&pool->tree_stack); - - if (ts_subtree_child_count(self) > 0 && self.ptr->ref_count == 1) { - array_push(&pool->tree_stack, ts_subtree_to_mut_unsafe(self)); - } - - while (pool->tree_stack.size > 0) { - MutableSubtree tree = array_pop(&pool->tree_stack); - - if (tree.ptr->repeat_depth > 0) { - Subtree child1 = tree.ptr->children[0]; - Subtree child2 = tree.ptr->children[tree.ptr->child_count - 1]; - long repeat_delta = (long)ts_subtree_repeat_depth(child1) - (long)ts_subtree_repeat_depth(child2); - if (repeat_delta > 0) { - unsigned n = repeat_delta; - for (unsigned i = n / 2; i > 0; i /= 2) { - ts_subtree__compress(tree, i, language, &pool->tree_stack); - n -= i; - } - } - } - - for (uint32_t i = 0; i < tree.ptr->child_count; i++) { - Subtree child = tree.ptr->children[i]; - if (ts_subtree_child_count(child) > 0 && child.ptr->ref_count == 1) { - array_push(&pool->tree_stack, ts_subtree_to_mut_unsafe(child)); - } - } - } -} - -void ts_subtree_set_children( - MutableSubtree self, Subtree *children, uint32_t child_count, const TSLanguage *language -) { - assert(!self.data.is_inline); - - if (self.ptr->child_count > 0 && children != self.ptr->children) { - ts_free(self.ptr->children); - } - - self.ptr->child_count = child_count; - self.ptr->children = children; - self.ptr->named_child_count = 0; - self.ptr->visible_child_count = 0; - self.ptr->error_cost = 0; - self.ptr->repeat_depth = 0; - self.ptr->node_count = 1; - self.ptr->has_external_tokens = false; - self.ptr->dynamic_precedence = 0; - - uint32_t non_extra_index = 0; - const TSSymbol *alias_sequence = ts_language_alias_sequence(language, self.ptr->production_id); - uint32_t lookahead_end_byte = 0; - - for (uint32_t i = 0; i < self.ptr->child_count; i++) { - Subtree child = self.ptr->children[i]; - - if (i == 0) { - self.ptr->padding = ts_subtree_padding(child); - self.ptr->size = ts_subtree_size(child); - } else { - self.ptr->size = length_add(self.ptr->size, ts_subtree_total_size(child)); - } - - uint32_t child_lookahead_end_byte = - self.ptr->padding.bytes + - self.ptr->size.bytes + - ts_subtree_lookahead_bytes(child); - if (child_lookahead_end_byte > lookahead_end_byte) lookahead_end_byte = child_lookahead_end_byte; - - if (ts_subtree_symbol(child) != ts_builtin_sym_error_repeat) { - self.ptr->error_cost += ts_subtree_error_cost(child); - } - - self.ptr->dynamic_precedence += ts_subtree_dynamic_precedence(child); - self.ptr->node_count += ts_subtree_node_count(child); - - if (alias_sequence && alias_sequence[non_extra_index] != 0 && !ts_subtree_extra(child)) { - self.ptr->visible_child_count++; - if (ts_language_symbol_metadata(language, alias_sequence[non_extra_index]).named) { - self.ptr->named_child_count++; - } - } else if (ts_subtree_visible(child)) { - self.ptr->visible_child_count++; - if (ts_subtree_named(child)) self.ptr->named_child_count++; - } else if (ts_subtree_child_count(child) > 0) { - self.ptr->visible_child_count += child.ptr->visible_child_count; - self.ptr->named_child_count += child.ptr->named_child_count; - } - - if (ts_subtree_has_external_tokens(child)) self.ptr->has_external_tokens = true; - - if (ts_subtree_is_error(child)) { - self.ptr->fragile_left = self.ptr->fragile_right = true; - self.ptr->parse_state = TS_TREE_STATE_NONE; - } - - if (!ts_subtree_extra(child)) non_extra_index++; - } - - self.ptr->lookahead_bytes = lookahead_end_byte - self.ptr->size.bytes - self.ptr->padding.bytes; - - if (self.ptr->symbol == ts_builtin_sym_error || self.ptr->symbol == ts_builtin_sym_error_repeat) { - self.ptr->error_cost += - ERROR_COST_PER_RECOVERY + - ERROR_COST_PER_SKIPPED_CHAR * self.ptr->size.bytes + - ERROR_COST_PER_SKIPPED_LINE * self.ptr->size.extent.row; - for (uint32_t i = 0; i < self.ptr->child_count; i++) { - Subtree child = self.ptr->children[i]; - uint32_t grandchild_count = ts_subtree_child_count(child); - if (ts_subtree_extra(child)) continue; - if (ts_subtree_is_error(child) && grandchild_count == 0) continue; - if (ts_subtree_visible(child)) { - self.ptr->error_cost += ERROR_COST_PER_SKIPPED_TREE; - } else if (grandchild_count > 0) { - self.ptr->error_cost += ERROR_COST_PER_SKIPPED_TREE * child.ptr->visible_child_count; - } - } - } - - if (self.ptr->child_count > 0) { - Subtree first_child = self.ptr->children[0]; - Subtree last_child = self.ptr->children[self.ptr->child_count - 1]; - - self.ptr->first_leaf.symbol = ts_subtree_leaf_symbol(first_child); - self.ptr->first_leaf.parse_state = ts_subtree_leaf_parse_state(first_child); - - if (ts_subtree_fragile_left(first_child)) self.ptr->fragile_left = true; - if (ts_subtree_fragile_right(last_child)) self.ptr->fragile_right = true; - - if ( - self.ptr->child_count >= 2 && - !self.ptr->visible && - !self.ptr->named && - ts_subtree_symbol(first_child) == self.ptr->symbol - ) { - if (ts_subtree_repeat_depth(first_child) > ts_subtree_repeat_depth(last_child)) { - self.ptr->repeat_depth = ts_subtree_repeat_depth(first_child) + 1; - } else { - self.ptr->repeat_depth = ts_subtree_repeat_depth(last_child) + 1; - } - } - } -} - -MutableSubtree ts_subtree_new_node(SubtreePool *pool, TSSymbol symbol, - SubtreeArray *children, unsigned production_id, - const TSLanguage *language) { - TSSymbolMetadata metadata = ts_language_symbol_metadata(language, symbol); - bool fragile = symbol == ts_builtin_sym_error || symbol == ts_builtin_sym_error_repeat; - SubtreeHeapData *data = ts_subtree_pool_allocate(pool); - *data = (SubtreeHeapData) { - .ref_count = 1, - .symbol = symbol, - .visible = metadata.visible, - .named = metadata.named, - .has_changes = false, - .fragile_left = fragile, - .fragile_right = fragile, - .is_keyword = false, - {{ - .node_count = 0, - .production_id = production_id, - .first_leaf = {.symbol = 0, .parse_state = 0}, - }} - }; - MutableSubtree result = {.ptr = data}; - ts_subtree_set_children(result, children->contents, children->size, language); - return result; -} - -Subtree ts_subtree_new_error_node(SubtreePool *pool, SubtreeArray *children, - bool extra, const TSLanguage *language) { - MutableSubtree result = ts_subtree_new_node( - pool, ts_builtin_sym_error, children, 0, language - ); - result.ptr->extra = extra; - return ts_subtree_from_mut(result); -} - -Subtree ts_subtree_new_missing_leaf(SubtreePool *pool, TSSymbol symbol, Length padding, - const TSLanguage *language) { - Subtree result = ts_subtree_new_leaf( - pool, symbol, padding, length_zero(), 0, - 0, false, false, language - ); - - if (result.data.is_inline) { - result.data.is_missing = true; - } else { - ((SubtreeHeapData *)result.ptr)->is_missing = true; - } - - return result; -} - -void ts_subtree_retain(Subtree self) { - if (self.data.is_inline) return; - assert(self.ptr->ref_count > 0); - atomic_inc((volatile uint32_t *)&self.ptr->ref_count); - assert(self.ptr->ref_count != 0); -} - -void ts_subtree_release(SubtreePool *pool, Subtree self) { - if (self.data.is_inline) return; - array_clear(&pool->tree_stack); - - assert(self.ptr->ref_count > 0); - if (atomic_dec((volatile uint32_t *)&self.ptr->ref_count) == 0) { - array_push(&pool->tree_stack, ts_subtree_to_mut_unsafe(self)); - } - - while (pool->tree_stack.size > 0) { - MutableSubtree tree = array_pop(&pool->tree_stack); - if (tree.ptr->child_count > 0) { - for (uint32_t i = 0; i < tree.ptr->child_count; i++) { - Subtree child = tree.ptr->children[i]; - if (child.data.is_inline) continue; - assert(child.ptr->ref_count > 0); - if (atomic_dec((volatile uint32_t *)&child.ptr->ref_count) == 0) { - array_push(&pool->tree_stack, ts_subtree_to_mut_unsafe(child)); - } - } - ts_free(tree.ptr->children); - } else if (tree.ptr->has_external_tokens) { - ts_external_scanner_state_delete(&tree.ptr->external_scanner_state); - } - ts_subtree_pool_free(pool, tree.ptr); - } -} - -bool ts_subtree_eq(Subtree self, Subtree other) { - if (self.data.is_inline || other.data.is_inline) { - return memcmp(&self, &other, sizeof(SubtreeInlineData)) == 0; - } - - if (self.ptr) { - if (!other.ptr) return false; - } else { - return !other.ptr; - } - - if (self.ptr->symbol != other.ptr->symbol) return false; - if (self.ptr->visible != other.ptr->visible) return false; - if (self.ptr->named != other.ptr->named) return false; - if (self.ptr->padding.bytes != other.ptr->padding.bytes) return false; - if (self.ptr->size.bytes != other.ptr->size.bytes) return false; - if (self.ptr->symbol == ts_builtin_sym_error) return self.ptr->lookahead_char == other.ptr->lookahead_char; - if (self.ptr->child_count != other.ptr->child_count) return false; - if (self.ptr->child_count > 0) { - if (self.ptr->visible_child_count != other.ptr->visible_child_count) return false; - if (self.ptr->named_child_count != other.ptr->named_child_count) return false; - - for (uint32_t i = 0; i < self.ptr->child_count; i++) { - if (!ts_subtree_eq(self.ptr->children[i], other.ptr->children[i])) { - return false; - } - } - } - return true; -} - -int ts_subtree_compare(Subtree left, Subtree right) { - if (ts_subtree_symbol(left) < ts_subtree_symbol(right)) return -1; - if (ts_subtree_symbol(right) < ts_subtree_symbol(left)) return 1; - if (ts_subtree_child_count(left) < ts_subtree_child_count(right)) return -1; - if (ts_subtree_child_count(right) < ts_subtree_child_count(left)) return 1; - for (uint32_t i = 0, n = ts_subtree_child_count(left); i < n; i++) { - Subtree left_child = left.ptr->children[i]; - Subtree right_child = right.ptr->children[i]; - switch (ts_subtree_compare(left_child, right_child)) { - case -1: return -1; - case 1: return 1; - default: break; - } - } - return 0; -} - -static inline void ts_subtree_set_has_changes(MutableSubtree *self) { - if (self->data.is_inline) { - self->data.has_changes = true; - } else { - self->ptr->has_changes = true; - } -} - -Subtree ts_subtree_edit(Subtree self, const TSInputEdit *edit, SubtreePool *pool) { - typedef struct { - Subtree *tree; - Edit edit; - } StackEntry; - - Array(StackEntry) stack = array_new(); - array_push(&stack, ((StackEntry) { - .tree = &self, - .edit = (Edit) { - .start = {edit->start_byte, edit->start_point}, - .old_end = {edit->old_end_byte, edit->old_end_point}, - .new_end = {edit->new_end_byte, edit->new_end_point}, - }, - })); - - while (stack.size) { - StackEntry entry = array_pop(&stack); - Edit edit = entry.edit; - bool is_noop = edit.old_end.bytes == edit.start.bytes && edit.new_end.bytes == edit.start.bytes; - bool is_pure_insertion = edit.old_end.bytes == edit.start.bytes; - - Length size = ts_subtree_size(*entry.tree); - Length padding = ts_subtree_padding(*entry.tree); - uint32_t lookahead_bytes = ts_subtree_lookahead_bytes(*entry.tree); - uint32_t end_byte = padding.bytes + size.bytes + lookahead_bytes; - if (edit.start.bytes > end_byte || (is_noop && edit.start.bytes == end_byte)) continue; - - // If the edit is entirely within the space before this subtree, then shift this - // subtree over according to the edit without changing its size. - if (edit.old_end.bytes <= padding.bytes) { - padding = length_add(edit.new_end, length_sub(padding, edit.old_end)); - } - - // If the edit starts in the space before this subtree and extends into this subtree, - // shrink the subtree's content to compensate for the change in the space before it. - else if (edit.start.bytes < padding.bytes) { - size = length_sub(size, length_sub(edit.old_end, padding)); - padding = edit.new_end; - } - - // If the edit is a pure insertion right at the start of the subtree, - // shift the subtree over according to the insertion. - else if (edit.start.bytes == padding.bytes && is_pure_insertion) { - padding = edit.new_end; - } - - // If the edit is within this subtree, resize the subtree to reflect the edit. - else { - uint32_t total_bytes = padding.bytes + size.bytes; - if (edit.start.bytes < total_bytes || - (edit.start.bytes == total_bytes && is_pure_insertion)) { - size = length_add( - length_sub(edit.new_end, padding), - length_sub(size, length_sub(edit.old_end, padding)) - ); - } - } - - MutableSubtree result = ts_subtree_make_mut(pool, *entry.tree); - - if (result.data.is_inline) { - if (ts_subtree_can_inline(padding, size, lookahead_bytes)) { - result.data.padding_bytes = padding.bytes; - result.data.padding_rows = padding.extent.row; - result.data.padding_columns = padding.extent.column; - result.data.size_bytes = size.bytes; - } else { - SubtreeHeapData *data = ts_subtree_pool_allocate(pool); - data->ref_count = 1; - data->padding = padding; - data->size = size; - data->lookahead_bytes = lookahead_bytes; - data->error_cost = 0; - data->child_count = 0; - data->symbol = result.data.symbol; - data->parse_state = result.data.parse_state; - data->visible = result.data.visible; - data->named = result.data.named; - data->extra = result.data.extra; - data->fragile_left = false; - data->fragile_right = false; - data->has_changes = false; - data->has_external_tokens = false; - data->is_missing = result.data.is_missing; - data->is_keyword = result.data.is_keyword; - result.ptr = data; - } - } else { - result.ptr->padding = padding; - result.ptr->size = size; - } - - ts_subtree_set_has_changes(&result); - *entry.tree = ts_subtree_from_mut(result); - - Length child_left, child_right = length_zero(); - for (uint32_t i = 0, n = ts_subtree_child_count(*entry.tree); i < n; i++) { - Subtree *child = &result.ptr->children[i]; - Length child_size = ts_subtree_total_size(*child); - child_left = child_right; - child_right = length_add(child_left, child_size); - - // If this child ends before the edit, it is not affected. - if (child_right.bytes + ts_subtree_lookahead_bytes(*child) < edit.start.bytes) continue; - - // If this child starts after the edit, then we're done processing children. - if (child_left.bytes > edit.old_end.bytes || - (child_left.bytes == edit.old_end.bytes && child_size.bytes > 0 && i > 0)) break; - - // Transform edit into the child's coordinate space. - Edit child_edit = { - .start = length_sub(edit.start, child_left), - .old_end = length_sub(edit.old_end, child_left), - .new_end = length_sub(edit.new_end, child_left), - }; - - // Clamp child_edit to the child's bounds. - if (edit.start.bytes < child_left.bytes) child_edit.start = length_zero(); - if (edit.old_end.bytes < child_left.bytes) child_edit.old_end = length_zero(); - if (edit.new_end.bytes < child_left.bytes) child_edit.new_end = length_zero(); - if (edit.old_end.bytes > child_right.bytes) child_edit.old_end = child_size; - - // Interpret all inserted text as applying to the *first* child that touches the edit. - // Subsequent children are only never have any text inserted into them; they are only - // shrunk to compensate for the edit. - if (child_right.bytes > edit.start.bytes || - (child_right.bytes == edit.start.bytes && is_pure_insertion)) { - edit.new_end = edit.start; - } - - // Children that occur before the edit are not reshaped by the edit. - else { - child_edit.old_end = child_edit.start; - child_edit.new_end = child_edit.start; - } - - // Queue processing of this child's subtree. - array_push(&stack, ((StackEntry) { - .tree = child, - .edit = child_edit, - })); - } - } - - array_delete(&stack); - return self; -} - -Subtree ts_subtree_last_external_token(Subtree tree) { - if (!ts_subtree_has_external_tokens(tree)) return NULL_SUBTREE; - while (tree.ptr->child_count > 0) { - for (uint32_t i = tree.ptr->child_count - 1; i + 1 > 0; i--) { - Subtree child = tree.ptr->children[i]; - if (ts_subtree_has_external_tokens(child)) { - tree = child; - break; - } - } - } - return tree; -} - -static size_t ts_subtree__write_char_to_string(char *s, size_t n, int32_t c) { - if (c == -1) - return snprintf(s, n, "INVALID"); - else if (c == '\0') - return snprintf(s, n, "'\\0'"); - else if (c == '\n') - return snprintf(s, n, "'\\n'"); - else if (c == '\t') - return snprintf(s, n, "'\\t'"); - else if (c == '\r') - return snprintf(s, n, "'\\r'"); - else if (0 < c && c < 128 && isprint(c)) - return snprintf(s, n, "'%c'", c); - else - return snprintf(s, n, "%d", c); -} - -static void ts_subtree__write_dot_string(FILE *f, const char *string) { - for (const char *c = string; *c; c++) { - if (*c == '"') { - fputs("\\\"", f); - } else if (*c == '\n') { - fputs("\\n", f); - } else { - fputc(*c, f); - } - } -} - -static const char *ROOT_FIELD = "__ROOT__"; - -static size_t ts_subtree__write_to_string( - Subtree self, char *string, size_t limit, - const TSLanguage *language, bool include_all, - TSSymbol alias_symbol, bool alias_is_named, const char *field_name -) { - if (!self.ptr) return snprintf(string, limit, "(NULL)"); - - char *cursor = string; - char **writer = (limit > 0) ? &cursor : &string; - bool is_root = field_name == ROOT_FIELD; - bool is_visible = - include_all || - ts_subtree_missing(self) || - ( - alias_symbol - ? alias_is_named - : ts_subtree_visible(self) && ts_subtree_named(self) - ); - - if (is_visible) { - if (!is_root) { - cursor += snprintf(*writer, limit, " "); - if (field_name) { - cursor += snprintf(*writer, limit, "%s: ", field_name); - } - } - - if (ts_subtree_is_error(self) && ts_subtree_child_count(self) == 0 && self.ptr->size.bytes > 0) { - cursor += snprintf(*writer, limit, "(UNEXPECTED "); - cursor += ts_subtree__write_char_to_string(*writer, limit, self.ptr->lookahead_char); - } else { - TSSymbol symbol = alias_symbol ? alias_symbol : ts_subtree_symbol(self); - const char *symbol_name = ts_language_symbol_name(language, symbol); - if (ts_subtree_missing(self)) { - cursor += snprintf(*writer, limit, "(MISSING "); - if (alias_is_named || ts_subtree_named(self)) { - cursor += snprintf(*writer, limit, "%s", symbol_name); - } else { - cursor += snprintf(*writer, limit, "\"%s\"", symbol_name); - } - } else { - cursor += snprintf(*writer, limit, "(%s", symbol_name); - } - } - } else if (is_root) { - TSSymbol symbol = ts_subtree_symbol(self); - const char *symbol_name = ts_language_symbol_name(language, symbol); - cursor += snprintf(*writer, limit, "(\"%s\")", symbol_name); - } - - if (ts_subtree_child_count(self)) { - const TSSymbol *alias_sequence = ts_language_alias_sequence(language, self.ptr->production_id); - const TSFieldMapEntry *field_map, *field_map_end; - ts_language_field_map( - language, - self.ptr->production_id, - &field_map, - &field_map_end - ); - - uint32_t structural_child_index = 0; - for (uint32_t i = 0; i < self.ptr->child_count; i++) { - Subtree child = self.ptr->children[i]; - if (ts_subtree_extra(child)) { - cursor += ts_subtree__write_to_string( - child, *writer, limit, - language, include_all, - 0, false, NULL - ); - } else { - TSSymbol alias_symbol = alias_sequence - ? alias_sequence[structural_child_index] - : 0; - bool alias_is_named = alias_symbol - ? ts_language_symbol_metadata(language, alias_symbol).named - : false; - - const char *child_field_name = is_visible ? NULL : field_name; - for (const TSFieldMapEntry *i = field_map; i < field_map_end; i++) { - if (!i->inherited && i->child_index == structural_child_index) { - child_field_name = language->field_names[i->field_id]; - break; - } - } - - cursor += ts_subtree__write_to_string( - child, *writer, limit, - language, include_all, - alias_symbol, alias_is_named, child_field_name - ); - structural_child_index++; - } - } - } - - if (is_visible) cursor += snprintf(*writer, limit, ")"); - - return cursor - string; -} - -char *ts_subtree_string( - Subtree self, - const TSLanguage *language, - bool include_all -) { - char scratch_string[1]; - size_t size = ts_subtree__write_to_string( - self, scratch_string, 0, - language, include_all, - 0, false, ROOT_FIELD - ) + 1; - char *result = malloc(size * sizeof(char)); - ts_subtree__write_to_string( - self, result, size, - language, include_all, - 0, false, ROOT_FIELD - ); - return result; -} - -void ts_subtree__print_dot_graph(const Subtree *self, uint32_t start_offset, - const TSLanguage *language, TSSymbol alias_symbol, - FILE *f) { - TSSymbol subtree_symbol = ts_subtree_symbol(*self); - TSSymbol symbol = alias_symbol ? alias_symbol : subtree_symbol; - uint32_t end_offset = start_offset + ts_subtree_total_bytes(*self); - fprintf(f, "tree_%p [label=\"", self); - ts_subtree__write_dot_string(f, ts_language_symbol_name(language, symbol)); - fprintf(f, "\""); - - if (ts_subtree_child_count(*self) == 0) fprintf(f, ", shape=plaintext"); - if (ts_subtree_extra(*self)) fprintf(f, ", fontcolor=gray"); - - fprintf(f, ", tooltip=\"" - "range: %u - %u\n" - "state: %d\n" - "error-cost: %u\n" - "has-changes: %u\n" - "repeat-depth: %u\n" - "lookahead-bytes: %u", - start_offset, end_offset, - ts_subtree_parse_state(*self), - ts_subtree_error_cost(*self), - ts_subtree_has_changes(*self), - ts_subtree_repeat_depth(*self), - ts_subtree_lookahead_bytes(*self) - ); - - if (ts_subtree_is_error(*self) && ts_subtree_child_count(*self) == 0) { - fprintf(f, "\ncharacter: '%c'", self->ptr->lookahead_char); - } - - fprintf(f, "\"]\n"); - - uint32_t child_start_offset = start_offset; - uint32_t child_info_offset = - language->max_alias_sequence_length * - ts_subtree_production_id(*self); - for (uint32_t i = 0, n = ts_subtree_child_count(*self); i < n; i++) { - const Subtree *child = &self->ptr->children[i]; - TSSymbol alias_symbol = 0; - if (!ts_subtree_extra(*child) && child_info_offset) { - alias_symbol = language->alias_sequences[child_info_offset]; - child_info_offset++; - } - ts_subtree__print_dot_graph(child, child_start_offset, language, alias_symbol, f); - fprintf(f, "tree_%p -> tree_%p [tooltip=%u]\n", self, child, i); - child_start_offset += ts_subtree_total_bytes(*child); - } -} - -void ts_subtree_print_dot_graph(Subtree self, const TSLanguage *language, FILE *f) { - fprintf(f, "digraph tree {\n"); - fprintf(f, "edge [arrowhead=none]\n"); - ts_subtree__print_dot_graph(&self, 0, language, 0, f); - fprintf(f, "}\n"); -} - -bool ts_subtree_external_scanner_state_eq(Subtree self, Subtree other) { - const ExternalScannerState *state1 = &empty_state; - const ExternalScannerState *state2 = &empty_state; - if (self.ptr && ts_subtree_has_external_tokens(self) && !self.ptr->child_count) { - state1 = &self.ptr->external_scanner_state; - } - if (other.ptr && ts_subtree_has_external_tokens(other) && !other.ptr->child_count) { - state2 = &other.ptr->external_scanner_state; - } - return ts_external_scanner_state_eq(state1, state2); -} diff --git a/src/tree_sitter/subtree.h b/src/tree_sitter/subtree.h deleted file mode 100644 index 18c48dcbd0..0000000000 --- a/src/tree_sitter/subtree.h +++ /dev/null @@ -1,285 +0,0 @@ -#ifndef TREE_SITTER_SUBTREE_H_ -#define TREE_SITTER_SUBTREE_H_ - -#ifdef __cplusplus -extern "C" { -#endif - -#include <limits.h> -#include <stdbool.h> -#include <stdio.h> -#include "./length.h" -#include "./array.h" -#include "./error_costs.h" -#include "tree_sitter/api.h" -#include "tree_sitter/parser.h" - -static const TSStateId TS_TREE_STATE_NONE = USHRT_MAX; -#define NULL_SUBTREE ((Subtree) {.ptr = NULL}) - -typedef union Subtree Subtree; -typedef union MutableSubtree MutableSubtree; - -typedef struct { - union { - char *long_data; - char short_data[24]; - }; - uint32_t length; -} ExternalScannerState; - -typedef struct { - bool is_inline : 1; - bool visible : 1; - bool named : 1; - bool extra : 1; - bool has_changes : 1; - bool is_missing : 1; - bool is_keyword : 1; - uint8_t symbol; - uint8_t padding_bytes; - uint8_t size_bytes; - uint8_t padding_columns; - uint8_t padding_rows : 4; - uint8_t lookahead_bytes : 4; - uint16_t parse_state; -} SubtreeInlineData; - -typedef struct { - volatile uint32_t ref_count; - Length padding; - Length size; - uint32_t lookahead_bytes; - uint32_t error_cost; - uint32_t child_count; - TSSymbol symbol; - TSStateId parse_state; - - bool visible : 1; - bool named : 1; - bool extra : 1; - bool fragile_left : 1; - bool fragile_right : 1; - bool has_changes : 1; - bool has_external_tokens : 1; - bool is_missing : 1; - bool is_keyword : 1; - - union { - // Non-terminal subtrees (`child_count > 0`) - struct { - Subtree *children; - uint32_t visible_child_count; - uint32_t named_child_count; - uint32_t node_count; - uint32_t repeat_depth; - int32_t dynamic_precedence; - uint16_t production_id; - struct { - TSSymbol symbol; - TSStateId parse_state; - } first_leaf; - }; - - // External terminal subtrees (`child_count == 0 && has_external_tokens`) - ExternalScannerState external_scanner_state; - - // Error terminal subtrees (`child_count == 0 && symbol == ts_builtin_sym_error`) - int32_t lookahead_char; - }; -} SubtreeHeapData; - -union Subtree { - SubtreeInlineData data; - const SubtreeHeapData *ptr; -}; - -union MutableSubtree { - SubtreeInlineData data; - SubtreeHeapData *ptr; -}; - -typedef Array(Subtree) SubtreeArray; -typedef Array(MutableSubtree) MutableSubtreeArray; - -typedef struct { - MutableSubtreeArray free_trees; - MutableSubtreeArray tree_stack; -} SubtreePool; - -void ts_external_scanner_state_init(ExternalScannerState *, const char *, unsigned); -const char *ts_external_scanner_state_data(const ExternalScannerState *); - -void ts_subtree_array_copy(SubtreeArray, SubtreeArray *); -void ts_subtree_array_delete(SubtreePool *, SubtreeArray *); -SubtreeArray ts_subtree_array_remove_trailing_extras(SubtreeArray *); -void ts_subtree_array_reverse(SubtreeArray *); - -SubtreePool ts_subtree_pool_new(uint32_t capacity); -void ts_subtree_pool_delete(SubtreePool *); - -Subtree ts_subtree_new_leaf( - SubtreePool *, TSSymbol, Length, Length, uint32_t, - TSStateId, bool, bool, const TSLanguage * -); -Subtree ts_subtree_new_error( - SubtreePool *, int32_t, Length, Length, uint32_t, TSStateId, const TSLanguage * -); -MutableSubtree ts_subtree_new_node(SubtreePool *, TSSymbol, SubtreeArray *, unsigned, const TSLanguage *); -Subtree ts_subtree_new_error_node(SubtreePool *, SubtreeArray *, bool, const TSLanguage *); -Subtree ts_subtree_new_missing_leaf(SubtreePool *, TSSymbol, Length, const TSLanguage *); -MutableSubtree ts_subtree_make_mut(SubtreePool *, Subtree); -void ts_subtree_retain(Subtree); -void ts_subtree_release(SubtreePool *, Subtree); -bool ts_subtree_eq(Subtree, Subtree); -int ts_subtree_compare(Subtree, Subtree); -void ts_subtree_set_symbol(MutableSubtree *, TSSymbol, const TSLanguage *); -void ts_subtree_set_children(MutableSubtree, Subtree *, uint32_t, const TSLanguage *); -void ts_subtree_balance(Subtree, SubtreePool *, const TSLanguage *); -Subtree ts_subtree_edit(Subtree, const TSInputEdit *edit, SubtreePool *); -char *ts_subtree_string(Subtree, const TSLanguage *, bool include_all); -void ts_subtree_print_dot_graph(Subtree, const TSLanguage *, FILE *); -Subtree ts_subtree_last_external_token(Subtree); -bool ts_subtree_external_scanner_state_eq(Subtree, Subtree); - -#define SUBTREE_GET(self, name) (self.data.is_inline ? self.data.name : self.ptr->name) - -static inline TSSymbol ts_subtree_symbol(Subtree self) { return SUBTREE_GET(self, symbol); } -static inline bool ts_subtree_visible(Subtree self) { return SUBTREE_GET(self, visible); } -static inline bool ts_subtree_named(Subtree self) { return SUBTREE_GET(self, named); } -static inline bool ts_subtree_extra(Subtree self) { return SUBTREE_GET(self, extra); } -static inline bool ts_subtree_has_changes(Subtree self) { return SUBTREE_GET(self, has_changes); } -static inline bool ts_subtree_missing(Subtree self) { return SUBTREE_GET(self, is_missing); } -static inline bool ts_subtree_is_keyword(Subtree self) { return SUBTREE_GET(self, is_keyword); } -static inline TSStateId ts_subtree_parse_state(Subtree self) { return SUBTREE_GET(self, parse_state); } -static inline uint32_t ts_subtree_lookahead_bytes(Subtree self) { return SUBTREE_GET(self, lookahead_bytes); } - -#undef SUBTREE_GET - -static inline void ts_subtree_set_extra(MutableSubtree *self) { - if (self->data.is_inline) { - self->data.extra = true; - } else { - self->ptr->extra = true; - } -} - -static inline TSSymbol ts_subtree_leaf_symbol(Subtree self) { - if (self.data.is_inline) return self.data.symbol; - if (self.ptr->child_count == 0) return self.ptr->symbol; - return self.ptr->first_leaf.symbol; -} - -static inline TSStateId ts_subtree_leaf_parse_state(Subtree self) { - if (self.data.is_inline) return self.data.parse_state; - if (self.ptr->child_count == 0) return self.ptr->parse_state; - return self.ptr->first_leaf.parse_state; -} - -static inline Length ts_subtree_padding(Subtree self) { - if (self.data.is_inline) { - Length result = {self.data.padding_bytes, {self.data.padding_rows, self.data.padding_columns}}; - return result; - } else { - return self.ptr->padding; - } -} - -static inline Length ts_subtree_size(Subtree self) { - if (self.data.is_inline) { - Length result = {self.data.size_bytes, {0, self.data.size_bytes}}; - return result; - } else { - return self.ptr->size; - } -} - -static inline Length ts_subtree_total_size(Subtree self) { - return length_add(ts_subtree_padding(self), ts_subtree_size(self)); -} - -static inline uint32_t ts_subtree_total_bytes(Subtree self) { - return ts_subtree_total_size(self).bytes; -} - -static inline uint32_t ts_subtree_child_count(Subtree self) { - return self.data.is_inline ? 0 : self.ptr->child_count; -} - -static inline uint32_t ts_subtree_repeat_depth(Subtree self) { - return self.data.is_inline ? 0 : self.ptr->repeat_depth; -} - -static inline uint32_t ts_subtree_node_count(Subtree self) { - return (self.data.is_inline || self.ptr->child_count == 0) ? 1 : self.ptr->node_count; -} - -static inline uint32_t ts_subtree_visible_child_count(Subtree self) { - if (ts_subtree_child_count(self) > 0) { - return self.ptr->visible_child_count; - } else { - return 0; - } -} - -static inline uint32_t ts_subtree_error_cost(Subtree self) { - if (ts_subtree_missing(self)) { - return ERROR_COST_PER_MISSING_TREE + ERROR_COST_PER_RECOVERY; - } else { - return self.data.is_inline ? 0 : self.ptr->error_cost; - } -} - -static inline int32_t ts_subtree_dynamic_precedence(Subtree self) { - return (self.data.is_inline || self.ptr->child_count == 0) ? 0 : self.ptr->dynamic_precedence; -} - -static inline uint16_t ts_subtree_production_id(Subtree self) { - if (ts_subtree_child_count(self) > 0) { - return self.ptr->production_id; - } else { - return 0; - } -} - -static inline bool ts_subtree_fragile_left(Subtree self) { - return self.data.is_inline ? false : self.ptr->fragile_left; -} - -static inline bool ts_subtree_fragile_right(Subtree self) { - return self.data.is_inline ? false : self.ptr->fragile_right; -} - -static inline bool ts_subtree_has_external_tokens(Subtree self) { - return self.data.is_inline ? false : self.ptr->has_external_tokens; -} - -static inline bool ts_subtree_is_fragile(Subtree self) { - return self.data.is_inline ? false : (self.ptr->fragile_left || self.ptr->fragile_right); -} - -static inline bool ts_subtree_is_error(Subtree self) { - return ts_subtree_symbol(self) == ts_builtin_sym_error; -} - -static inline bool ts_subtree_is_eof(Subtree self) { - return ts_subtree_symbol(self) == ts_builtin_sym_end; -} - -static inline Subtree ts_subtree_from_mut(MutableSubtree self) { - Subtree result; - result.data = self.data; - return result; -} - -static inline MutableSubtree ts_subtree_to_mut_unsafe(Subtree self) { - MutableSubtree result; - result.data = self.data; - return result; -} - -#ifdef __cplusplus -} -#endif - -#endif // TREE_SITTER_SUBTREE_H_ diff --git a/src/tree_sitter/tree.c b/src/tree_sitter/tree.c deleted file mode 100644 index 391fa7f592..0000000000 --- a/src/tree_sitter/tree.c +++ /dev/null @@ -1,148 +0,0 @@ -#include "tree_sitter/api.h" -#include "./array.h" -#include "./get_changed_ranges.h" -#include "./subtree.h" -#include "./tree_cursor.h" -#include "./tree.h" - -static const unsigned PARENT_CACHE_CAPACITY = 32; - -TSTree *ts_tree_new( - Subtree root, const TSLanguage *language, - const TSRange *included_ranges, unsigned included_range_count -) { - TSTree *result = ts_malloc(sizeof(TSTree)); - result->root = root; - result->language = language; - result->parent_cache = NULL; - result->parent_cache_start = 0; - result->parent_cache_size = 0; - result->included_ranges = ts_calloc(included_range_count, sizeof(TSRange)); - memcpy(result->included_ranges, included_ranges, included_range_count * sizeof(TSRange)); - result->included_range_count = included_range_count; - return result; -} - -TSTree *ts_tree_copy(const TSTree *self) { - ts_subtree_retain(self->root); - return ts_tree_new(self->root, self->language, self->included_ranges, self->included_range_count); -} - -void ts_tree_delete(TSTree *self) { - if (!self) return; - - SubtreePool pool = ts_subtree_pool_new(0); - ts_subtree_release(&pool, self->root); - ts_subtree_pool_delete(&pool); - ts_free(self->included_ranges); - if (self->parent_cache) ts_free(self->parent_cache); - ts_free(self); -} - -TSNode ts_tree_root_node(const TSTree *self) { - return ts_node_new(self, &self->root, ts_subtree_padding(self->root), 0); -} - -const TSLanguage *ts_tree_language(const TSTree *self) { - return self->language; -} - -void ts_tree_edit(TSTree *self, const TSInputEdit *edit) { - for (unsigned i = 0; i < self->included_range_count; i++) { - TSRange *range = &self->included_ranges[i]; - if (range->end_byte >= edit->old_end_byte) { - if (range->end_byte != UINT32_MAX) { - range->end_byte = edit->new_end_byte + (range->end_byte - edit->old_end_byte); - range->end_point = point_add( - edit->new_end_point, - point_sub(range->end_point, edit->old_end_point) - ); - if (range->end_byte < edit->new_end_byte) { - range->end_byte = UINT32_MAX; - range->end_point = POINT_MAX; - } - } - if (range->start_byte >= edit->old_end_byte) { - range->start_byte = edit->new_end_byte + (range->start_byte - edit->old_end_byte); - range->start_point = point_add( - edit->new_end_point, - point_sub(range->start_point, edit->old_end_point) - ); - if (range->start_byte < edit->new_end_byte) { - range->start_byte = UINT32_MAX; - range->start_point = POINT_MAX; - } - } - } - } - - SubtreePool pool = ts_subtree_pool_new(0); - self->root = ts_subtree_edit(self->root, edit, &pool); - self->parent_cache_start = 0; - self->parent_cache_size = 0; - ts_subtree_pool_delete(&pool); -} - -TSRange *ts_tree_get_changed_ranges(const TSTree *self, const TSTree *other, uint32_t *count) { - TreeCursor cursor1 = {NULL, array_new()}; - TreeCursor cursor2 = {NULL, array_new()}; - ts_tree_cursor_init(&cursor1, ts_tree_root_node(self)); - ts_tree_cursor_init(&cursor2, ts_tree_root_node(other)); - - TSRangeArray included_range_differences = array_new(); - ts_range_array_get_changed_ranges( - self->included_ranges, self->included_range_count, - other->included_ranges, other->included_range_count, - &included_range_differences - ); - - TSRange *result; - *count = ts_subtree_get_changed_ranges( - &self->root, &other->root, &cursor1, &cursor2, - self->language, &included_range_differences, &result - ); - - array_delete(&included_range_differences); - array_delete(&cursor1.stack); - array_delete(&cursor2.stack); - return result; -} - -void ts_tree_print_dot_graph(const TSTree *self, FILE *file) { - ts_subtree_print_dot_graph(self->root, self->language, file); -} - -TSNode ts_tree_get_cached_parent(const TSTree *self, const TSNode *node) { - for (uint32_t i = 0; i < self->parent_cache_size; i++) { - uint32_t index = (self->parent_cache_start + i) % PARENT_CACHE_CAPACITY; - ParentCacheEntry *entry = &self->parent_cache[index]; - if (entry->child == node->id) { - return ts_node_new(self, entry->parent, entry->position, entry->alias_symbol); - } - } - return ts_node_new(NULL, NULL, length_zero(), 0); -} - -void ts_tree_set_cached_parent(const TSTree *_self, const TSNode *node, const TSNode *parent) { - TSTree *self = (TSTree *)_self; - if (!self->parent_cache) { - self->parent_cache = ts_calloc(PARENT_CACHE_CAPACITY, sizeof(ParentCacheEntry)); - } - - uint32_t index = (self->parent_cache_start + self->parent_cache_size) % PARENT_CACHE_CAPACITY; - self->parent_cache[index] = (ParentCacheEntry) { - .child = node->id, - .parent = (const Subtree *)parent->id, - .position = { - parent->context[0], - {parent->context[1], parent->context[2]} - }, - .alias_symbol = parent->context[3], - }; - - if (self->parent_cache_size == PARENT_CACHE_CAPACITY) { - self->parent_cache_start++; - } else { - self->parent_cache_size++; - } -} diff --git a/src/tree_sitter/tree.h b/src/tree_sitter/tree.h deleted file mode 100644 index 92a7e64179..0000000000 --- a/src/tree_sitter/tree.h +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef TREE_SITTER_TREE_H_ -#define TREE_SITTER_TREE_H_ - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct { - const Subtree *child; - const Subtree *parent; - Length position; - TSSymbol alias_symbol; -} ParentCacheEntry; - -struct TSTree { - Subtree root; - const TSLanguage *language; - ParentCacheEntry *parent_cache; - uint32_t parent_cache_start; - uint32_t parent_cache_size; - TSRange *included_ranges; - unsigned included_range_count; -}; - -TSTree *ts_tree_new(Subtree root, const TSLanguage *language, const TSRange *, unsigned); -TSNode ts_node_new(const TSTree *, const Subtree *, Length, TSSymbol); -TSNode ts_tree_get_cached_parent(const TSTree *, const TSNode *); -void ts_tree_set_cached_parent(const TSTree *, const TSNode *, const TSNode *); - -#ifdef __cplusplus -} -#endif - -#endif // TREE_SITTER_TREE_H_ diff --git a/src/tree_sitter/tree_cursor.c b/src/tree_sitter/tree_cursor.c deleted file mode 100644 index 00b9679d73..0000000000 --- a/src/tree_sitter/tree_cursor.c +++ /dev/null @@ -1,367 +0,0 @@ -#include "tree_sitter/api.h" -#include "./alloc.h" -#include "./tree_cursor.h" -#include "./language.h" -#include "./tree.h" - -typedef struct { - Subtree parent; - const TSTree *tree; - Length position; - uint32_t child_index; - uint32_t structural_child_index; - const TSSymbol *alias_sequence; -} CursorChildIterator; - -// CursorChildIterator - -static inline CursorChildIterator ts_tree_cursor_iterate_children(const TreeCursor *self) { - TreeCursorEntry *last_entry = array_back(&self->stack); - if (ts_subtree_child_count(*last_entry->subtree) == 0) { - return (CursorChildIterator) {NULL_SUBTREE, self->tree, length_zero(), 0, 0, NULL}; - } - const TSSymbol *alias_sequence = ts_language_alias_sequence( - self->tree->language, - last_entry->subtree->ptr->production_id - ); - return (CursorChildIterator) { - .tree = self->tree, - .parent = *last_entry->subtree, - .position = last_entry->position, - .child_index = 0, - .structural_child_index = 0, - .alias_sequence = alias_sequence, - }; -} - -static inline bool ts_tree_cursor_child_iterator_next(CursorChildIterator *self, - TreeCursorEntry *result, - bool *visible) { - if (!self->parent.ptr || self->child_index == self->parent.ptr->child_count) return false; - const Subtree *child = &self->parent.ptr->children[self->child_index]; - *result = (TreeCursorEntry) { - .subtree = child, - .position = self->position, - .child_index = self->child_index, - .structural_child_index = self->structural_child_index, - }; - *visible = ts_subtree_visible(*child); - bool extra = ts_subtree_extra(*child); - if (!extra && self->alias_sequence) { - *visible |= self->alias_sequence[self->structural_child_index]; - self->structural_child_index++; - } - - self->position = length_add(self->position, ts_subtree_size(*child)); - self->child_index++; - - if (self->child_index < self->parent.ptr->child_count) { - Subtree next_child = self->parent.ptr->children[self->child_index]; - self->position = length_add(self->position, ts_subtree_padding(next_child)); - } - - return true; -} - -// TSTreeCursor - lifecycle - -TSTreeCursor ts_tree_cursor_new(TSNode node) { - TSTreeCursor self = {NULL, NULL, {0, 0}}; - ts_tree_cursor_init((TreeCursor *)&self, node); - return self; -} - -void ts_tree_cursor_reset(TSTreeCursor *_self, TSNode node) { - ts_tree_cursor_init((TreeCursor *)_self, node); -} - -void ts_tree_cursor_init(TreeCursor *self, TSNode node) { - self->tree = node.tree; - array_clear(&self->stack); - array_push(&self->stack, ((TreeCursorEntry) { - .subtree = (const Subtree *)node.id, - .position = { - ts_node_start_byte(node), - ts_node_start_point(node) - }, - .child_index = 0, - .structural_child_index = 0, - })); -} - -void ts_tree_cursor_delete(TSTreeCursor *_self) { - TreeCursor *self = (TreeCursor *)_self; - array_delete(&self->stack); -} - -// TSTreeCursor - walking the tree - -bool ts_tree_cursor_goto_first_child(TSTreeCursor *_self) { - TreeCursor *self = (TreeCursor *)_self; - - bool did_descend; - do { - did_descend = false; - - bool visible; - TreeCursorEntry entry; - CursorChildIterator iterator = ts_tree_cursor_iterate_children(self); - while (ts_tree_cursor_child_iterator_next(&iterator, &entry, &visible)) { - if (visible) { - array_push(&self->stack, entry); - return true; - } - - if (ts_subtree_visible_child_count(*entry.subtree) > 0) { - array_push(&self->stack, entry); - did_descend = true; - break; - } - } - } while (did_descend); - - return false; -} - -int64_t ts_tree_cursor_goto_first_child_for_byte(TSTreeCursor *_self, uint32_t goal_byte) { - TreeCursor *self = (TreeCursor *)_self; - uint32_t initial_size = self->stack.size; - uint32_t visible_child_index = 0; - - bool did_descend; - do { - did_descend = false; - - bool visible; - TreeCursorEntry entry; - CursorChildIterator iterator = ts_tree_cursor_iterate_children(self); - while (ts_tree_cursor_child_iterator_next(&iterator, &entry, &visible)) { - uint32_t end_byte = entry.position.bytes + ts_subtree_size(*entry.subtree).bytes; - bool at_goal = end_byte > goal_byte; - uint32_t visible_child_count = ts_subtree_visible_child_count(*entry.subtree); - - if (at_goal) { - if (visible) { - array_push(&self->stack, entry); - return visible_child_index; - } - - if (visible_child_count > 0) { - array_push(&self->stack, entry); - did_descend = true; - break; - } - } else if (visible) { - visible_child_index++; - } else { - visible_child_index += visible_child_count; - } - } - } while (did_descend); - - if (self->stack.size > initial_size && - ts_tree_cursor_goto_next_sibling((TSTreeCursor *)self)) { - return visible_child_index; - } - - self->stack.size = initial_size; - return -1; -} - -bool ts_tree_cursor_goto_next_sibling(TSTreeCursor *_self) { - TreeCursor *self = (TreeCursor *)_self; - uint32_t initial_size = self->stack.size; - - while (self->stack.size > 1) { - TreeCursorEntry entry = array_pop(&self->stack); - CursorChildIterator iterator = ts_tree_cursor_iterate_children(self); - iterator.child_index = entry.child_index; - iterator.structural_child_index = entry.structural_child_index; - iterator.position = entry.position; - - bool visible = false; - ts_tree_cursor_child_iterator_next(&iterator, &entry, &visible); - if (visible && self->stack.size + 1 < initial_size) break; - - while (ts_tree_cursor_child_iterator_next(&iterator, &entry, &visible)) { - if (visible) { - array_push(&self->stack, entry); - return true; - } - - if (ts_subtree_visible_child_count(*entry.subtree)) { - array_push(&self->stack, entry); - ts_tree_cursor_goto_first_child(_self); - return true; - } - } - } - - self->stack.size = initial_size; - return false; -} - -bool ts_tree_cursor_goto_parent(TSTreeCursor *_self) { - TreeCursor *self = (TreeCursor *)_self; - for (unsigned i = self->stack.size - 2; i + 1 > 0; i--) { - TreeCursorEntry *entry = &self->stack.contents[i]; - bool is_aliased = false; - if (i > 0) { - TreeCursorEntry *parent_entry = &self->stack.contents[i - 1]; - const TSSymbol *alias_sequence = ts_language_alias_sequence( - self->tree->language, - parent_entry->subtree->ptr->production_id - ); - is_aliased = alias_sequence && alias_sequence[entry->structural_child_index]; - } - if (ts_subtree_visible(*entry->subtree) || is_aliased) { - self->stack.size = i + 1; - return true; - } - } - return false; -} - -TSNode ts_tree_cursor_current_node(const TSTreeCursor *_self) { - const TreeCursor *self = (const TreeCursor *)_self; - TreeCursorEntry *last_entry = array_back(&self->stack); - TSSymbol alias_symbol = 0; - if (self->stack.size > 1) { - TreeCursorEntry *parent_entry = &self->stack.contents[self->stack.size - 2]; - const TSSymbol *alias_sequence = ts_language_alias_sequence( - self->tree->language, - parent_entry->subtree->ptr->production_id - ); - if (alias_sequence && !ts_subtree_extra(*last_entry->subtree)) { - alias_symbol = alias_sequence[last_entry->structural_child_index]; - } - } - return ts_node_new( - self->tree, - last_entry->subtree, - last_entry->position, - alias_symbol - ); -} - -TSFieldId ts_tree_cursor_current_status( - const TSTreeCursor *_self, - bool *can_have_later_siblings, - bool *can_have_later_siblings_with_this_field -) { - const TreeCursor *self = (const TreeCursor *)_self; - TSFieldId result = 0; - *can_have_later_siblings = false; - *can_have_later_siblings_with_this_field = false; - - // Walk up the tree, visiting the current node and its invisible ancestors, - // because fields can refer to nodes through invisible *wrapper* nodes, - for (unsigned i = self->stack.size - 1; i > 0; i--) { - TreeCursorEntry *entry = &self->stack.contents[i]; - TreeCursorEntry *parent_entry = &self->stack.contents[i - 1]; - - // Stop walking up when a visible ancestor is found. - if (i != self->stack.size - 1) { - if (ts_subtree_visible(*entry->subtree)) break; - const TSSymbol *alias_sequence = ts_language_alias_sequence( - self->tree->language, - parent_entry->subtree->ptr->production_id - ); - if (alias_sequence && alias_sequence[entry->structural_child_index]) { - break; - } - } - - if (ts_subtree_child_count(*parent_entry->subtree) > entry->child_index + 1) { - *can_have_later_siblings = true; - } - - if (ts_subtree_extra(*entry->subtree)) break; - - const TSFieldMapEntry *field_map, *field_map_end; - ts_language_field_map( - self->tree->language, - parent_entry->subtree->ptr->production_id, - &field_map, &field_map_end - ); - - // Look for a field name associated with the current node. - if (!result) { - for (const TSFieldMapEntry *i = field_map; i < field_map_end; i++) { - if (!i->inherited && i->child_index == entry->structural_child_index) { - result = i->field_id; - *can_have_later_siblings_with_this_field = false; - break; - } - } - } - - // Determine if there other later siblings with the same field name. - if (result) { - for (const TSFieldMapEntry *i = field_map; i < field_map_end; i++) { - if (i->field_id == result && i->child_index > entry->structural_child_index) { - *can_have_later_siblings_with_this_field = true; - break; - } - } - } - } - - return result; -} - -TSFieldId ts_tree_cursor_current_field_id(const TSTreeCursor *_self) { - const TreeCursor *self = (const TreeCursor *)_self; - - // Walk up the tree, visiting the current node and its invisible ancestors. - for (unsigned i = self->stack.size - 1; i > 0; i--) { - TreeCursorEntry *entry = &self->stack.contents[i]; - TreeCursorEntry *parent_entry = &self->stack.contents[i - 1]; - - // Stop walking up when another visible node is found. - if (i != self->stack.size - 1) { - if (ts_subtree_visible(*entry->subtree)) break; - const TSSymbol *alias_sequence = ts_language_alias_sequence( - self->tree->language, - parent_entry->subtree->ptr->production_id - ); - if (alias_sequence && alias_sequence[entry->structural_child_index]) { - break; - } - } - - if (ts_subtree_extra(*entry->subtree)) break; - - const TSFieldMapEntry *field_map, *field_map_end; - ts_language_field_map( - self->tree->language, - parent_entry->subtree->ptr->production_id, - &field_map, &field_map_end - ); - for (const TSFieldMapEntry *i = field_map; i < field_map_end; i++) { - if (!i->inherited && i->child_index == entry->structural_child_index) { - return i->field_id; - } - } - } - return 0; -} - -const char *ts_tree_cursor_current_field_name(const TSTreeCursor *_self) { - TSFieldId id = ts_tree_cursor_current_field_id(_self); - if (id) { - const TreeCursor *self = (const TreeCursor *)_self; - return self->tree->language->field_names[id]; - } else { - return NULL; - } -} - -TSTreeCursor ts_tree_cursor_copy(const TSTreeCursor *_cursor) { - const TreeCursor *cursor = (const TreeCursor *)_cursor; - TSTreeCursor res = {NULL, NULL, {0, 0}}; - TreeCursor *copy = (TreeCursor *)&res; - copy->tree = cursor->tree; - array_push_all(©->stack, &cursor->stack); - return res; -} diff --git a/src/tree_sitter/tree_cursor.h b/src/tree_sitter/tree_cursor.h deleted file mode 100644 index 5a39dd278c..0000000000 --- a/src/tree_sitter/tree_cursor.h +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef TREE_SITTER_TREE_CURSOR_H_ -#define TREE_SITTER_TREE_CURSOR_H_ - -#include "./subtree.h" - -typedef struct { - const Subtree *subtree; - Length position; - uint32_t child_index; - uint32_t structural_child_index; -} TreeCursorEntry; - -typedef struct { - const TSTree *tree; - Array(TreeCursorEntry) stack; -} TreeCursor; - -void ts_tree_cursor_init(TreeCursor *, TSNode); -TSFieldId ts_tree_cursor_current_status(const TSTreeCursor *, bool *, bool *); - -#endif // TREE_SITTER_TREE_CURSOR_H_ diff --git a/src/tree_sitter/treesitter_commit_hash.txt b/src/tree_sitter/treesitter_commit_hash.txt deleted file mode 100644 index bd7fcfbe76..0000000000 --- a/src/tree_sitter/treesitter_commit_hash.txt +++ /dev/null @@ -1 +0,0 @@ -81d533d2d1b580fdb507accabc91ceddffb5b6f0 diff --git a/src/tree_sitter/unicode.h b/src/tree_sitter/unicode.h deleted file mode 100644 index 2ab51c2a3a..0000000000 --- a/src/tree_sitter/unicode.h +++ /dev/null @@ -1,50 +0,0 @@ -#ifndef TREE_SITTER_UNICODE_H_ -#define TREE_SITTER_UNICODE_H_ - -#ifdef __cplusplus -extern "C" { -#endif - -#include <limits.h> -#include <stdint.h> - -#define U_EXPORT -#define U_EXPORT2 -#include "./unicode/utf8.h" -#include "./unicode/utf16.h" - -static const int32_t TS_DECODE_ERROR = U_SENTINEL; - -// These functions read one unicode code point from the given string, -// returning the number of bytes consumed. -typedef uint32_t (*UnicodeDecodeFunction)( - const uint8_t *string, - uint32_t length, - int32_t *code_point -); - -static inline uint32_t ts_decode_utf8( - const uint8_t *string, - uint32_t length, - int32_t *code_point -) { - uint32_t i = 0; - U8_NEXT(string, i, length, *code_point); - return i; -} - -static inline uint32_t ts_decode_utf16( - const uint8_t *string, - uint32_t length, - int32_t *code_point -) { - uint32_t i = 0; - U16_NEXT(((uint16_t *)string), i, length, *code_point); - return i * 2; -} - -#ifdef __cplusplus -} -#endif - -#endif // TREE_SITTER_UNICODE_H_ diff --git a/src/tree_sitter/unicode/ICU_SHA b/src/tree_sitter/unicode/ICU_SHA deleted file mode 100644 index 3622283ba3..0000000000 --- a/src/tree_sitter/unicode/ICU_SHA +++ /dev/null @@ -1 +0,0 @@ -552b01f61127d30d6589aa4bf99468224979b661 diff --git a/src/tree_sitter/unicode/LICENSE b/src/tree_sitter/unicode/LICENSE deleted file mode 100644 index 2e01e36876..0000000000 --- a/src/tree_sitter/unicode/LICENSE +++ /dev/null @@ -1,414 +0,0 @@ -COPYRIGHT AND PERMISSION NOTICE (ICU 58 and later) - -Copyright ยฉ 1991-2019 Unicode, Inc. All rights reserved. -Distributed under the Terms of Use in https://www.unicode.org/copyright.html. - -Permission is hereby granted, free of charge, to any person obtaining -a copy of the Unicode data files and any associated documentation -(the "Data Files") or Unicode software and any associated documentation -(the "Software") to deal in the Data Files or Software -without restriction, including without limitation the rights to use, -copy, modify, merge, publish, distribute, and/or sell copies of -the Data Files or Software, and to permit persons to whom the Data Files -or Software are furnished to do so, provided that either -(a) this copyright and permission notice appear with all copies -of the Data Files or Software, or -(b) this copyright and permission notice appear in associated -Documentation. - -THE DATA FILES AND SOFTWARE ARE 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 OF THIRD PARTY RIGHTS. -IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS -NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL -DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, -DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER -TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -PERFORMANCE OF THE DATA FILES OR SOFTWARE. - -Except as contained in this notice, the name of a copyright holder -shall not be used in advertising or otherwise to promote the sale, -use or other dealings in these Data Files or Software without prior -written authorization of the copyright holder. - ---------------------- - -Third-Party Software Licenses - -This section contains third-party software notices and/or additional -terms for licensed third-party software components included within ICU -libraries. - -1. ICU License - ICU 1.8.1 to ICU 57.1 - -COPYRIGHT AND PERMISSION NOTICE - -Copyright (c) 1995-2016 International Business Machines Corporation and others -All rights reserved. - -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, and/or sell copies of the Software, and to permit persons -to whom the Software is furnished to do so, provided that the above -copyright notice(s) and this permission notice appear in all copies of -the Software and that both the above copyright notice(s) and this -permission notice appear in supporting documentation. - -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 -OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR -HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY -SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER -RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF -CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN -CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - -Except as contained in this notice, the name of a copyright holder -shall not be used in advertising or otherwise to promote the sale, use -or other dealings in this Software without prior written authorization -of the copyright holder. - -All trademarks and registered trademarks mentioned herein are the -property of their respective owners. - -2. Chinese/Japanese Word Break Dictionary Data (cjdict.txt) - - # The Google Chrome software developed by Google is licensed under - # the BSD license. Other software included in this distribution is - # provided under other licenses, as set forth below. - # - # The BSD License - # http://opensource.org/licenses/bsd-license.php - # Copyright (C) 2006-2008, Google Inc. - # - # All rights reserved. - # - # Redistribution and use in source and binary forms, with or without - # modification, are permitted provided that the following conditions are met: - # - # Redistributions of source code must retain the above copyright notice, - # this list of conditions and the following disclaimer. - # Redistributions in binary form must reproduce the above - # copyright notice, this list of conditions and the following - # disclaimer in the documentation and/or other materials provided with - # the distribution. - # Neither the name of Google Inc. nor the names of its - # contributors may be used to endorse or promote products derived from - # this software without specific prior written permission. - # - # - # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - # CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR - # BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - # - # - # The word list in cjdict.txt are generated by combining three word lists - # listed below with further processing for compound word breaking. The - # frequency is generated with an iterative training against Google web - # corpora. - # - # * Libtabe (Chinese) - # - https://sourceforge.net/project/?group_id=1519 - # - Its license terms and conditions are shown below. - # - # * IPADIC (Japanese) - # - http://chasen.aist-nara.ac.jp/chasen/distribution.html - # - Its license terms and conditions are shown below. - # - # ---------COPYING.libtabe ---- BEGIN-------------------- - # - # /* - # * Copyright (c) 1999 TaBE Project. - # * Copyright (c) 1999 Pai-Hsiang Hsiao. - # * All rights reserved. - # * - # * Redistribution and use in source and binary forms, with or without - # * modification, are permitted provided that the following conditions - # * are met: - # * - # * . Redistributions of source code must retain the above copyright - # * notice, this list of conditions and the following disclaimer. - # * . Redistributions in binary form must reproduce the above copyright - # * notice, this list of conditions and the following disclaimer in - # * the documentation and/or other materials provided with the - # * distribution. - # * . Neither the name of the TaBE Project nor the names of its - # * contributors may be used to endorse or promote products derived - # * from this software without specific prior written permission. - # * - # * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - # * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - # * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - # * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - # * REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, - # * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - # * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - # * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - # * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - # * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - # * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED - # * OF THE POSSIBILITY OF SUCH DAMAGE. - # */ - # - # /* - # * Copyright (c) 1999 Computer Systems and Communication Lab, - # * Institute of Information Science, Academia - # * Sinica. All rights reserved. - # * - # * Redistribution and use in source and binary forms, with or without - # * modification, are permitted provided that the following conditions - # * are met: - # * - # * . Redistributions of source code must retain the above copyright - # * notice, this list of conditions and the following disclaimer. - # * . Redistributions in binary form must reproduce the above copyright - # * notice, this list of conditions and the following disclaimer in - # * the documentation and/or other materials provided with the - # * distribution. - # * . Neither the name of the Computer Systems and Communication Lab - # * nor the names of its contributors may be used to endorse or - # * promote products derived from this software without specific - # * prior written permission. - # * - # * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - # * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - # * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - # * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - # * REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, - # * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - # * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - # * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - # * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - # * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - # * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED - # * OF THE POSSIBILITY OF SUCH DAMAGE. - # */ - # - # Copyright 1996 Chih-Hao Tsai @ Beckman Institute, - # University of Illinois - # c-tsai4@uiuc.edu http://casper.beckman.uiuc.edu/~c-tsai4 - # - # ---------------COPYING.libtabe-----END-------------------------------- - # - # - # ---------------COPYING.ipadic-----BEGIN------------------------------- - # - # Copyright 2000, 2001, 2002, 2003 Nara Institute of Science - # and Technology. All Rights Reserved. - # - # Use, reproduction, and distribution of this software is permitted. - # Any copy of this software, whether in its original form or modified, - # must include both the above copyright notice and the following - # paragraphs. - # - # Nara Institute of Science and Technology (NAIST), - # the copyright holders, disclaims all warranties with regard to this - # software, including all implied warranties of merchantability and - # fitness, in no event shall NAIST be liable for - # any special, indirect or consequential damages or any damages - # whatsoever resulting from loss of use, data or profits, whether in an - # action of contract, negligence or other tortuous action, arising out - # of or in connection with the use or performance of this software. - # - # A large portion of the dictionary entries - # originate from ICOT Free Software. The following conditions for ICOT - # Free Software applies to the current dictionary as well. - # - # Each User may also freely distribute the Program, whether in its - # original form or modified, to any third party or parties, PROVIDED - # that the provisions of Section 3 ("NO WARRANTY") will ALWAYS appear - # on, or be attached to, the Program, which is distributed substantially - # in the same form as set out herein and that such intended - # distribution, if actually made, will neither violate or otherwise - # contravene any of the laws and regulations of the countries having - # jurisdiction over the User or the intended distribution itself. - # - # NO WARRANTY - # - # The program was produced on an experimental basis in the course of the - # research and development conducted during the project and is provided - # to users as so produced on an experimental basis. Accordingly, the - # program is provided without any warranty whatsoever, whether express, - # implied, statutory or otherwise. The term "warranty" used herein - # includes, but is not limited to, any warranty of the quality, - # performance, merchantability and fitness for a particular purpose of - # the program and the nonexistence of any infringement or violation of - # any right of any third party. - # - # Each user of the program will agree and understand, and be deemed to - # have agreed and understood, that there is no warranty whatsoever for - # the program and, accordingly, the entire risk arising from or - # otherwise connected with the program is assumed by the user. - # - # Therefore, neither ICOT, the copyright holder, or any other - # organization that participated in or was otherwise related to the - # development of the program and their respective officials, directors, - # officers and other employees shall be held liable for any and all - # damages, including, without limitation, general, special, incidental - # and consequential damages, arising out of or otherwise in connection - # with the use or inability to use the program or any product, material - # or result produced or otherwise obtained by using the program, - # regardless of whether they have been advised of, or otherwise had - # knowledge of, the possibility of such damages at any time during the - # project or thereafter. Each user will be deemed to have agreed to the - # foregoing by his or her commencement of use of the program. The term - # "use" as used herein includes, but is not limited to, the use, - # modification, copying and distribution of the program and the - # production of secondary products from the program. - # - # In the case where the program, whether in its original form or - # modified, was distributed or delivered to or received by a user from - # any person, organization or entity other than ICOT, unless it makes or - # grants independently of ICOT any specific warranty to the user in - # writing, such person, organization or entity, will also be exempted - # from and not be held liable to the user for any such damages as noted - # above as far as the program is concerned. - # - # ---------------COPYING.ipadic-----END---------------------------------- - -3. Lao Word Break Dictionary Data (laodict.txt) - - # Copyright (c) 2013 International Business Machines Corporation - # and others. All Rights Reserved. - # - # Project: http://code.google.com/p/lao-dictionary/ - # Dictionary: http://lao-dictionary.googlecode.com/git/Lao-Dictionary.txt - # License: http://lao-dictionary.googlecode.com/git/Lao-Dictionary-LICENSE.txt - # (copied below) - # - # This file is derived from the above dictionary, with slight - # modifications. - # ---------------------------------------------------------------------- - # Copyright (C) 2013 Brian Eugene Wilson, Robert Martin Campbell. - # All rights reserved. - # - # Redistribution and use in source and binary forms, with or without - # modification, - # are permitted provided that the following conditions are met: - # - # - # Redistributions of source code must retain the above copyright notice, this - # list of conditions and the following disclaimer. Redistributions in - # binary form must reproduce the above copyright notice, this list of - # conditions and the following disclaimer in the documentation and/or - # other materials provided with the distribution. - # - # - # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, - # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED - # OF THE POSSIBILITY OF SUCH DAMAGE. - # -------------------------------------------------------------------------- - -4. Burmese Word Break Dictionary Data (burmesedict.txt) - - # Copyright (c) 2014 International Business Machines Corporation - # and others. All Rights Reserved. - # - # This list is part of a project hosted at: - # github.com/kanyawtech/myanmar-karen-word-lists - # - # -------------------------------------------------------------------------- - # Copyright (c) 2013, LeRoy Benjamin Sharon - # All rights reserved. - # - # Redistribution and use in source and binary forms, with or without - # modification, are permitted provided that the following conditions - # are met: Redistributions of source code must retain the above - # copyright notice, this list of conditions and the following - # disclaimer. Redistributions in binary form must reproduce the - # above copyright notice, this list of conditions and the following - # disclaimer in the documentation and/or other materials provided - # with the distribution. - # - # Neither the name Myanmar Karen Word Lists, nor the names of its - # contributors may be used to endorse or promote products derived - # from this software without specific prior written permission. - # - # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - # CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS - # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR - # TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF - # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - # SUCH DAMAGE. - # -------------------------------------------------------------------------- - -5. Time Zone Database - - ICU uses the public domain data and code derived from Time Zone -Database for its time zone support. The ownership of the TZ database -is explained in BCP 175: Procedure for Maintaining the Time Zone -Database section 7. - - # 7. Database Ownership - # - # The TZ database itself is not an IETF Contribution or an IETF - # document. Rather it is a pre-existing and regularly updated work - # that is in the public domain, and is intended to remain in the - # public domain. Therefore, BCPs 78 [RFC5378] and 79 [RFC3979] do - # not apply to the TZ Database or contributions that individuals make - # to it. Should any claims be made and substantiated against the TZ - # Database, the organization that is providing the IANA - # Considerations defined in this RFC, under the memorandum of - # understanding with the IETF, currently ICANN, may act in accordance - # with all competent court orders. No ownership claims will be made - # by ICANN or the IETF Trust on the database or the code. Any person - # making a contribution to the database or code waives all rights to - # future claims in that contribution or in the TZ Database. - -6. Google double-conversion - -Copyright 2006-2011, the V8 project authors. All rights reserved. -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - * Neither the name of Google Inc. nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/tree_sitter/unicode/README.md b/src/tree_sitter/unicode/README.md deleted file mode 100644 index 623b8e3843..0000000000 --- a/src/tree_sitter/unicode/README.md +++ /dev/null @@ -1,29 +0,0 @@ -# ICU Parts - -This directory contains a small subset of files from the Unicode organization's [ICU repository](https://github.com/unicode-org/icu). - -### License - -The license for these files is contained in the `LICENSE` file within this directory. - -### Contents - -* Source files taken from the [`icu4c/source/common/unicode`](https://github.com/unicode-org/icu/tree/552b01f61127d30d6589aa4bf99468224979b661/icu4c/source/common/unicode) directory: - * `utf8.h` - * `utf16.h` - * `umachine.h` -* Empty source files that are referenced by the above source files, but whose original contents in `libicu` are not needed: - * `ptypes.h` - * `urename.h` - * `utf.h` -* `ICU_SHA` - File containing the Git SHA of the commit in the `icu` repository from which the files were obtained. -* `LICENSE` - The license file from the [`icu4c`](https://github.com/unicode-org/icu/tree/552b01f61127d30d6589aa4bf99468224979b661/icu4c) directory of the `icu` repository. -* `README.md` - This text file. - -### Updating ICU - -To incorporate changes from the upstream `icu` repository: - -* Update `ICU_SHA` with the new Git SHA. -* Update `LICENSE` with the license text from the directory mentioned above. -* Update `utf8.h`, `utf16.h`, and `umachine.h` with their new contents in the `icu` repository. diff --git a/src/tree_sitter/unicode/ptypes.h b/src/tree_sitter/unicode/ptypes.h deleted file mode 100644 index ac79ad0f98..0000000000 --- a/src/tree_sitter/unicode/ptypes.h +++ /dev/null @@ -1 +0,0 @@ -// This file must exist in order for `utf8.h` and `utf16.h` to be used. diff --git a/src/tree_sitter/unicode/umachine.h b/src/tree_sitter/unicode/umachine.h deleted file mode 100644 index bbf6ef9c8b..0000000000 --- a/src/tree_sitter/unicode/umachine.h +++ /dev/null @@ -1,448 +0,0 @@ -// ยฉ 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html -/* -****************************************************************************** -* -* Copyright (C) 1999-2015, International Business Machines -* Corporation and others. All Rights Reserved. -* -****************************************************************************** -* file name: umachine.h -* encoding: UTF-8 -* tab size: 8 (not used) -* indentation:4 -* -* created on: 1999sep13 -* created by: Markus W. Scherer -* -* This file defines basic types and constants for ICU to be -* platform-independent. umachine.h and utf.h are included into -* utypes.h to provide all the general definitions for ICU. -* All of these definitions used to be in utypes.h before -* the UTF-handling macros made this unmaintainable. -*/ - -#ifndef __UMACHINE_H__ -#define __UMACHINE_H__ - - -/** - * \file - * \brief Basic types and constants for UTF - * - * <h2> Basic types and constants for UTF </h2> - * This file defines basic types and constants for utf.h to be - * platform-independent. umachine.h and utf.h are included into - * utypes.h to provide all the general definitions for ICU. - * All of these definitions used to be in utypes.h before - * the UTF-handling macros made this unmaintainable. - * - */ -/*==========================================================================*/ -/* Include platform-dependent definitions */ -/* which are contained in the platform-specific file platform.h */ -/*==========================================================================*/ - -#include "./ptypes.h" /* platform.h is included in ptypes.h */ - -/* - * ANSI C headers: - * stddef.h defines wchar_t - */ -#include <stddef.h> - -/*==========================================================================*/ -/* For C wrappers, we use the symbol U_STABLE. */ -/* This works properly if the includer is C or C++. */ -/* Functions are declared U_STABLE return-type U_EXPORT2 function-name()... */ -/*==========================================================================*/ - -/** - * \def U_CFUNC - * This is used in a declaration of a library private ICU C function. - * @stable ICU 2.4 - */ - -/** - * \def U_CDECL_BEGIN - * This is used to begin a declaration of a library private ICU C API. - * @stable ICU 2.4 - */ - -/** - * \def U_CDECL_END - * This is used to end a declaration of a library private ICU C API - * @stable ICU 2.4 - */ - -#ifdef __cplusplus -# define U_CFUNC extern "C" -# define U_CDECL_BEGIN extern "C" { -# define U_CDECL_END } -#else -# define U_CFUNC extern -# define U_CDECL_BEGIN -# define U_CDECL_END -#endif - -#ifndef U_ATTRIBUTE_DEPRECATED -/** - * \def U_ATTRIBUTE_DEPRECATED - * This is used for GCC specific attributes - * @internal - */ -#if U_GCC_MAJOR_MINOR >= 302 -# define U_ATTRIBUTE_DEPRECATED __attribute__ ((deprecated)) -/** - * \def U_ATTRIBUTE_DEPRECATED - * This is used for Visual C++ specific attributes - * @internal - */ -#elif defined(_MSC_VER) && (_MSC_VER >= 1400) -# define U_ATTRIBUTE_DEPRECATED __declspec(deprecated) -#else -# define U_ATTRIBUTE_DEPRECATED -#endif -#endif - -/** This is used to declare a function as a public ICU C API @stable ICU 2.0*/ -#define U_CAPI U_CFUNC U_EXPORT -/** This is used to declare a function as a stable public ICU C API*/ -#define U_STABLE U_CAPI -/** This is used to declare a function as a draft public ICU C API */ -#define U_DRAFT U_CAPI -/** This is used to declare a function as a deprecated public ICU C API */ -#define U_DEPRECATED U_CAPI U_ATTRIBUTE_DEPRECATED -/** This is used to declare a function as an obsolete public ICU C API */ -#define U_OBSOLETE U_CAPI -/** This is used to declare a function as an internal ICU C API */ -#define U_INTERNAL U_CAPI - -/** - * \def U_OVERRIDE - * Defined to the C++11 "override" keyword if available. - * Denotes a class or member which is an override of the base class. - * May result in an error if it applied to something not an override. - * @internal - */ -#ifndef U_OVERRIDE -#define U_OVERRIDE override -#endif - -/** - * \def U_FINAL - * Defined to the C++11 "final" keyword if available. - * Denotes a class or member which may not be overridden in subclasses. - * May result in an error if subclasses attempt to override. - * @internal - */ -#if !defined(U_FINAL) || defined(U_IN_DOXYGEN) -#define U_FINAL final -#endif - -// Before ICU 65, function-like, multi-statement ICU macros were just defined as -// series of statements wrapped in { } blocks and the caller could choose to -// either treat them as if they were actual functions and end the invocation -// with a trailing ; creating an empty statement after the block or else omit -// this trailing ; using the knowledge that the macro would expand to { }. -// -// But doing so doesn't work well with macros that look like functions and -// compiler warnings about empty statements (ICU-20601) and ICU 65 therefore -// switches to the standard solution of wrapping such macros in do { } while. -// -// This will however break existing code that depends on being able to invoke -// these macros without a trailing ; so to be able to remain compatible with -// such code the wrapper is itself defined as macros so that it's possible to -// build ICU 65 and later with the old macro behaviour, like this: -// -// CPPFLAGS='-DUPRV_BLOCK_MACRO_BEGIN="" -DUPRV_BLOCK_MACRO_END=""' -// runConfigureICU ... - -/** - * \def UPRV_BLOCK_MACRO_BEGIN - * Defined as the "do" keyword by default. - * @internal - */ -#ifndef UPRV_BLOCK_MACRO_BEGIN -#define UPRV_BLOCK_MACRO_BEGIN do -#endif - -/** - * \def UPRV_BLOCK_MACRO_END - * Defined as "while (FALSE)" by default. - * @internal - */ -#ifndef UPRV_BLOCK_MACRO_END -#define UPRV_BLOCK_MACRO_END while (FALSE) -#endif - -/*==========================================================================*/ -/* limits for int32_t etc., like in POSIX inttypes.h */ -/*==========================================================================*/ - -#ifndef INT8_MIN -/** The smallest value an 8 bit signed integer can hold @stable ICU 2.0 */ -# define INT8_MIN ((int8_t)(-128)) -#endif -#ifndef INT16_MIN -/** The smallest value a 16 bit signed integer can hold @stable ICU 2.0 */ -# define INT16_MIN ((int16_t)(-32767-1)) -#endif -#ifndef INT32_MIN -/** The smallest value a 32 bit signed integer can hold @stable ICU 2.0 */ -# define INT32_MIN ((int32_t)(-2147483647-1)) -#endif - -#ifndef INT8_MAX -/** The largest value an 8 bit signed integer can hold @stable ICU 2.0 */ -# define INT8_MAX ((int8_t)(127)) -#endif -#ifndef INT16_MAX -/** The largest value a 16 bit signed integer can hold @stable ICU 2.0 */ -# define INT16_MAX ((int16_t)(32767)) -#endif -#ifndef INT32_MAX -/** The largest value a 32 bit signed integer can hold @stable ICU 2.0 */ -# define INT32_MAX ((int32_t)(2147483647)) -#endif - -#ifndef UINT8_MAX -/** The largest value an 8 bit unsigned integer can hold @stable ICU 2.0 */ -# define UINT8_MAX ((uint8_t)(255U)) -#endif -#ifndef UINT16_MAX -/** The largest value a 16 bit unsigned integer can hold @stable ICU 2.0 */ -# define UINT16_MAX ((uint16_t)(65535U)) -#endif -#ifndef UINT32_MAX -/** The largest value a 32 bit unsigned integer can hold @stable ICU 2.0 */ -# define UINT32_MAX ((uint32_t)(4294967295U)) -#endif - -#if defined(U_INT64_T_UNAVAILABLE) -# error int64_t is required for decimal format and rule-based number format. -#else -# ifndef INT64_C -/** - * Provides a platform independent way to specify a signed 64-bit integer constant. - * note: may be wrong for some 64 bit platforms - ensure your compiler provides INT64_C - * @stable ICU 2.8 - */ -# define INT64_C(c) c ## LL -# endif -# ifndef UINT64_C -/** - * Provides a platform independent way to specify an unsigned 64-bit integer constant. - * note: may be wrong for some 64 bit platforms - ensure your compiler provides UINT64_C - * @stable ICU 2.8 - */ -# define UINT64_C(c) c ## ULL -# endif -# ifndef U_INT64_MIN -/** The smallest value a 64 bit signed integer can hold @stable ICU 2.8 */ -# define U_INT64_MIN ((int64_t)(INT64_C(-9223372036854775807)-1)) -# endif -# ifndef U_INT64_MAX -/** The largest value a 64 bit signed integer can hold @stable ICU 2.8 */ -# define U_INT64_MAX ((int64_t)(INT64_C(9223372036854775807))) -# endif -# ifndef U_UINT64_MAX -/** The largest value a 64 bit unsigned integer can hold @stable ICU 2.8 */ -# define U_UINT64_MAX ((uint64_t)(UINT64_C(18446744073709551615))) -# endif -#endif - -/*==========================================================================*/ -/* Boolean data type */ -/*==========================================================================*/ - -/** The ICU boolean type @stable ICU 2.0 */ -typedef int8_t UBool; - -#ifndef TRUE -/** The TRUE value of a UBool @stable ICU 2.0 */ -# define TRUE 1 -#endif -#ifndef FALSE -/** The FALSE value of a UBool @stable ICU 2.0 */ -# define FALSE 0 -#endif - - -/*==========================================================================*/ -/* Unicode data types */ -/*==========================================================================*/ - -/* wchar_t-related definitions -------------------------------------------- */ - -/* - * \def U_WCHAR_IS_UTF16 - * Defined if wchar_t uses UTF-16. - * - * @stable ICU 2.0 - */ -/* - * \def U_WCHAR_IS_UTF32 - * Defined if wchar_t uses UTF-32. - * - * @stable ICU 2.0 - */ -#if !defined(U_WCHAR_IS_UTF16) && !defined(U_WCHAR_IS_UTF32) -# ifdef __STDC_ISO_10646__ -# if (U_SIZEOF_WCHAR_T==2) -# define U_WCHAR_IS_UTF16 -# elif (U_SIZEOF_WCHAR_T==4) -# define U_WCHAR_IS_UTF32 -# endif -# elif defined __UCS2__ -# if (U_PF_OS390 <= U_PLATFORM && U_PLATFORM <= U_PF_OS400) && (U_SIZEOF_WCHAR_T==2) -# define U_WCHAR_IS_UTF16 -# endif -# elif defined(__UCS4__) || (U_PLATFORM == U_PF_OS400 && defined(__UTF32__)) -# if (U_SIZEOF_WCHAR_T==4) -# define U_WCHAR_IS_UTF32 -# endif -# elif U_PLATFORM_IS_DARWIN_BASED || (U_SIZEOF_WCHAR_T==4 && U_PLATFORM_IS_LINUX_BASED) -# define U_WCHAR_IS_UTF32 -# elif U_PLATFORM_HAS_WIN32_API -# define U_WCHAR_IS_UTF16 -# endif -#endif - -/* UChar and UChar32 definitions -------------------------------------------- */ - -/** Number of bytes in a UChar. @stable ICU 2.0 */ -#define U_SIZEOF_UCHAR 2 - -/** - * \def U_CHAR16_IS_TYPEDEF - * If 1, then char16_t is a typedef and not a real type (yet) - * @internal - */ -#if (U_PLATFORM == U_PF_AIX) && defined(__cplusplus) &&(U_CPLUSPLUS_VERSION < 11) -// for AIX, uchar.h needs to be included -# include <uchar.h> -# define U_CHAR16_IS_TYPEDEF 1 -#elif defined(_MSC_VER) && (_MSC_VER < 1900) -// Versions of Visual Studio/MSVC below 2015 do not support char16_t as a real type, -// and instead use a typedef. https://msdn.microsoft.com/library/bb531344.aspx -# define U_CHAR16_IS_TYPEDEF 1 -#else -# define U_CHAR16_IS_TYPEDEF 0 -#endif - - -/** - * \var UChar - * - * The base type for UTF-16 code units and pointers. - * Unsigned 16-bit integer. - * Starting with ICU 59, C++ API uses char16_t directly, while C API continues to use UChar. - * - * UChar is configurable by defining the macro UCHAR_TYPE - * on the preprocessor or compiler command line: - * -DUCHAR_TYPE=uint16_t or -DUCHAR_TYPE=wchar_t (if U_SIZEOF_WCHAR_T==2) etc. - * (The UCHAR_TYPE can also be \#defined earlier in this file, for outside the ICU library code.) - * This is for transitional use from application code that uses uint16_t or wchar_t for UTF-16. - * - * The default is UChar=char16_t. - * - * C++11 defines char16_t as bit-compatible with uint16_t, but as a distinct type. - * - * In C, char16_t is a simple typedef of uint_least16_t. - * ICU requires uint_least16_t=uint16_t for data memory mapping. - * On macOS, char16_t is not available because the uchar.h standard header is missing. - * - * @stable ICU 4.4 - */ - -#if 1 - // #if 1 is normal. UChar defaults to char16_t in C++. - // For configuration testing of UChar=uint16_t temporarily change this to #if 0. - // The intltest Makefile #defines UCHAR_TYPE=char16_t, - // so we only #define it to uint16_t if it is undefined so far. -#elif !defined(UCHAR_TYPE) -# define UCHAR_TYPE uint16_t -#endif - -#if defined(U_COMBINED_IMPLEMENTATION) || defined(U_COMMON_IMPLEMENTATION) || \ - defined(U_I18N_IMPLEMENTATION) || defined(U_IO_IMPLEMENTATION) - // Inside the ICU library code, never configurable. - typedef char16_t UChar; -#elif defined(UCHAR_TYPE) - typedef UCHAR_TYPE UChar; -#elif defined(__cplusplus) - typedef char16_t UChar; -#else - typedef uint16_t UChar; -#endif - -/** - * \var OldUChar - * Default ICU 58 definition of UChar. - * A base type for UTF-16 code units and pointers. - * Unsigned 16-bit integer. - * - * Define OldUChar to be wchar_t if that is 16 bits wide. - * If wchar_t is not 16 bits wide, then define UChar to be uint16_t. - * - * This makes the definition of OldUChar platform-dependent - * but allows direct string type compatibility with platforms with - * 16-bit wchar_t types. - * - * This is how UChar was defined in ICU 58, for transition convenience. - * Exception: ICU 58 UChar was defined to UCHAR_TYPE if that macro was defined. - * The current UChar responds to UCHAR_TYPE but OldUChar does not. - * - * @stable ICU 59 - */ -#if U_SIZEOF_WCHAR_T==2 - typedef wchar_t OldUChar; -#elif defined(__CHAR16_TYPE__) - typedef __CHAR16_TYPE__ OldUChar; -#else - typedef uint16_t OldUChar; -#endif - -/** - * Define UChar32 as a type for single Unicode code points. - * UChar32 is a signed 32-bit integer (same as int32_t). - * - * The Unicode code point range is 0..0x10ffff. - * All other values (negative or >=0x110000) are illegal as Unicode code points. - * They may be used as sentinel values to indicate "done", "error" - * or similar non-code point conditions. - * - * Before ICU 2.4 (Jitterbug 2146), UChar32 was defined - * to be wchar_t if that is 32 bits wide (wchar_t may be signed or unsigned) - * or else to be uint32_t. - * That is, the definition of UChar32 was platform-dependent. - * - * @see U_SENTINEL - * @stable ICU 2.4 - */ -typedef int32_t UChar32; - -/** - * This value is intended for sentinel values for APIs that - * (take or) return single code points (UChar32). - * It is outside of the Unicode code point range 0..0x10ffff. - * - * For example, a "done" or "error" value in a new API - * could be indicated with U_SENTINEL. - * - * ICU APIs designed before ICU 2.4 usually define service-specific "done" - * values, mostly 0xffff. - * Those may need to be distinguished from - * actual U+ffff text contents by calling functions like - * CharacterIterator::hasNext() or UnicodeString::length(). - * - * @return -1 - * @see UChar32 - * @stable ICU 2.4 - */ -#define U_SENTINEL (-1) - -#include "./urename.h" - -#endif diff --git a/src/tree_sitter/unicode/urename.h b/src/tree_sitter/unicode/urename.h deleted file mode 100644 index ac79ad0f98..0000000000 --- a/src/tree_sitter/unicode/urename.h +++ /dev/null @@ -1 +0,0 @@ -// This file must exist in order for `utf8.h` and `utf16.h` to be used. diff --git a/src/tree_sitter/unicode/utf.h b/src/tree_sitter/unicode/utf.h deleted file mode 100644 index ac79ad0f98..0000000000 --- a/src/tree_sitter/unicode/utf.h +++ /dev/null @@ -1 +0,0 @@ -// This file must exist in order for `utf8.h` and `utf16.h` to be used. diff --git a/src/tree_sitter/unicode/utf16.h b/src/tree_sitter/unicode/utf16.h deleted file mode 100644 index b547922441..0000000000 --- a/src/tree_sitter/unicode/utf16.h +++ /dev/null @@ -1,733 +0,0 @@ -// ยฉ 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html -/* -******************************************************************************* -* -* Copyright (C) 1999-2012, International Business Machines -* Corporation and others. All Rights Reserved. -* -******************************************************************************* -* file name: utf16.h -* encoding: UTF-8 -* tab size: 8 (not used) -* indentation:4 -* -* created on: 1999sep09 -* created by: Markus W. Scherer -*/ - -/** - * \file - * \brief C API: 16-bit Unicode handling macros - * - * This file defines macros to deal with 16-bit Unicode (UTF-16) code units and strings. - * - * For more information see utf.h and the ICU User Guide Strings chapter - * (http://userguide.icu-project.org/strings). - * - * <em>Usage:</em> - * ICU coding guidelines for if() statements should be followed when using these macros. - * Compound statements (curly braces {}) must be used for if-else-while... - * bodies and all macro statements should be terminated with semicolon. - */ - -#ifndef __UTF16_H__ -#define __UTF16_H__ - -#include "./umachine.h" -#ifndef __UTF_H__ -# include "./utf.h" -#endif - -/* single-code point definitions -------------------------------------------- */ - -/** - * Does this code unit alone encode a code point (BMP, not a surrogate)? - * @param c 16-bit code unit - * @return TRUE or FALSE - * @stable ICU 2.4 - */ -#define U16_IS_SINGLE(c) !U_IS_SURROGATE(c) - -/** - * Is this code unit a lead surrogate (U+d800..U+dbff)? - * @param c 16-bit code unit - * @return TRUE or FALSE - * @stable ICU 2.4 - */ -#define U16_IS_LEAD(c) (((c)&0xfffffc00)==0xd800) - -/** - * Is this code unit a trail surrogate (U+dc00..U+dfff)? - * @param c 16-bit code unit - * @return TRUE or FALSE - * @stable ICU 2.4 - */ -#define U16_IS_TRAIL(c) (((c)&0xfffffc00)==0xdc00) - -/** - * Is this code unit a surrogate (U+d800..U+dfff)? - * @param c 16-bit code unit - * @return TRUE or FALSE - * @stable ICU 2.4 - */ -#define U16_IS_SURROGATE(c) U_IS_SURROGATE(c) - -/** - * Assuming c is a surrogate code point (U16_IS_SURROGATE(c)), - * is it a lead surrogate? - * @param c 16-bit code unit - * @return TRUE or FALSE - * @stable ICU 2.4 - */ -#define U16_IS_SURROGATE_LEAD(c) (((c)&0x400)==0) - -/** - * Assuming c is a surrogate code point (U16_IS_SURROGATE(c)), - * is it a trail surrogate? - * @param c 16-bit code unit - * @return TRUE or FALSE - * @stable ICU 4.2 - */ -#define U16_IS_SURROGATE_TRAIL(c) (((c)&0x400)!=0) - -/** - * Helper constant for U16_GET_SUPPLEMENTARY. - * @internal - */ -#define U16_SURROGATE_OFFSET ((0xd800<<10UL)+0xdc00-0x10000) - -/** - * Get a supplementary code point value (U+10000..U+10ffff) - * from its lead and trail surrogates. - * The result is undefined if the input values are not - * lead and trail surrogates. - * - * @param lead lead surrogate (U+d800..U+dbff) - * @param trail trail surrogate (U+dc00..U+dfff) - * @return supplementary code point (U+10000..U+10ffff) - * @stable ICU 2.4 - */ -#define U16_GET_SUPPLEMENTARY(lead, trail) \ - (((UChar32)(lead)<<10UL)+(UChar32)(trail)-U16_SURROGATE_OFFSET) - - -/** - * Get the lead surrogate (0xd800..0xdbff) for a - * supplementary code point (0x10000..0x10ffff). - * @param supplementary 32-bit code point (U+10000..U+10ffff) - * @return lead surrogate (U+d800..U+dbff) for supplementary - * @stable ICU 2.4 - */ -#define U16_LEAD(supplementary) (UChar)(((supplementary)>>10)+0xd7c0) - -/** - * Get the trail surrogate (0xdc00..0xdfff) for a - * supplementary code point (0x10000..0x10ffff). - * @param supplementary 32-bit code point (U+10000..U+10ffff) - * @return trail surrogate (U+dc00..U+dfff) for supplementary - * @stable ICU 2.4 - */ -#define U16_TRAIL(supplementary) (UChar)(((supplementary)&0x3ff)|0xdc00) - -/** - * How many 16-bit code units are used to encode this Unicode code point? (1 or 2) - * The result is not defined if c is not a Unicode code point (U+0000..U+10ffff). - * @param c 32-bit code point - * @return 1 or 2 - * @stable ICU 2.4 - */ -#define U16_LENGTH(c) ((uint32_t)(c)<=0xffff ? 1 : 2) - -/** - * The maximum number of 16-bit code units per Unicode code point (U+0000..U+10ffff). - * @return 2 - * @stable ICU 2.4 - */ -#define U16_MAX_LENGTH 2 - -/** - * Get a code point from a string at a random-access offset, - * without changing the offset. - * "Unsafe" macro, assumes well-formed UTF-16. - * - * The offset may point to either the lead or trail surrogate unit - * for a supplementary code point, in which case the macro will read - * the adjacent matching surrogate as well. - * The result is undefined if the offset points to a single, unpaired surrogate. - * Iteration through a string is more efficient with U16_NEXT_UNSAFE or U16_NEXT. - * - * @param s const UChar * string - * @param i string offset - * @param c output UChar32 variable - * @see U16_GET - * @stable ICU 2.4 - */ -#define U16_GET_UNSAFE(s, i, c) UPRV_BLOCK_MACRO_BEGIN { \ - (c)=(s)[i]; \ - if(U16_IS_SURROGATE(c)) { \ - if(U16_IS_SURROGATE_LEAD(c)) { \ - (c)=U16_GET_SUPPLEMENTARY((c), (s)[(i)+1]); \ - } else { \ - (c)=U16_GET_SUPPLEMENTARY((s)[(i)-1], (c)); \ - } \ - } \ -} UPRV_BLOCK_MACRO_END - -/** - * Get a code point from a string at a random-access offset, - * without changing the offset. - * "Safe" macro, handles unpaired surrogates and checks for string boundaries. - * - * The offset may point to either the lead or trail surrogate unit - * for a supplementary code point, in which case the macro will read - * the adjacent matching surrogate as well. - * - * The length can be negative for a NUL-terminated string. - * - * If the offset points to a single, unpaired surrogate, then - * c is set to that unpaired surrogate. - * Iteration through a string is more efficient with U16_NEXT_UNSAFE or U16_NEXT. - * - * @param s const UChar * string - * @param start starting string offset (usually 0) - * @param i string offset, must be start<=i<length - * @param length string length - * @param c output UChar32 variable - * @see U16_GET_UNSAFE - * @stable ICU 2.4 - */ -#define U16_GET(s, start, i, length, c) UPRV_BLOCK_MACRO_BEGIN { \ - (c)=(s)[i]; \ - if(U16_IS_SURROGATE(c)) { \ - uint16_t __c2; \ - if(U16_IS_SURROGATE_LEAD(c)) { \ - if((i)+1!=(length) && U16_IS_TRAIL(__c2=(s)[(i)+1])) { \ - (c)=U16_GET_SUPPLEMENTARY((c), __c2); \ - } \ - } else { \ - if((i)>(start) && U16_IS_LEAD(__c2=(s)[(i)-1])) { \ - (c)=U16_GET_SUPPLEMENTARY(__c2, (c)); \ - } \ - } \ - } \ -} UPRV_BLOCK_MACRO_END - -/** - * Get a code point from a string at a random-access offset, - * without changing the offset. - * "Safe" macro, handles unpaired surrogates and checks for string boundaries. - * - * The offset may point to either the lead or trail surrogate unit - * for a supplementary code point, in which case the macro will read - * the adjacent matching surrogate as well. - * - * The length can be negative for a NUL-terminated string. - * - * If the offset points to a single, unpaired surrogate, then - * c is set to U+FFFD. - * Iteration through a string is more efficient with U16_NEXT_UNSAFE or U16_NEXT_OR_FFFD. - * - * @param s const UChar * string - * @param start starting string offset (usually 0) - * @param i string offset, must be start<=i<length - * @param length string length - * @param c output UChar32 variable - * @see U16_GET_UNSAFE - * @stable ICU 60 - */ -#define U16_GET_OR_FFFD(s, start, i, length, c) UPRV_BLOCK_MACRO_BEGIN { \ - (c)=(s)[i]; \ - if(U16_IS_SURROGATE(c)) { \ - uint16_t __c2; \ - if(U16_IS_SURROGATE_LEAD(c)) { \ - if((i)+1!=(length) && U16_IS_TRAIL(__c2=(s)[(i)+1])) { \ - (c)=U16_GET_SUPPLEMENTARY((c), __c2); \ - } else { \ - (c)=0xfffd; \ - } \ - } else { \ - if((i)>(start) && U16_IS_LEAD(__c2=(s)[(i)-1])) { \ - (c)=U16_GET_SUPPLEMENTARY(__c2, (c)); \ - } else { \ - (c)=0xfffd; \ - } \ - } \ - } \ -} UPRV_BLOCK_MACRO_END - -/* definitions with forward iteration --------------------------------------- */ - -/** - * Get a code point from a string at a code point boundary offset, - * and advance the offset to the next code point boundary. - * (Post-incrementing forward iteration.) - * "Unsafe" macro, assumes well-formed UTF-16. - * - * The offset may point to the lead surrogate unit - * for a supplementary code point, in which case the macro will read - * the following trail surrogate as well. - * If the offset points to a trail surrogate, then that itself - * will be returned as the code point. - * The result is undefined if the offset points to a single, unpaired lead surrogate. - * - * @param s const UChar * string - * @param i string offset - * @param c output UChar32 variable - * @see U16_NEXT - * @stable ICU 2.4 - */ -#define U16_NEXT_UNSAFE(s, i, c) UPRV_BLOCK_MACRO_BEGIN { \ - (c)=(s)[(i)++]; \ - if(U16_IS_LEAD(c)) { \ - (c)=U16_GET_SUPPLEMENTARY((c), (s)[(i)++]); \ - } \ -} UPRV_BLOCK_MACRO_END - -/** - * Get a code point from a string at a code point boundary offset, - * and advance the offset to the next code point boundary. - * (Post-incrementing forward iteration.) - * "Safe" macro, handles unpaired surrogates and checks for string boundaries. - * - * The length can be negative for a NUL-terminated string. - * - * The offset may point to the lead surrogate unit - * for a supplementary code point, in which case the macro will read - * the following trail surrogate as well. - * If the offset points to a trail surrogate or - * to a single, unpaired lead surrogate, then c is set to that unpaired surrogate. - * - * @param s const UChar * string - * @param i string offset, must be i<length - * @param length string length - * @param c output UChar32 variable - * @see U16_NEXT_UNSAFE - * @stable ICU 2.4 - */ -#define U16_NEXT(s, i, length, c) UPRV_BLOCK_MACRO_BEGIN { \ - (c)=(s)[(i)++]; \ - if(U16_IS_LEAD(c)) { \ - uint16_t __c2; \ - if((i)!=(length) && U16_IS_TRAIL(__c2=(s)[(i)])) { \ - ++(i); \ - (c)=U16_GET_SUPPLEMENTARY((c), __c2); \ - } \ - } \ -} UPRV_BLOCK_MACRO_END - -/** - * Get a code point from a string at a code point boundary offset, - * and advance the offset to the next code point boundary. - * (Post-incrementing forward iteration.) - * "Safe" macro, handles unpaired surrogates and checks for string boundaries. - * - * The length can be negative for a NUL-terminated string. - * - * The offset may point to the lead surrogate unit - * for a supplementary code point, in which case the macro will read - * the following trail surrogate as well. - * If the offset points to a trail surrogate or - * to a single, unpaired lead surrogate, then c is set to U+FFFD. - * - * @param s const UChar * string - * @param i string offset, must be i<length - * @param length string length - * @param c output UChar32 variable - * @see U16_NEXT_UNSAFE - * @stable ICU 60 - */ -#define U16_NEXT_OR_FFFD(s, i, length, c) UPRV_BLOCK_MACRO_BEGIN { \ - (c)=(s)[(i)++]; \ - if(U16_IS_SURROGATE(c)) { \ - uint16_t __c2; \ - if(U16_IS_SURROGATE_LEAD(c) && (i)!=(length) && U16_IS_TRAIL(__c2=(s)[(i)])) { \ - ++(i); \ - (c)=U16_GET_SUPPLEMENTARY((c), __c2); \ - } else { \ - (c)=0xfffd; \ - } \ - } \ -} UPRV_BLOCK_MACRO_END - -/** - * Append a code point to a string, overwriting 1 or 2 code units. - * The offset points to the current end of the string contents - * and is advanced (post-increment). - * "Unsafe" macro, assumes a valid code point and sufficient space in the string. - * Otherwise, the result is undefined. - * - * @param s const UChar * string buffer - * @param i string offset - * @param c code point to append - * @see U16_APPEND - * @stable ICU 2.4 - */ -#define U16_APPEND_UNSAFE(s, i, c) UPRV_BLOCK_MACRO_BEGIN { \ - if((uint32_t)(c)<=0xffff) { \ - (s)[(i)++]=(uint16_t)(c); \ - } else { \ - (s)[(i)++]=(uint16_t)(((c)>>10)+0xd7c0); \ - (s)[(i)++]=(uint16_t)(((c)&0x3ff)|0xdc00); \ - } \ -} UPRV_BLOCK_MACRO_END - -/** - * Append a code point to a string, overwriting 1 or 2 code units. - * The offset points to the current end of the string contents - * and is advanced (post-increment). - * "Safe" macro, checks for a valid code point. - * If a surrogate pair is written, checks for sufficient space in the string. - * If the code point is not valid or a trail surrogate does not fit, - * then isError is set to TRUE. - * - * @param s const UChar * string buffer - * @param i string offset, must be i<capacity - * @param capacity size of the string buffer - * @param c code point to append - * @param isError output UBool set to TRUE if an error occurs, otherwise not modified - * @see U16_APPEND_UNSAFE - * @stable ICU 2.4 - */ -#define U16_APPEND(s, i, capacity, c, isError) UPRV_BLOCK_MACRO_BEGIN { \ - if((uint32_t)(c)<=0xffff) { \ - (s)[(i)++]=(uint16_t)(c); \ - } else if((uint32_t)(c)<=0x10ffff && (i)+1<(capacity)) { \ - (s)[(i)++]=(uint16_t)(((c)>>10)+0xd7c0); \ - (s)[(i)++]=(uint16_t)(((c)&0x3ff)|0xdc00); \ - } else /* c>0x10ffff or not enough space */ { \ - (isError)=TRUE; \ - } \ -} UPRV_BLOCK_MACRO_END - -/** - * Advance the string offset from one code point boundary to the next. - * (Post-incrementing iteration.) - * "Unsafe" macro, assumes well-formed UTF-16. - * - * @param s const UChar * string - * @param i string offset - * @see U16_FWD_1 - * @stable ICU 2.4 - */ -#define U16_FWD_1_UNSAFE(s, i) UPRV_BLOCK_MACRO_BEGIN { \ - if(U16_IS_LEAD((s)[(i)++])) { \ - ++(i); \ - } \ -} UPRV_BLOCK_MACRO_END - -/** - * Advance the string offset from one code point boundary to the next. - * (Post-incrementing iteration.) - * "Safe" macro, handles unpaired surrogates and checks for string boundaries. - * - * The length can be negative for a NUL-terminated string. - * - * @param s const UChar * string - * @param i string offset, must be i<length - * @param length string length - * @see U16_FWD_1_UNSAFE - * @stable ICU 2.4 - */ -#define U16_FWD_1(s, i, length) UPRV_BLOCK_MACRO_BEGIN { \ - if(U16_IS_LEAD((s)[(i)++]) && (i)!=(length) && U16_IS_TRAIL((s)[i])) { \ - ++(i); \ - } \ -} UPRV_BLOCK_MACRO_END - -/** - * Advance the string offset from one code point boundary to the n-th next one, - * i.e., move forward by n code points. - * (Post-incrementing iteration.) - * "Unsafe" macro, assumes well-formed UTF-16. - * - * @param s const UChar * string - * @param i string offset - * @param n number of code points to skip - * @see U16_FWD_N - * @stable ICU 2.4 - */ -#define U16_FWD_N_UNSAFE(s, i, n) UPRV_BLOCK_MACRO_BEGIN { \ - int32_t __N=(n); \ - while(__N>0) { \ - U16_FWD_1_UNSAFE(s, i); \ - --__N; \ - } \ -} UPRV_BLOCK_MACRO_END - -/** - * Advance the string offset from one code point boundary to the n-th next one, - * i.e., move forward by n code points. - * (Post-incrementing iteration.) - * "Safe" macro, handles unpaired surrogates and checks for string boundaries. - * - * The length can be negative for a NUL-terminated string. - * - * @param s const UChar * string - * @param i int32_t string offset, must be i<length - * @param length int32_t string length - * @param n number of code points to skip - * @see U16_FWD_N_UNSAFE - * @stable ICU 2.4 - */ -#define U16_FWD_N(s, i, length, n) UPRV_BLOCK_MACRO_BEGIN { \ - int32_t __N=(n); \ - while(__N>0 && ((i)<(length) || ((length)<0 && (s)[i]!=0))) { \ - U16_FWD_1(s, i, length); \ - --__N; \ - } \ -} UPRV_BLOCK_MACRO_END - -/** - * Adjust a random-access offset to a code point boundary - * at the start of a code point. - * If the offset points to the trail surrogate of a surrogate pair, - * then the offset is decremented. - * Otherwise, it is not modified. - * "Unsafe" macro, assumes well-formed UTF-16. - * - * @param s const UChar * string - * @param i string offset - * @see U16_SET_CP_START - * @stable ICU 2.4 - */ -#define U16_SET_CP_START_UNSAFE(s, i) UPRV_BLOCK_MACRO_BEGIN { \ - if(U16_IS_TRAIL((s)[i])) { \ - --(i); \ - } \ -} UPRV_BLOCK_MACRO_END - -/** - * Adjust a random-access offset to a code point boundary - * at the start of a code point. - * If the offset points to the trail surrogate of a surrogate pair, - * then the offset is decremented. - * Otherwise, it is not modified. - * "Safe" macro, handles unpaired surrogates and checks for string boundaries. - * - * @param s const UChar * string - * @param start starting string offset (usually 0) - * @param i string offset, must be start<=i - * @see U16_SET_CP_START_UNSAFE - * @stable ICU 2.4 - */ -#define U16_SET_CP_START(s, start, i) UPRV_BLOCK_MACRO_BEGIN { \ - if(U16_IS_TRAIL((s)[i]) && (i)>(start) && U16_IS_LEAD((s)[(i)-1])) { \ - --(i); \ - } \ -} UPRV_BLOCK_MACRO_END - -/* definitions with backward iteration -------------------------------------- */ - -/** - * Move the string offset from one code point boundary to the previous one - * and get the code point between them. - * (Pre-decrementing backward iteration.) - * "Unsafe" macro, assumes well-formed UTF-16. - * - * The input offset may be the same as the string length. - * If the offset is behind a trail surrogate unit - * for a supplementary code point, then the macro will read - * the preceding lead surrogate as well. - * If the offset is behind a lead surrogate, then that itself - * will be returned as the code point. - * The result is undefined if the offset is behind a single, unpaired trail surrogate. - * - * @param s const UChar * string - * @param i string offset - * @param c output UChar32 variable - * @see U16_PREV - * @stable ICU 2.4 - */ -#define U16_PREV_UNSAFE(s, i, c) UPRV_BLOCK_MACRO_BEGIN { \ - (c)=(s)[--(i)]; \ - if(U16_IS_TRAIL(c)) { \ - (c)=U16_GET_SUPPLEMENTARY((s)[--(i)], (c)); \ - } \ -} UPRV_BLOCK_MACRO_END - -/** - * Move the string offset from one code point boundary to the previous one - * and get the code point between them. - * (Pre-decrementing backward iteration.) - * "Safe" macro, handles unpaired surrogates and checks for string boundaries. - * - * The input offset may be the same as the string length. - * If the offset is behind a trail surrogate unit - * for a supplementary code point, then the macro will read - * the preceding lead surrogate as well. - * If the offset is behind a lead surrogate or behind a single, unpaired - * trail surrogate, then c is set to that unpaired surrogate. - * - * @param s const UChar * string - * @param start starting string offset (usually 0) - * @param i string offset, must be start<i - * @param c output UChar32 variable - * @see U16_PREV_UNSAFE - * @stable ICU 2.4 - */ -#define U16_PREV(s, start, i, c) UPRV_BLOCK_MACRO_BEGIN { \ - (c)=(s)[--(i)]; \ - if(U16_IS_TRAIL(c)) { \ - uint16_t __c2; \ - if((i)>(start) && U16_IS_LEAD(__c2=(s)[(i)-1])) { \ - --(i); \ - (c)=U16_GET_SUPPLEMENTARY(__c2, (c)); \ - } \ - } \ -} UPRV_BLOCK_MACRO_END - -/** - * Move the string offset from one code point boundary to the previous one - * and get the code point between them. - * (Pre-decrementing backward iteration.) - * "Safe" macro, handles unpaired surrogates and checks for string boundaries. - * - * The input offset may be the same as the string length. - * If the offset is behind a trail surrogate unit - * for a supplementary code point, then the macro will read - * the preceding lead surrogate as well. - * If the offset is behind a lead surrogate or behind a single, unpaired - * trail surrogate, then c is set to U+FFFD. - * - * @param s const UChar * string - * @param start starting string offset (usually 0) - * @param i string offset, must be start<i - * @param c output UChar32 variable - * @see U16_PREV_UNSAFE - * @stable ICU 60 - */ -#define U16_PREV_OR_FFFD(s, start, i, c) UPRV_BLOCK_MACRO_BEGIN { \ - (c)=(s)[--(i)]; \ - if(U16_IS_SURROGATE(c)) { \ - uint16_t __c2; \ - if(U16_IS_SURROGATE_TRAIL(c) && (i)>(start) && U16_IS_LEAD(__c2=(s)[(i)-1])) { \ - --(i); \ - (c)=U16_GET_SUPPLEMENTARY(__c2, (c)); \ - } else { \ - (c)=0xfffd; \ - } \ - } \ -} UPRV_BLOCK_MACRO_END - -/** - * Move the string offset from one code point boundary to the previous one. - * (Pre-decrementing backward iteration.) - * The input offset may be the same as the string length. - * "Unsafe" macro, assumes well-formed UTF-16. - * - * @param s const UChar * string - * @param i string offset - * @see U16_BACK_1 - * @stable ICU 2.4 - */ -#define U16_BACK_1_UNSAFE(s, i) UPRV_BLOCK_MACRO_BEGIN { \ - if(U16_IS_TRAIL((s)[--(i)])) { \ - --(i); \ - } \ -} UPRV_BLOCK_MACRO_END - -/** - * Move the string offset from one code point boundary to the previous one. - * (Pre-decrementing backward iteration.) - * The input offset may be the same as the string length. - * "Safe" macro, handles unpaired surrogates and checks for string boundaries. - * - * @param s const UChar * string - * @param start starting string offset (usually 0) - * @param i string offset, must be start<i - * @see U16_BACK_1_UNSAFE - * @stable ICU 2.4 - */ -#define U16_BACK_1(s, start, i) UPRV_BLOCK_MACRO_BEGIN { \ - if(U16_IS_TRAIL((s)[--(i)]) && (i)>(start) && U16_IS_LEAD((s)[(i)-1])) { \ - --(i); \ - } \ -} UPRV_BLOCK_MACRO_END - -/** - * Move the string offset from one code point boundary to the n-th one before it, - * i.e., move backward by n code points. - * (Pre-decrementing backward iteration.) - * The input offset may be the same as the string length. - * "Unsafe" macro, assumes well-formed UTF-16. - * - * @param s const UChar * string - * @param i string offset - * @param n number of code points to skip - * @see U16_BACK_N - * @stable ICU 2.4 - */ -#define U16_BACK_N_UNSAFE(s, i, n) UPRV_BLOCK_MACRO_BEGIN { \ - int32_t __N=(n); \ - while(__N>0) { \ - U16_BACK_1_UNSAFE(s, i); \ - --__N; \ - } \ -} UPRV_BLOCK_MACRO_END - -/** - * Move the string offset from one code point boundary to the n-th one before it, - * i.e., move backward by n code points. - * (Pre-decrementing backward iteration.) - * The input offset may be the same as the string length. - * "Safe" macro, handles unpaired surrogates and checks for string boundaries. - * - * @param s const UChar * string - * @param start start of string - * @param i string offset, must be start<i - * @param n number of code points to skip - * @see U16_BACK_N_UNSAFE - * @stable ICU 2.4 - */ -#define U16_BACK_N(s, start, i, n) UPRV_BLOCK_MACRO_BEGIN { \ - int32_t __N=(n); \ - while(__N>0 && (i)>(start)) { \ - U16_BACK_1(s, start, i); \ - --__N; \ - } \ -} UPRV_BLOCK_MACRO_END - -/** - * Adjust a random-access offset to a code point boundary after a code point. - * If the offset is behind the lead surrogate of a surrogate pair, - * then the offset is incremented. - * Otherwise, it is not modified. - * The input offset may be the same as the string length. - * "Unsafe" macro, assumes well-formed UTF-16. - * - * @param s const UChar * string - * @param i string offset - * @see U16_SET_CP_LIMIT - * @stable ICU 2.4 - */ -#define U16_SET_CP_LIMIT_UNSAFE(s, i) UPRV_BLOCK_MACRO_BEGIN { \ - if(U16_IS_LEAD((s)[(i)-1])) { \ - ++(i); \ - } \ -} UPRV_BLOCK_MACRO_END - -/** - * Adjust a random-access offset to a code point boundary after a code point. - * If the offset is behind the lead surrogate of a surrogate pair, - * then the offset is incremented. - * Otherwise, it is not modified. - * The input offset may be the same as the string length. - * "Safe" macro, handles unpaired surrogates and checks for string boundaries. - * - * The length can be negative for a NUL-terminated string. - * - * @param s const UChar * string - * @param start int32_t starting string offset (usually 0) - * @param i int32_t string offset, start<=i<=length - * @param length int32_t string length - * @see U16_SET_CP_LIMIT_UNSAFE - * @stable ICU 2.4 - */ -#define U16_SET_CP_LIMIT(s, start, i, length) UPRV_BLOCK_MACRO_BEGIN { \ - if((start)<(i) && ((i)<(length) || (length)<0) && U16_IS_LEAD((s)[(i)-1]) && U16_IS_TRAIL((s)[i])) { \ - ++(i); \ - } \ -} UPRV_BLOCK_MACRO_END - -#endif diff --git a/src/tree_sitter/unicode/utf8.h b/src/tree_sitter/unicode/utf8.h deleted file mode 100644 index 3b37873e37..0000000000 --- a/src/tree_sitter/unicode/utf8.h +++ /dev/null @@ -1,881 +0,0 @@ -// ยฉ 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html -/* -******************************************************************************* -* -* Copyright (C) 1999-2015, International Business Machines -* Corporation and others. All Rights Reserved. -* -******************************************************************************* -* file name: utf8.h -* encoding: UTF-8 -* tab size: 8 (not used) -* indentation:4 -* -* created on: 1999sep13 -* created by: Markus W. Scherer -*/ - -/** - * \file - * \brief C API: 8-bit Unicode handling macros - * - * This file defines macros to deal with 8-bit Unicode (UTF-8) code units (bytes) and strings. - * - * For more information see utf.h and the ICU User Guide Strings chapter - * (http://userguide.icu-project.org/strings). - * - * <em>Usage:</em> - * ICU coding guidelines for if() statements should be followed when using these macros. - * Compound statements (curly braces {}) must be used for if-else-while... - * bodies and all macro statements should be terminated with semicolon. - */ - -#ifndef __UTF8_H__ -#define __UTF8_H__ - -#include "./umachine.h" -#ifndef __UTF_H__ -# include "./utf.h" -#endif - -/* internal definitions ----------------------------------------------------- */ - -/** - * Counts the trail bytes for a UTF-8 lead byte. - * Returns 0 for 0..0xc1 as well as for 0xf5..0xff. - * leadByte might be evaluated multiple times. - * - * This is internal since it is not meant to be called directly by external clients; - * however it is called by public macros in this file and thus must remain stable. - * - * @param leadByte The first byte of a UTF-8 sequence. Must be 0..0xff. - * @internal - */ -#define U8_COUNT_TRAIL_BYTES(leadByte) \ - (U8_IS_LEAD(leadByte) ? \ - ((uint8_t)(leadByte)>=0xe0)+((uint8_t)(leadByte)>=0xf0)+1 : 0) - -/** - * Counts the trail bytes for a UTF-8 lead byte of a valid UTF-8 sequence. - * Returns 0 for 0..0xc1. Undefined for 0xf5..0xff. - * leadByte might be evaluated multiple times. - * - * This is internal since it is not meant to be called directly by external clients; - * however it is called by public macros in this file and thus must remain stable. - * - * @param leadByte The first byte of a UTF-8 sequence. Must be 0..0xff. - * @internal - */ -#define U8_COUNT_TRAIL_BYTES_UNSAFE(leadByte) \ - (((uint8_t)(leadByte)>=0xc2)+((uint8_t)(leadByte)>=0xe0)+((uint8_t)(leadByte)>=0xf0)) - -/** - * Mask a UTF-8 lead byte, leave only the lower bits that form part of the code point value. - * - * This is internal since it is not meant to be called directly by external clients; - * however it is called by public macros in this file and thus must remain stable. - * @internal - */ -#define U8_MASK_LEAD_BYTE(leadByte, countTrailBytes) ((leadByte)&=(1<<(6-(countTrailBytes)))-1) - -/** - * Internal bit vector for 3-byte UTF-8 validity check, for use in U8_IS_VALID_LEAD3_AND_T1. - * Each bit indicates whether one lead byte + first trail byte pair starts a valid sequence. - * Lead byte E0..EF bits 3..0 are used as byte index, - * first trail byte bits 7..5 are used as bit index into that byte. - * @see U8_IS_VALID_LEAD3_AND_T1 - * @internal - */ -#define U8_LEAD3_T1_BITS "\x20\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x10\x30\x30" - -/** - * Internal 3-byte UTF-8 validity check. - * Non-zero if lead byte E0..EF and first trail byte 00..FF start a valid sequence. - * @internal - */ -#define U8_IS_VALID_LEAD3_AND_T1(lead, t1) (U8_LEAD3_T1_BITS[(lead)&0xf]&(1<<((uint8_t)(t1)>>5))) - -/** - * Internal bit vector for 4-byte UTF-8 validity check, for use in U8_IS_VALID_LEAD4_AND_T1. - * Each bit indicates whether one lead byte + first trail byte pair starts a valid sequence. - * First trail byte bits 7..4 are used as byte index, - * lead byte F0..F4 bits 2..0 are used as bit index into that byte. - * @see U8_IS_VALID_LEAD4_AND_T1 - * @internal - */ -#define U8_LEAD4_T1_BITS "\x00\x00\x00\x00\x00\x00\x00\x00\x1E\x0F\x0F\x0F\x00\x00\x00\x00" - -/** - * Internal 4-byte UTF-8 validity check. - * Non-zero if lead byte F0..F4 and first trail byte 00..FF start a valid sequence. - * @internal - */ -#define U8_IS_VALID_LEAD4_AND_T1(lead, t1) (U8_LEAD4_T1_BITS[(uint8_t)(t1)>>4]&(1<<((lead)&7))) - -/** - * Function for handling "next code point" with error-checking. - * - * This is internal since it is not meant to be called directly by external clients; - * however it is U_STABLE (not U_INTERNAL) since it is called by public macros in this - * file and thus must remain stable, and should not be hidden when other internal - * functions are hidden (otherwise public macros would fail to compile). - * @internal - */ -U_STABLE UChar32 U_EXPORT2 -utf8_nextCharSafeBody(const uint8_t *s, int32_t *pi, int32_t length, UChar32 c, UBool strict); - -/** - * Function for handling "append code point" with error-checking. - * - * This is internal since it is not meant to be called directly by external clients; - * however it is U_STABLE (not U_INTERNAL) since it is called by public macros in this - * file and thus must remain stable, and should not be hidden when other internal - * functions are hidden (otherwise public macros would fail to compile). - * @internal - */ -U_STABLE int32_t U_EXPORT2 -utf8_appendCharSafeBody(uint8_t *s, int32_t i, int32_t length, UChar32 c, UBool *pIsError); - -/** - * Function for handling "previous code point" with error-checking. - * - * This is internal since it is not meant to be called directly by external clients; - * however it is U_STABLE (not U_INTERNAL) since it is called by public macros in this - * file and thus must remain stable, and should not be hidden when other internal - * functions are hidden (otherwise public macros would fail to compile). - * @internal - */ -U_STABLE UChar32 U_EXPORT2 -utf8_prevCharSafeBody(const uint8_t *s, int32_t start, int32_t *pi, UChar32 c, UBool strict); - -/** - * Function for handling "skip backward one code point" with error-checking. - * - * This is internal since it is not meant to be called directly by external clients; - * however it is U_STABLE (not U_INTERNAL) since it is called by public macros in this - * file and thus must remain stable, and should not be hidden when other internal - * functions are hidden (otherwise public macros would fail to compile). - * @internal - */ -U_STABLE int32_t U_EXPORT2 -utf8_back1SafeBody(const uint8_t *s, int32_t start, int32_t i); - -/* single-code point definitions -------------------------------------------- */ - -/** - * Does this code unit (byte) encode a code point by itself (US-ASCII 0..0x7f)? - * @param c 8-bit code unit (byte) - * @return TRUE or FALSE - * @stable ICU 2.4 - */ -#define U8_IS_SINGLE(c) (((c)&0x80)==0) - -/** - * Is this code unit (byte) a UTF-8 lead byte? (0xC2..0xF4) - * @param c 8-bit code unit (byte) - * @return TRUE or FALSE - * @stable ICU 2.4 - */ -#define U8_IS_LEAD(c) ((uint8_t)((c)-0xc2)<=0x32) -// 0x32=0xf4-0xc2 - -/** - * Is this code unit (byte) a UTF-8 trail byte? (0x80..0xBF) - * @param c 8-bit code unit (byte) - * @return TRUE or FALSE - * @stable ICU 2.4 - */ -#define U8_IS_TRAIL(c) ((int8_t)(c)<-0x40) - -/** - * How many code units (bytes) are used for the UTF-8 encoding - * of this Unicode code point? - * @param c 32-bit code point - * @return 1..4, or 0 if c is a surrogate or not a Unicode code point - * @stable ICU 2.4 - */ -#define U8_LENGTH(c) \ - ((uint32_t)(c)<=0x7f ? 1 : \ - ((uint32_t)(c)<=0x7ff ? 2 : \ - ((uint32_t)(c)<=0xd7ff ? 3 : \ - ((uint32_t)(c)<=0xdfff || (uint32_t)(c)>0x10ffff ? 0 : \ - ((uint32_t)(c)<=0xffff ? 3 : 4)\ - ) \ - ) \ - ) \ - ) - -/** - * The maximum number of UTF-8 code units (bytes) per Unicode code point (U+0000..U+10ffff). - * @return 4 - * @stable ICU 2.4 - */ -#define U8_MAX_LENGTH 4 - -/** - * Get a code point from a string at a random-access offset, - * without changing the offset. - * The offset may point to either the lead byte or one of the trail bytes - * for a code point, in which case the macro will read all of the bytes - * for the code point. - * The result is undefined if the offset points to an illegal UTF-8 - * byte sequence. - * Iteration through a string is more efficient with U8_NEXT_UNSAFE or U8_NEXT. - * - * @param s const uint8_t * string - * @param i string offset - * @param c output UChar32 variable - * @see U8_GET - * @stable ICU 2.4 - */ -#define U8_GET_UNSAFE(s, i, c) UPRV_BLOCK_MACRO_BEGIN { \ - int32_t _u8_get_unsafe_index=(int32_t)(i); \ - U8_SET_CP_START_UNSAFE(s, _u8_get_unsafe_index); \ - U8_NEXT_UNSAFE(s, _u8_get_unsafe_index, c); \ -} UPRV_BLOCK_MACRO_END - -/** - * Get a code point from a string at a random-access offset, - * without changing the offset. - * The offset may point to either the lead byte or one of the trail bytes - * for a code point, in which case the macro will read all of the bytes - * for the code point. - * - * The length can be negative for a NUL-terminated string. - * - * If the offset points to an illegal UTF-8 byte sequence, then - * c is set to a negative value. - * Iteration through a string is more efficient with U8_NEXT_UNSAFE or U8_NEXT. - * - * @param s const uint8_t * string - * @param start int32_t starting string offset - * @param i int32_t string offset, must be start<=i<length - * @param length int32_t string length - * @param c output UChar32 variable, set to <0 in case of an error - * @see U8_GET_UNSAFE - * @stable ICU 2.4 - */ -#define U8_GET(s, start, i, length, c) UPRV_BLOCK_MACRO_BEGIN { \ - int32_t _u8_get_index=(i); \ - U8_SET_CP_START(s, start, _u8_get_index); \ - U8_NEXT(s, _u8_get_index, length, c); \ -} UPRV_BLOCK_MACRO_END - -/** - * Get a code point from a string at a random-access offset, - * without changing the offset. - * The offset may point to either the lead byte or one of the trail bytes - * for a code point, in which case the macro will read all of the bytes - * for the code point. - * - * The length can be negative for a NUL-terminated string. - * - * If the offset points to an illegal UTF-8 byte sequence, then - * c is set to U+FFFD. - * Iteration through a string is more efficient with U8_NEXT_UNSAFE or U8_NEXT_OR_FFFD. - * - * This macro does not distinguish between a real U+FFFD in the text - * and U+FFFD returned for an ill-formed sequence. - * Use U8_GET() if that distinction is important. - * - * @param s const uint8_t * string - * @param start int32_t starting string offset - * @param i int32_t string offset, must be start<=i<length - * @param length int32_t string length - * @param c output UChar32 variable, set to U+FFFD in case of an error - * @see U8_GET - * @stable ICU 51 - */ -#define U8_GET_OR_FFFD(s, start, i, length, c) UPRV_BLOCK_MACRO_BEGIN { \ - int32_t _u8_get_index=(i); \ - U8_SET_CP_START(s, start, _u8_get_index); \ - U8_NEXT_OR_FFFD(s, _u8_get_index, length, c); \ -} UPRV_BLOCK_MACRO_END - -/* definitions with forward iteration --------------------------------------- */ - -/** - * Get a code point from a string at a code point boundary offset, - * and advance the offset to the next code point boundary. - * (Post-incrementing forward iteration.) - * "Unsafe" macro, assumes well-formed UTF-8. - * - * The offset may point to the lead byte of a multi-byte sequence, - * in which case the macro will read the whole sequence. - * The result is undefined if the offset points to a trail byte - * or an illegal UTF-8 sequence. - * - * @param s const uint8_t * string - * @param i string offset - * @param c output UChar32 variable - * @see U8_NEXT - * @stable ICU 2.4 - */ -#define U8_NEXT_UNSAFE(s, i, c) UPRV_BLOCK_MACRO_BEGIN { \ - (c)=(uint8_t)(s)[(i)++]; \ - if(!U8_IS_SINGLE(c)) { \ - if((c)<0xe0) { \ - (c)=(((c)&0x1f)<<6)|((s)[(i)++]&0x3f); \ - } else if((c)<0xf0) { \ - /* no need for (c&0xf) because the upper bits are truncated after <<12 in the cast to (UChar) */ \ - (c)=(UChar)(((c)<<12)|(((s)[i]&0x3f)<<6)|((s)[(i)+1]&0x3f)); \ - (i)+=2; \ - } else { \ - (c)=(((c)&7)<<18)|(((s)[i]&0x3f)<<12)|(((s)[(i)+1]&0x3f)<<6)|((s)[(i)+2]&0x3f); \ - (i)+=3; \ - } \ - } \ -} UPRV_BLOCK_MACRO_END - -/** - * Get a code point from a string at a code point boundary offset, - * and advance the offset to the next code point boundary. - * (Post-incrementing forward iteration.) - * "Safe" macro, checks for illegal sequences and for string boundaries. - * - * The length can be negative for a NUL-terminated string. - * - * The offset may point to the lead byte of a multi-byte sequence, - * in which case the macro will read the whole sequence. - * If the offset points to a trail byte or an illegal UTF-8 sequence, then - * c is set to a negative value. - * - * @param s const uint8_t * string - * @param i int32_t string offset, must be i<length - * @param length int32_t string length - * @param c output UChar32 variable, set to <0 in case of an error - * @see U8_NEXT_UNSAFE - * @stable ICU 2.4 - */ -#define U8_NEXT(s, i, length, c) U8_INTERNAL_NEXT_OR_SUB(s, i, length, c, U_SENTINEL) - -/** - * Get a code point from a string at a code point boundary offset, - * and advance the offset to the next code point boundary. - * (Post-incrementing forward iteration.) - * "Safe" macro, checks for illegal sequences and for string boundaries. - * - * The length can be negative for a NUL-terminated string. - * - * The offset may point to the lead byte of a multi-byte sequence, - * in which case the macro will read the whole sequence. - * If the offset points to a trail byte or an illegal UTF-8 sequence, then - * c is set to U+FFFD. - * - * This macro does not distinguish between a real U+FFFD in the text - * and U+FFFD returned for an ill-formed sequence. - * Use U8_NEXT() if that distinction is important. - * - * @param s const uint8_t * string - * @param i int32_t string offset, must be i<length - * @param length int32_t string length - * @param c output UChar32 variable, set to U+FFFD in case of an error - * @see U8_NEXT - * @stable ICU 51 - */ -#define U8_NEXT_OR_FFFD(s, i, length, c) U8_INTERNAL_NEXT_OR_SUB(s, i, length, c, 0xfffd) - -/** @internal */ -#define U8_INTERNAL_NEXT_OR_SUB(s, i, length, c, sub) UPRV_BLOCK_MACRO_BEGIN { \ - (c)=(uint8_t)(s)[(i)++]; \ - if(!U8_IS_SINGLE(c)) { \ - uint8_t __t = 0; \ - if((i)!=(length) && \ - /* fetch/validate/assemble all but last trail byte */ \ - ((c)>=0xe0 ? \ - ((c)<0xf0 ? /* U+0800..U+FFFF except surrogates */ \ - U8_LEAD3_T1_BITS[(c)&=0xf]&(1<<((__t=(s)[i])>>5)) && \ - (__t&=0x3f, 1) \ - : /* U+10000..U+10FFFF */ \ - ((c)-=0xf0)<=4 && \ - U8_LEAD4_T1_BITS[(__t=(s)[i])>>4]&(1<<(c)) && \ - ((c)=((c)<<6)|(__t&0x3f), ++(i)!=(length)) && \ - (__t=(s)[i]-0x80)<=0x3f) && \ - /* valid second-to-last trail byte */ \ - ((c)=((c)<<6)|__t, ++(i)!=(length)) \ - : /* U+0080..U+07FF */ \ - (c)>=0xc2 && ((c)&=0x1f, 1)) && \ - /* last trail byte */ \ - (__t=(s)[i]-0x80)<=0x3f && \ - ((c)=((c)<<6)|__t, ++(i), 1)) { \ - } else { \ - (c)=(sub); /* ill-formed*/ \ - } \ - } \ -} UPRV_BLOCK_MACRO_END - -/** - * Append a code point to a string, overwriting 1 to 4 bytes. - * The offset points to the current end of the string contents - * and is advanced (post-increment). - * "Unsafe" macro, assumes a valid code point and sufficient space in the string. - * Otherwise, the result is undefined. - * - * @param s const uint8_t * string buffer - * @param i string offset - * @param c code point to append - * @see U8_APPEND - * @stable ICU 2.4 - */ -#define U8_APPEND_UNSAFE(s, i, c) UPRV_BLOCK_MACRO_BEGIN { \ - uint32_t __uc=(c); \ - if(__uc<=0x7f) { \ - (s)[(i)++]=(uint8_t)__uc; \ - } else { \ - if(__uc<=0x7ff) { \ - (s)[(i)++]=(uint8_t)((__uc>>6)|0xc0); \ - } else { \ - if(__uc<=0xffff) { \ - (s)[(i)++]=(uint8_t)((__uc>>12)|0xe0); \ - } else { \ - (s)[(i)++]=(uint8_t)((__uc>>18)|0xf0); \ - (s)[(i)++]=(uint8_t)(((__uc>>12)&0x3f)|0x80); \ - } \ - (s)[(i)++]=(uint8_t)(((__uc>>6)&0x3f)|0x80); \ - } \ - (s)[(i)++]=(uint8_t)((__uc&0x3f)|0x80); \ - } \ -} UPRV_BLOCK_MACRO_END - -/** - * Append a code point to a string, overwriting 1 to 4 bytes. - * The offset points to the current end of the string contents - * and is advanced (post-increment). - * "Safe" macro, checks for a valid code point. - * If a non-ASCII code point is written, checks for sufficient space in the string. - * If the code point is not valid or trail bytes do not fit, - * then isError is set to TRUE. - * - * @param s const uint8_t * string buffer - * @param i int32_t string offset, must be i<capacity - * @param capacity int32_t size of the string buffer - * @param c UChar32 code point to append - * @param isError output UBool set to TRUE if an error occurs, otherwise not modified - * @see U8_APPEND_UNSAFE - * @stable ICU 2.4 - */ -#define U8_APPEND(s, i, capacity, c, isError) UPRV_BLOCK_MACRO_BEGIN { \ - uint32_t __uc=(c); \ - if(__uc<=0x7f) { \ - (s)[(i)++]=(uint8_t)__uc; \ - } else if(__uc<=0x7ff && (i)+1<(capacity)) { \ - (s)[(i)++]=(uint8_t)((__uc>>6)|0xc0); \ - (s)[(i)++]=(uint8_t)((__uc&0x3f)|0x80); \ - } else if((__uc<=0xd7ff || (0xe000<=__uc && __uc<=0xffff)) && (i)+2<(capacity)) { \ - (s)[(i)++]=(uint8_t)((__uc>>12)|0xe0); \ - (s)[(i)++]=(uint8_t)(((__uc>>6)&0x3f)|0x80); \ - (s)[(i)++]=(uint8_t)((__uc&0x3f)|0x80); \ - } else if(0xffff<__uc && __uc<=0x10ffff && (i)+3<(capacity)) { \ - (s)[(i)++]=(uint8_t)((__uc>>18)|0xf0); \ - (s)[(i)++]=(uint8_t)(((__uc>>12)&0x3f)|0x80); \ - (s)[(i)++]=(uint8_t)(((__uc>>6)&0x3f)|0x80); \ - (s)[(i)++]=(uint8_t)((__uc&0x3f)|0x80); \ - } else { \ - (isError)=TRUE; \ - } \ -} UPRV_BLOCK_MACRO_END - -/** - * Advance the string offset from one code point boundary to the next. - * (Post-incrementing iteration.) - * "Unsafe" macro, assumes well-formed UTF-8. - * - * @param s const uint8_t * string - * @param i string offset - * @see U8_FWD_1 - * @stable ICU 2.4 - */ -#define U8_FWD_1_UNSAFE(s, i) UPRV_BLOCK_MACRO_BEGIN { \ - (i)+=1+U8_COUNT_TRAIL_BYTES_UNSAFE((s)[i]); \ -} UPRV_BLOCK_MACRO_END - -/** - * Advance the string offset from one code point boundary to the next. - * (Post-incrementing iteration.) - * "Safe" macro, checks for illegal sequences and for string boundaries. - * - * The length can be negative for a NUL-terminated string. - * - * @param s const uint8_t * string - * @param i int32_t string offset, must be i<length - * @param length int32_t string length - * @see U8_FWD_1_UNSAFE - * @stable ICU 2.4 - */ -#define U8_FWD_1(s, i, length) UPRV_BLOCK_MACRO_BEGIN { \ - uint8_t __b=(s)[(i)++]; \ - if(U8_IS_LEAD(__b) && (i)!=(length)) { \ - uint8_t __t1=(s)[i]; \ - if((0xe0<=__b && __b<0xf0)) { \ - if(U8_IS_VALID_LEAD3_AND_T1(__b, __t1) && \ - ++(i)!=(length) && U8_IS_TRAIL((s)[i])) { \ - ++(i); \ - } \ - } else if(__b<0xe0) { \ - if(U8_IS_TRAIL(__t1)) { \ - ++(i); \ - } \ - } else /* c>=0xf0 */ { \ - if(U8_IS_VALID_LEAD4_AND_T1(__b, __t1) && \ - ++(i)!=(length) && U8_IS_TRAIL((s)[i]) && \ - ++(i)!=(length) && U8_IS_TRAIL((s)[i])) { \ - ++(i); \ - } \ - } \ - } \ -} UPRV_BLOCK_MACRO_END - -/** - * Advance the string offset from one code point boundary to the n-th next one, - * i.e., move forward by n code points. - * (Post-incrementing iteration.) - * "Unsafe" macro, assumes well-formed UTF-8. - * - * @param s const uint8_t * string - * @param i string offset - * @param n number of code points to skip - * @see U8_FWD_N - * @stable ICU 2.4 - */ -#define U8_FWD_N_UNSAFE(s, i, n) UPRV_BLOCK_MACRO_BEGIN { \ - int32_t __N=(n); \ - while(__N>0) { \ - U8_FWD_1_UNSAFE(s, i); \ - --__N; \ - } \ -} UPRV_BLOCK_MACRO_END - -/** - * Advance the string offset from one code point boundary to the n-th next one, - * i.e., move forward by n code points. - * (Post-incrementing iteration.) - * "Safe" macro, checks for illegal sequences and for string boundaries. - * - * The length can be negative for a NUL-terminated string. - * - * @param s const uint8_t * string - * @param i int32_t string offset, must be i<length - * @param length int32_t string length - * @param n number of code points to skip - * @see U8_FWD_N_UNSAFE - * @stable ICU 2.4 - */ -#define U8_FWD_N(s, i, length, n) UPRV_BLOCK_MACRO_BEGIN { \ - int32_t __N=(n); \ - while(__N>0 && ((i)<(length) || ((length)<0 && (s)[i]!=0))) { \ - U8_FWD_1(s, i, length); \ - --__N; \ - } \ -} UPRV_BLOCK_MACRO_END - -/** - * Adjust a random-access offset to a code point boundary - * at the start of a code point. - * If the offset points to a UTF-8 trail byte, - * then the offset is moved backward to the corresponding lead byte. - * Otherwise, it is not modified. - * "Unsafe" macro, assumes well-formed UTF-8. - * - * @param s const uint8_t * string - * @param i string offset - * @see U8_SET_CP_START - * @stable ICU 2.4 - */ -#define U8_SET_CP_START_UNSAFE(s, i) UPRV_BLOCK_MACRO_BEGIN { \ - while(U8_IS_TRAIL((s)[i])) { --(i); } \ -} UPRV_BLOCK_MACRO_END - -/** - * Adjust a random-access offset to a code point boundary - * at the start of a code point. - * If the offset points to a UTF-8 trail byte, - * then the offset is moved backward to the corresponding lead byte. - * Otherwise, it is not modified. - * - * "Safe" macro, checks for illegal sequences and for string boundaries. - * Unlike U8_TRUNCATE_IF_INCOMPLETE(), this macro always reads s[i]. - * - * @param s const uint8_t * string - * @param start int32_t starting string offset (usually 0) - * @param i int32_t string offset, must be start<=i - * @see U8_SET_CP_START_UNSAFE - * @see U8_TRUNCATE_IF_INCOMPLETE - * @stable ICU 2.4 - */ -#define U8_SET_CP_START(s, start, i) UPRV_BLOCK_MACRO_BEGIN { \ - if(U8_IS_TRAIL((s)[(i)])) { \ - (i)=utf8_back1SafeBody(s, start, (i)); \ - } \ -} UPRV_BLOCK_MACRO_END - -/** - * If the string ends with a UTF-8 byte sequence that is valid so far - * but incomplete, then reduce the length of the string to end before - * the lead byte of that incomplete sequence. - * For example, if the string ends with E1 80, the length is reduced by 2. - * - * In all other cases (the string ends with a complete sequence, or it is not - * possible for any further trail byte to extend the trailing sequence) - * the length remains unchanged. - * - * Useful for processing text split across multiple buffers - * (save the incomplete sequence for later) - * and for optimizing iteration - * (check for string length only once per character). - * - * "Safe" macro, checks for illegal sequences and for string boundaries. - * Unlike U8_SET_CP_START(), this macro never reads s[length]. - * - * (In UTF-16, simply check for U16_IS_LEAD(last code unit).) - * - * @param s const uint8_t * string - * @param start int32_t starting string offset (usually 0) - * @param length int32_t string length (usually start<=length) - * @see U8_SET_CP_START - * @stable ICU 61 - */ -#define U8_TRUNCATE_IF_INCOMPLETE(s, start, length) UPRV_BLOCK_MACRO_BEGIN { \ - if((length)>(start)) { \ - uint8_t __b1=s[(length)-1]; \ - if(U8_IS_SINGLE(__b1)) { \ - /* common ASCII character */ \ - } else if(U8_IS_LEAD(__b1)) { \ - --(length); \ - } else if(U8_IS_TRAIL(__b1) && ((length)-2)>=(start)) { \ - uint8_t __b2=s[(length)-2]; \ - if(0xe0<=__b2 && __b2<=0xf4) { \ - if(__b2<0xf0 ? U8_IS_VALID_LEAD3_AND_T1(__b2, __b1) : \ - U8_IS_VALID_LEAD4_AND_T1(__b2, __b1)) { \ - (length)-=2; \ - } \ - } else if(U8_IS_TRAIL(__b2) && ((length)-3)>=(start)) { \ - uint8_t __b3=s[(length)-3]; \ - if(0xf0<=__b3 && __b3<=0xf4 && U8_IS_VALID_LEAD4_AND_T1(__b3, __b2)) { \ - (length)-=3; \ - } \ - } \ - } \ - } \ -} UPRV_BLOCK_MACRO_END - -/* definitions with backward iteration -------------------------------------- */ - -/** - * Move the string offset from one code point boundary to the previous one - * and get the code point between them. - * (Pre-decrementing backward iteration.) - * "Unsafe" macro, assumes well-formed UTF-8. - * - * The input offset may be the same as the string length. - * If the offset is behind a multi-byte sequence, then the macro will read - * the whole sequence. - * If the offset is behind a lead byte, then that itself - * will be returned as the code point. - * The result is undefined if the offset is behind an illegal UTF-8 sequence. - * - * @param s const uint8_t * string - * @param i string offset - * @param c output UChar32 variable - * @see U8_PREV - * @stable ICU 2.4 - */ -#define U8_PREV_UNSAFE(s, i, c) UPRV_BLOCK_MACRO_BEGIN { \ - (c)=(uint8_t)(s)[--(i)]; \ - if(U8_IS_TRAIL(c)) { \ - uint8_t __b, __count=1, __shift=6; \ -\ - /* c is a trail byte */ \ - (c)&=0x3f; \ - for(;;) { \ - __b=(s)[--(i)]; \ - if(__b>=0xc0) { \ - U8_MASK_LEAD_BYTE(__b, __count); \ - (c)|=(UChar32)__b<<__shift; \ - break; \ - } else { \ - (c)|=(UChar32)(__b&0x3f)<<__shift; \ - ++__count; \ - __shift+=6; \ - } \ - } \ - } \ -} UPRV_BLOCK_MACRO_END - -/** - * Move the string offset from one code point boundary to the previous one - * and get the code point between them. - * (Pre-decrementing backward iteration.) - * "Safe" macro, checks for illegal sequences and for string boundaries. - * - * The input offset may be the same as the string length. - * If the offset is behind a multi-byte sequence, then the macro will read - * the whole sequence. - * If the offset is behind a lead byte, then that itself - * will be returned as the code point. - * If the offset is behind an illegal UTF-8 sequence, then c is set to a negative value. - * - * @param s const uint8_t * string - * @param start int32_t starting string offset (usually 0) - * @param i int32_t string offset, must be start<i - * @param c output UChar32 variable, set to <0 in case of an error - * @see U8_PREV_UNSAFE - * @stable ICU 2.4 - */ -#define U8_PREV(s, start, i, c) UPRV_BLOCK_MACRO_BEGIN { \ - (c)=(uint8_t)(s)[--(i)]; \ - if(!U8_IS_SINGLE(c)) { \ - (c)=utf8_prevCharSafeBody((const uint8_t *)s, start, &(i), c, -1); \ - } \ -} UPRV_BLOCK_MACRO_END - -/** - * Move the string offset from one code point boundary to the previous one - * and get the code point between them. - * (Pre-decrementing backward iteration.) - * "Safe" macro, checks for illegal sequences and for string boundaries. - * - * The input offset may be the same as the string length. - * If the offset is behind a multi-byte sequence, then the macro will read - * the whole sequence. - * If the offset is behind a lead byte, then that itself - * will be returned as the code point. - * If the offset is behind an illegal UTF-8 sequence, then c is set to U+FFFD. - * - * This macro does not distinguish between a real U+FFFD in the text - * and U+FFFD returned for an ill-formed sequence. - * Use U8_PREV() if that distinction is important. - * - * @param s const uint8_t * string - * @param start int32_t starting string offset (usually 0) - * @param i int32_t string offset, must be start<i - * @param c output UChar32 variable, set to U+FFFD in case of an error - * @see U8_PREV - * @stable ICU 51 - */ -#define U8_PREV_OR_FFFD(s, start, i, c) UPRV_BLOCK_MACRO_BEGIN { \ - (c)=(uint8_t)(s)[--(i)]; \ - if(!U8_IS_SINGLE(c)) { \ - (c)=utf8_prevCharSafeBody((const uint8_t *)s, start, &(i), c, -3); \ - } \ -} UPRV_BLOCK_MACRO_END - -/** - * Move the string offset from one code point boundary to the previous one. - * (Pre-decrementing backward iteration.) - * The input offset may be the same as the string length. - * "Unsafe" macro, assumes well-formed UTF-8. - * - * @param s const uint8_t * string - * @param i string offset - * @see U8_BACK_1 - * @stable ICU 2.4 - */ -#define U8_BACK_1_UNSAFE(s, i) UPRV_BLOCK_MACRO_BEGIN { \ - while(U8_IS_TRAIL((s)[--(i)])) {} \ -} UPRV_BLOCK_MACRO_END - -/** - * Move the string offset from one code point boundary to the previous one. - * (Pre-decrementing backward iteration.) - * The input offset may be the same as the string length. - * "Safe" macro, checks for illegal sequences and for string boundaries. - * - * @param s const uint8_t * string - * @param start int32_t starting string offset (usually 0) - * @param i int32_t string offset, must be start<i - * @see U8_BACK_1_UNSAFE - * @stable ICU 2.4 - */ -#define U8_BACK_1(s, start, i) UPRV_BLOCK_MACRO_BEGIN { \ - if(U8_IS_TRAIL((s)[--(i)])) { \ - (i)=utf8_back1SafeBody(s, start, (i)); \ - } \ -} UPRV_BLOCK_MACRO_END - -/** - * Move the string offset from one code point boundary to the n-th one before it, - * i.e., move backward by n code points. - * (Pre-decrementing backward iteration.) - * The input offset may be the same as the string length. - * "Unsafe" macro, assumes well-formed UTF-8. - * - * @param s const uint8_t * string - * @param i string offset - * @param n number of code points to skip - * @see U8_BACK_N - * @stable ICU 2.4 - */ -#define U8_BACK_N_UNSAFE(s, i, n) UPRV_BLOCK_MACRO_BEGIN { \ - int32_t __N=(n); \ - while(__N>0) { \ - U8_BACK_1_UNSAFE(s, i); \ - --__N; \ - } \ -} UPRV_BLOCK_MACRO_END - -/** - * Move the string offset from one code point boundary to the n-th one before it, - * i.e., move backward by n code points. - * (Pre-decrementing backward iteration.) - * The input offset may be the same as the string length. - * "Safe" macro, checks for illegal sequences and for string boundaries. - * - * @param s const uint8_t * string - * @param start int32_t index of the start of the string - * @param i int32_t string offset, must be start<i - * @param n number of code points to skip - * @see U8_BACK_N_UNSAFE - * @stable ICU 2.4 - */ -#define U8_BACK_N(s, start, i, n) UPRV_BLOCK_MACRO_BEGIN { \ - int32_t __N=(n); \ - while(__N>0 && (i)>(start)) { \ - U8_BACK_1(s, start, i); \ - --__N; \ - } \ -} UPRV_BLOCK_MACRO_END - -/** - * Adjust a random-access offset to a code point boundary after a code point. - * If the offset is behind a partial multi-byte sequence, - * then the offset is incremented to behind the whole sequence. - * Otherwise, it is not modified. - * The input offset may be the same as the string length. - * "Unsafe" macro, assumes well-formed UTF-8. - * - * @param s const uint8_t * string - * @param i string offset - * @see U8_SET_CP_LIMIT - * @stable ICU 2.4 - */ -#define U8_SET_CP_LIMIT_UNSAFE(s, i) UPRV_BLOCK_MACRO_BEGIN { \ - U8_BACK_1_UNSAFE(s, i); \ - U8_FWD_1_UNSAFE(s, i); \ -} UPRV_BLOCK_MACRO_END - -/** - * Adjust a random-access offset to a code point boundary after a code point. - * If the offset is behind a partial multi-byte sequence, - * then the offset is incremented to behind the whole sequence. - * Otherwise, it is not modified. - * The input offset may be the same as the string length. - * "Safe" macro, checks for illegal sequences and for string boundaries. - * - * The length can be negative for a NUL-terminated string. - * - * @param s const uint8_t * string - * @param start int32_t starting string offset (usually 0) - * @param i int32_t string offset, must be start<=i<=length - * @param length int32_t string length - * @see U8_SET_CP_LIMIT_UNSAFE - * @stable ICU 2.4 - */ -#define U8_SET_CP_LIMIT(s, start, i, length) UPRV_BLOCK_MACRO_BEGIN { \ - if((start)<(i) && ((i)<(length) || (length)<0)) { \ - U8_BACK_1(s, start, i); \ - U8_FWD_1(s, i, length); \ - } \ -} UPRV_BLOCK_MACRO_END - -#endif diff --git a/test/functional/api/buffer_spec.lua b/test/functional/api/buffer_spec.lua index da7515f012..8ed642b43e 100644 --- a/test/functional/api/buffer_spec.lua +++ b/test/functional/api/buffer_spec.lua @@ -534,6 +534,26 @@ describe('api/buf', function() end) end) + describe('nvim_buf_delete', function() + it('allows for just deleting', function() + nvim('command', 'new') + local b = nvim('get_current_buf') + ok(buffer('is_valid', b)) + nvim('buf_delete', b, {}) + ok(not buffer('is_loaded', b)) + ok(not buffer('is_valid', b)) + end) + + it('allows for just unloading', function() + nvim('command', 'new') + local b = nvim('get_current_buf') + ok(buffer('is_valid', b)) + nvim('buf_delete', b, { unload = true }) + ok(not buffer('is_loaded', b)) + ok(buffer('is_valid', b)) + end) + end) + describe('nvim_buf_get_mark', function() it('works', function() curbuf('set_lines', -1, -1, true, {'a', 'bit of', 'text'}) diff --git a/test/functional/api/extmark_spec.lua b/test/functional/api/extmark_spec.lua index 5bf3fa554f..ab913ba4a4 100644 --- a/test/functional/api/extmark_spec.lua +++ b/test/functional/api/extmark_spec.lua @@ -17,22 +17,14 @@ local function expect(contents) return eq(contents, helpers.curbuf_contents()) end -local function check_undo_redo(ns, mark, sr, sc, er, ec) --s = start, e = end - local rv = curbufmeths.get_extmark_by_id(ns, mark) - eq({er, ec}, rv) - feed("u") - rv = curbufmeths.get_extmark_by_id(ns, mark) - eq({sr, sc}, rv) - feed("<c-r>") - rv = curbufmeths.get_extmark_by_id(ns, mark) - eq({er, ec}, rv) -end - local function set_extmark(ns_id, id, line, col, opts) if opts == nil then opts = {} end - return curbufmeths.set_extmark(ns_id, id, line, col, opts) + if id ~= nil and id ~= 0 then + opts.id = id + end + return curbufmeths.set_extmark(ns_id, line, col, opts) end local function get_extmarks(ns_id, start, end_, opts) @@ -42,6 +34,24 @@ local function get_extmarks(ns_id, start, end_, opts) return curbufmeths.get_extmarks(ns_id, start, end_, opts) end +local function get_extmark_by_id(ns_id, id, opts) + if opts == nil then + opts = {} + end + return curbufmeths.get_extmark_by_id(ns_id, id, opts) +end + +local function check_undo_redo(ns, mark, sr, sc, er, ec) --s = start, e = end + local rv = get_extmark_by_id(ns, mark) + eq({er, ec}, rv) + feed("u") + rv = get_extmark_by_id(ns, mark) + eq({sr, sc}, rv) + feed("<c-r>") + rv = get_extmark_by_id(ns, mark) + eq({er, ec}, rv) +end + local function batch_set(ns_id, positions) local ids = {} for _, pos in ipairs(positions) do @@ -90,10 +100,19 @@ describe('API/extmarks', function() ns2 = request('nvim_create_namespace', "my-fancy-plugin2") end) + it("can end extranges past final newline using end_col = 0", function() + set_extmark(ns, marks[1], 0, 0, { + end_col = 0, + end_line = 1 + }) + eq("end_col value outside range", + pcall_err(set_extmark, ns, marks[2], 0, 0, { end_col = 1, end_line = 1 })) + end) + it('adds, updates and deletes marks', function() local rv = set_extmark(ns, marks[1], positions[1][1], positions[1][2]) eq(marks[1], rv) - rv = curbufmeths.get_extmark_by_id(ns, marks[1]) + rv = get_extmark_by_id(ns, marks[1]) eq({positions[1][1], positions[1][2]}, rv) -- Test adding a second mark on same row works rv = set_extmark(ns, marks[2], positions[2][1], positions[2][2]) @@ -102,14 +121,14 @@ describe('API/extmarks', function() -- Test an update, (same pos) rv = set_extmark(ns, marks[1], positions[1][1], positions[1][2]) eq(marks[1], rv) - rv = curbufmeths.get_extmark_by_id(ns, marks[2]) + rv = get_extmark_by_id(ns, marks[2]) eq({positions[2][1], positions[2][2]}, rv) -- Test an update, (new pos) row = positions[1][1] col = positions[1][2] + 1 rv = set_extmark(ns, marks[1], row, col) eq(marks[1], rv) - rv = curbufmeths.get_extmark_by_id(ns, marks[1]) + rv = get_extmark_by_id(ns, marks[1]) eq({row, col}, rv) -- remove the test marks @@ -204,13 +223,13 @@ describe('API/extmarks', function() eq({marks[2], positions[2][1], positions[2][2]}, rv[2]) -- nextrange with `limit` rv = get_extmarks(ns, marks[1], marks[3], {limit=2}) - eq(2, table.getn(rv)) + eq(2, #rv) -- nextrange with positional when mark exists at position rv = get_extmarks(ns, positions[1], positions[3]) eq({marks[1], positions[1][1], positions[1][2]}, rv[1]) eq({marks[2], positions[2][1], positions[2][2]}, rv[2]) rv = get_extmarks(ns, positions[2], positions[3]) - eq(2, table.getn(rv)) + eq(2, #rv) -- nextrange with positional index (no mark at position) local lower = {positions[1][1], positions[2][2] -1} local upper = {positions[2][1], positions[3][2] - 1} @@ -248,14 +267,14 @@ describe('API/extmarks', function() eq({marks[1], positions[1][1], positions[1][2]}, rv[3]) -- prevrange with limit rv = get_extmarks(ns, marks[3], marks[1], {limit=2}) - eq(2, table.getn(rv)) + eq(2, #rv) -- prevrange with positional when mark exists at position rv = get_extmarks(ns, positions[3], positions[1]) eq({{marks[3], positions[3][1], positions[3][2]}, {marks[2], positions[2][1], positions[2][2]}, {marks[1], positions[1][1], positions[1][2]}}, rv) rv = get_extmarks(ns, positions[2], positions[1]) - eq(2, table.getn(rv)) + eq(2, #rv) -- prevrange with positional index (no mark at position) lower = {positions[2][1], positions[2][2] + 1} upper = {positions[3][1], positions[3][2] + 1} @@ -282,19 +301,19 @@ describe('API/extmarks', function() end local rv = get_extmarks(ns, {0, 0}, {-1, -1}, {limit=1}) - eq(1, table.getn(rv)) + eq(1, #rv) rv = get_extmarks(ns, {0, 0}, {-1, -1}, {limit=2}) - eq(2, table.getn(rv)) + eq(2, #rv) rv = get_extmarks(ns, {0, 0}, {-1, -1}, {limit=3}) - eq(3, table.getn(rv)) + eq(3, #rv) -- now in reverse rv = get_extmarks(ns, {0, 0}, {-1, -1}, {limit=1}) - eq(1, table.getn(rv)) + eq(1, #rv) rv = get_extmarks(ns, {0, 0}, {-1, -1}, {limit=2}) - eq(2, table.getn(rv)) + eq(2, #rv) rv = get_extmarks(ns, {0, 0}, {-1, -1}, {limit=3}) - eq(3, table.getn(rv)) + eq(3, #rv) end) it('get_marks works when mark col > upper col', function() @@ -432,7 +451,7 @@ describe('API/extmarks', function() ~ | | ]]) - local rv = curbufmeths.get_extmark_by_id(ns, marks[1]) + local rv = get_extmark_by_id(ns, marks[1]) eq({0, 6}, rv) check_undo_redo(ns, marks[1], 0, 3, 0, 6) end) @@ -798,17 +817,17 @@ describe('API/extmarks', function() feed("u") local rv = get_extmarks(ns, {0, 0}, {-1, -1}) - eq(3, table.getn(rv)) + eq(3, #rv) feed("<c-r>") rv = get_extmarks(ns, {0, 0}, {-1, -1}) - eq(3, table.getn(rv)) + eq(3, #rv) -- Test updates feed('o<esc>') set_extmark(ns, marks[1], positions[1][1], positions[1][2]) rv = get_extmarks(ns, marks[1], marks[1], {limit=1}) - eq(1, table.getn(rv)) + eq(1, #rv) feed("u") feed("<c-r>") -- old value is NOT kept in history @@ -820,10 +839,10 @@ describe('API/extmarks', function() feed("u") rv = get_extmarks(ns, {0, 0}, {-1, -1}) -- undo does NOT restore deleted marks - eq(2, table.getn(rv)) + eq(2, #rv) feed("<c-r>") rv = get_extmarks(ns, {0, 0}, {-1, -1}) - eq(2, table.getn(rv)) + eq(2, #rv) end) it('undo and redo of marks deleted during edits', function() @@ -840,9 +859,9 @@ describe('API/extmarks', function() rv = set_extmark(ns2, marks[1], positions[1][1], positions[1][2]) eq(1, rv) rv = get_extmarks(ns, {0, 0}, {-1, -1}) - eq(1, table.getn(rv)) + eq(1, #rv) rv = get_extmarks(ns2, {0, 0}, {-1, -1}) - eq(1, table.getn(rv)) + eq(1, #rv) -- Set more marks for testing the ranges set_extmark(ns, marks[2], positions[2][1], positions[2][2]) @@ -852,32 +871,32 @@ describe('API/extmarks', function() -- get_next (limit set) rv = get_extmarks(ns, {0, 0}, positions[2], {limit=1}) - eq(1, table.getn(rv)) + eq(1, #rv) rv = get_extmarks(ns2, {0, 0}, positions[2], {limit=1}) - eq(1, table.getn(rv)) + eq(1, #rv) -- get_prev (limit set) rv = get_extmarks(ns, positions[1], {0, 0}, {limit=1}) - eq(1, table.getn(rv)) + eq(1, #rv) rv = get_extmarks(ns2, positions[1], {0, 0}, {limit=1}) - eq(1, table.getn(rv)) + eq(1, #rv) -- get_next (no limit) rv = get_extmarks(ns, positions[1], positions[2]) - eq(2, table.getn(rv)) + eq(2, #rv) rv = get_extmarks(ns2, positions[1], positions[2]) - eq(2, table.getn(rv)) + eq(2, #rv) -- get_prev (no limit) rv = get_extmarks(ns, positions[2], positions[1]) - eq(2, table.getn(rv)) + eq(2, #rv) rv = get_extmarks(ns2, positions[2], positions[1]) - eq(2, table.getn(rv)) + eq(2, #rv) curbufmeths.del_extmark(ns, marks[1]) rv = get_extmarks(ns, {0, 0}, {-1, -1}) - eq(2, table.getn(rv)) + eq(2, #rv) curbufmeths.del_extmark(ns2, marks[1]) rv = get_extmarks(ns2, {0, 0}, {-1, -1}) - eq(2, table.getn(rv)) + eq(2, #rv) end) it('mark set can create unique identifiers', function() @@ -906,9 +925,9 @@ describe('API/extmarks', function() -- Set the mark before the cursor, should stay there set_extmark(ns, marks[2], 0, 10) feed("i<cr><esc>") - local rv = curbufmeths.get_extmark_by_id(ns, marks[1]) + local rv = get_extmark_by_id(ns, marks[1]) eq({1, 3}, rv) - rv = curbufmeths.get_extmark_by_id(ns, marks[2]) + rv = get_extmark_by_id(ns, marks[2]) eq({0, 10}, rv) check_undo_redo(ns, marks[1], 0, 12, 1, 3) end) @@ -921,12 +940,12 @@ describe('API/extmarks', function() feed("0iint <esc>A {<cr><esc>0i1M1<esc>") set_extmark(ns, marks[1], 1, 1) feed("0i<c-f><esc>") - local rv = curbufmeths.get_extmark_by_id(ns, marks[1]) + local rv = get_extmark_by_id(ns, marks[1]) eq({1, 3}, rv) check_undo_redo(ns, marks[1], 1, 1, 1, 3) -- now check when cursor at eol feed("uA<c-f><esc>") - rv = curbufmeths.get_extmark_by_id(ns, marks[1]) + rv = get_extmark_by_id(ns, marks[1]) eq({1, 3}, rv) end) @@ -937,12 +956,12 @@ describe('API/extmarks', function() feed("0i<tab><esc>") set_extmark(ns, marks[1], 0, 3) feed("bi<c-d><esc>") - local rv = curbufmeths.get_extmark_by_id(ns, marks[1]) + local rv = get_extmark_by_id(ns, marks[1]) eq({0, 1}, rv) check_undo_redo(ns, marks[1], 0, 3, 0, 1) -- check when cursor at eol feed("uA<c-d><esc>") - rv = curbufmeths.get_extmark_by_id(ns, marks[1]) + rv = get_extmark_by_id(ns, marks[1]) eq({0, 1}, rv) end) @@ -1072,7 +1091,7 @@ describe('API/extmarks', function() check_undo_redo(ns, marks[5], 2, 0, 3, 0) feed('u') feed([[:1,2s:3:\rxx<cr>]]) - eq({1, 3}, curbufmeths.get_extmark_by_id(ns, marks[3])) + eq({1, 3}, get_extmark_by_id(ns, marks[3])) end) it('substitions over multiple lines with replace in substition', function() @@ -1311,16 +1330,16 @@ describe('API/extmarks', function() eq("Invalid ns_id", pcall_err(set_extmark, ns_invalid, marks[1], positions[1][1], positions[1][2])) eq("Invalid ns_id", pcall_err(curbufmeths.del_extmark, ns_invalid, marks[1])) eq("Invalid ns_id", pcall_err(get_extmarks, ns_invalid, positions[1], positions[2])) - eq("Invalid ns_id", pcall_err(curbufmeths.get_extmark_by_id, ns_invalid, marks[1])) + eq("Invalid ns_id", pcall_err(get_extmark_by_id, ns_invalid, marks[1])) end) it('when col = line-length, set the mark on eol', function() set_extmark(ns, marks[1], 0, -1) - local rv = curbufmeths.get_extmark_by_id(ns, marks[1]) + local rv = get_extmark_by_id(ns, marks[1]) eq({0, init_text:len()}, rv) -- Test another set_extmark(ns, marks[1], 0, -1) - rv = curbufmeths.get_extmark_by_id(ns, marks[1]) + rv = get_extmark_by_id(ns, marks[1]) eq({0, init_text:len()}, rv) end) @@ -1333,7 +1352,7 @@ describe('API/extmarks', function() local invalid_col = init_text:len() + 1 local invalid_lnum = 3 eq('line value outside range', pcall_err(set_extmark, ns, marks[1], invalid_lnum, invalid_col)) - eq({}, curbufmeths.get_extmark_by_id(ns, marks[1])) + eq({}, get_extmark_by_id(ns, marks[1])) end) it('bug from check_col in extmark_set', function() @@ -1357,14 +1376,14 @@ describe('API/extmarks', function() it('can set a mark to other buffer', function() local buf = request('nvim_create_buf', 0, 1) request('nvim_buf_set_lines', buf, 0, -1, 1, {"", ""}) - local id = bufmeths.set_extmark(buf, ns, 0, 1, 0, {}) + local id = bufmeths.set_extmark(buf, ns, 1, 0, {}) eq({{id, 1, 0}}, bufmeths.get_extmarks(buf, ns, 0, -1, {})) end) it('does not crash with append/delete/undo seqence', function() meths.exec([[ let ns = nvim_create_namespace('myplugin') - call nvim_buf_set_extmark(0, ns, 0, 0, 0, {}) + call nvim_buf_set_extmark(0, ns, 0, 0, {}) call append(0, '') %delete undo]],false) diff --git a/test/functional/api/highlight_spec.lua b/test/functional/api/highlight_spec.lua index a9d4c72d31..058706718a 100644 --- a/test/functional/api/highlight_spec.lua +++ b/test/functional/api/highlight_spec.lua @@ -7,6 +7,7 @@ local meths = helpers.meths local funcs = helpers.funcs local pcall_err = helpers.pcall_err local ok = helpers.ok +local assert_alive = helpers.assert_alive describe('API: highlight',function() local expected_rgb = { @@ -56,7 +57,7 @@ describe('API: highlight',function() -- Test nil argument. err, emsg = pcall(meths.get_hl_by_id, { nil }, false) eq(false, err) - eq('Wrong type for argument 1, expecting Integer', + eq('Wrong type for argument 1 when calling nvim_get_hl_by_id, expecting Integer', string.match(emsg, 'Wrong.*')) -- Test 0 argument. @@ -111,7 +112,7 @@ describe('API: highlight',function() -- Test nil argument. err, emsg = pcall(meths.get_hl_by_name , { nil }, false) eq(false, err) - eq('Wrong type for argument 1, expecting String', + eq('Wrong type for argument 1 when calling nvim_get_hl_by_name, expecting String', string.match(emsg, 'Wrong.*')) -- Test empty string argument. @@ -145,4 +146,15 @@ describe('API: highlight',function() eq({foreground=tonumber("0x888888"), background=tonumber("0x888888")}, meths.get_hl_by_name("Shrubbery", true)) end) + + it("nvim_buf_add_highlight to other buffer doesn't crash if undo is disabled #12873", function() + command('vsplit file') + local err, _ = pcall(meths.buf_set_option, 1, 'undofile', false) + eq(true, err) + err, _ = pcall(meths.buf_set_option, 1, 'undolevels', -1) + eq(true, err) + err, _ = pcall(meths.buf_add_highlight, 1, -1, 'Question', 0, 0, -1) + eq(true, err) + assert_alive() + end) end) diff --git a/test/functional/api/keymap_spec.lua b/test/functional/api/keymap_spec.lua index 5da2c6b531..d8a9c3b411 100644 --- a/test/functional/api/keymap_spec.lua +++ b/test/functional/api/keymap_spec.lua @@ -751,7 +751,7 @@ describe('nvim_buf_set_keymap, nvim_buf_del_keymap', function() end it('rejects negative bufnr values', function() - eq('Wrong type for argument 1, expecting Buffer', + eq('Wrong type for argument 1 when calling nvim_buf_set_keymap, expecting Buffer', pcall_err(bufmeths.set_keymap, -1, '', 'lhs', 'rhs', {})) end) diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index 72e810e3e4..7948f7da09 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -449,19 +449,19 @@ describe('API', function() end) it('reports errors', function() - eq([[Error loading lua: [string "<nvim>"]:1: '=' expected near '+']], + eq([[Error loading lua: [string "<nvim>"]:0: '=' expected near '+']], pcall_err(meths.exec_lua, 'a+*b', {})) - eq([[Error loading lua: [string "<nvim>"]:1: unexpected symbol near '1']], + eq([[Error loading lua: [string "<nvim>"]:0: unexpected symbol near '1']], pcall_err(meths.exec_lua, '1+2', {})) - eq([[Error loading lua: [string "<nvim>"]:1: unexpected symbol]], + eq([[Error loading lua: [string "<nvim>"]:0: unexpected symbol]], pcall_err(meths.exec_lua, 'aa=bb\0', {})) - eq([[Error executing lua: [string "<nvim>"]:1: attempt to call global 'bork' (a nil value)]], + eq([[Error executing lua: [string "<nvim>"]:0: attempt to call global 'bork' (a nil value)]], pcall_err(meths.exec_lua, 'bork()', {})) - eq('Error executing lua: [string "<nvim>"]:1: did\nthe\nfail', + eq('Error executing lua: [string "<nvim>"]:0: did\nthe\nfail', pcall_err(meths.exec_lua, 'error("did\\nthe\\nfail")', {})) end) @@ -605,7 +605,7 @@ describe('API', function() end) it('vim.paste() failure', function() nvim('exec_lua', 'vim.paste = (function(lines, phase) error("fake fail") end)', {}) - eq([[Error executing lua: [string "<nvim>"]:1: fake fail]], + eq([[Error executing lua: [string "<nvim>"]:0: fake fail]], pcall_err(request, 'nvim_paste', 'line 1\nline 2\nline 3', false, 1)) end) end) @@ -1252,7 +1252,7 @@ describe('API', function() {0:~ }| {1:very fail} | ]]) - helpers.wait() + helpers.poke_eventloop() -- shows up to &cmdheight lines nvim_async('err_write', 'more fail\ntoo fail\n') @@ -1350,7 +1350,7 @@ describe('API', function() eq({info=info}, meths.get_var("info_event")) eq({[1]=testinfo,[2]=stderr,[3]=info}, meths.list_chans()) - eq("Vim:Error invoking 'nvim_set_current_buf' on channel 3 (amazing-cat):\nWrong type for argument 1, expecting Buffer", + eq("Vim:Error invoking 'nvim_set_current_buf' on channel 3 (amazing-cat):\nWrong type for argument 1 when calling nvim_set_current_buf, expecting Buffer", pcall_err(eval, 'rpcrequest(3, "nvim_set_current_buf", -1)')) end) @@ -1512,7 +1512,7 @@ describe('API', function() it("does not leak memory on incorrect argument types", function() local status, err = pcall(nvim, 'set_current_dir',{'not', 'a', 'dir'}) eq(false, status) - ok(err:match(': Wrong type for argument 1, expecting String') ~= nil) + ok(err:match(': Wrong type for argument 1 when calling nvim_set_current_dir, expecting String') ~= nil) end) describe('nvim_parse_expression', function() diff --git a/test/functional/api/window_spec.lua b/test/functional/api/window_spec.lua index 8c7c3208c0..7471f50dbd 100644 --- a/test/functional/api/window_spec.lua +++ b/test/functional/api/window_spec.lua @@ -3,7 +3,7 @@ local clear, nvim, curbuf, curbuf_contents, window, curwin, eq, neq, ok, feed, insert, eval = helpers.clear, helpers.nvim, helpers.curbuf, helpers.curbuf_contents, helpers.window, helpers.curwin, helpers.eq, helpers.neq, helpers.ok, helpers.feed, helpers.insert, helpers.eval -local wait = helpers.wait +local poke_eventloop = helpers.poke_eventloop local curwinmeths = helpers.curwinmeths local funcs = helpers.funcs local request = helpers.request @@ -82,7 +82,7 @@ describe('API/win', function() insert("epilogue") local win = curwin() feed('gg') - wait() -- let nvim process the 'gg' command + poke_eventloop() -- let nvim process the 'gg' command -- cursor position is at beginning eq({1, 0}, window('get_cursor', win)) @@ -128,7 +128,7 @@ describe('API/win', function() insert("second line") feed('gg') - wait() -- let nvim process the 'gg' command + poke_eventloop() -- let nvim process the 'gg' command -- cursor position is at beginning local win = curwin() @@ -139,7 +139,7 @@ describe('API/win', function() -- move down a line feed('j') - wait() -- let nvim process the 'j' command + poke_eventloop() -- let nvim process the 'j' command -- cursor is still in column 5 eq({2, 5}, window('get_cursor', win)) diff --git a/test/functional/autocmd/bufmodifiedset_spec.lua b/test/functional/autocmd/bufmodifiedset_spec.lua new file mode 100644 index 0000000000..c566361e37 --- /dev/null +++ b/test/functional/autocmd/bufmodifiedset_spec.lua @@ -0,0 +1,22 @@ +local helpers = require('test.functional.helpers')(after_each) + +local clear = helpers.clear +local eq = helpers.eq +local eval = helpers.eval +local source = helpers.source +local request = helpers.request + +describe('BufModified', function() + before_each(clear) + + it('is triggered when modified and un-modified', function() + source([[ + let g:modified = 0 + autocmd BufModifiedSet * let g:modified += 1 + ]]) + request("nvim_command", [[normal! aa\<Esc>]]) + eq(1, eval('g:modified')) + request("nvim_command", [[normal! u]]) + eq(2, eval('g:modified')) + end) +end) diff --git a/test/functional/autocmd/dirchanged_spec.lua b/test/functional/autocmd/dirchanged_spec.lua index 7979e91a4c..6f2da24cf2 100644 --- a/test/functional/autocmd/dirchanged_spec.lua +++ b/test/functional/autocmd/dirchanged_spec.lua @@ -29,15 +29,15 @@ describe('autocmd DirChanged', function() it('sets v:event', function() command('lcd '..dirs[1]) - eq({cwd=dirs[1], scope='window'}, eval('g:ev')) + eq({cwd=dirs[1], scope='window', changed_window=false}, eval('g:ev')) eq(1, eval('g:cdcount')) command('tcd '..dirs[2]) - eq({cwd=dirs[2], scope='tab'}, eval('g:ev')) + eq({cwd=dirs[2], scope='tab', changed_window=false}, eval('g:ev')) eq(2, eval('g:cdcount')) command('cd '..dirs[3]) - eq({cwd=dirs[3], scope='global'}, eval('g:ev')) + eq({cwd=dirs[3], scope='global', changed_window=false}, eval('g:ev')) eq(3, eval('g:cdcount')) end) @@ -57,7 +57,7 @@ describe('autocmd DirChanged', function() -- Set up a _nested_ handler. command('autocmd DirChanged * nested lcd '..dirs[3]) command('lcd '..dirs[1]) - eq({cwd=dirs[1], scope='window'}, eval('g:ev')) + eq({cwd=dirs[1], scope='window', changed_window=false}, eval('g:ev')) eq(1, eval('g:cdcount')) -- autocmd changed to dirs[3], but did NOT trigger another DirChanged. eq(dirs[3], eval('getcwd()')) @@ -105,10 +105,10 @@ describe('autocmd DirChanged', function() command('set autochdir') command('split '..dirs[1]..'/foo') - eq({cwd=dirs[1], scope='window'}, eval('g:ev')) + eq({cwd=dirs[1], scope='window', changed_window=false}, eval('g:ev')) command('split '..dirs[2]..'/bar') - eq({cwd=dirs[2], scope='window'}, eval('g:ev')) + eq({cwd=dirs[2], scope='window', changed_window=false}, eval('g:ev')) eq(2, eval('g:cdcount')) end) @@ -121,16 +121,16 @@ describe('autocmd DirChanged', function() command('lcd '..dirs[1]) command('2wincmd w') -- window 2 - eq({cwd=dirs[2], scope='window'}, eval('g:ev')) + eq({cwd=dirs[2], scope='window', changed_window=true}, eval('g:ev')) eq(4, eval('g:cdcount')) command('tabnew') -- tab 2 (tab-local CWD) eq(4, eval('g:cdcount')) -- same CWD, no DirChanged event command('tcd '..dirs[3]) command('tabnext') -- tab 1 (no tab-local CWD) - eq({cwd=dirs[2], scope='window'}, eval('g:ev')) + eq({cwd=dirs[2], scope='window', changed_window=true}, eval('g:ev')) command('tabnext') -- tab 2 - eq({cwd=dirs[3], scope='tab'}, eval('g:ev')) + eq({cwd=dirs[3], scope='tab', changed_window=true}, eval('g:ev')) eq(7, eval('g:cdcount')) command('tabnext') -- tab 1 @@ -142,17 +142,17 @@ describe('autocmd DirChanged', function() it('is triggered by nvim_set_current_dir()', function() request('nvim_set_current_dir', dirs[1]) - eq({cwd=dirs[1], scope='global'}, eval('g:ev')) + eq({cwd=dirs[1], scope='global', changed_window=false}, eval('g:ev')) request('nvim_set_current_dir', dirs[2]) - eq({cwd=dirs[2], scope='global'}, eval('g:ev')) + eq({cwd=dirs[2], scope='global', changed_window=false}, eval('g:ev')) local status, err = pcall(function() request('nvim_set_current_dir', '/doesnotexist') end) eq(false, status) eq('Failed to change directory', string.match(err, ': (.*)')) - eq({cwd=dirs[2], scope='global'}, eval('g:ev')) + eq({cwd=dirs[2], scope='global', changed_window=false}, eval('g:ev')) end) it('works when local to buffer', function() diff --git a/test/functional/autocmd/winscrolled_spec.lua b/test/functional/autocmd/winscrolled_spec.lua new file mode 100644 index 0000000000..1ef5a37479 --- /dev/null +++ b/test/functional/autocmd/winscrolled_spec.lua @@ -0,0 +1,62 @@ +local helpers = require('test.functional.helpers')(after_each) + +local clear = helpers.clear +local eq = helpers.eq +local eval = helpers.eval +local source = helpers.source + +describe('WinScrolled', function() + before_each(clear) + + it('is triggered by scrolling vertically', function() + source([[ + set nowrap + let width = winwidth(0) + let line = '123' . repeat('*', width * 2) + let lines = [line, line] + call nvim_buf_set_lines(0, 0, -1, v:true, lines) + + let g:scrolled = 0 + autocmd WinScrolled * let g:scrolled += 1 + execute "normal! \<C-e>" + ]]) + eq(1, eval('g:scrolled')) + end) + + it('is triggered by scrolling horizontally', function() + source([[ + set nowrap + let width = winwidth(0) + let line = '123' . repeat('*', width * 2) + let lines = [line, line] + call nvim_buf_set_lines(0, 0, -1, v:true, lines) + + let g:scrolled = 0 + autocmd WinScrolled * let g:scrolled += 1 + execute "normal! zl" + ]]) + eq(1, eval('g:scrolled')) + end) + + it('is triggered when the window scrolls in insert mode', function() + source([[ + let height = winheight(0) + let lines = map(range(height * 2), {_, i -> string(i)}) + call nvim_buf_set_lines(0, 0, -1, v:true, lines) + + let g:scrolled = 0 + autocmd WinScrolled * let g:scrolled += 1 + call feedkeys("LA\<CR><Esc>", "n") + ]]) + eq(2, eval('g:scrolled')) + end) + + it('is triggered when the window is resized', function() + source([[ + let g:scrolled = 0 + autocmd WinScrolled * let g:scrolled += 1 + wincmd v + ]]) + eq(1, eval('g:scrolled')) + end) +end) diff --git a/test/functional/core/exit_spec.lua b/test/functional/core/exit_spec.lua index 80c65e4544..230b7f8e01 100644 --- a/test/functional/core/exit_spec.lua +++ b/test/functional/core/exit_spec.lua @@ -8,7 +8,7 @@ local run = helpers.run local funcs = helpers.funcs local nvim_prog = helpers.nvim_prog local redir_exec = helpers.redir_exec -local wait = helpers.wait +local poke_eventloop = helpers.poke_eventloop describe('v:exiting', function() local cid @@ -52,7 +52,7 @@ describe(':cquit', function() local function test_cq(cmdline, exit_code, redir_msg) if redir_msg then eq('\n' .. redir_msg, redir_exec(cmdline)) - wait() + poke_eventloop() eq(2, eval("1+1")) -- Still alive? else funcs.system({nvim_prog, '-u', 'NONE', '-i', 'NONE', '--headless', '--cmd', cmdline}) diff --git a/test/functional/core/job_spec.lua b/test/functional/core/job_spec.lua index 57e6f4fd63..6d1182478a 100644 --- a/test/functional/core/job_spec.lua +++ b/test/functional/core/job_spec.lua @@ -11,7 +11,7 @@ local os_kill = helpers.os_kill local retry = helpers.retry local meths = helpers.meths local NIL = helpers.NIL -local wait = helpers.wait +local poke_eventloop = helpers.poke_eventloop local iswin = helpers.iswin local get_pathsep = helpers.get_pathsep local pathroot = helpers.pathroot @@ -93,6 +93,7 @@ describe('jobs', function() {'notification', 'stdout', {0, {'hello world %VAR%', ''}}} }) else + nvim('command', "set shell=/bin/sh") nvim('command', [[call jobstart('echo $TOTO $VAR', g:job_opts)]]) expect_msg_seq({ {'notification', 'stdout', {0, {'hello world', ''}}} @@ -427,7 +428,7 @@ describe('jobs', function() \ } let job = jobstart(['cat', '-'], g:callbacks) ]]) - wait() + poke_eventloop() source([[ function! g:JobHandler(job_id, data, event) endfunction diff --git a/test/functional/core/startup_spec.lua b/test/functional/core/startup_spec.lua index 9b0668f9e6..3bfae5f3d7 100644 --- a/test/functional/core/startup_spec.lua +++ b/test/functional/core/startup_spec.lua @@ -7,6 +7,7 @@ local ok = helpers.ok local eq = helpers.eq local matches = helpers.matches local eval = helpers.eval +local exec_lua = helpers.exec_lua local feed = helpers.feed local funcs = helpers.funcs local mkdir = helpers.mkdir @@ -305,6 +306,27 @@ describe('startup', function() '+q' }) eq('[\'+q\'] 1', out) end) + + local function pack_clear(cmd) + clear('--cmd', 'set packpath=test/functional/fixtures', '--cmd', cmd) + end + + + it("handles &packpath during startup", function() + pack_clear [[ let g:x = bar#test() ]] + eq(-3, eval 'g:x') + + pack_clear [[ lua _G.y = require'bar'.doit() ]] + eq(9003, exec_lua [[ return _G.y ]]) + end) + + it("handles :packadd during startup", function() + pack_clear [[ packadd! bonus | let g:x = bonus#secret() ]] + eq('halloj', eval 'g:x') + + pack_clear [[ packadd! bonus | lua _G.y = require'bonus'.launch() ]] + eq('CPE 1704 TKS', exec_lua [[ return _G.y ]]) + end) end) describe('sysinit', function() diff --git a/test/functional/eval/api_functions_spec.lua b/test/functional/eval/api_functions_spec.lua index ccd97fc8c7..ed110efeb4 100644 --- a/test/functional/eval/api_functions_spec.lua +++ b/test/functional/eval/api_functions_spec.lua @@ -35,13 +35,13 @@ describe('eval-API', function() eq('Vim(call):E119: Not enough arguments for function: nvim_set_option', err) err = exc_exec('call nvim_buf_set_lines(1, 0, -1, [], ["list"])') - eq('Vim(call):E5555: API call: Wrong type for argument 4, expecting Boolean', err) + eq('Vim(call):E5555: API call: Wrong type for argument 4 when calling nvim_buf_set_lines, expecting Boolean', err) err = exc_exec('call nvim_buf_set_lines(0, 0, -1, v:true, "string")') - eq('Vim(call):E5555: API call: Wrong type for argument 5, expecting ArrayOf(String)', err) + eq('Vim(call):E5555: API call: Wrong type for argument 5 when calling nvim_buf_set_lines, expecting ArrayOf(String)', err) err = exc_exec('call nvim_buf_get_number("0")') - eq('Vim(call):E5555: API call: Wrong type for argument 1, expecting Buffer', err) + eq('Vim(call):E5555: API call: Wrong type for argument 1 when calling nvim_buf_get_number, expecting Buffer', err) err = exc_exec('call nvim_buf_line_count(17)') eq('Vim(call):E5555: API call: Invalid buffer id: 17', err) diff --git a/test/functional/eval/interrupt_spec.lua b/test/functional/eval/interrupt_spec.lua index 7f4ca95317..05b1f4ff57 100644 --- a/test/functional/eval/interrupt_spec.lua +++ b/test/functional/eval/interrupt_spec.lua @@ -4,7 +4,7 @@ local command = helpers.command local meths = helpers.meths local clear = helpers.clear local sleep = helpers.sleep -local wait = helpers.wait +local poke_eventloop = helpers.poke_eventloop local feed = helpers.feed local eq = helpers.eq @@ -39,7 +39,7 @@ describe('List support code', function() feed(':let t_rt = reltime()<CR>:let t_bl = copy(bl)<CR>') sleep(min_dur / 16 * 1000) feed('<C-c>') - wait() + poke_eventloop() command('let t_dur = reltimestr(reltime(t_rt))') local t_dur = tonumber(meths.get_var('t_dur')) if t_dur >= dur / 8 then @@ -50,7 +50,7 @@ describe('List support code', function() feed(':let t_rt = reltime()<CR>:let t_j = join(bl)<CR>') sleep(min_dur / 16 * 1000) feed('<C-c>') - wait() + poke_eventloop() command('let t_dur = reltimestr(reltime(t_rt))') local t_dur = tonumber(meths.get_var('t_dur')) print(('t_dur: %g'):format(t_dur)) diff --git a/test/functional/eval/null_spec.lua b/test/functional/eval/null_spec.lua index db0a706319..fa8f7d873f 100644 --- a/test/functional/eval/null_spec.lua +++ b/test/functional/eval/null_spec.lua @@ -132,7 +132,7 @@ describe('NULL', function() end) describe('dict', function() it('does not crash when indexing NULL dict', function() - eq('\nE716: Key not present in Dictionary: test\nE15: Invalid expression: v:_null_dict.test', + eq('\nE716: Key not present in Dictionary: "test"\nE15: Invalid expression: v:_null_dict.test', redir_exec('echo v:_null_dict.test')) end) null_expr_test('makes extend error out', 'extend(D, {})', 'E742: Cannot change value of extend() argument', 0) diff --git a/test/functional/ex_cmds/oldfiles_spec.lua b/test/functional/ex_cmds/oldfiles_spec.lua index 802c3f68c6..003ab64dd4 100644 --- a/test/functional/ex_cmds/oldfiles_spec.lua +++ b/test/functional/ex_cmds/oldfiles_spec.lua @@ -3,7 +3,7 @@ local helpers = require('test.functional.helpers')(after_each) local clear = helpers.clear local buf, eq, feed_command = helpers.curbufmeths, helpers.eq, helpers.feed_command -local feed, wait = helpers.feed, helpers.wait +local feed, poke_eventloop = helpers.feed, helpers.poke_eventloop local ok = helpers.ok local eval = helpers.eval @@ -90,7 +90,7 @@ describe(':browse oldfiles', function() feed_command('edit testfile2') filename2 = buf.get_name() feed_command('wshada') - wait() + poke_eventloop() _clear() -- Ensure nvim is out of "Press ENTER..." prompt. diff --git a/test/functional/fixtures/fake-lsp-server.lua b/test/functional/fixtures/fake-lsp-server.lua index dca7f35923..a30eb748d0 100644 --- a/test/functional/fixtures/fake-lsp-server.lua +++ b/test/functional/fixtures/fake-lsp-server.lua @@ -125,6 +125,26 @@ function tests.basic_check_capabilities() } end +function tests.capabilities_for_client_supports_method() + skeleton { + on_init = function(params) + local expected_capabilities = protocol.make_client_capabilities() + assert_eq(params.capabilities, expected_capabilities) + return { + capabilities = { + textDocumentSync = protocol.TextDocumentSyncKind.Full; + completionProvider = true; + hoverProvider = true; + definitionProvider = false; + referencesProvider = false; + } + } + end; + body = function() + end; + } +end + function tests.basic_finish() skeleton { on_init = function(params) diff --git a/test/functional/fixtures/pack/foo/opt/bonus/autoload/bonus.vim b/test/functional/fixtures/pack/foo/opt/bonus/autoload/bonus.vim new file mode 100644 index 0000000000..5ed8b1b887 --- /dev/null +++ b/test/functional/fixtures/pack/foo/opt/bonus/autoload/bonus.vim @@ -0,0 +1,3 @@ +func bonus#secret() + return "halloj" +endfunc diff --git a/test/functional/fixtures/pack/foo/opt/bonus/lua/bonus.lua b/test/functional/fixtures/pack/foo/opt/bonus/lua/bonus.lua new file mode 100644 index 0000000000..52cb0bc118 --- /dev/null +++ b/test/functional/fixtures/pack/foo/opt/bonus/lua/bonus.lua @@ -0,0 +1 @@ +return {launch=function() return "CPE 1704 TKS" end} diff --git a/test/functional/fixtures/pack/foo/start/bar/autoload/bar.vim b/test/functional/fixtures/pack/foo/start/bar/autoload/bar.vim new file mode 100644 index 0000000000..405e7be71c --- /dev/null +++ b/test/functional/fixtures/pack/foo/start/bar/autoload/bar.vim @@ -0,0 +1,3 @@ +func bar#test() + return -3 +endfunc diff --git a/test/functional/fixtures/pack/foo/start/bar/lua/bar.lua b/test/functional/fixtures/pack/foo/start/bar/lua/bar.lua new file mode 100644 index 0000000000..a7e9a61e35 --- /dev/null +++ b/test/functional/fixtures/pack/foo/start/bar/lua/bar.lua @@ -0,0 +1 @@ +return {doit=function() return 9003 end} diff --git a/test/functional/fixtures/streams-test.c b/test/functional/fixtures/streams-test.c index 56d475d7dc..eec447153c 100644 --- a/test/functional/fixtures/streams-test.c +++ b/test/functional/fixtures/streams-test.c @@ -1,3 +1,6 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check +// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + /// Helper program to exit and keep stdout open (like "xclip -i -loops 1"). #include <stdio.h> diff --git a/test/functional/helpers.lua b/test/functional/helpers.lua index e8435cd3b7..d85a6a3cfe 100644 --- a/test/functional/helpers.lua +++ b/test/functional/helpers.lua @@ -22,6 +22,7 @@ local ok = global_helpers.ok local sleep = global_helpers.sleep local tbl_contains = global_helpers.tbl_contains local write_file = global_helpers.write_file +local fail = global_helpers.fail local module = { NIL = mpack.NIL, @@ -553,9 +554,9 @@ function module.curbuf(method, ...) return module.buffer(method, 0, ...) end -function module.wait() - -- Execute 'nvim_eval' (a deferred function) to block - -- until all pending input is processed. +function module.poke_eventloop() + -- Execute 'nvim_eval' (a deferred function) to + -- force at least one main_loop iteration session:request('nvim_eval', '1') end @@ -565,7 +566,7 @@ end --@see buf_lines() function module.curbuf_contents() - module.wait() -- Before inspecting the buffer, process all input. + module.poke_eventloop() -- Before inspecting the buffer, do whatever. return table.concat(module.curbuf('get_lines', 0, -1, true), '\n') end @@ -592,6 +593,24 @@ function module.expect_any(contents) return ok(nil ~= string.find(module.curbuf_contents(), contents, 1, true)) end +function module.expect_events(expected, received, kind) + local inspect = require'vim.inspect' + if not pcall(eq, expected, received) then + local msg = 'unexpected '..kind..' received.\n\n' + + msg = msg .. 'received events:\n' + for _, e in ipairs(received) do + msg = msg .. ' ' .. inspect(e) .. ';\n' + end + msg = msg .. '\nexpected events:\n' + for _, e in ipairs(expected) do + msg = msg .. ' ' .. inspect(e) .. ';\n' + end + fail(msg) + end + return received +end + -- Checks that the Nvim session did not terminate. function module.assert_alive() assert(2 == module.eval('1+1'), 'crash? request failed') @@ -732,6 +751,14 @@ module.curbufmeths = module.create_callindex(module.curbuf) module.curwinmeths = module.create_callindex(module.curwin) module.curtabmeths = module.create_callindex(module.curtab) +function module.exec(code) + return module.meths.exec(code, false) +end + +function module.exec_capture(code) + return module.meths.exec(code, true) +end + function module.exec_lua(code, ...) return module.meths.exec_lua(code, {...}) end @@ -769,14 +796,14 @@ end function module.missing_provider(provider) if provider == 'ruby' or provider == 'node' or provider == 'perl' then - local prog = module.funcs['provider#' .. provider .. '#Detect']() - return prog == '' and (provider .. ' not detected') or false + local e = module.funcs['provider#'..provider..'#Detect']()[2] + return e ~= '' and e or false elseif provider == 'python' or provider == 'python3' then local py_major_version = (provider == 'python3' and 3 or 2) - local errors = module.funcs['provider#pythonx#Detect'](py_major_version)[2] - return errors ~= '' and errors or false + local e = module.funcs['provider#pythonx#Detect'](py_major_version)[2] + return e ~= '' and e or false else - assert(false, 'Unknown provider: ' .. provider) + assert(false, 'Unknown provider: '..provider) end end diff --git a/test/functional/legacy/005_bufleave_delete_buffer_spec.lua b/test/functional/legacy/005_bufleave_delete_buffer_spec.lua index 8b92c877a6..8e977aa73e 100644 --- a/test/functional/legacy/005_bufleave_delete_buffer_spec.lua +++ b/test/functional/legacy/005_bufleave_delete_buffer_spec.lua @@ -4,7 +4,7 @@ local helpers = require('test.functional.helpers')(after_each) local clear, feed, insert = helpers.clear, helpers.feed, helpers.insert local command, expect = helpers.command, helpers.expect -local wait = helpers.wait +local poke_eventloop = helpers.poke_eventloop describe('test5', function() setup(clear) @@ -34,7 +34,7 @@ describe('test5', function() command('bwipe') feed('G?this is a<cr>') feed('othis is some more text<esc>') - wait() + poke_eventloop() -- Append some text to this file. @@ -45,7 +45,7 @@ describe('test5', function() command('bwipe!') -- Append an extra line to the output register. feed('ithis is another test line<esc>:yank A<cr>') - wait() + poke_eventloop() -- Output results command('%d') diff --git a/test/functional/legacy/006_argument_list_spec.lua b/test/functional/legacy/006_argument_list_spec.lua index 9f75a91fa8..d269bf8ec9 100644 --- a/test/functional/legacy/006_argument_list_spec.lua +++ b/test/functional/legacy/006_argument_list_spec.lua @@ -4,7 +4,7 @@ local helpers = require('test.functional.helpers')(after_each) local clear, feed, insert = helpers.clear, helpers.feed, helpers.insert local command, dedent, eq = helpers.command, helpers.dedent, helpers.eq local curbuf_contents = helpers.curbuf_contents -local wait = helpers.wait +local poke_eventloop = helpers.poke_eventloop describe('argument list', function() setup(clear) @@ -17,7 +17,7 @@ describe('argument list', function() this is a test this is a test end of test file Xxx]]) - wait() + poke_eventloop() command('au BufReadPost Xxx2 next Xxx2 Xxx1') command('/^start of') @@ -30,7 +30,7 @@ describe('argument list', function() -- Write test file Xxx3 feed('$r3:.,/end of/w! Xxx3<cr>') - wait() + poke_eventloop() -- Redefine arglist; go to Xxx1 command('next! Xxx1 Xxx2 Xxx3') @@ -43,7 +43,7 @@ describe('argument list', function() -- Append contents of last window (Xxx1) feed('') - wait() + poke_eventloop() command('%yank A') -- should now be in Xxx2 diff --git a/test/functional/legacy/012_directory_spec.lua b/test/functional/legacy/012_directory_spec.lua index cec4f93737..48dd24db9e 100644 --- a/test/functional/legacy/012_directory_spec.lua +++ b/test/functional/legacy/012_directory_spec.lua @@ -8,7 +8,7 @@ local lfs = require('lfs') local eq = helpers.eq local neq = helpers.neq -local wait = helpers.wait +local poke_eventloop = helpers.poke_eventloop local funcs = helpers.funcs local meths = helpers.meths local clear = helpers.clear @@ -64,7 +64,7 @@ describe("'directory' option", function() eq(nil, lfs.attributes('.Xtest1.swp')) command('edit! Xtest1') - wait() + poke_eventloop() eq('Xtest1', funcs.buffer_name('%')) -- Verify that the swapfile exists. In the legacy test this was done by -- reading the output from :!ls. @@ -72,7 +72,7 @@ describe("'directory' option", function() meths.set_option('directory', './Xtest2,.') command('edit Xtest1') - wait() + poke_eventloop() -- swapfile should no longer exist in CWD. eq(nil, lfs.attributes('.Xtest1.swp')) @@ -82,7 +82,7 @@ describe("'directory' option", function() meths.set_option('directory', 'Xtest.je') command('edit Xtest2/Xtest3') eq(true, curbufmeths.get_option('swapfile')) - wait() + poke_eventloop() eq({ "Xtest3" }, ls_dir_sorted("Xtest2")) eq({ "Xtest3.swp" }, ls_dir_sorted("Xtest.je")) diff --git a/test/functional/legacy/023_edit_arguments_spec.lua b/test/functional/legacy/023_edit_arguments_spec.lua index e705397a2b..f59d192c1e 100644 --- a/test/functional/legacy/023_edit_arguments_spec.lua +++ b/test/functional/legacy/023_edit_arguments_spec.lua @@ -3,7 +3,7 @@ local helpers = require('test.functional.helpers')(after_each) local clear, insert = helpers.clear, helpers.insert local command, expect = helpers.command, helpers.expect -local wait = helpers.wait +local poke_eventloop = helpers.poke_eventloop describe(':edit', function() setup(clear) @@ -13,7 +13,7 @@ describe(':edit', function() The result should be in Xfile1: "fooPIPEbar", in Xfile2: "fooSLASHbar" foo|bar foo/bar]]) - wait() + poke_eventloop() -- Prepare some test files command('$-1w! Xfile1') diff --git a/test/functional/legacy/030_fileformats_spec.lua b/test/functional/legacy/030_fileformats_spec.lua index 2fd51602d8..15dbd05cf5 100644 --- a/test/functional/legacy/030_fileformats_spec.lua +++ b/test/functional/legacy/030_fileformats_spec.lua @@ -3,7 +3,7 @@ local helpers = require('test.functional.helpers')(after_each) local feed, clear, command = helpers.feed, helpers.clear, helpers.command local eq, write_file = helpers.eq, helpers.write_file -local wait = helpers.wait +local poke_eventloop = helpers.poke_eventloop describe('fileformats option', function() setup(function() @@ -107,7 +107,7 @@ describe('fileformats option', function() command('bwipe XXDosMac') command('e! XXEol') feed('ggO<C-R>=&ffs<CR>:<C-R>=&ff<CR><ESC>') - wait() + poke_eventloop() command('w! XXtt54') command('bwipeout! XXEol') command('set fileformats=dos,mac') @@ -116,7 +116,7 @@ describe('fileformats option', function() command('bwipe XXUxDs') command('e! XXUxMac') feed('ggO<C-R>=&ffs<CR>:<C-R>=&ff<CR><ESC>') - wait() + poke_eventloop() command('w! XXtt62') command('bwipeout! XXUxMac') command('e! XXUxDsMc') @@ -124,7 +124,7 @@ describe('fileformats option', function() command('bwipe XXUxDsMc') command('e! XXMacEol') feed('ggO<C-R>=&ffs<CR>:<C-R>=&ff<CR><ESC>') - wait() + poke_eventloop() command('w! XXtt64') command('bwipeout! XXMacEol') @@ -135,7 +135,7 @@ describe('fileformats option', function() command('bwipe XXUxDsMc') command('e! XXEol') feed('ggO<C-R>=&ffs<CR>:<C-R>=&ff<CR><ESC>') - wait() + poke_eventloop() command('w! XXtt72') command('bwipeout! XXEol') command('set fileformats=mac,dos,unix') @@ -144,7 +144,7 @@ describe('fileformats option', function() command('bwipe XXUxDsMc') command('e! XXEol') feed('ggO<C-R>=&ffs<CR>:<C-R>=&ff<CR><ESC>') - wait() + poke_eventloop() command('w! XXtt82') command('bwipeout! XXEol') -- Try with 'binary' set. @@ -165,7 +165,7 @@ describe('fileformats option', function() -- char was. command('set fileformat=unix nobin') feed('ggdGaEND<esc>') - wait() + poke_eventloop() command('w >>XXtt01') command('w >>XXtt02') command('w >>XXtt11') @@ -204,52 +204,52 @@ describe('fileformats option', function() command('$r XXtt01') command('$r XXtt02') feed('Go1<esc>') - wait() + poke_eventloop() command('$r XXtt11') command('$r XXtt12') command('$r XXtt13') feed('Go2<esc>') - wait() + poke_eventloop() command('$r XXtt21') command('$r XXtt22') command('$r XXtt23') feed('Go3<esc>') - wait() + poke_eventloop() command('$r XXtt31') command('$r XXtt32') command('$r XXtt33') feed('Go4<esc>') - wait() + poke_eventloop() command('$r XXtt41') command('$r XXtt42') command('$r XXtt43') feed('Go5<esc>') - wait() + poke_eventloop() command('$r XXtt51') command('$r XXtt52') command('$r XXtt53') command('$r XXtt54') feed('Go6<esc>') - wait() + poke_eventloop() command('$r XXtt61') command('$r XXtt62') command('$r XXtt63') command('$r XXtt64') feed('Go7<esc>') - wait() + poke_eventloop() command('$r XXtt71') command('$r XXtt72') feed('Go8<esc>') - wait() + poke_eventloop() command('$r XXtt81') command('$r XXtt82') feed('Go9<esc>') - wait() + poke_eventloop() command('$r XXtt91') command('$r XXtt92') command('$r XXtt93') feed('Go10<esc>') - wait() + poke_eventloop() command('$r XXUnix') command('set nobinary ff&') diff --git a/test/functional/legacy/033_lisp_indent_spec.lua b/test/functional/legacy/033_lisp_indent_spec.lua index 5132333a5c..b27de6c16d 100644 --- a/test/functional/legacy/033_lisp_indent_spec.lua +++ b/test/functional/legacy/033_lisp_indent_spec.lua @@ -4,7 +4,7 @@ local helpers = require('test.functional.helpers')(after_each) local clear, feed, insert = helpers.clear, helpers.feed, helpers.insert local command, expect = helpers.command, helpers.expect -local wait = helpers.wait +local poke_eventloop = helpers.poke_eventloop describe('lisp indent', function() setup(clear) @@ -39,7 +39,7 @@ describe('lisp indent', function() command('set lisp') command('/^(defun') feed('=G:/^(defun/,$yank A<cr>') - wait() + poke_eventloop() -- Put @a and clean empty line command('%d') diff --git a/test/functional/legacy/036_regexp_character_classes_spec.lua b/test/functional/legacy/036_regexp_character_classes_spec.lua index 38e8145d1c..6f66efcb67 100644 --- a/test/functional/legacy/036_regexp_character_classes_spec.lua +++ b/test/functional/legacy/036_regexp_character_classes_spec.lua @@ -15,7 +15,7 @@ end local function diff(text, nodedent) local fname = helpers.tmpname() command('w! '..fname) - helpers.wait() + helpers.poke_eventloop() local data = io.open(fname):read('*all') if nodedent then helpers.eq(text, data) diff --git a/test/functional/legacy/045_folding_spec.lua b/test/functional/legacy/045_folding_spec.lua index 1e5239ceac..7d7856fd37 100644 --- a/test/functional/legacy/045_folding_spec.lua +++ b/test/functional/legacy/045_folding_spec.lua @@ -59,7 +59,7 @@ describe('folding', function() feed('kYpj') feed_command('call append("$", foldlevel("."))') - helpers.wait() + helpers.poke_eventloop() screen:expect([[ dd {{{ | ee {{{ }}} | @@ -88,7 +88,7 @@ describe('folding', function() feed_command('call append("$", foldlevel(2))') feed('zR') - helpers.wait() + helpers.poke_eventloop() screen:expect([[ aa | bb | diff --git a/test/functional/legacy/051_highlight_spec.lua b/test/functional/legacy/051_highlight_spec.lua index 0c9c9621ee..d3f2897493 100644 --- a/test/functional/legacy/051_highlight_spec.lua +++ b/test/functional/legacy/051_highlight_spec.lua @@ -5,7 +5,7 @@ local helpers = require('test.functional.helpers')(after_each) local clear, feed = helpers.clear, helpers.feed local expect = helpers.expect local eq = helpers.eq -local wait = helpers.wait +local poke_eventloop = helpers.poke_eventloop local exc_exec = helpers.exc_exec local feed_command = helpers.feed_command @@ -34,7 +34,7 @@ describe(':highlight', function() -- More --^ | ]]) feed('q') - wait() -- wait until we're back to normal + poke_eventloop() -- wait until we're back to normal feed_command('hi Search') feed_command('hi Normal') diff --git a/test/functional/legacy/055_list_and_dict_types_spec.lua b/test/functional/legacy/055_list_and_dict_types_spec.lua index 91ba8bb106..4d71a526c1 100644 --- a/test/functional/legacy/055_list_and_dict_types_spec.lua +++ b/test/functional/legacy/055_list_and_dict_types_spec.lua @@ -229,7 +229,7 @@ describe('list and dictionary types', function() try let n = d[1500] catch - $put =substitute(v:exception, '\v(.{14}).*( \d{4}).*', '\1\2', '') + $put = substitute(v:exception, '\v(.{14}).*( \"\d{4}\").*', '\1\2', '') endtry " Lookup each items. for i in range(1500) @@ -260,7 +260,7 @@ describe('list and dictionary types', function() expect([[ 3000 2900 2001 1600 1501 - Vim(let):E716: 1500 + Vim(let):E716: "1500" NONE 2999 33=999 {'33': 999}]]) diff --git a/test/functional/legacy/057_sort_spec.lua b/test/functional/legacy/057_sort_spec.lua index bdc2c9779c..328d6f6fa0 100644 --- a/test/functional/legacy/057_sort_spec.lua +++ b/test/functional/legacy/057_sort_spec.lua @@ -2,8 +2,8 @@ local helpers = require('test.functional.helpers')(after_each) -local insert, command, clear, expect, eq, wait = helpers.insert, - helpers.command, helpers.clear, helpers.expect, helpers.eq, helpers.wait +local insert, command, clear, expect, eq, poke_eventloop = helpers.insert, + helpers.command, helpers.clear, helpers.expect, helpers.eq, helpers.poke_eventloop local exc_exec = helpers.exc_exec describe(':sort', function() @@ -27,7 +27,7 @@ describe(':sort', function() it('alphabetical', function() insert(text) - wait() + poke_eventloop() command('sort') expect([[ @@ -67,7 +67,7 @@ describe(':sort', function() b321 b321b ]]) - wait() + poke_eventloop() command('sort n') expect([[ abc @@ -92,7 +92,7 @@ describe(':sort', function() it('hexadecimal', function() insert(text) - wait() + poke_eventloop() command('sort x') expect([[ @@ -114,7 +114,7 @@ describe(':sort', function() it('alphabetical, unique', function() insert(text) - wait() + poke_eventloop() command('sort u') expect([[ @@ -135,7 +135,7 @@ describe(':sort', function() it('alphabetical, reverse', function() insert(text) - wait() + poke_eventloop() command('sort!') expect([[ c321d @@ -157,7 +157,7 @@ describe(':sort', function() it('numerical, reverse', function() insert(text) - wait() + poke_eventloop() command('sort! n') expect([[ b322b @@ -179,7 +179,7 @@ describe(':sort', function() it('unique, reverse', function() insert(text) - wait() + poke_eventloop() command('sort! u') expect([[ c321d @@ -200,7 +200,7 @@ describe(':sort', function() it('octal', function() insert(text) - wait() + poke_eventloop() command('sort o') expect([[ abc @@ -222,7 +222,7 @@ describe(':sort', function() it('reverse, hexadecimal', function() insert(text) - wait() + poke_eventloop() command('sort! x') expect([[ c321d @@ -244,7 +244,7 @@ describe(':sort', function() it('alphabetical, skip first character', function() insert(text) - wait() + poke_eventloop() command('sort/./') expect([[ a @@ -266,7 +266,7 @@ describe(':sort', function() it('alphabetical, skip first 2 characters', function() insert(text) - wait() + poke_eventloop() command('sort/../') expect([[ ab @@ -288,7 +288,7 @@ describe(':sort', function() it('alphabetical, unique, skip first 2 characters', function() insert(text) - wait() + poke_eventloop() command('sort/../u') expect([[ ab @@ -309,7 +309,7 @@ describe(':sort', function() it('numerical, skip first character', function() insert(text) - wait() + poke_eventloop() command('sort/./n') expect([[ abc @@ -331,7 +331,7 @@ describe(':sort', function() it('alphabetical, sort on first character', function() insert(text) - wait() + poke_eventloop() command('sort/./r') expect([[ @@ -353,7 +353,7 @@ describe(':sort', function() it('alphabetical, sort on first 2 characters', function() insert(text) - wait() + poke_eventloop() command('sort/../r') expect([[ a @@ -375,7 +375,7 @@ describe(':sort', function() it('numerical, sort on first character', function() insert(text) - wait() + poke_eventloop() command('sort/./rn') expect([[ abc @@ -397,7 +397,7 @@ describe(':sort', function() it('alphabetical, skip past first digit', function() insert(text) - wait() + poke_eventloop() command([[sort/\d/]]) expect([[ abc @@ -419,7 +419,7 @@ describe(':sort', function() it('alphabetical, sort on first digit', function() insert(text) - wait() + poke_eventloop() command([[sort/\d/r]]) expect([[ abc @@ -441,7 +441,7 @@ describe(':sort', function() it('numerical, skip past first digit', function() insert(text) - wait() + poke_eventloop() command([[sort/\d/n]]) expect([[ abc @@ -463,7 +463,7 @@ describe(':sort', function() it('numerical, sort on first digit', function() insert(text) - wait() + poke_eventloop() command([[sort/\d/rn]]) expect([[ abc @@ -485,7 +485,7 @@ describe(':sort', function() it('alphabetical, skip past first 2 digits', function() insert(text) - wait() + poke_eventloop() command([[sort/\d\d/]]) expect([[ abc @@ -507,7 +507,7 @@ describe(':sort', function() it('numerical, skip past first 2 digits', function() insert(text) - wait() + poke_eventloop() command([[sort/\d\d/n]]) expect([[ abc @@ -529,7 +529,7 @@ describe(':sort', function() it('hexadecimal, skip past first 2 digits', function() insert(text) - wait() + poke_eventloop() command([[sort/\d\d/x]]) expect([[ abc @@ -551,7 +551,7 @@ describe(':sort', function() it('alpha, on first 2 digits', function() insert(text) - wait() + poke_eventloop() command([[sort/\d\d/r]]) expect([[ abc @@ -573,7 +573,7 @@ describe(':sort', function() it('numeric, on first 2 digits', function() insert(text) - wait() + poke_eventloop() command([[sort/\d\d/rn]]) expect([[ abc @@ -595,7 +595,7 @@ describe(':sort', function() it('hexadecimal, on first 2 digits', function() insert(text) - wait() + poke_eventloop() command([[sort/\d\d/rx]]) expect([[ abc @@ -638,7 +638,7 @@ describe(':sort', function() 0b100010 0b100100 0b100010]]) - wait() + poke_eventloop() command([[sort b]]) expect([[ 0b000000 @@ -673,7 +673,7 @@ describe(':sort', function() 0b101010 0b000000 b0b111000]]) - wait() + poke_eventloop() command([[sort b]]) expect([[ 0b000000 @@ -700,7 +700,7 @@ describe(':sort', function() 1.15e-6 -1.1e3 -1.01e3]]) - wait() + poke_eventloop() command([[sort f]]) expect([[ -1.1e3 diff --git a/test/functional/legacy/074_global_var_in_viminfo_spec.lua b/test/functional/legacy/074_global_var_in_viminfo_spec.lua index f7f074c61a..445d742c1f 100644 --- a/test/functional/legacy/074_global_var_in_viminfo_spec.lua +++ b/test/functional/legacy/074_global_var_in_viminfo_spec.lua @@ -2,9 +2,9 @@ local helpers = require('test.functional.helpers')(after_each) local lfs = require('lfs') -local clear, command, eq, neq, eval, wait = +local clear, command, eq, neq, eval, poke_eventloop = helpers.clear, helpers.command, helpers.eq, helpers.neq, helpers.eval, - helpers.wait + helpers.poke_eventloop describe('storing global variables in ShaDa files', function() local tempname = 'Xtest-functional-legacy-074' @@ -36,7 +36,7 @@ describe('storing global variables in ShaDa files', function() eq(test_list, eval('MY_GLOBAL_LIST')) command('wsh! ' .. tempname) - wait() + poke_eventloop() -- Assert that the shada file exists. neq(nil, lfs.attributes(tempname)) diff --git a/test/functional/legacy/075_maparg_spec.lua b/test/functional/legacy/075_maparg_spec.lua index ee2b041b51..ad6c190104 100644 --- a/test/functional/legacy/075_maparg_spec.lua +++ b/test/functional/legacy/075_maparg_spec.lua @@ -4,7 +4,7 @@ local helpers = require('test.functional.helpers')(after_each) local clear, feed = helpers.clear, helpers.feed local command, expect = helpers.command, helpers.expect -local wait = helpers.wait +local poke_eventloop = helpers.poke_eventloop describe('maparg()', function() setup(clear) @@ -25,7 +25,7 @@ describe('maparg()', function() command('map abc y<S-char-114>y') command([[call append('$', maparg('abc'))]]) feed('Go<esc>:<cr>') - wait() + poke_eventloop() -- Outside of the range, minimum command('inoremap <Char-0x1040> a') diff --git a/test/functional/legacy/107_adjust_window_and_contents_spec.lua b/test/functional/legacy/107_adjust_window_and_contents_spec.lua index 239f60341a..841eeef0af 100644 --- a/test/functional/legacy/107_adjust_window_and_contents_spec.lua +++ b/test/functional/legacy/107_adjust_window_and_contents_spec.lua @@ -3,7 +3,7 @@ local helpers = require('test.functional.helpers')(after_each) local Screen = require('test.functional.ui.screen') -local wait = helpers.wait +local poke_eventloop = helpers.poke_eventloop local clear = helpers.clear local insert = helpers.insert local command = helpers.command @@ -16,7 +16,7 @@ describe('107', function() screen:attach() insert('start:') - wait() + poke_eventloop() command('new') command('call setline(1, range(1,256))') command('let r=[]') diff --git a/test/functional/legacy/arglist_spec.lua b/test/functional/legacy/arglist_spec.lua index 241a19d940..67c5750033 100644 --- a/test/functional/legacy/arglist_spec.lua +++ b/test/functional/legacy/arglist_spec.lua @@ -42,9 +42,7 @@ describe('argument list commands', function() end) it('test that argadd() works', function() - -- Fails with โE474: Invalid argumentโ. Not sure whether it is how it is - -- supposed to behave. - -- command('%argdelete') + command('%argdelete') command('argadd a b c') eq(0, eval('argidx()')) @@ -176,9 +174,14 @@ describe('argument list commands', function() command('last') command('argdelete %') eq({'b'}, eval('argv()')) - assert_fails('argdelete', 'E471:') + assert_fails('argdelete', 'E610:') assert_fails('1,100argdelete', 'E16:') - command('%argd') + reset_arglist() + command('args a b c d') + command('next') + command('argdel') + eq({'a', 'c', 'd'}, eval('argv()')) + command('%argdel') end) it('test for the :next, :prev, :first, :last, :rewind commands', function() diff --git a/test/functional/legacy/autoformat_join_spec.lua b/test/functional/legacy/autoformat_join_spec.lua index 84d661c190..22b1c258fe 100644 --- a/test/functional/legacy/autoformat_join_spec.lua +++ b/test/functional/legacy/autoformat_join_spec.lua @@ -3,7 +3,7 @@ local helpers = require('test.functional.helpers')(after_each) local clear, feed, insert = helpers.clear, helpers.feed, helpers.insert local command, expect = helpers.command, helpers.expect -local wait = helpers.wait +local poke_eventloop = helpers.poke_eventloop describe('autoformat join', function() setup(clear) @@ -21,7 +21,7 @@ Results:]]) feed('gg') feed('0gqj<cr>') - wait() + poke_eventloop() command([[let a=string(getpos("'[")).'/'.string(getpos("']"))]]) command("g/^This line/;'}-join") diff --git a/test/functional/legacy/close_count_spec.lua b/test/functional/legacy/close_count_spec.lua index 9b932e2ef0..60ae155fbf 100644 --- a/test/functional/legacy/close_count_spec.lua +++ b/test/functional/legacy/close_count_spec.lua @@ -3,7 +3,7 @@ local helpers = require('test.functional.helpers')(after_each) local eq = helpers.eq -local wait = helpers.wait +local poke_eventloop = helpers.poke_eventloop local eval = helpers.eval local feed = helpers.feed local clear = helpers.clear @@ -110,23 +110,23 @@ describe('close_count', function() command('for i in range(5)|new|endfor') command('4wincmd w') feed('<C-W>c<cr>') - wait() + poke_eventloop() command('let buffers = []') command('windo call add(buffers, bufnr("%"))') eq({25, 24, 23, 21, 1}, eval('buffers')) feed('1<C-W>c<cr>') - wait() + poke_eventloop() command('let buffers = []') command('windo call add(buffers, bufnr("%"))') eq({24, 23, 21, 1}, eval('buffers')) feed('9<C-W>c<cr>') - wait() + poke_eventloop() command('let buffers = []') command('windo call add(buffers, bufnr("%"))') eq({24, 23, 21}, eval('buffers')) command('1wincmd w') feed('2<C-W>c<cr>') - wait() + poke_eventloop() command('let buffers = []') command('windo call add(buffers, bufnr("%"))') eq({24, 21}, eval('buffers')) diff --git a/test/functional/legacy/cmdline_spec.lua b/test/functional/legacy/cmdline_spec.lua new file mode 100644 index 0000000000..9ebe9aeb91 --- /dev/null +++ b/test/functional/legacy/cmdline_spec.lua @@ -0,0 +1,66 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') +local clear = helpers.clear +local feed = helpers.feed +local feed_command = helpers.feed_command +local source = helpers.source + +describe('cmdline', function() + before_each(clear) + + it('is cleared when switching tabs', function() + local screen = Screen.new(30, 10) + screen:attach() + feed_command([[call setline(1, range(30))]]) + screen:expect([[ + ^0 | + 1 | + 2 | + 3 | + 4 | + 5 | + 6 | + 7 | + 8 | + :call setline(1, range(30)) | + ]]) + feed([[:tabnew<cr><C-w>-<C-w>-gtgt]]) + screen:expect([[ + + [No Name] [No Name] X| + ^ | + ~ | + ~ | + ~ | + ~ | + ~ | + 6 | + 7 | + | + ]]) + end) + + it('prints every executed Ex command if verbose >= 16', function() + local screen = Screen.new(60, 12) + screen:attach() + source([[ + command DoSomething echo 'hello' |set ts=4 |let v = '123' |echo v + call feedkeys("\r", 't') " for the hit-enter prompt + set verbose=20 + ]]) + feed_command('DoSomething') + screen:expect([[ + | + ~ | + ~ | + | + Executing: DoSomething | + Executing: echo 'hello' |set ts=4 |let v = '123' |echo v | + hello | + Executing: set ts=4 |let v = '123' |echo v | + Executing: let v = '123' |echo v | + Executing: echo v | + 123 | + Press ENTER or type command to continue^ | + ]]) + end) +end) diff --git a/test/functional/legacy/display_spec.lua b/test/functional/legacy/display_spec.lua new file mode 100644 index 0000000000..3fbbe96947 --- /dev/null +++ b/test/functional/legacy/display_spec.lua @@ -0,0 +1,31 @@ +local helpers = require('test.functional.helpers')(after_each) + +local Screen = require('test.functional.ui.screen') +local clear = helpers.clear +local poke_eventloop = helpers.poke_eventloop +local feed = helpers.feed +local feed_command = helpers.feed_command + +describe('display', function() + local screen + + it('scroll when modified at topline', function() + clear() + screen = Screen.new(20, 4) + screen:attach() + screen:set_default_attr_ids({ + [1] = {bold = true}, + }) + + feed_command([[call setline(1, repeat('a', 21))]]) + poke_eventloop() + feed('O') + screen:expect([[ + ^ | + aaaaaaaaaaaaaaaaaaaa| + a | + {1:-- INSERT --} | + ]]) + end) +end) + diff --git a/test/functional/legacy/eval_spec.lua b/test/functional/legacy/eval_spec.lua index 4198ea8bfe..ee9bd29fc4 100644 --- a/test/functional/legacy/eval_spec.lua +++ b/test/functional/legacy/eval_spec.lua @@ -4,7 +4,7 @@ local helpers = require('test.functional.helpers')(after_each) local feed, insert, source = helpers.feed, helpers.insert, helpers.source local clear, command, expect = helpers.clear, helpers.command, helpers.expect local eq, eval, write_file = helpers.eq, helpers.eval, helpers.write_file -local wait = helpers.wait +local poke_eventloop = helpers.poke_eventloop local exc_exec = helpers.exc_exec local dedent = helpers.dedent @@ -71,7 +71,7 @@ describe('eval', function() command([[call SetReg('I', 'abcI')]]) feed('Go{{{1 Appending single lines with setreg()<esc>') - wait() + poke_eventloop() command([[call SetReg('A', 'abcAc', 'c')]]) command([[call SetReg('A', 'abcAl', 'l')]]) command([[call SetReg('A', 'abcAc2','c')]]) @@ -700,13 +700,13 @@ describe('eval', function() start:]]) command('/^012345678') feed('6l') - wait() + poke_eventloop() command('let sp = getcurpos()') feed('0') - wait() + poke_eventloop() command("call setpos('.', sp)") feed('jyl') - wait() + poke_eventloop() command('$put') expect([[ 012345678 diff --git a/test/functional/legacy/mapping_spec.lua b/test/functional/legacy/mapping_spec.lua index 56a5652184..92a757ca85 100644 --- a/test/functional/legacy/mapping_spec.lua +++ b/test/functional/legacy/mapping_spec.lua @@ -2,7 +2,7 @@ local helpers = require('test.functional.helpers')(after_each) local clear, feed, insert = helpers.clear, helpers.feed, helpers.insert -local feed_command, expect, wait = helpers.feed_command, helpers.expect, helpers.wait +local feed_command, expect, poke_eventloop = helpers.feed_command, helpers.expect, helpers.poke_eventloop describe('mapping', function() before_each(clear) @@ -29,9 +29,9 @@ describe('mapping', function() feed_command('cunmap <c-c>') feed('GA<cr>') feed('TEST2: CTRL-C |') - wait() + poke_eventloop() feed('<c-c>A|<cr><esc>') - wait() + poke_eventloop() feed_command('unmap <c-c>') feed_command('unmap! <c-c>') @@ -46,7 +46,7 @@ describe('mapping', function() feed('GV') -- XXX: For some reason the mapping is only triggered -- when <C-c> is in a separate feed command. - wait() + poke_eventloop() feed('<c-c>') feed_command('vunmap <c-c>') diff --git a/test/functional/legacy/memory_usage_spec.lua b/test/functional/legacy/memory_usage_spec.lua index 28ca749749..fb0bacc2d2 100644 --- a/test/functional/legacy/memory_usage_spec.lua +++ b/test/functional/legacy/memory_usage_spec.lua @@ -7,6 +7,9 @@ local iswin = helpers.iswin local retry = helpers.retry local ok = helpers.ok local source = helpers.source +local poke_eventloop = helpers.poke_eventloop +local uname = helpers.uname +local load_adjust = helpers.load_adjust local monitor_memory_usage = { memory_usage = function(self) @@ -99,6 +102,7 @@ describe('memory usage', function() call s:f(0) endfor ]]) + poke_eventloop() local after = monitor_memory_usage(pid) -- Estimate the limit of max usage as 2x initial usage. -- The lower limit can fluctuate a bit, use 97%. @@ -143,16 +147,20 @@ describe('memory usage', function() call s:f() endfor ]]) + poke_eventloop() local after = monitor_memory_usage(pid) for _ = 1, 3 do feed_command('so '..fname) + poke_eventloop() end local last = monitor_memory_usage(pid) -- The usage may be a bit less than the last value, use 80%. -- Allow for 20% tolerance at the upper limit. That's very permissive, but - -- otherwise the test fails sometimes. + -- otherwise the test fails sometimes. On Sourcehut CI with FreeBSD we need to + -- be even more permissive. + local upper_multiplier = uname() == 'freebsd' and 15 or 12 local lower = before.last * 8 / 10 - local upper = (after.max + (after.last - before.last)) * 12 / 10 + local upper = load_adjust((after.max + (after.last - before.last)) * upper_multiplier / 10) check_result({before=before, after=after, last=last}, pcall(ok, lower < last.last)) check_result({before=before, after=after, last=last}, diff --git a/test/functional/legacy/search_mbyte_spec.lua b/test/functional/legacy/search_mbyte_spec.lua index a365f79cdf..ef7e41aa30 100644 --- a/test/functional/legacy/search_mbyte_spec.lua +++ b/test/functional/legacy/search_mbyte_spec.lua @@ -1,6 +1,6 @@ local helpers = require('test.functional.helpers')(after_each) -local wait = helpers.wait +local poke_eventloop = helpers.poke_eventloop local clear = helpers.clear local insert = helpers.insert local expect = helpers.expect @@ -15,7 +15,7 @@ describe('search_mbyte', function() Test bce: ๏ผก]=]) - wait() + poke_eventloop() command('/^Test bce:/+1') command([[$put =search('๏ผก', 'bce', line('.'))]]) diff --git a/test/functional/legacy/search_spec.lua b/test/functional/legacy/search_spec.lua index a207b176d3..4ed08881de 100644 --- a/test/functional/legacy/search_spec.lua +++ b/test/functional/legacy/search_spec.lua @@ -6,7 +6,7 @@ local eq = helpers.eq local eval = helpers.eval local feed = helpers.feed local funcs = helpers.funcs -local wait = helpers.wait +local poke_eventloop = helpers.poke_eventloop describe('search cmdline', function() local screen @@ -21,6 +21,7 @@ describe('search cmdline', function() err = { foreground = Screen.colors.Grey100, background = Screen.colors.Red }, more = { bold = true, foreground = Screen.colors.SeaGreen4 }, tilde = { bold = true, foreground = Screen.colors.Blue1 }, + hl = { background = Screen.colors.Yellow }, }) end) @@ -482,9 +483,9 @@ describe('search cmdline', function() -- "interactive". This mimics Vim's test_override("char_avail"). -- (See legacy test: test_search.vim) feed('?the') - wait() + poke_eventloop() feed('<c-g>') - wait() + poke_eventloop() feed('<cr>') screen:expect([[ 1 the first | @@ -495,11 +496,11 @@ describe('search cmdline', function() command('$') feed('?the') - wait() + poke_eventloop() feed('<c-g>') - wait() + poke_eventloop() feed('<c-g>') - wait() + poke_eventloop() feed('<cr>') screen:expect([[ 1 ^the first | @@ -510,13 +511,13 @@ describe('search cmdline', function() command('$') feed('?the') - wait() + poke_eventloop() feed('<c-g>') - wait() + poke_eventloop() feed('<c-g>') - wait() + poke_eventloop() feed('<c-g>') - wait() + poke_eventloop() feed('<cr>') screen:expect([[ 1 the first | @@ -527,9 +528,9 @@ describe('search cmdline', function() command('$') feed('?the') - wait() + poke_eventloop() feed('<c-t>') - wait() + poke_eventloop() feed('<cr>') screen:expect([[ 1 ^the first | @@ -540,11 +541,11 @@ describe('search cmdline', function() command('$') feed('?the') - wait() + poke_eventloop() feed('<c-t>') - wait() + poke_eventloop() feed('<c-t>') - wait() + poke_eventloop() feed('<cr>') screen:expect([[ 1 the first | @@ -555,13 +556,13 @@ describe('search cmdline', function() command('$') feed('?the') - wait() + poke_eventloop() feed('<c-t>') - wait() + poke_eventloop() feed('<c-t>') - wait() + poke_eventloop() feed('<c-t>') - wait() + poke_eventloop() feed('<cr>') screen:expect([[ 1 the first | @@ -570,4 +571,72 @@ describe('search cmdline', function() ?the | ]]) end) + + it('incsearch works with :sort', function() + -- oldtest: Test_incsearch_sort_dump(). + screen:try_resize(20, 4) + command('set incsearch hlsearch scrolloff=0') + funcs.setline(1, {'another one 2', 'that one 3', 'the one 1'}) + + feed(':sort ni u /on') + screen:expect([[ + another {inc:on}e 2 | + that {hl:on}e 3 | + the {hl:on}e 1 | + :sort ni u /on^ | + ]]) + feed('<esc>') + end) + + it('incsearch works with :vimgrep family', function() + -- oldtest: Test_incsearch_vimgrep_dump(). + screen:try_resize(30, 4) + command('set incsearch hlsearch scrolloff=0') + funcs.setline(1, {'another one 2', 'that one 3', 'the one 1'}) + + feed(':vimgrep on') + screen:expect([[ + another {inc:on}e 2 | + that {hl:on}e 3 | + the {hl:on}e 1 | + :vimgrep on^ | + ]]) + feed('<esc>') + + feed(':vimg /on/ *.txt') + screen:expect([[ + another {inc:on}e 2 | + that {hl:on}e 3 | + the {hl:on}e 1 | + :vimg /on/ *.txt^ | + ]]) + feed('<esc>') + + feed(':vimgrepadd "\\<LT>on') + screen:expect([[ + another {inc:on}e 2 | + that {hl:on}e 3 | + the {hl:on}e 1 | + :vimgrepadd "\<on^ | + ]]) + feed('<esc>') + + feed(':lv "tha') + screen:expect([[ + another one 2 | + {inc:tha}t one 3 | + the one 1 | + :lv "tha^ | + ]]) + feed('<esc>') + + feed(':lvimgrepa "the" **/*.txt') + screen:expect([[ + ano{inc:the}r one 2 | + that one 3 | + {hl:the} one 1 | + :lvimgrepa "the" **/*.txt^ | + ]]) + feed('<esc>') + end) end) diff --git a/test/functional/legacy/utf8_spec.lua b/test/functional/legacy/utf8_spec.lua index 5b93f25b24..8b5fc02d11 100644 --- a/test/functional/legacy/utf8_spec.lua +++ b/test/functional/legacy/utf8_spec.lua @@ -5,7 +5,7 @@ local clear, feed, insert = helpers.clear, helpers.feed, helpers.insert local command, expect = helpers.command, helpers.expect local eq, eval = helpers.eq, helpers.eval local source = helpers.source -local wait = helpers.wait +local poke_eventloop = helpers.poke_eventloop describe('utf8', function() before_each(clear) @@ -18,7 +18,7 @@ describe('utf8', function() -- Visual block Insert adjusts for multi-byte char feed('gg0l<C-V>jjIx<Esc>') - wait() + poke_eventloop() command('let r = getline(1, "$")') command('bwipeout!') diff --git a/test/functional/legacy/visual_mode_spec.lua b/test/functional/legacy/visual_mode_spec.lua new file mode 100644 index 0000000000..c8e83ed649 --- /dev/null +++ b/test/functional/legacy/visual_mode_spec.lua @@ -0,0 +1,42 @@ +-- Test visual line mode selection redraw after scrolling + +local helpers = require('test.functional.helpers')(after_each) + +local Screen = require('test.functional.ui.screen') +local call = helpers.call +local clear = helpers.clear +local feed = helpers.feed +local feed_command = helpers.feed_command +local funcs = helpers.funcs +local meths = helpers.meths +local eq = helpers.eq + +describe('visual line mode', function() + local screen + + it('redraws properly after scrolling with matchparen loaded and scrolloff=1', function() + clear{args={'-u', 'NORC'}} + screen = Screen.new(30, 7) + screen:attach() + screen:set_default_attr_ids({ + [1] = {bold = true}, + [2] = {background = Screen.colors.LightGrey}, + }) + + eq(1, meths.get_var('loaded_matchparen')) + feed_command('set scrolloff=1') + funcs.setline(1, {'a', 'b', 'c', 'd', 'e', '', '{', '}', '{', 'f', 'g', '}'}) + call('cursor', 5, 1) + + feed('V<c-d><c-d>') + screen:expect([[ + {2:{} | + {2:}} | + {2:{} | + {2:f} | + ^g | + } | + {1:-- VISUAL LINE --} | + ]]) + end) +end) diff --git a/test/functional/legacy/wordcount_spec.lua b/test/functional/legacy/wordcount_spec.lua index 0c8bd2cdcc..826743b0ca 100644 --- a/test/functional/legacy/wordcount_spec.lua +++ b/test/functional/legacy/wordcount_spec.lua @@ -4,7 +4,7 @@ local helpers = require('test.functional.helpers')(after_each) local feed, insert, source = helpers.feed, helpers.insert, helpers.source local clear, command = helpers.clear, helpers.command local eq, eval = helpers.eq, helpers.eval -local wait = helpers.wait +local poke_eventloop = helpers.poke_eventloop describe('wordcount', function() before_each(clear) @@ -14,7 +14,7 @@ describe('wordcount', function() insert([=[ RESULT test:]=]) - wait() + poke_eventloop() command('new') source([=[ @@ -127,7 +127,7 @@ describe('wordcount', function() -- -- Start visual mode quickly and select complete buffer. command('0') feed('V2jy<cr>') - wait() + poke_eventloop() command('set stl= ls=1') command('let log=DoRecordWin([3,99,0])') command('let log[1]=g:visual_stat') @@ -144,7 +144,7 @@ describe('wordcount', function() -- Start visual mode quickly and select complete buffer. command('0') feed('v$y<cr>') - wait() + poke_eventloop() command('set stl= ls=1') command('let log=DoRecordWin([3,99,0])') command('let log[1]=g:visual_stat') @@ -161,7 +161,7 @@ describe('wordcount', function() -- Start visual mode quickly and select complete buffer. command('2') feed('0v$y<cr>') - wait() + poke_eventloop() command('set stl= ls=1') command('let log=DoRecordWin([3,99,0])') command('let log[1]=g:visual_stat') diff --git a/test/functional/lua/buffer_updates_spec.lua b/test/functional/lua/buffer_updates_spec.lua index 77f8189bb9..7e4de7c39a 100644 --- a/test/functional/lua/buffer_updates_spec.lua +++ b/test/functional/lua/buffer_updates_spec.lua @@ -3,10 +3,14 @@ local helpers = require('test.functional.helpers')(after_each) local command = helpers.command local meths = helpers.meths +local funcs = helpers.funcs local clear = helpers.clear local eq = helpers.eq +local fail = helpers.fail local exec_lua = helpers.exec_lua local feed = helpers.feed +local deepcopy = helpers.deepcopy +local expect_events = helpers.expect_events local origlines = {"original line 1", "original line 2", @@ -16,32 +20,37 @@ local origlines = {"original line 1", "original line 6", " indented line"} -describe('lua: buffer event callbacks', function() - before_each(function() - clear() - exec_lua([[ - local events = {} +local function attach_buffer(evname) + exec_lua([[ + local evname = ... + local events = {} - function test_register(bufnr, id, changedtick, utf_sizes) - local function callback(...) - table.insert(events, {id, ...}) - if test_unreg == id then - return true - end + function test_register(bufnr, id, changedtick, utf_sizes) + local function callback(...) + table.insert(events, {id, ...}) + if test_unreg == id then + return true end - local opts = {on_lines=callback, on_detach=callback, utf_sizes=utf_sizes} - if changedtick then - opts.on_changedtick = callback - end - vim.api.nvim_buf_attach(bufnr, false, opts) end - - function get_events() - local ret_events = events - events = {} - return ret_events + local opts = {[evname]=callback, on_detach=callback, utf_sizes=utf_sizes} + if changedtick then + opts.on_changedtick = callback end - ]]) + vim.api.nvim_buf_attach(bufnr, false, opts) + end + + function get_events() + local ret_events = events + events = {} + return ret_events + end + ]], evname) +end + +describe('lua buffer event callbacks: on_lines', function() + before_each(function() + clear() + attach_buffer('on_lines') end) @@ -62,7 +71,7 @@ describe('lua: buffer event callbacks', function() local function check_events(expected) local events = exec_lua("return get_events(...)" ) if utf_sizes then - -- this test case uses ASCII only, so sizes sshould be the same. + -- this test case uses ASCII only, so sizes should be the same. -- Unicode is tested below. for _, event in ipairs(expected) do event[9] = event[8] @@ -216,4 +225,282 @@ describe('lua: buffer event callbacks', function() eq(1, meths.get_var('listener_cursor_line')) end) + it('does not SEGFAULT when calling win_findbuf in on_detach', function() + + exec_lua[[ + local buf = vim.api.nvim_create_buf(false, false) + + vim.cmd"split" + vim.api.nvim_win_set_buf(0, buf) + + vim.api.nvim_buf_attach(buf, false, { + on_detach = function(_, buf) + vim.fn.win_findbuf(buf) + end + }) + ]] + + command("q!") + helpers.assert_alive() + end) + + it('#12718 lnume', function() + meths.buf_set_lines(0, 0, -1, true, {'1', '2', '3'}) + exec_lua([[ + vim.api.nvim_buf_attach(0, false, { + on_lines = function(...) + vim.api.nvim_set_var('linesev', { ... }) + end, + }) + ]]) + feed('1G0') + feed('y<C-v>2j') + feed('G0') + feed('p') + -- Is the last arg old_byte_size correct? Doesn't matter for this PR + eq(meths.get_var('linesev'), { "lines", 1, 4, 2, 3, 5, 4 }) + + feed('2G0') + feed('p') + eq(meths.get_var('linesev'), { "lines", 1, 5, 1, 4, 4, 8 }) + + feed('1G0') + feed('P') + eq(meths.get_var('linesev'), { "lines", 1, 6, 0, 3, 3, 9 }) + + end) +end) + +describe('lua: nvim_buf_attach on_bytes', function() + before_each(function() + clear() + attach_buffer('on_bytes') + end) + + -- verifying the sizes with nvim_buf_get_offset is nice (checks we cannot + -- assert the wrong thing), but masks errors with unflushed lines (as + -- nvim_buf_get_offset forces a flush of the memline). To be safe run the + -- test both ways. + local function setup_eventcheck(verify, start_txt) + meths.buf_set_lines(0, 0, -1, true, start_txt) + local shadow = deepcopy(start_txt) + local shadowbytes = table.concat(shadow, '\n') .. '\n' + -- TODO: while we are brewing the real strong coffe, + -- verify should check buf_get_offset after every check_events + if verify then + meths.buf_get_offset(0, meths.buf_line_count(0)) + end + exec_lua("return test_register(...)", 0, "test1",false, nil) + meths.buf_get_changedtick(0) + + local verify_name = "test1" + local function check_events(expected) + local events = exec_lua("return get_events(...)" ) + expect_events(expected, events, "byte updates") + + if not verify then + return + end + + for _, event in ipairs(events) do + for _, elem in ipairs(event) do + if type(elem) == "number" and elem < 0 then + fail(string.format("Received event has negative values")) + end + end + + if event[1] == verify_name and event[2] == "bytes" then + local _, _, _, _, _, _, start_byte, _, _, old_byte, _, _, new_byte = unpack(event) + local before = string.sub(shadowbytes, 1, start_byte) + -- no text in the tests will contain 0xff bytes (invalid UTF-8) + -- so we can use it as marker for unknown bytes + local unknown = string.rep('\255', new_byte) + local after = string.sub(shadowbytes, start_byte + old_byte + 1) + shadowbytes = before .. unknown .. after + end + end + + local text = meths.buf_get_lines(0, 0, -1, true) + local bytes = table.concat(text, '\n') .. '\n' + + eq(string.len(bytes), string.len(shadowbytes), '\non_bytes: total bytecount of buffer is wrong') + for i = 1, string.len(shadowbytes) do + local shadowbyte = string.sub(shadowbytes, i, i) + if shadowbyte ~= '\255' then + eq(string.sub(bytes, i, i), shadowbyte, i) + end + end + end + + return check_events + end + + -- Yes, we can do both + local function do_both(verify) + it('single and multiple join', function() + local check_events = setup_eventcheck(verify, origlines) + feed 'ggJ' + check_events { + {'test1', 'bytes', 1, 3, 0, 15, 15, 1, 0, 1, 0, 1, 1}; + } + + feed '3J' + check_events { + {'test1', 'bytes', 1, 5, 0, 31, 31, 1, 0, 1, 0, 1, 1}; + {'test1', 'bytes', 1, 5, 0, 47, 47, 1, 0, 1, 0, 1, 1}; + } + end) + + it('opening lines', function() + local check_events = setup_eventcheck(verify, origlines) + -- meths.buf_set_option(0, 'autoindent', true) + feed 'Go' + check_events { + { "test1", "bytes", 1, 4, 7, 0, 114, 0, 0, 0, 1, 0, 1 }; + } + feed '<cr>' + check_events { + { "test1", "bytes", 1, 5, 7, 0, 114, 0, 0, 0, 1, 0, 1 }; + } + end) + + it('opening lines with autoindent', function() + local check_events = setup_eventcheck(verify, origlines) + meths.buf_set_option(0, 'autoindent', true) + feed 'Go' + check_events { + { "test1", "bytes", 1, 4, 7, 0, 114, 0, 0, 0, 1, 0, 5 }; + } + feed '<cr>' + check_events { + { "test1", "bytes", 1, 4, 8, 0, 115, 0, 4, 4, 0, 0, 0 }; + { "test1", "bytes", 1, 5, 7, 4, 118, 0, 0, 0, 1, 4, 5 }; + } + end) + + it('setline(num, line)', function() + local check_events = setup_eventcheck(verify, origlines) + funcs.setline(2, "babla") + check_events { + { "test1", "bytes", 1, 3, 1, 0, 16, 0, 15, 15, 0, 5, 5 }; + } + + funcs.setline(2, {"foo", "bar"}) + check_events { + { "test1", "bytes", 1, 4, 1, 0, 16, 0, 5, 5, 0, 3, 3 }; + { "test1", "bytes", 1, 5, 2, 0, 20, 0, 15, 15, 0, 3, 3 }; + } + + local buf_len = meths.buf_line_count(0) + funcs.setline(buf_len + 1, "baz") + check_events { + { "test1", "bytes", 1, 6, 7, 0, 90, 0, 0, 0, 1, 0, 4 }; + } + end) + + it('continuing comments with fo=or', function() + local check_events = setup_eventcheck(verify, {'// Comment'}) + meths.buf_set_option(0, 'formatoptions', 'ro') + meths.buf_set_option(0, 'filetype', 'c') + feed 'A<CR>' + check_events { + { "test1", "bytes", 1, 4, 0, 10, 10, 0, 0, 0, 1, 3, 4 }; + } + + feed '<ESC>' + check_events { + { "test1", "bytes", 1, 4, 1, 2, 13, 0, 1, 1, 0, 0, 0 }; + } + + feed 'ggo' -- goto first line to continue testing + check_events { + { "test1", "bytes", 1, 6, 1, 0, 11, 0, 0, 0, 1, 0, 4 }; + } + + feed '<CR>' + check_events { + { "test1", "bytes", 1, 6, 2, 2, 16, 0, 1, 1, 0, 0, 0 }; + { "test1", "bytes", 1, 7, 1, 3, 14, 0, 0, 0, 1, 3, 4 }; + } + end) + + it('editing empty buffers', function() + local check_events = setup_eventcheck(verify, {}) + + feed 'ia' + check_events { + { "test1", "bytes", 1, 3, 0, 0, 0, 0, 0, 0, 0, 1, 1 }; + } + end) + + it("changing lines", function() + local check_events = setup_eventcheck(verify, origlines) + + feed "cc" + check_events { + { "test1", "bytes", 1, 4, 0, 0, 0, 0, 15, 15, 0, 0, 0 }; + } + + feed "<ESC>" + check_events {} + + feed "c3j" + check_events { + { "test1", "bytes", 1, 4, 1, 0, 1, 3, 0, 48, 0, 0, 0 }; + } + end) + + it("visual charwise paste", function() + local check_events = setup_eventcheck(verify, {'1234567890'}) + funcs.setreg('a', '___') + + feed '1G1|vll' + check_events {} + + feed '"ap' + check_events { + { "test1", "bytes", 1, 3, 0, 0, 0, 0, 3, 3, 0, 0, 0 }; + { "test1", "bytes", 1, 5, 0, 0, 0, 0, 0, 0, 0, 3, 3 }; + } + end) + + it('blockwise paste', function() + local check_events = setup_eventcheck(verify, {'1', '2', '3'}) + feed('1G0') + feed('y<C-v>2j') + feed('G0') + feed('p') + check_events { + { "test1", "bytes", 1, 3, 2, 1, 5, 0, 0, 0, 0, 1, 1 }; + { "test1", "bytes", 1, 3, 3, 0, 7, 0, 0, 0, 0, 3, 3 }; + { "test1", "bytes", 1, 3, 4, 0, 10, 0, 0, 0, 0, 3, 3 }; + } + + feed('2G0') + feed('p') + check_events { + { "test1", "bytes", 1, 4, 1, 1, 3, 0, 0, 0, 0, 1, 1 }; + { "test1", "bytes", 1, 4, 2, 1, 6, 0, 0, 0, 0, 1, 1 }; + { "test1", "bytes", 1, 4, 3, 1, 10, 0, 0, 0, 0, 1, 1 }; + } + + feed('1G0') + feed('P') + check_events { + { "test1", "bytes", 1, 5, 0, 0, 0, 0, 0, 0, 0, 1, 1 }; + { "test1", "bytes", 1, 5, 1, 0, 3, 0, 0, 0, 0, 1, 1 }; + { "test1", "bytes", 1, 5, 2, 0, 7, 0, 0, 0, 0, 1, 1 }; + } + + end) + end + + describe('(with verify) handles', function() + do_both(true) + end) + + describe('(without verify) handles', function() + do_both(false) + end) end) + diff --git a/test/functional/lua/commands_spec.lua b/test/functional/lua/commands_spec.lua index cbc3aee557..f2a1b7dede 100644 --- a/test/functional/lua/commands_spec.lua +++ b/test/functional/lua/commands_spec.lua @@ -43,7 +43,7 @@ describe(':lua command', function() eq({'', 'ETTS', 'TTSE', 'STTE'}, curbufmeths.get_lines(0, 100, false)) end) it('throws catchable errors', function() - eq([[Vim(lua):E5107: Error loading lua [string ":lua"]:1: unexpected symbol near ')']], + eq([[Vim(lua):E5107: Error loading lua [string ":lua"]:0: unexpected symbol near ')']], pcall_err(command, 'lua ()')) eq([[Vim(lua):E5108: Error executing lua [string ":lua"]:1: TEST]], exc_exec('lua error("TEST")')) diff --git a/test/functional/lua/luaeval_spec.lua b/test/functional/lua/luaeval_spec.lua index 75966393b1..2ec48777fd 100644 --- a/test/functional/lua/luaeval_spec.lua +++ b/test/functional/lua/luaeval_spec.lua @@ -477,14 +477,14 @@ describe('v:lua', function() eq(NIL, eval('v:lua.mymod.noisy("eval")')) eq("hey eval", meths.get_current_line()) - eq("Vim:E5108: Error executing lua [string \"<nvim>\"]:10: attempt to call global 'nonexistent' (a nil value)", + eq("Vim:E5108: Error executing lua [string \"<nvim>\"]:0: attempt to call global 'nonexistent' (a nil value)", pcall_err(eval, 'v:lua.mymod.crashy()')) end) it('works in :call', function() command(":call v:lua.mymod.noisy('command')") eq("hey command", meths.get_current_line()) - eq("Vim(call):E5108: Error executing lua [string \"<nvim>\"]:10: attempt to call global 'nonexistent' (a nil value)", + eq("Vim(call):E5108: Error executing lua [string \"<nvim>\"]:0: attempt to call global 'nonexistent' (a nil value)", pcall_err(command, 'call v:lua.mymod.crashy()')) end) diff --git a/test/functional/lua/overrides_spec.lua b/test/functional/lua/overrides_spec.lua index 1bccc02847..267c759faf 100644 --- a/test/functional/lua/overrides_spec.lua +++ b/test/functional/lua/overrides_spec.lua @@ -3,7 +3,6 @@ local helpers = require('test.functional.helpers')(after_each) local Screen = require('test.functional.ui.screen') local eq = helpers.eq -local neq = helpers.neq local NIL = helpers.NIL local feed = helpers.feed local clear = helpers.clear @@ -13,7 +12,6 @@ local iswin = helpers.iswin local command = helpers.command local write_file = helpers.write_file local redir_exec = helpers.redir_exec -local alter_slashes = helpers.alter_slashes local exec_lua = helpers.exec_lua local screen @@ -285,119 +283,6 @@ describe('debug.debug', function() end) end) -describe('package.path/package.cpath', function() - local sl = alter_slashes - - local function get_new_paths(sufs, runtimepaths) - runtimepaths = runtimepaths or meths.list_runtime_paths() - local new_paths = {} - local sep = package.config:sub(1, 1) - for _, v in ipairs(runtimepaths) do - for _, suf in ipairs(sufs) do - new_paths[#new_paths + 1] = v .. sep .. 'lua' .. suf - end - end - return new_paths - end - local function eval_lua(expr, ...) - return meths.exec_lua('return '..expr, {...}) - end - local function set_path(which, value) - return exec_lua('package[select(1, ...)] = select(2, ...)', which, value) - end - - it('contains directories from &runtimepath on first invocation', function() - local new_paths = get_new_paths(sl{'/?.lua', '/?/init.lua'}) - local new_paths_str = table.concat(new_paths, ';') - eq(new_paths_str, eval_lua('package.path'):sub(1, #new_paths_str)) - - local new_cpaths = get_new_paths(iswin() and {'\\?.dll'} or {'/?.so'}) - local new_cpaths_str = table.concat(new_cpaths, ';') - eq(new_cpaths_str, eval_lua('package.cpath'):sub(1, #new_cpaths_str)) - end) - it('puts directories from &runtimepath always at the start', function() - meths.set_option('runtimepath', 'a,b') - local new_paths = get_new_paths(sl{'/?.lua', '/?/init.lua'}, {'a', 'b'}) - local new_paths_str = table.concat(new_paths, ';') - eq(new_paths_str, eval_lua('package.path'):sub(1, #new_paths_str)) - - set_path('path', sl'foo/?.lua;foo/?/init.lua;' .. new_paths_str) - - neq(new_paths_str, eval_lua('package.path'):sub(1, #new_paths_str)) - - command('set runtimepath+=c') - new_paths = get_new_paths(sl{'/?.lua', '/?/init.lua'}, {'a', 'b', 'c'}) - new_paths_str = table.concat(new_paths, ';') - eq(new_paths_str, eval_lua('package.path'):sub(1, #new_paths_str)) - end) - it('understands uncommon suffixes', function() - set_path('cpath', './?/foo/bar/baz/x.nlua') - meths.set_option('runtimepath', 'a') - local new_paths = get_new_paths({'/?/foo/bar/baz/x.nlua'}, {'a'}) - local new_paths_str = table.concat(new_paths, ';') - eq(new_paths_str, eval_lua('package.cpath'):sub(1, #new_paths_str)) - - set_path('cpath', './yyy?zzz/x') - meths.set_option('runtimepath', 'b') - new_paths = get_new_paths({'/yyy?zzz/x'}, {'b'}) - new_paths_str = table.concat(new_paths, ';') - eq(new_paths_str, eval_lua('package.cpath'):sub(1, #new_paths_str)) - - set_path('cpath', './yyy?zzz/123?ghi/x') - meths.set_option('runtimepath', 'b') - new_paths = get_new_paths({'/yyy?zzz/123?ghi/x'}, {'b'}) - new_paths_str = table.concat(new_paths, ';') - eq(new_paths_str, eval_lua('package.cpath'):sub(1, #new_paths_str)) - end) - it('preserves empty items', function() - local many_empty_path = ';;;;;;' - local many_empty_cpath = ';;;;;;./?.luaso' - set_path('path', many_empty_path) - set_path('cpath', many_empty_cpath) - meths.set_option('runtimepath', 'a') - local new_paths = get_new_paths(sl{'/?.lua', '/?/init.lua'}, {'a'}) - local new_paths_str = table.concat(new_paths, ';') - eq(new_paths_str .. ';' .. many_empty_path, eval_lua('package.path')) - local new_cpaths = get_new_paths({'/?.luaso'}, {'a'}) - local new_cpaths_str = table.concat(new_cpaths, ';') - eq(new_cpaths_str .. ';' .. many_empty_cpath, eval_lua('package.cpath')) - end) - it('preserves empty value', function() - set_path('path', '') - meths.set_option('runtimepath', 'a') - local new_paths = get_new_paths(sl{'/?.lua', '/?/init.lua'}, {'a'}) - local new_paths_str = table.concat(new_paths, ';') - eq(new_paths_str .. ';', eval_lua('package.path')) - end) - it('purges out all additions if runtimepath is set to empty', function() - local new_paths = get_new_paths(sl{'/?.lua', '/?/init.lua'}) - local new_paths_str = table.concat(new_paths, ';') - local path = eval_lua('package.path') - eq(new_paths_str, path:sub(1, #new_paths_str)) - - local new_cpaths = get_new_paths(iswin() and {'\\?.dll'} or {'/?.so'}) - local new_cpaths_str = table.concat(new_cpaths, ';') - local cpath = eval_lua('package.cpath') - eq(new_cpaths_str, cpath:sub(1, #new_cpaths_str)) - - meths.set_option('runtimepath', '') - eq(path:sub(#new_paths_str + 2, -1), eval_lua('package.path')) - eq(cpath:sub(#new_cpaths_str + 2, -1), eval_lua('package.cpath')) - end) - it('works with paths with escaped commas', function() - meths.set_option('runtimepath', '\\,') - local new_paths = get_new_paths(sl{'/?.lua', '/?/init.lua'}, {','}) - local new_paths_str = table.concat(new_paths, ';') - eq(new_paths_str, eval_lua('package.path'):sub(1, #new_paths_str)) - end) - it('ignores paths with semicolons', function() - meths.set_option('runtimepath', 'foo;bar,\\,') - local new_paths = get_new_paths(sl{'/?.lua', '/?/init.lua'}, {','}) - local new_paths_str = table.concat(new_paths, ';') - eq(new_paths_str, eval_lua('package.path'):sub(1, #new_paths_str)) - end) -end) - describe('os.getenv', function() it('returns nothing for undefined env var', function() eq(NIL, funcs.luaeval('os.getenv("XTEST_1")')) diff --git a/test/functional/lua/treesitter_spec.lua b/test/functional/lua/treesitter_spec.lua index aa3d55b06d..4c1083c386 100644 --- a/test/functional/lua/treesitter_spec.lua +++ b/test/functional/lua/treesitter_spec.lua @@ -15,17 +15,16 @@ before_each(clear) describe('treesitter API', function() -- error tests not requiring a parser library it('handles missing language', function() - eq("Error executing lua: .../treesitter.lua: no parser for 'borklang' language", - pcall_err(exec_lua, "parser = vim.treesitter.create_parser(0, 'borklang')")) + eq("Error executing lua: .../language.lua:0: no parser for 'borklang' language, see :help treesitter-parsers", + pcall_err(exec_lua, "parser = vim.treesitter.get_parser(0, 'borklang')")) -- actual message depends on platform matches("Error executing lua: Failed to load parser: uv_dlopen: .+", pcall_err(exec_lua, "parser = vim.treesitter.require_language('borklang', 'borkbork.so')")) - eq("Error executing lua: .../treesitter.lua: no parser for 'borklang' language", + eq("Error executing lua: .../language.lua:0: no parser for 'borklang' language, see :help treesitter-parsers", pcall_err(exec_lua, "parser = vim.treesitter.inspect_language('borklang')")) end) - end) describe('treesitter API with C parser', function() @@ -42,7 +41,7 @@ describe('treesitter API with C parser', function() end it('parses buffer', function() - if not check_parser() then return end + if helpers.pending_win32(pending) or not check_parser() then return end insert([[ int main() { @@ -81,13 +80,6 @@ describe('treesitter API with C parser', function() eq({1,2,1,12}, exec_lua("return {descendant:range()}")) eq("(declaration type: (primitive_type) declarator: (init_declarator declarator: (identifier) value: (number_literal)))", exec_lua("return descendant:sexpr()")) - eq(true, exec_lua("return child == child")) - -- separate lua object, but represents same node - eq(true, exec_lua("return child == root:child(0)")) - eq(false, exec_lua("return child == descendant2")) - eq(false, exec_lua("return child == nil")) - eq(false, exec_lua("return child == tree")) - feed("2G7|ay") exec_lua([[ tree2 = parser:parse() @@ -99,6 +91,21 @@ describe('treesitter API with C parser', function() eq("<node declaration>", exec_lua("return tostring(descendant2)")) eq({1,2,1,13}, exec_lua("return {descendant2:range()}")) + eq(true, exec_lua("return child == child")) + -- separate lua object, but represents same node + eq(true, exec_lua("return child == root:child(0)")) + eq(false, exec_lua("return child == descendant2")) + eq(false, exec_lua("return child == nil")) + eq(false, exec_lua("return child == tree")) + + eq("string", exec_lua("return type(child:id())")) + eq(true, exec_lua("return child:id() == child:id()")) + -- separate lua object, but represents same node + eq(true, exec_lua("return child:id() == root:child(0):id()")) + eq(false, exec_lua("return child:id() == descendant2:id()")) + eq(false, exec_lua("return child:id() == nil")) + eq(false, exec_lua("return child:id() == tree")) + -- orginal tree did not change eq({1,2,1,12}, exec_lua("return {descendant:range()}")) @@ -127,6 +134,58 @@ void ui_refresh(void) } }]] + it('allows to iterate over nodes children', function() + if not check_parser() then return end + + insert(test_text); + + local res = exec_lua([[ + parser = vim.treesitter.get_parser(0, "c") + + func_node = parser:parse():root():child(0) + + res = {} + for node, field in func_node:iter_children() do + table.insert(res, {node:type(), field}) + end + return res + ]]) + + eq({ + {"primitive_type", "type"}, + {"function_declarator", "declarator"}, + {"compound_statement", "body"} + }, res) + end) + + it('allows to get a child by field', function() + if not check_parser() then return end + + insert(test_text); + + local res = exec_lua([[ + parser = vim.treesitter.get_parser(0, "c") + + func_node = parser:parse():root():child(0) + + local res = {} + for _, node in ipairs(func_node:field("type")) do + table.insert(res, {node:type(), node:range()}) + end + return res + ]]) + + eq({{ "primitive_type", 0, 0, 0, 4 }}, res) + + local res_fail = exec_lua([[ + parser = vim.treesitter.get_parser(0, "c") + + return #func_node:field("foo") == 0 + ]]) + + assert(res_fail) + end) + local query = [[ ((call_expression function: (identifier) @minfunc (argument_list (identifier) @min_id)) (eq? @minfunc "MIN")) "for" @keyword @@ -134,6 +193,16 @@ void ui_refresh(void) (field_expression argument: (identifier) @fieldarg) ]] + it("supports runtime queries", function() + if not check_parser() then return end + + local ret = exec_lua [[ + return require"vim.treesitter.query".get_query("c", "highlights").captures[1] + ]] + + eq('variable', ret) + end) + it('support query and iter by capture', function() if not check_parser() then return end @@ -198,6 +267,82 @@ void ui_refresh(void) }, res) end) + it('allow loading query with escaped quotes and capture them with `lua-match?` and `vim-match?`', function() + if not check_parser() then return end + + insert('char* astring = "Hello World!";') + + local res = exec_lua([[ + cquery = vim.treesitter.parse_query("c", '((_) @quote (vim-match? @quote "^\\"$")) ((_) @quote (lua-match? @quote "^\\"$"))') + parser = vim.treesitter.get_parser(0, "c") + tree = parser:parse() + res = {} + for pattern, match in cquery:iter_matches(tree:root(), 0, 0, 1) do + -- can't transmit node over RPC. just check the name and range + local mrepr = {} + for cid,node in pairs(match) do + table.insert(mrepr, {cquery.captures[cid], node:type(), node:range()}) + end + table.insert(res, {pattern, mrepr}) + end + return res + ]]) + + eq({ + { 1, { { "quote", '"', 0, 16, 0, 17 } } }, + { 2, { { "quote", '"', 0, 16, 0, 17 } } }, + { 1, { { "quote", '"', 0, 29, 0, 30 } } }, + { 2, { { "quote", '"', 0, 29, 0, 30 } } }, + }, res) + end) + + it('allows to add predicates', function() + insert([[ + int main(void) { + return 0; + } + ]]) + + local custom_query = "((identifier) @main (#is-main? @main))" + + local res = exec_lua([[ + local query = require"vim.treesitter.query" + + local function is_main(match, pattern, bufnr, predicate) + local node = match[ predicate[2] ] + + return query.get_node_text(node, bufnr) + end + + local parser = vim.treesitter.get_parser(0, "c") + + query.add_predicate("is-main?", is_main) + + local query = query.parse_query("c", ...) + + local nodes = {} + for _, node in query:iter_captures(parser:parse():root(), 0, 0, 19) do + table.insert(nodes, {node:range()}) + end + + return nodes + ]], custom_query) + + eq({{0, 4, 0, 8}}, res) + + local res_list = exec_lua[[ + local query = require'vim.treesitter.query' + + local list = query.list_predicates() + + table.sort(list) + + return list + ]] + + eq({ 'contains?', 'eq?', 'is-main?', 'lua-match?', 'match?', 'vim-match?' }, res_list) + end) + it('supports highlighting', function() if not check_parser() then return end @@ -243,10 +388,10 @@ static int nlua_schedule(lua_State *const lstate) (primitive_type) @type (sized_type_specifier) @type -; defaults to very magic syntax, for best compatibility -((identifier) @Identifier (#match? @Identifier "^l(u)a_")) -; still support \M etc prefixes -((identifier) @Constant (#match? @Constant "\M^\[A-Z_]\+$")) +; Use lua regexes +((identifier) @Identifier (#contains? @Identifier "lua_")) +((identifier) @Constant (#lua-match? @Constant "^[A-Z_]+$")) +((identifier) @Normal (#vim-match? @Constant "^lstate$")) ((binary_expression left: (identifier) @WarningMsg.left right: (identifier) @WarningMsg.right) (#eq? @WarningMsg.left @WarningMsg.right)) @@ -292,13 +437,14 @@ static int nlua_schedule(lua_State *const lstate) ]]} exec_lua([[ - local TSHighlighter = vim.treesitter.TSHighlighter + local parser = vim.treesitter.get_parser(0, "c") + local highlighter = vim.treesitter.highlighter local query = ... - test_hl = TSHighlighter.new(query, 0, "c") + test_hl = highlighter.new(parser, query) ]], hl_query) screen:expect{grid=[[ {2:/// Schedule Lua callback on main loop's event queue} | - {3:static} {3:int} nlua_schedule({3:lua_State} *{3:const} lstate) | + {3:static} {3:int} {11:nlua_schedule}({3:lua_State} *{3:const} lstate) | { | {4:if} ({11:lua_type}(lstate, {5:1}) != {5:LUA_TFUNCTION} | || {6:lstate} != {6:lstate}) { | @@ -306,9 +452,9 @@ static int nlua_schedule(lua_State *const lstate) {4:return} {11:lua_error}(lstate); | } | | - {7:LuaRef} cb = nlua_ref(lstate, {5:1}); | + {7:LuaRef} cb = {11:nlua_ref}(lstate, {5:1}); | | - multiqueue_put(main_loop.events, nlua_schedule_event, | + multiqueue_put(main_loop.events, {11:nlua_schedule_event}, | {5:1}, ({3:void} *)({3:ptrdiff_t})cb); | {4:return} {5:0}; | ^} | @@ -317,10 +463,33 @@ static int nlua_schedule(lua_State *const lstate) | ]]} + feed("5Goc<esc>dd") + + screen:expect{grid=[[ + {2:/// Schedule Lua callback on main loop's event queue} | + {3:static} {3:int} {11:nlua_schedule}({3:lua_State} *{3:const} lstate) | + { | + {4:if} ({11:lua_type}(lstate, {5:1}) != {5:LUA_TFUNCTION} | + || {6:lstate} != {6:lstate}) { | + {11:^lua_pushliteral}(lstate, {5:"vim.schedule: expected function"}); | + {4:return} {11:lua_error}(lstate); | + } | + | + {7:LuaRef} cb = {11:nlua_ref}(lstate, {5:1}); | + | + multiqueue_put(main_loop.events, {11:nlua_schedule_event}, | + {5:1}, ({3:void} *)({3:ptrdiff_t})cb); | + {4:return} {5:0}; | + } | + {1:~ }| + {1:~ }| + | + ]]} + feed('7Go*/<esc>') screen:expect{grid=[[ {2:/// Schedule Lua callback on main loop's event queue} | - {3:static} {3:int} nlua_schedule({3:lua_State} *{3:const} lstate) | + {3:static} {3:int} {11:nlua_schedule}({3:lua_State} *{3:const} lstate) | { | {4:if} ({11:lua_type}(lstate, {5:1}) != {5:LUA_TFUNCTION} | || {6:lstate} != {6:lstate}) { | @@ -329,9 +498,9 @@ static int nlua_schedule(lua_State *const lstate) {8:*^/} | } | | - {7:LuaRef} cb = nlua_ref(lstate, {5:1}); | + {7:LuaRef} cb = {11:nlua_ref}(lstate, {5:1}); | | - multiqueue_put(main_loop.events, nlua_schedule_event, | + multiqueue_put(main_loop.events, {11:nlua_schedule_event}, | {5:1}, ({3:void} *)({3:ptrdiff_t})cb); | {4:return} {5:0}; | } | @@ -342,7 +511,7 @@ static int nlua_schedule(lua_State *const lstate) feed('3Go/*<esc>') screen:expect{grid=[[ {2:/// Schedule Lua callback on main loop's event queue} | - {3:static} {3:int} nlua_schedule({3:lua_State} *{3:const} lstate) | + {3:static} {3:int} {11:nlua_schedule}({3:lua_State} *{3:const} lstate) | { | {2:/^*} | {2: if (lua_type(lstate, 1) != LUA_TFUNCTION} | @@ -352,9 +521,9 @@ static int nlua_schedule(lua_State *const lstate) {2:*/} | } | | - {7:LuaRef} cb = nlua_ref(lstate, {5:1}); | + {7:LuaRef} cb = {11:nlua_ref}(lstate, {5:1}); | | - multiqueue_put(main_loop.events, nlua_schedule_event, | + multiqueue_put(main_loop.events, {11:nlua_schedule_event}, | {5:1}, ({3:void} *)({3:ptrdiff_t})cb); | {4:return} {5:0}; | {8:}} | @@ -365,7 +534,7 @@ static int nlua_schedule(lua_State *const lstate) feed("~") screen:expect{grid=[[ {2:/// Schedule Lua callback on main loop's event queu^E} | - {3:static} {3:int} nlua_schedule({3:lua_State} *{3:const} lstate) | + {3:static} {3:int} {11:nlua_schedule}({3:lua_State} *{3:const} lstate) | { | {2:/*} | {2: if (lua_type(lstate, 1) != LUA_TFUNCTION} | @@ -375,9 +544,9 @@ static int nlua_schedule(lua_State *const lstate) {2:*/} | } | | - {7:LuaRef} cb = nlua_ref(lstate, {5:1}); | + {7:LuaRef} cb = {11:nlua_ref}(lstate, {5:1}); | | - multiqueue_put(main_loop.events, nlua_schedule_event, | + multiqueue_put(main_loop.events, {11:nlua_schedule_event}, | {5:1}, ({3:void} *)({3:ptrdiff_t})cb); | {4:return} {5:0}; | {8:}} | @@ -388,7 +557,7 @@ static int nlua_schedule(lua_State *const lstate) feed("re") screen:expect{grid=[[ {2:/// Schedule Lua callback on main loop's event queu^e} | - {3:static} {3:int} nlua_schedule({3:lua_State} *{3:const} lstate) | + {3:static} {3:int} {11:nlua_schedule}({3:lua_State} *{3:const} lstate) | { | {2:/*} | {2: if (lua_type(lstate, 1) != LUA_TFUNCTION} | @@ -398,9 +567,9 @@ static int nlua_schedule(lua_State *const lstate) {2:*/} | } | | - {7:LuaRef} cb = nlua_ref(lstate, {5:1}); | + {7:LuaRef} cb = {11:nlua_ref}(lstate, {5:1}); | | - multiqueue_put(main_loop.events, nlua_schedule_event, | + multiqueue_put(main_loop.events, {11:nlua_schedule_event}, | {5:1}, ({3:void} *)({3:ptrdiff_t})cb); | {4:return} {5:0}; | {8:}} | @@ -408,6 +577,72 @@ static int nlua_schedule(lua_State *const lstate) ]]} end) + it("supports highlighting with custom parser", function() + if not check_parser() then return end + + local screen = Screen.new(65, 18) + screen:attach() + screen:set_default_attr_ids({ {bold = true, foreground = Screen.colors.SeaGreen4} }) + + insert(test_text) + + screen:expect{ grid= [[ + int width = INT_MAX, height = INT_MAX; | + bool ext_widgets[kUIExtCount]; | + for (UIExtension i = 0; (int)i < kUIExtCount; i++) { | + ext_widgets[i] = true; | + } | + | + bool inclusive = ui_override(); | + for (size_t i = 0; i < ui_count; i++) { | + UI *ui = uis[i]; | + width = MIN(ui->width, width); | + height = MIN(ui->height, height); | + foo = BAR(ui->bazaar, bazaar); | + for (UIExtension j = 0; (int)j < kUIExtCount; j++) { | + ext_widgets[j] &= (ui->ui_ext[j] || inclusive); | + } | + } | + ^} | + | + ]] } + + exec_lua([[ + parser = vim.treesitter.get_parser(0, "c") + query = vim.treesitter.parse_query("c", "(declaration) @decl") + + local nodes = {} + for _, node in query:iter_captures(parser:parse():root(), 0, 0, 19) do + table.insert(nodes, node) + end + + parser:set_included_ranges(nodes) + + local hl = vim.treesitter.highlighter.new(parser, "(identifier) @type") + ]]) + + screen:expect{ grid = [[ + int {1:width} = {1:INT_MAX}, {1:height} = {1:INT_MAX}; | + bool {1:ext_widgets}[{1:kUIExtCount}]; | + for (UIExtension {1:i} = 0; (int)i < kUIExtCount; i++) { | + ext_widgets[i] = true; | + } | + | + bool {1:inclusive} = {1:ui_override}(); | + for (size_t {1:i} = 0; i < ui_count; i++) { | + UI *{1:ui} = {1:uis}[{1:i}]; | + width = MIN(ui->width, width); | + height = MIN(ui->height, height); | + foo = BAR(ui->bazaar, bazaar); | + for (UIExtension {1:j} = 0; (int)j < kUIExtCount; j++) { | + ext_widgets[j] &= (ui->ui_ext[j] || inclusive); | + } | + } | + ^} | + | + ]] } + end) + it('inspects language', function() if not check_parser() then return end @@ -453,23 +688,29 @@ static int nlua_schedule(lua_State *const lstate) insert(test_text) - local res = exec_lua([[ + local res = exec_lua [[ parser = vim.treesitter.get_parser(0, "c") return { parser:parse():root():range() } - ]]) + ]] eq({0, 0, 19, 0}, res) -- The following sets the included ranges for the current parser -- As stated here, this only includes the function (thus the whole buffer, without the last line) - local res2 = exec_lua([[ + local res2 = exec_lua [[ local root = parser:parse():root() parser:set_included_ranges({root:child(0)}) parser.valid = false return { parser:parse():root():range() } - ]]) + ]] eq({0, 0, 18, 1}, res2) + + local range = exec_lua [[ + return parser:included_ranges() + ]] + + eq(range, { { 0, 0, 18, 1 } }) end) it("allows to set complex ranges", function() if not check_parser() then return end @@ -477,7 +718,7 @@ static int nlua_schedule(lua_State *const lstate) insert(test_text) - local res = exec_lua([[ + local res = exec_lua [[ parser = vim.treesitter.get_parser(0, "c") query = vim.treesitter.parse_query("c", "(declaration) @decl") @@ -495,7 +736,7 @@ static int nlua_schedule(lua_State *const lstate) table.insert(res, { root:named_child(i):range() }) end return res - ]]) + ]] eq({ { 2, 2, 2, 40 }, @@ -509,4 +750,35 @@ static int nlua_schedule(lua_State *const lstate) { 10, 5, 10, 20 }, { 14, 9, 14, 27 } }, res) end) + + it("allows to create string parsers", function() + local ret = exec_lua [[ + local parser = vim.treesitter.get_string_parser("int foo = 42;", "c") + return { parser:parse():root():range() } + ]] + + eq({ 0, 0, 0, 13 }, ret) + end) + + it("allows to run queries with string parsers", function() + local txt = [[ + int foo = 42; + int bar = 13; + ]] + + local ret = exec_lua([[ + local str = ... + local parser = vim.treesitter.get_string_parser(str, "c") + + local nodes = {} + local query = vim.treesitter.parse_query("c", '((identifier) @id (eq? @id "foo"))') + + for _, node in query:iter_captures(parser:parse():root(), str, 0, 2) do + table.insert(nodes, { node:range() }) + end + + return nodes]], txt) + + eq({ {0, 10, 0, 13} }, ret) + end) end) diff --git a/test/functional/lua/vim_spec.lua b/test/functional/lua/vim_spec.lua index 9b2697b3c2..e9e1f7ec12 100644 --- a/test/functional/lua/vim_spec.lua +++ b/test/functional/lua/vim_spec.lua @@ -4,6 +4,7 @@ local Screen = require('test.functional.ui.screen') local funcs = helpers.funcs local meths = helpers.meths +local dedent = helpers.dedent local command = helpers.command local clear = helpers.clear local eq = helpers.eq @@ -118,6 +119,11 @@ describe('lua stdlib', function() eq(1, funcs.luaeval('vim.stricmp("\\0C\\0", "\\0B\\0")')) end) + -- for brevity, match only the error header (not the traceback) + local function pcall_header(...) + return string.gsub(string.gsub(pcall_err(exec_lua, ...), '[\r\n].*', ''), '^Error executing lua: ', '') + end + it('vim.startswith', function() eq(true, funcs.luaeval('vim.startswith("123", "1")')) eq(true, funcs.luaeval('vim.startswith("123", "")')) @@ -128,8 +134,8 @@ describe('lua stdlib', function() eq(false, funcs.luaeval('vim.startswith("123", "2")')) eq(false, funcs.luaeval('vim.startswith("123", "1234")')) - eq("string", type(pcall_err(funcs.luaeval, 'vim.startswith("123", nil)'))) - eq("string", type(pcall_err(funcs.luaeval, 'vim.startswith(nil, "123")'))) + eq("vim/shared.lua:0: prefix: expected string, got nil", pcall_header 'return vim.startswith("123", nil)') + eq("vim/shared.lua:0: s: expected string, got nil", pcall_header 'return vim.startswith(nil, "123")') end) it('vim.endswith', function() @@ -142,8 +148,8 @@ describe('lua stdlib', function() eq(false, funcs.luaeval('vim.endswith("123", "2")')) eq(false, funcs.luaeval('vim.endswith("123", "1234")')) - eq("string", type(pcall_err(funcs.luaeval, 'vim.endswith("123", nil)'))) - eq("string", type(pcall_err(funcs.luaeval, 'vim.endswith(nil, "123")'))) + eq("vim/shared.lua:0: suffix: expected string, got nil", pcall_header 'return vim.endswith("123", nil)') + eq("vim/shared.lua:0: s: expected string, got nil", pcall_header 'return vim.endswith(nil, "123")') end) it("vim.str_utfindex/str_byteindex", function() @@ -257,16 +263,28 @@ describe('lua stdlib', function() } for _, t in ipairs(loops) do - matches(".*Infinite loop detected", pcall_err(split, t[1], t[2])) + eq("Error executing lua: vim/shared.lua:0: Infinite loop detected", pcall_err(split, t[1], t[2])) end -- Validates args. eq(true, pcall(split, 'string', 'string')) - eq('Error executing lua: .../shared.lua: s: expected string, got number', + eq(dedent([[ + Error executing lua: vim/shared.lua:0: s: expected string, got number + stack traceback: + vim/shared.lua:0: in function 'gsplit' + vim/shared.lua:0: in function <vim/shared.lua:0>]]), pcall_err(split, 1, 'string')) - eq('Error executing lua: .../shared.lua: sep: expected string, got number', + eq(dedent([[ + Error executing lua: vim/shared.lua:0: sep: expected string, got number + stack traceback: + vim/shared.lua:0: in function 'gsplit' + vim/shared.lua:0: in function <vim/shared.lua:0>]]), pcall_err(split, 'string', 1)) - eq('Error executing lua: .../shared.lua: plain: expected boolean, got number', + eq(dedent([[ + Error executing lua: vim/shared.lua:0: plain: expected boolean, got number + stack traceback: + vim/shared.lua:0: in function 'gsplit' + vim/shared.lua:0: in function <vim/shared.lua:0>]]), pcall_err(split, 'string', 'string', 1)) end) @@ -287,7 +305,10 @@ describe('lua stdlib', function() end -- Validates args. - eq('Error executing lua: .../shared.lua: s: expected string, got number', + eq(dedent([[ + Error executing lua: vim/shared.lua:0: s: expected string, got number + stack traceback: + vim/shared.lua:0: in function <vim/shared.lua:0>]]), pcall_err(trim, 2)) end) @@ -353,7 +374,7 @@ describe('lua stdlib', function() return t1.f() ~= t2.f() ]])) - eq('Error executing lua: .../shared.lua: Cannot deepcopy object of type thread', + eq('Error executing lua: vim/shared.lua:0: Cannot deepcopy object of type thread', pcall_err(exec_lua, [[ local thread = coroutine.create(function () return 0 end) local t = {thr = thread} @@ -366,7 +387,10 @@ describe('lua stdlib', function() eq('foo%%%-bar', exec_lua([[return vim.pesc(vim.pesc('foo-bar'))]])) -- Validates args. - eq('Error executing lua: .../shared.lua: s: expected string, got number', + eq(dedent([[ + Error executing lua: vim/shared.lua:0: s: expected string, got number + stack traceback: + vim/shared.lua:0: in function <vim/shared.lua:0>]]), pcall_err(exec_lua, [[return vim.pesc(2)]])) end) @@ -491,19 +515,19 @@ describe('lua stdlib', function() return c.x.a == 1 and c.x.b == 2 and c.x.c == nil and count == 1 ]])) - eq('Error executing lua: .../shared.lua: invalid "behavior": nil', + eq('Error executing lua: vim/shared.lua:0: invalid "behavior": nil', pcall_err(exec_lua, [[ return vim.tbl_extend() ]]) ) - eq('Error executing lua: .../shared.lua: wrong number of arguments (given 1, expected at least 3)', + eq('Error executing lua: vim/shared.lua:0: wrong number of arguments (given 1, expected at least 3)', pcall_err(exec_lua, [[ return vim.tbl_extend("keep") ]]) ) - eq('Error executing lua: .../shared.lua: wrong number of arguments (given 2, expected at least 3)', + eq('Error executing lua: vim/shared.lua:0: wrong number of arguments (given 2, expected at least 3)', pcall_err(exec_lua, [[ return vim.tbl_extend("keep", {}) ]]) @@ -579,19 +603,19 @@ describe('lua stdlib', function() return vim.tbl_islist(c) and count == 0 ]])) - eq('Error executing lua: .../shared.lua: invalid "behavior": nil', + eq('Error executing lua: vim/shared.lua:0: invalid "behavior": nil', pcall_err(exec_lua, [[ return vim.tbl_deep_extend() ]]) ) - eq('Error executing lua: .../shared.lua: wrong number of arguments (given 1, expected at least 3)', + eq('Error executing lua: vim/shared.lua:0: wrong number of arguments (given 1, expected at least 3)', pcall_err(exec_lua, [[ return vim.tbl_deep_extend("keep") ]]) ) - eq('Error executing lua: .../shared.lua: wrong number of arguments (given 2, expected at least 3)', + eq('Error executing lua: vim/shared.lua:0: wrong number of arguments (given 2, expected at least 3)', pcall_err(exec_lua, [[ return vim.tbl_deep_extend("keep", {}) ]]) @@ -624,7 +648,10 @@ describe('lua stdlib', function() it('vim.list_extend', function() eq({1,2,3}, exec_lua [[ return vim.list_extend({1}, {2,3}) ]]) - eq('Error executing lua: .../shared.lua: src: expected table, got nil', + eq(dedent([[ + Error executing lua: vim/shared.lua:0: src: expected table, got nil + stack traceback: + vim/shared.lua:0: in function <vim/shared.lua:0>]]), pcall_err(exec_lua, [[ return vim.list_extend({1}, nil) ]])) eq({1,2}, exec_lua [[ return vim.list_extend({1}, {2;a=1}) ]]) eq(true, exec_lua [[ local a = {1} return vim.list_extend(a, {2;a=1}) == a ]]) @@ -648,7 +675,7 @@ describe('lua stdlib', function() assert(vim.deep_equal(a, { A = 1; [1] = 'A'; })) vim.tbl_add_reverse_lookup(a) ]] - matches('Error executing lua: .../shared.lua: The reverse lookup found an existing value for "[1A]" while processing key "[1A]"', + matches('^Error executing lua: vim/shared%.lua:0: The reverse lookup found an existing value for "[1A]" while processing key "[1A]"$', pcall_err(exec_lua, code)) end) @@ -820,34 +847,77 @@ describe('lua stdlib', function() exec_lua("vim.validate{arg1={{}, 't' }, arg2={ 'foo', 's' }}") exec_lua("vim.validate{arg1={2, function(a) return (a % 2) == 0 end, 'even number' }}") - eq("Error executing lua: .../shared.lua: 1: expected table, got number", + eq(dedent([[ + Error executing lua: [string "<nvim>"]:0: opt[1]: expected table, got number + stack traceback: + [string "<nvim>"]:0: in main chunk]]), pcall_err(exec_lua, "vim.validate{ 1, 'x' }")) - eq("Error executing lua: .../shared.lua: invalid type name: x", + eq(dedent([[ + Error executing lua: [string "<nvim>"]:0: invalid type name: x + stack traceback: + [string "<nvim>"]:0: in main chunk]]), pcall_err(exec_lua, "vim.validate{ arg1={ 1, 'x' }}")) - eq("Error executing lua: .../shared.lua: invalid type name: 1", + eq(dedent([[ + Error executing lua: [string "<nvim>"]:0: invalid type name: 1 + stack traceback: + [string "<nvim>"]:0: in main chunk]]), pcall_err(exec_lua, "vim.validate{ arg1={ 1, 1 }}")) - eq("Error executing lua: .../shared.lua: invalid type name: nil", + eq(dedent([[ + Error executing lua: [string "<nvim>"]:0: invalid type name: nil + stack traceback: + [string "<nvim>"]:0: in main chunk]]), pcall_err(exec_lua, "vim.validate{ arg1={ 1 }}")) -- Validated parameters are required by default. - eq("Error executing lua: .../shared.lua: arg1: expected string, got nil", + eq(dedent([[ + Error executing lua: [string "<nvim>"]:0: arg1: expected string, got nil + stack traceback: + [string "<nvim>"]:0: in main chunk]]), pcall_err(exec_lua, "vim.validate{ arg1={ nil, 's' }}")) -- Explicitly required. - eq("Error executing lua: .../shared.lua: arg1: expected string, got nil", + eq(dedent([[ + Error executing lua: [string "<nvim>"]:0: arg1: expected string, got nil + stack traceback: + [string "<nvim>"]:0: in main chunk]]), pcall_err(exec_lua, "vim.validate{ arg1={ nil, 's', false }}")) - eq("Error executing lua: .../shared.lua: arg1: expected table, got number", + eq(dedent([[ + Error executing lua: [string "<nvim>"]:0: arg1: expected table, got number + stack traceback: + [string "<nvim>"]:0: in main chunk]]), pcall_err(exec_lua, "vim.validate{arg1={1, 't'}}")) - eq("Error executing lua: .../shared.lua: arg2: expected string, got number", + eq(dedent([[ + Error executing lua: [string "<nvim>"]:0: arg2: expected string, got number + stack traceback: + [string "<nvim>"]:0: in main chunk]]), pcall_err(exec_lua, "vim.validate{arg1={{}, 't'}, arg2={1, 's'}}")) - eq("Error executing lua: .../shared.lua: arg2: expected string, got nil", + eq(dedent([[ + Error executing lua: [string "<nvim>"]:0: arg2: expected string, got nil + stack traceback: + [string "<nvim>"]:0: in main chunk]]), pcall_err(exec_lua, "vim.validate{arg1={{}, 't'}, arg2={nil, 's'}}")) - eq("Error executing lua: .../shared.lua: arg2: expected string, got nil", + eq(dedent([[ + Error executing lua: [string "<nvim>"]:0: arg2: expected string, got nil + stack traceback: + [string "<nvim>"]:0: in main chunk]]), pcall_err(exec_lua, "vim.validate{arg1={{}, 't'}, arg2={nil, 's'}}")) - eq("Error executing lua: .../shared.lua: arg1: expected even number, got 3", + eq(dedent([[ + Error executing lua: [string "<nvim>"]:0: arg1: expected even number, got 3 + stack traceback: + [string "<nvim>"]:0: in main chunk]]), pcall_err(exec_lua, "vim.validate{arg1={3, function(a) return a == 1 end, 'even number'}}")) - eq("Error executing lua: .../shared.lua: arg1: expected ?, got 3", + eq(dedent([[ + Error executing lua: [string "<nvim>"]:0: arg1: expected ?, got 3 + stack traceback: + [string "<nvim>"]:0: in main chunk]]), pcall_err(exec_lua, "vim.validate{arg1={3, function(a) return a == 1 end}}")) + + -- Pass an additional message back. + eq(dedent([[ + Error executing lua: [string "<nvim>"]:0: arg1: expected ?, got 3. Info: TEST_MSG + stack traceback: + [string "<nvim>"]:0: in main chunk]]), + pcall_err(exec_lua, "vim.validate{arg1={3, function(a) return a == 1, 'TEST_MSG' end}}")) end) it('vim.is_callable', function() @@ -1068,6 +1138,104 @@ describe('lua stdlib', function() eq({5,15}, exec_lua[[ return vim.region(0,{1,5},{1,14},'v',true)[1] ]]) end) + describe('vim.execute_on_keystroke', function() + it('should keep track of keystrokes', function() + helpers.insert([[hello world ]]) + + exec_lua [[ + KeysPressed = {} + + vim.register_keystroke_callback(function(buf) + if buf:byte() == 27 then + buf = "<ESC>" + end + + table.insert(KeysPressed, buf) + end) + ]] + + helpers.insert([[next ๐คฆ lines รฅ ]]) + + -- It has escape in the keys pressed + eq('inext ๐คฆ lines รฅ <ESC>', exec_lua [[return table.concat(KeysPressed, '')]]) + end) + + it('should allow removing trackers.', function() + helpers.insert([[hello world]]) + + exec_lua [[ + KeysPressed = {} + + return vim.register_keystroke_callback(function(buf) + if buf:byte() == 27 then + buf = "<ESC>" + end + + table.insert(KeysPressed, buf) + end, vim.api.nvim_create_namespace("logger")) + ]] + + helpers.insert([[next lines]]) + + exec_lua("vim.register_keystroke_callback(nil, vim.api.nvim_create_namespace('logger'))") + + helpers.insert([[more lines]]) + + -- It has escape in the keys pressed + eq('inext lines<ESC>', exec_lua [[return table.concat(KeysPressed, '')]]) + end) + + it('should not call functions that error again.', function() + helpers.insert([[hello world]]) + + exec_lua [[ + KeysPressed = {} + + return vim.register_keystroke_callback(function(buf) + if buf:byte() == 27 then + buf = "<ESC>" + end + + table.insert(KeysPressed, buf) + + if buf == 'l' then + error("Dumb Error") + end + end) + ]] + + helpers.insert([[next lines]]) + helpers.insert([[more lines]]) + + -- Only the first letter gets added. After that we remove the callback + eq('inext l', exec_lua [[ return table.concat(KeysPressed, '') ]]) + end) + + it('should process mapped keys, not unmapped keys', function() + exec_lua [[ + KeysPressed = {} + + vim.cmd("inoremap hello world") + + vim.register_keystroke_callback(function(buf) + if buf:byte() == 27 then + buf = "<ESC>" + end + + table.insert(KeysPressed, buf) + end) + ]] + + helpers.insert("hello") + + local next_status = exec_lua [[ + return table.concat(KeysPressed, '') + ]] + + eq("iworld<ESC>", next_status) + end) + end) + describe('vim.wait', function() before_each(function() exec_lua[[ @@ -1116,6 +1284,23 @@ describe('lua stdlib', function() ]]) end) + it('should not process non-fast events when commanded', function() + eq({wait_result = false}, exec_lua[[ + start_time = get_time() + + vim.g.timer_result = false + timer = vim.loop.new_timer() + timer:start(100, 0, vim.schedule_wrap(function() + vim.g.timer_result = true + end)) + + wait_result = vim.wait(300, function() return vim.g.timer_result end, nil, true) + + return { + wait_result = wait_result, + } + ]]) + end) it('should work with vim.defer_fn', function() eq({time = true, wait_result = true}, exec_lua[[ start_time = get_time() @@ -1130,22 +1315,32 @@ describe('lua stdlib', function() ]]) end) - it('should require functions to be passed', function() - local pcall_result = exec_lua [[ - return {pcall(function() vim.wait(1000, 13) end)} - ]] + it('should not crash when callback errors', function() + eq({false, '[string "<nvim>"]:1: As Expected'}, exec_lua [[ + return {pcall(function() vim.wait(1000, function() error("As Expected") end) end)} + ]]) + end) - eq(pcall_result[1], false) - matches('condition must be a function', pcall_result[2]) + it('if callback is passed, it must be a function', function() + eq({false, 'vim.wait: if passed, condition must be a function'}, exec_lua [[ + return {pcall(function() vim.wait(1000, 13) end)} + ]]) end) - it('should not crash when callback errors', function() - local pcall_result = exec_lua [[ - return {pcall(function() vim.wait(1000, function() error("As Expected") end) end)} - ]] + it('should allow waiting with no callback, explicit', function() + eq(true, exec_lua [[ + local start_time = vim.loop.hrtime() + vim.wait(50, nil) + return vim.loop.hrtime() - start_time > 25000 + ]]) + end) - eq(pcall_result[1], false) - matches('As Expected', pcall_result[2]) + it('should allow waiting with no callback, implicit', function() + eq(true, exec_lua [[ + local start_time = vim.loop.hrtime() + vim.wait(50) + return vim.loop.hrtime() - start_time > 25000 + ]]) end) it('should call callbacks exactly once if they return true immediately', function() @@ -1232,4 +1427,29 @@ describe('lua stdlib', function() eq(false, pcall_result) end) end) + + describe('vim.api.nvim_buf_call', function() + it('can access buf options', function() + local buf1 = meths.get_current_buf() + local buf2 = exec_lua [[ + buf2 = vim.api.nvim_create_buf(false, true) + return buf2 + ]] + + eq(false, meths.buf_get_option(buf1, 'autoindent')) + eq(false, meths.buf_get_option(buf2, 'autoindent')) + + local val = exec_lua [[ + return vim.api.nvim_buf_call(buf2, function() + vim.cmd "set autoindent" + return vim.api.nvim_get_current_buf() + end) + ]] + + eq(false, meths.buf_get_option(buf1, 'autoindent')) + eq(true, meths.buf_get_option(buf2, 'autoindent')) + eq(buf1, meths.get_current_buf()) + eq(buf2, val) + end) + end) end) diff --git a/test/functional/normal/meta_key_spec.lua b/test/functional/normal/meta_key_spec.lua new file mode 100644 index 0000000000..9f9fad67d2 --- /dev/null +++ b/test/functional/normal/meta_key_spec.lua @@ -0,0 +1,22 @@ +local helpers = require('test.functional.helpers')(after_each) +local clear, feed, insert = helpers.clear, helpers.feed, helpers.insert +local command = helpers.command +local expect = helpers.expect + +describe('meta-keys-in-normal-mode', function() + before_each(function() + clear() + end) + + it('ALT/META', function() + -- Unmapped ALT-chords behave as Esc+c + insert('hello') + feed('0<A-x><M-x>') + expect('llo') + -- Mapped ALT-chord behaves as mapped. + command('nnoremap <M-l> Ameta-l<Esc>') + command('nnoremap <A-j> Aalt-j<Esc>') + feed('<A-j><M-l>') + expect('lloalt-jmeta-l') + end) +end) diff --git a/test/functional/options/defaults_spec.lua b/test/functional/options/defaults_spec.lua index 11ce26410d..92d077ed14 100644 --- a/test/functional/options/defaults_spec.lua +++ b/test/functional/options/defaults_spec.lua @@ -290,9 +290,6 @@ describe('XDG-based defaults', function() end) end) - -- TODO(jkeyes): tests below fail on win32 because of path separator. - if helpers.pending_win32(pending) then return end - local function vimruntime_and_libdir() local vimruntime = eval('$VIMRUNTIME') -- libdir is hard to calculate reliably across various ci platforms @@ -301,71 +298,78 @@ describe('XDG-based defaults', function() return vimruntime, libdir end + local env_sep = iswin() and ';' or ':' + local data_dir = iswin() and 'nvim-data' or 'nvim' + local root_path = iswin() and 'C:' or '' + describe('with too long XDG variables', function() before_each(function() clear({env={ - XDG_CONFIG_HOME=('/x'):rep(4096), - XDG_CONFIG_DIRS=(('/a'):rep(2048) - .. ':' .. ('/b'):rep(2048) - .. (':/c'):rep(512)), - XDG_DATA_HOME=('/X'):rep(4096), - XDG_DATA_DIRS=(('/A'):rep(2048) - .. ':' .. ('/B'):rep(2048) - .. (':/C'):rep(512)), + XDG_CONFIG_HOME=(root_path .. ('/x'):rep(4096)), + XDG_CONFIG_DIRS=(root_path .. ('/a'):rep(2048) + .. env_sep.. root_path .. ('/b'):rep(2048) + .. (env_sep .. root_path .. '/c'):rep(512)), + XDG_DATA_HOME=(root_path .. ('/X'):rep(4096)), + XDG_DATA_DIRS=(root_path .. ('/A'):rep(2048) + .. env_sep .. root_path .. ('/B'):rep(2048) + .. (env_sep .. root_path .. '/C'):rep(512)), }}) end) it('are correctly set', function() local vimruntime, libdir = vimruntime_and_libdir() - eq((('/x'):rep(4096) .. '/nvim' - .. ',' .. ('/a'):rep(2048) .. '/nvim' - .. ',' .. ('/b'):rep(2048) .. '/nvim' - .. (',' .. '/c/nvim'):rep(512) - .. ',' .. ('/X'):rep(4096) .. '/nvim/site' - .. ',' .. ('/A'):rep(2048) .. '/nvim/site' - .. ',' .. ('/B'):rep(2048) .. '/nvim/site' - .. (',' .. '/C/nvim/site'):rep(512) + eq(((root_path .. ('/x'):rep(4096) .. '/nvim' + .. ',' .. root_path .. ('/a'):rep(2048) .. '/nvim' + .. ',' .. root_path .. ('/b'):rep(2048) .. '/nvim' + .. (',' .. root_path .. '/c/nvim'):rep(512) + .. ',' .. root_path .. ('/X'):rep(4096) .. '/' .. data_dir .. '/site' + .. ',' .. root_path .. ('/A'):rep(2048) .. '/nvim/site' + .. ',' .. root_path .. ('/B'):rep(2048) .. '/nvim/site' + .. (',' .. root_path .. '/C/nvim/site'):rep(512) .. ',' .. vimruntime .. ',' .. libdir - .. (',' .. '/C/nvim/site/after'):rep(512) - .. ',' .. ('/B'):rep(2048) .. '/nvim/site/after' - .. ',' .. ('/A'):rep(2048) .. '/nvim/site/after' - .. ',' .. ('/X'):rep(4096) .. '/nvim/site/after' - .. (',' .. '/c/nvim/after'):rep(512) - .. ',' .. ('/b'):rep(2048) .. '/nvim/after' - .. ',' .. ('/a'):rep(2048) .. '/nvim/after' - .. ',' .. ('/x'):rep(4096) .. '/nvim/after' - ), meths.get_option('runtimepath')) + .. (',' .. root_path .. '/C/nvim/site/after'):rep(512) + .. ',' .. root_path .. ('/B'):rep(2048) .. '/nvim/site/after' + .. ',' .. root_path .. ('/A'):rep(2048) .. '/nvim/site/after' + .. ',' .. root_path .. ('/X'):rep(4096) .. '/' .. data_dir .. '/site/after' + .. (',' .. root_path .. '/c/nvim/after'):rep(512) + .. ',' .. root_path .. ('/b'):rep(2048) .. '/nvim/after' + .. ',' .. root_path .. ('/a'):rep(2048) .. '/nvim/after' + .. ',' .. root_path .. ('/x'):rep(4096) .. '/nvim/after' + ):gsub('\\', '/')), (meths.get_option('runtimepath')):gsub('\\', '/')) meths.command('set runtimepath&') meths.command('set backupdir&') meths.command('set directory&') meths.command('set undodir&') meths.command('set viewdir&') - eq((('/x'):rep(4096) .. '/nvim' - .. ',' .. ('/a'):rep(2048) .. '/nvim' - .. ',' .. ('/b'):rep(2048) .. '/nvim' - .. (',' .. '/c/nvim'):rep(512) - .. ',' .. ('/X'):rep(4096) .. '/nvim/site' - .. ',' .. ('/A'):rep(2048) .. '/nvim/site' - .. ',' .. ('/B'):rep(2048) .. '/nvim/site' - .. (',' .. '/C/nvim/site'):rep(512) + eq(((root_path .. ('/x'):rep(4096) .. '/nvim' + .. ',' .. root_path .. ('/a'):rep(2048) .. '/nvim' + .. ',' .. root_path .. ('/b'):rep(2048) .. '/nvim' + .. (',' .. root_path .. '/c/nvim'):rep(512) + .. ',' .. root_path .. ('/X'):rep(4096) .. '/' .. data_dir .. '/site' + .. ',' .. root_path .. ('/A'):rep(2048) .. '/nvim/site' + .. ',' .. root_path .. ('/B'):rep(2048) .. '/nvim/site' + .. (',' .. root_path .. '/C/nvim/site'):rep(512) .. ',' .. vimruntime .. ',' .. libdir - .. (',' .. '/C/nvim/site/after'):rep(512) - .. ',' .. ('/B'):rep(2048) .. '/nvim/site/after' - .. ',' .. ('/A'):rep(2048) .. '/nvim/site/after' - .. ',' .. ('/X'):rep(4096) .. '/nvim/site/after' - .. (',' .. '/c/nvim/after'):rep(512) - .. ',' .. ('/b'):rep(2048) .. '/nvim/after' - .. ',' .. ('/a'):rep(2048) .. '/nvim/after' - .. ',' .. ('/x'):rep(4096) .. '/nvim/after' - ), meths.get_option('runtimepath')) - eq('.,' .. ('/X'):rep(4096) .. '/nvim/backup', - meths.get_option('backupdir')) - eq(('/X'):rep(4096) .. '/nvim/swap//', meths.get_option('directory')) - eq(('/X'):rep(4096) .. '/nvim/undo', meths.get_option('undodir')) - eq(('/X'):rep(4096) .. '/nvim/view', meths.get_option('viewdir')) + .. (',' .. root_path .. '/C/nvim/site/after'):rep(512) + .. ',' .. root_path .. ('/B'):rep(2048) .. '/nvim/site/after' + .. ',' .. root_path .. ('/A'):rep(2048) .. '/nvim/site/after' + .. ',' .. root_path .. ('/X'):rep(4096) .. '/' .. data_dir .. '/site/after' + .. (',' .. root_path .. '/c/nvim/after'):rep(512) + .. ',' .. root_path .. ('/b'):rep(2048) .. '/nvim/after' + .. ',' .. root_path .. ('/a'):rep(2048) .. '/nvim/after' + .. ',' .. root_path .. ('/x'):rep(4096) .. '/nvim/after' + ):gsub('\\', '/')), (meths.get_option('runtimepath')):gsub('\\', '/')) + eq('.,' .. root_path .. ('/X'):rep(4096).. '/' .. data_dir .. '/backup', + (meths.get_option('backupdir'):gsub('\\', '/'))) + eq(root_path .. ('/X'):rep(4096) .. '/' .. data_dir .. '/swap//', + (meths.get_option('directory')):gsub('\\', '/')) + eq(root_path .. ('/X'):rep(4096) .. '/' .. data_dir .. '/undo', + (meths.get_option('undodir')):gsub('\\', '/')) + eq(root_path .. ('/X'):rep(4096) .. '/' .. data_dir .. '/view', + (meths.get_option('viewdir')):gsub('\\', '/')) end) end) @@ -381,53 +385,61 @@ describe('XDG-based defaults', function() it('are not expanded', function() local vimruntime, libdir = vimruntime_and_libdir() - eq(('$XDG_DATA_HOME/nvim' + eq((('$XDG_DATA_HOME/nvim' .. ',$XDG_DATA_DIRS/nvim' - .. ',$XDG_CONFIG_HOME/nvim/site' + .. ',$XDG_CONFIG_HOME/' .. data_dir .. '/site' .. ',$XDG_CONFIG_DIRS/nvim/site' .. ',' .. vimruntime .. ',' .. libdir .. ',$XDG_CONFIG_DIRS/nvim/site/after' - .. ',$XDG_CONFIG_HOME/nvim/site/after' + .. ',$XDG_CONFIG_HOME/' .. data_dir .. '/site/after' .. ',$XDG_DATA_DIRS/nvim/after' .. ',$XDG_DATA_HOME/nvim/after' - ), meths.get_option('runtimepath')) + ):gsub('\\', '/')), (meths.get_option('runtimepath')):gsub('\\', '/')) meths.command('set runtimepath&') meths.command('set backupdir&') meths.command('set directory&') meths.command('set undodir&') meths.command('set viewdir&') - eq(('$XDG_DATA_HOME/nvim' + eq((('$XDG_DATA_HOME/nvim' .. ',$XDG_DATA_DIRS/nvim' - .. ',$XDG_CONFIG_HOME/nvim/site' + .. ',$XDG_CONFIG_HOME/' .. data_dir .. '/site' .. ',$XDG_CONFIG_DIRS/nvim/site' .. ',' .. vimruntime .. ',' .. libdir .. ',$XDG_CONFIG_DIRS/nvim/site/after' - .. ',$XDG_CONFIG_HOME/nvim/site/after' + .. ',$XDG_CONFIG_HOME/' .. data_dir .. '/site/after' .. ',$XDG_DATA_DIRS/nvim/after' .. ',$XDG_DATA_HOME/nvim/after' - ), meths.get_option('runtimepath')) - eq('.,$XDG_CONFIG_HOME/nvim/backup', meths.get_option('backupdir')) - eq('$XDG_CONFIG_HOME/nvim/swap//', meths.get_option('directory')) - eq('$XDG_CONFIG_HOME/nvim/undo', meths.get_option('undodir')) - eq('$XDG_CONFIG_HOME/nvim/view', meths.get_option('viewdir')) + ):gsub('\\', '/')), (meths.get_option('runtimepath')):gsub('\\', '/')) + eq(('.,$XDG_CONFIG_HOME/' .. data_dir .. '/backup'), + meths.get_option('backupdir'):gsub('\\', '/')) + eq(('$XDG_CONFIG_HOME/' .. data_dir .. '/swap//'), + meths.get_option('directory'):gsub('\\', '/')) + eq(('$XDG_CONFIG_HOME/' .. data_dir .. '/undo'), + meths.get_option('undodir'):gsub('\\', '/')) + eq(('$XDG_CONFIG_HOME/' .. data_dir .. '/view'), + meths.get_option('viewdir'):gsub('\\', '/')) meths.command('set all&') eq(('$XDG_DATA_HOME/nvim' .. ',$XDG_DATA_DIRS/nvim' - .. ',$XDG_CONFIG_HOME/nvim/site' + .. ',$XDG_CONFIG_HOME/' .. data_dir .. '/site' .. ',$XDG_CONFIG_DIRS/nvim/site' .. ',' .. vimruntime .. ',' .. libdir .. ',$XDG_CONFIG_DIRS/nvim/site/after' - .. ',$XDG_CONFIG_HOME/nvim/site/after' + .. ',$XDG_CONFIG_HOME/' .. data_dir .. '/site/after' .. ',$XDG_DATA_DIRS/nvim/after' .. ',$XDG_DATA_HOME/nvim/after' - ), meths.get_option('runtimepath')) - eq('.,$XDG_CONFIG_HOME/nvim/backup', meths.get_option('backupdir')) - eq('$XDG_CONFIG_HOME/nvim/swap//', meths.get_option('directory')) - eq('$XDG_CONFIG_HOME/nvim/undo', meths.get_option('undodir')) - eq('$XDG_CONFIG_HOME/nvim/view', meths.get_option('viewdir')) + ):gsub('\\', '/'), (meths.get_option('runtimepath')):gsub('\\', '/')) + eq(('.,$XDG_CONFIG_HOME/' .. data_dir .. '/backup'), + meths.get_option('backupdir'):gsub('\\', '/')) + eq(('$XDG_CONFIG_HOME/' .. data_dir .. '/swap//'), + meths.get_option('directory'):gsub('\\', '/')) + eq(('$XDG_CONFIG_HOME/' .. data_dir .. '/undo'), + meths.get_option('undodir'):gsub('\\', '/')) + eq(('$XDG_CONFIG_HOME/' .. data_dir .. '/view'), + meths.get_option('viewdir'):gsub('\\', '/')) end) end) @@ -435,53 +447,58 @@ describe('XDG-based defaults', function() before_each(function() clear({env={ XDG_CONFIG_HOME=', , ,', - XDG_CONFIG_DIRS=',-,-,:-,-,-', + XDG_CONFIG_DIRS=',-,-,' .. env_sep .. '-,-,-', XDG_DATA_HOME=',=,=,', - XDG_DATA_DIRS=',โก,โก,:โก,โก,โก', + XDG_DATA_DIRS=',โก,โก,' .. env_sep .. 'โก,โก,โก', }}) end) it('are escaped properly', function() local vimruntime, libdir = vimruntime_and_libdir() - eq(('\\, \\, \\,/nvim' - .. ',\\,-\\,-\\,/nvim' - .. ',-\\,-\\,-/nvim' - .. ',\\,=\\,=\\,/nvim/site' - .. ',\\,โก\\,โก\\,/nvim/site' - .. ',โก\\,โก\\,โก/nvim/site' + local path_sep = iswin() and '\\' or '/' + eq(('\\, \\, \\,' .. path_sep .. 'nvim' + .. ',\\,-\\,-\\,' .. path_sep .. 'nvim' + .. ',-\\,-\\,-' .. path_sep .. 'nvim' + .. ',\\,=\\,=\\,' .. path_sep .. data_dir .. path_sep .. 'site' + .. ',\\,โก\\,โก\\,' .. path_sep .. 'nvim' .. path_sep .. 'site' + .. ',โก\\,โก\\,โก' .. path_sep .. 'nvim' .. path_sep .. 'site' .. ',' .. vimruntime .. ',' .. libdir - .. ',โก\\,โก\\,โก/nvim/site/after' - .. ',\\,โก\\,โก\\,/nvim/site/after' - .. ',\\,=\\,=\\,/nvim/site/after' - .. ',-\\,-\\,-/nvim/after' - .. ',\\,-\\,-\\,/nvim/after' - .. ',\\, \\, \\,/nvim/after' + .. ',โก\\,โก\\,โก' .. path_sep .. 'nvim' .. path_sep .. 'site' .. path_sep .. 'after' + .. ',\\,โก\\,โก\\,' .. path_sep .. 'nvim' .. path_sep .. 'site' .. path_sep .. 'after' + .. ',\\,=\\,=\\,' .. path_sep.. data_dir .. path_sep .. 'site' .. path_sep .. 'after' + .. ',-\\,-\\,-' .. path_sep .. 'nvim' .. path_sep .. 'after' + .. ',\\,-\\,-\\,' .. path_sep .. 'nvim' .. path_sep .. 'after' + .. ',\\, \\, \\,' .. path_sep .. 'nvim' .. path_sep .. 'after' ), meths.get_option('runtimepath')) meths.command('set runtimepath&') meths.command('set backupdir&') meths.command('set directory&') meths.command('set undodir&') meths.command('set viewdir&') - eq(('\\, \\, \\,/nvim' - .. ',\\,-\\,-\\,/nvim' - .. ',-\\,-\\,-/nvim' - .. ',\\,=\\,=\\,/nvim/site' - .. ',\\,โก\\,โก\\,/nvim/site' - .. ',โก\\,โก\\,โก/nvim/site' + eq(('\\, \\, \\,' .. path_sep .. 'nvim' + .. ',\\,-\\,-\\,' .. path_sep ..'nvim' + .. ',-\\,-\\,-' .. path_sep ..'nvim' + .. ',\\,=\\,=\\,' .. path_sep ..'' .. data_dir .. '' .. path_sep ..'site' + .. ',\\,โก\\,โก\\,' .. path_sep ..'nvim' .. path_sep ..'site' + .. ',โก\\,โก\\,โก' .. path_sep ..'nvim' .. path_sep ..'site' .. ',' .. vimruntime .. ',' .. libdir - .. ',โก\\,โก\\,โก/nvim/site/after' - .. ',\\,โก\\,โก\\,/nvim/site/after' - .. ',\\,=\\,=\\,/nvim/site/after' - .. ',-\\,-\\,-/nvim/after' - .. ',\\,-\\,-\\,/nvim/after' - .. ',\\, \\, \\,/nvim/after' + .. ',โก\\,โก\\,โก' .. path_sep ..'nvim' .. path_sep ..'site' .. path_sep ..'after' + .. ',\\,โก\\,โก\\,' .. path_sep ..'nvim' .. path_sep ..'site' .. path_sep ..'after' + .. ',\\,=\\,=\\,' .. path_sep ..'' .. data_dir .. '' .. path_sep ..'site' .. path_sep ..'after' + .. ',-\\,-\\,-' .. path_sep ..'nvim' .. path_sep ..'after' + .. ',\\,-\\,-\\,' .. path_sep ..'nvim' .. path_sep ..'after' + .. ',\\, \\, \\,' .. path_sep ..'nvim' .. path_sep ..'after' ), meths.get_option('runtimepath')) - eq('.,\\,=\\,=\\,/nvim/backup', meths.get_option('backupdir')) - eq('\\,=\\,=\\,/nvim/swap//', meths.get_option('directory')) - eq('\\,=\\,=\\,/nvim/undo', meths.get_option('undodir')) - eq('\\,=\\,=\\,/nvim/view', meths.get_option('viewdir')) + eq('.,\\,=\\,=\\,' .. path_sep .. data_dir .. '' .. path_sep ..'backup', + meths.get_option('backupdir')) + eq('\\,=\\,=\\,' .. path_sep ..'' .. data_dir .. '' .. path_sep ..'swap' .. (path_sep):rep(2), + meths.get_option('directory')) + eq('\\,=\\,=\\,' .. path_sep ..'' .. data_dir .. '' .. path_sep ..'undo', + meths.get_option('undodir')) + eq('\\,=\\,=\\,' .. path_sep ..'' .. data_dir .. '' .. path_sep ..'view', + meths.get_option('viewdir')) end) end) end) @@ -491,6 +508,7 @@ describe('stdpath()', function() -- Windows appends 'nvim-data' instead of just 'nvim' to prevent collisions -- due to XDG_CONFIG_HOME and XDG_DATA_HOME being the same. local datadir = iswin() and 'nvim-data' or 'nvim' + local env_sep = iswin() and ';' or ':' it('acceptance', function() clear() -- Do not explicitly set any env vars. @@ -634,13 +652,13 @@ describe('stdpath()', function() local function set_paths_via_system(var_name, paths) local env = base_env() - env[var_name] = table.concat(paths, ':') + env[var_name] = table.concat(paths, env_sep) clear({env=env}) end local function set_paths_at_runtime(var_name, paths) clear({env=base_env()}) - meths.set_var('env_val', table.concat(paths, ':')) + meths.set_var('env_val', table.concat(paths, env_sep)) command(('let $%s=g:env_val'):format(var_name)) end diff --git a/test/functional/plugin/lsp/diagnostic_spec.lua b/test/functional/plugin/lsp/diagnostic_spec.lua new file mode 100644 index 0000000000..0fb55da4bd --- /dev/null +++ b/test/functional/plugin/lsp/diagnostic_spec.lua @@ -0,0 +1,767 @@ +local helpers = require('test.functional.helpers')(after_each) + +local clear = helpers.clear +local exec_lua = helpers.exec_lua +local eq = helpers.eq +local nvim = helpers.nvim + +describe('vim.lsp.diagnostic', function() + local fake_uri + + before_each(function() + clear() + + exec_lua [[ + require('vim.lsp') + + make_range = function(x1, y1, x2, y2) + return { start = { line = x1, character = y1 }, ['end'] = { line = x2, character = y2 } } + end + + make_error = function(msg, x1, y1, x2, y2) + return { + range = make_range(x1, y1, x2, y2), + message = msg, + severity = 1, + } + end + + make_warning = function(msg, x1, y1, x2, y2) + return { + range = make_range(x1, y1, x2, y2), + message = msg, + severity = 2, + } + end + + make_information = function(msg, x1, y1, x2, y2) + return { + range = make_range(x1, y1, x2, y2), + message = msg, + severity = 3, + } + end + + count_of_extmarks_for_client = function(bufnr, client_id) + return #vim.api.nvim_buf_get_extmarks( + bufnr, vim.lsp.diagnostic._get_diagnostic_namespace(client_id), 0, -1, {} + ) + end + ]] + + fake_uri = "file://fake/uri" + + exec_lua([[ + fake_uri = ... + diagnostic_bufnr = vim.uri_to_bufnr(fake_uri) + local lines = {"1st line of text", "2nd line of text", "wow", "cool", "more", "lines"} + vim.fn.bufload(diagnostic_bufnr) + vim.api.nvim_buf_set_lines(diagnostic_bufnr, 0, 1, false, lines) + return diagnostic_bufnr + ]], fake_uri) + end) + + after_each(function() + clear() + end) + + describe('vim.lsp.diagnostic', function() + describe('handle_publish_diagnostics', function() + it('should be able to save and count a single client error', function() + eq(1, exec_lua [[ + vim.lsp.diagnostic.save( + { + make_error('Diagnostic #1', 1, 1, 1, 1), + }, 0, 1 + ) + return vim.lsp.diagnostic.get_count(0, "Error", 1) + ]]) + end) + + it('should be able to save and count from two clients', function() + eq(2, exec_lua [[ + vim.lsp.diagnostic.save( + { + make_error('Diagnostic #1', 1, 1, 1, 1), + make_error('Diagnostic #2', 2, 1, 2, 1), + }, 0, 1 + ) + return vim.lsp.diagnostic.get_count(0, "Error", 1) + ]]) + end) + + it('should be able to save and count from multiple clients', function() + eq({1, 1, 2}, exec_lua [[ + vim.lsp.diagnostic.save( + { + make_error('Diagnostic From Server 1', 1, 1, 1, 1), + }, 0, 1 + ) + vim.lsp.diagnostic.save( + { + make_error('Diagnostic From Server 2', 1, 1, 1, 1), + }, 0, 2 + ) + return { + -- Server 1 + vim.lsp.diagnostic.get_count(0, "Error", 1), + -- Server 2 + vim.lsp.diagnostic.get_count(0, "Error", 2), + -- All servers + vim.lsp.diagnostic.get_count(0, "Error", nil), + } + ]]) + end) + + it('should be able to save and count from multiple clients with respect to severity', function() + eq({3, 0, 3}, exec_lua [[ + vim.lsp.diagnostic.save( + { + make_error('Diagnostic From Server 1:1', 1, 1, 1, 1), + make_error('Diagnostic From Server 1:2', 2, 2, 2, 2), + make_error('Diagnostic From Server 1:3', 2, 3, 3, 2), + }, 0, 1 + ) + vim.lsp.diagnostic.save( + { + make_warning('Warning From Server 2', 3, 3, 3, 3), + }, 0, 2 + ) + return { + -- Server 1 + vim.lsp.diagnostic.get_count(0, "Error", 1), + -- Server 2 + vim.lsp.diagnostic.get_count(0, "Error", 2), + -- All servers + vim.lsp.diagnostic.get_count(0, "Error", nil), + } + ]]) + end) + + it('should handle one server clearing highlights while the other still has highlights', function() + -- 1 Error (1) + -- 1 Warning (2) + -- 1 Warning (2) + 1 Warning (1) + -- 2 highlights and 2 underlines (since error) + -- 1 highlight + 1 underline + local all_highlights = {1, 1, 2, 4, 2} + eq(all_highlights, exec_lua [[ + local server_1_diags = { + make_error("Error 1", 1, 1, 1, 5), + make_warning("Warning on Server 1", 2, 1, 2, 5), + } + local server_2_diags = { + make_warning("Warning 1", 2, 1, 2, 5), + } + + vim.lsp.diagnostic.on_publish_diagnostics(nil, nil, { uri = fake_uri, diagnostics = server_1_diags }, 1) + vim.lsp.diagnostic.on_publish_diagnostics(nil, nil, { uri = fake_uri, diagnostics = server_2_diags }, 2) + return { + vim.lsp.diagnostic.get_count(diagnostic_bufnr, "Error", 1), + vim.lsp.diagnostic.get_count(diagnostic_bufnr, "Warning", 2), + vim.lsp.diagnostic.get_count(diagnostic_bufnr, "Warning", nil), + count_of_extmarks_for_client(diagnostic_bufnr, 1), + count_of_extmarks_for_client(diagnostic_bufnr, 2), + } + ]]) + + -- Clear diagnostics from server 1, and make sure we have the right amount of stuff for client 2 + eq({1, 1, 2, 0, 2}, exec_lua [[ + vim.lsp.diagnostic.clear(diagnostic_bufnr, 1) + return { + vim.lsp.diagnostic.get_count(diagnostic_bufnr, "Error", 1), + vim.lsp.diagnostic.get_count(diagnostic_bufnr, "Warning", 2), + vim.lsp.diagnostic.get_count(diagnostic_bufnr, "Warning", nil), + count_of_extmarks_for_client(diagnostic_bufnr, 1), + count_of_extmarks_for_client(diagnostic_bufnr, 2), + } + ]]) + + -- Show diagnostics from server 1 again + eq(all_highlights, exec_lua([[ + vim.lsp.diagnostic.display(nil, diagnostic_bufnr, 1) + return { + vim.lsp.diagnostic.get_count(diagnostic_bufnr, "Error", 1), + vim.lsp.diagnostic.get_count(diagnostic_bufnr, "Warning", 2), + vim.lsp.diagnostic.get_count(diagnostic_bufnr, "Warning", nil), + count_of_extmarks_for_client(diagnostic_bufnr, 1), + count_of_extmarks_for_client(diagnostic_bufnr, 2), + } + ]])) + end) + + describe('get_next_diagnostic_pos', function() + it('can find the next pos with only one client', function() + eq({1, 1}, exec_lua [[ + vim.lsp.diagnostic.save( + { + make_error('Diagnostic #1', 1, 1, 1, 1), + }, diagnostic_bufnr, 1 + ) + vim.api.nvim_win_set_buf(0, diagnostic_bufnr) + return vim.lsp.diagnostic.get_next_pos() + ]]) + end) + + it('can find next pos with two errors', function() + eq({4, 4}, exec_lua [[ + vim.lsp.diagnostic.save( + { + make_error('Diagnostic #1', 1, 1, 1, 1), + make_error('Diagnostic #2', 4, 4, 4, 4), + }, diagnostic_bufnr, 1 + ) + vim.api.nvim_win_set_buf(0, diagnostic_bufnr) + vim.api.nvim_win_set_cursor(0, {3, 1}) + return vim.lsp.diagnostic.get_next_pos { client_id = 1 } + ]]) + end) + + it('can cycle when position is past error', function() + eq({1, 1}, exec_lua [[ + vim.lsp.diagnostic.save( + { + make_error('Diagnostic #1', 1, 1, 1, 1), + }, diagnostic_bufnr, 1 + ) + vim.api.nvim_win_set_buf(0, diagnostic_bufnr) + vim.api.nvim_win_set_cursor(0, {3, 1}) + return vim.lsp.diagnostic.get_next_pos { client_id = 1 } + ]]) + end) + + it('will not cycle when wrap is off', function() + eq(false, exec_lua [[ + vim.lsp.diagnostic.save( + { + make_error('Diagnostic #1', 1, 1, 1, 1), + }, diagnostic_bufnr, 1 + ) + vim.api.nvim_win_set_buf(0, diagnostic_bufnr) + vim.api.nvim_win_set_cursor(0, {3, 1}) + return vim.lsp.diagnostic.get_next_pos { client_id = 1, wrap = false } + ]]) + end) + + it('can cycle even from the last line', function() + eq({4, 4}, exec_lua [[ + vim.lsp.diagnostic.save( + { + make_error('Diagnostic #2', 4, 4, 4, 4), + }, diagnostic_bufnr, 1 + ) + vim.api.nvim_win_set_buf(0, diagnostic_bufnr) + vim.api.nvim_win_set_cursor(0, {vim.api.nvim_buf_line_count(0), 1}) + return vim.lsp.diagnostic.get_prev_pos { client_id = 1 } + ]]) + end) + end) + + describe('get_prev_diagnostic_pos', function() + it('can find the prev pos with only one client', function() + eq({1, 1}, exec_lua [[ + vim.lsp.diagnostic.save( + { + make_error('Diagnostic #1', 1, 1, 1, 1), + }, diagnostic_bufnr, 1 + ) + vim.api.nvim_win_set_buf(0, diagnostic_bufnr) + vim.api.nvim_win_set_cursor(0, {3, 1}) + return vim.lsp.diagnostic.get_prev_pos() + ]]) + end) + + it('can find prev pos with two errors', function() + eq({1, 1}, exec_lua [[ + vim.lsp.diagnostic.save( + { + make_error('Diagnostic #1', 1, 1, 1, 1), + make_error('Diagnostic #2', 4, 4, 4, 4), + }, diagnostic_bufnr, 1 + ) + vim.api.nvim_win_set_buf(0, diagnostic_bufnr) + vim.api.nvim_win_set_cursor(0, {3, 1}) + return vim.lsp.diagnostic.get_prev_pos { client_id = 1 } + ]]) + end) + + it('can cycle when position is past error', function() + eq({4, 4}, exec_lua [[ + vim.lsp.diagnostic.save( + { + make_error('Diagnostic #2', 4, 4, 4, 4), + }, diagnostic_bufnr, 1 + ) + vim.api.nvim_win_set_buf(0, diagnostic_bufnr) + vim.api.nvim_win_set_cursor(0, {3, 1}) + return vim.lsp.diagnostic.get_prev_pos { client_id = 1 } + ]]) + end) + + it('respects wrap parameter', function() + eq(false, exec_lua [[ + vim.lsp.diagnostic.save( + { + make_error('Diagnostic #2', 4, 4, 4, 4), + }, diagnostic_bufnr, 1 + ) + vim.api.nvim_win_set_buf(0, diagnostic_bufnr) + vim.api.nvim_win_set_cursor(0, {3, 1}) + return vim.lsp.diagnostic.get_prev_pos { client_id = 1, wrap = false} + ]]) + end) + end) + end) + end) + + describe("vim.lsp.diagnostic.get_line_diagnostics", function() + it('should return an empty table when no diagnostics are present', function() + eq({}, exec_lua [[return vim.lsp.diagnostic.get_line_diagnostics(diagnostic_bufnr, 1)]]) + end) + + it('should return all diagnostics when no severity is supplied', function() + eq(2, exec_lua [[ + vim.lsp.diagnostic.on_publish_diagnostics(nil, nil, { + uri = fake_uri, + diagnostics = { + make_error("Error 1", 1, 1, 1, 5), + make_warning("Warning on Server 1", 1, 1, 2, 5), + make_error("Error On Other Line", 2, 1, 1, 5), + } + }, 1) + + return #vim.lsp.diagnostic.get_line_diagnostics(diagnostic_bufnr, 1) + ]]) + end) + + it('should return only requested diagnostics when severity_limit is supplied', function() + eq(2, exec_lua [[ + vim.lsp.diagnostic.on_publish_diagnostics(nil, nil, { + uri = fake_uri, + diagnostics = { + make_error("Error 1", 1, 1, 1, 5), + make_warning("Warning on Server 1", 1, 1, 2, 5), + make_information("Ignored information", 1, 1, 2, 5), + make_error("Error On Other Line", 2, 1, 1, 5), + } + }, 1) + + return #vim.lsp.diagnostic.get_line_diagnostics(diagnostic_bufnr, 1, { severity_limit = "Warning" }) + ]]) + end) + end) + + describe("vim.lsp.diagnostic.on_publish_diagnostics", function() + it('can use functions for config values', function() + exec_lua [[ + vim.lsp.with(vim.lsp.diagnostic.on_publish_diagnostics, { + virtual_text = function() return true end, + })(nil, nil, { + uri = fake_uri, + diagnostics = { + make_error('Delayed Diagnostic', 4, 4, 4, 4), + } + }, 1 + ) + ]] + + eq(1, exec_lua [[return vim.lsp.diagnostic.get_count(diagnostic_bufnr, "Error", 1)]]) + eq(2, exec_lua [[return count_of_extmarks_for_client(diagnostic_bufnr, 1)]]) + + -- Now, don't enable virtual text. + -- We should have one less extmark displayed. + exec_lua [[ + vim.lsp.with(vim.lsp.diagnostic.on_publish_diagnostics, { + virtual_text = function() return false end, + })(nil, nil, { + uri = fake_uri, + diagnostics = { + make_error('Delayed Diagnostic', 4, 4, 4, 4), + } + }, 1 + ) + ]] + + eq(1, exec_lua [[return vim.lsp.diagnostic.get_count(diagnostic_bufnr, "Error", 1)]]) + eq(1, exec_lua [[return count_of_extmarks_for_client(diagnostic_bufnr, 1)]]) + end) + + it('can perform updates after insert_leave', function() + exec_lua [[vim.api.nvim_set_current_buf(diagnostic_bufnr)]] + nvim("input", "o") + eq({mode='i', blocking=false}, nvim("get_mode")) + + -- Save the diagnostics + exec_lua [[ + vim.lsp.with(vim.lsp.diagnostic.on_publish_diagnostics, { + update_in_insert = false, + })(nil, nil, { + uri = fake_uri, + diagnostics = { + make_error('Delayed Diagnostic', 4, 4, 4, 4), + } + }, 1 + ) + ]] + + -- No diagnostics displayed yet. + eq({mode='i', blocking=false}, nvim("get_mode")) + eq(1, exec_lua [[return vim.lsp.diagnostic.get_count(diagnostic_bufnr, "Error", 1)]]) + eq(0, exec_lua [[return count_of_extmarks_for_client(diagnostic_bufnr, 1)]]) + + nvim("input", "<esc>") + eq({mode='n', blocking=false}, nvim("get_mode")) + + eq(1, exec_lua [[return vim.lsp.diagnostic.get_count(diagnostic_bufnr, "Error", 1)]]) + eq(2, exec_lua [[return count_of_extmarks_for_client(diagnostic_bufnr, 1)]]) + end) + + it('does not perform updates when not needed', function() + exec_lua [[vim.api.nvim_set_current_buf(diagnostic_bufnr)]] + nvim("input", "o") + eq({mode='i', blocking=false}, nvim("get_mode")) + + -- Save the diagnostics + exec_lua [[ + PublishDiagnostics = vim.lsp.with(vim.lsp.diagnostic.on_publish_diagnostics, { + update_in_insert = false, + virtual_text = true, + }) + + -- Count how many times we call display. + SetVirtualTextOriginal = vim.lsp.diagnostic.set_virtual_text + + DisplayCount = 0 + vim.lsp.diagnostic.set_virtual_text = function(...) + DisplayCount = DisplayCount + 1 + return SetVirtualTextOriginal(...) + end + + PublishDiagnostics(nil, nil, { + uri = fake_uri, + diagnostics = { + make_error('Delayed Diagnostic', 4, 4, 4, 4), + } + }, 1 + ) + ]] + + -- No diagnostics displayed yet. + eq({mode='i', blocking=false}, nvim("get_mode")) + eq(1, exec_lua [[return vim.lsp.diagnostic.get_count(diagnostic_bufnr, "Error", 1)]]) + eq(0, exec_lua [[return count_of_extmarks_for_client(diagnostic_bufnr, 1)]]) + eq(0, exec_lua [[return DisplayCount]]) + + nvim("input", "<esc>") + eq({mode='n', blocking=false}, nvim("get_mode")) + + eq(1, exec_lua [[return vim.lsp.diagnostic.get_count(diagnostic_bufnr, "Error", 1)]]) + eq(2, exec_lua [[return count_of_extmarks_for_client(diagnostic_bufnr, 1)]]) + eq(1, exec_lua [[return DisplayCount]]) + + -- Go in and out of insert mode one more time. + nvim("input", "o") + eq({mode='i', blocking=false}, nvim("get_mode")) + + nvim("input", "<esc>") + eq({mode='n', blocking=false}, nvim("get_mode")) + + -- Should not have set the virtual text again. + eq(1, exec_lua [[return DisplayCount]]) + end) + + it('never sets virtual text, in combination with insert leave', function() + exec_lua [[vim.api.nvim_set_current_buf(diagnostic_bufnr)]] + nvim("input", "o") + eq({mode='i', blocking=false}, nvim("get_mode")) + + -- Save the diagnostics + exec_lua [[ + PublishDiagnostics = vim.lsp.with(vim.lsp.diagnostic.on_publish_diagnostics, { + update_in_insert = false, + virtual_text = false, + }) + + -- Count how many times we call display. + SetVirtualTextOriginal = vim.lsp.diagnostic.set_virtual_text + + DisplayCount = 0 + vim.lsp.diagnostic.set_virtual_text = function(...) + DisplayCount = DisplayCount + 1 + return SetVirtualTextOriginal(...) + end + + PublishDiagnostics(nil, nil, { + uri = fake_uri, + diagnostics = { + make_error('Delayed Diagnostic', 4, 4, 4, 4), + } + }, 1 + ) + ]] + + -- No diagnostics displayed yet. + eq({mode='i', blocking=false}, nvim("get_mode")) + eq(1, exec_lua [[return vim.lsp.diagnostic.get_count(diagnostic_bufnr, "Error", 1)]]) + eq(0, exec_lua [[return count_of_extmarks_for_client(diagnostic_bufnr, 1)]]) + eq(0, exec_lua [[return DisplayCount]]) + + nvim("input", "<esc>") + eq({mode='n', blocking=false}, nvim("get_mode")) + + eq(1, exec_lua [[return vim.lsp.diagnostic.get_count(diagnostic_bufnr, "Error", 1)]]) + eq(1, exec_lua [[return count_of_extmarks_for_client(diagnostic_bufnr, 1)]]) + eq(0, exec_lua [[return DisplayCount]]) + + -- Go in and out of insert mode one more time. + nvim("input", "o") + eq({mode='i', blocking=false}, nvim("get_mode")) + + nvim("input", "<esc>") + eq({mode='n', blocking=false}, nvim("get_mode")) + + -- Should not have set the virtual text still. + eq(0, exec_lua [[return DisplayCount]]) + end) + + it('can perform updates while in insert mode, if desired', function() + exec_lua [[vim.api.nvim_set_current_buf(diagnostic_bufnr)]] + nvim("input", "o") + eq({mode='i', blocking=false}, nvim("get_mode")) + + -- Save the diagnostics + exec_lua [[ + vim.lsp.with(vim.lsp.diagnostic.on_publish_diagnostics, { + update_in_insert = true, + })(nil, nil, { + uri = fake_uri, + diagnostics = { + make_error('Delayed Diagnostic', 4, 4, 4, 4), + } + }, 1 + ) + ]] + + -- Diagnostics are displayed, because the user wanted them that way! + eq({mode='i', blocking=false}, nvim("get_mode")) + eq(1, exec_lua [[return vim.lsp.diagnostic.get_count(diagnostic_bufnr, "Error", 1)]]) + eq(2, exec_lua [[return count_of_extmarks_for_client(diagnostic_bufnr, 1)]]) + + nvim("input", "<esc>") + eq({mode='n', blocking=false}, nvim("get_mode")) + + eq(1, exec_lua [[return vim.lsp.diagnostic.get_count(diagnostic_bufnr, "Error", 1)]]) + eq(2, exec_lua [[return count_of_extmarks_for_client(diagnostic_bufnr, 1)]]) + end) + + it('allows configuring the virtual text via vim.lsp.with', function() + local expected_spacing = 10 + local extmarks = exec_lua([[ + PublishDiagnostics = vim.lsp.with(vim.lsp.diagnostic.on_publish_diagnostics, { + virtual_text = { + spacing = ..., + }, + }) + + PublishDiagnostics(nil, nil, { + uri = fake_uri, + diagnostics = { + make_error('Delayed Diagnostic', 4, 4, 4, 4), + } + }, 1 + ) + + return vim.api.nvim_buf_get_extmarks( + diagnostic_bufnr, + vim.lsp.diagnostic._get_diagnostic_namespace(1), + 0, + -1, + { details = true } + ) + ]], expected_spacing) + + local virt_text = extmarks[1][4].virt_text + local spacing = virt_text[1][1] + + eq(expected_spacing, #spacing) + end) + + + it('allows configuring the virtual text via vim.lsp.with using a function', function() + local expected_spacing = 10 + local extmarks = exec_lua([[ + spacing = ... + + PublishDiagnostics = vim.lsp.with(vim.lsp.diagnostic.on_publish_diagnostics, { + virtual_text = function() + return { + spacing = spacing, + } + end, + }) + + PublishDiagnostics(nil, nil, { + uri = fake_uri, + diagnostics = { + make_error('Delayed Diagnostic', 4, 4, 4, 4), + } + }, 1 + ) + + return vim.api.nvim_buf_get_extmarks( + diagnostic_bufnr, + vim.lsp.diagnostic._get_diagnostic_namespace(1), + 0, + -1, + { details = true } + ) + ]], expected_spacing) + + local virt_text = extmarks[1][4].virt_text + local spacing = virt_text[1][1] + + eq(expected_spacing, #spacing) + end) + end) + + describe('lsp.util.show_line_diagnostics', function() + it('creates floating window and returns popup bufnr and winnr if current line contains diagnostics', function() + -- Two lines: + -- Diagnostic: + -- 1. <msg> + eq(2, exec_lua [[ + local buffer = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_lines(buffer, 0, -1, false, { + "testing"; + "123"; + }) + local diagnostics = { + { + range = { + start = { line = 0; character = 1; }; + ["end"] = { line = 0; character = 3; }; + }; + severity = vim.lsp.protocol.DiagnosticSeverity.Error; + message = "Syntax error"; + }, + } + vim.api.nvim_win_set_buf(0, buffer) + vim.lsp.diagnostic.save(diagnostics, buffer, 1) + local popup_bufnr, winnr = vim.lsp.diagnostic.show_line_diagnostics() + return #vim.api.nvim_buf_get_lines(popup_bufnr, 0, -1, false) + ]]) + end) + + it('creates floating window and returns popup bufnr and winnr without header, if requested', function() + -- One line (since no header): + -- 1. <msg> + eq(1, exec_lua [[ + local buffer = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_lines(buffer, 0, -1, false, { + "testing"; + "123"; + }) + local diagnostics = { + { + range = { + start = { line = 0; character = 1; }; + ["end"] = { line = 0; character = 3; }; + }; + severity = vim.lsp.protocol.DiagnosticSeverity.Error; + message = "Syntax error"; + }, + } + vim.api.nvim_win_set_buf(0, buffer) + vim.lsp.diagnostic.save(diagnostics, buffer, 1) + local popup_bufnr, winnr = vim.lsp.diagnostic.show_line_diagnostics { show_header = false } + return #vim.api.nvim_buf_get_lines(popup_bufnr, 0, -1, false) + ]]) + end) + end) + + describe('set_signs', function() + -- TODO(tjdevries): Find out why signs are not displayed when set from Lua...?? + pending('sets signs by default', function() + exec_lua [[ + PublishDiagnostics = vim.lsp.with(vim.lsp.diagnostic.on_publish_diagnostics, { + update_in_insert = true, + signs = true, + }) + + local diagnostics = { + make_error('Delayed Diagnostic', 1, 1, 1, 2), + make_error('Delayed Diagnostic', 3, 3, 3, 3), + } + + vim.api.nvim_win_set_buf(0, diagnostic_bufnr) + vim.lsp.diagnostic.on_publish_diagnostics(nil, nil, { + uri = fake_uri, + diagnostics = diagnostics + }, 1 + ) + + vim.lsp.diagnostic.set_signs(diagnostics, diagnostic_bufnr, 1) + -- return vim.fn.sign_getplaced() + ]] + + nvim("input", "o") + nvim("input", "<esc>") + + -- TODO(tjdevries): Find a way to get the signs to display in the test... + eq(nil, exec_lua [[ + return im.fn.sign_getplaced()[1].signs + ]]) + end) + end) + + describe('set_loclist()', function() + it('sets diagnostics in lnum order', function() + local loc_list = exec_lua [[ + vim.api.nvim_win_set_buf(0, diagnostic_bufnr) + + vim.lsp.diagnostic.on_publish_diagnostics(nil, nil, { + uri = fake_uri, + diagnostics = { + make_error('Farther Diagnostic', 4, 4, 4, 4), + make_error('Lower Diagnostic', 1, 1, 1, 1), + } + }, 1 + ) + + vim.lsp.diagnostic.set_loclist() + + return vim.fn.getloclist(0) + ]] + + assert(loc_list[1].lnum < loc_list[2].lnum) + end) + + it('sets diagnostics in lnum order, regardless of client', function() + local loc_list = exec_lua [[ + vim.api.nvim_win_set_buf(0, diagnostic_bufnr) + + vim.lsp.diagnostic.on_publish_diagnostics(nil, nil, { + uri = fake_uri, + diagnostics = { + make_error('Lower Diagnostic', 1, 1, 1, 1), + } + }, 1 + ) + + vim.lsp.diagnostic.on_publish_diagnostics(nil, nil, { + uri = fake_uri, + diagnostics = { + make_warning('Farther Diagnostic', 4, 4, 4, 4), + } + }, 2 + ) + + vim.lsp.diagnostic.set_loclist() + + return vim.fn.getloclist(0) + ]] + + assert(loc_list[1].lnum < loc_list[2].lnum) + end) + end) +end) diff --git a/test/functional/plugin/lsp/handler_spec.lua b/test/functional/plugin/lsp/handler_spec.lua new file mode 100644 index 0000000000..3086c23fe8 --- /dev/null +++ b/test/functional/plugin/lsp/handler_spec.lua @@ -0,0 +1,29 @@ +local helpers = require('test.functional.helpers')(after_each) + +local eq = helpers.eq +local exec_lua = helpers.exec_lua +local pcall_err = helpers.pcall_err +local matches = helpers.matches + +describe('lsp-handlers', function() + describe('vim.lsp._with_extend', function() + it('should return a table with the default keys', function() + eq({hello = 'world' }, exec_lua [[ + return vim.lsp._with_extend('test', { hello = 'world' }) + ]]) + end) + + it('should override with config keys', function() + eq({hello = 'universe', other = true}, exec_lua [[ + return vim.lsp._with_extend('test', { other = true, hello = 'world' }, { hello = 'universe' }) + ]]) + end) + + it('should not allow invalid keys', function() + matches( + '.*Invalid option for `test`.*', + pcall_err(exec_lua, "return vim.lsp._with_extend('test', { hello = 'world' }, { invalid = true })") + ) + end) + end) +end) diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index aaa28390ea..5b048f57e9 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -270,6 +270,76 @@ describe('LSP', function() test_name = "basic_check_capabilities"; on_init = function(client) client.stop() + local full_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Full") + eq(full_kind, client.resolved_capabilities().text_document_did_change) + end; + on_exit = function(code, signal) + eq(0, code, "exit code", fake_lsp_logfile) + eq(0, signal, "exit signal", fake_lsp_logfile) + end; + on_callback = function(...) + eq(table.remove(expected_callbacks), {...}, "expected callback") + end; + } + end) + + it('client.supports_methods() should validate capabilities', function() + local expected_callbacks = { + {NIL, "shutdown", {}, 1}; + } + test_rpc_server { + test_name = "capabilities_for_client_supports_method"; + on_init = function(client) + client.stop() + local full_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Full") + eq(full_kind, client.resolved_capabilities().text_document_did_change) + eq(true, client.resolved_capabilities().completion) + eq(true, client.resolved_capabilities().hover) + eq(false, client.resolved_capabilities().goto_definition) + eq(false, client.resolved_capabilities().rename) + + -- known methods for resolved capabilities + eq(true, client.supports_method("textDocument/hover")) + eq(false, client.supports_method("textDocument/definition")) + + -- unknown methods are assumed to be supported. + eq(true, client.supports_method("unknown-method")) + end; + on_exit = function(code, signal) + eq(0, code, "exit code", fake_lsp_logfile) + eq(0, signal, "exit signal", fake_lsp_logfile) + end; + on_callback = function(...) + eq(table.remove(expected_callbacks), {...}, "expected callback") + end; + } + end) + + it('should call unsupported_method when trying to call an unsupported method', function() + local expected_callbacks = { + {NIL, "shutdown", {}, 1}; + } + test_rpc_server { + test_name = "capabilities_for_client_supports_method"; + on_setup = function() + exec_lua([=[ + vim.lsp.handlers['textDocument/hover'] = function(err, method) + vim.lsp._last_lsp_callback = { err = err; method = method } + end + vim.lsp._unsupported_method = function(method) + vim.lsp._last_unsupported_method = method + return 'fake-error' + end + vim.lsp.buf.hover() + ]=]) + end; + on_init = function(client) + client.stop() + local method = exec_lua("return vim.lsp._last_unsupported_method") + eq("textDocument/hover", method) + local lsp_cb_call = exec_lua("return vim.lsp._last_lsp_callback") + eq("fake-error", lsp_cb_call.err) + eq("textDocument/hover", lsp_cb_call.method) end; on_exit = function(code, signal) eq(0, code, "exit code", fake_lsp_logfile) @@ -747,8 +817,16 @@ describe('LSP', function() end) it('should invalid cmd argument', function() - eq('Error executing lua: .../shared.lua: cmd: expected list, got nvim', pcall_err(_cmd_parts, "nvim")) - eq('Error executing lua: .../shared.lua: cmd argument: expected string, got number', pcall_err(_cmd_parts, {"nvim", 1})) + eq(dedent([[ + Error executing lua: .../lsp.lua:0: cmd: expected list, got nvim + stack traceback: + .../lsp.lua:0: in function <.../lsp.lua:0>]]), + pcall_err(_cmd_parts, 'nvim')) + eq(dedent([[ + Error executing lua: .../lsp.lua:0: cmd argument: expected string, got number + stack traceback: + .../lsp.lua:0: in function <.../lsp.lua:0>]]), + pcall_err(_cmd_parts, {'nvim', 1})) end) end) end) @@ -769,25 +847,28 @@ describe('LSP', function() end it('highlight groups', function() - eq({'LspDiagnosticsError', - 'LspDiagnosticsErrorFloating', - 'LspDiagnosticsErrorSign', - 'LspDiagnosticsHint', - 'LspDiagnosticsHintFloating', - 'LspDiagnosticsHintSign', - 'LspDiagnosticsInformation', - 'LspDiagnosticsInformationFloating', - 'LspDiagnosticsInformationSign', - 'LspDiagnosticsUnderline', - 'LspDiagnosticsUnderlineError', - 'LspDiagnosticsUnderlineHint', - 'LspDiagnosticsUnderlineInformation', - 'LspDiagnosticsUnderlineWarning', - 'LspDiagnosticsWarning', - 'LspDiagnosticsWarningFloating', - 'LspDiagnosticsWarningSign', - }, - exec_lua([[require'vim.lsp'; return vim.fn.getcompletion('Lsp', 'highlight')]])) + eq({ + 'LspDiagnosticsDefaultError', + 'LspDiagnosticsDefaultHint', + 'LspDiagnosticsDefaultInformation', + 'LspDiagnosticsDefaultWarning', + 'LspDiagnosticsFloatingError', + 'LspDiagnosticsFloatingHint', + 'LspDiagnosticsFloatingInformation', + 'LspDiagnosticsFloatingWarning', + 'LspDiagnosticsSignError', + 'LspDiagnosticsSignHint', + 'LspDiagnosticsSignInformation', + 'LspDiagnosticsSignWarning', + 'LspDiagnosticsUnderlineError', + 'LspDiagnosticsUnderlineHint', + 'LspDiagnosticsUnderlineInformation', + 'LspDiagnosticsUnderlineWarning', + 'LspDiagnosticsVirtualTextError', + 'LspDiagnosticsVirtualTextHint', + 'LspDiagnosticsVirtualTextInformation', + 'LspDiagnosticsVirtualTextWarning', + }, exec_lua([[require'vim.lsp'; return vim.fn.getcompletion('Lsp', 'highlight')]])) end) describe('apply_text_edits', function() @@ -815,33 +896,10 @@ describe('LSP', function() 'aฬ รฅ ษง ๆฑ่ฏญ โฅ ๐คฆ ๐ฆ'; }, buf_lines(1)) end) - it('handles edits with the same start position, applying changes in the order in the array', function() - local edits = { - make_edit(0, 6, 0, 10, {""}); - make_edit(0, 6, 0, 6, {"REPLACE"}); - make_edit(1, 0, 1, 3, {""}); - make_edit(1, 0, 1, 0, {"123"}); - make_edit(2, 16, 2, 18, {""}); - make_edit(2, 16, 2, 16, {"XYZ"}); - make_edit(3, 7, 3, 11, {"this"}); - make_edit(3, 7, 3, 11, {"will"}); - make_edit(3, 7, 3, 11, {"not "}); - make_edit(3, 7, 3, 11, {"show"}); - make_edit(3, 7, 3, 11, {"(but this will)"}); - } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1) - eq({ - 'First REPLACE of text'; - '123ond line of text'; - 'Third line of teXYZ'; - 'Fourth (but this will) of text'; - 'aฬ รฅ ษง ๆฑ่ฏญ โฅ ๐คฆ ๐ฆ'; - }, buf_lines(1)) - end) it('applies complex edits', function() local edits = { - make_edit(0, 0, 0, 0, {"3", "foo"}); make_edit(0, 0, 0, 0, {"", "12"}); + make_edit(0, 0, 0, 0, {"3", "foo"}); make_edit(0, 1, 0, 1, {"bar", "123"}); make_edit(0, #"First ", 0, #"First line of text", {"guy"}); make_edit(1, 0, 1, #'Second', {"baz"}); @@ -982,7 +1040,7 @@ describe('LSP', function() label = nil; edit = {}; } - return vim.lsp.callbacks['workspace/applyEdit'](nil, nil, apply_edit) + return vim.lsp.handlers['workspace/applyEdit'](nil, nil, apply_edit) ]]) end) end) @@ -994,34 +1052,34 @@ describe('LSP', function() local prefix = 'foo' local completion_list = { -- resolves into label - { label='foobar' }, - { label='foobar', textEdit={} }, + { label='foobar', sortText="a" }, + { label='foobar', sortText="b", textEdit={} }, -- resolves into insertText - { label='foocar', insertText='foobar' }, - { label='foocar', insertText='foobar', textEdit={} }, + { label='foocar', sortText="c", insertText='foobar' }, + { label='foocar', sortText="d", insertText='foobar', textEdit={} }, -- resolves into textEdit.newText - { label='foocar', insertText='foodar', textEdit={newText='foobar'} }, - { label='foocar', textEdit={newText='foobar'} }, + { label='foocar', sortText="e", insertText='foodar', textEdit={newText='foobar'} }, + { label='foocar', sortText="f", textEdit={newText='foobar'} }, -- real-world snippet text - { label='foocar', insertText='foodar', textEdit={newText='foobar(${1:place holder}, ${2:more ...holder{\\}})'} }, - { label='foocar', insertText='foodar(${1:var1} typ1, ${2:var2} *typ2) {$0\\}', textEdit={} }, + { label='foocar', sortText="g", insertText='foodar', textEdit={newText='foobar(${1:place holder}, ${2:more ...holder{\\}})'} }, + { label='foocar', sortText="h", insertText='foodar(${1:var1} typ1, ${2:var2} *typ2) {$0\\}', textEdit={} }, -- nested snippet tokens - { label='foocar', insertText='foodar(${1:var1 ${2|typ2,typ3|} ${3:tail}}) {$0\\}', textEdit={} }, + { label='foocar', sortText="i", insertText='foodar(${1:var1 ${2|typ2,typ3|} ${3:tail}}) {$0\\}', textEdit={} }, -- plain text - { label='foocar', insertText='foodar(${1:var1})', insertTextFormat=1, textEdit={} }, + { label='foocar', sortText="j", insertText='foodar(${1:var1})', insertTextFormat=1, textEdit={} }, } local completion_list_items = {items=completion_list} local expected = { - { abbr = 'foobar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label = 'foobar' } } } } }, - { abbr = 'foobar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foobar', textEdit={} } } } } }, - { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foocar', insertText='foobar' } } } } }, - { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foocar', insertText='foobar', textEdit={} } } } } }, - { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foocar', insertText='foodar', textEdit={newText='foobar'} } } } } }, - { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foocar', textEdit={newText='foobar'} } } } } }, - { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar(place holder, more ...holder{})', user_data = { nvim = { lsp = { completion_item = { label='foocar', insertText='foodar', textEdit={newText='foobar(${1:place holder}, ${2:more ...holder{\\}})'} } } } } }, - { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foodar(var1 typ1, var2 *typ2) {}', user_data = { nvim = { lsp = { completion_item = { label='foocar', insertText='foodar(${1:var1} typ1, ${2:var2} *typ2) {$0\\}', textEdit={} } } } } }, - { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foodar(var1 typ2,typ3 tail) {}', user_data = { nvim = { lsp = { completion_item = { label='foocar', insertText='foodar(${1:var1 ${2|typ2,typ3|} ${3:tail}}) {$0\\}', textEdit={} } } } } }, - { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foodar(${1:var1})', user_data = { nvim = { lsp = { completion_item = { label='foocar', insertText='foodar(${1:var1})', insertTextFormat=1, textEdit={} } } } } }, + { abbr = 'foobar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label = 'foobar', sortText="a" } } } } }, + { abbr = 'foobar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foobar', sortText="b", textEdit={} } } } } }, + { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="c", insertText='foobar' } } } } }, + { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="d", insertText='foobar', textEdit={} } } } } }, + { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="e", insertText='foodar', textEdit={newText='foobar'} } } } } }, + { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="f", textEdit={newText='foobar'} } } } } }, + { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar(place holder, more ...holder{})', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="g", insertText='foodar', textEdit={newText='foobar(${1:place holder}, ${2:more ...holder{\\}})'} } } } } }, + { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foodar(var1 typ1, var2 *typ2) {}', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="h", insertText='foodar(${1:var1} typ1, ${2:var2} *typ2) {$0\\}', textEdit={} } } } } }, + { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foodar(var1 typ2,typ3 tail) {}', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="i", insertText='foodar(${1:var1 ${2|typ2,typ3|} ${3:tail}}) {$0\\}', textEdit={} } } } } }, + { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foodar(${1:var1})', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="j", insertText='foodar(${1:var1})', insertTextFormat=1, textEdit={} } } } } }, } eq(expected, exec_lua([[return vim.lsp.util.text_document_completion_list_to_complete_items(...)]], completion_list, prefix)) @@ -1029,47 +1087,7 @@ describe('LSP', function() eq({}, exec_lua([[return vim.lsp.util.text_document_completion_list_to_complete_items(...)]], {}, prefix)) end) end) - describe('buf_diagnostics_save_positions', function() - it('stores the diagnostics in diagnostics_by_buf', function () - local diagnostics = { - { range = {}; message = "diag1" }, - { range = {}; message = "diag2" }, - } - exec_lua([[ - vim.lsp.util.buf_diagnostics_save_positions(...)]], 0, diagnostics) - eq(1, exec_lua [[ return #vim.lsp.util.diagnostics_by_buf ]]) - eq(diagnostics, exec_lua [[ - for _, diagnostics in pairs(vim.lsp.util.diagnostics_by_buf) do - return diagnostics - end - ]]) - end) - end) - describe('lsp.util.show_line_diagnostics', function() - it('creates floating window and returns popup bufnr and winnr if current line contains diagnostics', function() - eq(3, exec_lua [[ - local buffer = vim.api.nvim_create_buf(false, true) - vim.api.nvim_buf_set_lines(buffer, 0, -1, false, { - "testing"; - "123"; - }) - local diagnostics = { - { - range = { - start = { line = 0; character = 1; }; - ["end"] = { line = 0; character = 3; }; - }; - severity = vim.lsp.protocol.DiagnosticSeverity.Error; - message = "Syntax error"; - }, - } - vim.api.nvim_win_set_buf(0, buffer) - vim.lsp.util.buf_diagnostics_save_positions(vim.fn.bufnr(buffer), diagnostics) - local popup_bufnr, winnr = vim.lsp.util.show_line_diagnostics() - return popup_bufnr - ]]) - end) - end) + describe('lsp.util.locations_to_items', function() it('Convert Location[] to items', function() local expected = { @@ -1501,7 +1519,7 @@ describe('LSP', function() describe('vim.lsp.buf.outgoing_calls', function() it('does nothing for an empty response', function() local qflist_count = exec_lua([=[ - require'vim.lsp.callbacks'['callHierarchy/outgoingCalls']() + require'vim.lsp.handlers'['callHierarchy/outgoingCalls']() return #vim.fn.getqflist() ]=]) eq(0, qflist_count) @@ -1547,7 +1565,7 @@ describe('LSP', function() uri = "file:///src/main.rs" } } } - local callback = require'vim.lsp.callbacks'['callHierarchy/outgoingCalls'] + local callback = require'vim.lsp.handlers'['callHierarchy/outgoingCalls'] callback(nil, nil, rust_analyzer_response) return vim.fn.getqflist() ]=]) @@ -1572,7 +1590,7 @@ describe('LSP', function() describe('vim.lsp.buf.incoming_calls', function() it('does nothing for an empty response', function() local qflist_count = exec_lua([=[ - require'vim.lsp.callbacks'['callHierarchy/incomingCalls']() + require'vim.lsp.handlers'['callHierarchy/incomingCalls']() return #vim.fn.getqflist() ]=]) eq(0, qflist_count) @@ -1619,7 +1637,7 @@ describe('LSP', function() } } } } - local callback = require'vim.lsp.callbacks'['callHierarchy/incomingCalls'] + local callback = require'vim.lsp.handlers'['callHierarchy/incomingCalls'] callback(nil, nil, rust_analyzer_response) return vim.fn.getqflist() ]=]) diff --git a/test/functional/provider/clipboard_spec.lua b/test/functional/provider/clipboard_spec.lua index da9dd09129..1431054494 100644 --- a/test/functional/provider/clipboard_spec.lua +++ b/test/functional/provider/clipboard_spec.lua @@ -153,6 +153,16 @@ describe('clipboard', function() eq('', eval('provider#clipboard#Error()')) end) + it('g:clipboard using lists', function() + source([[let g:clipboard = { + \ 'name': 'custom', + \ 'copy': { '+': ['any', 'command'], '*': ['some', 'other'] }, + \ 'paste': { '+': ['any', 'command'], '*': ['some', 'other'] }, + \}]]) + eq('custom', eval('provider#clipboard#Executable()')) + eq('', eval('provider#clipboard#Error()')) + end) + it('g:clipboard using VimL functions', function() -- Implements a fake clipboard provider. cache_enabled is meaningless here. source([[let g:clipboard = { diff --git a/test/functional/provider/perl_spec.lua b/test/functional/provider/perl_spec.lua index 7b446e4ab3..125674660b 100644 --- a/test/functional/provider/perl_spec.lua +++ b/test/functional/provider/perl_spec.lua @@ -5,6 +5,10 @@ local command = helpers.command local write_file = helpers.write_file local eval = helpers.eval local retry = helpers.retry +local curbufmeths = helpers.curbufmeths +local insert = helpers.insert +local expect = helpers.expect +local feed = helpers.feed do clear() @@ -19,7 +23,51 @@ before_each(function() clear() end) -describe('perl host', function() +describe('legacy perl provider', function() + if helpers.pending_win32(pending) then return end + + it('feature test', function() + eq(1, eval('has("perl")')) + end) + + it(':perl command', function() + command('perl $vim->vars->{set_by_perl} = [100, 0];') + eq({100, 0}, eval('g:set_by_perl')) + end) + + it(':perlfile command', function() + local fname = 'perlfile.pl' + write_file(fname, '$vim->command("let set_by_perlfile = 123")') + command('perlfile perlfile.pl') + eq(123, eval('g:set_by_perlfile')) + os.remove(fname) + end) + + it(':perldo command', function() + -- :perldo 1; doesn't change $_, + -- the buffer should not be changed + command('normal :perldo 1;') + eq(false, curbufmeths.get_option('modified')) + -- insert some text + insert('abc\ndef\nghi') + expect([[ + abc + def + ghi]]) + -- go to top and select and replace the first two lines + feed('ggvj:perldo $_ = reverse ($_)."$linenr"<CR>') + expect([[ + cba1 + fed2 + ghi]]) + end) + + it('perleval()', function() + eq({1, 2, {['key'] = 'val'}}, eval([[perleval('[1, 2, {"key" => "val"}]')]])) + end) +end) + +describe('perl provider', function() if helpers.pending_win32(pending) then return end teardown(function () os.remove('Xtest-perl-hello.pl') diff --git a/test/functional/provider/ruby_spec.lua b/test/functional/provider/ruby_spec.lua index bb7d23ede6..2729d8dfa2 100644 --- a/test/functional/provider/ruby_spec.lua +++ b/test/functional/provider/ruby_spec.lua @@ -5,6 +5,7 @@ local command = helpers.command local curbufmeths = helpers.curbufmeths local eq = helpers.eq local eval = helpers.eval +local exc_exec = helpers.exc_exec local expect = helpers.expect local feed = helpers.feed local feed_command = helpers.feed_command @@ -109,3 +110,24 @@ describe('ruby provider', function() eq(2, eval('1+1')) -- Still alive? end) end) + +describe('rubyeval()', function() + it('evaluates ruby objects', function() + eq({1, 2, {['key'] = 'val'}}, funcs.rubyeval('[1, 2, {key: "val"}]')) + end) + + it('returns nil for empty strings', function() + eq(helpers.NIL, funcs.rubyeval('')) + end) + + it('errors out when given non-string', function() + eq('Vim(call):E474: Invalid argument', exc_exec('call rubyeval(10)')) + eq('Vim(call):E474: Invalid argument', exc_exec('call rubyeval(v:_null_dict)')) + eq('Vim(call):E474: Invalid argument', exc_exec('call rubyeval(v:_null_list)')) + eq('Vim(call):E474: Invalid argument', exc_exec('call rubyeval(0.0)')) + eq('Vim(call):E474: Invalid argument', exc_exec('call rubyeval(function("tr"))')) + eq('Vim(call):E474: Invalid argument', exc_exec('call rubyeval(v:true)')) + eq('Vim(call):E474: Invalid argument', exc_exec('call rubyeval(v:false)')) + eq('Vim(call):E474: Invalid argument', exc_exec('call rubyeval(v:null)')) + end) +end) diff --git a/test/functional/shada/history_spec.lua b/test/functional/shada/history_spec.lua index 78b5c77857..9291f5e100 100644 --- a/test/functional/shada/history_spec.lua +++ b/test/functional/shada/history_spec.lua @@ -2,6 +2,7 @@ local helpers = require('test.functional.helpers')(after_each) local nvim_command, funcs, meths, nvim_feed, eq = helpers.command, helpers.funcs, helpers.meths, helpers.feed, helpers.eq +local eval = helpers.eval local shada_helpers = require('test.functional.shada.helpers') local reset, clear = shada_helpers.reset, shada_helpers.clear @@ -237,4 +238,13 @@ describe('ShaDa support code', function() nvim_command('wshada') end) + it('does not crash when number of history save to zero (#11497)', function() + nvim_command('set shada=\'10') + nvim_feed(':" Test\n') + nvim_command('wshada') + nvim_command('set shada=\'10,:0') + nvim_command('wshada') + eq(2, eval('1+1')) -- check nvim still running + end) + end) diff --git a/test/functional/terminal/buffer_spec.lua b/test/functional/terminal/buffer_spec.lua index 6372cd935e..8e171d31aa 100644 --- a/test/functional/terminal/buffer_spec.lua +++ b/test/functional/terminal/buffer_spec.lua @@ -1,7 +1,7 @@ local helpers = require('test.functional.helpers')(after_each) local thelpers = require('test.functional.terminal.helpers') local feed, clear, nvim = helpers.feed, helpers.clear, helpers.nvim -local wait = helpers.wait +local poke_eventloop = helpers.poke_eventloop local eval, feed_command, source = helpers.eval, helpers.feed_command, helpers.source local eq, neq = helpers.eq, helpers.neq local write_file = helpers.write_file @@ -13,7 +13,7 @@ describe(':terminal buffer', function() before_each(function() clear() feed_command('set modifiable swapfile undolevels=20') - wait() + poke_eventloop() screen = thelpers.screen_setup() end) diff --git a/test/functional/terminal/cursor_spec.lua b/test/functional/terminal/cursor_spec.lua index ef12438ecc..8d70ebf679 100644 --- a/test/functional/terminal/cursor_spec.lua +++ b/test/functional/terminal/cursor_spec.lua @@ -70,7 +70,7 @@ describe(':terminal cursor', function() :set number | ]]) feed('i') - helpers.wait() + helpers.poke_eventloop() screen:expect([[ {7: 1 }tty ready | {7: 2 }rows: 6, cols: 46 | diff --git a/test/functional/terminal/ex_terminal_spec.lua b/test/functional/terminal/ex_terminal_spec.lua index 138befd978..4b512605e1 100644 --- a/test/functional/terminal/ex_terminal_spec.lua +++ b/test/functional/terminal/ex_terminal_spec.lua @@ -1,6 +1,6 @@ local helpers = require('test.functional.helpers')(after_each) local Screen = require('test.functional.ui.screen') -local clear, wait, nvim = helpers.clear, helpers.wait, helpers.nvim +local clear, poke_eventloop, nvim = helpers.clear, helpers.poke_eventloop, helpers.nvim local nvim_dir, source, eq = helpers.nvim_dir, helpers.source, helpers.eq local feed = helpers.feed local feed_command, eval = helpers.feed_command, helpers.eval @@ -29,7 +29,7 @@ describe(':terminal', function() -- Invoke a command that emits frequent terminal activity. feed([[:terminal "]]..nvim_dir..[[/shell-test" REP 9999 !terminal_output!<cr>]]) feed([[<C-\><C-N>]]) - wait() + poke_eventloop() -- Wait for some terminal activity. retry(nil, 4000, function() ok(funcs.line('$') > 6) @@ -60,7 +60,7 @@ describe(':terminal', function() feed_command([[terminal while true; do echo foo; sleep .1; done]]) end feed([[<C-\><C-N>M]]) -- move cursor away from last line - wait() + poke_eventloop() eq(3, eval("line('$')")) -- window height eq(2, eval("line('.')")) -- cursor is in the middle feed_command('vsplit') @@ -76,11 +76,11 @@ describe(':terminal', function() -- Create a new line (in the shell). For a normal buffer this -- increments the jumplist; for a terminal-buffer it should not. #3723 feed('i') - wait() + poke_eventloop() feed('<CR><CR><CR><CR>') - wait() + poke_eventloop() feed([[<C-\><C-N>]]) - wait() + poke_eventloop() -- Wait for >=1 lines to be created. retry(nil, 4000, function() ok(funcs.line('$') > lines_before) @@ -210,7 +210,7 @@ describe(':terminal (with fake shell)', function() it('ignores writes if the backing stream closes', function() terminal_with_fake_shell() feed('iiXXXXXXX') - wait() + poke_eventloop() -- Race: Though the shell exited (and streams were closed by SIGCHLD -- handler), :terminal cleanup is pending on the main-loop. -- This write should be ignored (not crash, #5445). diff --git a/test/functional/terminal/scrollback_spec.lua b/test/functional/terminal/scrollback_spec.lua index 1df8df6f6e..77fdba7fc4 100644 --- a/test/functional/terminal/scrollback_spec.lua +++ b/test/functional/terminal/scrollback_spec.lua @@ -6,7 +6,7 @@ local feed, nvim_dir, feed_command = helpers.feed, helpers.nvim_dir, helpers.fee local iswin = helpers.iswin local eval = helpers.eval local command = helpers.command -local wait = helpers.wait +local poke_eventloop = helpers.poke_eventloop local retry = helpers.retry local curbufmeths = helpers.curbufmeths local nvim = helpers.nvim @@ -347,7 +347,7 @@ describe(':terminal prints more lines than the screen height and exits', functio local screen = Screen.new(30, 7) screen:attach({rgb=false}) feed_command('call termopen(["'..nvim_dir..'/tty-test", "10"]) | startinsert') - wait() + poke_eventloop() screen:expect([[ line6 | line7 | @@ -423,7 +423,7 @@ describe("'scrollback' option", function() retry(nil, nil, function() expect_lines(33, 2) end) curbufmeths.set_option('scrollback', 10) - wait() + poke_eventloop() retry(nil, nil, function() expect_lines(16) end) curbufmeths.set_option('scrollback', 10000) retry(nil, nil, function() expect_lines(16) end) diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index 5d82037f42..5948ab3a64 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -348,6 +348,10 @@ describe('TUI', function() end) it('paste: terminal mode', function() + if os.getenv('GITHUB_ACTIONS') ~= nil then + pending("tty-test complains about not owning the terminal -- actions/runner#241") + return + end feed_data(':set statusline=^^^^^^^\n') feed_data(':terminal '..nvim_dir..'/tty-test\n') feed_data('i') @@ -605,6 +609,8 @@ describe('TUI', function() wait_for_mode('i') -- "bracketed paste" feed_data('\027[200~'..expected..'\027[201~') + -- FIXME: Data race between the two feeds + if uname() == 'freebsd' then screen:sleep(1) end feed_data(' end') expected = expected..' end' screen:expect([[ @@ -774,6 +780,10 @@ describe('TUI', function() end) it('forwards :term palette colors with termguicolors', function() + if os.getenv('GITHUB_ACTIONS') ~= nil then + pending("tty-test complains about not owning the terminal -- actions/runner#241") + return + end screen:set_rgb_cterm(true) screen:set_default_attr_ids({ [1] = {{reverse = true}, {reverse = true}}, diff --git a/test/functional/terminal/window_spec.lua b/test/functional/terminal/window_spec.lua index f1c828d17e..9f278fd157 100644 --- a/test/functional/terminal/window_spec.lua +++ b/test/functional/terminal/window_spec.lua @@ -2,7 +2,7 @@ local helpers = require('test.functional.helpers')(after_each) local thelpers = require('test.functional.terminal.helpers') local feed_data = thelpers.feed_data local feed, clear = helpers.feed, helpers.clear -local wait = helpers.wait +local poke_eventloop = helpers.poke_eventloop local iswin = helpers.iswin local command = helpers.command local retry = helpers.retry @@ -127,7 +127,7 @@ describe(':terminal window', function() it('wont show any folds', function() feed([[<C-\><C-N>ggvGzf]]) - wait() + poke_eventloop() screen:expect([[ ^tty ready | line1 | diff --git a/test/functional/ui/bufhl_spec.lua b/test/functional/ui/bufhl_spec.lua index 3cb592c714..d7269d2c29 100644 --- a/test/functional/ui/bufhl_spec.lua +++ b/test/functional/ui/bufhl_spec.lua @@ -690,7 +690,7 @@ describe('Buffer highlighting', function() end) it('can be retrieved', function() - local get_virtual_text = curbufmeths.get_virtual_text + local get_extmarks = curbufmeths.get_extmarks local line_count = curbufmeths.line_count local s1 = {{'Kรถttbullar', 'Comment'}, {'Krรคuterbutter'}} @@ -699,12 +699,14 @@ describe('Buffer highlighting', function() -- TODO: only a virtual text from the same ns curretly overrides -- an existing virtual text. We might add a prioritation system. set_virtual_text(id1, 0, s1, {}) - eq(s1, get_virtual_text(0)) + eq({{1, 0, 0, {virt_text = s1}}}, get_extmarks(id1, {0,0}, {0, -1}, {details=true})) - set_virtual_text(-1, line_count(), s2, {}) - eq(s2, get_virtual_text(line_count())) + -- TODO: is this really valid? shouldn't the max be line_count()-1? + local lastline = line_count() + set_virtual_text(id1, line_count(), s2, {}) + eq({{3, lastline, 0, {virt_text = s2}}}, get_extmarks(id1, {lastline,0}, {lastline, -1}, {details=true})) - eq({}, get_virtual_text(line_count() + 9000)) + eq({}, get_extmarks(id1, {lastline+9000,0}, {lastline+9000, -1}, {})) end) it('is not highlighted by visual selection', function() diff --git a/test/functional/ui/cursor_spec.lua b/test/functional/ui/cursor_spec.lua index 6c913124ac..e1a72ced05 100644 --- a/test/functional/ui/cursor_spec.lua +++ b/test/functional/ui/cursor_spec.lua @@ -286,6 +286,21 @@ describe('ui/cursor', function() eq(173, named.normal.blinkon) eq(42, named.showmatch.cell_percentage) end) + + -- If there is no setting for guicursor, it becomes the default setting. + meths.set_option('guicursor', 'n:ver35-blinkwait171-blinkoff172-blinkon173-Cursor/lCursor') + screen:expect(function() + for _,m in ipairs(screen._mode_info) do + if m.name ~= 'normal' then + eq('block', m.cursor_shape or 'block') + eq(0, m.blinkon or 0) + eq(0, m.blinkoff or 0) + eq(0, m.blinkwait or 0) + eq(0, m.hl_id or 0) + eq(0, m.id_lm or 0) + end + end + end) end) it("empty 'guicursor' sets cursor_shape=block in all modes", function() @@ -297,6 +312,8 @@ describe('ui/cursor', function() if m['cursor_shape'] ~= nil then eq('block', m.cursor_shape) eq(0, m.blinkon) + eq(0, m.hl_id) + eq(0, m.id_lm) end end end) diff --git a/test/functional/ui/decorations_spec.lua b/test/functional/ui/decorations_spec.lua new file mode 100644 index 0000000000..4182090732 --- /dev/null +++ b/test/functional/ui/decorations_spec.lua @@ -0,0 +1,230 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') + +local clear = helpers.clear +local feed = helpers.feed +local insert = helpers.insert +local exec_lua = helpers.exec_lua +local exec = helpers.exec +local expect_events = helpers.expect_events +local meths = helpers.meths + +describe('decorations providers', function() + local screen + before_each(function() + clear() + screen = Screen.new(40, 8) + screen:attach() + screen:set_default_attr_ids { + [1] = {bold=true, foreground=Screen.colors.Blue}; + [2] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red}; + [3] = {foreground = Screen.colors.Brown}; + [4] = {foreground = Screen.colors.Blue1}; + [5] = {foreground = Screen.colors.Magenta}; + [6] = {bold = true, foreground = Screen.colors.Brown}; + [7] = {background = Screen.colors.Gray90}; + [8] = {bold = true, reverse = true}; + [9] = {reverse = true}; + [10] = {italic = true, background = Screen.colors.Magenta}; + [11] = {foreground = Screen.colors.Red, background = tonumber('0x005028')}; + } + end) + + local mulholland = [[ + // just to see if there was an accident + // on Mulholland Drive + try_start(); + bufref_T save_buf; + switch_buffer(&save_buf, buf); + posp = getmark(mark, false); + restore_buffer(&save_buf); ]] + + local function setup_provider(code) + return exec_lua ([[ + local a = vim.api + _G.ns1 = a.nvim_create_namespace "ns1" + ]] .. (code or [[ + beamtrace = {} + local function on_do(kind, ...) + table.insert(beamtrace, {kind, ...}) + end + ]]) .. [[ + a.nvim_set_decoration_provider(_G.ns1, { + on_start = on_do; on_buf = on_do; + on_win = on_do; on_line = on_do; + on_end = on_do; + }) + return _G.ns1 + ]]) + end + + local function check_trace(expected) + local actual = exec_lua [[ local b = beamtrace beamtrace = {} return b ]] + expect_events(expected, actual, "beam trace") + end + + it('leave a trace', function() + insert(mulholland) + + setup_provider() + + screen:expect{grid=[[ + // just to see if there was an accident | + // on Mulholland Drive | + try_start(); | + bufref_T save_buf; | + switch_buffer(&save_buf, buf); | + posp = getmark(mark, false); | + restore_buffer(&save_buf);^ | + | + ]]} + check_trace { + { "start", 4, 40 }; + { "win", 1000, 1, 0, 8 }; + { "line", 1000, 1, 0 }; + { "line", 1000, 1, 1 }; + { "line", 1000, 1, 2 }; + { "line", 1000, 1, 3 }; + { "line", 1000, 1, 4 }; + { "line", 1000, 1, 5 }; + { "line", 1000, 1, 6 }; + { "end", 4 }; + } + + feed "iรผ<esc>" + screen:expect{grid=[[ + // just to see if there was an accident | + // on Mulholland Drive | + try_start(); | + bufref_T save_buf; | + switch_buffer(&save_buf, buf); | + posp = getmark(mark, false); | + restore_buffer(&save_buf);^รผ | + | + ]]} + check_trace { + { "start", 5, 10 }; + { "buf", 1 }; + { "win", 1000, 1, 0, 8 }; + { "line", 1000, 1, 6 }; + { "end", 5 }; + } + end) + + it('can have single provider', function() + insert(mulholland) + setup_provider [[ + local hl = a.nvim_get_hl_id_by_name "ErrorMsg" + local test_ns = a.nvim_create_namespace "mulholland" + function on_do(event, ...) + if event == "line" then + local win, buf, line = ... + a.nvim_buf_set_extmark(buf, test_ns, line, line, + { end_line = line, end_col = line+1, + hl_group = hl, + ephemeral = true + }) + end + end + ]] + + screen:expect{grid=[[ + {2:/}/ just to see if there was an accident | + /{2:/} on Mulholland Drive | + tr{2:y}_start(); | + buf{2:r}ef_T save_buf; | + swit{2:c}h_buffer(&save_buf, buf); | + posp {2:=} getmark(mark, false); | + restor{2:e}_buffer(&save_buf);^ | + | + ]]} + end) + + it('can predefine highlights', function() + screen:try_resize(40, 16) + insert(mulholland) + exec [[ + 3 + set ft=c + syntax on + set number cursorline + split + ]] + local ns1 = setup_provider() + + for k,v in pairs { + LineNr = {italic=true, bg="Magenta"}; + Comment = {fg="#FF0000", bg = 80*256+40}; + CursorLine = {link="ErrorMsg"}; + } do meths.set_hl(ns1, k, v) end + + screen:expect{grid=[[ + {3: 1 }{4:// just to see if there was an accid}| + {3: }{4:ent} | + {3: 2 }{4:// on Mulholland Drive} | + {6: 3 }{7:^try_start(); }| + {3: 4 }bufref_T save_buf; | + {3: 5 }switch_buffer(&save_buf, buf); | + {3: 6 }posp = getmark(mark, {5:false}); | + {8:[No Name] [+] }| + {3: 2 }{4:// on Mulholland Drive} | + {6: 3 }{7:try_start(); }| + {3: 4 }bufref_T save_buf; | + {3: 5 }switch_buffer(&save_buf, buf); | + {3: 6 }posp = getmark(mark, {5:false}); | + {3: 7 }restore_buffer(&save_buf); | + {9:[No Name] [+] }| + | + ]]} + + meths.set_hl_ns(ns1) + screen:expect{grid=[[ + {10: 1 }{11:// just to see if there was an accid}| + {10: }{11:ent} | + {10: 2 }{11:// on Mulholland Drive} | + {6: 3 }{2:^try_start(); }| + {10: 4 }bufref_T save_buf; | + {10: 5 }switch_buffer(&save_buf, buf); | + {10: 6 }posp = getmark(mark, {5:false}); | + {8:[No Name] [+] }| + {10: 2 }{11:// on Mulholland Drive} | + {6: 3 }{2:try_start(); }| + {10: 4 }bufref_T save_buf; | + {10: 5 }switch_buffer(&save_buf, buf); | + {10: 6 }posp = getmark(mark, {5:false}); | + {10: 7 }restore_buffer(&save_buf); | + {9:[No Name] [+] }| + | + ]]} + + exec_lua [[ + local a = vim.api + local thewin = a.nvim_get_current_win() + 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) + end; + }) + ]] + screen:expect{grid=[[ + {10: 1 }{11:// just to see if there was an accid}| + {10: }{11:ent} | + {10: 2 }{11:// on Mulholland Drive} | + {6: 3 }{2:^try_start(); }| + {10: 4 }bufref_T save_buf; | + {10: 5 }switch_buffer(&save_buf, buf); | + {10: 6 }posp = getmark(mark, {5:false}); | + {8:[No Name] [+] }| + {3: 2 }{4:// on Mulholland Drive} | + {6: 3 }{7:try_start(); }| + {3: 4 }bufref_T save_buf; | + {3: 5 }switch_buffer(&save_buf, buf); | + {3: 6 }posp = getmark(mark, {5:false}); | + {3: 7 }restore_buffer(&save_buf); | + {9:[No Name] [+] }| + | + ]]} + + end) +end) diff --git a/test/functional/ui/diff_spec.lua b/test/functional/ui/diff_spec.lua index 252991aca7..69b6ab8cf0 100644 --- a/test/functional/ui/diff_spec.lua +++ b/test/functional/ui/diff_spec.lua @@ -6,6 +6,7 @@ local clear = helpers.clear local command = helpers.command local insert = helpers.insert local write_file = helpers.write_file +local source = helpers.source describe('Diff mode screen', function() local fname = 'Xtest-functional-diff-screen-1' @@ -1031,3 +1032,79 @@ it('win_update redraws lines properly', function() | ]]} end) + +it('diff updates line numbers below filler lines', function() + clear() + local screen = Screen.new(40, 14) + screen:attach() + screen:set_default_attr_ids({ + [1] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.WebGray}, + [2] = {background = Screen.colors.LightCyan1, bold = true, foreground = Screen.colors.Blue1}, + [3] = {reverse = true}, + [4] = {background = Screen.colors.LightBlue}, + [5] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.LightGrey}, + [6] = {bold = true, foreground = Screen.colors.Blue1}, + [7] = {bold = true, reverse = true}, + [8] = {bold = true, background = Screen.colors.Red}, + [9] = {background = Screen.colors.LightMagenta}, + [10] = {bold = true, foreground = Screen.colors.Brown}, + [11] = {foreground = Screen.colors.Brown}, + }) + source([[ + call setline(1, ['a', 'a', 'a', 'y', 'b', 'b', 'b', 'b', 'b']) + vnew + call setline(1, ['a', 'a', 'a', 'x', 'x', 'x', 'b', 'b', 'b', 'b', 'b']) + windo diffthis + setlocal number rnu foldcolumn=0 + ]]) + screen:expect([[ + {1: }a {3:โ}{10:1 }^a | + {1: }a {3:โ}{11: 1 }a | + {1: }a {3:โ}{11: 2 }a | + {1: }{8:x}{9: }{3:โ}{11: 3 }{8:y}{9: }| + {1: }{4:x }{3:โ}{11: }{2:----------------}| + {1: }{4:x }{3:โ}{11: }{2:----------------}| + {1: }b {3:โ}{11: 4 }b | + {1: }b {3:โ}{11: 5 }b | + {1: }b {3:โ}{11: 6 }b | + {1: }b {3:โ}{11: 7 }b | + {1: }b {3:โ}{11: 8 }b | + {6:~ }{3:โ}{6:~ }| + {3:[No Name] [+] }{7:[No Name] [+] }| + | + ]]) + feed('j') + screen:expect([[ + {1: }a {3:โ}{11: 1 }a | + {1: }a {3:โ}{10:2 }^a | + {1: }a {3:โ}{11: 1 }a | + {1: }{8:x}{9: }{3:โ}{11: 2 }{8:y}{9: }| + {1: }{4:x }{3:โ}{11: }{2:----------------}| + {1: }{4:x }{3:โ}{11: }{2:----------------}| + {1: }b {3:โ}{11: 3 }b | + {1: }b {3:โ}{11: 4 }b | + {1: }b {3:โ}{11: 5 }b | + {1: }b {3:โ}{11: 6 }b | + {1: }b {3:โ}{11: 7 }b | + {6:~ }{3:โ}{6:~ }| + {3:[No Name] [+] }{7:[No Name] [+] }| + | + ]]) + feed('j') + screen:expect([[ + {1: }a {3:โ}{11: 2 }a | + {1: }a {3:โ}{11: 1 }a | + {1: }a {3:โ}{10:3 }^a | + {1: }{8:x}{9: }{3:โ}{11: 1 }{8:y}{9: }| + {1: }{4:x }{3:โ}{11: }{2:----------------}| + {1: }{4:x }{3:โ}{11: }{2:----------------}| + {1: }b {3:โ}{11: 2 }b | + {1: }b {3:โ}{11: 3 }b | + {1: }b {3:โ}{11: 4 }b | + {1: }b {3:โ}{11: 5 }b | + {1: }b {3:โ}{11: 6 }b | + {6:~ }{3:โ}{6:~ }| + {3:[No Name] [+] }{7:[No Name] [+] }| + | + ]]) +end) diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua index 11fe861de8..eec8eb93d4 100644 --- a/test/functional/ui/float_spec.lua +++ b/test/functional/ui/float_spec.lua @@ -59,7 +59,7 @@ describe('floatwin', function() end) it('closed immediately by autocmd #11383', function() - eq('Error executing lua: [string "<nvim>"]:4: Window was closed immediately', + eq('Error executing lua: [string "<nvim>"]:0: Window was closed immediately', pcall_err(exec_lua, [[ local a = vim.api local function crashes(contents) diff --git a/test/functional/ui/fold_spec.lua b/test/functional/ui/fold_spec.lua index 6ec45064da..669ec5d3ed 100644 --- a/test/functional/ui/fold_spec.lua +++ b/test/functional/ui/fold_spec.lua @@ -6,353 +6,966 @@ local feed_command = helpers.feed_command local insert = helpers.insert local funcs = helpers.funcs local meths = helpers.meths +local source = helpers.source +local assert_alive = helpers.assert_alive describe("folded lines", function() - local screen before_each(function() clear() - screen = Screen.new(45, 8) - screen:attach({rgb=true}) - screen:set_default_attr_ids({ - [1] = {bold = true, foreground = Screen.colors.Blue1}, - [2] = {reverse = true}, - [3] = {bold = true, reverse = true}, - [4] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red}, - [5] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.LightGrey}, - [6] = {background = Screen.colors.Yellow}, - [7] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.WebGray}, - }) end) - it("work with more than one signcolumn", function() - command("set signcolumn=yes:9") - feed("i<cr><esc>") - feed("vkzf") - screen:expect([[ - {5: ^+-- 2 lines: ยทยทยทยทยทยทยทยทยทยทยทยทยท}| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - | - ]]) - end) + local function with_ext_multigrid(multigrid) + local screen + before_each(function() + clear() + screen = Screen.new(45, 8) + screen:attach({rgb=true, ext_multigrid=multigrid}) + screen:set_default_attr_ids({ + [1] = {bold = true, foreground = Screen.colors.Blue1}, + [2] = {reverse = true}, + [3] = {bold = true, reverse = true}, + [4] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red}, + [5] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.LightGrey}, + [6] = {background = Screen.colors.Yellow}, + [7] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.WebGray}, + [8] = {foreground = Screen.colors.Brown }, + [9] = {bold = true, foreground = Screen.colors.Brown} + }) + end) - it("highlighting with relative line numbers", function() - command("set relativenumber foldmethod=marker") - feed_command("set foldcolumn=2") - funcs.setline(1, '{{{1') - funcs.setline(2, 'line 1') - funcs.setline(3, '{{{1') - funcs.setline(4, 'line 2') - feed("j") - screen:expect([[ - {7:+ }{5: 1 +-- 2 lines: ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท}| - {7:+ }{5: 0 ^+-- 2 lines: ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท}| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - :set foldcolumn=2 | - ]]) - end) + it("work with more than one signcolumn", function() + command("set signcolumn=yes:9") + feed("i<cr><esc>") + feed("vkzf") + if multigrid then + screen:expect([[ + ## grid 1 + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [3:---------------------------------------------]| + ## grid 2 + {7: }{5:^+-- 2 lines: ยทยทยทยทยทยทยทยทยทยทยทยทยท}| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 3 + | + ]]) + else + screen:expect([[ + {7: }{5:^+-- 2 lines: ยทยทยทยทยทยทยทยทยทยทยทยทยท}| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + end + end) - it("works with multibyte fillchars", function() - insert([[ - aa - bb - cc - dd - ee - ff]]) - command("set fillchars+=foldopen:โพ,foldsep:โ,foldclose:โธ") - feed_command('1') - command("set foldcolumn=2") - feed('zf4j') - feed('zf2j') - feed('zO') - screen:expect{grid=[[ - {7:โพโพ}^aa | - {7:โโ}bb | - {7:โโ}cc | - {7:โโ}dd | - {7:โโ}ee | - {7:โ }ff | - {1:~ }| - :1 | - ]]} - - feed_command("set rightleft") - screen:expect{grid=[[ - a^a{7:โพโพ}| - bb{7:โโ}| - cc{7:โโ}| - dd{7:โโ}| - ee{7:โโ}| - ff{7: โ}| - {1: ~}| - :set rightleft | - ]]} - - feed_command("set norightleft") - meths.input_mouse('left', 'press', '', 0, 0, 1) - screen:expect{grid=[[ - {7:โพโธ}{5:^+--- 5 lines: aaยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท}| - {7:โ }ff | - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - :set norightleft | - ]]} - end) + it("highlighting with relative line numbers", function() + command("set relativenumber foldmethod=marker") + feed_command("set foldcolumn=2") + funcs.setline(1, '{{{1') + funcs.setline(2, 'line 1') + funcs.setline(3, '{{{1') + funcs.setline(4, 'line 2') + feed("j") + if multigrid then + screen:expect([[ + ## grid 1 + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [3:---------------------------------------------]| + ## grid 2 + {7:+ }{8: 1 }{5:+-- 2 lines: ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท}| + {7:+ }{9: 0 }{5:^+-- 2 lines: ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท}| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 3 + :set foldcolumn=2 | + ]]) + else + screen:expect([[ + {7:+ }{8: 1 }{5:+-- 2 lines: ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท}| + {7:+ }{9: 0 }{5:^+-- 2 lines: ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท}| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + :set foldcolumn=2 | + ]]) + end + end) - it("works with multibyte text", function() - -- Currently the only allowed value of 'maxcombine' - eq(6, meths.get_option('maxcombine')) - eq(true, meths.get_option('arabicshape')) - insert([[ - รฅ ่ฏญ xฬออฬออฬพฬฃฬฬซฬฒอฬจออขอ ุงูุนูุฑูุจููููุฉ - mรถre text]]) - screen:expect([[ - รฅ ่ฏญ xฬออฬออ ๏บ๏ป ๏ปู๏บฎู๏บู๏ปณูู๏บ | - mรถre tex^t | - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - | - ]]) - - feed('vkzf') - screen:expect([[ - {5:^+-- 2 lines: รฅ ่ฏญ xฬออฬออ ๏บ๏ป ๏ปู๏บฎู๏บู๏ปณูู๏บยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท}| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - | - ]]) - - feed_command("set noarabicshape") - screen:expect([[ - {5:^+-- 2 lines: รฅ ่ฏญ xฬออฬออ ุงูุนูุฑูุจููููุฉยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท}| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - :set noarabicshape | - ]]) - - feed_command("set number foldcolumn=2") - screen:expect([[ - {7:+ }{5: 1 ^+-- 2 lines: รฅ ่ฏญ xฬออฬออ ุงูุนูุฑูุจููููุฉยทยทยทยทยทยทยทยทยทยทยท}| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - :set number foldcolumn=2 | - ]]) - - -- Note: too much of the folded line gets cut off.This is a vim bug. - feed_command("set rightleft") - screen:expect([[ - {5:+-- 2 lines: รฅ ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท^ยท 1 }{7: +}| - {1: ~}| - {1: ~}| - {1: ~}| - {1: ~}| - {1: ~}| - {1: ~}| - :set rightleft | - ]]) - - feed_command("set nonumber foldcolumn=0") - screen:expect([[ - {5:+-- 2 lines: รฅ ่ฏญ xฬออฬออ ุงูยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท^ยท}| - {1: ~}| - {1: ~}| - {1: ~}| - {1: ~}| - {1: ~}| - {1: ~}| - :set nonumber foldcolumn=0 | - ]]) - - feed_command("set arabicshape") - screen:expect([[ - {5:+-- 2 lines: รฅ ่ฏญ xฬออฬออ ๏บ๏ปยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท^ยท}| - {1: ~}| - {1: ~}| - {1: ~}| - {1: ~}| - {1: ~}| - {1: ~}| - :set arabicshape | - ]]) - - feed('zo') - screen:expect([[ - ๏บ๏ปดูู๏บู๏บฎู๏ปู^๏ป๏บ xฬออฬออ ่ฏญ รฅ| - txet erรถm| - {1: ~}| - {1: ~}| - {1: ~}| - {1: ~}| - {1: ~}| - :set arabicshape | - ]]) - - feed_command('set noarabicshape') - screen:expect([[ - ุฉูููุจูุฑูุนู^ูุง xฬออฬออ ่ฏญ รฅ| - txet erรถm| - {1: ~}| - {1: ~}| - {1: ~}| - {1: ~}| - {1: ~}| - :set noarabicshape | - ]]) + it("works with multibyte fillchars", function() + insert([[ + aa + bb + cc + dd + ee + ff]]) + command("set fillchars+=foldopen:โพ,foldsep:โ,foldclose:โธ") + feed_command('1') + command("set foldcolumn=2") + feed('zf4j') + feed('zf2j') + feed('zO') + if multigrid then + screen:expect([[ + ## grid 1 + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [3:---------------------------------------------]| + ## grid 2 + {7:โพโพ}^aa | + {7:โโ}bb | + {7:โโ}cc | + {7:โโ}dd | + {7:โโ}ee | + {7:โ }ff | + {1:~ }| + ## grid 3 + :1 | + ]]) + else + screen:expect([[ + {7:โพโพ}^aa | + {7:โโ}bb | + {7:โโ}cc | + {7:โโ}dd | + {7:โโ}ee | + {7:โ }ff | + {1:~ }| + :1 | + ]]) + end - end) + feed_command("set rightleft") + if multigrid then + screen:expect([[ + ## grid 1 + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [3:---------------------------------------------]| + ## grid 2 + a^a{7:โพโพ}| + bb{7:โโ}| + cc{7:โโ}| + dd{7:โโ}| + ee{7:โโ}| + ff{7: โ}| + {1: ~}| + ## grid 3 + :set rightleft | + ]]) + else + screen:expect([[ + a^a{7:โพโพ}| + bb{7:โโ}| + cc{7:โโ}| + dd{7:โโ}| + ee{7:โโ}| + ff{7: โ}| + {1: ~}| + :set rightleft | + ]]) + end + + feed_command("set norightleft") + if multigrid then + meths.input_mouse('left', 'press', '', 2, 0, 1) + screen:expect([[ + ## grid 1 + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [3:---------------------------------------------]| + ## grid 2 + {7:โพโธ}{5:^+--- 5 lines: aaยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท}| + {7:โ }ff | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 3 + :set norightleft | + ]]) + else + meths.input_mouse('left', 'press', '', 0, 0, 1) + screen:expect([[ + {7:โพโธ}{5:^+--- 5 lines: aaยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท}| + {7:โ }ff | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + :set norightleft | + ]]) + end + end) + + it("works with multibyte text", function() + -- Currently the only allowed value of 'maxcombine' + eq(6, meths.get_option('maxcombine')) + eq(true, meths.get_option('arabicshape')) + insert([[ + รฅ ่ฏญ xฬออฬออฬพฬฃฬฬซฬฒอฬจออขอ ุงูุนูุฑูุจููููุฉ + mรถre text]]) + if multigrid then + screen:expect([[ + ## grid 1 + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [3:---------------------------------------------]| + ## grid 2 + รฅ ่ฏญ xฬออฬออ ๏บ๏ป ๏ปู๏บฎู๏บู๏ปณูู๏บ | + mรถre tex^t | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 3 + | + ]]) + else + screen:expect([[ + รฅ ่ฏญ xฬออฬออ ๏บ๏ป ๏ปู๏บฎู๏บู๏ปณูู๏บ | + mรถre tex^t | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + end + + feed('vkzf') + if multigrid then + screen:expect([[ + ## grid 1 + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [3:---------------------------------------------]| + ## grid 2 + {5:^+-- 2 lines: รฅ ่ฏญ xฬออฬออ ุงูุนูุฑูุจููููุฉยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท}| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 3 + | + ]]) + else + screen:expect([[ + {5:^+-- 2 lines: รฅ ่ฏญ xฬออฬออ ุงูุนูุฑูุจููููุฉยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท}| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + end + + feed_command("set noarabicshape") + if multigrid then + screen:expect([[ + ## grid 1 + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [3:---------------------------------------------]| + ## grid 2 + {5:^+-- 2 lines: รฅ ่ฏญ xฬออฬออ ุงูุนูุฑูุจููููุฉยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท}| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 3 + :set noarabicshape | + ]]) + else + screen:expect([[ + {5:^+-- 2 lines: รฅ ่ฏญ xฬออฬออ ุงูุนูุฑูุจููููุฉยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท}| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + :set noarabicshape | + ]]) + end + + feed_command("set number foldcolumn=2") + if multigrid then + screen:expect([[ + ## grid 1 + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [3:---------------------------------------------]| + ## grid 2 + {7:+ }{8: 1 }{5:^+-- 2 lines: รฅ ่ฏญ xฬออฬออ ุงูุนูุฑูุจููููุฉยทยทยทยทยทยทยทยทยทยทยท}| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 3 + :set number foldcolumn=2 | + ]]) + else + screen:expect([[ + {7:+ }{8: 1 }{5:^+-- 2 lines: รฅ ่ฏญ xฬออฬออ ุงูุนูุฑูุจููููุฉยทยทยทยทยทยทยทยทยทยทยท}| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + :set number foldcolumn=2 | + ]]) + end + + -- Note: too much of the folded line gets cut off.This is a vim bug. + feed_command("set rightleft") + if multigrid then + screen:expect([[ + ## grid 1 + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [3:---------------------------------------------]| + ## grid 2 + {5:ยทยทยทยทยทยทยทยทยทยทยทุฉูููุจูุฑูุนููุง xฬออฬออ ่ฏญ รฅ :senil 2 --^+}{8: 1 }{7: +}| + {1: ~}| + {1: ~}| + {1: ~}| + {1: ~}| + {1: ~}| + {1: ~}| + ## grid 3 + :set rightleft | + ]]) + else + screen:expect([[ + {5:ยทยทยทยทยทยทยทยทยทยทยทุฉูููุจูุฑูุนููุง xฬออฬออ ่ฏญ รฅ :senil 2 --^+}{8: 1 }{7: +}| + {1: ~}| + {1: ~}| + {1: ~}| + {1: ~}| + {1: ~}| + {1: ~}| + :set rightleft | + ]]) + end + + feed_command("set nonumber foldcolumn=0") + if multigrid then + screen:expect([[ + ## grid 1 + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [3:---------------------------------------------]| + ## grid 2 + {5:ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทุฉูููุจูุฑูุนููุง xฬออฬออ ่ฏญ รฅ :senil 2 --^+}| + {1: ~}| + {1: ~}| + {1: ~}| + {1: ~}| + {1: ~}| + {1: ~}| + ## grid 3 + :set nonumber foldcolumn=0 | + ]]) + else + screen:expect([[ + {5:ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทุฉูููุจูุฑูุนููุง xฬออฬออ ่ฏญ รฅ :senil 2 --^+}| + {1: ~}| + {1: ~}| + {1: ~}| + {1: ~}| + {1: ~}| + {1: ~}| + :set nonumber foldcolumn=0 | + ]]) + end + + feed_command("set arabicshape") + if multigrid then + screen:expect([[ + ## grid 1 + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [3:---------------------------------------------]| + ## grid 2 + {5:ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทุฉูููุจูุฑูุนููุง xฬออฬออ ่ฏญ รฅ :senil 2 --^+}| + {1: ~}| + {1: ~}| + {1: ~}| + {1: ~}| + {1: ~}| + {1: ~}| + ## grid 3 + :set arabicshape | + ]]) + else + screen:expect([[ + {5:ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทุฉูููุจูุฑูุนููุง xฬออฬออ ่ฏญ รฅ :senil 2 --^+}| + {1: ~}| + {1: ~}| + {1: ~}| + {1: ~}| + {1: ~}| + {1: ~}| + :set arabicshape | + ]]) + end + + feed('zo') + if multigrid then + screen:expect([[ + ## grid 1 + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [3:---------------------------------------------]| + ## grid 2 + ๏บ๏ปดูู๏บู๏บฎู๏ปู^๏ป๏บ xฬออฬออ ่ฏญ รฅ| + txet erรถm| + {1: ~}| + {1: ~}| + {1: ~}| + {1: ~}| + {1: ~}| + ## grid 3 + :set arabicshape | + ]]) + else + screen:expect([[ + ๏บ๏ปดูู๏บู๏บฎู๏ปู^๏ป๏บ xฬออฬออ ่ฏญ รฅ| + txet erรถm| + {1: ~}| + {1: ~}| + {1: ~}| + {1: ~}| + {1: ~}| + :set arabicshape | + ]]) + end + + feed_command('set noarabicshape') + if multigrid then + screen:expect([[ + ## grid 1 + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [3:---------------------------------------------]| + ## grid 2 + ุฉูููุจูุฑูุนู^ูุง xฬออฬออ ่ฏญ รฅ| + txet erรถm| + {1: ~}| + {1: ~}| + {1: ~}| + {1: ~}| + {1: ~}| + ## grid 3 + :set noarabicshape | + ]]) + else + screen:expect([[ + ุฉูููุจูุฑูุนู^ูุง xฬออฬออ ่ฏญ รฅ| + txet erรถm| + {1: ~}| + {1: ~}| + {1: ~}| + {1: ~}| + {1: ~}| + :set noarabicshape | + ]]) + end + + end) + + it("work in cmdline window", function() + feed_command("set foldmethod=manual") + feed_command("let x = 1") + feed_command("/alpha") + feed_command("/omega") + + feed("<cr>q:") + if multigrid then + screen:expect([[ + ## grid 1 + [2:---------------------------------------------]| + {2:[No Name] }| + [4:---------------------------------------------]| + [4:---------------------------------------------]| + [4:---------------------------------------------]| + [4:---------------------------------------------]| + {3:[Command Line] }| + [3:---------------------------------------------]| + ## grid 2 + | + ## grid 3 + : | + ## grid 4 + {1::}set foldmethod=manual | + {1::}let x = 1 | + {1::}^ | + {1:~ }| + ]]) + else + screen:expect([[ + | + {2:[No Name] }| + {1::}set foldmethod=manual | + {1::}let x = 1 | + {1::}^ | + {1:~ }| + {3:[Command Line] }| + : | + ]]) + end + + feed("kzfk") + if multigrid then + screen:expect([[ + ## grid 1 + [2:---------------------------------------------]| + {2:[No Name] }| + [4:---------------------------------------------]| + [4:---------------------------------------------]| + [4:---------------------------------------------]| + [4:---------------------------------------------]| + {3:[Command Line] }| + [3:---------------------------------------------]| + ## grid 2 + | + ## grid 3 + : | + ## grid 4 + {1::}{5:^+-- 2 lines: set foldmethod=manualยทยทยทยทยทยทยทยทยท}| + {1::} | + {1:~ }| + {1:~ }| + ]]) + else + screen:expect([[ + | + {2:[No Name] }| + {1::}{5:^+-- 2 lines: set foldmethod=manualยทยทยทยทยทยทยทยทยท}| + {1::} | + {1:~ }| + {1:~ }| + {3:[Command Line] }| + : | + ]]) + end + + feed("<cr>") + if multigrid then + screen:expect([[ + ## grid 1 + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [3:---------------------------------------------]| + ## grid 2 + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 3 + : | + ]]) + else + screen:expect([[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + : | + ]]) + end + + feed("/<c-f>") + if multigrid then + screen:expect([[ + ## grid 1 + [2:---------------------------------------------]| + {2:[No Name] }| + [5:---------------------------------------------]| + [5:---------------------------------------------]| + [5:---------------------------------------------]| + [5:---------------------------------------------]| + {3:[Command Line] }| + [3:---------------------------------------------]| + ## grid 2 + | + ## grid 3 + / | + ## grid 5 + {1:/}alpha | + {1:/}{6:omega} | + {1:/}^ | + {1:~ }| + ]]) + else + screen:expect([[ + | + {2:[No Name] }| + {1:/}alpha | + {1:/}{6:omega} | + {1:/}^ | + {1:~ }| + {3:[Command Line] }| + / | + ]]) + end + + feed("ggzfG") + if multigrid then + screen:expect([[ + ## grid 1 + [2:---------------------------------------------]| + {2:[No Name] }| + [5:---------------------------------------------]| + [5:---------------------------------------------]| + [5:---------------------------------------------]| + [5:---------------------------------------------]| + {3:[Command Line] }| + [3:---------------------------------------------]| + ## grid 2 + | + ## grid 3 + / | + ## grid 5 + {1:/}{5:^+-- 3 lines: alphaยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท}| + {1:~ }| + {1:~ }| + {1:~ }| + ]]) + else + screen:expect([[ + | + {2:[No Name] }| + {1:/}{5:^+-- 3 lines: alphaยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท}| + {1:~ }| + {1:~ }| + {1:~ }| + {3:[Command Line] }| + / | + ]]) + end + + end) + + it("work with autoresize", function() + + funcs.setline(1, 'line 1') + funcs.setline(2, 'line 2') + funcs.setline(3, 'line 3') + funcs.setline(4, 'line 4') + + feed("zfj") + command("set foldcolumn=0") + if multigrid then + screen:expect([[ + ## grid 1 + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [3:---------------------------------------------]| + ## grid 2 + {5:^+-- 2 lines: line 1ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท}| + line 3 | + line 4 | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 3 + | + ]]) + else + screen:expect([[ + {5:^+-- 2 lines: line 1ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท}| + line 3 | + line 4 | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + end + -- should adapt to the current nesting of folds (e.g., 1) + command("set foldcolumn=auto:1") + if multigrid then + screen:expect([[ + ## grid 1 + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [3:---------------------------------------------]| + ## grid 2 + {7:+}{5:^+-- 2 lines: line 1ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท}| + {7: }line 3 | + {7: }line 4 | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 3 + | + ]]) + else + screen:expect([[ + {7:+}{5:^+-- 2 lines: line 1ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท}| + {7: }line 3 | + {7: }line 4 | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + end + -- fdc should not change with a new fold as the maximum is 1 + feed("zf3j") + + if multigrid then + screen:expect([[ + ## grid 1 + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [3:---------------------------------------------]| + ## grid 2 + {7:+}{5:^+-- 4 lines: line 1ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท}| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 3 + | + ]]) + else + screen:expect([[ + {7:+}{5:^+-- 4 lines: line 1ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท}| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + end + + -- relax the maximum fdc thus fdc should expand to + -- accomodate the current number of folds + command("set foldcolumn=auto:4") + if multigrid then + screen:expect([[ + ## grid 1 + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [3:---------------------------------------------]| + ## grid 2 + {7:+ }{5:^+-- 4 lines: line 1ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท}| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 3 + | + ]]) + else + screen:expect([[ + {7:+ }{5:^+-- 4 lines: line 1ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท}| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + end + end) - it("work in cmdline window", function() - feed_command("set foldmethod=manual") - feed_command("let x = 1") - feed_command("/alpha") - feed_command("/omega") - - feed("<cr>q:") - screen:expect([[ - | - {2:[No Name] }| - {1::}set foldmethod=manual | - {1::}let x = 1 | - {1::}^ | - {1:~ }| - {3:[Command Line] }| - : | - ]]) - - feed("kzfk") - screen:expect([[ - | - {2:[No Name] }| - {1::}{5:^+-- 2 lines: set foldmethod=manualยทยทยทยทยทยทยทยทยท}| - {1::} | - {1:~ }| - {1:~ }| - {3:[Command Line] }| - : | - ]]) - - feed("<cr>") - screen:expect([[ - ^ | - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - : | - ]]) - - feed("/<c-f>") - screen:expect([[ - | - {2:[No Name] }| - {1:/}alpha | - {1:/}{6:omega} | - {1:/}^ | - {1:~ }| - {3:[Command Line] }| - / | - ]]) - - feed("ggzfG") - screen:expect([[ - | - {2:[No Name] }| - {1:/}{5:^+-- 3 lines: alphaยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท}| - {1:~ }| - {1:~ }| - {1:~ }| - {3:[Command Line] }| - / | - ]]) + it('does not crash when foldtext is longer than columns #12988', function() + source([[ + function! MyFoldText() abort + return repeat('-', &columns + 100) + endfunction + ]]) + command('set foldtext=MyFoldText()') + feed("i<cr><esc>") + feed("vkzf") + if multigrid then + screen:expect([[ + ## grid 1 + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [3:---------------------------------------------]| + ## grid 2 + {5:^---------------------------------------------}| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 3 + | + ]]) + else + screen:expect([[ + {5:^---------------------------------------------}| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + end + assert_alive() + end) + end + describe("with ext_multigrid", function() + with_ext_multigrid(true) end) - it("work with autoresize", function() - - funcs.setline(1, 'line 1') - funcs.setline(2, 'line 2') - funcs.setline(3, 'line 3') - funcs.setline(4, 'line 4') - - feed("zfj") - command("set foldcolumn=0") - screen:expect{grid=[[ - {5:^+-- 2 lines: line 1ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท}| - line 3 | - line 4 | - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - | - ]]} - -- should adapt to the current nesting of folds (e.g., 1) - command("set foldcolumn=auto:1") - screen:expect{grid=[[ - {7:+}{5:^+-- 2 lines: line 1ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท}| - {7: }line 3 | - {7: }line 4 | - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - | - ]]} - -- fdc should not change with a new fold as the maximum is 1 - feed("zf3j") - - screen:expect{grid=[[ - {7:+}{5:^+-- 4 lines: line 1ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท}| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - | - ]]} - - -- relax the maximum fdc thus fdc should expand to - -- accomodate the current number of folds - command("set foldcolumn=auto:4") - screen:expect{grid=[[ - {7:+ }{5:^+-- 4 lines: line 1ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท}| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - | - ]]} + describe('without ext_multigrid', function() + with_ext_multigrid(false) end) end) diff --git a/test/functional/ui/inccommand_spec.lua b/test/functional/ui/inccommand_spec.lua index afb0c9cfa6..712c1f377a 100644 --- a/test/functional/ui/inccommand_spec.lua +++ b/test/functional/ui/inccommand_spec.lua @@ -14,7 +14,7 @@ local neq = helpers.neq local ok = helpers.ok local retry = helpers.retry local source = helpers.source -local wait = helpers.wait +local poke_eventloop = helpers.poke_eventloop local nvim = helpers.nvim local sleep = helpers.sleep local nvim_dir = helpers.nvim_dir @@ -114,7 +114,7 @@ describe(":substitute, inccommand=split interactivity", function() it("no preview if invoked by a script", function() source('%s/tw/MO/g') - wait() + poke_eventloop() eq(1, eval("bufnr('$')")) -- sanity check: assert the buffer state expect(default_text:gsub("tw", "MO")) @@ -123,10 +123,10 @@ describe(":substitute, inccommand=split interactivity", function() it("no preview if invoked by feedkeys()", function() -- in a script... source([[:call feedkeys(":%s/tw/MO/g\<CR>")]]) - wait() + poke_eventloop() -- or interactively... feed([[:call feedkeys(":%s/tw/MO/g\<CR>")<CR>]]) - wait() + poke_eventloop() eq(1, eval("bufnr('$')")) -- sanity check: assert the buffer state expect(default_text:gsub("tw", "MO")) @@ -162,7 +162,7 @@ describe(":substitute, 'inccommand' preserves", function() insert(default_text) feed_command("set inccommand=" .. case) - local delims = { '/', '#', ';', '%', ',', '@', '!', ''} + local delims = { '/', '#', ';', '%', ',', '@', '!' } for _,delim in pairs(delims) do feed_command("%s"..delim.."lines"..delim.."LINES"..delim.."g") expect([[ @@ -194,7 +194,7 @@ describe(":substitute, 'inccommand' preserves", function() -- Start typing an incomplete :substitute command. feed([[:%s/e/YYYY/g]]) - wait() + poke_eventloop() -- Cancel the :substitute. feed([[<C-\><C-N>]]) @@ -230,7 +230,7 @@ describe(":substitute, 'inccommand' preserves", function() -- Start typing an incomplete :substitute command. feed([[:%s/e/YYYY/g]]) - wait() + poke_eventloop() -- Cancel the :substitute. feed([[<C-\><C-N>]]) @@ -251,7 +251,7 @@ describe(":substitute, 'inccommand' preserves", function() some text 1 some text 2]]) feed(":%s/e/XXX/") - wait() + poke_eventloop() eq(expected_tick, eval("b:changedtick")) end) @@ -1128,15 +1128,15 @@ describe(":substitute, inccommand=split", function() feed(":%s/tw/Xo/g") -- Delete and re-type the g a few times. feed("<BS>") - wait() + poke_eventloop() feed("g") - wait() + poke_eventloop() feed("<BS>") - wait() + poke_eventloop() feed("g") - wait() + poke_eventloop() feed("<CR>") - wait() + poke_eventloop() feed(":vs tmp<enter>") eq(3, helpers.call('bufnr', '$')) end) @@ -1171,7 +1171,7 @@ describe(":substitute, inccommand=split", function() feed_command("silent edit! test/functional/fixtures/bigfile_oneline.txt") -- Start :substitute with a slow pattern. feed([[:%s/B.*N/x]]) - wait() + poke_eventloop() -- Assert that 'inccommand' is DISABLED in cmdline mode. eq("", eval("&inccommand")) @@ -1360,7 +1360,7 @@ describe("inccommand=nosplit", function() feed("<Esc>") command("set icm=nosplit") feed(":%s/tw/OKOK") - wait() + poke_eventloop() screen:expect([[ Inc substitution on | {12:OKOK}o lines | @@ -2592,7 +2592,7 @@ describe(":substitute", function() feed("<C-c>") feed('gg') - wait() + poke_eventloop() feed([[:%s/\(some\)\@<lt>!thing/one/]]) screen:expect([[ something | @@ -2613,7 +2613,7 @@ describe(":substitute", function() ]]) feed([[<C-c>]]) - wait() + poke_eventloop() feed([[:%s/some\(thing\)\@=/every/]]) screen:expect([[ {12:every}thing | @@ -2634,7 +2634,7 @@ describe(":substitute", function() ]]) feed([[<C-c>]]) - wait() + poke_eventloop() feed([[:%s/some\(thing\)\@!/every/]]) screen:expect([[ something | @@ -2718,7 +2718,7 @@ it(':substitute with inccommand during :terminal activity', function() feed('gg') feed(':%s/foo/ZZZ') sleep(20) -- Allow some terminal activity. - helpers.wait() + helpers.poke_eventloop() screen:expect_unchanged() end) end) @@ -2750,6 +2750,26 @@ it(':substitute with inccommand, timer-induced :redraw #9777', function() ]]) end) +it(":substitute doesn't crash with inccommand, if undo is empty #12932", function() + local screen = Screen.new(10,5) + clear() + command('set undolevels=-1') + common_setup(screen, 'split', 'test') + feed(':%s/test') + sleep(100) + feed('/') + sleep(100) + feed('f') + screen:expect([[ + {12:f} | + {15:~ }| + {15:~ }| + {15:~ }| + :%s/test/f^ | + ]]) + assert_alive() +end) + it('long :%s/ with inccommand does not collapse cmdline', function() local screen = Screen.new(10,5) clear() diff --git a/test/functional/ui/messages_spec.lua b/test/functional/ui/messages_spec.lua index efc02db159..5df4a1d533 100644 --- a/test/functional/ui/messages_spec.lua +++ b/test/functional/ui/messages_spec.lua @@ -323,7 +323,7 @@ describe('ui/ext_messages', function() {1:~ }| {1:~ }| ]], messages={ - {content = {{"/line [1/2] W"}}, kind = "search_count"} + {content = {{"/line W [1/2]"}}, kind = "search_count"} }} feed('n') diff --git a/test/functional/ui/mouse_spec.lua b/test/functional/ui/mouse_spec.lua index d857b57a31..a741136111 100644 --- a/test/functional/ui/mouse_spec.lua +++ b/test/functional/ui/mouse_spec.lua @@ -546,7 +546,7 @@ describe('ui/mouse/input', function() :tabprevious | ]]) feed('<LeftMouse><10,0><LeftRelease>') -- go to second tab - helpers.wait() + helpers.poke_eventloop() feed('<LeftMouse><0,1>') screen:expect([[ {tab: + foo }{sel: + bar }{fill: }{tab:X}| diff --git a/test/functional/ui/multigrid_spec.lua b/test/functional/ui/multigrid_spec.lua index e4d1187dea..6601c2d68e 100644 --- a/test/functional/ui/multigrid_spec.lua +++ b/test/functional/ui/multigrid_spec.lua @@ -4,7 +4,7 @@ local clear = helpers.clear local feed, command, insert = helpers.feed, helpers.command, helpers.insert local eq = helpers.eq local meths = helpers.meths -local wait = helpers.wait +local poke_eventloop = helpers.poke_eventloop describe('ext_multigrid', function() @@ -1846,8 +1846,8 @@ describe('ext_multigrid', function() meths.input_mouse('left', 'press', '', 1,6, 20) -- TODO(bfredl): "batching" input_mouse is formally not supported yet. -- Normally it should work fine in async context when nvim is not blocked, - -- but add a wait be sure. - wait() + -- but add a poke_eventloop be sure. + poke_eventloop() meths.input_mouse('left', 'drag', '', 1, 4, 20) screen:expect{grid=[[ ## grid 1 @@ -1921,7 +1921,7 @@ describe('ext_multigrid', function() ]]} meths.input_mouse('left', 'press', '', 1,8, 26) - wait() + poke_eventloop() meths.input_mouse('left', 'drag', '', 1, 6, 30) screen:expect{grid=[[ ## grid 1 diff --git a/test/functional/ui/options_spec.lua b/test/functional/ui/options_spec.lua index 9646c3fdad..2f113f6ac6 100644 --- a/test/functional/ui/options_spec.lua +++ b/test/functional/ui/options_spec.lua @@ -14,10 +14,10 @@ describe('UI receives option updates', function() arabicshape=true, emoji=true, guifont='', - guifontset='', guifontwide='', linespace=0, pumblend=0, + mousefocus=false, showtabline=1, termguicolors=false, ttimeout=true, @@ -110,6 +110,12 @@ describe('UI receives option updates', function() eq(expected, screen.options) end) + command("set mousefocus") + expected.mousefocus = true + screen:expect(function() + eq(expected, screen.options) + end) + command("set nottimeout") expected.ttimeout = false screen:expect(function() diff --git a/test/functional/ui/popupmenu_spec.lua b/test/functional/ui/popupmenu_spec.lua index 0ea0aecb00..0944bfc21a 100644 --- a/test/functional/ui/popupmenu_spec.lua +++ b/test/functional/ui/popupmenu_spec.lua @@ -1214,10 +1214,10 @@ describe('builtin popupmenu', function() funcs.complete(29, {'word', 'choice', 'text', 'thing'}) screen:expect([[ some long prefix before the ^ | - {n:word }{1: }| - {n:choice }{1: }| - {n:text }{1: }| - {n:thing }{1: }| + {1:~ }{n: word }| + {1:~ }{n: choice}| + {1:~ }{n: text }| + {1:~ }{n: thing }| {1:~ }| {1:~ }| {1:~ }| @@ -1262,10 +1262,10 @@ describe('builtin popupmenu', function() feed('<c-p>') screen:expect([[ some long prefix before the text| - {n:^word }{1: }| - {n:choice }{1: }| - {s:text }{1: }| - {n:thing }{1: }| + {1:^~ }{n: word }| + {1:~ }{n: choice}| + {1:~ }{s: text }| + {1:~ }{n: thing }| {1:~ }| {1:~ }| {1:~ }| @@ -1342,10 +1342,10 @@ describe('builtin popupmenu', function() screen:expect([[ some long prefix | before the text^ | - {1:~ }{n: word }| - {1:~ }{n: choice }| - {1:~ }{s: text }| - {1:~ }{n: thing }| + {1:~ }{n: word }{1: }| + {1:~ }{n: choice }{1: }| + {1:~ }{s: text }{1: }| + {1:~ }{n: thing }{1: }| {1:~ }| {2:-- INSERT --} | ]]) @@ -1359,10 +1359,10 @@ describe('builtin popupmenu', function() funcs.complete(29, {'word', 'choice', 'text', 'thing'}) screen:expect([[ some long prefix before the ^ | - {n:word }{1: }| - {n:choice }{1: }| - {n:text }{1: }| - {n:thing }{1: }| + {1:~ }{n: word }| + {1:~ }{n: choice}| + {1:~ }{n: text }| + {1:~ }{n: thing }| {1:~ }| {1:~ }| {1:~ }| @@ -2169,8 +2169,8 @@ describe('builtin popupmenu', function() funcs.complete(29, {'word', 'choice', 'text', 'thing'}) screen:expect([[ some long prefix before the ^ | - {n:word }{c: }{1: }| - {n:choice }{s: }{1: }| + {1:~ }{n: word }{c: }| + {1:~ }{n: choice}{s: }| {1:~ }| {1:~ }| {1:~ }| @@ -2188,10 +2188,10 @@ describe('builtin popupmenu', function() funcs.complete(29, {'word', 'choice', 'text', 'thing'}) screen:expect([[ some long prefix before the ^ | - {n:word }{1: }| - {n:choice }{1: }| - {n:text }{1: }| - {n:thing }{1: }| + {1:~ }{n: word }| + {1:~ }{n: choice}| + {1:~ }{n: text }| + {1:~ }{n: thing }| {1:~ }| {1:~ }| {2:-- INSERT --} | diff --git a/test/functional/ui/screen.lua b/test/functional/ui/screen.lua index bf979e89f4..8fa9fcc42f 100644 --- a/test/functional/ui/screen.lua +++ b/test/functional/ui/screen.lua @@ -1289,7 +1289,7 @@ local function fmt_ext_state(name, state) for k,v in pairs(state) do str = (str.." ["..k.."] = {win = {id = "..v.win.id.."}, topline = " ..v.topline..", botline = "..v.botline..", curline = "..v.curline - ..", curcol = "..v.curcol.."},\n") + ..", curcol = "..v.curcol.."};\n") end return str .. "}" else @@ -1316,7 +1316,7 @@ function Screen:print_snapshot(attrs, ignore) dict = "{"..self:_pprint_attrs(a).."}" end local keyval = (type(i) == "number") and "["..tostring(i).."]" or i - table.insert(attrstrs, " "..keyval.." = "..dict..",") + table.insert(attrstrs, " "..keyval.." = "..dict..";") end attrstr = (", attr_ids={\n"..table.concat(attrstrs, "\n").."\n}") end diff --git a/test/functional/ui/searchhl_spec.lua b/test/functional/ui/searchhl_spec.lua index 635ce7392b..222275eb4d 100644 --- a/test/functional/ui/searchhl_spec.lua +++ b/test/functional/ui/searchhl_spec.lua @@ -158,7 +158,7 @@ describe('search highlighting', function() bar foo baz ]]) feed('/foo') - helpers.wait() + helpers.poke_eventloop() screen:expect_unchanged() end) diff --git a/test/functional/ui/spell_spec.lua b/test/functional/ui/spell_spec.lua index 243b737583..2c6e586665 100644 --- a/test/functional/ui/spell_spec.lua +++ b/test/functional/ui/spell_spec.lua @@ -4,8 +4,9 @@ local helpers = require('test.functional.helpers')(after_each) local Screen = require('test.functional.ui.screen') local clear = helpers.clear local feed = helpers.feed -local feed_command = helpers.feed_command local insert = helpers.insert +local uname = helpers.uname +local command = helpers.command describe("'spell'", function() local screen @@ -16,12 +17,14 @@ describe("'spell'", function() screen:attach() screen:set_default_attr_ids( { [0] = {bold=true, foreground=Screen.colors.Blue}, - [1] = {special = Screen.colors.Red, undercurl = true} + [1] = {special = Screen.colors.Red, undercurl = true}, + [2] = {special = Screen.colors.Blue1, undercurl = true}, }) end) it('joins long lines #7937', function() - feed_command('set spell') + if uname() == 'openbsd' then pending('FIXME #12104', function() end) return end + command('set spell') insert([[ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, @@ -42,4 +45,26 @@ describe("'spell'", function() | ]]) end) + + it('has correct highlight at start of line', function() + insert([[ + "This is some text without any spell errors. Everything", + "should just be black, nothing wrong here.", + "", + "This line has a sepll error. and missing caps.", + "And and this is the the duplication.", + "with missing caps here.", + ]]) + command('set spell spelllang=en_nz') + screen:expect([[ + "This is some text without any spell errors. Everything", | + "should just be black, nothing wrong here.", | + "", | + "This line has a {1:sepll} error. {2:and} missing caps.", | + "{1:And and} this is {1:the the} duplication.", | + "with missing caps here.", | + ^ | + | + ]]) + end) end) diff --git a/test/functional/viml/completion_spec.lua b/test/functional/viml/completion_spec.lua index 9d4cb325d9..01fc50289d 100644 --- a/test/functional/viml/completion_spec.lua +++ b/test/functional/viml/completion_spec.lua @@ -6,7 +6,7 @@ local feed_command, source, expect = helpers.feed_command, helpers.source, helpe local curbufmeths = helpers.curbufmeths local command = helpers.command local meths = helpers.meths -local wait = helpers.wait +local poke_eventloop = helpers.poke_eventloop describe('completion', function() local screen @@ -737,8 +737,8 @@ describe('completion', function() -- Does not indent when "ind" is typed. feed("in<C-X><C-N>") -- Completion list is generated incorrectly if we send everything at once - -- via nvim_input(). So wait() before sending <BS>. #8480 - wait() + -- via nvim_input(). So poke_eventloop() before sending <BS>. #8480 + poke_eventloop() feed("<BS>d") screen:expect([[ @@ -778,7 +778,7 @@ describe('completion', function() ]]) -- Works for unindenting too. feed("ounin<C-X><C-N>") - helpers.wait() + helpers.poke_eventloop() feed("<BS>d") screen:expect([[ inc uninc indent unindent | @@ -1000,65 +1000,65 @@ describe('completion', function() command('let g:foo = []') feed('o') - wait() + poke_eventloop() feed('<esc>') eq({'I'}, eval('g:foo')) command('let g:foo = []') feed('S') - wait() + poke_eventloop() feed('f') - wait() + poke_eventloop() eq({'I', 'I'}, eval('g:foo')) feed('<esc>') command('let g:foo = []') feed('S') - wait() + poke_eventloop() feed('f') - wait() + poke_eventloop() feed('<C-N>') - wait() + poke_eventloop() eq({'I', 'I', 'P'}, eval('g:foo')) feed('<esc>') command('let g:foo = []') feed('S') - wait() + poke_eventloop() feed('f') - wait() + poke_eventloop() feed('<C-N>') - wait() + poke_eventloop() feed('<C-N>') - wait() + poke_eventloop() eq({'I', 'I', 'P', 'P'}, eval('g:foo')) feed('<esc>') command('let g:foo = []') feed('S') - wait() + poke_eventloop() feed('f') - wait() + poke_eventloop() feed('<C-N>') - wait() + poke_eventloop() feed('<C-N>') - wait() + poke_eventloop() feed('<C-N>') - wait() + poke_eventloop() eq({'I', 'I', 'P', 'P', 'P'}, eval('g:foo')) feed('<esc>') command('let g:foo = []') feed('S') - wait() + poke_eventloop() feed('f') - wait() + poke_eventloop() feed('<C-N>') - wait() + poke_eventloop() feed('<C-N>') - wait() + poke_eventloop() feed('<C-N>') - wait() + poke_eventloop() feed('<C-N>') eq({'I', 'I', 'P', 'P', 'P', 'P'}, eval('g:foo')) feed('<esc>') diff --git a/test/functional/viml/errorlist_spec.lua b/test/functional/viml/errorlist_spec.lua index c5390cbd12..9acc61e398 100644 --- a/test/functional/viml/errorlist_spec.lua +++ b/test/functional/viml/errorlist_spec.lua @@ -27,7 +27,7 @@ describe('setqflist()', function() setqflist({''}, 'r', 'foo') command('copen') eq('foo', get_cur_win_var('quickfix_title')) - setqflist({''}, 'r', {['title'] = 'qf_title'}) + setqflist({}, 'r', {['title'] = 'qf_title'}) eq('qf_title', get_cur_win_var('quickfix_title')) end) diff --git a/test/functional/visual/meta_key_spec.lua b/test/functional/visual/meta_key_spec.lua new file mode 100644 index 0000000000..11f7203da0 --- /dev/null +++ b/test/functional/visual/meta_key_spec.lua @@ -0,0 +1,22 @@ +local helpers = require('test.functional.helpers')(after_each) +local clear, feed, insert = helpers.clear, helpers.feed, helpers.insert +local command = helpers.command +local expect = helpers.expect + +describe('meta-keys-in-visual-mode', function() + before_each(function() + clear() + end) + + it('ALT/META', function() + -- Unmapped ALT-chords behave as Esc+c + insert('peaches') + feed('viw<A-x>viw<M-x>') + expect('peach') + -- Mapped ALT-chord behaves as mapped. + command('vnoremap <M-l> Ameta-l<Esc>') + command('vnoremap <A-j> Aalt-j<Esc>') + feed('viw<A-j>viw<M-l>') + expect('peachalt-jmeta-l') + end) +end) diff --git a/test/helpers.lua b/test/helpers.lua index 40b93d9935..84148dc1a8 100644 --- a/test/helpers.lua +++ b/test/helpers.lua @@ -82,6 +82,17 @@ end function module.ok(res, msg, logfile) return dumplog(logfile, assert.is_true, res, msg) end + +-- TODO(bfredl): this should "failure" not "error" (issue with dumplog() ) +local function epicfail(state, arguments, _) + state.failure_message = arguments[1] + return false +end +assert:register("assertion", "epicfail", epicfail) +function module.fail(msg, logfile) + return dumplog(logfile, assert.epicfail, msg) +end + function module.matches(pat, actual) if nil ~= string.match(actual, pat) then return true @@ -105,8 +116,12 @@ function module.assert_log(pat, logfile) pat, nrlines, logfile, logtail)) end --- Invokes `fn` and returns the error string (may truncate full paths), or --- raises an error if `fn` succeeds. +-- Invokes `fn` and returns the error string (with truncated paths), or raises +-- an error if `fn` succeeds. +-- +-- Replaces line/column numbers with zero: +-- shared.lua:0: in function 'gsplit' +-- shared.lua:0: in function <shared.lua:0>' -- -- Usage: -- -- Match exact string. @@ -114,29 +129,38 @@ end -- -- Match Lua pattern. -- matches('e[or]+$', pcall_err(function(a, b) error('some error') end, 'arg1', 'arg2')) -- -function module.pcall_err(fn, ...) +function module.pcall_err_withfile(fn, ...) assert(type(fn) == 'function') local status, rv = pcall(fn, ...) if status == true then error('expected failure, but got success') end - -- From this: - -- /home/foo/neovim/runtime/lua/vim/shared.lua:186: Expected string, got number - -- to this: - -- Expected string, got number - local errmsg = tostring(rv):gsub('^[^:]+:%d+: ', '') - -- From this: - -- Error executing lua: /very/long/foo.lua:186: Expected string, got number - -- to this: - -- Error executing lua: .../foo.lua:186: Expected string, got number - errmsg = errmsg:gsub([[lua: [a-zA-Z]?:?[^:]-[/\]([^:/\]+):%d+: ]], 'lua: .../%1: ') - -- Compiled modules will not have a path and will just be a name like - -- shared.lua:186, so strip the number. - errmsg = errmsg:gsub([[lua: ([^:/\ ]+):%d+: ]], 'lua: .../%1: ') - -- ^ Windows drive-letter (C:) + -- From: + -- C:/long/path/foo.lua:186: Expected string, got number + -- to: + -- .../foo.lua:0: Expected string, got number + local errmsg = tostring(rv):gsub('([%s<])vim[/\\]([^%s:/\\]+):%d+', '%1\xffvim\xff%2:0') + :gsub('[^%s<]-[/\\]([^%s:/\\]+):%d+', '.../%1:0') + :gsub('\xffvim\xff', 'vim/') + -- Scrub numbers in paths/stacktraces: + -- shared.lua:0: in function 'gsplit' + -- shared.lua:0: in function <shared.lua:0>' + errmsg = errmsg:gsub('([^%s]):%d+', '%1:0') + -- Scrub tab chars: + errmsg = errmsg:gsub('\t', ' ') + -- In Lua 5.1, we sometimes get a "(tail call): ?" on the last line. + -- We remove this so that the tests are not lua dependent. + errmsg = errmsg:gsub('%s*%(tail call%): %?', '') + return errmsg end +function module.pcall_err(fn, ...) + local errmsg = module.pcall_err_withfile(fn, ...) + + return errmsg:gsub('.../helpers.lua:0: ', '') +end + -- initial_path: directory to recurse into -- re: include pattern (string) -- exc_re: exclude pattern(s) (string or table) @@ -200,14 +224,25 @@ function module.check_logs() end end fd:close() - os.remove(file) if #lines > 0 then + local status, f local out = io.stdout + if os.getenv('SYMBOLIZER') then + status, f = pcall(module.popen_r, os.getenv('SYMBOLIZER'), '-l', file) + end out:write(start_msg .. '\n') - out:write('= ' .. table.concat(lines, '\n= ') .. '\n') + if status then + for line in f:lines() do + out:write('= '..line..'\n') + end + f:close() + else + out:write('= ' .. table.concat(lines, '\n= ') .. '\n') + end out:write(select(1, start_msg:gsub('.', '=')) .. '\n') table.insert(runtime_errors, file) end + os.remove(file) end end end @@ -323,7 +358,7 @@ function module.check_cores(app, force) exc_re = { os.getenv('NVIM_TEST_CORE_EXC_RE'), local_tmpdir } db_cmd = os.getenv('NVIM_TEST_CORE_DB_CMD') or gdb_db_cmd random_skip = os.getenv('NVIM_TEST_CORE_RANDOM_SKIP') - elseif os.getenv('TRAVIS_OS_NAME') == 'osx' then + elseif 'darwin' == module.uname() then initial_path = '/cores' re = nil exc_re = { local_tmpdir } diff --git a/test/symbolic/klee/nvim/charset.c b/test/symbolic/klee/nvim/charset.c index 95853a6834..f9bc3fabc4 100644 --- a/test/symbolic/klee/nvim/charset.c +++ b/test/symbolic/klee/nvim/charset.c @@ -69,7 +69,7 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, && !STRING_ENDED(ptr + 1) && ptr[0] == '0' && ptr[1] != '8' && ptr[1] != '9') { pre = ptr[1]; - // Detect hexadecimal: 0x or 0X follwed by hex digit + // Detect hexadecimal: 0x or 0X followed by hex digit if ((what & STR2NR_HEX) && !STRING_ENDED(ptr + 2) && (pre == 'X' || pre == 'x') @@ -77,7 +77,7 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, ptr += 2; goto vim_str2nr_hex; } - // Detect binary: 0b or 0B follwed by 0 or 1 + // Detect binary: 0b or 0B followed by 0 or 1 if ((what & STR2NR_BIN) && !STRING_ENDED(ptr + 2) && (pre == 'B' || pre == 'b') diff --git a/test/unit/buffer_spec.lua b/test/unit/buffer_spec.lua index bf4e5a0e6d..3692e19379 100644 --- a/test/unit/buffer_spec.lua +++ b/test/unit/buffer_spec.lua @@ -212,7 +212,7 @@ describe('buffer functions', function() describe('build_stl_str_hl', function() local buffer_byte_size = 100 - local STL_MAX_ITEM = 80 + local STL_INITIAL_ITEMS = 20 local output_buffer = '' -- This function builds the statusline @@ -431,31 +431,23 @@ describe('buffer functions', function() 'aaaa%=b%=c%=d%=e%=fg%=hi%=jk%=lmnop%=qrstuv%=wxyz', 'aaaa b c d e fg hi jk lmnop qrstuv wxyz') - -- maximum stl item testing - statusline_test('should handle a much larger amount of = than buffer locations', 20, - ('%='):rep(STL_MAX_ITEM - 1), - ' ') -- Should be fine, because within limit - statusline_test('should handle a much larger amount of = than stl max item', 20, - ('%='):rep(STL_MAX_ITEM + 1), - ' E541') -- Should show the VIM error + -- stl item testing + local tabline = '' + for i= 1, 1000 do + tabline = tabline .. (i % 2 == 0 and '%#TabLineSel#' or '%#TabLineFill#') .. tostring(i % 2) + end + statusline_test('should handle a large amount of any items', 20, + tabline, + '<1010101010101010101') -- Should not show any error + statusline_test('should handle a larger amount of = than stl initial item', 20, + ('%='):rep(STL_INITIAL_ITEMS * 5), + ' ') -- Should not show any error statusline_test('should handle many extra characters', 20, - 'a' .. ('a'):rep(STL_MAX_ITEM * 4), - '<aaaaaaaaaaaaaaaaaaa') -- Does not show the error because there are no items - statusline_test('should handle almost maximum of characters and flags', 20, - 'a' .. ('%=a'):rep(STL_MAX_ITEM - 1), - 'a<aaaaaaaaaaaaaaaaaa') -- Should not show the VIM error - statusline_test('should handle many extra characters and flags', 20, - 'a' .. ('%=a'):rep(STL_MAX_ITEM), - 'a<aaaaaaaaaaaaa E541') -- Should show the VIM error + 'a' .. ('a'):rep(STL_INITIAL_ITEMS * 5), + '<aaaaaaaaaaaaaaaaaaa') -- Does not show any error statusline_test('should handle many extra characters and flags', 20, - 'a' .. ('%=a'):rep(STL_MAX_ITEM * 2), - 'a<aaaaaaaaaaaaa E541') -- Should show the VIM error - statusline_test('should handle many extra characters and flags with truncation', 20, - 'aaa%<' .. ('%=a'):rep(STL_MAX_ITEM), - 'aaa<aaaaaaaaaaa E541') -- Should show the VIM error - statusline_test('should handle many characters and flags before and after truncation', 20, - 'a%=a%=a%<' .. ('%=a'):rep(STL_MAX_ITEM), - 'aaa<aaaaaaaaaaa E541') -- Should show the VIM error + 'a' .. ('%=a'):rep(STL_INITIAL_ITEMS * 2), + 'a<aaaaaaaaaaaaaaaaaa') -- Should not show any error -- multi-byte testing diff --git a/test/unit/os/env_spec.lua b/test/unit/os/env_spec.lua index e24a389d69..e7cb5e5d5e 100644 --- a/test/unit/os/env_spec.lua +++ b/test/unit/os/env_spec.lua @@ -78,15 +78,22 @@ describe('env.c', function() end) describe('os_setenv_append_path', function() - itp('appends /foo/bar to $PATH', function() + itp('appends :/foo/bar to $PATH', function() local original_path = os.getenv('PATH') - eq(true, cimp.os_setenv_append_path(to_cstr('/foo/bar/baz'))) + eq(true, cimp.os_setenv_append_path(to_cstr('/foo/bar/baz.exe'))) eq(original_path..':/foo/bar', os.getenv('PATH')) end) + itp('avoids redundant separator when appending to $PATH #7377', function() + os_setenv('PATH', '/a/b/c:', true) + eq(true, cimp.os_setenv_append_path(to_cstr('/foo/bar/baz.exe'))) + -- Must not have duplicate separators. #7377 + eq('/a/b/c:/foo/bar', os.getenv('PATH')) + end) + itp('returns false if `fname` is not absolute', function() local original_path = os.getenv('PATH') - eq(false, cimp.os_setenv_append_path(to_cstr('foo/bar/baz'))) + eq(false, cimp.os_setenv_append_path(to_cstr('foo/bar/baz.exe'))) eq(original_path, os.getenv('PATH')) end) end) @@ -172,7 +179,7 @@ describe('env.c', function() i = i + 1 name = cimp.os_getenvname_at_index(i) end - eq(true, (table.getn(names)) > 0) + eq(true, #names > 0) eq(true, found_name) end) diff --git a/third-party/CMakeLists.txt b/third-party/CMakeLists.txt index 477e25a882..d8923fd65b 100644 --- a/third-party/CMakeLists.txt +++ b/third-party/CMakeLists.txt @@ -42,6 +42,7 @@ option(USE_BUNDLED_LUV "Use the bundled version of luv." ${USE_BUNDLED}) # build it unless explicitly requested option(USE_BUNDLED_LUA "Use the bundled version of lua." OFF) option(USE_BUNDLED_TS_PARSERS "Use the bundled treesitter parsers." ${USE_BUNDLED}) +option(USE_BUNDLED_TS "Use the bundled treesitter runtime." ${USE_BUNDLED}) if(USE_BUNDLED AND MSVC) option(USE_BUNDLED_GETTEXT "Use the bundled version of gettext." ON) @@ -161,8 +162,8 @@ set(UNIBILIUM_SHA256 29815283c654277ef77a3adcc8840db79ddbb20a0f0b0c8f648bd8cd49a set(LIBTERMKEY_URL http://www.leonerd.org.uk/code/libtermkey/libtermkey-0.22.tar.gz) set(LIBTERMKEY_SHA256 6945bd3c4aaa83da83d80a045c5563da4edd7d0374c62c0d35aec09eb3014600) -set(LIBVTERM_URL https://github.com/neovim/libvterm/archive/65dbda3ed214f036ee799d18b2e693a833a0e591.tar.gz) -set(LIBVTERM_SHA256 95d3c7e86336fbd40dfd7a0aa0a795320bb71bc957ea995ea0e549c96d20db3a) +set(LIBVTERM_URL http://www.leonerd.org.uk/code/libvterm/libvterm-0.1.4.tar.gz) +set(LIBVTERM_SHA256 bc70349e95559c667672fc8c55b9527d9db9ada0fb80a3beda533418d782d3dd) set(LUV_VERSION 1.30.1-1) set(LUV_URL https://github.com/luvit/luv/archive/${LUV_VERSION}.tar.gz) @@ -198,6 +199,9 @@ set(LIBICONV_SHA256 ccf536620a45458d26ba83887a983b96827001e92a13847b45e4925cc891 set(TREESITTER_C_URL https://github.com/tree-sitter/tree-sitter-c/archive/6002fcd.tar.gz) set(TREESITTER_C_SHA256 46f8d44fa886d9ddb92571bb6fa8b175992c8758eca749cb1217464e512b6e97) +set(TREESITTER_URL https://github.com/tree-sitter/tree-sitter/archive/0.17.3.zip) +set(TREESITTER_SHA256 19068b6663f5a4cacd5d805fa437419e3c29eb615ed9523e438b400b79f39c20) + if(USE_BUNDLED_UNIBILIUM) include(BuildUnibilium) endif() @@ -253,6 +257,10 @@ if(USE_BUNDLED_TS_PARSERS) include(BuildTreesitterParsers) endif() +if(USE_BUNDLED_TS) + include(BuildTreesitter) +endif() + if(WIN32) include(GetBinaryDeps) diff --git a/third-party/cmake/BuildTreesitter.cmake b/third-party/cmake/BuildTreesitter.cmake new file mode 100644 index 0000000000..a55b2e36e8 --- /dev/null +++ b/third-party/cmake/BuildTreesitter.cmake @@ -0,0 +1,60 @@ +include(CMakeParseArguments) + +# BuildTreeSitter(TARGET targetname CONFIGURE_COMMAND ... BUILD_COMMAND ... INSTALL_COMMAND ...) +function(BuildTreeSitter) + cmake_parse_arguments(_treesitter + "BUILD_IN_SOURCE" + "TARGET" + "CONFIGURE_COMMAND;BUILD_COMMAND;INSTALL_COMMAND" + ${ARGN}) + + if(NOT _treesitter_CONFIGURE_COMMAND AND NOT _treesitter_BUILD_COMMAND + AND NOT _treesitter_INSTALL_COMMAND) + message(FATAL_ERROR "Must pass at least one of CONFIGURE_COMMAND, BUILD_COMMAND, INSTALL_COMMAND") + endif() + if(NOT _treesitter_TARGET) + set(_treesitter_TARGET "tree-sitter") + endif() + + ExternalProject_Add(tree-sitter + PREFIX ${DEPS_BUILD_DIR} + URL ${TREESITTER_URL} + DOWNLOAD_DIR ${DEPS_DOWNLOAD_DIR}/tree-sitter + INSTALL_DIR ${DEPS_INSTALL_DIR} + DOWNLOAD_COMMAND ${CMAKE_COMMAND} + -DPREFIX=${DEPS_BUILD_DIR} + -DDOWNLOAD_DIR=${DEPS_DOWNLOAD_DIR}/tree-sitter + -DURL=${TREESITTER_URL} + -DEXPECTED_SHA256=${TREESITTER_SHA256} + -DTARGET=tree-sitter + -DUSE_EXISTING_SRC_DIR=${USE_EXISTING_SRC_DIR} + -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/DownloadAndExtractFile.cmake + BUILD_IN_SOURCE ${_treesitter_BUILD_IN_SOURCE} + PATCH_COMMAND "" + CONFIGURE_COMMAND "${_treesitter_CONFIGURE_COMMAND}" + BUILD_COMMAND "${_treesitter_BUILD_COMMAND}" + INSTALL_COMMAND "${_treesitter_INSTALL_COMMAND}") +endfunction() + +if(MSVC) + BuildTreeSitter(BUILD_IN_SOURCE + CONFIGURE_COMMAND ${CMAKE_COMMAND} -E copy + ${CMAKE_CURRENT_SOURCE_DIR}/cmake/TreesitterCMakeLists.txt + ${DEPS_BUILD_DIR}/src/tree-sitter/CMakeLists.txt + COMMAND ${CMAKE_COMMAND} ${DEPS_BUILD_DIR}/src/tree-sitter/CMakeLists.txt + -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} + -DCMAKE_GENERATOR=${CMAKE_GENERATOR} + -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} + -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} + ) +else() + set(TS_CFLAGS "-O3 -Wall -Wextra") + BuildTreeSitter(BUILD_IN_SOURCE + CONFIGURE_COMMAND "" + BUILD_COMMAND ${MAKE_PRG} CC=${DEPS_C_COMPILER} CFLAGS=${TS_CFLAGS} + INSTALL_COMMAND ${MAKE_PRG} CC=${DEPS_C_COMPILER} PREFIX=${DEPS_INSTALL_DIR} install) +endif() + +list(APPEND THIRD_PARTY_DEPS tree-sitter) diff --git a/third-party/cmake/GetBinaryDeps.cmake b/third-party/cmake/GetBinaryDeps.cmake index f262ae7159..982bf62265 100644 --- a/third-party/cmake/GetBinaryDeps.cmake +++ b/third-party/cmake/GetBinaryDeps.cmake @@ -39,7 +39,6 @@ function(GetBinaryDep) -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/DownloadAndExtractFile.cmake CONFIGURE_COMMAND "" BUILD_IN_SOURCE 1 - CONFIGURE_COMMAND "" BUILD_COMMAND "" INSTALL_COMMAND ${CMAKE_COMMAND} -E make_directory ${DEPS_INSTALL_DIR}/bin COMMAND "${_gettool_INSTALL_COMMAND}") diff --git a/third-party/cmake/TreesitterCMakeLists.txt b/third-party/cmake/TreesitterCMakeLists.txt new file mode 100644 index 0000000000..9e3ba3eeda --- /dev/null +++ b/third-party/cmake/TreesitterCMakeLists.txt @@ -0,0 +1,21 @@ +cmake_minimum_required(VERSION 2.8.12) +project(tree-sitter LANGUAGES C) + +file(GLOB SRC_FILES ${PROJECT_SOURCE_DIR}/lib/src/*.c) +foreach(sfile ${SRC_FILES}) + get_filename_component(f ${sfile} NAME) + if(${f} MATCHES "lib.c$") + list(REMOVE_ITEM SRC_FILES ${sfile}) + endif() +endforeach() +include_directories(${PROJECT_SOURCE_DIR}/lib/include) +add_library(tree-sitter ${SRC_FILES}) + +install(FILES + lib/include/tree_sitter/api.h + lib/include/tree_sitter/parser.h + DESTINATION include/tree_sitter) + +include(GNUInstallDirs) +install(TARGETS tree-sitter + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) diff --git a/unicode/CaseFolding.txt b/unicode/CaseFolding.txt index 7eeb915abf..033788b253 100644 --- a/unicode/CaseFolding.txt +++ b/unicode/CaseFolding.txt @@ -1,5 +1,5 @@ -# CaseFolding-12.1.0.txt -# Date: 2019-03-10, 10:53:00 GMT +# CaseFolding-13.0.0.txt +# Date: 2019-09-08, 23:30:59 GMT # ยฉ 2019 Unicodeยฎ, Inc. # Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. # For terms of use, see http://www.unicode.org/terms_of_use.html @@ -1234,6 +1234,9 @@ A7C2; C; A7C3; # LATIN CAPITAL LETTER ANGLICANA W A7C4; C; A794; # LATIN CAPITAL LETTER C WITH PALATAL HOOK A7C5; C; 0282; # LATIN CAPITAL LETTER S WITH HOOK A7C6; C; 1D8E; # LATIN CAPITAL LETTER Z WITH PALATAL HOOK +A7C7; C; A7C8; # LATIN CAPITAL LETTER D WITH SHORT STROKE OVERLAY +A7C9; C; A7CA; # LATIN CAPITAL LETTER S WITH SHORT STROKE OVERLAY +A7F5; C; A7F6; # LATIN CAPITAL LETTER REVERSED HALF H AB70; C; 13A0; # CHEROKEE SMALL LETTER A AB71; C; 13A1; # CHEROKEE SMALL LETTER E AB72; C; 13A2; # CHEROKEE SMALL LETTER I diff --git a/unicode/EastAsianWidth.txt b/unicode/EastAsianWidth.txt index 94d55d6654..b43aec9273 100644 --- a/unicode/EastAsianWidth.txt +++ b/unicode/EastAsianWidth.txt @@ -1,6 +1,6 @@ -# EastAsianWidth-12.1.0.txt -# Date: 2019-03-31, 22:01:58 GMT [KW, LI] -# ยฉ 2019 Unicodeยฎ, Inc. +# EastAsianWidth-13.0.0.txt +# Date: 2029-01-21, 18:14:00 GMT [KW, LI] +# ยฉ 2020 Unicodeยฎ, Inc. # Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. # For terms of use, see http://www.unicode.org/terms_of_use.html # @@ -9,7 +9,7 @@ # # East_Asian_Width Property # -# This file is an informative contributory data file in the +# This file is a normative contributory data file in the # Unicode Character Database. # # The format is two fields separated by a semicolon. @@ -332,7 +332,7 @@ 085E;N # Po MANDAIC PUNCTUATION 0860..086A;N # Lo [11] SYRIAC LETTER MALAYALAM NGA..SYRIAC LETTER MALAYALAM SSA 08A0..08B4;N # Lo [21] ARABIC LETTER BEH WITH SMALL V BELOW..ARABIC LETTER KAF WITH DOT BELOW -08B6..08BD;N # Lo [8] ARABIC LETTER BEH WITH SMALL MEEM ABOVE..ARABIC LETTER AFRICAN NOON +08B6..08C7;N # Lo [18] ARABIC LETTER BEH WITH SMALL MEEM ABOVE..ARABIC LETTER LAM WITH SMALL ARABIC LETTER TAH ABOVE 08D3..08E1;N # Mn [15] ARABIC SMALL LOW WAW..ARABIC SMALL HIGH SIGN SAFHA 08E2;N # Cf ARABIC DISPUTED END OF AYAH 08E3..08FF;N # Mn [29] ARABIC TURNED DAMMA BELOW..ARABIC MARK SIDEWAYS NOON GHUNNA @@ -450,7 +450,7 @@ 0B47..0B48;N # Mc [2] ORIYA VOWEL SIGN E..ORIYA VOWEL SIGN AI 0B4B..0B4C;N # Mc [2] ORIYA VOWEL SIGN O..ORIYA VOWEL SIGN AU 0B4D;N # Mn ORIYA SIGN VIRAMA -0B56;N # Mn ORIYA AI LENGTH MARK +0B55..0B56;N # Mn [2] ORIYA SIGN OVERLINE..ORIYA AI LENGTH MARK 0B57;N # Mc ORIYA AU LENGTH MARK 0B5C..0B5D;N # Lo [2] ORIYA LETTER RRA..ORIYA LETTER RHA 0B5F..0B61;N # Lo [3] ORIYA LETTER YYA..ORIYA LETTER VOCALIC LL @@ -529,7 +529,7 @@ 0CF1..0CF2;N # Lo [2] KANNADA SIGN JIHVAMULIYA..KANNADA SIGN UPADHMANIYA 0D00..0D01;N # Mn [2] MALAYALAM SIGN COMBINING ANUSVARA ABOVE..MALAYALAM SIGN CANDRABINDU 0D02..0D03;N # Mc [2] MALAYALAM SIGN ANUSVARA..MALAYALAM SIGN VISARGA -0D05..0D0C;N # Lo [8] MALAYALAM LETTER A..MALAYALAM LETTER VOCALIC L +0D04..0D0C;N # Lo [9] MALAYALAM LETTER VEDIC ANUSVARA..MALAYALAM LETTER VOCALIC L 0D0E..0D10;N # Lo [3] MALAYALAM LETTER E..MALAYALAM LETTER AI 0D12..0D3A;N # Lo [41] MALAYALAM LETTER O..MALAYALAM LETTER TTTA 0D3B..0D3C;N # Mn [2] MALAYALAM SIGN VERTICAL BAR VIRAMA..MALAYALAM SIGN CIRCULAR VIRAMA @@ -550,6 +550,7 @@ 0D70..0D78;N # No [9] MALAYALAM NUMBER TEN..MALAYALAM FRACTION THREE SIXTEENTHS 0D79;N # So MALAYALAM DATE MARK 0D7A..0D7F;N # Lo [6] MALAYALAM LETTER CHILLU NN..MALAYALAM LETTER CHILLU K +0D81;N # Mn SINHALA SIGN CANDRABINDU 0D82..0D83;N # Mc [2] SINHALA SIGN ANUSVARAYA..SINHALA SIGN VISARGAYA 0D85..0D96;N # Lo [18] SINHALA LETTER AYANNA..SINHALA LETTER AUYANNA 0D9A..0DB1;N # Lo [24] SINHALA LETTER ALPAPRAANA KAYANNA..SINHALA LETTER DANTAJA NAYANNA @@ -795,6 +796,7 @@ 1AA8..1AAD;N # Po [6] TAI THAM SIGN KAAN..TAI THAM SIGN CAANG 1AB0..1ABD;N # Mn [14] COMBINING DOUBLED CIRCUMFLEX ACCENT..COMBINING PARENTHESES BELOW 1ABE;N # Me COMBINING PARENTHESES OVERLAY +1ABF..1AC0;N # Mn [2] COMBINING LATIN SMALL LETTER W BELOW..COMBINING LATIN SMALL LETTER TURNED W BELOW 1B00..1B03;N # Mn [4] BALINESE SIGN ULU RICEM..BALINESE SIGN SURANG 1B04;N # Mc BALINESE SIGN BISAH 1B05..1B33;N # Lo [47] BALINESE LETTER AKARA..BALINESE LETTER HA @@ -1335,7 +1337,7 @@ 2B56..2B59;A # So [4] HEAVY OVAL WITH OVAL INSIDE..HEAVY CIRCLED SALTIRE 2B5A..2B73;N # So [26] SLANTED NORTH ARROW WITH HOOKED HEAD..DOWNWARDS TRIANGLE-HEADED ARROW TO BAR 2B76..2B95;N # So [32] NORTH WEST TRIANGLE-HEADED ARROW TO BAR..RIGHTWARDS BLACK ARROW -2B98..2BFF;N # So [104] THREE-D TOP-LIGHTED LEFTWARDS EQUILATERAL ARROWHEAD..HELLSCHREIBER PAUSE SYMBOL +2B97..2BFF;N # So [105] SYMBOL FOR TYPE A ELECTRONICS..HELLSCHREIBER PAUSE SYMBOL 2C00..2C2E;N # Lu [47] GLAGOLITIC CAPITAL LETTER AZU..GLAGOLITIC CAPITAL LETTER LATINATE MYSLITE 2C30..2C5E;N # Ll [47] GLAGOLITIC SMALL LETTER AZU..GLAGOLITIC SMALL LETTER LATINATE MYSLITE 2C60..2C7B;N # L& [28] LATIN CAPITAL LETTER L WITH DOUBLE BAR..LATIN LETTER SMALL CAPITAL TURNED E @@ -1404,6 +1406,8 @@ 2E41;N # Po REVERSED COMMA 2E42;N # Ps DOUBLE LOW-REVERSED-9 QUOTATION MARK 2E43..2E4F;N # Po [13] DASH WITH LEFT UPTURN..CORNISH VERSE DIVIDER +2E50..2E51;N # So [2] CROSS PATTY WITH RIGHT CROSSBAR..CROSS PATTY WITH LEFT CROSSBAR +2E52;N # Po TIRONIAN SIGN CAPITAL ET 2E80..2E99;W # So [26] CJK RADICAL REPEAT..CJK RADICAL RAP 2E9B..2EF3;W # So [89] CJK RADICAL CHOKE..CJK RADICAL C-SIMPLIFIED TURTLE 2F00..2FD5;W # So [214] KANGXI RADICAL ONE..KANGXI RADICAL FLUTE @@ -1464,7 +1468,7 @@ 3190..3191;W # So [2] IDEOGRAPHIC ANNOTATION LINKING MARK..IDEOGRAPHIC ANNOTATION REVERSE MARK 3192..3195;W # No [4] IDEOGRAPHIC ANNOTATION ONE MARK..IDEOGRAPHIC ANNOTATION FOUR MARK 3196..319F;W # So [10] IDEOGRAPHIC ANNOTATION TOP MARK..IDEOGRAPHIC ANNOTATION MAN MARK -31A0..31BA;W # Lo [27] BOPOMOFO LETTER BU..BOPOMOFO LETTER ZY +31A0..31BF;W # Lo [32] BOPOMOFO LETTER BU..BOPOMOFO LETTER AH 31C0..31E3;W # So [36] CJK STROKE T..CJK STROKE Q 31F0..31FF;W # Lo [16] KATAKANA LETTER SMALL KU..KATAKANA LETTER SMALL RO 3200..321E;W # So [31] PARENTHESIZED HANGUL KIYEOK..PARENTHESIZED KOREAN CHARACTER O HU @@ -1479,11 +1483,10 @@ 32B1..32BF;W # No [15] CIRCLED NUMBER THIRTY SIX..CIRCLED NUMBER FIFTY 32C0..32FF;W # So [64] IDEOGRAPHIC TELEGRAPH SYMBOL FOR JANUARY..SQUARE ERA NAME REIWA 3300..33FF;W # So [256] SQUARE APAATO..SQUARE GAL -3400..4DB5;W # Lo [6582] CJK UNIFIED IDEOGRAPH-3400..CJK UNIFIED IDEOGRAPH-4DB5 -4DB6..4DBF;W # Cn [10] <reserved-4DB6>..<reserved-4DBF> +3400..4DBF;W # Lo [6592] CJK UNIFIED IDEOGRAPH-3400..CJK UNIFIED IDEOGRAPH-4DBF 4DC0..4DFF;N # So [64] HEXAGRAM FOR THE CREATIVE HEAVEN..HEXAGRAM FOR BEFORE COMPLETION -4E00..9FEF;W # Lo [20976] CJK UNIFIED IDEOGRAPH-4E00..CJK UNIFIED IDEOGRAPH-9FEF -9FF0..9FFF;W # Cn [16] <reserved-9FF0>..<reserved-9FFF> +4E00..9FFC;W # Lo [20989] CJK UNIFIED IDEOGRAPH-4E00..CJK UNIFIED IDEOGRAPH-9FFC +9FFD..9FFF;W # Cn [3] <reserved-9FFD>..<reserved-9FFF> A000..A014;W # Lo [21] YI SYLLABLE IT..YI SYLLABLE E A015;W # Lm YI SYLLABLE WU A016..A48C;W # Lo [1143] YI SYLLABLE BIT..YI SYLLABLE YYR @@ -1523,7 +1526,8 @@ A789..A78A;N # Sk [2] MODIFIER LETTER COLON..MODIFIER LETTER SHORT EQUAL A78B..A78E;N # L& [4] LATIN CAPITAL LETTER SALTILLO..LATIN SMALL LETTER L WITH RETROFLEX HOOK AND BELT A78F;N # Lo LATIN LETTER SINOLOGICAL DOT A790..A7BF;N # L& [48] LATIN CAPITAL LETTER N WITH DESCENDER..LATIN SMALL LETTER GLOTTAL U -A7C2..A7C6;N # L& [5] LATIN CAPITAL LETTER ANGLICANA W..LATIN CAPITAL LETTER Z WITH PALATAL HOOK +A7C2..A7CA;N # L& [9] LATIN CAPITAL LETTER ANGLICANA W..LATIN SMALL LETTER S WITH SHORT STROKE OVERLAY +A7F5..A7F6;N # L& [2] LATIN CAPITAL LETTER REVERSED HALF H..LATIN SMALL LETTER REVERSED HALF H A7F7;N # Lo LATIN EPIGRAPHIC LETTER SIDEWAYS I A7F8..A7F9;N # Lm [2] MODIFIER LETTER CAPITAL H WITH STROKE..MODIFIER LETTER SMALL LIGATURE OE A7FA;N # Ll LATIN LETTER SMALL CAPITAL TURNED M @@ -1539,6 +1543,7 @@ A823..A824;N # Mc [2] SYLOTI NAGRI VOWEL SIGN A..SYLOTI NAGRI VOWEL SIGN A825..A826;N # Mn [2] SYLOTI NAGRI VOWEL SIGN U..SYLOTI NAGRI VOWEL SIGN E A827;N # Mc SYLOTI NAGRI VOWEL SIGN OO A828..A82B;N # So [4] SYLOTI NAGRI POETRY MARK-1..SYLOTI NAGRI POETRY MARK-4 +A82C;N # Mn SYLOTI NAGRI SIGN ALTERNATE HASANTA A830..A835;N # No [6] NORTH INDIC FRACTION ONE QUARTER..NORTH INDIC FRACTION THREE SIXTEENTHS A836..A837;N # So [2] NORTH INDIC QUARTER MARK..NORTH INDIC PLACEHOLDER MARK A838;N # Sc NORTH INDIC RUPEE MARK @@ -1639,7 +1644,9 @@ AB28..AB2E;N # Lo [7] ETHIOPIC SYLLABLE BBA..ETHIOPIC SYLLABLE BBO AB30..AB5A;N # Ll [43] LATIN SMALL LETTER BARRED ALPHA..LATIN SMALL LETTER Y WITH SHORT RIGHT LEG AB5B;N # Sk MODIFIER BREVE WITH INVERTED BREVE AB5C..AB5F;N # Lm [4] MODIFIER LETTER SMALL HENG..MODIFIER LETTER SMALL U WITH LEFT HOOK -AB60..AB67;N # Ll [8] LATIN SMALL LETTER SAKHA YAT..LATIN SMALL LETTER TS DIGRAPH WITH RETROFLEX HOOK +AB60..AB68;N # Ll [9] LATIN SMALL LETTER SAKHA YAT..LATIN SMALL LETTER TURNED R WITH MIDDLE TILDE +AB69;N # Lm MODIFIER LETTER SMALL TURNED W +AB6A..AB6B;N # Sk [2] MODIFIER LETTER LEFT TACK..MODIFIER LETTER RIGHT TACK AB70..ABBF;N # Ll [80] CHEROKEE SMALL LETTER A..CHEROKEE SMALL LETTER YA ABC0..ABE2;N # Lo [35] MEETEI MAYEK LETTER KOK..MEETEI MAYEK LETTER I LONSUM ABE3..ABE4;N # Mc [2] MEETEI MAYEK VOWEL SIGN ONAP..MEETEI MAYEK VOWEL SIGN INAP @@ -1800,7 +1807,7 @@ FFFD;A # So REPLACEMENT CHARACTER 10179..10189;N # So [17] GREEK YEAR SIGN..GREEK TRYBLION BASE SIGN 1018A..1018B;N # No [2] GREEK ZERO SIGN..GREEK ONE QUARTER SIGN 1018C..1018E;N # So [3] GREEK SINUSOID SIGN..NOMISMA SIGN -10190..1019B;N # So [12] ROMAN SEXTANS SIGN..ROMAN CENTURIAL SIGN +10190..1019C;N # So [13] ROMAN SEXTANS SIGN..ASCIA SYMBOL 101A0;N # So GREEK SYMBOL TAU RHO 101D0..101FC;N # So [45] PHAISTOS DISC SIGN PEDESTRIAN..PHAISTOS DISC SIGN WAVY BAND 101FD;N # Mn PHAISTOS DISC SIGN COMBINING OBLIQUE STROKE @@ -1902,6 +1909,10 @@ FFFD;A # So REPLACEMENT CHARACTER 10D24..10D27;N # Mn [4] HANIFI ROHINGYA SIGN HARBAHAY..HANIFI ROHINGYA SIGN TASSI 10D30..10D39;N # Nd [10] HANIFI ROHINGYA DIGIT ZERO..HANIFI ROHINGYA DIGIT NINE 10E60..10E7E;N # No [31] RUMI DIGIT ONE..RUMI FRACTION TWO THIRDS +10E80..10EA9;N # Lo [42] YEZIDI LETTER ELIF..YEZIDI LETTER ET +10EAB..10EAC;N # Mn [2] YEZIDI COMBINING HAMZA MARK..YEZIDI COMBINING MADDA MARK +10EAD;N # Pd YEZIDI HYPHENATION MARK +10EB0..10EB1;N # Lo [2] YEZIDI LETTER LAM WITH DOT ABOVE..YEZIDI LETTER YOT WITH CIRCUMFLEX ABOVE 10F00..10F1C;N # Lo [29] OLD SOGDIAN LETTER ALEPH..OLD SOGDIAN LETTER FINAL TAW WITH VERTICAL TAIL 10F1D..10F26;N # No [10] OLD SOGDIAN NUMBER ONE..OLD SOGDIAN FRACTION ONE HALF 10F27;N # Lo OLD SOGDIAN LIGATURE AYIN-DALETH @@ -1909,6 +1920,8 @@ FFFD;A # So REPLACEMENT CHARACTER 10F46..10F50;N # Mn [11] SOGDIAN COMBINING DOT BELOW..SOGDIAN COMBINING STROKE BELOW 10F51..10F54;N # No [4] SOGDIAN NUMBER ONE..SOGDIAN NUMBER ONE HUNDRED 10F55..10F59;N # Po [5] SOGDIAN PUNCTUATION TWO VERTICAL BARS..SOGDIAN PUNCTUATION HALF CIRCLE WITH DOT +10FB0..10FC4;N # Lo [21] CHORASMIAN LETTER ALEPH..CHORASMIAN LETTER TAW +10FC5..10FCB;N # No [7] CHORASMIAN NUMBER ONE..CHORASMIAN NUMBER ONE HUNDRED 10FE0..10FF6;N # Lo [23] ELYMAIC LETTER ALEPH..ELYMAIC LIGATURE ZAYIN-YODH 11000;N # Mc BRAHMI SIGN CANDRABINDU 11001;N # Mn BRAHMI SIGN ANUSVARA @@ -1941,6 +1954,7 @@ FFFD;A # So REPLACEMENT CHARACTER 11140..11143;N # Po [4] CHAKMA SECTION MARK..CHAKMA QUESTION MARK 11144;N # Lo CHAKMA LETTER LHAA 11145..11146;N # Mc [2] CHAKMA VOWEL SIGN AA..CHAKMA VOWEL SIGN EI +11147;N # Lo CHAKMA LETTER VAA 11150..11172;N # Lo [35] MAHAJANI LETTER A..MAHAJANI LETTER RRA 11173;N # Mn MAHAJANI SIGN NUKTA 11174..11175;N # Po [2] MAHAJANI ABBREVIATION SIGN..MAHAJANI SECTION MARK @@ -1955,6 +1969,8 @@ FFFD;A # So REPLACEMENT CHARACTER 111C5..111C8;N # Po [4] SHARADA DANDA..SHARADA SEPARATOR 111C9..111CC;N # Mn [4] SHARADA SANDHI MARK..SHARADA EXTRA SHORT VOWEL MARK 111CD;N # Po SHARADA SUTRA MARK +111CE;N # Mc SHARADA VOWEL SIGN PRISHTHAMATRA E +111CF;N # Mn SHARADA SIGN INVERTED CANDRABINDU 111D0..111D9;N # Nd [10] SHARADA DIGIT ZERO..SHARADA DIGIT NINE 111DA;N # Lo SHARADA EKAM 111DB;N # Po SHARADA SIGN SIDDHAM @@ -2013,10 +2029,10 @@ FFFD;A # So REPLACEMENT CHARACTER 11447..1144A;N # Lo [4] NEWA SIGN AVAGRAHA..NEWA SIDDHI 1144B..1144F;N # Po [5] NEWA DANDA..NEWA ABBREVIATION SIGN 11450..11459;N # Nd [10] NEWA DIGIT ZERO..NEWA DIGIT NINE -1145B;N # Po NEWA PLACEHOLDER MARK +1145A..1145B;N # Po [2] NEWA DOUBLE COMMA..NEWA PLACEHOLDER MARK 1145D;N # Po NEWA INSERTION SIGN 1145E;N # Mn NEWA SANDHI MARK -1145F;N # Lo NEWA LETTER VEDIC ANUSVARA +1145F..11461;N # Lo [3] NEWA LETTER VEDIC ANUSVARA..NEWA SIGN UPADHMANIYA 11480..114AF;N # Lo [48] TIRHUTA ANJI..TIRHUTA LETTER HA 114B0..114B2;N # Mc [3] TIRHUTA VOWEL SIGN AA..TIRHUTA VOWEL SIGN II 114B3..114B8;N # Mn [6] TIRHUTA VOWEL SIGN U..TIRHUTA VOWEL SIGN VOCALIC LL @@ -2081,6 +2097,23 @@ FFFD;A # So REPLACEMENT CHARACTER 118E0..118E9;N # Nd [10] WARANG CITI DIGIT ZERO..WARANG CITI DIGIT NINE 118EA..118F2;N # No [9] WARANG CITI NUMBER TEN..WARANG CITI NUMBER NINETY 118FF;N # Lo WARANG CITI OM +11900..11906;N # Lo [7] DIVES AKURU LETTER A..DIVES AKURU LETTER E +11909;N # Lo DIVES AKURU LETTER O +1190C..11913;N # Lo [8] DIVES AKURU LETTER KA..DIVES AKURU LETTER JA +11915..11916;N # Lo [2] DIVES AKURU LETTER NYA..DIVES AKURU LETTER TTA +11918..1192F;N # Lo [24] DIVES AKURU LETTER DDA..DIVES AKURU LETTER ZA +11930..11935;N # Mc [6] DIVES AKURU VOWEL SIGN AA..DIVES AKURU VOWEL SIGN E +11937..11938;N # Mc [2] DIVES AKURU VOWEL SIGN AI..DIVES AKURU VOWEL SIGN O +1193B..1193C;N # Mn [2] DIVES AKURU SIGN ANUSVARA..DIVES AKURU SIGN CANDRABINDU +1193D;N # Mc DIVES AKURU SIGN HALANTA +1193E;N # Mn DIVES AKURU VIRAMA +1193F;N # Lo DIVES AKURU PREFIXED NASAL SIGN +11940;N # Mc DIVES AKURU MEDIAL YA +11941;N # Lo DIVES AKURU INITIAL RA +11942;N # Mc DIVES AKURU MEDIAL RA +11943;N # Mn DIVES AKURU SIGN NUKTA +11944..11946;N # Po [3] DIVES AKURU DOUBLE DANDA..DIVES AKURU END OF TEXT MARK +11950..11959;N # Nd [10] DIVES AKURU DIGIT ZERO..DIVES AKURU DIGIT NINE 119A0..119A7;N # Lo [8] NANDINAGARI LETTER A..NANDINAGARI LETTER VOCALIC RR 119AA..119D0;N # Lo [39] NANDINAGARI LETTER E..NANDINAGARI LETTER RRA 119D1..119D3;N # Mc [3] NANDINAGARI VOWEL SIGN AA..NANDINAGARI VOWEL SIGN II @@ -2158,6 +2191,7 @@ FFFD;A # So REPLACEMENT CHARACTER 11EF3..11EF4;N # Mn [2] MAKASAR VOWEL SIGN I..MAKASAR VOWEL SIGN U 11EF5..11EF6;N # Mc [2] MAKASAR VOWEL SIGN E..MAKASAR VOWEL SIGN O 11EF7..11EF8;N # Po [2] MAKASAR PASSIMBANG..MAKASAR END OF SECTION +11FB0;N # Lo LISU LETTER YHA 11FC0..11FD4;N # No [21] TAMIL FRACTION ONE THREE-HUNDRED-AND-TWENTIETH..TAMIL FRACTION DOWNSCALING FACTOR KIIZH 11FD5..11FDC;N # So [8] TAMIL SIGN NEL..TAMIL SIGN MUKKURUNI 11FDD..11FE0;N # Sc [4] TAMIL SIGN KAACU..TAMIL SIGN VARAAKAN @@ -2200,8 +2234,12 @@ FFFD;A # So REPLACEMENT CHARACTER 16FE0..16FE1;W # Lm [2] TANGUT ITERATION MARK..NUSHU ITERATION MARK 16FE2;W # Po OLD CHINESE HOOK MARK 16FE3;W # Lm OLD CHINESE ITERATION MARK +16FE4;W # Mn KHITAN SMALL SCRIPT FILLER +16FF0..16FF1;W # Mc [2] VIETNAMESE ALTERNATE READING MARK CA..VIETNAMESE ALTERNATE READING MARK NHAY 17000..187F7;W # Lo [6136] TANGUT IDEOGRAPH-17000..TANGUT IDEOGRAPH-187F7 -18800..18AF2;W # Lo [755] TANGUT COMPONENT-001..TANGUT COMPONENT-755 +18800..18AFF;W # Lo [768] TANGUT COMPONENT-001..TANGUT COMPONENT-768 +18B00..18CD5;W # Lo [470] KHITAN SMALL SCRIPT CHARACTER-18B00..KHITAN SMALL SCRIPT CHARACTER-18CD5 +18D00..18D08;W # Lo [9] TANGUT IDEOGRAPH-18D00..TANGUT IDEOGRAPH-18D08 1B000..1B0FF;W # Lo [256] KATAKANA LETTER ARCHAIC E..HENTAIGANA LETTER RE-2 1B100..1B11E;W # Lo [31] HENTAIGANA LETTER RE-3..HENTAIGANA LETTER N-MU-MO-2 1B150..1B152;W # Lo [3] HIRAGANA LETTER SMALL WI..HIRAGANA LETTER SMALL WO @@ -2364,15 +2402,17 @@ FFFD;A # So REPLACEMENT CHARACTER 1F0D1..1F0F5;N # So [37] PLAYING CARD ACE OF CLUBS..PLAYING CARD TRUMP-21 1F100..1F10A;A # No [11] DIGIT ZERO FULL STOP..DIGIT NINE COMMA 1F10B..1F10C;N # No [2] DINGBAT CIRCLED SANS-SERIF DIGIT ZERO..DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT ZERO +1F10D..1F10F;N # So [3] CIRCLED ZERO WITH SLASH..CIRCLED DOLLAR SIGN WITH OVERLAID BACKSLASH 1F110..1F12D;A # So [30] PARENTHESIZED LATIN CAPITAL LETTER A..CIRCLED CD 1F12E..1F12F;N # So [2] CIRCLED WZ..COPYLEFT SYMBOL 1F130..1F169;A # So [58] SQUARED LATIN CAPITAL LETTER A..NEGATIVE CIRCLED LATIN CAPITAL LETTER Z -1F16A..1F16C;N # So [3] RAISED MC SIGN..RAISED MR SIGN +1F16A..1F16F;N # So [6] RAISED MC SIGN..CIRCLED HUMAN FIGURE 1F170..1F18D;A # So [30] NEGATIVE SQUARED LATIN CAPITAL LETTER A..NEGATIVE SQUARED SA 1F18E;W # So NEGATIVE SQUARED AB 1F18F..1F190;A # So [2] NEGATIVE SQUARED WC..SQUARE DJ 1F191..1F19A;W # So [10] SQUARED CL..SQUARED VS 1F19B..1F1AC;A # So [18] SQUARED THREE D..SQUARED VOD +1F1AD;N # So MASK WORK SYMBOL 1F1E6..1F1FF;N # So [26] REGIONAL INDICATOR SYMBOL LETTER A..REGIONAL INDICATOR SYMBOL LETTER Z 1F200..1F202;W # So [3] SQUARE HIRAGANA HOKA..SQUARED KATAKANA SA 1F210..1F23B;W # So [44] SQUARED CJK UNIFIED IDEOGRAPH-624B..SQUARED CJK UNIFIED IDEOGRAPH-914D @@ -2424,11 +2464,11 @@ FFFD;A # So REPLACEMENT CHARACTER 1F6CD..1F6CF;N # So [3] SHOPPING BAGS..BED 1F6D0..1F6D2;W # So [3] PLACE OF WORSHIP..SHOPPING TROLLEY 1F6D3..1F6D4;N # So [2] STUPA..PAGODA -1F6D5;W # So HINDU TEMPLE +1F6D5..1F6D7;W # So [3] HINDU TEMPLE..ELEVATOR 1F6E0..1F6EA;N # So [11] HAMMER AND WRENCH..NORTHEAST-POINTING AIRPLANE 1F6EB..1F6EC;W # So [2] AIRPLANE DEPARTURE..AIRPLANE ARRIVING 1F6F0..1F6F3;N # So [4] SATELLITE..PASSENGER SHIP -1F6F4..1F6FA;W # So [7] SCOOTER..AUTO RICKSHAW +1F6F4..1F6FC;W # So [9] SCOOTER..ROLLER SKATE 1F700..1F773;N # So [116] ALCHEMICAL SYMBOL FOR QUINTESSENCE..ALCHEMICAL SYMBOL FOR HALF OUNCE 1F780..1F7D8;N # So [89] BLACK LEFT-POINTING ISOSCELES RIGHT TRIANGLE..NEGATIVE CIRCLED SQUARE 1F7E0..1F7EB;W # So [12] LARGE ORANGE CIRCLE..LARGE BROWN SQUARE @@ -2437,21 +2477,29 @@ FFFD;A # So REPLACEMENT CHARACTER 1F850..1F859;N # So [10] LEFTWARDS SANS-SERIF ARROW..UP DOWN SANS-SERIF ARROW 1F860..1F887;N # So [40] WIDE-HEADED LEFTWARDS LIGHT BARB ARROW..WIDE-HEADED SOUTH WEST VERY HEAVY BARB ARROW 1F890..1F8AD;N # So [30] LEFTWARDS TRIANGLE ARROWHEAD..WHITE ARROW SHAFT WIDTH TWO THIRDS +1F8B0..1F8B1;N # So [2] ARROW POINTING UPWARDS THEN NORTH WEST..ARROW POINTING RIGHTWARDS THEN CURVING SOUTH WEST 1F900..1F90B;N # So [12] CIRCLED CROSS FORMEE WITH FOUR DOTS..DOWNWARD FACING NOTCHED HOOK WITH DOT -1F90D..1F971;W # So [101] WHITE HEART..YAWNING FACE -1F973..1F976;W # So [4] FACE WITH PARTY HORN AND PARTY HAT..FREEZING FACE -1F97A..1F9A2;W # So [41] FACE WITH PLEADING EYES..SWAN -1F9A5..1F9AA;W # So [6] SLOTH..OYSTER -1F9AE..1F9CA;W # So [29] GUIDE DOG..ICE CUBE +1F90C..1F93A;W # So [47] PINCHED FINGERS..FENCER +1F93B;N # So MODERN PENTATHLON +1F93C..1F945;W # So [10] WRESTLERS..GOAL NET +1F946;N # So RIFLE +1F947..1F978;W # So [50] FIRST PLACE MEDAL..DISGUISED FACE +1F97A..1F9CB;W # So [82] FACE WITH PLEADING EYES..BUBBLE TEA 1F9CD..1F9FF;W # So [51] STANDING PERSON..NAZAR AMULET 1FA00..1FA53;N # So [84] NEUTRAL CHESS KING..BLACK CHESS KNIGHT-BISHOP 1FA60..1FA6D;N # So [14] XIANGQI RED GENERAL..XIANGQI BLACK SOLDIER -1FA70..1FA73;W # So [4] BALLET SHOES..SHORTS +1FA70..1FA74;W # So [5] BALLET SHOES..THONG SANDAL 1FA78..1FA7A;W # So [3] DROP OF BLOOD..STETHOSCOPE -1FA80..1FA82;W # So [3] YO-YO..PARACHUTE -1FA90..1FA95;W # So [6] RINGED PLANET..BANJO -20000..2A6D6;W # Lo [42711] CJK UNIFIED IDEOGRAPH-20000..CJK UNIFIED IDEOGRAPH-2A6D6 -2A6D7..2A6FF;W # Cn [41] <reserved-2A6D7>..<reserved-2A6FF> +1FA80..1FA86;W # So [7] YO-YO..NESTING DOLLS +1FA90..1FAA8;W # So [25] RINGED PLANET..ROCK +1FAB0..1FAB6;W # So [7] FLY..FEATHER +1FAC0..1FAC2;W # So [3] ANATOMICAL HEART..PEOPLE HUGGING +1FAD0..1FAD6;W # So [7] BLUEBERRIES..TEAPOT +1FB00..1FB92;N # So [147] BLOCK SEXTANT-1..UPPER HALF INVERSE MEDIUM SHADE AND LOWER HALF BLOCK +1FB94..1FBCA;N # So [55] LEFT HALF INVERSE MEDIUM SHADE AND RIGHT HALF BLOCK..WHITE UP-POINTING CHEVRON +1FBF0..1FBF9;N # Nd [10] SEGMENTED DIGIT ZERO..SEGMENTED DIGIT NINE +20000..2A6DD;W # Lo [42718] CJK UNIFIED IDEOGRAPH-20000..CJK UNIFIED IDEOGRAPH-2A6DD +2A6DE..2A6FF;W # Cn [34] <reserved-2A6DE>..<reserved-2A6FF> 2A700..2B734;W # Lo [4149] CJK UNIFIED IDEOGRAPH-2A700..CJK UNIFIED IDEOGRAPH-2B734 2B735..2B73F;W # Cn [11] <reserved-2B735>..<reserved-2B73F> 2B740..2B81D;W # Lo [222] CJK UNIFIED IDEOGRAPH-2B740..CJK UNIFIED IDEOGRAPH-2B81D @@ -2463,7 +2511,8 @@ FFFD;A # So REPLACEMENT CHARACTER 2F800..2FA1D;W # Lo [542] CJK COMPATIBILITY IDEOGRAPH-2F800..CJK COMPATIBILITY IDEOGRAPH-2FA1D 2FA1E..2FA1F;W # Cn [2] <reserved-2FA1E>..<reserved-2FA1F> 2FA20..2FFFD;W # Cn [1502] <reserved-2FA20>..<reserved-2FFFD> -30000..3FFFD;W # Cn [65534] <reserved-30000>..<reserved-3FFFD> +30000..3134A;W # Lo [4939] CJK UNIFIED IDEOGRAPH-30000..CJK UNIFIED IDEOGRAPH-3134A +3134B..3FFFD;W # Cn [60595] <reserved-3134B>..<reserved-3FFFD> E0001;N # Cf LANGUAGE TAG E0020..E007F;N # Cf [96] TAG SPACE..CANCEL TAG E0100..E01EF;A # Mn [240] VARIATION SELECTOR-17..VARIATION SELECTOR-256 diff --git a/unicode/UnicodeData.txt b/unicode/UnicodeData.txt index e65aec52f7..e22f967bba 100644 --- a/unicode/UnicodeData.txt +++ b/unicode/UnicodeData.txt @@ -2118,6 +2118,16 @@ 08BB;ARABIC LETTER AFRICAN FEH;Lo;0;AL;;;;;N;;;;; 08BC;ARABIC LETTER AFRICAN QAF;Lo;0;AL;;;;;N;;;;; 08BD;ARABIC LETTER AFRICAN NOON;Lo;0;AL;;;;;N;;;;; +08BE;ARABIC LETTER PEH WITH SMALL V;Lo;0;AL;;;;;N;;;;; +08BF;ARABIC LETTER TEH WITH SMALL V;Lo;0;AL;;;;;N;;;;; +08C0;ARABIC LETTER TTEH WITH SMALL V;Lo;0;AL;;;;;N;;;;; +08C1;ARABIC LETTER TCHEH WITH SMALL V;Lo;0;AL;;;;;N;;;;; +08C2;ARABIC LETTER KEHEH WITH SMALL V;Lo;0;AL;;;;;N;;;;; +08C3;ARABIC LETTER GHAIN WITH THREE DOTS ABOVE;Lo;0;AL;;;;;N;;;;; +08C4;ARABIC LETTER AFRICAN QAF WITH THREE DOTS ABOVE;Lo;0;AL;;;;;N;;;;; +08C5;ARABIC LETTER JEEM WITH THREE DOTS ABOVE;Lo;0;AL;;;;;N;;;;; +08C6;ARABIC LETTER JEEM WITH THREE DOTS BELOW;Lo;0;AL;;;;;N;;;;; +08C7;ARABIC LETTER LAM WITH SMALL ARABIC LETTER TAH ABOVE;Lo;0;AL;;;;;N;;;;; 08D3;ARABIC SMALL LOW WAW;Mn;220;NSM;;;;;N;;;;; 08D4;ARABIC SMALL HIGH WORD AR-RUB;Mn;230;NSM;;;;;N;;;;; 08D5;ARABIC SMALL HIGH SAD;Mn;230;NSM;;;;;N;;;;; @@ -2621,6 +2631,7 @@ 0B4B;ORIYA VOWEL SIGN O;Mc;0;L;0B47 0B3E;;;;N;;;;; 0B4C;ORIYA VOWEL SIGN AU;Mc;0;L;0B47 0B57;;;;N;;;;; 0B4D;ORIYA SIGN VIRAMA;Mn;9;NSM;;;;;N;;;;; +0B55;ORIYA SIGN OVERLINE;Mn;0;NSM;;;;;N;;;;; 0B56;ORIYA AI LENGTH MARK;Mn;0;NSM;;;;;N;;;;; 0B57;ORIYA AU LENGTH MARK;Mc;0;L;;;;;N;;;;; 0B5C;ORIYA LETTER RRA;Lo;0;L;0B21 0B3C;;;;N;;;;; @@ -2911,6 +2922,7 @@ 0D01;MALAYALAM SIGN CANDRABINDU;Mn;0;NSM;;;;;N;;;;; 0D02;MALAYALAM SIGN ANUSVARA;Mc;0;L;;;;;N;;;;; 0D03;MALAYALAM SIGN VISARGA;Mc;0;L;;;;;N;;;;; +0D04;MALAYALAM LETTER VEDIC ANUSVARA;Lo;0;L;;;;;N;;;;; 0D05;MALAYALAM LETTER A;Lo;0;L;;;;;N;;;;; 0D06;MALAYALAM LETTER AA;Lo;0;L;;;;;N;;;;; 0D07;MALAYALAM LETTER I;Lo;0;L;;;;;N;;;;; @@ -3024,6 +3036,7 @@ 0D7D;MALAYALAM LETTER CHILLU L;Lo;0;L;;;;;N;;;;; 0D7E;MALAYALAM LETTER CHILLU LL;Lo;0;L;;;;;N;;;;; 0D7F;MALAYALAM LETTER CHILLU K;Lo;0;L;;;;;N;;;;; +0D81;SINHALA SIGN CANDRABINDU;Mn;0;NSM;;;;;N;;;;; 0D82;SINHALA SIGN ANUSVARAYA;Mc;0;L;;;;;N;;;;; 0D83;SINHALA SIGN VISARGAYA;Mc;0;L;;;;;N;;;;; 0D85;SINHALA LETTER AYANNA;Lo;0;L;;;;;N;;;;; @@ -6044,6 +6057,8 @@ 1ABC;COMBINING DOUBLE PARENTHESES ABOVE;Mn;230;NSM;;;;;N;;;;; 1ABD;COMBINING PARENTHESES BELOW;Mn;220;NSM;;;;;N;;;;; 1ABE;COMBINING PARENTHESES OVERLAY;Me;0;NSM;;;;;N;;;;; +1ABF;COMBINING LATIN SMALL LETTER W BELOW;Mn;220;NSM;;;;;N;;;;; +1AC0;COMBINING LATIN SMALL LETTER TURNED W BELOW;Mn;220;NSM;;;;;N;;;;; 1B00;BALINESE SIGN ULU RICEM;Mn;0;NSM;;;;;N;;;;; 1B01;BALINESE SIGN ULU CANDRA;Mn;0;NSM;;;;;N;;;;; 1B02;BALINESE SIGN CECEK;Mn;0;NSM;;;;;N;;;;; @@ -10133,6 +10148,7 @@ 2B93;NEWLINE RIGHT;So;0;ON;;;;;N;;;;; 2B94;FOUR CORNER ARROWS CIRCLING ANTICLOCKWISE;So;0;ON;;;;;N;;;;; 2B95;RIGHTWARDS BLACK ARROW;So;0;ON;;;;;N;;;;; +2B97;SYMBOL FOR TYPE A ELECTRONICS;So;0;ON;;;;;N;;;;; 2B98;THREE-D TOP-LIGHTED LEFTWARDS EQUILATERAL ARROWHEAD;So;0;ON;;;;;N;;;;; 2B99;THREE-D RIGHT-LIGHTED UPWARDS EQUILATERAL ARROWHEAD;So;0;ON;;;;;N;;;;; 2B9A;THREE-D TOP-LIGHTED RIGHTWARDS EQUILATERAL ARROWHEAD;So;0;ON;;;;;N;;;;; @@ -10776,6 +10792,9 @@ 2E4D;PARAGRAPHUS MARK;Po;0;ON;;;;;N;;;;; 2E4E;PUNCTUS ELEVATUS MARK;Po;0;ON;;;;;N;;;;; 2E4F;CORNISH VERSE DIVIDER;Po;0;ON;;;;;N;;;;; +2E50;CROSS PATTY WITH RIGHT CROSSBAR;So;0;ON;;;;;N;;;;; +2E51;CROSS PATTY WITH LEFT CROSSBAR;So;0;ON;;;;;N;;;;; +2E52;TIRONIAN SIGN CAPITAL ET;Po;0;ON;;;;;N;;;;; 2E80;CJK RADICAL REPEAT;So;0;ON;;;;;N;;;;; 2E81;CJK RADICAL CLIFF;So;0;ON;;;;;N;;;;; 2E82;CJK RADICAL SECOND ONE;So;0;ON;;;;;N;;;;; @@ -11550,6 +11569,11 @@ 31B8;BOPOMOFO LETTER GH;Lo;0;L;;;;;N;;;;; 31B9;BOPOMOFO LETTER LH;Lo;0;L;;;;;N;;;;; 31BA;BOPOMOFO LETTER ZY;Lo;0;L;;;;;N;;;;; +31BB;BOPOMOFO FINAL LETTER G;Lo;0;L;;;;;N;;;;; +31BC;BOPOMOFO LETTER GW;Lo;0;L;;;;;N;;;;; +31BD;BOPOMOFO LETTER KW;Lo;0;L;;;;;N;;;;; +31BE;BOPOMOFO LETTER OE;Lo;0;L;;;;;N;;;;; +31BF;BOPOMOFO LETTER AH;Lo;0;L;;;;;N;;;;; 31C0;CJK STROKE T;So;0;ON;;;;;N;;;;; 31C1;CJK STROKE WG;So;0;ON;;;;;N;;;;; 31C2;CJK STROKE XG;So;0;ON;;;;;N;;;;; @@ -12114,7 +12138,7 @@ 33FE;IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY THIRTY-ONE;So;0;L;<compat> 0033 0031 65E5;;;;N;;;;; 33FF;SQUARE GAL;So;0;ON;<square> 0067 0061 006C;;;;N;;;;; 3400;<CJK Ideograph Extension A, First>;Lo;0;L;;;;;N;;;;; -4DB5;<CJK Ideograph Extension A, Last>;Lo;0;L;;;;;N;;;;; +4DBF;<CJK Ideograph Extension A, Last>;Lo;0;L;;;;;N;;;;; 4DC0;HEXAGRAM FOR THE CREATIVE HEAVEN;So;0;ON;;;;;N;;;;; 4DC1;HEXAGRAM FOR THE RECEPTIVE EARTH;So;0;ON;;;;;N;;;;; 4DC2;HEXAGRAM FOR DIFFICULTY AT THE BEGINNING;So;0;ON;;;;;N;;;;; @@ -12180,7 +12204,7 @@ 4DFE;HEXAGRAM FOR AFTER COMPLETION;So;0;ON;;;;;N;;;;; 4DFF;HEXAGRAM FOR BEFORE COMPLETION;So;0;ON;;;;;N;;;;; 4E00;<CJK Ideograph, First>;Lo;0;L;;;;;N;;;;; -9FEF;<CJK Ideograph, Last>;Lo;0;L;;;;;N;;;;; +9FFC;<CJK Ideograph, Last>;Lo;0;L;;;;;N;;;;; A000;YI SYLLABLE IT;Lo;0;L;;;;;N;;;;; A001;YI SYLLABLE IX;Lo;0;L;;;;;N;;;;; A002;YI SYLLABLE I;Lo;0;L;;;;;N;;;;; @@ -14130,6 +14154,12 @@ A7C3;LATIN SMALL LETTER ANGLICANA W;Ll;0;L;;;;;N;;;A7C2;;A7C2 A7C4;LATIN CAPITAL LETTER C WITH PALATAL HOOK;Lu;0;L;;;;;N;;;;A794; A7C5;LATIN CAPITAL LETTER S WITH HOOK;Lu;0;L;;;;;N;;;;0282; A7C6;LATIN CAPITAL LETTER Z WITH PALATAL HOOK;Lu;0;L;;;;;N;;;;1D8E; +A7C7;LATIN CAPITAL LETTER D WITH SHORT STROKE OVERLAY;Lu;0;L;;;;;N;;;;A7C8; +A7C8;LATIN SMALL LETTER D WITH SHORT STROKE OVERLAY;Ll;0;L;;;;;N;;;A7C7;;A7C7 +A7C9;LATIN CAPITAL LETTER S WITH SHORT STROKE OVERLAY;Lu;0;L;;;;;N;;;;A7CA; +A7CA;LATIN SMALL LETTER S WITH SHORT STROKE OVERLAY;Ll;0;L;;;;;N;;;A7C9;;A7C9 +A7F5;LATIN CAPITAL LETTER REVERSED HALF H;Lu;0;L;;;;;N;;;;A7F6; +A7F6;LATIN SMALL LETTER REVERSED HALF H;Ll;0;L;;;;;N;;;A7F5;;A7F5 A7F7;LATIN EPIGRAPHIC LETTER SIDEWAYS I;Lo;0;L;;;;;N;;;;; A7F8;MODIFIER LETTER CAPITAL H WITH STROKE;Lm;0;L;<super> 0126;;;;N;;;;; A7F9;MODIFIER LETTER SMALL LIGATURE OE;Lm;0;L;<super> 0153;;;;N;;;;; @@ -14183,6 +14213,7 @@ A828;SYLOTI NAGRI POETRY MARK-1;So;0;ON;;;;;N;;;;; A829;SYLOTI NAGRI POETRY MARK-2;So;0;ON;;;;;N;;;;; A82A;SYLOTI NAGRI POETRY MARK-3;So;0;ON;;;;;N;;;;; A82B;SYLOTI NAGRI POETRY MARK-4;So;0;ON;;;;;N;;;;; +A82C;SYLOTI NAGRI SIGN ALTERNATE HASANTA;Mn;9;NSM;;;;;N;;;;; A830;NORTH INDIC FRACTION ONE QUARTER;No;0;L;;;;1/4;N;;;;; A831;NORTH INDIC FRACTION ONE HALF;No;0;L;;;;1/2;N;;;;; A832;NORTH INDIC FRACTION THREE QUARTERS;No;0;L;;;;3/4;N;;;;; @@ -14897,6 +14928,10 @@ AB64;LATIN SMALL LETTER INVERTED ALPHA;Ll;0;L;;;;;N;;;;; AB65;GREEK LETTER SMALL CAPITAL OMEGA;Ll;0;L;;;;;N;;;;; AB66;LATIN SMALL LETTER DZ DIGRAPH WITH RETROFLEX HOOK;Ll;0;L;;;;;N;;;;; AB67;LATIN SMALL LETTER TS DIGRAPH WITH RETROFLEX HOOK;Ll;0;L;;;;;N;;;;; +AB68;LATIN SMALL LETTER TURNED R WITH MIDDLE TILDE;Ll;0;L;;;;;N;;;;; +AB69;MODIFIER LETTER SMALL TURNED W;Lm;0;L;<super> 028D;;;;N;;;;; +AB6A;MODIFIER LETTER LEFT TACK;Sk;0;ON;;;;;N;;;;; +AB6B;MODIFIER LETTER RIGHT TACK;Sk;0;ON;;;;;N;;;;; AB70;CHEROKEE SMALL LETTER A;Ll;0;L;;;;;N;;;13A0;;13A0 AB71;CHEROKEE SMALL LETTER E;Ll;0;L;;;;;N;;;13A1;;13A1 AB72;CHEROKEE SMALL LETTER I;Ll;0;L;;;;;N;;;13A2;;13A2 @@ -17086,6 +17121,7 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 10199;ROMAN DUPONDIUS SIGN;So;0;ON;;;;;N;;;;; 1019A;ROMAN AS SIGN;So;0;ON;;;;;N;;;;; 1019B;ROMAN CENTURIAL SIGN;So;0;ON;;;;;N;;;;; +1019C;ASCIA SYMBOL;So;0;ON;;;;;N;;;;; 101A0;GREEK SYMBOL TAU RHO;So;0;ON;;;;;N;;;;; 101D0;PHAISTOS DISC SIGN PEDESTRIAN;So;0;L;;;;;N;;;;; 101D1;PHAISTOS DISC SIGN PLUMED HEAD;So;0;L;;;;;N;;;;; @@ -19057,6 +19093,53 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 10E7C;RUMI FRACTION ONE QUARTER;No;0;AN;;;;1/4;N;;;;; 10E7D;RUMI FRACTION ONE THIRD;No;0;AN;;;;1/3;N;;;;; 10E7E;RUMI FRACTION TWO THIRDS;No;0;AN;;;;2/3;N;;;;; +10E80;YEZIDI LETTER ELIF;Lo;0;R;;;;;N;;;;; +10E81;YEZIDI LETTER BE;Lo;0;R;;;;;N;;;;; +10E82;YEZIDI LETTER PE;Lo;0;R;;;;;N;;;;; +10E83;YEZIDI LETTER PHE;Lo;0;R;;;;;N;;;;; +10E84;YEZIDI LETTER THE;Lo;0;R;;;;;N;;;;; +10E85;YEZIDI LETTER SE;Lo;0;R;;;;;N;;;;; +10E86;YEZIDI LETTER CIM;Lo;0;R;;;;;N;;;;; +10E87;YEZIDI LETTER CHIM;Lo;0;R;;;;;N;;;;; +10E88;YEZIDI LETTER CHHIM;Lo;0;R;;;;;N;;;;; +10E89;YEZIDI LETTER HHA;Lo;0;R;;;;;N;;;;; +10E8A;YEZIDI LETTER XA;Lo;0;R;;;;;N;;;;; +10E8B;YEZIDI LETTER DAL;Lo;0;R;;;;;N;;;;; +10E8C;YEZIDI LETTER ZAL;Lo;0;R;;;;;N;;;;; +10E8D;YEZIDI LETTER RA;Lo;0;R;;;;;N;;;;; +10E8E;YEZIDI LETTER RHA;Lo;0;R;;;;;N;;;;; +10E8F;YEZIDI LETTER ZA;Lo;0;R;;;;;N;;;;; +10E90;YEZIDI LETTER JA;Lo;0;R;;;;;N;;;;; +10E91;YEZIDI LETTER SIN;Lo;0;R;;;;;N;;;;; +10E92;YEZIDI LETTER SHIN;Lo;0;R;;;;;N;;;;; +10E93;YEZIDI LETTER SAD;Lo;0;R;;;;;N;;;;; +10E94;YEZIDI LETTER DAD;Lo;0;R;;;;;N;;;;; +10E95;YEZIDI LETTER TA;Lo;0;R;;;;;N;;;;; +10E96;YEZIDI LETTER ZE;Lo;0;R;;;;;N;;;;; +10E97;YEZIDI LETTER EYN;Lo;0;R;;;;;N;;;;; +10E98;YEZIDI LETTER XHEYN;Lo;0;R;;;;;N;;;;; +10E99;YEZIDI LETTER FA;Lo;0;R;;;;;N;;;;; +10E9A;YEZIDI LETTER VA;Lo;0;R;;;;;N;;;;; +10E9B;YEZIDI LETTER VA ALTERNATE FORM;Lo;0;R;;;;;N;;;;; +10E9C;YEZIDI LETTER QAF;Lo;0;R;;;;;N;;;;; +10E9D;YEZIDI LETTER KAF;Lo;0;R;;;;;N;;;;; +10E9E;YEZIDI LETTER KHAF;Lo;0;R;;;;;N;;;;; +10E9F;YEZIDI LETTER GAF;Lo;0;R;;;;;N;;;;; +10EA0;YEZIDI LETTER LAM;Lo;0;R;;;;;N;;;;; +10EA1;YEZIDI LETTER MIM;Lo;0;R;;;;;N;;;;; +10EA2;YEZIDI LETTER NUN;Lo;0;R;;;;;N;;;;; +10EA3;YEZIDI LETTER UM;Lo;0;R;;;;;N;;;;; +10EA4;YEZIDI LETTER WAW;Lo;0;R;;;;;N;;;;; +10EA5;YEZIDI LETTER OW;Lo;0;R;;;;;N;;;;; +10EA6;YEZIDI LETTER EW;Lo;0;R;;;;;N;;;;; +10EA7;YEZIDI LETTER HAY;Lo;0;R;;;;;N;;;;; +10EA8;YEZIDI LETTER YOT;Lo;0;R;;;;;N;;;;; +10EA9;YEZIDI LETTER ET;Lo;0;R;;;;;N;;;;; +10EAB;YEZIDI COMBINING HAMZA MARK;Mn;230;NSM;;;;;N;;;;; +10EAC;YEZIDI COMBINING MADDA MARK;Mn;230;NSM;;;;;N;;;;; +10EAD;YEZIDI HYPHENATION MARK;Pd;0;R;;;;;N;;;;; +10EB0;YEZIDI LETTER LAM WITH DOT ABOVE;Lo;0;R;;;;;N;;;;; +10EB1;YEZIDI LETTER YOT WITH CIRCUMFLEX ABOVE;Lo;0;R;;;;;N;;;;; 10F00;OLD SOGDIAN LETTER ALEPH;Lo;0;R;;;;;N;;;;; 10F01;OLD SOGDIAN LETTER FINAL ALEPH;Lo;0;R;;;;;N;;;;; 10F02;OLD SOGDIAN LETTER BETH;Lo;0;R;;;;;N;;;;; @@ -19139,6 +19222,34 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 10F57;SOGDIAN PUNCTUATION CIRCLE WITH DOT;Po;0;AL;;;;;N;;;;; 10F58;SOGDIAN PUNCTUATION TWO CIRCLES WITH DOTS;Po;0;AL;;;;;N;;;;; 10F59;SOGDIAN PUNCTUATION HALF CIRCLE WITH DOT;Po;0;AL;;;;;N;;;;; +10FB0;CHORASMIAN LETTER ALEPH;Lo;0;R;;;;;N;;;;; +10FB1;CHORASMIAN LETTER SMALL ALEPH;Lo;0;R;;;;;N;;;;; +10FB2;CHORASMIAN LETTER BETH;Lo;0;R;;;;;N;;;;; +10FB3;CHORASMIAN LETTER GIMEL;Lo;0;R;;;;;N;;;;; +10FB4;CHORASMIAN LETTER DALETH;Lo;0;R;;;;;N;;;;; +10FB5;CHORASMIAN LETTER HE;Lo;0;R;;;;;N;;;;; +10FB6;CHORASMIAN LETTER WAW;Lo;0;R;;;;;N;;;;; +10FB7;CHORASMIAN LETTER CURLED WAW;Lo;0;R;;;;;N;;;;; +10FB8;CHORASMIAN LETTER ZAYIN;Lo;0;R;;;;;N;;;;; +10FB9;CHORASMIAN LETTER HETH;Lo;0;R;;;;;N;;;;; +10FBA;CHORASMIAN LETTER YODH;Lo;0;R;;;;;N;;;;; +10FBB;CHORASMIAN LETTER KAPH;Lo;0;R;;;;;N;;;;; +10FBC;CHORASMIAN LETTER LAMEDH;Lo;0;R;;;;;N;;;;; +10FBD;CHORASMIAN LETTER MEM;Lo;0;R;;;;;N;;;;; +10FBE;CHORASMIAN LETTER NUN;Lo;0;R;;;;;N;;;;; +10FBF;CHORASMIAN LETTER SAMEKH;Lo;0;R;;;;;N;;;;; +10FC0;CHORASMIAN LETTER AYIN;Lo;0;R;;;;;N;;;;; +10FC1;CHORASMIAN LETTER PE;Lo;0;R;;;;;N;;;;; +10FC2;CHORASMIAN LETTER RESH;Lo;0;R;;;;;N;;;;; +10FC3;CHORASMIAN LETTER SHIN;Lo;0;R;;;;;N;;;;; +10FC4;CHORASMIAN LETTER TAW;Lo;0;R;;;;;N;;;;; +10FC5;CHORASMIAN NUMBER ONE;No;0;R;;;;1;N;;;;; +10FC6;CHORASMIAN NUMBER TWO;No;0;R;;;;2;N;;;;; +10FC7;CHORASMIAN NUMBER THREE;No;0;R;;;;3;N;;;;; +10FC8;CHORASMIAN NUMBER FOUR;No;0;R;;;;4;N;;;;; +10FC9;CHORASMIAN NUMBER TEN;No;0;R;;;;10;N;;;;; +10FCA;CHORASMIAN NUMBER TWENTY;No;0;R;;;;20;N;;;;; +10FCB;CHORASMIAN NUMBER ONE HUNDRED;No;0;R;;;;100;N;;;;; 10FE0;ELYMAIC LETTER ALEPH;Lo;0;R;;;;;N;;;;; 10FE1;ELYMAIC LETTER BETH;Lo;0;R;;;;;N;;;;; 10FE2;ELYMAIC LETTER GIMEL;Lo;0;R;;;;;N;;;;; @@ -19443,6 +19554,7 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 11144;CHAKMA LETTER LHAA;Lo;0;L;;;;;N;;;;; 11145;CHAKMA VOWEL SIGN AA;Mc;0;L;;;;;N;;;;; 11146;CHAKMA VOWEL SIGN EI;Mc;0;L;;;;;N;;;;; +11147;CHAKMA LETTER VAA;Lo;0;L;;;;;N;;;;; 11150;MAHAJANI LETTER A;Lo;0;L;;;;;N;;;;; 11151;MAHAJANI LETTER I;Lo;0;L;;;;;N;;;;; 11152;MAHAJANI LETTER U;Lo;0;L;;;;;N;;;;; @@ -19560,6 +19672,8 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 111CB;SHARADA VOWEL MODIFIER MARK;Mn;0;NSM;;;;;N;;;;; 111CC;SHARADA EXTRA SHORT VOWEL MARK;Mn;0;NSM;;;;;N;;;;; 111CD;SHARADA SUTRA MARK;Po;0;L;;;;;N;;;;; +111CE;SHARADA VOWEL SIGN PRISHTHAMATRA E;Mc;0;L;;;;;N;;;;; +111CF;SHARADA SIGN INVERTED CANDRABINDU;Mn;0;NSM;;;;;N;;;;; 111D0;SHARADA DIGIT ZERO;Nd;0;L;;0;0;0;N;;;;; 111D1;SHARADA DIGIT ONE;Nd;0;L;;1;1;1;N;;;;; 111D2;SHARADA DIGIT TWO;Nd;0;L;;2;2;2;N;;;;; @@ -19941,10 +20055,13 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 11457;NEWA DIGIT SEVEN;Nd;0;L;;7;7;7;N;;;;; 11458;NEWA DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;; 11459;NEWA DIGIT NINE;Nd;0;L;;9;9;9;N;;;;; +1145A;NEWA DOUBLE COMMA;Po;0;L;;;;;N;;;;; 1145B;NEWA PLACEHOLDER MARK;Po;0;L;;;;;N;;;;; 1145D;NEWA INSERTION SIGN;Po;0;L;;;;;N;;;;; 1145E;NEWA SANDHI MARK;Mn;230;NSM;;;;;N;;;;; 1145F;NEWA LETTER VEDIC ANUSVARA;Lo;0;L;;;;;N;;;;; +11460;NEWA SIGN JIHVAMULIYA;Lo;0;L;;;;;N;;;;; +11461;NEWA SIGN UPADHMANIYA;Lo;0;L;;;;;N;;;;; 11480;TIRHUTA ANJI;Lo;0;L;;;;;N;;;;; 11481;TIRHUTA LETTER A;Lo;0;L;;;;;N;;;;; 11482;TIRHUTA LETTER AA;Lo;0;L;;;;;N;;;;; @@ -20480,6 +20597,78 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 118F1;WARANG CITI NUMBER EIGHTY;No;0;L;;;;80;N;;;;; 118F2;WARANG CITI NUMBER NINETY;No;0;L;;;;90;N;;;;; 118FF;WARANG CITI OM;Lo;0;L;;;;;N;;;;; +11900;DIVES AKURU LETTER A;Lo;0;L;;;;;N;;;;; +11901;DIVES AKURU LETTER AA;Lo;0;L;;;;;N;;;;; +11902;DIVES AKURU LETTER I;Lo;0;L;;;;;N;;;;; +11903;DIVES AKURU LETTER II;Lo;0;L;;;;;N;;;;; +11904;DIVES AKURU LETTER U;Lo;0;L;;;;;N;;;;; +11905;DIVES AKURU LETTER UU;Lo;0;L;;;;;N;;;;; +11906;DIVES AKURU LETTER E;Lo;0;L;;;;;N;;;;; +11909;DIVES AKURU LETTER O;Lo;0;L;;;;;N;;;;; +1190C;DIVES AKURU LETTER KA;Lo;0;L;;;;;N;;;;; +1190D;DIVES AKURU LETTER KHA;Lo;0;L;;;;;N;;;;; +1190E;DIVES AKURU LETTER GA;Lo;0;L;;;;;N;;;;; +1190F;DIVES AKURU LETTER GHA;Lo;0;L;;;;;N;;;;; +11910;DIVES AKURU LETTER NGA;Lo;0;L;;;;;N;;;;; +11911;DIVES AKURU LETTER CA;Lo;0;L;;;;;N;;;;; +11912;DIVES AKURU LETTER CHA;Lo;0;L;;;;;N;;;;; +11913;DIVES AKURU LETTER JA;Lo;0;L;;;;;N;;;;; +11915;DIVES AKURU LETTER NYA;Lo;0;L;;;;;N;;;;; +11916;DIVES AKURU LETTER TTA;Lo;0;L;;;;;N;;;;; +11918;DIVES AKURU LETTER DDA;Lo;0;L;;;;;N;;;;; +11919;DIVES AKURU LETTER DDHA;Lo;0;L;;;;;N;;;;; +1191A;DIVES AKURU LETTER NNA;Lo;0;L;;;;;N;;;;; +1191B;DIVES AKURU LETTER TA;Lo;0;L;;;;;N;;;;; +1191C;DIVES AKURU LETTER THA;Lo;0;L;;;;;N;;;;; +1191D;DIVES AKURU LETTER DA;Lo;0;L;;;;;N;;;;; +1191E;DIVES AKURU LETTER DHA;Lo;0;L;;;;;N;;;;; +1191F;DIVES AKURU LETTER NA;Lo;0;L;;;;;N;;;;; +11920;DIVES AKURU LETTER PA;Lo;0;L;;;;;N;;;;; +11921;DIVES AKURU LETTER PHA;Lo;0;L;;;;;N;;;;; +11922;DIVES AKURU LETTER BA;Lo;0;L;;;;;N;;;;; +11923;DIVES AKURU LETTER BHA;Lo;0;L;;;;;N;;;;; +11924;DIVES AKURU LETTER MA;Lo;0;L;;;;;N;;;;; +11925;DIVES AKURU LETTER YA;Lo;0;L;;;;;N;;;;; +11926;DIVES AKURU LETTER YYA;Lo;0;L;;;;;N;;;;; +11927;DIVES AKURU LETTER RA;Lo;0;L;;;;;N;;;;; +11928;DIVES AKURU LETTER LA;Lo;0;L;;;;;N;;;;; +11929;DIVES AKURU LETTER VA;Lo;0;L;;;;;N;;;;; +1192A;DIVES AKURU LETTER SHA;Lo;0;L;;;;;N;;;;; +1192B;DIVES AKURU LETTER SSA;Lo;0;L;;;;;N;;;;; +1192C;DIVES AKURU LETTER SA;Lo;0;L;;;;;N;;;;; +1192D;DIVES AKURU LETTER HA;Lo;0;L;;;;;N;;;;; +1192E;DIVES AKURU LETTER LLA;Lo;0;L;;;;;N;;;;; +1192F;DIVES AKURU LETTER ZA;Lo;0;L;;;;;N;;;;; +11930;DIVES AKURU VOWEL SIGN AA;Mc;0;L;;;;;N;;;;; +11931;DIVES AKURU VOWEL SIGN I;Mc;0;L;;;;;N;;;;; +11932;DIVES AKURU VOWEL SIGN II;Mc;0;L;;;;;N;;;;; +11933;DIVES AKURU VOWEL SIGN U;Mc;0;L;;;;;N;;;;; +11934;DIVES AKURU VOWEL SIGN UU;Mc;0;L;;;;;N;;;;; +11935;DIVES AKURU VOWEL SIGN E;Mc;0;L;;;;;N;;;;; +11937;DIVES AKURU VOWEL SIGN AI;Mc;0;L;;;;;N;;;;; +11938;DIVES AKURU VOWEL SIGN O;Mc;0;L;11935 11930;;;;N;;;;; +1193B;DIVES AKURU SIGN ANUSVARA;Mn;0;NSM;;;;;N;;;;; +1193C;DIVES AKURU SIGN CANDRABINDU;Mn;0;NSM;;;;;N;;;;; +1193D;DIVES AKURU SIGN HALANTA;Mc;9;L;;;;;N;;;;; +1193E;DIVES AKURU VIRAMA;Mn;9;NSM;;;;;N;;;;; +1193F;DIVES AKURU PREFIXED NASAL SIGN;Lo;0;L;;;;;N;;;;; +11940;DIVES AKURU MEDIAL YA;Mc;0;L;;;;;N;;;;; +11941;DIVES AKURU INITIAL RA;Lo;0;L;;;;;N;;;;; +11942;DIVES AKURU MEDIAL RA;Mc;0;L;;;;;N;;;;; +11943;DIVES AKURU SIGN NUKTA;Mn;7;NSM;;;;;N;;;;; +11944;DIVES AKURU DOUBLE DANDA;Po;0;L;;;;;N;;;;; +11945;DIVES AKURU GAP FILLER;Po;0;L;;;;;N;;;;; +11946;DIVES AKURU END OF TEXT MARK;Po;0;L;;;;;N;;;;; +11950;DIVES AKURU DIGIT ZERO;Nd;0;L;;0;0;0;N;;;;; +11951;DIVES AKURU DIGIT ONE;Nd;0;L;;1;1;1;N;;;;; +11952;DIVES AKURU DIGIT TWO;Nd;0;L;;2;2;2;N;;;;; +11953;DIVES AKURU DIGIT THREE;Nd;0;L;;3;3;3;N;;;;; +11954;DIVES AKURU DIGIT FOUR;Nd;0;L;;4;4;4;N;;;;; +11955;DIVES AKURU DIGIT FIVE;Nd;0;L;;5;5;5;N;;;;; +11956;DIVES AKURU DIGIT SIX;Nd;0;L;;6;6;6;N;;;;; +11957;DIVES AKURU DIGIT SEVEN;Nd;0;L;;7;7;7;N;;;;; +11958;DIVES AKURU DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;; +11959;DIVES AKURU DIGIT NINE;Nd;0;L;;9;9;9;N;;;;; 119A0;NANDINAGARI LETTER A;Lo;0;L;;;;;N;;;;; 119A1;NANDINAGARI LETTER AA;Lo;0;L;;;;;N;;;;; 119A2;NANDINAGARI LETTER I;Lo;0;L;;;;;N;;;;; @@ -21085,6 +21274,7 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 11EF6;MAKASAR VOWEL SIGN O;Mc;0;L;;;;;N;;;;; 11EF7;MAKASAR PASSIMBANG;Po;0;L;;;;;N;;;;; 11EF8;MAKASAR END OF SECTION;Po;0;L;;;;;N;;;;; +11FB0;LISU LETTER YHA;Lo;0;L;;;;;N;;;;; 11FC0;TAMIL FRACTION ONE THREE-HUNDRED-AND-TWENTIETH;No;0;L;;;;1/320;N;;;;; 11FC1;TAMIL FRACTION ONE ONE-HUNDRED-AND-SIXTIETH;No;0;L;;;;1/160;N;;;;; 11FC2;TAMIL FRACTION ONE EIGHTIETH;No;0;L;;;;1/80;N;;;;; @@ -25052,6 +25242,9 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 16FE1;NUSHU ITERATION MARK;Lm;0;L;;;;;N;;;;; 16FE2;OLD CHINESE HOOK MARK;Po;0;ON;;;;;N;;;;; 16FE3;OLD CHINESE ITERATION MARK;Lm;0;L;;;;;N;;;;; +16FE4;KHITAN SMALL SCRIPT FILLER;Mn;0;NSM;;;;;N;;;;; +16FF0;VIETNAMESE ALTERNATE READING MARK CA;Mc;6;L;;;;;N;;;;; +16FF1;VIETNAMESE ALTERNATE READING MARK NHAY;Mc;6;L;;;;;N;;;;; 17000;<Tangut Ideograph, First>;Lo;0;L;;;;;N;;;;; 187F7;<Tangut Ideograph, Last>;Lo;0;L;;;;;N;;;;; 18800;TANGUT COMPONENT-001;Lo;0;L;;;;;N;;;;; @@ -25809,6 +26002,491 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 18AF0;TANGUT COMPONENT-753;Lo;0;L;;;;;N;;;;; 18AF1;TANGUT COMPONENT-754;Lo;0;L;;;;;N;;;;; 18AF2;TANGUT COMPONENT-755;Lo;0;L;;;;;N;;;;; +18AF3;TANGUT COMPONENT-756;Lo;0;L;;;;;N;;;;; +18AF4;TANGUT COMPONENT-757;Lo;0;L;;;;;N;;;;; +18AF5;TANGUT COMPONENT-758;Lo;0;L;;;;;N;;;;; +18AF6;TANGUT COMPONENT-759;Lo;0;L;;;;;N;;;;; +18AF7;TANGUT COMPONENT-760;Lo;0;L;;;;;N;;;;; +18AF8;TANGUT COMPONENT-761;Lo;0;L;;;;;N;;;;; +18AF9;TANGUT COMPONENT-762;Lo;0;L;;;;;N;;;;; +18AFA;TANGUT COMPONENT-763;Lo;0;L;;;;;N;;;;; +18AFB;TANGUT COMPONENT-764;Lo;0;L;;;;;N;;;;; +18AFC;TANGUT COMPONENT-765;Lo;0;L;;;;;N;;;;; +18AFD;TANGUT COMPONENT-766;Lo;0;L;;;;;N;;;;; +18AFE;TANGUT COMPONENT-767;Lo;0;L;;;;;N;;;;; +18AFF;TANGUT COMPONENT-768;Lo;0;L;;;;;N;;;;; +18B00;KHITAN SMALL SCRIPT CHARACTER-18B00;Lo;0;L;;;;;N;;;;; +18B01;KHITAN SMALL SCRIPT CHARACTER-18B01;Lo;0;L;;;;;N;;;;; +18B02;KHITAN SMALL SCRIPT CHARACTER-18B02;Lo;0;L;;;;;N;;;;; +18B03;KHITAN SMALL SCRIPT CHARACTER-18B03;Lo;0;L;;;;;N;;;;; +18B04;KHITAN SMALL SCRIPT CHARACTER-18B04;Lo;0;L;;;;;N;;;;; +18B05;KHITAN SMALL SCRIPT CHARACTER-18B05;Lo;0;L;;;;;N;;;;; +18B06;KHITAN SMALL SCRIPT CHARACTER-18B06;Lo;0;L;;;;;N;;;;; +18B07;KHITAN SMALL SCRIPT CHARACTER-18B07;Lo;0;L;;;;;N;;;;; +18B08;KHITAN SMALL SCRIPT CHARACTER-18B08;Lo;0;L;;;;;N;;;;; +18B09;KHITAN SMALL SCRIPT CHARACTER-18B09;Lo;0;L;;;;;N;;;;; +18B0A;KHITAN SMALL SCRIPT CHARACTER-18B0A;Lo;0;L;;;;;N;;;;; +18B0B;KHITAN SMALL SCRIPT CHARACTER-18B0B;Lo;0;L;;;;;N;;;;; +18B0C;KHITAN SMALL SCRIPT CHARACTER-18B0C;Lo;0;L;;;;;N;;;;; +18B0D;KHITAN SMALL SCRIPT CHARACTER-18B0D;Lo;0;L;;;;;N;;;;; +18B0E;KHITAN SMALL SCRIPT CHARACTER-18B0E;Lo;0;L;;;;;N;;;;; +18B0F;KHITAN SMALL SCRIPT CHARACTER-18B0F;Lo;0;L;;;;;N;;;;; +18B10;KHITAN SMALL SCRIPT CHARACTER-18B10;Lo;0;L;;;;;N;;;;; +18B11;KHITAN SMALL SCRIPT CHARACTER-18B11;Lo;0;L;;;;;N;;;;; +18B12;KHITAN SMALL SCRIPT CHARACTER-18B12;Lo;0;L;;;;;N;;;;; +18B13;KHITAN SMALL SCRIPT CHARACTER-18B13;Lo;0;L;;;;;N;;;;; +18B14;KHITAN SMALL SCRIPT CHARACTER-18B14;Lo;0;L;;;;;N;;;;; +18B15;KHITAN SMALL SCRIPT CHARACTER-18B15;Lo;0;L;;;;;N;;;;; +18B16;KHITAN SMALL SCRIPT CHARACTER-18B16;Lo;0;L;;;;;N;;;;; +18B17;KHITAN SMALL SCRIPT CHARACTER-18B17;Lo;0;L;;;;;N;;;;; +18B18;KHITAN SMALL SCRIPT CHARACTER-18B18;Lo;0;L;;;;;N;;;;; +18B19;KHITAN SMALL SCRIPT CHARACTER-18B19;Lo;0;L;;;;;N;;;;; +18B1A;KHITAN SMALL SCRIPT CHARACTER-18B1A;Lo;0;L;;;;;N;;;;; +18B1B;KHITAN SMALL SCRIPT CHARACTER-18B1B;Lo;0;L;;;;;N;;;;; +18B1C;KHITAN SMALL SCRIPT CHARACTER-18B1C;Lo;0;L;;;;;N;;;;; +18B1D;KHITAN SMALL SCRIPT CHARACTER-18B1D;Lo;0;L;;;;;N;;;;; +18B1E;KHITAN SMALL SCRIPT CHARACTER-18B1E;Lo;0;L;;;;;N;;;;; +18B1F;KHITAN SMALL SCRIPT CHARACTER-18B1F;Lo;0;L;;;;;N;;;;; +18B20;KHITAN SMALL SCRIPT CHARACTER-18B20;Lo;0;L;;;;;N;;;;; +18B21;KHITAN SMALL SCRIPT CHARACTER-18B21;Lo;0;L;;;;;N;;;;; +18B22;KHITAN SMALL SCRIPT CHARACTER-18B22;Lo;0;L;;;;;N;;;;; +18B23;KHITAN SMALL SCRIPT CHARACTER-18B23;Lo;0;L;;;;;N;;;;; +18B24;KHITAN SMALL SCRIPT CHARACTER-18B24;Lo;0;L;;;;;N;;;;; +18B25;KHITAN SMALL SCRIPT CHARACTER-18B25;Lo;0;L;;;;;N;;;;; +18B26;KHITAN SMALL SCRIPT CHARACTER-18B26;Lo;0;L;;;;;N;;;;; +18B27;KHITAN SMALL SCRIPT CHARACTER-18B27;Lo;0;L;;;;;N;;;;; +18B28;KHITAN SMALL SCRIPT CHARACTER-18B28;Lo;0;L;;;;;N;;;;; +18B29;KHITAN SMALL SCRIPT CHARACTER-18B29;Lo;0;L;;;;;N;;;;; +18B2A;KHITAN SMALL SCRIPT CHARACTER-18B2A;Lo;0;L;;;;;N;;;;; +18B2B;KHITAN SMALL SCRIPT CHARACTER-18B2B;Lo;0;L;;;;;N;;;;; +18B2C;KHITAN SMALL SCRIPT CHARACTER-18B2C;Lo;0;L;;;;;N;;;;; +18B2D;KHITAN SMALL SCRIPT CHARACTER-18B2D;Lo;0;L;;;;;N;;;;; +18B2E;KHITAN SMALL SCRIPT CHARACTER-18B2E;Lo;0;L;;;;;N;;;;; +18B2F;KHITAN SMALL SCRIPT CHARACTER-18B2F;Lo;0;L;;;;;N;;;;; +18B30;KHITAN SMALL SCRIPT CHARACTER-18B30;Lo;0;L;;;;;N;;;;; +18B31;KHITAN SMALL SCRIPT CHARACTER-18B31;Lo;0;L;;;;;N;;;;; +18B32;KHITAN SMALL SCRIPT CHARACTER-18B32;Lo;0;L;;;;;N;;;;; +18B33;KHITAN SMALL SCRIPT CHARACTER-18B33;Lo;0;L;;;;;N;;;;; +18B34;KHITAN SMALL SCRIPT CHARACTER-18B34;Lo;0;L;;;;;N;;;;; +18B35;KHITAN SMALL SCRIPT CHARACTER-18B35;Lo;0;L;;;;;N;;;;; +18B36;KHITAN SMALL SCRIPT CHARACTER-18B36;Lo;0;L;;;;;N;;;;; +18B37;KHITAN SMALL SCRIPT CHARACTER-18B37;Lo;0;L;;;;;N;;;;; +18B38;KHITAN SMALL SCRIPT CHARACTER-18B38;Lo;0;L;;;;;N;;;;; +18B39;KHITAN SMALL SCRIPT CHARACTER-18B39;Lo;0;L;;;;;N;;;;; +18B3A;KHITAN SMALL SCRIPT CHARACTER-18B3A;Lo;0;L;;;;;N;;;;; +18B3B;KHITAN SMALL SCRIPT CHARACTER-18B3B;Lo;0;L;;;;;N;;;;; +18B3C;KHITAN SMALL SCRIPT CHARACTER-18B3C;Lo;0;L;;;;;N;;;;; +18B3D;KHITAN SMALL SCRIPT CHARACTER-18B3D;Lo;0;L;;;;;N;;;;; +18B3E;KHITAN SMALL SCRIPT CHARACTER-18B3E;Lo;0;L;;;;;N;;;;; +18B3F;KHITAN SMALL SCRIPT CHARACTER-18B3F;Lo;0;L;;;;;N;;;;; +18B40;KHITAN SMALL SCRIPT CHARACTER-18B40;Lo;0;L;;;;;N;;;;; +18B41;KHITAN SMALL SCRIPT CHARACTER-18B41;Lo;0;L;;;;;N;;;;; +18B42;KHITAN SMALL SCRIPT CHARACTER-18B42;Lo;0;L;;;;;N;;;;; +18B43;KHITAN SMALL SCRIPT CHARACTER-18B43;Lo;0;L;;;;;N;;;;; +18B44;KHITAN SMALL SCRIPT CHARACTER-18B44;Lo;0;L;;;;;N;;;;; +18B45;KHITAN SMALL SCRIPT CHARACTER-18B45;Lo;0;L;;;;;N;;;;; +18B46;KHITAN SMALL SCRIPT CHARACTER-18B46;Lo;0;L;;;;;N;;;;; +18B47;KHITAN SMALL SCRIPT CHARACTER-18B47;Lo;0;L;;;;;N;;;;; +18B48;KHITAN SMALL SCRIPT CHARACTER-18B48;Lo;0;L;;;;;N;;;;; +18B49;KHITAN SMALL SCRIPT CHARACTER-18B49;Lo;0;L;;;;;N;;;;; +18B4A;KHITAN SMALL SCRIPT CHARACTER-18B4A;Lo;0;L;;;;;N;;;;; +18B4B;KHITAN SMALL SCRIPT CHARACTER-18B4B;Lo;0;L;;;;;N;;;;; +18B4C;KHITAN SMALL SCRIPT CHARACTER-18B4C;Lo;0;L;;;;;N;;;;; +18B4D;KHITAN SMALL SCRIPT CHARACTER-18B4D;Lo;0;L;;;;;N;;;;; +18B4E;KHITAN SMALL SCRIPT CHARACTER-18B4E;Lo;0;L;;;;;N;;;;; +18B4F;KHITAN SMALL SCRIPT CHARACTER-18B4F;Lo;0;L;;;;;N;;;;; +18B50;KHITAN SMALL SCRIPT CHARACTER-18B50;Lo;0;L;;;;;N;;;;; +18B51;KHITAN SMALL SCRIPT CHARACTER-18B51;Lo;0;L;;;;;N;;;;; +18B52;KHITAN SMALL SCRIPT CHARACTER-18B52;Lo;0;L;;;;;N;;;;; +18B53;KHITAN SMALL SCRIPT CHARACTER-18B53;Lo;0;L;;;;;N;;;;; +18B54;KHITAN SMALL SCRIPT CHARACTER-18B54;Lo;0;L;;;;;N;;;;; +18B55;KHITAN SMALL SCRIPT CHARACTER-18B55;Lo;0;L;;;;;N;;;;; +18B56;KHITAN SMALL SCRIPT CHARACTER-18B56;Lo;0;L;;;;;N;;;;; +18B57;KHITAN SMALL SCRIPT CHARACTER-18B57;Lo;0;L;;;;;N;;;;; +18B58;KHITAN SMALL SCRIPT CHARACTER-18B58;Lo;0;L;;;;;N;;;;; +18B59;KHITAN SMALL SCRIPT CHARACTER-18B59;Lo;0;L;;;;;N;;;;; +18B5A;KHITAN SMALL SCRIPT CHARACTER-18B5A;Lo;0;L;;;;;N;;;;; +18B5B;KHITAN SMALL SCRIPT CHARACTER-18B5B;Lo;0;L;;;;;N;;;;; +18B5C;KHITAN SMALL SCRIPT CHARACTER-18B5C;Lo;0;L;;;;;N;;;;; +18B5D;KHITAN SMALL SCRIPT CHARACTER-18B5D;Lo;0;L;;;;;N;;;;; +18B5E;KHITAN SMALL SCRIPT CHARACTER-18B5E;Lo;0;L;;;;;N;;;;; +18B5F;KHITAN SMALL SCRIPT CHARACTER-18B5F;Lo;0;L;;;;;N;;;;; +18B60;KHITAN SMALL SCRIPT CHARACTER-18B60;Lo;0;L;;;;;N;;;;; +18B61;KHITAN SMALL SCRIPT CHARACTER-18B61;Lo;0;L;;;;;N;;;;; +18B62;KHITAN SMALL SCRIPT CHARACTER-18B62;Lo;0;L;;;;;N;;;;; +18B63;KHITAN SMALL SCRIPT CHARACTER-18B63;Lo;0;L;;;;;N;;;;; +18B64;KHITAN SMALL SCRIPT CHARACTER-18B64;Lo;0;L;;;;;N;;;;; +18B65;KHITAN SMALL SCRIPT CHARACTER-18B65;Lo;0;L;;;;;N;;;;; +18B66;KHITAN SMALL SCRIPT CHARACTER-18B66;Lo;0;L;;;;;N;;;;; +18B67;KHITAN SMALL SCRIPT CHARACTER-18B67;Lo;0;L;;;;;N;;;;; +18B68;KHITAN SMALL SCRIPT CHARACTER-18B68;Lo;0;L;;;;;N;;;;; +18B69;KHITAN SMALL SCRIPT CHARACTER-18B69;Lo;0;L;;;;;N;;;;; +18B6A;KHITAN SMALL SCRIPT CHARACTER-18B6A;Lo;0;L;;;;;N;;;;; +18B6B;KHITAN SMALL SCRIPT CHARACTER-18B6B;Lo;0;L;;;;;N;;;;; +18B6C;KHITAN SMALL SCRIPT CHARACTER-18B6C;Lo;0;L;;;;;N;;;;; +18B6D;KHITAN SMALL SCRIPT CHARACTER-18B6D;Lo;0;L;;;;;N;;;;; +18B6E;KHITAN SMALL SCRIPT CHARACTER-18B6E;Lo;0;L;;;;;N;;;;; +18B6F;KHITAN SMALL SCRIPT CHARACTER-18B6F;Lo;0;L;;;;;N;;;;; +18B70;KHITAN SMALL SCRIPT CHARACTER-18B70;Lo;0;L;;;;;N;;;;; +18B71;KHITAN SMALL SCRIPT CHARACTER-18B71;Lo;0;L;;;;;N;;;;; +18B72;KHITAN SMALL SCRIPT CHARACTER-18B72;Lo;0;L;;;;;N;;;;; +18B73;KHITAN SMALL SCRIPT CHARACTER-18B73;Lo;0;L;;;;;N;;;;; +18B74;KHITAN SMALL SCRIPT CHARACTER-18B74;Lo;0;L;;;;;N;;;;; +18B75;KHITAN SMALL SCRIPT CHARACTER-18B75;Lo;0;L;;;;;N;;;;; +18B76;KHITAN SMALL SCRIPT CHARACTER-18B76;Lo;0;L;;;;;N;;;;; +18B77;KHITAN SMALL SCRIPT CHARACTER-18B77;Lo;0;L;;;;;N;;;;; +18B78;KHITAN SMALL SCRIPT CHARACTER-18B78;Lo;0;L;;;;;N;;;;; +18B79;KHITAN SMALL SCRIPT CHARACTER-18B79;Lo;0;L;;;;;N;;;;; +18B7A;KHITAN SMALL SCRIPT CHARACTER-18B7A;Lo;0;L;;;;;N;;;;; +18B7B;KHITAN SMALL SCRIPT CHARACTER-18B7B;Lo;0;L;;;;;N;;;;; +18B7C;KHITAN SMALL SCRIPT CHARACTER-18B7C;Lo;0;L;;;;;N;;;;; +18B7D;KHITAN SMALL SCRIPT CHARACTER-18B7D;Lo;0;L;;;;;N;;;;; +18B7E;KHITAN SMALL SCRIPT CHARACTER-18B7E;Lo;0;L;;;;;N;;;;; +18B7F;KHITAN SMALL SCRIPT CHARACTER-18B7F;Lo;0;L;;;;;N;;;;; +18B80;KHITAN SMALL SCRIPT CHARACTER-18B80;Lo;0;L;;;;;N;;;;; +18B81;KHITAN SMALL SCRIPT CHARACTER-18B81;Lo;0;L;;;;;N;;;;; +18B82;KHITAN SMALL SCRIPT CHARACTER-18B82;Lo;0;L;;;;;N;;;;; +18B83;KHITAN SMALL SCRIPT CHARACTER-18B83;Lo;0;L;;;;;N;;;;; +18B84;KHITAN SMALL SCRIPT CHARACTER-18B84;Lo;0;L;;;;;N;;;;; +18B85;KHITAN SMALL SCRIPT CHARACTER-18B85;Lo;0;L;;;;;N;;;;; +18B86;KHITAN SMALL SCRIPT CHARACTER-18B86;Lo;0;L;;;;;N;;;;; +18B87;KHITAN SMALL SCRIPT CHARACTER-18B87;Lo;0;L;;;;;N;;;;; +18B88;KHITAN SMALL SCRIPT CHARACTER-18B88;Lo;0;L;;;;;N;;;;; +18B89;KHITAN SMALL SCRIPT CHARACTER-18B89;Lo;0;L;;;;;N;;;;; +18B8A;KHITAN SMALL SCRIPT CHARACTER-18B8A;Lo;0;L;;;;;N;;;;; +18B8B;KHITAN SMALL SCRIPT CHARACTER-18B8B;Lo;0;L;;;;;N;;;;; +18B8C;KHITAN SMALL SCRIPT CHARACTER-18B8C;Lo;0;L;;;;;N;;;;; +18B8D;KHITAN SMALL SCRIPT CHARACTER-18B8D;Lo;0;L;;;;;N;;;;; +18B8E;KHITAN SMALL SCRIPT CHARACTER-18B8E;Lo;0;L;;;;;N;;;;; +18B8F;KHITAN SMALL SCRIPT CHARACTER-18B8F;Lo;0;L;;;;;N;;;;; +18B90;KHITAN SMALL SCRIPT CHARACTER-18B90;Lo;0;L;;;;;N;;;;; +18B91;KHITAN SMALL SCRIPT CHARACTER-18B91;Lo;0;L;;;;;N;;;;; +18B92;KHITAN SMALL SCRIPT CHARACTER-18B92;Lo;0;L;;;;;N;;;;; +18B93;KHITAN SMALL SCRIPT CHARACTER-18B93;Lo;0;L;;;;;N;;;;; +18B94;KHITAN SMALL SCRIPT CHARACTER-18B94;Lo;0;L;;;;;N;;;;; +18B95;KHITAN SMALL SCRIPT CHARACTER-18B95;Lo;0;L;;;;;N;;;;; +18B96;KHITAN SMALL SCRIPT CHARACTER-18B96;Lo;0;L;;;;;N;;;;; +18B97;KHITAN SMALL SCRIPT CHARACTER-18B97;Lo;0;L;;;;;N;;;;; +18B98;KHITAN SMALL SCRIPT CHARACTER-18B98;Lo;0;L;;;;;N;;;;; +18B99;KHITAN SMALL SCRIPT CHARACTER-18B99;Lo;0;L;;;;;N;;;;; +18B9A;KHITAN SMALL SCRIPT CHARACTER-18B9A;Lo;0;L;;;;;N;;;;; +18B9B;KHITAN SMALL SCRIPT CHARACTER-18B9B;Lo;0;L;;;;;N;;;;; +18B9C;KHITAN SMALL SCRIPT CHARACTER-18B9C;Lo;0;L;;;;;N;;;;; +18B9D;KHITAN SMALL SCRIPT CHARACTER-18B9D;Lo;0;L;;;;;N;;;;; +18B9E;KHITAN SMALL SCRIPT CHARACTER-18B9E;Lo;0;L;;;;;N;;;;; +18B9F;KHITAN SMALL SCRIPT CHARACTER-18B9F;Lo;0;L;;;;;N;;;;; +18BA0;KHITAN SMALL SCRIPT CHARACTER-18BA0;Lo;0;L;;;;;N;;;;; +18BA1;KHITAN SMALL SCRIPT CHARACTER-18BA1;Lo;0;L;;;;;N;;;;; +18BA2;KHITAN SMALL SCRIPT CHARACTER-18BA2;Lo;0;L;;;;;N;;;;; +18BA3;KHITAN SMALL SCRIPT CHARACTER-18BA3;Lo;0;L;;;;;N;;;;; +18BA4;KHITAN SMALL SCRIPT CHARACTER-18BA4;Lo;0;L;;;;;N;;;;; +18BA5;KHITAN SMALL SCRIPT CHARACTER-18BA5;Lo;0;L;;;;;N;;;;; +18BA6;KHITAN SMALL SCRIPT CHARACTER-18BA6;Lo;0;L;;;;;N;;;;; +18BA7;KHITAN SMALL SCRIPT CHARACTER-18BA7;Lo;0;L;;;;;N;;;;; +18BA8;KHITAN SMALL SCRIPT CHARACTER-18BA8;Lo;0;L;;;;;N;;;;; +18BA9;KHITAN SMALL SCRIPT CHARACTER-18BA9;Lo;0;L;;;;;N;;;;; +18BAA;KHITAN SMALL SCRIPT CHARACTER-18BAA;Lo;0;L;;;;;N;;;;; +18BAB;KHITAN SMALL SCRIPT CHARACTER-18BAB;Lo;0;L;;;;;N;;;;; +18BAC;KHITAN SMALL SCRIPT CHARACTER-18BAC;Lo;0;L;;;;;N;;;;; +18BAD;KHITAN SMALL SCRIPT CHARACTER-18BAD;Lo;0;L;;;;;N;;;;; +18BAE;KHITAN SMALL SCRIPT CHARACTER-18BAE;Lo;0;L;;;;;N;;;;; +18BAF;KHITAN SMALL SCRIPT CHARACTER-18BAF;Lo;0;L;;;;;N;;;;; +18BB0;KHITAN SMALL SCRIPT CHARACTER-18BB0;Lo;0;L;;;;;N;;;;; +18BB1;KHITAN SMALL SCRIPT CHARACTER-18BB1;Lo;0;L;;;;;N;;;;; +18BB2;KHITAN SMALL SCRIPT CHARACTER-18BB2;Lo;0;L;;;;;N;;;;; +18BB3;KHITAN SMALL SCRIPT CHARACTER-18BB3;Lo;0;L;;;;;N;;;;; +18BB4;KHITAN SMALL SCRIPT CHARACTER-18BB4;Lo;0;L;;;;;N;;;;; +18BB5;KHITAN SMALL SCRIPT CHARACTER-18BB5;Lo;0;L;;;;;N;;;;; +18BB6;KHITAN SMALL SCRIPT CHARACTER-18BB6;Lo;0;L;;;;;N;;;;; +18BB7;KHITAN SMALL SCRIPT CHARACTER-18BB7;Lo;0;L;;;;;N;;;;; +18BB8;KHITAN SMALL SCRIPT CHARACTER-18BB8;Lo;0;L;;;;;N;;;;; +18BB9;KHITAN SMALL SCRIPT CHARACTER-18BB9;Lo;0;L;;;;;N;;;;; +18BBA;KHITAN SMALL SCRIPT CHARACTER-18BBA;Lo;0;L;;;;;N;;;;; +18BBB;KHITAN SMALL SCRIPT CHARACTER-18BBB;Lo;0;L;;;;;N;;;;; +18BBC;KHITAN SMALL SCRIPT CHARACTER-18BBC;Lo;0;L;;;;;N;;;;; +18BBD;KHITAN SMALL SCRIPT CHARACTER-18BBD;Lo;0;L;;;;;N;;;;; +18BBE;KHITAN SMALL SCRIPT CHARACTER-18BBE;Lo;0;L;;;;;N;;;;; +18BBF;KHITAN SMALL SCRIPT CHARACTER-18BBF;Lo;0;L;;;;;N;;;;; +18BC0;KHITAN SMALL SCRIPT CHARACTER-18BC0;Lo;0;L;;;;;N;;;;; +18BC1;KHITAN SMALL SCRIPT CHARACTER-18BC1;Lo;0;L;;;;;N;;;;; +18BC2;KHITAN SMALL SCRIPT CHARACTER-18BC2;Lo;0;L;;;;;N;;;;; +18BC3;KHITAN SMALL SCRIPT CHARACTER-18BC3;Lo;0;L;;;;;N;;;;; +18BC4;KHITAN SMALL SCRIPT CHARACTER-18BC4;Lo;0;L;;;;;N;;;;; +18BC5;KHITAN SMALL SCRIPT CHARACTER-18BC5;Lo;0;L;;;;;N;;;;; +18BC6;KHITAN SMALL SCRIPT CHARACTER-18BC6;Lo;0;L;;;;;N;;;;; +18BC7;KHITAN SMALL SCRIPT CHARACTER-18BC7;Lo;0;L;;;;;N;;;;; +18BC8;KHITAN SMALL SCRIPT CHARACTER-18BC8;Lo;0;L;;;;;N;;;;; +18BC9;KHITAN SMALL SCRIPT CHARACTER-18BC9;Lo;0;L;;;;;N;;;;; +18BCA;KHITAN SMALL SCRIPT CHARACTER-18BCA;Lo;0;L;;;;;N;;;;; +18BCB;KHITAN SMALL SCRIPT CHARACTER-18BCB;Lo;0;L;;;;;N;;;;; +18BCC;KHITAN SMALL SCRIPT CHARACTER-18BCC;Lo;0;L;;;;;N;;;;; +18BCD;KHITAN SMALL SCRIPT CHARACTER-18BCD;Lo;0;L;;;;;N;;;;; +18BCE;KHITAN SMALL SCRIPT CHARACTER-18BCE;Lo;0;L;;;;;N;;;;; +18BCF;KHITAN SMALL SCRIPT CHARACTER-18BCF;Lo;0;L;;;;;N;;;;; +18BD0;KHITAN SMALL SCRIPT CHARACTER-18BD0;Lo;0;L;;;;;N;;;;; +18BD1;KHITAN SMALL SCRIPT CHARACTER-18BD1;Lo;0;L;;;;;N;;;;; +18BD2;KHITAN SMALL SCRIPT CHARACTER-18BD2;Lo;0;L;;;;;N;;;;; +18BD3;KHITAN SMALL SCRIPT CHARACTER-18BD3;Lo;0;L;;;;;N;;;;; +18BD4;KHITAN SMALL SCRIPT CHARACTER-18BD4;Lo;0;L;;;;;N;;;;; +18BD5;KHITAN SMALL SCRIPT CHARACTER-18BD5;Lo;0;L;;;;;N;;;;; +18BD6;KHITAN SMALL SCRIPT CHARACTER-18BD6;Lo;0;L;;;;;N;;;;; +18BD7;KHITAN SMALL SCRIPT CHARACTER-18BD7;Lo;0;L;;;;;N;;;;; +18BD8;KHITAN SMALL SCRIPT CHARACTER-18BD8;Lo;0;L;;;;;N;;;;; +18BD9;KHITAN SMALL SCRIPT CHARACTER-18BD9;Lo;0;L;;;;;N;;;;; +18BDA;KHITAN SMALL SCRIPT CHARACTER-18BDA;Lo;0;L;;;;;N;;;;; +18BDB;KHITAN SMALL SCRIPT CHARACTER-18BDB;Lo;0;L;;;;;N;;;;; +18BDC;KHITAN SMALL SCRIPT CHARACTER-18BDC;Lo;0;L;;;;;N;;;;; +18BDD;KHITAN SMALL SCRIPT CHARACTER-18BDD;Lo;0;L;;;;;N;;;;; +18BDE;KHITAN SMALL SCRIPT CHARACTER-18BDE;Lo;0;L;;;;;N;;;;; +18BDF;KHITAN SMALL SCRIPT CHARACTER-18BDF;Lo;0;L;;;;;N;;;;; +18BE0;KHITAN SMALL SCRIPT CHARACTER-18BE0;Lo;0;L;;;;;N;;;;; +18BE1;KHITAN SMALL SCRIPT CHARACTER-18BE1;Lo;0;L;;;;;N;;;;; +18BE2;KHITAN SMALL SCRIPT CHARACTER-18BE2;Lo;0;L;;;;;N;;;;; +18BE3;KHITAN SMALL SCRIPT CHARACTER-18BE3;Lo;0;L;;;;;N;;;;; +18BE4;KHITAN SMALL SCRIPT CHARACTER-18BE4;Lo;0;L;;;;;N;;;;; +18BE5;KHITAN SMALL SCRIPT CHARACTER-18BE5;Lo;0;L;;;;;N;;;;; +18BE6;KHITAN SMALL SCRIPT CHARACTER-18BE6;Lo;0;L;;;;;N;;;;; +18BE7;KHITAN SMALL SCRIPT CHARACTER-18BE7;Lo;0;L;;;;;N;;;;; +18BE8;KHITAN SMALL SCRIPT CHARACTER-18BE8;Lo;0;L;;;;;N;;;;; +18BE9;KHITAN SMALL SCRIPT CHARACTER-18BE9;Lo;0;L;;;;;N;;;;; +18BEA;KHITAN SMALL SCRIPT CHARACTER-18BEA;Lo;0;L;;;;;N;;;;; +18BEB;KHITAN SMALL SCRIPT CHARACTER-18BEB;Lo;0;L;;;;;N;;;;; +18BEC;KHITAN SMALL SCRIPT CHARACTER-18BEC;Lo;0;L;;;;;N;;;;; +18BED;KHITAN SMALL SCRIPT CHARACTER-18BED;Lo;0;L;;;;;N;;;;; +18BEE;KHITAN SMALL SCRIPT CHARACTER-18BEE;Lo;0;L;;;;;N;;;;; +18BEF;KHITAN SMALL SCRIPT CHARACTER-18BEF;Lo;0;L;;;;;N;;;;; +18BF0;KHITAN SMALL SCRIPT CHARACTER-18BF0;Lo;0;L;;;;;N;;;;; +18BF1;KHITAN SMALL SCRIPT CHARACTER-18BF1;Lo;0;L;;;;;N;;;;; +18BF2;KHITAN SMALL SCRIPT CHARACTER-18BF2;Lo;0;L;;;;;N;;;;; +18BF3;KHITAN SMALL SCRIPT CHARACTER-18BF3;Lo;0;L;;;;;N;;;;; +18BF4;KHITAN SMALL SCRIPT CHARACTER-18BF4;Lo;0;L;;;;;N;;;;; +18BF5;KHITAN SMALL SCRIPT CHARACTER-18BF5;Lo;0;L;;;;;N;;;;; +18BF6;KHITAN SMALL SCRIPT CHARACTER-18BF6;Lo;0;L;;;;;N;;;;; +18BF7;KHITAN SMALL SCRIPT CHARACTER-18BF7;Lo;0;L;;;;;N;;;;; +18BF8;KHITAN SMALL SCRIPT CHARACTER-18BF8;Lo;0;L;;;;;N;;;;; +18BF9;KHITAN SMALL SCRIPT CHARACTER-18BF9;Lo;0;L;;;;;N;;;;; +18BFA;KHITAN SMALL SCRIPT CHARACTER-18BFA;Lo;0;L;;;;;N;;;;; +18BFB;KHITAN SMALL SCRIPT CHARACTER-18BFB;Lo;0;L;;;;;N;;;;; +18BFC;KHITAN SMALL SCRIPT CHARACTER-18BFC;Lo;0;L;;;;;N;;;;; +18BFD;KHITAN SMALL SCRIPT CHARACTER-18BFD;Lo;0;L;;;;;N;;;;; +18BFE;KHITAN SMALL SCRIPT CHARACTER-18BFE;Lo;0;L;;;;;N;;;;; +18BFF;KHITAN SMALL SCRIPT CHARACTER-18BFF;Lo;0;L;;;;;N;;;;; +18C00;KHITAN SMALL SCRIPT CHARACTER-18C00;Lo;0;L;;;;;N;;;;; +18C01;KHITAN SMALL SCRIPT CHARACTER-18C01;Lo;0;L;;;;;N;;;;; +18C02;KHITAN SMALL SCRIPT CHARACTER-18C02;Lo;0;L;;;;;N;;;;; +18C03;KHITAN SMALL SCRIPT CHARACTER-18C03;Lo;0;L;;;;;N;;;;; +18C04;KHITAN SMALL SCRIPT CHARACTER-18C04;Lo;0;L;;;;;N;;;;; +18C05;KHITAN SMALL SCRIPT CHARACTER-18C05;Lo;0;L;;;;;N;;;;; +18C06;KHITAN SMALL SCRIPT CHARACTER-18C06;Lo;0;L;;;;;N;;;;; +18C07;KHITAN SMALL SCRIPT CHARACTER-18C07;Lo;0;L;;;;;N;;;;; +18C08;KHITAN SMALL SCRIPT CHARACTER-18C08;Lo;0;L;;;;;N;;;;; +18C09;KHITAN SMALL SCRIPT CHARACTER-18C09;Lo;0;L;;;;;N;;;;; +18C0A;KHITAN SMALL SCRIPT CHARACTER-18C0A;Lo;0;L;;;;;N;;;;; +18C0B;KHITAN SMALL SCRIPT CHARACTER-18C0B;Lo;0;L;;;;;N;;;;; +18C0C;KHITAN SMALL SCRIPT CHARACTER-18C0C;Lo;0;L;;;;;N;;;;; +18C0D;KHITAN SMALL SCRIPT CHARACTER-18C0D;Lo;0;L;;;;;N;;;;; +18C0E;KHITAN SMALL SCRIPT CHARACTER-18C0E;Lo;0;L;;;;;N;;;;; +18C0F;KHITAN SMALL SCRIPT CHARACTER-18C0F;Lo;0;L;;;;;N;;;;; +18C10;KHITAN SMALL SCRIPT CHARACTER-18C10;Lo;0;L;;;;;N;;;;; +18C11;KHITAN SMALL SCRIPT CHARACTER-18C11;Lo;0;L;;;;;N;;;;; +18C12;KHITAN SMALL SCRIPT CHARACTER-18C12;Lo;0;L;;;;;N;;;;; +18C13;KHITAN SMALL SCRIPT CHARACTER-18C13;Lo;0;L;;;;;N;;;;; +18C14;KHITAN SMALL SCRIPT CHARACTER-18C14;Lo;0;L;;;;;N;;;;; +18C15;KHITAN SMALL SCRIPT CHARACTER-18C15;Lo;0;L;;;;;N;;;;; +18C16;KHITAN SMALL SCRIPT CHARACTER-18C16;Lo;0;L;;;;;N;;;;; +18C17;KHITAN SMALL SCRIPT CHARACTER-18C17;Lo;0;L;;;;;N;;;;; +18C18;KHITAN SMALL SCRIPT CHARACTER-18C18;Lo;0;L;;;;;N;;;;; +18C19;KHITAN SMALL SCRIPT CHARACTER-18C19;Lo;0;L;;;;;N;;;;; +18C1A;KHITAN SMALL SCRIPT CHARACTER-18C1A;Lo;0;L;;;;;N;;;;; +18C1B;KHITAN SMALL SCRIPT CHARACTER-18C1B;Lo;0;L;;;;;N;;;;; +18C1C;KHITAN SMALL SCRIPT CHARACTER-18C1C;Lo;0;L;;;;;N;;;;; +18C1D;KHITAN SMALL SCRIPT CHARACTER-18C1D;Lo;0;L;;;;;N;;;;; +18C1E;KHITAN SMALL SCRIPT CHARACTER-18C1E;Lo;0;L;;;;;N;;;;; +18C1F;KHITAN SMALL SCRIPT CHARACTER-18C1F;Lo;0;L;;;;;N;;;;; +18C20;KHITAN SMALL SCRIPT CHARACTER-18C20;Lo;0;L;;;;;N;;;;; +18C21;KHITAN SMALL SCRIPT CHARACTER-18C21;Lo;0;L;;;;;N;;;;; +18C22;KHITAN SMALL SCRIPT CHARACTER-18C22;Lo;0;L;;;;;N;;;;; +18C23;KHITAN SMALL SCRIPT CHARACTER-18C23;Lo;0;L;;;;;N;;;;; +18C24;KHITAN SMALL SCRIPT CHARACTER-18C24;Lo;0;L;;;;;N;;;;; +18C25;KHITAN SMALL SCRIPT CHARACTER-18C25;Lo;0;L;;;;;N;;;;; +18C26;KHITAN SMALL SCRIPT CHARACTER-18C26;Lo;0;L;;;;;N;;;;; +18C27;KHITAN SMALL SCRIPT CHARACTER-18C27;Lo;0;L;;;;;N;;;;; +18C28;KHITAN SMALL SCRIPT CHARACTER-18C28;Lo;0;L;;;;;N;;;;; +18C29;KHITAN SMALL SCRIPT CHARACTER-18C29;Lo;0;L;;;;;N;;;;; +18C2A;KHITAN SMALL SCRIPT CHARACTER-18C2A;Lo;0;L;;;;;N;;;;; +18C2B;KHITAN SMALL SCRIPT CHARACTER-18C2B;Lo;0;L;;;;;N;;;;; +18C2C;KHITAN SMALL SCRIPT CHARACTER-18C2C;Lo;0;L;;;;;N;;;;; +18C2D;KHITAN SMALL SCRIPT CHARACTER-18C2D;Lo;0;L;;;;;N;;;;; +18C2E;KHITAN SMALL SCRIPT CHARACTER-18C2E;Lo;0;L;;;;;N;;;;; +18C2F;KHITAN SMALL SCRIPT CHARACTER-18C2F;Lo;0;L;;;;;N;;;;; +18C30;KHITAN SMALL SCRIPT CHARACTER-18C30;Lo;0;L;;;;;N;;;;; +18C31;KHITAN SMALL SCRIPT CHARACTER-18C31;Lo;0;L;;;;;N;;;;; +18C32;KHITAN SMALL SCRIPT CHARACTER-18C32;Lo;0;L;;;;;N;;;;; +18C33;KHITAN SMALL SCRIPT CHARACTER-18C33;Lo;0;L;;;;;N;;;;; +18C34;KHITAN SMALL SCRIPT CHARACTER-18C34;Lo;0;L;;;;;N;;;;; +18C35;KHITAN SMALL SCRIPT CHARACTER-18C35;Lo;0;L;;;;;N;;;;; +18C36;KHITAN SMALL SCRIPT CHARACTER-18C36;Lo;0;L;;;;;N;;;;; +18C37;KHITAN SMALL SCRIPT CHARACTER-18C37;Lo;0;L;;;;;N;;;;; +18C38;KHITAN SMALL SCRIPT CHARACTER-18C38;Lo;0;L;;;;;N;;;;; +18C39;KHITAN SMALL SCRIPT CHARACTER-18C39;Lo;0;L;;;;;N;;;;; +18C3A;KHITAN SMALL SCRIPT CHARACTER-18C3A;Lo;0;L;;;;;N;;;;; +18C3B;KHITAN SMALL SCRIPT CHARACTER-18C3B;Lo;0;L;;;;;N;;;;; +18C3C;KHITAN SMALL SCRIPT CHARACTER-18C3C;Lo;0;L;;;;;N;;;;; +18C3D;KHITAN SMALL SCRIPT CHARACTER-18C3D;Lo;0;L;;;;;N;;;;; +18C3E;KHITAN SMALL SCRIPT CHARACTER-18C3E;Lo;0;L;;;;;N;;;;; +18C3F;KHITAN SMALL SCRIPT CHARACTER-18C3F;Lo;0;L;;;;;N;;;;; +18C40;KHITAN SMALL SCRIPT CHARACTER-18C40;Lo;0;L;;;;;N;;;;; +18C41;KHITAN SMALL SCRIPT CHARACTER-18C41;Lo;0;L;;;;;N;;;;; +18C42;KHITAN SMALL SCRIPT CHARACTER-18C42;Lo;0;L;;;;;N;;;;; +18C43;KHITAN SMALL SCRIPT CHARACTER-18C43;Lo;0;L;;;;;N;;;;; +18C44;KHITAN SMALL SCRIPT CHARACTER-18C44;Lo;0;L;;;;;N;;;;; +18C45;KHITAN SMALL SCRIPT CHARACTER-18C45;Lo;0;L;;;;;N;;;;; +18C46;KHITAN SMALL SCRIPT CHARACTER-18C46;Lo;0;L;;;;;N;;;;; +18C47;KHITAN SMALL SCRIPT CHARACTER-18C47;Lo;0;L;;;;;N;;;;; +18C48;KHITAN SMALL SCRIPT CHARACTER-18C48;Lo;0;L;;;;;N;;;;; +18C49;KHITAN SMALL SCRIPT CHARACTER-18C49;Lo;0;L;;;;;N;;;;; +18C4A;KHITAN SMALL SCRIPT CHARACTER-18C4A;Lo;0;L;;;;;N;;;;; +18C4B;KHITAN SMALL SCRIPT CHARACTER-18C4B;Lo;0;L;;;;;N;;;;; +18C4C;KHITAN SMALL SCRIPT CHARACTER-18C4C;Lo;0;L;;;;;N;;;;; +18C4D;KHITAN SMALL SCRIPT CHARACTER-18C4D;Lo;0;L;;;;;N;;;;; +18C4E;KHITAN SMALL SCRIPT CHARACTER-18C4E;Lo;0;L;;;;;N;;;;; +18C4F;KHITAN SMALL SCRIPT CHARACTER-18C4F;Lo;0;L;;;;;N;;;;; +18C50;KHITAN SMALL SCRIPT CHARACTER-18C50;Lo;0;L;;;;;N;;;;; +18C51;KHITAN SMALL SCRIPT CHARACTER-18C51;Lo;0;L;;;;;N;;;;; +18C52;KHITAN SMALL SCRIPT CHARACTER-18C52;Lo;0;L;;;;;N;;;;; +18C53;KHITAN SMALL SCRIPT CHARACTER-18C53;Lo;0;L;;;;;N;;;;; +18C54;KHITAN SMALL SCRIPT CHARACTER-18C54;Lo;0;L;;;;;N;;;;; +18C55;KHITAN SMALL SCRIPT CHARACTER-18C55;Lo;0;L;;;;;N;;;;; +18C56;KHITAN SMALL SCRIPT CHARACTER-18C56;Lo;0;L;;;;;N;;;;; +18C57;KHITAN SMALL SCRIPT CHARACTER-18C57;Lo;0;L;;;;;N;;;;; +18C58;KHITAN SMALL SCRIPT CHARACTER-18C58;Lo;0;L;;;;;N;;;;; +18C59;KHITAN SMALL SCRIPT CHARACTER-18C59;Lo;0;L;;;;;N;;;;; +18C5A;KHITAN SMALL SCRIPT CHARACTER-18C5A;Lo;0;L;;;;;N;;;;; +18C5B;KHITAN SMALL SCRIPT CHARACTER-18C5B;Lo;0;L;;;;;N;;;;; +18C5C;KHITAN SMALL SCRIPT CHARACTER-18C5C;Lo;0;L;;;;;N;;;;; +18C5D;KHITAN SMALL SCRIPT CHARACTER-18C5D;Lo;0;L;;;;;N;;;;; +18C5E;KHITAN SMALL SCRIPT CHARACTER-18C5E;Lo;0;L;;;;;N;;;;; +18C5F;KHITAN SMALL SCRIPT CHARACTER-18C5F;Lo;0;L;;;;;N;;;;; +18C60;KHITAN SMALL SCRIPT CHARACTER-18C60;Lo;0;L;;;;;N;;;;; +18C61;KHITAN SMALL SCRIPT CHARACTER-18C61;Lo;0;L;;;;;N;;;;; +18C62;KHITAN SMALL SCRIPT CHARACTER-18C62;Lo;0;L;;;;;N;;;;; +18C63;KHITAN SMALL SCRIPT CHARACTER-18C63;Lo;0;L;;;;;N;;;;; +18C64;KHITAN SMALL SCRIPT CHARACTER-18C64;Lo;0;L;;;;;N;;;;; +18C65;KHITAN SMALL SCRIPT CHARACTER-18C65;Lo;0;L;;;;;N;;;;; +18C66;KHITAN SMALL SCRIPT CHARACTER-18C66;Lo;0;L;;;;;N;;;;; +18C67;KHITAN SMALL SCRIPT CHARACTER-18C67;Lo;0;L;;;;;N;;;;; +18C68;KHITAN SMALL SCRIPT CHARACTER-18C68;Lo;0;L;;;;;N;;;;; +18C69;KHITAN SMALL SCRIPT CHARACTER-18C69;Lo;0;L;;;;;N;;;;; +18C6A;KHITAN SMALL SCRIPT CHARACTER-18C6A;Lo;0;L;;;;;N;;;;; +18C6B;KHITAN SMALL SCRIPT CHARACTER-18C6B;Lo;0;L;;;;;N;;;;; +18C6C;KHITAN SMALL SCRIPT CHARACTER-18C6C;Lo;0;L;;;;;N;;;;; +18C6D;KHITAN SMALL SCRIPT CHARACTER-18C6D;Lo;0;L;;;;;N;;;;; +18C6E;KHITAN SMALL SCRIPT CHARACTER-18C6E;Lo;0;L;;;;;N;;;;; +18C6F;KHITAN SMALL SCRIPT CHARACTER-18C6F;Lo;0;L;;;;;N;;;;; +18C70;KHITAN SMALL SCRIPT CHARACTER-18C70;Lo;0;L;;;;;N;;;;; +18C71;KHITAN SMALL SCRIPT CHARACTER-18C71;Lo;0;L;;;;;N;;;;; +18C72;KHITAN SMALL SCRIPT CHARACTER-18C72;Lo;0;L;;;;;N;;;;; +18C73;KHITAN SMALL SCRIPT CHARACTER-18C73;Lo;0;L;;;;;N;;;;; +18C74;KHITAN SMALL SCRIPT CHARACTER-18C74;Lo;0;L;;;;;N;;;;; +18C75;KHITAN SMALL SCRIPT CHARACTER-18C75;Lo;0;L;;;;;N;;;;; +18C76;KHITAN SMALL SCRIPT CHARACTER-18C76;Lo;0;L;;;;;N;;;;; +18C77;KHITAN SMALL SCRIPT CHARACTER-18C77;Lo;0;L;;;;;N;;;;; +18C78;KHITAN SMALL SCRIPT CHARACTER-18C78;Lo;0;L;;;;;N;;;;; +18C79;KHITAN SMALL SCRIPT CHARACTER-18C79;Lo;0;L;;;;;N;;;;; +18C7A;KHITAN SMALL SCRIPT CHARACTER-18C7A;Lo;0;L;;;;;N;;;;; +18C7B;KHITAN SMALL SCRIPT CHARACTER-18C7B;Lo;0;L;;;;;N;;;;; +18C7C;KHITAN SMALL SCRIPT CHARACTER-18C7C;Lo;0;L;;;;;N;;;;; +18C7D;KHITAN SMALL SCRIPT CHARACTER-18C7D;Lo;0;L;;;;;N;;;;; +18C7E;KHITAN SMALL SCRIPT CHARACTER-18C7E;Lo;0;L;;;;;N;;;;; +18C7F;KHITAN SMALL SCRIPT CHARACTER-18C7F;Lo;0;L;;;;;N;;;;; +18C80;KHITAN SMALL SCRIPT CHARACTER-18C80;Lo;0;L;;;;;N;;;;; +18C81;KHITAN SMALL SCRIPT CHARACTER-18C81;Lo;0;L;;;;;N;;;;; +18C82;KHITAN SMALL SCRIPT CHARACTER-18C82;Lo;0;L;;;;;N;;;;; +18C83;KHITAN SMALL SCRIPT CHARACTER-18C83;Lo;0;L;;;;;N;;;;; +18C84;KHITAN SMALL SCRIPT CHARACTER-18C84;Lo;0;L;;;;;N;;;;; +18C85;KHITAN SMALL SCRIPT CHARACTER-18C85;Lo;0;L;;;;;N;;;;; +18C86;KHITAN SMALL SCRIPT CHARACTER-18C86;Lo;0;L;;;;;N;;;;; +18C87;KHITAN SMALL SCRIPT CHARACTER-18C87;Lo;0;L;;;;;N;;;;; +18C88;KHITAN SMALL SCRIPT CHARACTER-18C88;Lo;0;L;;;;;N;;;;; +18C89;KHITAN SMALL SCRIPT CHARACTER-18C89;Lo;0;L;;;;;N;;;;; +18C8A;KHITAN SMALL SCRIPT CHARACTER-18C8A;Lo;0;L;;;;;N;;;;; +18C8B;KHITAN SMALL SCRIPT CHARACTER-18C8B;Lo;0;L;;;;;N;;;;; +18C8C;KHITAN SMALL SCRIPT CHARACTER-18C8C;Lo;0;L;;;;;N;;;;; +18C8D;KHITAN SMALL SCRIPT CHARACTER-18C8D;Lo;0;L;;;;;N;;;;; +18C8E;KHITAN SMALL SCRIPT CHARACTER-18C8E;Lo;0;L;;;;;N;;;;; +18C8F;KHITAN SMALL SCRIPT CHARACTER-18C8F;Lo;0;L;;;;;N;;;;; +18C90;KHITAN SMALL SCRIPT CHARACTER-18C90;Lo;0;L;;;;;N;;;;; +18C91;KHITAN SMALL SCRIPT CHARACTER-18C91;Lo;0;L;;;;;N;;;;; +18C92;KHITAN SMALL SCRIPT CHARACTER-18C92;Lo;0;L;;;;;N;;;;; +18C93;KHITAN SMALL SCRIPT CHARACTER-18C93;Lo;0;L;;;;;N;;;;; +18C94;KHITAN SMALL SCRIPT CHARACTER-18C94;Lo;0;L;;;;;N;;;;; +18C95;KHITAN SMALL SCRIPT CHARACTER-18C95;Lo;0;L;;;;;N;;;;; +18C96;KHITAN SMALL SCRIPT CHARACTER-18C96;Lo;0;L;;;;;N;;;;; +18C97;KHITAN SMALL SCRIPT CHARACTER-18C97;Lo;0;L;;;;;N;;;;; +18C98;KHITAN SMALL SCRIPT CHARACTER-18C98;Lo;0;L;;;;;N;;;;; +18C99;KHITAN SMALL SCRIPT CHARACTER-18C99;Lo;0;L;;;;;N;;;;; +18C9A;KHITAN SMALL SCRIPT CHARACTER-18C9A;Lo;0;L;;;;;N;;;;; +18C9B;KHITAN SMALL SCRIPT CHARACTER-18C9B;Lo;0;L;;;;;N;;;;; +18C9C;KHITAN SMALL SCRIPT CHARACTER-18C9C;Lo;0;L;;;;;N;;;;; +18C9D;KHITAN SMALL SCRIPT CHARACTER-18C9D;Lo;0;L;;;;;N;;;;; +18C9E;KHITAN SMALL SCRIPT CHARACTER-18C9E;Lo;0;L;;;;;N;;;;; +18C9F;KHITAN SMALL SCRIPT CHARACTER-18C9F;Lo;0;L;;;;;N;;;;; +18CA0;KHITAN SMALL SCRIPT CHARACTER-18CA0;Lo;0;L;;;;;N;;;;; +18CA1;KHITAN SMALL SCRIPT CHARACTER-18CA1;Lo;0;L;;;;;N;;;;; +18CA2;KHITAN SMALL SCRIPT CHARACTER-18CA2;Lo;0;L;;;;;N;;;;; +18CA3;KHITAN SMALL SCRIPT CHARACTER-18CA3;Lo;0;L;;;;;N;;;;; +18CA4;KHITAN SMALL SCRIPT CHARACTER-18CA4;Lo;0;L;;;;;N;;;;; +18CA5;KHITAN SMALL SCRIPT CHARACTER-18CA5;Lo;0;L;;;;;N;;;;; +18CA6;KHITAN SMALL SCRIPT CHARACTER-18CA6;Lo;0;L;;;;;N;;;;; +18CA7;KHITAN SMALL SCRIPT CHARACTER-18CA7;Lo;0;L;;;;;N;;;;; +18CA8;KHITAN SMALL SCRIPT CHARACTER-18CA8;Lo;0;L;;;;;N;;;;; +18CA9;KHITAN SMALL SCRIPT CHARACTER-18CA9;Lo;0;L;;;;;N;;;;; +18CAA;KHITAN SMALL SCRIPT CHARACTER-18CAA;Lo;0;L;;;;;N;;;;; +18CAB;KHITAN SMALL SCRIPT CHARACTER-18CAB;Lo;0;L;;;;;N;;;;; +18CAC;KHITAN SMALL SCRIPT CHARACTER-18CAC;Lo;0;L;;;;;N;;;;; +18CAD;KHITAN SMALL SCRIPT CHARACTER-18CAD;Lo;0;L;;;;;N;;;;; +18CAE;KHITAN SMALL SCRIPT CHARACTER-18CAE;Lo;0;L;;;;;N;;;;; +18CAF;KHITAN SMALL SCRIPT CHARACTER-18CAF;Lo;0;L;;;;;N;;;;; +18CB0;KHITAN SMALL SCRIPT CHARACTER-18CB0;Lo;0;L;;;;;N;;;;; +18CB1;KHITAN SMALL SCRIPT CHARACTER-18CB1;Lo;0;L;;;;;N;;;;; +18CB2;KHITAN SMALL SCRIPT CHARACTER-18CB2;Lo;0;L;;;;;N;;;;; +18CB3;KHITAN SMALL SCRIPT CHARACTER-18CB3;Lo;0;L;;;;;N;;;;; +18CB4;KHITAN SMALL SCRIPT CHARACTER-18CB4;Lo;0;L;;;;;N;;;;; +18CB5;KHITAN SMALL SCRIPT CHARACTER-18CB5;Lo;0;L;;;;;N;;;;; +18CB6;KHITAN SMALL SCRIPT CHARACTER-18CB6;Lo;0;L;;;;;N;;;;; +18CB7;KHITAN SMALL SCRIPT CHARACTER-18CB7;Lo;0;L;;;;;N;;;;; +18CB8;KHITAN SMALL SCRIPT CHARACTER-18CB8;Lo;0;L;;;;;N;;;;; +18CB9;KHITAN SMALL SCRIPT CHARACTER-18CB9;Lo;0;L;;;;;N;;;;; +18CBA;KHITAN SMALL SCRIPT CHARACTER-18CBA;Lo;0;L;;;;;N;;;;; +18CBB;KHITAN SMALL SCRIPT CHARACTER-18CBB;Lo;0;L;;;;;N;;;;; +18CBC;KHITAN SMALL SCRIPT CHARACTER-18CBC;Lo;0;L;;;;;N;;;;; +18CBD;KHITAN SMALL SCRIPT CHARACTER-18CBD;Lo;0;L;;;;;N;;;;; +18CBE;KHITAN SMALL SCRIPT CHARACTER-18CBE;Lo;0;L;;;;;N;;;;; +18CBF;KHITAN SMALL SCRIPT CHARACTER-18CBF;Lo;0;L;;;;;N;;;;; +18CC0;KHITAN SMALL SCRIPT CHARACTER-18CC0;Lo;0;L;;;;;N;;;;; +18CC1;KHITAN SMALL SCRIPT CHARACTER-18CC1;Lo;0;L;;;;;N;;;;; +18CC2;KHITAN SMALL SCRIPT CHARACTER-18CC2;Lo;0;L;;;;;N;;;;; +18CC3;KHITAN SMALL SCRIPT CHARACTER-18CC3;Lo;0;L;;;;;N;;;;; +18CC4;KHITAN SMALL SCRIPT CHARACTER-18CC4;Lo;0;L;;;;;N;;;;; +18CC5;KHITAN SMALL SCRIPT CHARACTER-18CC5;Lo;0;L;;;;;N;;;;; +18CC6;KHITAN SMALL SCRIPT CHARACTER-18CC6;Lo;0;L;;;;;N;;;;; +18CC7;KHITAN SMALL SCRIPT CHARACTER-18CC7;Lo;0;L;;;;;N;;;;; +18CC8;KHITAN SMALL SCRIPT CHARACTER-18CC8;Lo;0;L;;;;;N;;;;; +18CC9;KHITAN SMALL SCRIPT CHARACTER-18CC9;Lo;0;L;;;;;N;;;;; +18CCA;KHITAN SMALL SCRIPT CHARACTER-18CCA;Lo;0;L;;;;;N;;;;; +18CCB;KHITAN SMALL SCRIPT CHARACTER-18CCB;Lo;0;L;;;;;N;;;;; +18CCC;KHITAN SMALL SCRIPT CHARACTER-18CCC;Lo;0;L;;;;;N;;;;; +18CCD;KHITAN SMALL SCRIPT CHARACTER-18CCD;Lo;0;L;;;;;N;;;;; +18CCE;KHITAN SMALL SCRIPT CHARACTER-18CCE;Lo;0;L;;;;;N;;;;; +18CCF;KHITAN SMALL SCRIPT CHARACTER-18CCF;Lo;0;L;;;;;N;;;;; +18CD0;KHITAN SMALL SCRIPT CHARACTER-18CD0;Lo;0;L;;;;;N;;;;; +18CD1;KHITAN SMALL SCRIPT CHARACTER-18CD1;Lo;0;L;;;;;N;;;;; +18CD2;KHITAN SMALL SCRIPT CHARACTER-18CD2;Lo;0;L;;;;;N;;;;; +18CD3;KHITAN SMALL SCRIPT CHARACTER-18CD3;Lo;0;L;;;;;N;;;;; +18CD4;KHITAN SMALL SCRIPT CHARACTER-18CD4;Lo;0;L;;;;;N;;;;; +18CD5;KHITAN SMALL SCRIPT CHARACTER-18CD5;Lo;0;L;;;;;N;;;;; +18D00;<Tangut Ideograph Supplement, First>;Lo;0;L;;;;;N;;;;; +18D08;<Tangut Ideograph Supplement, Last>;Lo;0;L;;;;;N;;;;; 1B000;KATAKANA LETTER ARCHAIC E;Lo;0;L;;;;;N;;;;; 1B001;HIRAGANA LETTER ARCHAIC YE;Lo;0;L;;;;;N;;;;; 1B002;HENTAIGANA LETTER A-1;Lo;0;L;;;;;N;;;;; @@ -29973,6 +30651,9 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1F10A;DIGIT NINE COMMA;No;0;EN;<compat> 0039 002C;;9;9;N;;;;; 1F10B;DINGBAT CIRCLED SANS-SERIF DIGIT ZERO;No;0;ON;;;;0;N;;;;; 1F10C;DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT ZERO;No;0;ON;;;;0;N;;;;; +1F10D;CIRCLED ZERO WITH SLASH;So;0;ON;;;;;N;;;;; +1F10E;CIRCLED ANTICLOCKWISE ARROW;So;0;ON;;;;;N;;;;; +1F10F;CIRCLED DOLLAR SIGN WITH OVERLAID BACKSLASH;So;0;ON;;;;;N;;;;; 1F110;PARENTHESIZED LATIN CAPITAL LETTER A;So;0;L;<compat> 0028 0041 0029;;;;N;;;;; 1F111;PARENTHESIZED LATIN CAPITAL LETTER B;So;0;L;<compat> 0028 0042 0029;;;;N;;;;; 1F112;PARENTHESIZED LATIN CAPITAL LETTER C;So;0;L;<compat> 0028 0043 0029;;;;N;;;;; @@ -30066,6 +30747,9 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1F16A;RAISED MC SIGN;So;0;ON;<super> 004D 0043;;;;N;;;;; 1F16B;RAISED MD SIGN;So;0;ON;<super> 004D 0044;;;;N;;;;; 1F16C;RAISED MR SIGN;So;0;ON;<super> 004D 0052;;;;N;;;;; +1F16D;CIRCLED CC;So;0;ON;;;;;N;;;;; +1F16E;CIRCLED C WITH OVERLAID BACKSLASH;So;0;ON;;;;;N;;;;; +1F16F;CIRCLED HUMAN FIGURE;So;0;ON;;;;;N;;;;; 1F170;NEGATIVE SQUARED LATIN CAPITAL LETTER A;So;0;L;;;;;N;;;;; 1F171;NEGATIVE SQUARED LATIN CAPITAL LETTER B;So;0;L;;;;;N;;;;; 1F172;NEGATIVE SQUARED LATIN CAPITAL LETTER C;So;0;L;;;;;N;;;;; @@ -30127,6 +30811,7 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1F1AA;SQUARED SHV;So;0;L;;;;;N;;;;; 1F1AB;SQUARED UHD;So;0;L;;;;;N;;;;; 1F1AC;SQUARED VOD;So;0;L;;;;;N;;;;; +1F1AD;MASK WORK SYMBOL;So;0;ON;;;;;N;;;;; 1F1E6;REGIONAL INDICATOR SYMBOL LETTER A;So;0;L;;;;;N;;;;; 1F1E7;REGIONAL INDICATOR SYMBOL LETTER B;So;0;L;;;;;N;;;;; 1F1E8;REGIONAL INDICATOR SYMBOL LETTER C;So;0;L;;;;;N;;;;; @@ -31199,6 +31884,8 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1F6D3;STUPA;So;0;ON;;;;;N;;;;; 1F6D4;PAGODA;So;0;ON;;;;;N;;;;; 1F6D5;HINDU TEMPLE;So;0;ON;;;;;N;;;;; +1F6D6;HUT;So;0;ON;;;;;N;;;;; +1F6D7;ELEVATOR;So;0;ON;;;;;N;;;;; 1F6E0;HAMMER AND WRENCH;So;0;ON;;;;;N;;;;; 1F6E1;SHIELD;So;0;ON;;;;;N;;;;; 1F6E2;OIL DRUM;So;0;ON;;;;;N;;;;; @@ -31223,6 +31910,8 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1F6F8;FLYING SAUCER;So;0;ON;;;;;N;;;;; 1F6F9;SKATEBOARD;So;0;ON;;;;;N;;;;; 1F6FA;AUTO RICKSHAW;So;0;ON;;;;;N;;;;; +1F6FB;PICKUP TRUCK;So;0;ON;;;;;N;;;;; +1F6FC;ROLLER SKATE;So;0;ON;;;;;N;;;;; 1F700;ALCHEMICAL SYMBOL FOR QUINTESSENCE;So;0;ON;;;;;N;;;;; 1F701;ALCHEMICAL SYMBOL FOR AIR;So;0;ON;;;;;N;;;;; 1F702;ALCHEMICAL SYMBOL FOR FIRE;So;0;ON;;;;;N;;;;; @@ -31588,6 +32277,8 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1F8AB;RIGHTWARDS FRONT-TILTED SHADOWED WHITE ARROW;So;0;ON;;;;;N;;;;; 1F8AC;WHITE ARROW SHAFT WIDTH ONE;So;0;ON;;;;;N;;;;; 1F8AD;WHITE ARROW SHAFT WIDTH TWO THIRDS;So;0;ON;;;;;N;;;;; +1F8B0;ARROW POINTING UPWARDS THEN NORTH WEST;So;0;ON;;;;;N;;;;; +1F8B1;ARROW POINTING RIGHTWARDS THEN CURVING SOUTH WEST;So;0;ON;;;;;N;;;;; 1F900;CIRCLED CROSS FORMEE WITH FOUR DOTS;So;0;ON;;;;;N;;;;; 1F901;CIRCLED CROSS FORMEE WITH TWO DOTS;So;0;ON;;;;;N;;;;; 1F902;CIRCLED CROSS FORMEE;So;0;ON;;;;;N;;;;; @@ -31600,6 +32291,7 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1F909;DOWNWARD FACING NOTCHED HOOK;So;0;ON;;;;;N;;;;; 1F90A;DOWNWARD FACING HOOK WITH DOT;So;0;ON;;;;;N;;;;; 1F90B;DOWNWARD FACING NOTCHED HOOK WITH DOT;So;0;ON;;;;;N;;;;; +1F90C;PINCHED FINGERS;So;0;ON;;;;;N;;;;; 1F90D;WHITE HEART;So;0;ON;;;;;N;;;;; 1F90E;BROWN HEART;So;0;ON;;;;;N;;;;; 1F90F;PINCHING HAND;So;0;ON;;;;;N;;;;; @@ -31701,10 +32393,13 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1F96F;BAGEL;So;0;ON;;;;;N;;;;; 1F970;SMILING FACE WITH SMILING EYES AND THREE HEARTS;So;0;ON;;;;;N;;;;; 1F971;YAWNING FACE;So;0;ON;;;;;N;;;;; +1F972;SMILING FACE WITH TEAR;So;0;ON;;;;;N;;;;; 1F973;FACE WITH PARTY HORN AND PARTY HAT;So;0;ON;;;;;N;;;;; 1F974;FACE WITH UNEVEN EYES AND WAVY MOUTH;So;0;ON;;;;;N;;;;; 1F975;OVERHEATED FACE;So;0;ON;;;;;N;;;;; 1F976;FREEZING FACE;So;0;ON;;;;;N;;;;; +1F977;NINJA;So;0;ON;;;;;N;;;;; +1F978;DISGUISED FACE;So;0;ON;;;;;N;;;;; 1F97A;FACE WITH PLEADING EYES;So;0;ON;;;;;N;;;;; 1F97B;SARI;So;0;ON;;;;;N;;;;; 1F97C;LAB COAT;So;0;ON;;;;;N;;;;; @@ -31746,12 +32441,17 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1F9A0;MICROBE;So;0;ON;;;;;N;;;;; 1F9A1;BADGER;So;0;ON;;;;;N;;;;; 1F9A2;SWAN;So;0;ON;;;;;N;;;;; +1F9A3;MAMMOTH;So;0;ON;;;;;N;;;;; +1F9A4;DODO;So;0;ON;;;;;N;;;;; 1F9A5;SLOTH;So;0;ON;;;;;N;;;;; 1F9A6;OTTER;So;0;ON;;;;;N;;;;; 1F9A7;ORANGUTAN;So;0;ON;;;;;N;;;;; 1F9A8;SKUNK;So;0;ON;;;;;N;;;;; 1F9A9;FLAMINGO;So;0;ON;;;;;N;;;;; 1F9AA;OYSTER;So;0;ON;;;;;N;;;;; +1F9AB;BEAVER;So;0;ON;;;;;N;;;;; +1F9AC;BISON;So;0;ON;;;;;N;;;;; +1F9AD;SEAL;So;0;ON;;;;;N;;;;; 1F9AE;GUIDE DOG;So;0;ON;;;;;N;;;;; 1F9AF;PROBING CANE;So;0;ON;;;;;N;;;;; 1F9B0;EMOJI COMPONENT RED HAIR;So;0;ON;;;;;N;;;;; @@ -31781,6 +32481,7 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1F9C8;BUTTER;So;0;ON;;;;;N;;;;; 1F9C9;MATE DRINK;So;0;ON;;;;;N;;;;; 1F9CA;ICE CUBE;So;0;ON;;;;;N;;;;; +1F9CB;BUBBLE TEA;So;0;ON;;;;;N;;;;; 1F9CD;STANDING PERSON;So;0;ON;;;;;N;;;;; 1F9CE;KNEELING PERSON;So;0;ON;;;;;N;;;;; 1F9CF;DEAF PERSON;So;0;ON;;;;;N;;;;; @@ -31934,20 +32635,273 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1FA71;ONE-PIECE SWIMSUIT;So;0;ON;;;;;N;;;;; 1FA72;BRIEFS;So;0;ON;;;;;N;;;;; 1FA73;SHORTS;So;0;ON;;;;;N;;;;; +1FA74;THONG SANDAL;So;0;ON;;;;;N;;;;; 1FA78;DROP OF BLOOD;So;0;ON;;;;;N;;;;; 1FA79;ADHESIVE BANDAGE;So;0;ON;;;;;N;;;;; 1FA7A;STETHOSCOPE;So;0;ON;;;;;N;;;;; 1FA80;YO-YO;So;0;ON;;;;;N;;;;; 1FA81;KITE;So;0;ON;;;;;N;;;;; 1FA82;PARACHUTE;So;0;ON;;;;;N;;;;; +1FA83;BOOMERANG;So;0;ON;;;;;N;;;;; +1FA84;MAGIC WAND;So;0;ON;;;;;N;;;;; +1FA85;PINATA;So;0;ON;;;;;N;;;;; +1FA86;NESTING DOLLS;So;0;ON;;;;;N;;;;; 1FA90;RINGED PLANET;So;0;ON;;;;;N;;;;; 1FA91;CHAIR;So;0;ON;;;;;N;;;;; 1FA92;RAZOR;So;0;ON;;;;;N;;;;; 1FA93;AXE;So;0;ON;;;;;N;;;;; 1FA94;DIYA LAMP;So;0;ON;;;;;N;;;;; 1FA95;BANJO;So;0;ON;;;;;N;;;;; +1FA96;MILITARY HELMET;So;0;ON;;;;;N;;;;; +1FA97;ACCORDION;So;0;ON;;;;;N;;;;; +1FA98;LONG DRUM;So;0;ON;;;;;N;;;;; +1FA99;COIN;So;0;ON;;;;;N;;;;; +1FA9A;CARPENTRY SAW;So;0;ON;;;;;N;;;;; +1FA9B;SCREWDRIVER;So;0;ON;;;;;N;;;;; +1FA9C;LADDER;So;0;ON;;;;;N;;;;; +1FA9D;HOOK;So;0;ON;;;;;N;;;;; +1FA9E;MIRROR;So;0;ON;;;;;N;;;;; +1FA9F;WINDOW;So;0;ON;;;;;N;;;;; +1FAA0;PLUNGER;So;0;ON;;;;;N;;;;; +1FAA1;SEWING NEEDLE;So;0;ON;;;;;N;;;;; +1FAA2;KNOT;So;0;ON;;;;;N;;;;; +1FAA3;BUCKET;So;0;ON;;;;;N;;;;; +1FAA4;MOUSE TRAP;So;0;ON;;;;;N;;;;; +1FAA5;TOOTHBRUSH;So;0;ON;;;;;N;;;;; +1FAA6;HEADSTONE;So;0;ON;;;;;N;;;;; +1FAA7;PLACARD;So;0;ON;;;;;N;;;;; +1FAA8;ROCK;So;0;ON;;;;;N;;;;; +1FAB0;FLY;So;0;ON;;;;;N;;;;; +1FAB1;WORM;So;0;ON;;;;;N;;;;; +1FAB2;BEETLE;So;0;ON;;;;;N;;;;; +1FAB3;COCKROACH;So;0;ON;;;;;N;;;;; +1FAB4;POTTED PLANT;So;0;ON;;;;;N;;;;; +1FAB5;WOOD;So;0;ON;;;;;N;;;;; +1FAB6;FEATHER;So;0;ON;;;;;N;;;;; +1FAC0;ANATOMICAL HEART;So;0;ON;;;;;N;;;;; +1FAC1;LUNGS;So;0;ON;;;;;N;;;;; +1FAC2;PEOPLE HUGGING;So;0;ON;;;;;N;;;;; +1FAD0;BLUEBERRIES;So;0;ON;;;;;N;;;;; +1FAD1;BELL PEPPER;So;0;ON;;;;;N;;;;; +1FAD2;OLIVE;So;0;ON;;;;;N;;;;; +1FAD3;FLATBREAD;So;0;ON;;;;;N;;;;; +1FAD4;TAMALE;So;0;ON;;;;;N;;;;; +1FAD5;FONDUE;So;0;ON;;;;;N;;;;; +1FAD6;TEAPOT;So;0;ON;;;;;N;;;;; +1FB00;BLOCK SEXTANT-1;So;0;ON;;;;;N;;;;; +1FB01;BLOCK SEXTANT-2;So;0;ON;;;;;N;;;;; +1FB02;BLOCK SEXTANT-12;So;0;ON;;;;;N;;;;; +1FB03;BLOCK SEXTANT-3;So;0;ON;;;;;N;;;;; +1FB04;BLOCK SEXTANT-13;So;0;ON;;;;;N;;;;; +1FB05;BLOCK SEXTANT-23;So;0;ON;;;;;N;;;;; +1FB06;BLOCK SEXTANT-123;So;0;ON;;;;;N;;;;; +1FB07;BLOCK SEXTANT-4;So;0;ON;;;;;N;;;;; +1FB08;BLOCK SEXTANT-14;So;0;ON;;;;;N;;;;; +1FB09;BLOCK SEXTANT-24;So;0;ON;;;;;N;;;;; +1FB0A;BLOCK SEXTANT-124;So;0;ON;;;;;N;;;;; +1FB0B;BLOCK SEXTANT-34;So;0;ON;;;;;N;;;;; +1FB0C;BLOCK SEXTANT-134;So;0;ON;;;;;N;;;;; +1FB0D;BLOCK SEXTANT-234;So;0;ON;;;;;N;;;;; +1FB0E;BLOCK SEXTANT-1234;So;0;ON;;;;;N;;;;; +1FB0F;BLOCK SEXTANT-5;So;0;ON;;;;;N;;;;; +1FB10;BLOCK SEXTANT-15;So;0;ON;;;;;N;;;;; +1FB11;BLOCK SEXTANT-25;So;0;ON;;;;;N;;;;; +1FB12;BLOCK SEXTANT-125;So;0;ON;;;;;N;;;;; +1FB13;BLOCK SEXTANT-35;So;0;ON;;;;;N;;;;; +1FB14;BLOCK SEXTANT-235;So;0;ON;;;;;N;;;;; +1FB15;BLOCK SEXTANT-1235;So;0;ON;;;;;N;;;;; +1FB16;BLOCK SEXTANT-45;So;0;ON;;;;;N;;;;; +1FB17;BLOCK SEXTANT-145;So;0;ON;;;;;N;;;;; +1FB18;BLOCK SEXTANT-245;So;0;ON;;;;;N;;;;; +1FB19;BLOCK SEXTANT-1245;So;0;ON;;;;;N;;;;; +1FB1A;BLOCK SEXTANT-345;So;0;ON;;;;;N;;;;; +1FB1B;BLOCK SEXTANT-1345;So;0;ON;;;;;N;;;;; +1FB1C;BLOCK SEXTANT-2345;So;0;ON;;;;;N;;;;; +1FB1D;BLOCK SEXTANT-12345;So;0;ON;;;;;N;;;;; +1FB1E;BLOCK SEXTANT-6;So;0;ON;;;;;N;;;;; +1FB1F;BLOCK SEXTANT-16;So;0;ON;;;;;N;;;;; +1FB20;BLOCK SEXTANT-26;So;0;ON;;;;;N;;;;; +1FB21;BLOCK SEXTANT-126;So;0;ON;;;;;N;;;;; +1FB22;BLOCK SEXTANT-36;So;0;ON;;;;;N;;;;; +1FB23;BLOCK SEXTANT-136;So;0;ON;;;;;N;;;;; +1FB24;BLOCK SEXTANT-236;So;0;ON;;;;;N;;;;; +1FB25;BLOCK SEXTANT-1236;So;0;ON;;;;;N;;;;; +1FB26;BLOCK SEXTANT-46;So;0;ON;;;;;N;;;;; +1FB27;BLOCK SEXTANT-146;So;0;ON;;;;;N;;;;; +1FB28;BLOCK SEXTANT-1246;So;0;ON;;;;;N;;;;; +1FB29;BLOCK SEXTANT-346;So;0;ON;;;;;N;;;;; +1FB2A;BLOCK SEXTANT-1346;So;0;ON;;;;;N;;;;; +1FB2B;BLOCK SEXTANT-2346;So;0;ON;;;;;N;;;;; +1FB2C;BLOCK SEXTANT-12346;So;0;ON;;;;;N;;;;; +1FB2D;BLOCK SEXTANT-56;So;0;ON;;;;;N;;;;; +1FB2E;BLOCK SEXTANT-156;So;0;ON;;;;;N;;;;; +1FB2F;BLOCK SEXTANT-256;So;0;ON;;;;;N;;;;; +1FB30;BLOCK SEXTANT-1256;So;0;ON;;;;;N;;;;; +1FB31;BLOCK SEXTANT-356;So;0;ON;;;;;N;;;;; +1FB32;BLOCK SEXTANT-1356;So;0;ON;;;;;N;;;;; +1FB33;BLOCK SEXTANT-2356;So;0;ON;;;;;N;;;;; +1FB34;BLOCK SEXTANT-12356;So;0;ON;;;;;N;;;;; +1FB35;BLOCK SEXTANT-456;So;0;ON;;;;;N;;;;; +1FB36;BLOCK SEXTANT-1456;So;0;ON;;;;;N;;;;; +1FB37;BLOCK SEXTANT-2456;So;0;ON;;;;;N;;;;; +1FB38;BLOCK SEXTANT-12456;So;0;ON;;;;;N;;;;; +1FB39;BLOCK SEXTANT-3456;So;0;ON;;;;;N;;;;; +1FB3A;BLOCK SEXTANT-13456;So;0;ON;;;;;N;;;;; +1FB3B;BLOCK SEXTANT-23456;So;0;ON;;;;;N;;;;; +1FB3C;LOWER LEFT BLOCK DIAGONAL LOWER MIDDLE LEFT TO LOWER CENTRE;So;0;ON;;;;;N;;;;; +1FB3D;LOWER LEFT BLOCK DIAGONAL LOWER MIDDLE LEFT TO LOWER RIGHT;So;0;ON;;;;;N;;;;; +1FB3E;LOWER LEFT BLOCK DIAGONAL UPPER MIDDLE LEFT TO LOWER CENTRE;So;0;ON;;;;;N;;;;; +1FB3F;LOWER LEFT BLOCK DIAGONAL UPPER MIDDLE LEFT TO LOWER RIGHT;So;0;ON;;;;;N;;;;; +1FB40;LOWER LEFT BLOCK DIAGONAL UPPER LEFT TO LOWER CENTRE;So;0;ON;;;;;N;;;;; +1FB41;LOWER RIGHT BLOCK DIAGONAL UPPER MIDDLE LEFT TO UPPER CENTRE;So;0;ON;;;;;N;;;;; +1FB42;LOWER RIGHT BLOCK DIAGONAL UPPER MIDDLE LEFT TO UPPER RIGHT;So;0;ON;;;;;N;;;;; +1FB43;LOWER RIGHT BLOCK DIAGONAL LOWER MIDDLE LEFT TO UPPER CENTRE;So;0;ON;;;;;N;;;;; +1FB44;LOWER RIGHT BLOCK DIAGONAL LOWER MIDDLE LEFT TO UPPER RIGHT;So;0;ON;;;;;N;;;;; +1FB45;LOWER RIGHT BLOCK DIAGONAL LOWER LEFT TO UPPER CENTRE;So;0;ON;;;;;N;;;;; +1FB46;LOWER RIGHT BLOCK DIAGONAL LOWER MIDDLE LEFT TO UPPER MIDDLE RIGHT;So;0;ON;;;;;N;;;;; +1FB47;LOWER RIGHT BLOCK DIAGONAL LOWER CENTRE TO LOWER MIDDLE RIGHT;So;0;ON;;;;;N;;;;; +1FB48;LOWER RIGHT BLOCK DIAGONAL LOWER LEFT TO LOWER MIDDLE RIGHT;So;0;ON;;;;;N;;;;; +1FB49;LOWER RIGHT BLOCK DIAGONAL LOWER CENTRE TO UPPER MIDDLE RIGHT;So;0;ON;;;;;N;;;;; +1FB4A;LOWER RIGHT BLOCK DIAGONAL LOWER LEFT TO UPPER MIDDLE RIGHT;So;0;ON;;;;;N;;;;; +1FB4B;LOWER RIGHT BLOCK DIAGONAL LOWER CENTRE TO UPPER RIGHT;So;0;ON;;;;;N;;;;; +1FB4C;LOWER LEFT BLOCK DIAGONAL UPPER CENTRE TO UPPER MIDDLE RIGHT;So;0;ON;;;;;N;;;;; +1FB4D;LOWER LEFT BLOCK DIAGONAL UPPER LEFT TO UPPER MIDDLE RIGHT;So;0;ON;;;;;N;;;;; +1FB4E;LOWER LEFT BLOCK DIAGONAL UPPER CENTRE TO LOWER MIDDLE RIGHT;So;0;ON;;;;;N;;;;; +1FB4F;LOWER LEFT BLOCK DIAGONAL UPPER LEFT TO LOWER MIDDLE RIGHT;So;0;ON;;;;;N;;;;; +1FB50;LOWER LEFT BLOCK DIAGONAL UPPER CENTRE TO LOWER RIGHT;So;0;ON;;;;;N;;;;; +1FB51;LOWER LEFT BLOCK DIAGONAL UPPER MIDDLE LEFT TO LOWER MIDDLE RIGHT;So;0;ON;;;;;N;;;;; +1FB52;UPPER RIGHT BLOCK DIAGONAL LOWER MIDDLE LEFT TO LOWER CENTRE;So;0;ON;;;;;N;;;;; +1FB53;UPPER RIGHT BLOCK DIAGONAL LOWER MIDDLE LEFT TO LOWER RIGHT;So;0;ON;;;;;N;;;;; +1FB54;UPPER RIGHT BLOCK DIAGONAL UPPER MIDDLE LEFT TO LOWER CENTRE;So;0;ON;;;;;N;;;;; +1FB55;UPPER RIGHT BLOCK DIAGONAL UPPER MIDDLE LEFT TO LOWER RIGHT;So;0;ON;;;;;N;;;;; +1FB56;UPPER RIGHT BLOCK DIAGONAL UPPER LEFT TO LOWER CENTRE;So;0;ON;;;;;N;;;;; +1FB57;UPPER LEFT BLOCK DIAGONAL UPPER MIDDLE LEFT TO UPPER CENTRE;So;0;ON;;;;;N;;;;; +1FB58;UPPER LEFT BLOCK DIAGONAL UPPER MIDDLE LEFT TO UPPER RIGHT;So;0;ON;;;;;N;;;;; +1FB59;UPPER LEFT BLOCK DIAGONAL LOWER MIDDLE LEFT TO UPPER CENTRE;So;0;ON;;;;;N;;;;; +1FB5A;UPPER LEFT BLOCK DIAGONAL LOWER MIDDLE LEFT TO UPPER RIGHT;So;0;ON;;;;;N;;;;; +1FB5B;UPPER LEFT BLOCK DIAGONAL LOWER LEFT TO UPPER CENTRE;So;0;ON;;;;;N;;;;; +1FB5C;UPPER LEFT BLOCK DIAGONAL LOWER MIDDLE LEFT TO UPPER MIDDLE RIGHT;So;0;ON;;;;;N;;;;; +1FB5D;UPPER LEFT BLOCK DIAGONAL LOWER CENTRE TO LOWER MIDDLE RIGHT;So;0;ON;;;;;N;;;;; +1FB5E;UPPER LEFT BLOCK DIAGONAL LOWER LEFT TO LOWER MIDDLE RIGHT;So;0;ON;;;;;N;;;;; +1FB5F;UPPER LEFT BLOCK DIAGONAL LOWER CENTRE TO UPPER MIDDLE RIGHT;So;0;ON;;;;;N;;;;; +1FB60;UPPER LEFT BLOCK DIAGONAL LOWER LEFT TO UPPER MIDDLE RIGHT;So;0;ON;;;;;N;;;;; +1FB61;UPPER LEFT BLOCK DIAGONAL LOWER CENTRE TO UPPER RIGHT;So;0;ON;;;;;N;;;;; +1FB62;UPPER RIGHT BLOCK DIAGONAL UPPER CENTRE TO UPPER MIDDLE RIGHT;So;0;ON;;;;;N;;;;; +1FB63;UPPER RIGHT BLOCK DIAGONAL UPPER LEFT TO UPPER MIDDLE RIGHT;So;0;ON;;;;;N;;;;; +1FB64;UPPER RIGHT BLOCK DIAGONAL UPPER CENTRE TO LOWER MIDDLE RIGHT;So;0;ON;;;;;N;;;;; +1FB65;UPPER RIGHT BLOCK DIAGONAL UPPER LEFT TO LOWER MIDDLE RIGHT;So;0;ON;;;;;N;;;;; +1FB66;UPPER RIGHT BLOCK DIAGONAL UPPER CENTRE TO LOWER RIGHT;So;0;ON;;;;;N;;;;; +1FB67;UPPER RIGHT BLOCK DIAGONAL UPPER MIDDLE LEFT TO LOWER MIDDLE RIGHT;So;0;ON;;;;;N;;;;; +1FB68;UPPER AND RIGHT AND LOWER TRIANGULAR THREE QUARTERS BLOCK;So;0;ON;;;;;N;;;;; +1FB69;LEFT AND LOWER AND RIGHT TRIANGULAR THREE QUARTERS BLOCK;So;0;ON;;;;;N;;;;; +1FB6A;UPPER AND LEFT AND LOWER TRIANGULAR THREE QUARTERS BLOCK;So;0;ON;;;;;N;;;;; +1FB6B;LEFT AND UPPER AND RIGHT TRIANGULAR THREE QUARTERS BLOCK;So;0;ON;;;;;N;;;;; +1FB6C;LEFT TRIANGULAR ONE QUARTER BLOCK;So;0;ON;;;;;N;;;;; +1FB6D;UPPER TRIANGULAR ONE QUARTER BLOCK;So;0;ON;;;;;N;;;;; +1FB6E;RIGHT TRIANGULAR ONE QUARTER BLOCK;So;0;ON;;;;;N;;;;; +1FB6F;LOWER TRIANGULAR ONE QUARTER BLOCK;So;0;ON;;;;;N;;;;; +1FB70;VERTICAL ONE EIGHTH BLOCK-2;So;0;ON;;;;;N;;;;; +1FB71;VERTICAL ONE EIGHTH BLOCK-3;So;0;ON;;;;;N;;;;; +1FB72;VERTICAL ONE EIGHTH BLOCK-4;So;0;ON;;;;;N;;;;; +1FB73;VERTICAL ONE EIGHTH BLOCK-5;So;0;ON;;;;;N;;;;; +1FB74;VERTICAL ONE EIGHTH BLOCK-6;So;0;ON;;;;;N;;;;; +1FB75;VERTICAL ONE EIGHTH BLOCK-7;So;0;ON;;;;;N;;;;; +1FB76;HORIZONTAL ONE EIGHTH BLOCK-2;So;0;ON;;;;;N;;;;; +1FB77;HORIZONTAL ONE EIGHTH BLOCK-3;So;0;ON;;;;;N;;;;; +1FB78;HORIZONTAL ONE EIGHTH BLOCK-4;So;0;ON;;;;;N;;;;; +1FB79;HORIZONTAL ONE EIGHTH BLOCK-5;So;0;ON;;;;;N;;;;; +1FB7A;HORIZONTAL ONE EIGHTH BLOCK-6;So;0;ON;;;;;N;;;;; +1FB7B;HORIZONTAL ONE EIGHTH BLOCK-7;So;0;ON;;;;;N;;;;; +1FB7C;LEFT AND LOWER ONE EIGHTH BLOCK;So;0;ON;;;;;N;;;;; +1FB7D;LEFT AND UPPER ONE EIGHTH BLOCK;So;0;ON;;;;;N;;;;; +1FB7E;RIGHT AND UPPER ONE EIGHTH BLOCK;So;0;ON;;;;;N;;;;; +1FB7F;RIGHT AND LOWER ONE EIGHTH BLOCK;So;0;ON;;;;;N;;;;; +1FB80;UPPER AND LOWER ONE EIGHTH BLOCK;So;0;ON;;;;;N;;;;; +1FB81;HORIZONTAL ONE EIGHTH BLOCK-1358;So;0;ON;;;;;N;;;;; +1FB82;UPPER ONE QUARTER BLOCK;So;0;ON;;;;;N;;;;; +1FB83;UPPER THREE EIGHTHS BLOCK;So;0;ON;;;;;N;;;;; +1FB84;UPPER FIVE EIGHTHS BLOCK;So;0;ON;;;;;N;;;;; +1FB85;UPPER THREE QUARTERS BLOCK;So;0;ON;;;;;N;;;;; +1FB86;UPPER SEVEN EIGHTHS BLOCK;So;0;ON;;;;;N;;;;; +1FB87;RIGHT ONE QUARTER BLOCK;So;0;ON;;;;;N;;;;; +1FB88;RIGHT THREE EIGHTHS BLOCK;So;0;ON;;;;;N;;;;; +1FB89;RIGHT FIVE EIGHTHS BLOCK;So;0;ON;;;;;N;;;;; +1FB8A;RIGHT THREE QUARTERS BLOCK;So;0;ON;;;;;N;;;;; +1FB8B;RIGHT SEVEN EIGHTHS BLOCK;So;0;ON;;;;;N;;;;; +1FB8C;LEFT HALF MEDIUM SHADE;So;0;ON;;;;;N;;;;; +1FB8D;RIGHT HALF MEDIUM SHADE;So;0;ON;;;;;N;;;;; +1FB8E;UPPER HALF MEDIUM SHADE;So;0;ON;;;;;N;;;;; +1FB8F;LOWER HALF MEDIUM SHADE;So;0;ON;;;;;N;;;;; +1FB90;INVERSE MEDIUM SHADE;So;0;ON;;;;;N;;;;; +1FB91;UPPER HALF BLOCK AND LOWER HALF INVERSE MEDIUM SHADE;So;0;ON;;;;;N;;;;; +1FB92;UPPER HALF INVERSE MEDIUM SHADE AND LOWER HALF BLOCK;So;0;ON;;;;;N;;;;; +1FB94;LEFT HALF INVERSE MEDIUM SHADE AND RIGHT HALF BLOCK;So;0;ON;;;;;N;;;;; +1FB95;CHECKER BOARD FILL;So;0;ON;;;;;N;;;;; +1FB96;INVERSE CHECKER BOARD FILL;So;0;ON;;;;;N;;;;; +1FB97;HEAVY HORIZONTAL FILL;So;0;ON;;;;;N;;;;; +1FB98;UPPER LEFT TO LOWER RIGHT FILL;So;0;ON;;;;;N;;;;; +1FB99;UPPER RIGHT TO LOWER LEFT FILL;So;0;ON;;;;;N;;;;; +1FB9A;UPPER AND LOWER TRIANGULAR HALF BLOCK;So;0;ON;;;;;N;;;;; +1FB9B;LEFT AND RIGHT TRIANGULAR HALF BLOCK;So;0;ON;;;;;N;;;;; +1FB9C;UPPER LEFT TRIANGULAR MEDIUM SHADE;So;0;ON;;;;;N;;;;; +1FB9D;UPPER RIGHT TRIANGULAR MEDIUM SHADE;So;0;ON;;;;;N;;;;; +1FB9E;LOWER RIGHT TRIANGULAR MEDIUM SHADE;So;0;ON;;;;;N;;;;; +1FB9F;LOWER LEFT TRIANGULAR MEDIUM SHADE;So;0;ON;;;;;N;;;;; +1FBA0;BOX DRAWINGS LIGHT DIAGONAL UPPER CENTRE TO MIDDLE LEFT;So;0;ON;;;;;N;;;;; +1FBA1;BOX DRAWINGS LIGHT DIAGONAL UPPER CENTRE TO MIDDLE RIGHT;So;0;ON;;;;;N;;;;; +1FBA2;BOX DRAWINGS LIGHT DIAGONAL MIDDLE LEFT TO LOWER CENTRE;So;0;ON;;;;;N;;;;; +1FBA3;BOX DRAWINGS LIGHT DIAGONAL MIDDLE RIGHT TO LOWER CENTRE;So;0;ON;;;;;N;;;;; +1FBA4;BOX DRAWINGS LIGHT DIAGONAL UPPER CENTRE TO MIDDLE LEFT TO LOWER CENTRE;So;0;ON;;;;;N;;;;; +1FBA5;BOX DRAWINGS LIGHT DIAGONAL UPPER CENTRE TO MIDDLE RIGHT TO LOWER CENTRE;So;0;ON;;;;;N;;;;; +1FBA6;BOX DRAWINGS LIGHT DIAGONAL MIDDLE LEFT TO LOWER CENTRE TO MIDDLE RIGHT;So;0;ON;;;;;N;;;;; +1FBA7;BOX DRAWINGS LIGHT DIAGONAL MIDDLE LEFT TO UPPER CENTRE TO MIDDLE RIGHT;So;0;ON;;;;;N;;;;; +1FBA8;BOX DRAWINGS LIGHT DIAGONAL UPPER CENTRE TO MIDDLE LEFT AND MIDDLE RIGHT TO LOWER CENTRE;So;0;ON;;;;;N;;;;; +1FBA9;BOX DRAWINGS LIGHT DIAGONAL UPPER CENTRE TO MIDDLE RIGHT AND MIDDLE LEFT TO LOWER CENTRE;So;0;ON;;;;;N;;;;; +1FBAA;BOX DRAWINGS LIGHT DIAGONAL UPPER CENTRE TO MIDDLE RIGHT TO LOWER CENTRE TO MIDDLE LEFT;So;0;ON;;;;;N;;;;; +1FBAB;BOX DRAWINGS LIGHT DIAGONAL UPPER CENTRE TO MIDDLE LEFT TO LOWER CENTRE TO MIDDLE RIGHT;So;0;ON;;;;;N;;;;; +1FBAC;BOX DRAWINGS LIGHT DIAGONAL MIDDLE LEFT TO UPPER CENTRE TO MIDDLE RIGHT TO LOWER CENTRE;So;0;ON;;;;;N;;;;; +1FBAD;BOX DRAWINGS LIGHT DIAGONAL MIDDLE RIGHT TO UPPER CENTRE TO MIDDLE LEFT TO LOWER CENTRE;So;0;ON;;;;;N;;;;; +1FBAE;BOX DRAWINGS LIGHT DIAGONAL DIAMOND;So;0;ON;;;;;N;;;;; +1FBAF;BOX DRAWINGS LIGHT HORIZONTAL WITH VERTICAL STROKE;So;0;ON;;;;;N;;;;; +1FBB0;ARROWHEAD-SHAPED POINTER;So;0;ON;;;;;N;;;;; +1FBB1;INVERSE CHECK MARK;So;0;ON;;;;;N;;;;; +1FBB2;LEFT HALF RUNNING MAN;So;0;ON;;;;;N;;;;; +1FBB3;RIGHT HALF RUNNING MAN;So;0;ON;;;;;N;;;;; +1FBB4;INVERSE DOWNWARDS ARROW WITH TIP LEFTWARDS;So;0;ON;;;;;N;;;;; +1FBB5;LEFTWARDS ARROW AND UPPER AND LOWER ONE EIGHTH BLOCK;So;0;ON;;;;;N;;;;; +1FBB6;RIGHTWARDS ARROW AND UPPER AND LOWER ONE EIGHTH BLOCK;So;0;ON;;;;;N;;;;; +1FBB7;DOWNWARDS ARROW AND RIGHT ONE EIGHTH BLOCK;So;0;ON;;;;;N;;;;; +1FBB8;UPWARDS ARROW AND RIGHT ONE EIGHTH BLOCK;So;0;ON;;;;;N;;;;; +1FBB9;LEFT HALF FOLDER;So;0;ON;;;;;N;;;;; +1FBBA;RIGHT HALF FOLDER;So;0;ON;;;;;N;;;;; +1FBBB;VOIDED GREEK CROSS;So;0;ON;;;;;N;;;;; +1FBBC;RIGHT OPEN SQUARED DOT;So;0;ON;;;;;N;;;;; +1FBBD;NEGATIVE DIAGONAL CROSS;So;0;ON;;;;;N;;;;; +1FBBE;NEGATIVE DIAGONAL MIDDLE RIGHT TO LOWER CENTRE;So;0;ON;;;;;N;;;;; +1FBBF;NEGATIVE DIAGONAL DIAMOND;So;0;ON;;;;;N;;;;; +1FBC0;WHITE HEAVY SALTIRE WITH ROUNDED CORNERS;So;0;ON;;;;;N;;;;; +1FBC1;LEFT THIRD WHITE RIGHT POINTING INDEX;So;0;ON;;;;;N;;;;; +1FBC2;MIDDLE THIRD WHITE RIGHT POINTING INDEX;So;0;ON;;;;;N;;;;; +1FBC3;RIGHT THIRD WHITE RIGHT POINTING INDEX;So;0;ON;;;;;N;;;;; +1FBC4;NEGATIVE SQUARED QUESTION MARK;So;0;ON;;;;;N;;;;; +1FBC5;STICK FIGURE;So;0;ON;;;;;N;;;;; +1FBC6;STICK FIGURE WITH ARMS RAISED;So;0;ON;;;;;N;;;;; +1FBC7;STICK FIGURE LEANING LEFT;So;0;ON;;;;;N;;;;; +1FBC8;STICK FIGURE LEANING RIGHT;So;0;ON;;;;;N;;;;; +1FBC9;STICK FIGURE WITH DRESS;So;0;ON;;;;;N;;;;; +1FBCA;WHITE UP-POINTING CHEVRON;So;0;ON;;;;;N;;;;; +1FBF0;SEGMENTED DIGIT ZERO;Nd;0;EN;<font> 0030;0;0;0;N;;;;; +1FBF1;SEGMENTED DIGIT ONE;Nd;0;EN;<font> 0031;1;1;1;N;;;;; +1FBF2;SEGMENTED DIGIT TWO;Nd;0;EN;<font> 0032;2;2;2;N;;;;; +1FBF3;SEGMENTED DIGIT THREE;Nd;0;EN;<font> 0033;3;3;3;N;;;;; +1FBF4;SEGMENTED DIGIT FOUR;Nd;0;EN;<font> 0034;4;4;4;N;;;;; +1FBF5;SEGMENTED DIGIT FIVE;Nd;0;EN;<font> 0035;5;5;5;N;;;;; +1FBF6;SEGMENTED DIGIT SIX;Nd;0;EN;<font> 0036;6;6;6;N;;;;; +1FBF7;SEGMENTED DIGIT SEVEN;Nd;0;EN;<font> 0037;7;7;7;N;;;;; +1FBF8;SEGMENTED DIGIT EIGHT;Nd;0;EN;<font> 0038;8;8;8;N;;;;; +1FBF9;SEGMENTED DIGIT NINE;Nd;0;EN;<font> 0039;9;9;9;N;;;;; 20000;<CJK Ideograph Extension B, First>;Lo;0;L;;;;;N;;;;; -2A6D6;<CJK Ideograph Extension B, Last>;Lo;0;L;;;;;N;;;;; +2A6DD;<CJK Ideograph Extension B, Last>;Lo;0;L;;;;;N;;;;; 2A700;<CJK Ideograph Extension C, First>;Lo;0;L;;;;;N;;;;; 2B734;<CJK Ideograph Extension C, Last>;Lo;0;L;;;;;N;;;;; 2B740;<CJK Ideograph Extension D, First>;Lo;0;L;;;;;N;;;;; @@ -32498,6 +33452,8 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 2FA1B;CJK COMPATIBILITY IDEOGRAPH-2FA1B;Lo;0;L;9F16;;;;N;;;;; 2FA1C;CJK COMPATIBILITY IDEOGRAPH-2FA1C;Lo;0;L;9F3B;;;;N;;;;; 2FA1D;CJK COMPATIBILITY IDEOGRAPH-2FA1D;Lo;0;L;2A600;;;;N;;;;; +30000;<CJK Ideograph Extension G, First>;Lo;0;L;;;;;N;;;;; +3134A;<CJK Ideograph Extension G, Last>;Lo;0;L;;;;;N;;;;; E0001;LANGUAGE TAG;Cf;0;BN;;;;;N;;;;; E0020;TAG SPACE;Cf;0;BN;;;;;N;;;;; E0021;TAG EXCLAMATION MARK;Cf;0;BN;;;;;N;;;;; diff --git a/unicode/emoji-data.txt b/unicode/emoji-data.txt index 2fb5c3ff68..5d7dc1b156 100644 --- a/unicode/emoji-data.txt +++ b/unicode/emoji-data.txt @@ -1,11 +1,11 @@ # emoji-data.txt -# Date: 2019-01-15, 12:10:05 GMT -# ยฉ 2019 Unicodeยฎ, Inc. +# Date: 2020-01-28, 20:52:38 GMT +# ยฉ 2020 Unicodeยฎ, Inc. # Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. # For terms of use, see http://www.unicode.org/terms_of_use.html # # Emoji Data for UTS #51 -# Version: 12.0 +# Version: 13.0 # # For documentation and usage, see http://www.unicode.org/reports/tr51 # @@ -22,414 +22,667 @@ # All omitted code points have Emoji=No # @missing: 0000..10FFFF ; Emoji ; No -0023 ; Emoji # 1.1 [1] (#๏ธ) number sign -002A ; Emoji # 1.1 [1] (*๏ธ) asterisk -0030..0039 ; Emoji # 1.1 [10] (0๏ธ..9๏ธ) digit zero..digit nine -00A9 ; Emoji # 1.1 [1] (ยฉ๏ธ) copyright -00AE ; Emoji # 1.1 [1] (ยฎ๏ธ) registered -203C ; Emoji # 1.1 [1] (โผ๏ธ) double exclamation mark -2049 ; Emoji # 3.0 [1] (โ๏ธ) exclamation question mark -2122 ; Emoji # 1.1 [1] (โข๏ธ) trade mark -2139 ; Emoji # 3.0 [1] (โน๏ธ) information -2194..2199 ; Emoji # 1.1 [6] (โ๏ธ..โ๏ธ) left-right arrow..down-left arrow -21A9..21AA ; Emoji # 1.1 [2] (โฉ๏ธ..โช๏ธ) right arrow curving left..left arrow curving right -231A..231B ; Emoji # 1.1 [2] (โ..โ) watch..hourglass done -2328 ; Emoji # 1.1 [1] (โจ๏ธ) keyboard -23CF ; Emoji # 4.0 [1] (โ๏ธ) eject button -23E9..23F3 ; Emoji # 6.0 [11] (โฉ..โณ) fast-forward button..hourglass not done -23F8..23FA ; Emoji # 7.0 [3] (โธ๏ธ..โบ๏ธ) pause button..record button -24C2 ; Emoji # 1.1 [1] (โ๏ธ) circled M -25AA..25AB ; Emoji # 1.1 [2] (โช๏ธ..โซ๏ธ) black small square..white small square -25B6 ; Emoji # 1.1 [1] (โถ๏ธ) play button -25C0 ; Emoji # 1.1 [1] (โ๏ธ) reverse button -25FB..25FE ; Emoji # 3.2 [4] (โป๏ธ..โพ) white medium square..black medium-small square -2600..2604 ; Emoji # 1.1 [5] (โ๏ธ..โ๏ธ) sun..comet -260E ; Emoji # 1.1 [1] (โ๏ธ) telephone -2611 ; Emoji # 1.1 [1] (โ๏ธ) check box with check -2614..2615 ; Emoji # 4.0 [2] (โ..โ) umbrella with rain drops..hot beverage -2618 ; Emoji # 4.1 [1] (โ๏ธ) shamrock -261D ; Emoji # 1.1 [1] (โ๏ธ) index pointing up -2620 ; Emoji # 1.1 [1] (โ ๏ธ) skull and crossbones -2622..2623 ; Emoji # 1.1 [2] (โข๏ธ..โฃ๏ธ) radioactive..biohazard -2626 ; Emoji # 1.1 [1] (โฆ๏ธ) orthodox cross -262A ; Emoji # 1.1 [1] (โช๏ธ) star and crescent -262E..262F ; Emoji # 1.1 [2] (โฎ๏ธ..โฏ๏ธ) peace symbol..yin yang -2638..263A ; Emoji # 1.1 [3] (โธ๏ธ..โบ๏ธ) wheel of dharma..smiling face -2640 ; Emoji # 1.1 [1] (โ๏ธ) female sign -2642 ; Emoji # 1.1 [1] (โ๏ธ) male sign -2648..2653 ; Emoji # 1.1 [12] (โ..โ) Aries..Pisces -265F..2660 ; Emoji # 1.1 [2] (โ๏ธ..โ ๏ธ) chess pawn..spade suit -2663 ; Emoji # 1.1 [1] (โฃ๏ธ) club suit -2665..2666 ; Emoji # 1.1 [2] (โฅ๏ธ..โฆ๏ธ) heart suit..diamond suit -2668 ; Emoji # 1.1 [1] (โจ๏ธ) hot springs -267B ; Emoji # 3.2 [1] (โป๏ธ) recycling symbol -267E..267F ; Emoji # 4.1 [2] (โพ๏ธ..โฟ) infinity..wheelchair symbol -2692..2697 ; Emoji # 4.1 [6] (โ๏ธ..โ๏ธ) hammer and pick..alembic -2699 ; Emoji # 4.1 [1] (โ๏ธ) gear -269B..269C ; Emoji # 4.1 [2] (โ๏ธ..โ๏ธ) atom symbol..fleur-de-lis -26A0..26A1 ; Emoji # 4.0 [2] (โ ๏ธ..โก) warning..high voltage -26AA..26AB ; Emoji # 4.1 [2] (โช..โซ) white circle..black circle -26B0..26B1 ; Emoji # 4.1 [2] (โฐ๏ธ..โฑ๏ธ) coffin..funeral urn -26BD..26BE ; Emoji # 5.2 [2] (โฝ..โพ) soccer ball..baseball -26C4..26C5 ; Emoji # 5.2 [2] (โ..โ
) snowman without snow..sun behind cloud -26C8 ; Emoji # 5.2 [1] (โ๏ธ) cloud with lightning and rain -26CE ; Emoji # 6.0 [1] (โ) Ophiuchus -26CF ; Emoji # 5.2 [1] (โ๏ธ) pick -26D1 ; Emoji # 5.2 [1] (โ๏ธ) rescue workerโs helmet -26D3..26D4 ; Emoji # 5.2 [2] (โ๏ธ..โ) chains..no entry -26E9..26EA ; Emoji # 5.2 [2] (โฉ๏ธ..โช) shinto shrine..church -26F0..26F5 ; Emoji # 5.2 [6] (โฐ๏ธ..โต) mountain..sailboat -26F7..26FA ; Emoji # 5.2 [4] (โท๏ธ..โบ) skier..tent -26FD ; Emoji # 5.2 [1] (โฝ) fuel pump -2702 ; Emoji # 1.1 [1] (โ๏ธ) scissors -2705 ; Emoji # 6.0 [1] (โ
) check mark button -2708..2709 ; Emoji # 1.1 [2] (โ๏ธ..โ๏ธ) airplane..envelope -270A..270B ; Emoji # 6.0 [2] (โ..โ) raised fist..raised hand -270C..270D ; Emoji # 1.1 [2] (โ๏ธ..โ๏ธ) victory hand..writing hand -270F ; Emoji # 1.1 [1] (โ๏ธ) pencil -2712 ; Emoji # 1.1 [1] (โ๏ธ) black nib -2714 ; Emoji # 1.1 [1] (โ๏ธ) check mark -2716 ; Emoji # 1.1 [1] (โ๏ธ) multiplication sign -271D ; Emoji # 1.1 [1] (โ๏ธ) latin cross -2721 ; Emoji # 1.1 [1] (โก๏ธ) star of David -2728 ; Emoji # 6.0 [1] (โจ) sparkles -2733..2734 ; Emoji # 1.1 [2] (โณ๏ธ..โด๏ธ) eight-spoked asterisk..eight-pointed star -2744 ; Emoji # 1.1 [1] (โ๏ธ) snowflake -2747 ; Emoji # 1.1 [1] (โ๏ธ) sparkle -274C ; Emoji # 6.0 [1] (โ) cross mark -274E ; Emoji # 6.0 [1] (โ) cross mark button -2753..2755 ; Emoji # 6.0 [3] (โ..โ) question mark..white exclamation mark -2757 ; Emoji # 5.2 [1] (โ) exclamation mark -2763..2764 ; Emoji # 1.1 [2] (โฃ๏ธ..โค๏ธ) heart exclamation..red heart -2795..2797 ; Emoji # 6.0 [3] (โ..โ) plus sign..division sign -27A1 ; Emoji # 1.1 [1] (โก๏ธ) right arrow -27B0 ; Emoji # 6.0 [1] (โฐ) curly loop -27BF ; Emoji # 6.0 [1] (โฟ) double curly loop -2934..2935 ; Emoji # 3.2 [2] (โคด๏ธ..โคต๏ธ) right arrow curving up..right arrow curving down -2B05..2B07 ; Emoji # 4.0 [3] (โฌ
๏ธ..โฌ๏ธ) left arrow..down arrow -2B1B..2B1C ; Emoji # 5.1 [2] (โฌ..โฌ) black large square..white large square -2B50 ; Emoji # 5.1 [1] (โญ) star -2B55 ; Emoji # 5.2 [1] (โญ) hollow red circle -3030 ; Emoji # 1.1 [1] (ใฐ๏ธ) wavy dash -303D ; Emoji # 3.2 [1] (ใฝ๏ธ) part alternation mark -3297 ; Emoji # 1.1 [1] (ใ๏ธ) Japanese โcongratulationsโ button -3299 ; Emoji # 1.1 [1] (ใ๏ธ) Japanese โsecretโ button -1F004 ; Emoji # 5.1 [1] (๐) mahjong red dragon -1F0CF ; Emoji # 6.0 [1] (๐) joker -1F170..1F171 ; Emoji # 6.0 [2] (๐
ฐ๏ธ..๐
ฑ๏ธ) A button (blood type)..B button (blood type) -1F17E ; Emoji # 6.0 [1] (๐
พ๏ธ) O button (blood type) -1F17F ; Emoji # 5.2 [1] (๐
ฟ๏ธ) P button -1F18E ; Emoji # 6.0 [1] (๐) AB button (blood type) -1F191..1F19A ; Emoji # 6.0 [10] (๐..๐) CL button..VS button -1F1E6..1F1FF ; Emoji # 6.0 [26] (๐ฆ..๐ฟ) regional indicator symbol letter a..regional indicator symbol letter z -1F201..1F202 ; Emoji # 6.0 [2] (๐..๐๏ธ) Japanese โhereโ button..Japanese โservice chargeโ button -1F21A ; Emoji # 5.2 [1] (๐) Japanese โfree of chargeโ button -1F22F ; Emoji # 5.2 [1] (๐ฏ) Japanese โreservedโ button -1F232..1F23A ; Emoji # 6.0 [9] (๐ฒ..๐บ) Japanese โprohibitedโ button..Japanese โopen for businessโ button -1F250..1F251 ; Emoji # 6.0 [2] (๐..๐) Japanese โbargainโ button..Japanese โacceptableโ button -1F300..1F320 ; Emoji # 6.0 [33] (๐..๐ ) cyclone..shooting star -1F321 ; Emoji # 7.0 [1] (๐ก๏ธ) thermometer -1F324..1F32C ; Emoji # 7.0 [9] (๐ค๏ธ..๐ฌ๏ธ) sun behind small cloud..wind face -1F32D..1F32F ; Emoji # 8.0 [3] (๐ญ..๐ฏ) hot dog..burrito -1F330..1F335 ; Emoji # 6.0 [6] (๐ฐ..๐ต) chestnut..cactus -1F336 ; Emoji # 7.0 [1] (๐ถ๏ธ) hot pepper -1F337..1F37C ; Emoji # 6.0 [70] (๐ท..๐ผ) tulip..baby bottle -1F37D ; Emoji # 7.0 [1] (๐ฝ๏ธ) fork and knife with plate -1F37E..1F37F ; Emoji # 8.0 [2] (๐พ..๐ฟ) bottle with popping cork..popcorn -1F380..1F393 ; Emoji # 6.0 [20] (๐..๐) ribbon..graduation cap -1F396..1F397 ; Emoji # 7.0 [2] (๐๏ธ..๐๏ธ) military medal..reminder ribbon -1F399..1F39B ; Emoji # 7.0 [3] (๐๏ธ..๐๏ธ) studio microphone..control knobs -1F39E..1F39F ; Emoji # 7.0 [2] (๐๏ธ..๐๏ธ) film frames..admission tickets -1F3A0..1F3C4 ; Emoji # 6.0 [37] (๐ ..๐) carousel horse..person surfing -1F3C5 ; Emoji # 7.0 [1] (๐
) sports medal -1F3C6..1F3CA ; Emoji # 6.0 [5] (๐..๐) trophy..person swimming -1F3CB..1F3CE ; Emoji # 7.0 [4] (๐๏ธ..๐๏ธ) person lifting weights..racing car -1F3CF..1F3D3 ; Emoji # 8.0 [5] (๐..๐) cricket game..ping pong -1F3D4..1F3DF ; Emoji # 7.0 [12] (๐๏ธ..๐๏ธ) snow-capped mountain..stadium -1F3E0..1F3F0 ; Emoji # 6.0 [17] (๐ ..๐ฐ) house..castle -1F3F3..1F3F5 ; Emoji # 7.0 [3] (๐ณ๏ธ..๐ต๏ธ) white flag..rosette -1F3F7 ; Emoji # 7.0 [1] (๐ท๏ธ) label -1F3F8..1F3FF ; Emoji # 8.0 [8] (๐ธ..๐ฟ) badminton..dark skin tone -1F400..1F43E ; Emoji # 6.0 [63] (๐..๐พ) rat..paw prints -1F43F ; Emoji # 7.0 [1] (๐ฟ๏ธ) chipmunk -1F440 ; Emoji # 6.0 [1] (๐) eyes -1F441 ; Emoji # 7.0 [1] (๐๏ธ) eye -1F442..1F4F7 ; Emoji # 6.0[182] (๐..๐ท) ear..camera -1F4F8 ; Emoji # 7.0 [1] (๐ธ) camera with flash -1F4F9..1F4FC ; Emoji # 6.0 [4] (๐น..๐ผ) video camera..videocassette -1F4FD ; Emoji # 7.0 [1] (๐ฝ๏ธ) film projector -1F4FF ; Emoji # 8.0 [1] (๐ฟ) prayer beads -1F500..1F53D ; Emoji # 6.0 [62] (๐..๐ฝ) shuffle tracks button..downwards button -1F549..1F54A ; Emoji # 7.0 [2] (๐๏ธ..๐๏ธ) om..dove -1F54B..1F54E ; Emoji # 8.0 [4] (๐..๐) kaaba..menorah -1F550..1F567 ; Emoji # 6.0 [24] (๐..๐ง) one oโclock..twelve-thirty -1F56F..1F570 ; Emoji # 7.0 [2] (๐ฏ๏ธ..๐ฐ๏ธ) candle..mantelpiece clock -1F573..1F579 ; Emoji # 7.0 [7] (๐ณ๏ธ..๐น๏ธ) hole..joystick -1F57A ; Emoji # 9.0 [1] (๐บ) man dancing -1F587 ; Emoji # 7.0 [1] (๐๏ธ) linked paperclips -1F58A..1F58D ; Emoji # 7.0 [4] (๐๏ธ..๐๏ธ) pen..crayon -1F590 ; Emoji # 7.0 [1] (๐๏ธ) hand with fingers splayed -1F595..1F596 ; Emoji # 7.0 [2] (๐..๐) middle finger..vulcan salute -1F5A4 ; Emoji # 9.0 [1] (๐ค) black heart -1F5A5 ; Emoji # 7.0 [1] (๐ฅ๏ธ) desktop computer -1F5A8 ; Emoji # 7.0 [1] (๐จ๏ธ) printer -1F5B1..1F5B2 ; Emoji # 7.0 [2] (๐ฑ๏ธ..๐ฒ๏ธ) computer mouse..trackball -1F5BC ; Emoji # 7.0 [1] (๐ผ๏ธ) framed picture -1F5C2..1F5C4 ; Emoji # 7.0 [3] (๐๏ธ..๐๏ธ) card index dividers..file cabinet -1F5D1..1F5D3 ; Emoji # 7.0 [3] (๐๏ธ..๐๏ธ) wastebasket..spiral calendar -1F5DC..1F5DE ; Emoji # 7.0 [3] (๐๏ธ..๐๏ธ) clamp..rolled-up newspaper -1F5E1 ; Emoji # 7.0 [1] (๐ก๏ธ) dagger -1F5E3 ; Emoji # 7.0 [1] (๐ฃ๏ธ) speaking head -1F5E8 ; Emoji # 7.0 [1] (๐จ๏ธ) left speech bubble -1F5EF ; Emoji # 7.0 [1] (๐ฏ๏ธ) right anger bubble -1F5F3 ; Emoji # 7.0 [1] (๐ณ๏ธ) ballot box with ballot -1F5FA ; Emoji # 7.0 [1] (๐บ๏ธ) world map -1F5FB..1F5FF ; Emoji # 6.0 [5] (๐ป..๐ฟ) mount fuji..moai -1F600 ; Emoji # 6.1 [1] (๐) grinning face -1F601..1F610 ; Emoji # 6.0 [16] (๐..๐) beaming face with smiling eyes..neutral face -1F611 ; Emoji # 6.1 [1] (๐) expressionless face -1F612..1F614 ; Emoji # 6.0 [3] (๐..๐) unamused face..pensive face -1F615 ; Emoji # 6.1 [1] (๐) confused face -1F616 ; Emoji # 6.0 [1] (๐) confounded face -1F617 ; Emoji # 6.1 [1] (๐) kissing face -1F618 ; Emoji # 6.0 [1] (๐) face blowing a kiss -1F619 ; Emoji # 6.1 [1] (๐) kissing face with smiling eyes -1F61A ; Emoji # 6.0 [1] (๐) kissing face with closed eyes -1F61B ; Emoji # 6.1 [1] (๐) face with tongue -1F61C..1F61E ; Emoji # 6.0 [3] (๐..๐) winking face with tongue..disappointed face -1F61F ; Emoji # 6.1 [1] (๐) worried face -1F620..1F625 ; Emoji # 6.0 [6] (๐ ..๐ฅ) angry face..sad but relieved face -1F626..1F627 ; Emoji # 6.1 [2] (๐ฆ..๐ง) frowning face with open mouth..anguished face -1F628..1F62B ; Emoji # 6.0 [4] (๐จ..๐ซ) fearful face..tired face -1F62C ; Emoji # 6.1 [1] (๐ฌ) grimacing face -1F62D ; Emoji # 6.0 [1] (๐ญ) loudly crying face -1F62E..1F62F ; Emoji # 6.1 [2] (๐ฎ..๐ฏ) face with open mouth..hushed face -1F630..1F633 ; Emoji # 6.0 [4] (๐ฐ..๐ณ) anxious face with sweat..flushed face -1F634 ; Emoji # 6.1 [1] (๐ด) sleeping face -1F635..1F640 ; Emoji # 6.0 [12] (๐ต..๐) dizzy face..weary cat -1F641..1F642 ; Emoji # 7.0 [2] (๐..๐) slightly frowning face..slightly smiling face -1F643..1F644 ; Emoji # 8.0 [2] (๐..๐) upside-down face..face with rolling eyes -1F645..1F64F ; Emoji # 6.0 [11] (๐
..๐) person gesturing NO..folded hands -1F680..1F6C5 ; Emoji # 6.0 [70] (๐..๐
) rocket..left luggage -1F6CB..1F6CF ; Emoji # 7.0 [5] (๐๏ธ..๐๏ธ) couch and lamp..bed -1F6D0 ; Emoji # 8.0 [1] (๐) place of worship -1F6D1..1F6D2 ; Emoji # 9.0 [2] (๐..๐) stop sign..shopping cart -1F6D5 ; Emoji # 12.0 [1] (๐) hindu temple -1F6E0..1F6E5 ; Emoji # 7.0 [6] (๐ ๏ธ..๐ฅ๏ธ) hammer and wrench..motor boat -1F6E9 ; Emoji # 7.0 [1] (๐ฉ๏ธ) small airplane -1F6EB..1F6EC ; Emoji # 7.0 [2] (๐ซ..๐ฌ) airplane departure..airplane arrival -1F6F0 ; Emoji # 7.0 [1] (๐ฐ๏ธ) satellite -1F6F3 ; Emoji # 7.0 [1] (๐ณ๏ธ) passenger ship -1F6F4..1F6F6 ; Emoji # 9.0 [3] (๐ด..๐ถ) kick scooter..canoe -1F6F7..1F6F8 ; Emoji # 10.0 [2] (๐ท..๐ธ) sled..flying saucer -1F6F9 ; Emoji # 11.0 [1] (๐น) skateboard -1F6FA ; Emoji # 12.0 [1] (๐บ) auto rickshaw -1F7E0..1F7EB ; Emoji # 12.0 [12] (๐ ..๐ซ) orange circle..brown square -1F90D..1F90F ; Emoji # 12.0 [3] (๐ค..๐ค) white heart..pinching hand -1F910..1F918 ; Emoji # 8.0 [9] (๐ค..๐ค) zipper-mouth face..sign of the horns -1F919..1F91E ; Emoji # 9.0 [6] (๐ค..๐ค) call me hand..crossed fingers -1F91F ; Emoji # 10.0 [1] (๐ค) love-you gesture -1F920..1F927 ; Emoji # 9.0 [8] (๐ค ..๐คง) cowboy hat face..sneezing face -1F928..1F92F ; Emoji # 10.0 [8] (๐คจ..๐คฏ) face with raised eyebrow..exploding head -1F930 ; Emoji # 9.0 [1] (๐คฐ) pregnant woman -1F931..1F932 ; Emoji # 10.0 [2] (๐คฑ..๐คฒ) breast-feeding..palms up together -1F933..1F93A ; Emoji # 9.0 [8] (๐คณ..๐คบ) selfie..person fencing -1F93C..1F93E ; Emoji # 9.0 [3] (๐คผ..๐คพ) people wrestling..person playing handball -1F93F ; Emoji # 12.0 [1] (๐คฟ) diving mask -1F940..1F945 ; Emoji # 9.0 [6] (๐ฅ..๐ฅ
) wilted flower..goal net -1F947..1F94B ; Emoji # 9.0 [5] (๐ฅ..๐ฅ) 1st place medal..martial arts uniform -1F94C ; Emoji # 10.0 [1] (๐ฅ) curling stone -1F94D..1F94F ; Emoji # 11.0 [3] (๐ฅ..๐ฅ) lacrosse..flying disc -1F950..1F95E ; Emoji # 9.0 [15] (๐ฅ..๐ฅ) croissant..pancakes -1F95F..1F96B ; Emoji # 10.0 [13] (๐ฅ..๐ฅซ) dumpling..canned food -1F96C..1F970 ; Emoji # 11.0 [5] (๐ฅฌ..๐ฅฐ) leafy green..smiling face with hearts -1F971 ; Emoji # 12.0 [1] (๐ฅฑ) yawning face -1F973..1F976 ; Emoji # 11.0 [4] (๐ฅณ..๐ฅถ) partying face..cold face -1F97A ; Emoji # 11.0 [1] (๐ฅบ) pleading face -1F97B ; Emoji # 12.0 [1] (๐ฅป) sari -1F97C..1F97F ; Emoji # 11.0 [4] (๐ฅผ..๐ฅฟ) lab coat..flat shoe -1F980..1F984 ; Emoji # 8.0 [5] (๐ฆ..๐ฆ) crab..unicorn -1F985..1F991 ; Emoji # 9.0 [13] (๐ฆ
..๐ฆ) eagle..squid -1F992..1F997 ; Emoji # 10.0 [6] (๐ฆ..๐ฆ) giraffe..cricket -1F998..1F9A2 ; Emoji # 11.0 [11] (๐ฆ..๐ฆข) kangaroo..swan -1F9A5..1F9AA ; Emoji # 12.0 [6] (๐ฆฅ..๐ฆช) sloth..oyster -1F9AE..1F9AF ; Emoji # 12.0 [2] (๐ฆฎ..๐ฆฏ) guide dog..probing cane -1F9B0..1F9B9 ; Emoji # 11.0 [10] (๐ฆฐ..๐ฆน) red hair..supervillain -1F9BA..1F9BF ; Emoji # 12.0 [6] (๐ฆบ..๐ฆฟ) safety vest..mechanical leg -1F9C0 ; Emoji # 8.0 [1] (๐ง) cheese wedge -1F9C1..1F9C2 ; Emoji # 11.0 [2] (๐ง..๐ง) cupcake..salt -1F9C3..1F9CA ; Emoji # 12.0 [8] (๐ง..๐ง) beverage box..ice cube -1F9CD..1F9CF ; Emoji # 12.0 [3] (๐ง..๐ง) person standing..deaf person -1F9D0..1F9E6 ; Emoji # 10.0 [23] (๐ง..๐งฆ) face with monocle..socks -1F9E7..1F9FF ; Emoji # 11.0 [25] (๐งง..๐งฟ) red envelope..nazar amulet -1FA70..1FA73 ; Emoji # 12.0 [4] (๐ฉฐ..๐ฉณ) ballet shoes..shorts -1FA78..1FA7A ; Emoji # 12.0 [3] (๐ฉธ..๐ฉบ) drop of blood..stethoscope -1FA80..1FA82 ; Emoji # 12.0 [3] (๐ช..๐ช) yo-yo..parachute -1FA90..1FA95 ; Emoji # 12.0 [6] (๐ช..๐ช) ringed planet..banjo +0023 ; Emoji # E0.0 [1] (#๏ธ) number sign +002A ; Emoji # E0.0 [1] (*๏ธ) asterisk +0030..0039 ; Emoji # E0.0 [10] (0๏ธ..9๏ธ) digit zero..digit nine +00A9 ; Emoji # E0.6 [1] (ยฉ๏ธ) copyright +00AE ; Emoji # E0.6 [1] (ยฎ๏ธ) registered +203C ; Emoji # E0.6 [1] (โผ๏ธ) double exclamation mark +2049 ; Emoji # E0.6 [1] (โ๏ธ) exclamation question mark +2122 ; Emoji # E0.6 [1] (โข๏ธ) trade mark +2139 ; Emoji # E0.6 [1] (โน๏ธ) information +2194..2199 ; Emoji # E0.6 [6] (โ๏ธ..โ๏ธ) left-right arrow..down-left arrow +21A9..21AA ; Emoji # E0.6 [2] (โฉ๏ธ..โช๏ธ) right arrow curving left..left arrow curving right +231A..231B ; Emoji # E0.6 [2] (โ..โ) watch..hourglass done +2328 ; Emoji # E1.0 [1] (โจ๏ธ) keyboard +23CF ; Emoji # E1.0 [1] (โ๏ธ) eject button +23E9..23EC ; Emoji # E0.6 [4] (โฉ..โฌ) fast-forward button..fast down button +23ED..23EE ; Emoji # E0.7 [2] (โญ๏ธ..โฎ๏ธ) next track button..last track button +23EF ; Emoji # E1.0 [1] (โฏ๏ธ) play or pause button +23F0 ; Emoji # E0.6 [1] (โฐ) alarm clock +23F1..23F2 ; Emoji # E1.0 [2] (โฑ๏ธ..โฒ๏ธ) stopwatch..timer clock +23F3 ; Emoji # E0.6 [1] (โณ) hourglass not done +23F8..23FA ; Emoji # E0.7 [3] (โธ๏ธ..โบ๏ธ) pause button..record button +24C2 ; Emoji # E0.6 [1] (โ๏ธ) circled M +25AA..25AB ; Emoji # E0.6 [2] (โช๏ธ..โซ๏ธ) black small square..white small square +25B6 ; Emoji # E0.6 [1] (โถ๏ธ) play button +25C0 ; Emoji # E0.6 [1] (โ๏ธ) reverse button +25FB..25FE ; Emoji # E0.6 [4] (โป๏ธ..โพ) white medium square..black medium-small square +2600..2601 ; Emoji # E0.6 [2] (โ๏ธ..โ๏ธ) sun..cloud +2602..2603 ; Emoji # E0.7 [2] (โ๏ธ..โ๏ธ) umbrella..snowman +2604 ; Emoji # E1.0 [1] (โ๏ธ) comet +260E ; Emoji # E0.6 [1] (โ๏ธ) telephone +2611 ; Emoji # E0.6 [1] (โ๏ธ) check box with check +2614..2615 ; Emoji # E0.6 [2] (โ..โ) umbrella with rain drops..hot beverage +2618 ; Emoji # E1.0 [1] (โ๏ธ) shamrock +261D ; Emoji # E0.6 [1] (โ๏ธ) index pointing up +2620 ; Emoji # E1.0 [1] (โ ๏ธ) skull and crossbones +2622..2623 ; Emoji # E1.0 [2] (โข๏ธ..โฃ๏ธ) radioactive..biohazard +2626 ; Emoji # E1.0 [1] (โฆ๏ธ) orthodox cross +262A ; Emoji # E0.7 [1] (โช๏ธ) star and crescent +262E ; Emoji # E1.0 [1] (โฎ๏ธ) peace symbol +262F ; Emoji # E0.7 [1] (โฏ๏ธ) yin yang +2638..2639 ; Emoji # E0.7 [2] (โธ๏ธ..โน๏ธ) wheel of dharma..frowning face +263A ; Emoji # E0.6 [1] (โบ๏ธ) smiling face +2640 ; Emoji # E4.0 [1] (โ๏ธ) female sign +2642 ; Emoji # E4.0 [1] (โ๏ธ) male sign +2648..2653 ; Emoji # E0.6 [12] (โ..โ) Aries..Pisces +265F ; Emoji # E11.0 [1] (โ๏ธ) chess pawn +2660 ; Emoji # E0.6 [1] (โ ๏ธ) spade suit +2663 ; Emoji # E0.6 [1] (โฃ๏ธ) club suit +2665..2666 ; Emoji # E0.6 [2] (โฅ๏ธ..โฆ๏ธ) heart suit..diamond suit +2668 ; Emoji # E0.6 [1] (โจ๏ธ) hot springs +267B ; Emoji # E0.6 [1] (โป๏ธ) recycling symbol +267E ; Emoji # E11.0 [1] (โพ๏ธ) infinity +267F ; Emoji # E0.6 [1] (โฟ) wheelchair symbol +2692 ; Emoji # E1.0 [1] (โ๏ธ) hammer and pick +2693 ; Emoji # E0.6 [1] (โ) anchor +2694 ; Emoji # E1.0 [1] (โ๏ธ) crossed swords +2695 ; Emoji # E4.0 [1] (โ๏ธ) medical symbol +2696..2697 ; Emoji # E1.0 [2] (โ๏ธ..โ๏ธ) balance scale..alembic +2699 ; Emoji # E1.0 [1] (โ๏ธ) gear +269B..269C ; Emoji # E1.0 [2] (โ๏ธ..โ๏ธ) atom symbol..fleur-de-lis +26A0..26A1 ; Emoji # E0.6 [2] (โ ๏ธ..โก) warning..high voltage +26A7 ; Emoji # E13.0 [1] (โง๏ธ) transgender symbol +26AA..26AB ; Emoji # E0.6 [2] (โช..โซ) white circle..black circle +26B0..26B1 ; Emoji # E1.0 [2] (โฐ๏ธ..โฑ๏ธ) coffin..funeral urn +26BD..26BE ; Emoji # E0.6 [2] (โฝ..โพ) soccer ball..baseball +26C4..26C5 ; Emoji # E0.6 [2] (โ..โ
) snowman without snow..sun behind cloud +26C8 ; Emoji # E0.7 [1] (โ๏ธ) cloud with lightning and rain +26CE ; Emoji # E0.6 [1] (โ) Ophiuchus +26CF ; Emoji # E0.7 [1] (โ๏ธ) pick +26D1 ; Emoji # E0.7 [1] (โ๏ธ) rescue workerโs helmet +26D3 ; Emoji # E0.7 [1] (โ๏ธ) chains +26D4 ; Emoji # E0.6 [1] (โ) no entry +26E9 ; Emoji # E0.7 [1] (โฉ๏ธ) shinto shrine +26EA ; Emoji # E0.6 [1] (โช) church +26F0..26F1 ; Emoji # E0.7 [2] (โฐ๏ธ..โฑ๏ธ) mountain..umbrella on ground +26F2..26F3 ; Emoji # E0.6 [2] (โฒ..โณ) fountain..flag in hole +26F4 ; Emoji # E0.7 [1] (โด๏ธ) ferry +26F5 ; Emoji # E0.6 [1] (โต) sailboat +26F7..26F9 ; Emoji # E0.7 [3] (โท๏ธ..โน๏ธ) skier..person bouncing ball +26FA ; Emoji # E0.6 [1] (โบ) tent +26FD ; Emoji # E0.6 [1] (โฝ) fuel pump +2702 ; Emoji # E0.6 [1] (โ๏ธ) scissors +2705 ; Emoji # E0.6 [1] (โ
) check mark button +2708..270C ; Emoji # E0.6 [5] (โ๏ธ..โ๏ธ) airplane..victory hand +270D ; Emoji # E0.7 [1] (โ๏ธ) writing hand +270F ; Emoji # E0.6 [1] (โ๏ธ) pencil +2712 ; Emoji # E0.6 [1] (โ๏ธ) black nib +2714 ; Emoji # E0.6 [1] (โ๏ธ) check mark +2716 ; Emoji # E0.6 [1] (โ๏ธ) multiply +271D ; Emoji # E0.7 [1] (โ๏ธ) latin cross +2721 ; Emoji # E0.7 [1] (โก๏ธ) star of David +2728 ; Emoji # E0.6 [1] (โจ) sparkles +2733..2734 ; Emoji # E0.6 [2] (โณ๏ธ..โด๏ธ) eight-spoked asterisk..eight-pointed star +2744 ; Emoji # E0.6 [1] (โ๏ธ) snowflake +2747 ; Emoji # E0.6 [1] (โ๏ธ) sparkle +274C ; Emoji # E0.6 [1] (โ) cross mark +274E ; Emoji # E0.6 [1] (โ) cross mark button +2753..2755 ; Emoji # E0.6 [3] (โ..โ) question mark..white exclamation mark +2757 ; Emoji # E0.6 [1] (โ) exclamation mark +2763 ; Emoji # E1.0 [1] (โฃ๏ธ) heart exclamation +2764 ; Emoji # E0.6 [1] (โค๏ธ) red heart +2795..2797 ; Emoji # E0.6 [3] (โ..โ) plus..divide +27A1 ; Emoji # E0.6 [1] (โก๏ธ) right arrow +27B0 ; Emoji # E0.6 [1] (โฐ) curly loop +27BF ; Emoji # E1.0 [1] (โฟ) double curly loop +2934..2935 ; Emoji # E0.6 [2] (โคด๏ธ..โคต๏ธ) right arrow curving up..right arrow curving down +2B05..2B07 ; Emoji # E0.6 [3] (โฌ
๏ธ..โฌ๏ธ) left arrow..down arrow +2B1B..2B1C ; Emoji # E0.6 [2] (โฌ..โฌ) black large square..white large square +2B50 ; Emoji # E0.6 [1] (โญ) star +2B55 ; Emoji # E0.6 [1] (โญ) hollow red circle +3030 ; Emoji # E0.6 [1] (ใฐ๏ธ) wavy dash +303D ; Emoji # E0.6 [1] (ใฝ๏ธ) part alternation mark +3297 ; Emoji # E0.6 [1] (ใ๏ธ) Japanese โcongratulationsโ button +3299 ; Emoji # E0.6 [1] (ใ๏ธ) Japanese โsecretโ button +1F004 ; Emoji # E0.6 [1] (๐) mahjong red dragon +1F0CF ; Emoji # E0.6 [1] (๐) joker +1F170..1F171 ; Emoji # E0.6 [2] (๐
ฐ๏ธ..๐
ฑ๏ธ) A button (blood type)..B button (blood type) +1F17E..1F17F ; Emoji # E0.6 [2] (๐
พ๏ธ..๐
ฟ๏ธ) O button (blood type)..P button +1F18E ; Emoji # E0.6 [1] (๐) AB button (blood type) +1F191..1F19A ; Emoji # E0.6 [10] (๐..๐) CL button..VS button +1F1E6..1F1FF ; Emoji # E0.0 [26] (๐ฆ..๐ฟ) regional indicator symbol letter a..regional indicator symbol letter z +1F201..1F202 ; Emoji # E0.6 [2] (๐..๐๏ธ) Japanese โhereโ button..Japanese โservice chargeโ button +1F21A ; Emoji # E0.6 [1] (๐) Japanese โfree of chargeโ button +1F22F ; Emoji # E0.6 [1] (๐ฏ) Japanese โreservedโ button +1F232..1F23A ; Emoji # E0.6 [9] (๐ฒ..๐บ) Japanese โprohibitedโ button..Japanese โopen for businessโ button +1F250..1F251 ; Emoji # E0.6 [2] (๐..๐) Japanese โbargainโ button..Japanese โacceptableโ button +1F300..1F30C ; Emoji # E0.6 [13] (๐..๐) cyclone..milky way +1F30D..1F30E ; Emoji # E0.7 [2] (๐..๐) globe showing Europe-Africa..globe showing Americas +1F30F ; Emoji # E0.6 [1] (๐) globe showing Asia-Australia +1F310 ; Emoji # E1.0 [1] (๐) globe with meridians +1F311 ; Emoji # E0.6 [1] (๐) new moon +1F312 ; Emoji # E1.0 [1] (๐) waxing crescent moon +1F313..1F315 ; Emoji # E0.6 [3] (๐..๐) first quarter moon..full moon +1F316..1F318 ; Emoji # E1.0 [3] (๐..๐) waning gibbous moon..waning crescent moon +1F319 ; Emoji # E0.6 [1] (๐) crescent moon +1F31A ; Emoji # E1.0 [1] (๐) new moon face +1F31B ; Emoji # E0.6 [1] (๐) first quarter moon face +1F31C ; Emoji # E0.7 [1] (๐) last quarter moon face +1F31D..1F31E ; Emoji # E1.0 [2] (๐..๐) full moon face..sun with face +1F31F..1F320 ; Emoji # E0.6 [2] (๐..๐ ) glowing star..shooting star +1F321 ; Emoji # E0.7 [1] (๐ก๏ธ) thermometer +1F324..1F32C ; Emoji # E0.7 [9] (๐ค๏ธ..๐ฌ๏ธ) sun behind small cloud..wind face +1F32D..1F32F ; Emoji # E1.0 [3] (๐ญ..๐ฏ) hot dog..burrito +1F330..1F331 ; Emoji # E0.6 [2] (๐ฐ..๐ฑ) chestnut..seedling +1F332..1F333 ; Emoji # E1.0 [2] (๐ฒ..๐ณ) evergreen tree..deciduous tree +1F334..1F335 ; Emoji # E0.6 [2] (๐ด..๐ต) palm tree..cactus +1F336 ; Emoji # E0.7 [1] (๐ถ๏ธ) hot pepper +1F337..1F34A ; Emoji # E0.6 [20] (๐ท..๐) tulip..tangerine +1F34B ; Emoji # E1.0 [1] (๐) lemon +1F34C..1F34F ; Emoji # E0.6 [4] (๐..๐) banana..green apple +1F350 ; Emoji # E1.0 [1] (๐) pear +1F351..1F37B ; Emoji # E0.6 [43] (๐..๐ป) peach..clinking beer mugs +1F37C ; Emoji # E1.0 [1] (๐ผ) baby bottle +1F37D ; Emoji # E0.7 [1] (๐ฝ๏ธ) fork and knife with plate +1F37E..1F37F ; Emoji # E1.0 [2] (๐พ..๐ฟ) bottle with popping cork..popcorn +1F380..1F393 ; Emoji # E0.6 [20] (๐..๐) ribbon..graduation cap +1F396..1F397 ; Emoji # E0.7 [2] (๐๏ธ..๐๏ธ) military medal..reminder ribbon +1F399..1F39B ; Emoji # E0.7 [3] (๐๏ธ..๐๏ธ) studio microphone..control knobs +1F39E..1F39F ; Emoji # E0.7 [2] (๐๏ธ..๐๏ธ) film frames..admission tickets +1F3A0..1F3C4 ; Emoji # E0.6 [37] (๐ ..๐) carousel horse..person surfing +1F3C5 ; Emoji # E1.0 [1] (๐
) sports medal +1F3C6 ; Emoji # E0.6 [1] (๐) trophy +1F3C7 ; Emoji # E1.0 [1] (๐) horse racing +1F3C8 ; Emoji # E0.6 [1] (๐) american football +1F3C9 ; Emoji # E1.0 [1] (๐) rugby football +1F3CA ; Emoji # E0.6 [1] (๐) person swimming +1F3CB..1F3CE ; Emoji # E0.7 [4] (๐๏ธ..๐๏ธ) person lifting weights..racing car +1F3CF..1F3D3 ; Emoji # E1.0 [5] (๐..๐) cricket game..ping pong +1F3D4..1F3DF ; Emoji # E0.7 [12] (๐๏ธ..๐๏ธ) snow-capped mountain..stadium +1F3E0..1F3E3 ; Emoji # E0.6 [4] (๐ ..๐ฃ) house..Japanese post office +1F3E4 ; Emoji # E1.0 [1] (๐ค) post office +1F3E5..1F3F0 ; Emoji # E0.6 [12] (๐ฅ..๐ฐ) hospital..castle +1F3F3 ; Emoji # E0.7 [1] (๐ณ๏ธ) white flag +1F3F4 ; Emoji # E1.0 [1] (๐ด) black flag +1F3F5 ; Emoji # E0.7 [1] (๐ต๏ธ) rosette +1F3F7 ; Emoji # E0.7 [1] (๐ท๏ธ) label +1F3F8..1F407 ; Emoji # E1.0 [16] (๐ธ..๐) badminton..rabbit +1F408 ; Emoji # E0.7 [1] (๐) cat +1F409..1F40B ; Emoji # E1.0 [3] (๐..๐) dragon..whale +1F40C..1F40E ; Emoji # E0.6 [3] (๐..๐) snail..horse +1F40F..1F410 ; Emoji # E1.0 [2] (๐..๐) ram..goat +1F411..1F412 ; Emoji # E0.6 [2] (๐..๐) ewe..monkey +1F413 ; Emoji # E1.0 [1] (๐) rooster +1F414 ; Emoji # E0.6 [1] (๐) chicken +1F415 ; Emoji # E0.7 [1] (๐) dog +1F416 ; Emoji # E1.0 [1] (๐) pig +1F417..1F429 ; Emoji # E0.6 [19] (๐..๐ฉ) boar..poodle +1F42A ; Emoji # E1.0 [1] (๐ช) camel +1F42B..1F43E ; Emoji # E0.6 [20] (๐ซ..๐พ) two-hump camel..paw prints +1F43F ; Emoji # E0.7 [1] (๐ฟ๏ธ) chipmunk +1F440 ; Emoji # E0.6 [1] (๐) eyes +1F441 ; Emoji # E0.7 [1] (๐๏ธ) eye +1F442..1F464 ; Emoji # E0.6 [35] (๐..๐ค) ear..bust in silhouette +1F465 ; Emoji # E1.0 [1] (๐ฅ) busts in silhouette +1F466..1F46B ; Emoji # E0.6 [6] (๐ฆ..๐ซ) boy..woman and man holding hands +1F46C..1F46D ; Emoji # E1.0 [2] (๐ฌ..๐ญ) men holding hands..women holding hands +1F46E..1F4AC ; Emoji # E0.6 [63] (๐ฎ..๐ฌ) police officer..speech balloon +1F4AD ; Emoji # E1.0 [1] (๐ญ) thought balloon +1F4AE..1F4B5 ; Emoji # E0.6 [8] (๐ฎ..๐ต) white flower..dollar banknote +1F4B6..1F4B7 ; Emoji # E1.0 [2] (๐ถ..๐ท) euro banknote..pound banknote +1F4B8..1F4EB ; Emoji # E0.6 [52] (๐ธ..๐ซ) money with wings..closed mailbox with raised flag +1F4EC..1F4ED ; Emoji # E0.7 [2] (๐ฌ..๐ญ) open mailbox with raised flag..open mailbox with lowered flag +1F4EE ; Emoji # E0.6 [1] (๐ฎ) postbox +1F4EF ; Emoji # E1.0 [1] (๐ฏ) postal horn +1F4F0..1F4F4 ; Emoji # E0.6 [5] (๐ฐ..๐ด) newspaper..mobile phone off +1F4F5 ; Emoji # E1.0 [1] (๐ต) no mobile phones +1F4F6..1F4F7 ; Emoji # E0.6 [2] (๐ถ..๐ท) antenna bars..camera +1F4F8 ; Emoji # E1.0 [1] (๐ธ) camera with flash +1F4F9..1F4FC ; Emoji # E0.6 [4] (๐น..๐ผ) video camera..videocassette +1F4FD ; Emoji # E0.7 [1] (๐ฝ๏ธ) film projector +1F4FF..1F502 ; Emoji # E1.0 [4] (๐ฟ..๐) prayer beads..repeat single button +1F503 ; Emoji # E0.6 [1] (๐) clockwise vertical arrows +1F504..1F507 ; Emoji # E1.0 [4] (๐..๐) counterclockwise arrows button..muted speaker +1F508 ; Emoji # E0.7 [1] (๐) speaker low volume +1F509 ; Emoji # E1.0 [1] (๐) speaker medium volume +1F50A..1F514 ; Emoji # E0.6 [11] (๐..๐) speaker high volume..bell +1F515 ; Emoji # E1.0 [1] (๐) bell with slash +1F516..1F52B ; Emoji # E0.6 [22] (๐..๐ซ) bookmark..pistol +1F52C..1F52D ; Emoji # E1.0 [2] (๐ฌ..๐ญ) microscope..telescope +1F52E..1F53D ; Emoji # E0.6 [16] (๐ฎ..๐ฝ) crystal ball..downwards button +1F549..1F54A ; Emoji # E0.7 [2] (๐๏ธ..๐๏ธ) om..dove +1F54B..1F54E ; Emoji # E1.0 [4] (๐..๐) kaaba..menorah +1F550..1F55B ; Emoji # E0.6 [12] (๐..๐) one oโclock..twelve oโclock +1F55C..1F567 ; Emoji # E0.7 [12] (๐..๐ง) one-thirty..twelve-thirty +1F56F..1F570 ; Emoji # E0.7 [2] (๐ฏ๏ธ..๐ฐ๏ธ) candle..mantelpiece clock +1F573..1F579 ; Emoji # E0.7 [7] (๐ณ๏ธ..๐น๏ธ) hole..joystick +1F57A ; Emoji # E3.0 [1] (๐บ) man dancing +1F587 ; Emoji # E0.7 [1] (๐๏ธ) linked paperclips +1F58A..1F58D ; Emoji # E0.7 [4] (๐๏ธ..๐๏ธ) pen..crayon +1F590 ; Emoji # E0.7 [1] (๐๏ธ) hand with fingers splayed +1F595..1F596 ; Emoji # E1.0 [2] (๐..๐) middle finger..vulcan salute +1F5A4 ; Emoji # E3.0 [1] (๐ค) black heart +1F5A5 ; Emoji # E0.7 [1] (๐ฅ๏ธ) desktop computer +1F5A8 ; Emoji # E0.7 [1] (๐จ๏ธ) printer +1F5B1..1F5B2 ; Emoji # E0.7 [2] (๐ฑ๏ธ..๐ฒ๏ธ) computer mouse..trackball +1F5BC ; Emoji # E0.7 [1] (๐ผ๏ธ) framed picture +1F5C2..1F5C4 ; Emoji # E0.7 [3] (๐๏ธ..๐๏ธ) card index dividers..file cabinet +1F5D1..1F5D3 ; Emoji # E0.7 [3] (๐๏ธ..๐๏ธ) wastebasket..spiral calendar +1F5DC..1F5DE ; Emoji # E0.7 [3] (๐๏ธ..๐๏ธ) clamp..rolled-up newspaper +1F5E1 ; Emoji # E0.7 [1] (๐ก๏ธ) dagger +1F5E3 ; Emoji # E0.7 [1] (๐ฃ๏ธ) speaking head +1F5E8 ; Emoji # E2.0 [1] (๐จ๏ธ) left speech bubble +1F5EF ; Emoji # E0.7 [1] (๐ฏ๏ธ) right anger bubble +1F5F3 ; Emoji # E0.7 [1] (๐ณ๏ธ) ballot box with ballot +1F5FA ; Emoji # E0.7 [1] (๐บ๏ธ) world map +1F5FB..1F5FF ; Emoji # E0.6 [5] (๐ป..๐ฟ) mount fuji..moai +1F600 ; Emoji # E1.0 [1] (๐) grinning face +1F601..1F606 ; Emoji # E0.6 [6] (๐..๐) beaming face with smiling eyes..grinning squinting face +1F607..1F608 ; Emoji # E1.0 [2] (๐..๐) smiling face with halo..smiling face with horns +1F609..1F60D ; Emoji # E0.6 [5] (๐..๐) winking face..smiling face with heart-eyes +1F60E ; Emoji # E1.0 [1] (๐) smiling face with sunglasses +1F60F ; Emoji # E0.6 [1] (๐) smirking face +1F610 ; Emoji # E0.7 [1] (๐) neutral face +1F611 ; Emoji # E1.0 [1] (๐) expressionless face +1F612..1F614 ; Emoji # E0.6 [3] (๐..๐) unamused face..pensive face +1F615 ; Emoji # E1.0 [1] (๐) confused face +1F616 ; Emoji # E0.6 [1] (๐) confounded face +1F617 ; Emoji # E1.0 [1] (๐) kissing face +1F618 ; Emoji # E0.6 [1] (๐) face blowing a kiss +1F619 ; Emoji # E1.0 [1] (๐) kissing face with smiling eyes +1F61A ; Emoji # E0.6 [1] (๐) kissing face with closed eyes +1F61B ; Emoji # E1.0 [1] (๐) face with tongue +1F61C..1F61E ; Emoji # E0.6 [3] (๐..๐) winking face with tongue..disappointed face +1F61F ; Emoji # E1.0 [1] (๐) worried face +1F620..1F625 ; Emoji # E0.6 [6] (๐ ..๐ฅ) angry face..sad but relieved face +1F626..1F627 ; Emoji # E1.0 [2] (๐ฆ..๐ง) frowning face with open mouth..anguished face +1F628..1F62B ; Emoji # E0.6 [4] (๐จ..๐ซ) fearful face..tired face +1F62C ; Emoji # E1.0 [1] (๐ฌ) grimacing face +1F62D ; Emoji # E0.6 [1] (๐ญ) loudly crying face +1F62E..1F62F ; Emoji # E1.0 [2] (๐ฎ..๐ฏ) face with open mouth..hushed face +1F630..1F633 ; Emoji # E0.6 [4] (๐ฐ..๐ณ) anxious face with sweat..flushed face +1F634 ; Emoji # E1.0 [1] (๐ด) sleeping face +1F635 ; Emoji # E0.6 [1] (๐ต) dizzy face +1F636 ; Emoji # E1.0 [1] (๐ถ) face without mouth +1F637..1F640 ; Emoji # E0.6 [10] (๐ท..๐) face with medical mask..weary cat +1F641..1F644 ; Emoji # E1.0 [4] (๐..๐) slightly frowning face..face with rolling eyes +1F645..1F64F ; Emoji # E0.6 [11] (๐
..๐) person gesturing NO..folded hands +1F680 ; Emoji # E0.6 [1] (๐) rocket +1F681..1F682 ; Emoji # E1.0 [2] (๐..๐) helicopter..locomotive +1F683..1F685 ; Emoji # E0.6 [3] (๐..๐
) railway car..bullet train +1F686 ; Emoji # E1.0 [1] (๐) train +1F687 ; Emoji # E0.6 [1] (๐) metro +1F688 ; Emoji # E1.0 [1] (๐) light rail +1F689 ; Emoji # E0.6 [1] (๐) station +1F68A..1F68B ; Emoji # E1.0 [2] (๐..๐) tram..tram car +1F68C ; Emoji # E0.6 [1] (๐) bus +1F68D ; Emoji # E0.7 [1] (๐) oncoming bus +1F68E ; Emoji # E1.0 [1] (๐) trolleybus +1F68F ; Emoji # E0.6 [1] (๐) bus stop +1F690 ; Emoji # E1.0 [1] (๐) minibus +1F691..1F693 ; Emoji # E0.6 [3] (๐..๐) ambulance..police car +1F694 ; Emoji # E0.7 [1] (๐) oncoming police car +1F695 ; Emoji # E0.6 [1] (๐) taxi +1F696 ; Emoji # E1.0 [1] (๐) oncoming taxi +1F697 ; Emoji # E0.6 [1] (๐) automobile +1F698 ; Emoji # E0.7 [1] (๐) oncoming automobile +1F699..1F69A ; Emoji # E0.6 [2] (๐..๐) sport utility vehicle..delivery truck +1F69B..1F6A1 ; Emoji # E1.0 [7] (๐..๐ก) articulated lorry..aerial tramway +1F6A2 ; Emoji # E0.6 [1] (๐ข) ship +1F6A3 ; Emoji # E1.0 [1] (๐ฃ) person rowing boat +1F6A4..1F6A5 ; Emoji # E0.6 [2] (๐ค..๐ฅ) speedboat..horizontal traffic light +1F6A6 ; Emoji # E1.0 [1] (๐ฆ) vertical traffic light +1F6A7..1F6AD ; Emoji # E0.6 [7] (๐ง..๐ญ) construction..no smoking +1F6AE..1F6B1 ; Emoji # E1.0 [4] (๐ฎ..๐ฑ) litter in bin sign..non-potable water +1F6B2 ; Emoji # E0.6 [1] (๐ฒ) bicycle +1F6B3..1F6B5 ; Emoji # E1.0 [3] (๐ณ..๐ต) no bicycles..person mountain biking +1F6B6 ; Emoji # E0.6 [1] (๐ถ) person walking +1F6B7..1F6B8 ; Emoji # E1.0 [2] (๐ท..๐ธ) no pedestrians..children crossing +1F6B9..1F6BE ; Emoji # E0.6 [6] (๐น..๐พ) menโs room..water closet +1F6BF ; Emoji # E1.0 [1] (๐ฟ) shower +1F6C0 ; Emoji # E0.6 [1] (๐) person taking bath +1F6C1..1F6C5 ; Emoji # E1.0 [5] (๐..๐
) bathtub..left luggage +1F6CB ; Emoji # E0.7 [1] (๐๏ธ) couch and lamp +1F6CC ; Emoji # E1.0 [1] (๐) person in bed +1F6CD..1F6CF ; Emoji # E0.7 [3] (๐๏ธ..๐๏ธ) shopping bags..bed +1F6D0 ; Emoji # E1.0 [1] (๐) place of worship +1F6D1..1F6D2 ; Emoji # E3.0 [2] (๐..๐) stop sign..shopping cart +1F6D5 ; Emoji # E12.0 [1] (๐) hindu temple +1F6D6..1F6D7 ; Emoji # E13.0 [2] (๐..๐) hut..elevator +1F6E0..1F6E5 ; Emoji # E0.7 [6] (๐ ๏ธ..๐ฅ๏ธ) hammer and wrench..motor boat +1F6E9 ; Emoji # E0.7 [1] (๐ฉ๏ธ) small airplane +1F6EB..1F6EC ; Emoji # E1.0 [2] (๐ซ..๐ฌ) airplane departure..airplane arrival +1F6F0 ; Emoji # E0.7 [1] (๐ฐ๏ธ) satellite +1F6F3 ; Emoji # E0.7 [1] (๐ณ๏ธ) passenger ship +1F6F4..1F6F6 ; Emoji # E3.0 [3] (๐ด..๐ถ) kick scooter..canoe +1F6F7..1F6F8 ; Emoji # E5.0 [2] (๐ท..๐ธ) sled..flying saucer +1F6F9 ; Emoji # E11.0 [1] (๐น) skateboard +1F6FA ; Emoji # E12.0 [1] (๐บ) auto rickshaw +1F6FB..1F6FC ; Emoji # E13.0 [2] (๐ป..๐ผ) pickup truck..roller skate +1F7E0..1F7EB ; Emoji # E12.0 [12] (๐ ..๐ซ) orange circle..brown square +1F90C ; Emoji # E13.0 [1] (๐ค) pinched fingers +1F90D..1F90F ; Emoji # E12.0 [3] (๐ค..๐ค) white heart..pinching hand +1F910..1F918 ; Emoji # E1.0 [9] (๐ค..๐ค) zipper-mouth face..sign of the horns +1F919..1F91E ; Emoji # E3.0 [6] (๐ค..๐ค) call me hand..crossed fingers +1F91F ; Emoji # E5.0 [1] (๐ค) love-you gesture +1F920..1F927 ; Emoji # E3.0 [8] (๐ค ..๐คง) cowboy hat face..sneezing face +1F928..1F92F ; Emoji # E5.0 [8] (๐คจ..๐คฏ) face with raised eyebrow..exploding head +1F930 ; Emoji # E3.0 [1] (๐คฐ) pregnant woman +1F931..1F932 ; Emoji # E5.0 [2] (๐คฑ..๐คฒ) breast-feeding..palms up together +1F933..1F93A ; Emoji # E3.0 [8] (๐คณ..๐คบ) selfie..person fencing +1F93C..1F93E ; Emoji # E3.0 [3] (๐คผ..๐คพ) people wrestling..person playing handball +1F93F ; Emoji # E12.0 [1] (๐คฟ) diving mask +1F940..1F945 ; Emoji # E3.0 [6] (๐ฅ..๐ฅ
) wilted flower..goal net +1F947..1F94B ; Emoji # E3.0 [5] (๐ฅ..๐ฅ) 1st place medal..martial arts uniform +1F94C ; Emoji # E5.0 [1] (๐ฅ) curling stone +1F94D..1F94F ; Emoji # E11.0 [3] (๐ฅ..๐ฅ) lacrosse..flying disc +1F950..1F95E ; Emoji # E3.0 [15] (๐ฅ..๐ฅ) croissant..pancakes +1F95F..1F96B ; Emoji # E5.0 [13] (๐ฅ..๐ฅซ) dumpling..canned food +1F96C..1F970 ; Emoji # E11.0 [5] (๐ฅฌ..๐ฅฐ) leafy green..smiling face with hearts +1F971 ; Emoji # E12.0 [1] (๐ฅฑ) yawning face +1F972 ; Emoji # E13.0 [1] (๐ฅฒ) smiling face with tear +1F973..1F976 ; Emoji # E11.0 [4] (๐ฅณ..๐ฅถ) partying face..cold face +1F977..1F978 ; Emoji # E13.0 [2] (๐ฅท..๐ฅธ) ninja..disguised face +1F97A ; Emoji # E11.0 [1] (๐ฅบ) pleading face +1F97B ; Emoji # E12.0 [1] (๐ฅป) sari +1F97C..1F97F ; Emoji # E11.0 [4] (๐ฅผ..๐ฅฟ) lab coat..flat shoe +1F980..1F984 ; Emoji # E1.0 [5] (๐ฆ..๐ฆ) crab..unicorn +1F985..1F991 ; Emoji # E3.0 [13] (๐ฆ
..๐ฆ) eagle..squid +1F992..1F997 ; Emoji # E5.0 [6] (๐ฆ..๐ฆ) giraffe..cricket +1F998..1F9A2 ; Emoji # E11.0 [11] (๐ฆ..๐ฆข) kangaroo..swan +1F9A3..1F9A4 ; Emoji # E13.0 [2] (๐ฆฃ..๐ฆค) mammoth..dodo +1F9A5..1F9AA ; Emoji # E12.0 [6] (๐ฆฅ..๐ฆช) sloth..oyster +1F9AB..1F9AD ; Emoji # E13.0 [3] (๐ฆซ..๐ฆญ) beaver..seal +1F9AE..1F9AF ; Emoji # E12.0 [2] (๐ฆฎ..๐ฆฏ) guide dog..white cane +1F9B0..1F9B9 ; Emoji # E11.0 [10] (๐ฆฐ..๐ฆน) red hair..supervillain +1F9BA..1F9BF ; Emoji # E12.0 [6] (๐ฆบ..๐ฆฟ) safety vest..mechanical leg +1F9C0 ; Emoji # E1.0 [1] (๐ง) cheese wedge +1F9C1..1F9C2 ; Emoji # E11.0 [2] (๐ง..๐ง) cupcake..salt +1F9C3..1F9CA ; Emoji # E12.0 [8] (๐ง..๐ง) beverage box..ice +1F9CB ; Emoji # E13.0 [1] (๐ง) bubble tea +1F9CD..1F9CF ; Emoji # E12.0 [3] (๐ง..๐ง) person standing..deaf person +1F9D0..1F9E6 ; Emoji # E5.0 [23] (๐ง..๐งฆ) face with monocle..socks +1F9E7..1F9FF ; Emoji # E11.0 [25] (๐งง..๐งฟ) red envelope..nazar amulet +1FA70..1FA73 ; Emoji # E12.0 [4] (๐ฉฐ..๐ฉณ) ballet shoes..shorts +1FA74 ; Emoji # E13.0 [1] (๐ฉด) thong sandal +1FA78..1FA7A ; Emoji # E12.0 [3] (๐ฉธ..๐ฉบ) drop of blood..stethoscope +1FA80..1FA82 ; Emoji # E12.0 [3] (๐ช..๐ช) yo-yo..parachute +1FA83..1FA86 ; Emoji # E13.0 [4] (๐ช..๐ช) boomerang..nesting dolls +1FA90..1FA95 ; Emoji # E12.0 [6] (๐ช..๐ช) ringed planet..banjo +1FA96..1FAA8 ; Emoji # E13.0 [19] (๐ช..๐ชจ) military helmet..rock +1FAB0..1FAB6 ; Emoji # E13.0 [7] (๐ชฐ..๐ชถ) fly..feather +1FAC0..1FAC2 ; Emoji # E13.0 [3] (๐ซ..๐ซ) anatomical heart..people hugging +1FAD0..1FAD6 ; Emoji # E13.0 [7] (๐ซ..๐ซ) blueberries..teapot -# Total elements: 1311 +# Total elements: 1367 # ================================================ # All omitted code points have Emoji_Presentation=No # @missing: 0000..10FFFF ; Emoji_Presentation ; No -231A..231B ; Emoji_Presentation # 1.1 [2] (โ..โ) watch..hourglass done -23E9..23EC ; Emoji_Presentation # 6.0 [4] (โฉ..โฌ) fast-forward button..fast down button -23F0 ; Emoji_Presentation # 6.0 [1] (โฐ) alarm clock -23F3 ; Emoji_Presentation # 6.0 [1] (โณ) hourglass not done -25FD..25FE ; Emoji_Presentation # 3.2 [2] (โฝ..โพ) white medium-small square..black medium-small square -2614..2615 ; Emoji_Presentation # 4.0 [2] (โ..โ) umbrella with rain drops..hot beverage -2648..2653 ; Emoji_Presentation # 1.1 [12] (โ..โ) Aries..Pisces -267F ; Emoji_Presentation # 4.1 [1] (โฟ) wheelchair symbol -2693 ; Emoji_Presentation # 4.1 [1] (โ) anchor -26A1 ; Emoji_Presentation # 4.0 [1] (โก) high voltage -26AA..26AB ; Emoji_Presentation # 4.1 [2] (โช..โซ) white circle..black circle -26BD..26BE ; Emoji_Presentation # 5.2 [2] (โฝ..โพ) soccer ball..baseball -26C4..26C5 ; Emoji_Presentation # 5.2 [2] (โ..โ
) snowman without snow..sun behind cloud -26CE ; Emoji_Presentation # 6.0 [1] (โ) Ophiuchus -26D4 ; Emoji_Presentation # 5.2 [1] (โ) no entry -26EA ; Emoji_Presentation # 5.2 [1] (โช) church -26F2..26F3 ; Emoji_Presentation # 5.2 [2] (โฒ..โณ) fountain..flag in hole -26F5 ; Emoji_Presentation # 5.2 [1] (โต) sailboat -26FA ; Emoji_Presentation # 5.2 [1] (โบ) tent -26FD ; Emoji_Presentation # 5.2 [1] (โฝ) fuel pump -2705 ; Emoji_Presentation # 6.0 [1] (โ
) check mark button -270A..270B ; Emoji_Presentation # 6.0 [2] (โ..โ) raised fist..raised hand -2728 ; Emoji_Presentation # 6.0 [1] (โจ) sparkles -274C ; Emoji_Presentation # 6.0 [1] (โ) cross mark -274E ; Emoji_Presentation # 6.0 [1] (โ) cross mark button -2753..2755 ; Emoji_Presentation # 6.0 [3] (โ..โ) question mark..white exclamation mark -2757 ; Emoji_Presentation # 5.2 [1] (โ) exclamation mark -2795..2797 ; Emoji_Presentation # 6.0 [3] (โ..โ) plus sign..division sign -27B0 ; Emoji_Presentation # 6.0 [1] (โฐ) curly loop -27BF ; Emoji_Presentation # 6.0 [1] (โฟ) double curly loop -2B1B..2B1C ; Emoji_Presentation # 5.1 [2] (โฌ..โฌ) black large square..white large square -2B50 ; Emoji_Presentation # 5.1 [1] (โญ) star -2B55 ; Emoji_Presentation # 5.2 [1] (โญ) hollow red circle -1F004 ; Emoji_Presentation # 5.1 [1] (๐) mahjong red dragon -1F0CF ; Emoji_Presentation # 6.0 [1] (๐) joker -1F18E ; Emoji_Presentation # 6.0 [1] (๐) AB button (blood type) -1F191..1F19A ; Emoji_Presentation # 6.0 [10] (๐..๐) CL button..VS button -1F1E6..1F1FF ; Emoji_Presentation # 6.0 [26] (๐ฆ..๐ฟ) regional indicator symbol letter a..regional indicator symbol letter z -1F201 ; Emoji_Presentation # 6.0 [1] (๐) Japanese โhereโ button -1F21A ; Emoji_Presentation # 5.2 [1] (๐) Japanese โfree of chargeโ button -1F22F ; Emoji_Presentation # 5.2 [1] (๐ฏ) Japanese โreservedโ button -1F232..1F236 ; Emoji_Presentation # 6.0 [5] (๐ฒ..๐ถ) Japanese โprohibitedโ button..Japanese โnot free of chargeโ button -1F238..1F23A ; Emoji_Presentation # 6.0 [3] (๐ธ..๐บ) Japanese โapplicationโ button..Japanese โopen for businessโ button -1F250..1F251 ; Emoji_Presentation # 6.0 [2] (๐..๐) Japanese โbargainโ button..Japanese โacceptableโ button -1F300..1F320 ; Emoji_Presentation # 6.0 [33] (๐..๐ ) cyclone..shooting star -1F32D..1F32F ; Emoji_Presentation # 8.0 [3] (๐ญ..๐ฏ) hot dog..burrito -1F330..1F335 ; Emoji_Presentation # 6.0 [6] (๐ฐ..๐ต) chestnut..cactus -1F337..1F37C ; Emoji_Presentation # 6.0 [70] (๐ท..๐ผ) tulip..baby bottle -1F37E..1F37F ; Emoji_Presentation # 8.0 [2] (๐พ..๐ฟ) bottle with popping cork..popcorn -1F380..1F393 ; Emoji_Presentation # 6.0 [20] (๐..๐) ribbon..graduation cap -1F3A0..1F3C4 ; Emoji_Presentation # 6.0 [37] (๐ ..๐) carousel horse..person surfing -1F3C5 ; Emoji_Presentation # 7.0 [1] (๐
) sports medal -1F3C6..1F3CA ; Emoji_Presentation # 6.0 [5] (๐..๐) trophy..person swimming -1F3CF..1F3D3 ; Emoji_Presentation # 8.0 [5] (๐..๐) cricket game..ping pong -1F3E0..1F3F0 ; Emoji_Presentation # 6.0 [17] (๐ ..๐ฐ) house..castle -1F3F4 ; Emoji_Presentation # 7.0 [1] (๐ด) black flag -1F3F8..1F3FF ; Emoji_Presentation # 8.0 [8] (๐ธ..๐ฟ) badminton..dark skin tone -1F400..1F43E ; Emoji_Presentation # 6.0 [63] (๐..๐พ) rat..paw prints -1F440 ; Emoji_Presentation # 6.0 [1] (๐) eyes -1F442..1F4F7 ; Emoji_Presentation # 6.0[182] (๐..๐ท) ear..camera -1F4F8 ; Emoji_Presentation # 7.0 [1] (๐ธ) camera with flash -1F4F9..1F4FC ; Emoji_Presentation # 6.0 [4] (๐น..๐ผ) video camera..videocassette -1F4FF ; Emoji_Presentation # 8.0 [1] (๐ฟ) prayer beads -1F500..1F53D ; Emoji_Presentation # 6.0 [62] (๐..๐ฝ) shuffle tracks button..downwards button -1F54B..1F54E ; Emoji_Presentation # 8.0 [4] (๐..๐) kaaba..menorah -1F550..1F567 ; Emoji_Presentation # 6.0 [24] (๐..๐ง) one oโclock..twelve-thirty -1F57A ; Emoji_Presentation # 9.0 [1] (๐บ) man dancing -1F595..1F596 ; Emoji_Presentation # 7.0 [2] (๐..๐) middle finger..vulcan salute -1F5A4 ; Emoji_Presentation # 9.0 [1] (๐ค) black heart -1F5FB..1F5FF ; Emoji_Presentation # 6.0 [5] (๐ป..๐ฟ) mount fuji..moai -1F600 ; Emoji_Presentation # 6.1 [1] (๐) grinning face -1F601..1F610 ; Emoji_Presentation # 6.0 [16] (๐..๐) beaming face with smiling eyes..neutral face -1F611 ; Emoji_Presentation # 6.1 [1] (๐) expressionless face -1F612..1F614 ; Emoji_Presentation # 6.0 [3] (๐..๐) unamused face..pensive face -1F615 ; Emoji_Presentation # 6.1 [1] (๐) confused face -1F616 ; Emoji_Presentation # 6.0 [1] (๐) confounded face -1F617 ; Emoji_Presentation # 6.1 [1] (๐) kissing face -1F618 ; Emoji_Presentation # 6.0 [1] (๐) face blowing a kiss -1F619 ; Emoji_Presentation # 6.1 [1] (๐) kissing face with smiling eyes -1F61A ; Emoji_Presentation # 6.0 [1] (๐) kissing face with closed eyes -1F61B ; Emoji_Presentation # 6.1 [1] (๐) face with tongue -1F61C..1F61E ; Emoji_Presentation # 6.0 [3] (๐..๐) winking face with tongue..disappointed face -1F61F ; Emoji_Presentation # 6.1 [1] (๐) worried face -1F620..1F625 ; Emoji_Presentation # 6.0 [6] (๐ ..๐ฅ) angry face..sad but relieved face -1F626..1F627 ; Emoji_Presentation # 6.1 [2] (๐ฆ..๐ง) frowning face with open mouth..anguished face -1F628..1F62B ; Emoji_Presentation # 6.0 [4] (๐จ..๐ซ) fearful face..tired face -1F62C ; Emoji_Presentation # 6.1 [1] (๐ฌ) grimacing face -1F62D ; Emoji_Presentation # 6.0 [1] (๐ญ) loudly crying face -1F62E..1F62F ; Emoji_Presentation # 6.1 [2] (๐ฎ..๐ฏ) face with open mouth..hushed face -1F630..1F633 ; Emoji_Presentation # 6.0 [4] (๐ฐ..๐ณ) anxious face with sweat..flushed face -1F634 ; Emoji_Presentation # 6.1 [1] (๐ด) sleeping face -1F635..1F640 ; Emoji_Presentation # 6.0 [12] (๐ต..๐) dizzy face..weary cat -1F641..1F642 ; Emoji_Presentation # 7.0 [2] (๐..๐) slightly frowning face..slightly smiling face -1F643..1F644 ; Emoji_Presentation # 8.0 [2] (๐..๐) upside-down face..face with rolling eyes -1F645..1F64F ; Emoji_Presentation # 6.0 [11] (๐
..๐) person gesturing NO..folded hands -1F680..1F6C5 ; Emoji_Presentation # 6.0 [70] (๐..๐
) rocket..left luggage -1F6CC ; Emoji_Presentation # 7.0 [1] (๐) person in bed -1F6D0 ; Emoji_Presentation # 8.0 [1] (๐) place of worship -1F6D1..1F6D2 ; Emoji_Presentation # 9.0 [2] (๐..๐) stop sign..shopping cart -1F6D5 ; Emoji_Presentation # 12.0 [1] (๐) hindu temple -1F6EB..1F6EC ; Emoji_Presentation # 7.0 [2] (๐ซ..๐ฌ) airplane departure..airplane arrival -1F6F4..1F6F6 ; Emoji_Presentation # 9.0 [3] (๐ด..๐ถ) kick scooter..canoe -1F6F7..1F6F8 ; Emoji_Presentation # 10.0 [2] (๐ท..๐ธ) sled..flying saucer -1F6F9 ; Emoji_Presentation # 11.0 [1] (๐น) skateboard -1F6FA ; Emoji_Presentation # 12.0 [1] (๐บ) auto rickshaw -1F7E0..1F7EB ; Emoji_Presentation # 12.0 [12] (๐ ..๐ซ) orange circle..brown square -1F90D..1F90F ; Emoji_Presentation # 12.0 [3] (๐ค..๐ค) white heart..pinching hand -1F910..1F918 ; Emoji_Presentation # 8.0 [9] (๐ค..๐ค) zipper-mouth face..sign of the horns -1F919..1F91E ; Emoji_Presentation # 9.0 [6] (๐ค..๐ค) call me hand..crossed fingers -1F91F ; Emoji_Presentation # 10.0 [1] (๐ค) love-you gesture -1F920..1F927 ; Emoji_Presentation # 9.0 [8] (๐ค ..๐คง) cowboy hat face..sneezing face -1F928..1F92F ; Emoji_Presentation # 10.0 [8] (๐คจ..๐คฏ) face with raised eyebrow..exploding head -1F930 ; Emoji_Presentation # 9.0 [1] (๐คฐ) pregnant woman -1F931..1F932 ; Emoji_Presentation # 10.0 [2] (๐คฑ..๐คฒ) breast-feeding..palms up together -1F933..1F93A ; Emoji_Presentation # 9.0 [8] (๐คณ..๐คบ) selfie..person fencing -1F93C..1F93E ; Emoji_Presentation # 9.0 [3] (๐คผ..๐คพ) people wrestling..person playing handball -1F93F ; Emoji_Presentation # 12.0 [1] (๐คฟ) diving mask -1F940..1F945 ; Emoji_Presentation # 9.0 [6] (๐ฅ..๐ฅ
) wilted flower..goal net -1F947..1F94B ; Emoji_Presentation # 9.0 [5] (๐ฅ..๐ฅ) 1st place medal..martial arts uniform -1F94C ; Emoji_Presentation # 10.0 [1] (๐ฅ) curling stone -1F94D..1F94F ; Emoji_Presentation # 11.0 [3] (๐ฅ..๐ฅ) lacrosse..flying disc -1F950..1F95E ; Emoji_Presentation # 9.0 [15] (๐ฅ..๐ฅ) croissant..pancakes -1F95F..1F96B ; Emoji_Presentation # 10.0 [13] (๐ฅ..๐ฅซ) dumpling..canned food -1F96C..1F970 ; Emoji_Presentation # 11.0 [5] (๐ฅฌ..๐ฅฐ) leafy green..smiling face with hearts -1F971 ; Emoji_Presentation # 12.0 [1] (๐ฅฑ) yawning face -1F973..1F976 ; Emoji_Presentation # 11.0 [4] (๐ฅณ..๐ฅถ) partying face..cold face -1F97A ; Emoji_Presentation # 11.0 [1] (๐ฅบ) pleading face -1F97B ; Emoji_Presentation # 12.0 [1] (๐ฅป) sari -1F97C..1F97F ; Emoji_Presentation # 11.0 [4] (๐ฅผ..๐ฅฟ) lab coat..flat shoe -1F980..1F984 ; Emoji_Presentation # 8.0 [5] (๐ฆ..๐ฆ) crab..unicorn -1F985..1F991 ; Emoji_Presentation # 9.0 [13] (๐ฆ
..๐ฆ) eagle..squid -1F992..1F997 ; Emoji_Presentation # 10.0 [6] (๐ฆ..๐ฆ) giraffe..cricket -1F998..1F9A2 ; Emoji_Presentation # 11.0 [11] (๐ฆ..๐ฆข) kangaroo..swan -1F9A5..1F9AA ; Emoji_Presentation # 12.0 [6] (๐ฆฅ..๐ฆช) sloth..oyster -1F9AE..1F9AF ; Emoji_Presentation # 12.0 [2] (๐ฆฎ..๐ฆฏ) guide dog..probing cane -1F9B0..1F9B9 ; Emoji_Presentation # 11.0 [10] (๐ฆฐ..๐ฆน) red hair..supervillain -1F9BA..1F9BF ; Emoji_Presentation # 12.0 [6] (๐ฆบ..๐ฆฟ) safety vest..mechanical leg -1F9C0 ; Emoji_Presentation # 8.0 [1] (๐ง) cheese wedge -1F9C1..1F9C2 ; Emoji_Presentation # 11.0 [2] (๐ง..๐ง) cupcake..salt -1F9C3..1F9CA ; Emoji_Presentation # 12.0 [8] (๐ง..๐ง) beverage box..ice cube -1F9CD..1F9CF ; Emoji_Presentation # 12.0 [3] (๐ง..๐ง) person standing..deaf person -1F9D0..1F9E6 ; Emoji_Presentation # 10.0 [23] (๐ง..๐งฆ) face with monocle..socks -1F9E7..1F9FF ; Emoji_Presentation # 11.0 [25] (๐งง..๐งฟ) red envelope..nazar amulet -1FA70..1FA73 ; Emoji_Presentation # 12.0 [4] (๐ฉฐ..๐ฉณ) ballet shoes..shorts -1FA78..1FA7A ; Emoji_Presentation # 12.0 [3] (๐ฉธ..๐ฉบ) drop of blood..stethoscope -1FA80..1FA82 ; Emoji_Presentation # 12.0 [3] (๐ช..๐ช) yo-yo..parachute -1FA90..1FA95 ; Emoji_Presentation # 12.0 [6] (๐ช..๐ช) ringed planet..banjo +231A..231B ; Emoji_Presentation # E0.6 [2] (โ..โ) watch..hourglass done +23E9..23EC ; Emoji_Presentation # E0.6 [4] (โฉ..โฌ) fast-forward button..fast down button +23F0 ; Emoji_Presentation # E0.6 [1] (โฐ) alarm clock +23F3 ; Emoji_Presentation # E0.6 [1] (โณ) hourglass not done +25FD..25FE ; Emoji_Presentation # E0.6 [2] (โฝ..โพ) white medium-small square..black medium-small square +2614..2615 ; Emoji_Presentation # E0.6 [2] (โ..โ) umbrella with rain drops..hot beverage +2648..2653 ; Emoji_Presentation # E0.6 [12] (โ..โ) Aries..Pisces +267F ; Emoji_Presentation # E0.6 [1] (โฟ) wheelchair symbol +2693 ; Emoji_Presentation # E0.6 [1] (โ) anchor +26A1 ; Emoji_Presentation # E0.6 [1] (โก) high voltage +26AA..26AB ; Emoji_Presentation # E0.6 [2] (โช..โซ) white circle..black circle +26BD..26BE ; Emoji_Presentation # E0.6 [2] (โฝ..โพ) soccer ball..baseball +26C4..26C5 ; Emoji_Presentation # E0.6 [2] (โ..โ
) snowman without snow..sun behind cloud +26CE ; Emoji_Presentation # E0.6 [1] (โ) Ophiuchus +26D4 ; Emoji_Presentation # E0.6 [1] (โ) no entry +26EA ; Emoji_Presentation # E0.6 [1] (โช) church +26F2..26F3 ; Emoji_Presentation # E0.6 [2] (โฒ..โณ) fountain..flag in hole +26F5 ; Emoji_Presentation # E0.6 [1] (โต) sailboat +26FA ; Emoji_Presentation # E0.6 [1] (โบ) tent +26FD ; Emoji_Presentation # E0.6 [1] (โฝ) fuel pump +2705 ; Emoji_Presentation # E0.6 [1] (โ
) check mark button +270A..270B ; Emoji_Presentation # E0.6 [2] (โ..โ) raised fist..raised hand +2728 ; Emoji_Presentation # E0.6 [1] (โจ) sparkles +274C ; Emoji_Presentation # E0.6 [1] (โ) cross mark +274E ; Emoji_Presentation # E0.6 [1] (โ) cross mark button +2753..2755 ; Emoji_Presentation # E0.6 [3] (โ..โ) question mark..white exclamation mark +2757 ; Emoji_Presentation # E0.6 [1] (โ) exclamation mark +2795..2797 ; Emoji_Presentation # E0.6 [3] (โ..โ) plus..divide +27B0 ; Emoji_Presentation # E0.6 [1] (โฐ) curly loop +27BF ; Emoji_Presentation # E1.0 [1] (โฟ) double curly loop +2B1B..2B1C ; Emoji_Presentation # E0.6 [2] (โฌ..โฌ) black large square..white large square +2B50 ; Emoji_Presentation # E0.6 [1] (โญ) star +2B55 ; Emoji_Presentation # E0.6 [1] (โญ) hollow red circle +1F004 ; Emoji_Presentation # E0.6 [1] (๐) mahjong red dragon +1F0CF ; Emoji_Presentation # E0.6 [1] (๐) joker +1F18E ; Emoji_Presentation # E0.6 [1] (๐) AB button (blood type) +1F191..1F19A ; Emoji_Presentation # E0.6 [10] (๐..๐) CL button..VS button +1F1E6..1F1FF ; Emoji_Presentation # E0.0 [26] (๐ฆ..๐ฟ) regional indicator symbol letter a..regional indicator symbol letter z +1F201 ; Emoji_Presentation # E0.6 [1] (๐) Japanese โhereโ button +1F21A ; Emoji_Presentation # E0.6 [1] (๐) Japanese โfree of chargeโ button +1F22F ; Emoji_Presentation # E0.6 [1] (๐ฏ) Japanese โreservedโ button +1F232..1F236 ; Emoji_Presentation # E0.6 [5] (๐ฒ..๐ถ) Japanese โprohibitedโ button..Japanese โnot free of chargeโ button +1F238..1F23A ; Emoji_Presentation # E0.6 [3] (๐ธ..๐บ) Japanese โapplicationโ button..Japanese โopen for businessโ button +1F250..1F251 ; Emoji_Presentation # E0.6 [2] (๐..๐) Japanese โbargainโ button..Japanese โacceptableโ button +1F300..1F30C ; Emoji_Presentation # E0.6 [13] (๐..๐) cyclone..milky way +1F30D..1F30E ; Emoji_Presentation # E0.7 [2] (๐..๐) globe showing Europe-Africa..globe showing Americas +1F30F ; Emoji_Presentation # E0.6 [1] (๐) globe showing Asia-Australia +1F310 ; Emoji_Presentation # E1.0 [1] (๐) globe with meridians +1F311 ; Emoji_Presentation # E0.6 [1] (๐) new moon +1F312 ; Emoji_Presentation # E1.0 [1] (๐) waxing crescent moon +1F313..1F315 ; Emoji_Presentation # E0.6 [3] (๐..๐) first quarter moon..full moon +1F316..1F318 ; Emoji_Presentation # E1.0 [3] (๐..๐) waning gibbous moon..waning crescent moon +1F319 ; Emoji_Presentation # E0.6 [1] (๐) crescent moon +1F31A ; Emoji_Presentation # E1.0 [1] (๐) new moon face +1F31B ; Emoji_Presentation # E0.6 [1] (๐) first quarter moon face +1F31C ; Emoji_Presentation # E0.7 [1] (๐) last quarter moon face +1F31D..1F31E ; Emoji_Presentation # E1.0 [2] (๐..๐) full moon face..sun with face +1F31F..1F320 ; Emoji_Presentation # E0.6 [2] (๐..๐ ) glowing star..shooting star +1F32D..1F32F ; Emoji_Presentation # E1.0 [3] (๐ญ..๐ฏ) hot dog..burrito +1F330..1F331 ; Emoji_Presentation # E0.6 [2] (๐ฐ..๐ฑ) chestnut..seedling +1F332..1F333 ; Emoji_Presentation # E1.0 [2] (๐ฒ..๐ณ) evergreen tree..deciduous tree +1F334..1F335 ; Emoji_Presentation # E0.6 [2] (๐ด..๐ต) palm tree..cactus +1F337..1F34A ; Emoji_Presentation # E0.6 [20] (๐ท..๐) tulip..tangerine +1F34B ; Emoji_Presentation # E1.0 [1] (๐) lemon +1F34C..1F34F ; Emoji_Presentation # E0.6 [4] (๐..๐) banana..green apple +1F350 ; Emoji_Presentation # E1.0 [1] (๐) pear +1F351..1F37B ; Emoji_Presentation # E0.6 [43] (๐..๐ป) peach..clinking beer mugs +1F37C ; Emoji_Presentation # E1.0 [1] (๐ผ) baby bottle +1F37E..1F37F ; Emoji_Presentation # E1.0 [2] (๐พ..๐ฟ) bottle with popping cork..popcorn +1F380..1F393 ; Emoji_Presentation # E0.6 [20] (๐..๐) ribbon..graduation cap +1F3A0..1F3C4 ; Emoji_Presentation # E0.6 [37] (๐ ..๐) carousel horse..person surfing +1F3C5 ; Emoji_Presentation # E1.0 [1] (๐
) sports medal +1F3C6 ; Emoji_Presentation # E0.6 [1] (๐) trophy +1F3C7 ; Emoji_Presentation # E1.0 [1] (๐) horse racing +1F3C8 ; Emoji_Presentation # E0.6 [1] (๐) american football +1F3C9 ; Emoji_Presentation # E1.0 [1] (๐) rugby football +1F3CA ; Emoji_Presentation # E0.6 [1] (๐) person swimming +1F3CF..1F3D3 ; Emoji_Presentation # E1.0 [5] (๐..๐) cricket game..ping pong +1F3E0..1F3E3 ; Emoji_Presentation # E0.6 [4] (๐ ..๐ฃ) house..Japanese post office +1F3E4 ; Emoji_Presentation # E1.0 [1] (๐ค) post office +1F3E5..1F3F0 ; Emoji_Presentation # E0.6 [12] (๐ฅ..๐ฐ) hospital..castle +1F3F4 ; Emoji_Presentation # E1.0 [1] (๐ด) black flag +1F3F8..1F407 ; Emoji_Presentation # E1.0 [16] (๐ธ..๐) badminton..rabbit +1F408 ; Emoji_Presentation # E0.7 [1] (๐) cat +1F409..1F40B ; Emoji_Presentation # E1.0 [3] (๐..๐) dragon..whale +1F40C..1F40E ; Emoji_Presentation # E0.6 [3] (๐..๐) snail..horse +1F40F..1F410 ; Emoji_Presentation # E1.0 [2] (๐..๐) ram..goat +1F411..1F412 ; Emoji_Presentation # E0.6 [2] (๐..๐) ewe..monkey +1F413 ; Emoji_Presentation # E1.0 [1] (๐) rooster +1F414 ; Emoji_Presentation # E0.6 [1] (๐) chicken +1F415 ; Emoji_Presentation # E0.7 [1] (๐) dog +1F416 ; Emoji_Presentation # E1.0 [1] (๐) pig +1F417..1F429 ; Emoji_Presentation # E0.6 [19] (๐..๐ฉ) boar..poodle +1F42A ; Emoji_Presentation # E1.0 [1] (๐ช) camel +1F42B..1F43E ; Emoji_Presentation # E0.6 [20] (๐ซ..๐พ) two-hump camel..paw prints +1F440 ; Emoji_Presentation # E0.6 [1] (๐) eyes +1F442..1F464 ; Emoji_Presentation # E0.6 [35] (๐..๐ค) ear..bust in silhouette +1F465 ; Emoji_Presentation # E1.0 [1] (๐ฅ) busts in silhouette +1F466..1F46B ; Emoji_Presentation # E0.6 [6] (๐ฆ..๐ซ) boy..woman and man holding hands +1F46C..1F46D ; Emoji_Presentation # E1.0 [2] (๐ฌ..๐ญ) men holding hands..women holding hands +1F46E..1F4AC ; Emoji_Presentation # E0.6 [63] (๐ฎ..๐ฌ) police officer..speech balloon +1F4AD ; Emoji_Presentation # E1.0 [1] (๐ญ) thought balloon +1F4AE..1F4B5 ; Emoji_Presentation # E0.6 [8] (๐ฎ..๐ต) white flower..dollar banknote +1F4B6..1F4B7 ; Emoji_Presentation # E1.0 [2] (๐ถ..๐ท) euro banknote..pound banknote +1F4B8..1F4EB ; Emoji_Presentation # E0.6 [52] (๐ธ..๐ซ) money with wings..closed mailbox with raised flag +1F4EC..1F4ED ; Emoji_Presentation # E0.7 [2] (๐ฌ..๐ญ) open mailbox with raised flag..open mailbox with lowered flag +1F4EE ; Emoji_Presentation # E0.6 [1] (๐ฎ) postbox +1F4EF ; Emoji_Presentation # E1.0 [1] (๐ฏ) postal horn +1F4F0..1F4F4 ; Emoji_Presentation # E0.6 [5] (๐ฐ..๐ด) newspaper..mobile phone off +1F4F5 ; Emoji_Presentation # E1.0 [1] (๐ต) no mobile phones +1F4F6..1F4F7 ; Emoji_Presentation # E0.6 [2] (๐ถ..๐ท) antenna bars..camera +1F4F8 ; Emoji_Presentation # E1.0 [1] (๐ธ) camera with flash +1F4F9..1F4FC ; Emoji_Presentation # E0.6 [4] (๐น..๐ผ) video camera..videocassette +1F4FF..1F502 ; Emoji_Presentation # E1.0 [4] (๐ฟ..๐) prayer beads..repeat single button +1F503 ; Emoji_Presentation # E0.6 [1] (๐) clockwise vertical arrows +1F504..1F507 ; Emoji_Presentation # E1.0 [4] (๐..๐) counterclockwise arrows button..muted speaker +1F508 ; Emoji_Presentation # E0.7 [1] (๐) speaker low volume +1F509 ; Emoji_Presentation # E1.0 [1] (๐) speaker medium volume +1F50A..1F514 ; Emoji_Presentation # E0.6 [11] (๐..๐) speaker high volume..bell +1F515 ; Emoji_Presentation # E1.0 [1] (๐) bell with slash +1F516..1F52B ; Emoji_Presentation # E0.6 [22] (๐..๐ซ) bookmark..pistol +1F52C..1F52D ; Emoji_Presentation # E1.0 [2] (๐ฌ..๐ญ) microscope..telescope +1F52E..1F53D ; Emoji_Presentation # E0.6 [16] (๐ฎ..๐ฝ) crystal ball..downwards button +1F54B..1F54E ; Emoji_Presentation # E1.0 [4] (๐..๐) kaaba..menorah +1F550..1F55B ; Emoji_Presentation # E0.6 [12] (๐..๐) one oโclock..twelve oโclock +1F55C..1F567 ; Emoji_Presentation # E0.7 [12] (๐..๐ง) one-thirty..twelve-thirty +1F57A ; Emoji_Presentation # E3.0 [1] (๐บ) man dancing +1F595..1F596 ; Emoji_Presentation # E1.0 [2] (๐..๐) middle finger..vulcan salute +1F5A4 ; Emoji_Presentation # E3.0 [1] (๐ค) black heart +1F5FB..1F5FF ; Emoji_Presentation # E0.6 [5] (๐ป..๐ฟ) mount fuji..moai +1F600 ; Emoji_Presentation # E1.0 [1] (๐) grinning face +1F601..1F606 ; Emoji_Presentation # E0.6 [6] (๐..๐) beaming face with smiling eyes..grinning squinting face +1F607..1F608 ; Emoji_Presentation # E1.0 [2] (๐..๐) smiling face with halo..smiling face with horns +1F609..1F60D ; Emoji_Presentation # E0.6 [5] (๐..๐) winking face..smiling face with heart-eyes +1F60E ; Emoji_Presentation # E1.0 [1] (๐) smiling face with sunglasses +1F60F ; Emoji_Presentation # E0.6 [1] (๐) smirking face +1F610 ; Emoji_Presentation # E0.7 [1] (๐) neutral face +1F611 ; Emoji_Presentation # E1.0 [1] (๐) expressionless face +1F612..1F614 ; Emoji_Presentation # E0.6 [3] (๐..๐) unamused face..pensive face +1F615 ; Emoji_Presentation # E1.0 [1] (๐) confused face +1F616 ; Emoji_Presentation # E0.6 [1] (๐) confounded face +1F617 ; Emoji_Presentation # E1.0 [1] (๐) kissing face +1F618 ; Emoji_Presentation # E0.6 [1] (๐) face blowing a kiss +1F619 ; Emoji_Presentation # E1.0 [1] (๐) kissing face with smiling eyes +1F61A ; Emoji_Presentation # E0.6 [1] (๐) kissing face with closed eyes +1F61B ; Emoji_Presentation # E1.0 [1] (๐) face with tongue +1F61C..1F61E ; Emoji_Presentation # E0.6 [3] (๐..๐) winking face with tongue..disappointed face +1F61F ; Emoji_Presentation # E1.0 [1] (๐) worried face +1F620..1F625 ; Emoji_Presentation # E0.6 [6] (๐ ..๐ฅ) angry face..sad but relieved face +1F626..1F627 ; Emoji_Presentation # E1.0 [2] (๐ฆ..๐ง) frowning face with open mouth..anguished face +1F628..1F62B ; Emoji_Presentation # E0.6 [4] (๐จ..๐ซ) fearful face..tired face +1F62C ; Emoji_Presentation # E1.0 [1] (๐ฌ) grimacing face +1F62D ; Emoji_Presentation # E0.6 [1] (๐ญ) loudly crying face +1F62E..1F62F ; Emoji_Presentation # E1.0 [2] (๐ฎ..๐ฏ) face with open mouth..hushed face +1F630..1F633 ; Emoji_Presentation # E0.6 [4] (๐ฐ..๐ณ) anxious face with sweat..flushed face +1F634 ; Emoji_Presentation # E1.0 [1] (๐ด) sleeping face +1F635 ; Emoji_Presentation # E0.6 [1] (๐ต) dizzy face +1F636 ; Emoji_Presentation # E1.0 [1] (๐ถ) face without mouth +1F637..1F640 ; Emoji_Presentation # E0.6 [10] (๐ท..๐) face with medical mask..weary cat +1F641..1F644 ; Emoji_Presentation # E1.0 [4] (๐..๐) slightly frowning face..face with rolling eyes +1F645..1F64F ; Emoji_Presentation # E0.6 [11] (๐
..๐) person gesturing NO..folded hands +1F680 ; Emoji_Presentation # E0.6 [1] (๐) rocket +1F681..1F682 ; Emoji_Presentation # E1.0 [2] (๐..๐) helicopter..locomotive +1F683..1F685 ; Emoji_Presentation # E0.6 [3] (๐..๐
) railway car..bullet train +1F686 ; Emoji_Presentation # E1.0 [1] (๐) train +1F687 ; Emoji_Presentation # E0.6 [1] (๐) metro +1F688 ; Emoji_Presentation # E1.0 [1] (๐) light rail +1F689 ; Emoji_Presentation # E0.6 [1] (๐) station +1F68A..1F68B ; Emoji_Presentation # E1.0 [2] (๐..๐) tram..tram car +1F68C ; Emoji_Presentation # E0.6 [1] (๐) bus +1F68D ; Emoji_Presentation # E0.7 [1] (๐) oncoming bus +1F68E ; Emoji_Presentation # E1.0 [1] (๐) trolleybus +1F68F ; Emoji_Presentation # E0.6 [1] (๐) bus stop +1F690 ; Emoji_Presentation # E1.0 [1] (๐) minibus +1F691..1F693 ; Emoji_Presentation # E0.6 [3] (๐..๐) ambulance..police car +1F694 ; Emoji_Presentation # E0.7 [1] (๐) oncoming police car +1F695 ; Emoji_Presentation # E0.6 [1] (๐) taxi +1F696 ; Emoji_Presentation # E1.0 [1] (๐) oncoming taxi +1F697 ; Emoji_Presentation # E0.6 [1] (๐) automobile +1F698 ; Emoji_Presentation # E0.7 [1] (๐) oncoming automobile +1F699..1F69A ; Emoji_Presentation # E0.6 [2] (๐..๐) sport utility vehicle..delivery truck +1F69B..1F6A1 ; Emoji_Presentation # E1.0 [7] (๐..๐ก) articulated lorry..aerial tramway +1F6A2 ; Emoji_Presentation # E0.6 [1] (๐ข) ship +1F6A3 ; Emoji_Presentation # E1.0 [1] (๐ฃ) person rowing boat +1F6A4..1F6A5 ; Emoji_Presentation # E0.6 [2] (๐ค..๐ฅ) speedboat..horizontal traffic light +1F6A6 ; Emoji_Presentation # E1.0 [1] (๐ฆ) vertical traffic light +1F6A7..1F6AD ; Emoji_Presentation # E0.6 [7] (๐ง..๐ญ) construction..no smoking +1F6AE..1F6B1 ; Emoji_Presentation # E1.0 [4] (๐ฎ..๐ฑ) litter in bin sign..non-potable water +1F6B2 ; Emoji_Presentation # E0.6 [1] (๐ฒ) bicycle +1F6B3..1F6B5 ; Emoji_Presentation # E1.0 [3] (๐ณ..๐ต) no bicycles..person mountain biking +1F6B6 ; Emoji_Presentation # E0.6 [1] (๐ถ) person walking +1F6B7..1F6B8 ; Emoji_Presentation # E1.0 [2] (๐ท..๐ธ) no pedestrians..children crossing +1F6B9..1F6BE ; Emoji_Presentation # E0.6 [6] (๐น..๐พ) menโs room..water closet +1F6BF ; Emoji_Presentation # E1.0 [1] (๐ฟ) shower +1F6C0 ; Emoji_Presentation # E0.6 [1] (๐) person taking bath +1F6C1..1F6C5 ; Emoji_Presentation # E1.0 [5] (๐..๐
) bathtub..left luggage +1F6CC ; Emoji_Presentation # E1.0 [1] (๐) person in bed +1F6D0 ; Emoji_Presentation # E1.0 [1] (๐) place of worship +1F6D1..1F6D2 ; Emoji_Presentation # E3.0 [2] (๐..๐) stop sign..shopping cart +1F6D5 ; Emoji_Presentation # E12.0 [1] (๐) hindu temple +1F6D6..1F6D7 ; Emoji_Presentation # E13.0 [2] (๐..๐) hut..elevator +1F6EB..1F6EC ; Emoji_Presentation # E1.0 [2] (๐ซ..๐ฌ) airplane departure..airplane arrival +1F6F4..1F6F6 ; Emoji_Presentation # E3.0 [3] (๐ด..๐ถ) kick scooter..canoe +1F6F7..1F6F8 ; Emoji_Presentation # E5.0 [2] (๐ท..๐ธ) sled..flying saucer +1F6F9 ; Emoji_Presentation # E11.0 [1] (๐น) skateboard +1F6FA ; Emoji_Presentation # E12.0 [1] (๐บ) auto rickshaw +1F6FB..1F6FC ; Emoji_Presentation # E13.0 [2] (๐ป..๐ผ) pickup truck..roller skate +1F7E0..1F7EB ; Emoji_Presentation # E12.0 [12] (๐ ..๐ซ) orange circle..brown square +1F90C ; Emoji_Presentation # E13.0 [1] (๐ค) pinched fingers +1F90D..1F90F ; Emoji_Presentation # E12.0 [3] (๐ค..๐ค) white heart..pinching hand +1F910..1F918 ; Emoji_Presentation # E1.0 [9] (๐ค..๐ค) zipper-mouth face..sign of the horns +1F919..1F91E ; Emoji_Presentation # E3.0 [6] (๐ค..๐ค) call me hand..crossed fingers +1F91F ; Emoji_Presentation # E5.0 [1] (๐ค) love-you gesture +1F920..1F927 ; Emoji_Presentation # E3.0 [8] (๐ค ..๐คง) cowboy hat face..sneezing face +1F928..1F92F ; Emoji_Presentation # E5.0 [8] (๐คจ..๐คฏ) face with raised eyebrow..exploding head +1F930 ; Emoji_Presentation # E3.0 [1] (๐คฐ) pregnant woman +1F931..1F932 ; Emoji_Presentation # E5.0 [2] (๐คฑ..๐คฒ) breast-feeding..palms up together +1F933..1F93A ; Emoji_Presentation # E3.0 [8] (๐คณ..๐คบ) selfie..person fencing +1F93C..1F93E ; Emoji_Presentation # E3.0 [3] (๐คผ..๐คพ) people wrestling..person playing handball +1F93F ; Emoji_Presentation # E12.0 [1] (๐คฟ) diving mask +1F940..1F945 ; Emoji_Presentation # E3.0 [6] (๐ฅ..๐ฅ
) wilted flower..goal net +1F947..1F94B ; Emoji_Presentation # E3.0 [5] (๐ฅ..๐ฅ) 1st place medal..martial arts uniform +1F94C ; Emoji_Presentation # E5.0 [1] (๐ฅ) curling stone +1F94D..1F94F ; Emoji_Presentation # E11.0 [3] (๐ฅ..๐ฅ) lacrosse..flying disc +1F950..1F95E ; Emoji_Presentation # E3.0 [15] (๐ฅ..๐ฅ) croissant..pancakes +1F95F..1F96B ; Emoji_Presentation # E5.0 [13] (๐ฅ..๐ฅซ) dumpling..canned food +1F96C..1F970 ; Emoji_Presentation # E11.0 [5] (๐ฅฌ..๐ฅฐ) leafy green..smiling face with hearts +1F971 ; Emoji_Presentation # E12.0 [1] (๐ฅฑ) yawning face +1F972 ; Emoji_Presentation # E13.0 [1] (๐ฅฒ) smiling face with tear +1F973..1F976 ; Emoji_Presentation # E11.0 [4] (๐ฅณ..๐ฅถ) partying face..cold face +1F977..1F978 ; Emoji_Presentation # E13.0 [2] (๐ฅท..๐ฅธ) ninja..disguised face +1F97A ; Emoji_Presentation # E11.0 [1] (๐ฅบ) pleading face +1F97B ; Emoji_Presentation # E12.0 [1] (๐ฅป) sari +1F97C..1F97F ; Emoji_Presentation # E11.0 [4] (๐ฅผ..๐ฅฟ) lab coat..flat shoe +1F980..1F984 ; Emoji_Presentation # E1.0 [5] (๐ฆ..๐ฆ) crab..unicorn +1F985..1F991 ; Emoji_Presentation # E3.0 [13] (๐ฆ
..๐ฆ) eagle..squid +1F992..1F997 ; Emoji_Presentation # E5.0 [6] (๐ฆ..๐ฆ) giraffe..cricket +1F998..1F9A2 ; Emoji_Presentation # E11.0 [11] (๐ฆ..๐ฆข) kangaroo..swan +1F9A3..1F9A4 ; Emoji_Presentation # E13.0 [2] (๐ฆฃ..๐ฆค) mammoth..dodo +1F9A5..1F9AA ; Emoji_Presentation # E12.0 [6] (๐ฆฅ..๐ฆช) sloth..oyster +1F9AB..1F9AD ; Emoji_Presentation # E13.0 [3] (๐ฆซ..๐ฆญ) beaver..seal +1F9AE..1F9AF ; Emoji_Presentation # E12.0 [2] (๐ฆฎ..๐ฆฏ) guide dog..white cane +1F9B0..1F9B9 ; Emoji_Presentation # E11.0 [10] (๐ฆฐ..๐ฆน) red hair..supervillain +1F9BA..1F9BF ; Emoji_Presentation # E12.0 [6] (๐ฆบ..๐ฆฟ) safety vest..mechanical leg +1F9C0 ; Emoji_Presentation # E1.0 [1] (๐ง) cheese wedge +1F9C1..1F9C2 ; Emoji_Presentation # E11.0 [2] (๐ง..๐ง) cupcake..salt +1F9C3..1F9CA ; Emoji_Presentation # E12.0 [8] (๐ง..๐ง) beverage box..ice +1F9CB ; Emoji_Presentation # E13.0 [1] (๐ง) bubble tea +1F9CD..1F9CF ; Emoji_Presentation # E12.0 [3] (๐ง..๐ง) person standing..deaf person +1F9D0..1F9E6 ; Emoji_Presentation # E5.0 [23] (๐ง..๐งฆ) face with monocle..socks +1F9E7..1F9FF ; Emoji_Presentation # E11.0 [25] (๐งง..๐งฟ) red envelope..nazar amulet +1FA70..1FA73 ; Emoji_Presentation # E12.0 [4] (๐ฉฐ..๐ฉณ) ballet shoes..shorts +1FA74 ; Emoji_Presentation # E13.0 [1] (๐ฉด) thong sandal +1FA78..1FA7A ; Emoji_Presentation # E12.0 [3] (๐ฉธ..๐ฉบ) drop of blood..stethoscope +1FA80..1FA82 ; Emoji_Presentation # E12.0 [3] (๐ช..๐ช) yo-yo..parachute +1FA83..1FA86 ; Emoji_Presentation # E13.0 [4] (๐ช..๐ช) boomerang..nesting dolls +1FA90..1FA95 ; Emoji_Presentation # E12.0 [6] (๐ช..๐ช) ringed planet..banjo +1FA96..1FAA8 ; Emoji_Presentation # E13.0 [19] (๐ช..๐ชจ) military helmet..rock +1FAB0..1FAB6 ; Emoji_Presentation # E13.0 [7] (๐ชฐ..๐ชถ) fly..feather +1FAC0..1FAC2 ; Emoji_Presentation # E13.0 [3] (๐ซ..๐ซ) anatomical heart..people hugging +1FAD0..1FAD6 ; Emoji_Presentation # E13.0 [7] (๐ซ..๐ซ) blueberries..teapot -# Total elements: 1093 +# Total elements: 1148 # ================================================ # All omitted code points have Emoji_Modifier=No # @missing: 0000..10FFFF ; Emoji_Modifier ; No -1F3FB..1F3FF ; Emoji_Modifier # 8.0 [5] (๐ป..๐ฟ) light skin tone..dark skin tone +1F3FB..1F3FF ; Emoji_Modifier # E1.0 [5] (๐ป..๐ฟ) light skin tone..dark skin tone # Total elements: 5 @@ -438,66 +691,71 @@ # All omitted code points have Emoji_Modifier_Base=No # @missing: 0000..10FFFF ; Emoji_Modifier_Base ; No -261D ; Emoji_Modifier_Base # 1.1 [1] (โ๏ธ) index pointing up -26F9 ; Emoji_Modifier_Base # 5.2 [1] (โน๏ธ) person bouncing ball -270A..270B ; Emoji_Modifier_Base # 6.0 [2] (โ..โ) raised fist..raised hand -270C..270D ; Emoji_Modifier_Base # 1.1 [2] (โ๏ธ..โ๏ธ) victory hand..writing hand -1F385 ; Emoji_Modifier_Base # 6.0 [1] (๐
) Santa Claus -1F3C2..1F3C4 ; Emoji_Modifier_Base # 6.0 [3] (๐..๐) snowboarder..person surfing -1F3C7 ; Emoji_Modifier_Base # 6.0 [1] (๐) horse racing -1F3CA ; Emoji_Modifier_Base # 6.0 [1] (๐) person swimming -1F3CB..1F3CC ; Emoji_Modifier_Base # 7.0 [2] (๐๏ธ..๐๏ธ) person lifting weights..person golfing -1F442..1F443 ; Emoji_Modifier_Base # 6.0 [2] (๐..๐) ear..nose -1F446..1F450 ; Emoji_Modifier_Base # 6.0 [11] (๐..๐) backhand index pointing up..open hands -1F466..1F478 ; Emoji_Modifier_Base # 6.0 [19] (๐ฆ..๐ธ) boy..princess -1F47C ; Emoji_Modifier_Base # 6.0 [1] (๐ผ) baby angel -1F481..1F483 ; Emoji_Modifier_Base # 6.0 [3] (๐..๐) person tipping hand..woman dancing -1F485..1F487 ; Emoji_Modifier_Base # 6.0 [3] (๐
..๐) nail polish..person getting haircut -1F48F ; Emoji_Modifier_Base # 6.0 [1] (๐) kiss -1F491 ; Emoji_Modifier_Base # 6.0 [1] (๐) couple with heart -1F4AA ; Emoji_Modifier_Base # 6.0 [1] (๐ช) flexed biceps -1F574..1F575 ; Emoji_Modifier_Base # 7.0 [2] (๐ด๏ธ..๐ต๏ธ) man in suit levitating..detective -1F57A ; Emoji_Modifier_Base # 9.0 [1] (๐บ) man dancing -1F590 ; Emoji_Modifier_Base # 7.0 [1] (๐๏ธ) hand with fingers splayed -1F595..1F596 ; Emoji_Modifier_Base # 7.0 [2] (๐..๐) middle finger..vulcan salute -1F645..1F647 ; Emoji_Modifier_Base # 6.0 [3] (๐
..๐) person gesturing NO..person bowing -1F64B..1F64F ; Emoji_Modifier_Base # 6.0 [5] (๐..๐) person raising hand..folded hands -1F6A3 ; Emoji_Modifier_Base # 6.0 [1] (๐ฃ) person rowing boat -1F6B4..1F6B6 ; Emoji_Modifier_Base # 6.0 [3] (๐ด..๐ถ) person biking..person walking -1F6C0 ; Emoji_Modifier_Base # 6.0 [1] (๐) person taking bath -1F6CC ; Emoji_Modifier_Base # 7.0 [1] (๐) person in bed -1F90F ; Emoji_Modifier_Base # 12.0 [1] (๐ค) pinching hand -1F918 ; Emoji_Modifier_Base # 8.0 [1] (๐ค) sign of the horns -1F919..1F91E ; Emoji_Modifier_Base # 9.0 [6] (๐ค..๐ค) call me hand..crossed fingers -1F91F ; Emoji_Modifier_Base # 10.0 [1] (๐ค) love-you gesture -1F926 ; Emoji_Modifier_Base # 9.0 [1] (๐คฆ) person facepalming -1F930 ; Emoji_Modifier_Base # 9.0 [1] (๐คฐ) pregnant woman -1F931..1F932 ; Emoji_Modifier_Base # 10.0 [2] (๐คฑ..๐คฒ) breast-feeding..palms up together -1F933..1F939 ; Emoji_Modifier_Base # 9.0 [7] (๐คณ..๐คน) selfie..person juggling -1F93C..1F93E ; Emoji_Modifier_Base # 9.0 [3] (๐คผ..๐คพ) people wrestling..person playing handball -1F9B5..1F9B6 ; Emoji_Modifier_Base # 11.0 [2] (๐ฆต..๐ฆถ) leg..foot -1F9B8..1F9B9 ; Emoji_Modifier_Base # 11.0 [2] (๐ฆธ..๐ฆน) superhero..supervillain -1F9BB ; Emoji_Modifier_Base # 12.0 [1] (๐ฆป) ear with hearing aid -1F9CD..1F9CF ; Emoji_Modifier_Base # 12.0 [3] (๐ง..๐ง) person standing..deaf person -1F9D1..1F9DD ; Emoji_Modifier_Base # 10.0 [13] (๐ง..๐ง) person..elf +261D ; Emoji_Modifier_Base # E0.6 [1] (โ๏ธ) index pointing up +26F9 ; Emoji_Modifier_Base # E0.7 [1] (โน๏ธ) person bouncing ball +270A..270C ; Emoji_Modifier_Base # E0.6 [3] (โ..โ๏ธ) raised fist..victory hand +270D ; Emoji_Modifier_Base # E0.7 [1] (โ๏ธ) writing hand +1F385 ; Emoji_Modifier_Base # E0.6 [1] (๐
) Santa Claus +1F3C2..1F3C4 ; Emoji_Modifier_Base # E0.6 [3] (๐..๐) snowboarder..person surfing +1F3C7 ; Emoji_Modifier_Base # E1.0 [1] (๐) horse racing +1F3CA ; Emoji_Modifier_Base # E0.6 [1] (๐) person swimming +1F3CB..1F3CC ; Emoji_Modifier_Base # E0.7 [2] (๐๏ธ..๐๏ธ) person lifting weights..person golfing +1F442..1F443 ; Emoji_Modifier_Base # E0.6 [2] (๐..๐) ear..nose +1F446..1F450 ; Emoji_Modifier_Base # E0.6 [11] (๐..๐) backhand index pointing up..open hands +1F466..1F46B ; Emoji_Modifier_Base # E0.6 [6] (๐ฆ..๐ซ) boy..woman and man holding hands +1F46C..1F46D ; Emoji_Modifier_Base # E1.0 [2] (๐ฌ..๐ญ) men holding hands..women holding hands +1F46E..1F478 ; Emoji_Modifier_Base # E0.6 [11] (๐ฎ..๐ธ) police officer..princess +1F47C ; Emoji_Modifier_Base # E0.6 [1] (๐ผ) baby angel +1F481..1F483 ; Emoji_Modifier_Base # E0.6 [3] (๐..๐) person tipping hand..woman dancing +1F485..1F487 ; Emoji_Modifier_Base # E0.6 [3] (๐
..๐) nail polish..person getting haircut +1F48F ; Emoji_Modifier_Base # E0.6 [1] (๐) kiss +1F491 ; Emoji_Modifier_Base # E0.6 [1] (๐) couple with heart +1F4AA ; Emoji_Modifier_Base # E0.6 [1] (๐ช) flexed biceps +1F574..1F575 ; Emoji_Modifier_Base # E0.7 [2] (๐ด๏ธ..๐ต๏ธ) person in suit levitating..detective +1F57A ; Emoji_Modifier_Base # E3.0 [1] (๐บ) man dancing +1F590 ; Emoji_Modifier_Base # E0.7 [1] (๐๏ธ) hand with fingers splayed +1F595..1F596 ; Emoji_Modifier_Base # E1.0 [2] (๐..๐) middle finger..vulcan salute +1F645..1F647 ; Emoji_Modifier_Base # E0.6 [3] (๐
..๐) person gesturing NO..person bowing +1F64B..1F64F ; Emoji_Modifier_Base # E0.6 [5] (๐..๐) person raising hand..folded hands +1F6A3 ; Emoji_Modifier_Base # E1.0 [1] (๐ฃ) person rowing boat +1F6B4..1F6B5 ; Emoji_Modifier_Base # E1.0 [2] (๐ด..๐ต) person biking..person mountain biking +1F6B6 ; Emoji_Modifier_Base # E0.6 [1] (๐ถ) person walking +1F6C0 ; Emoji_Modifier_Base # E0.6 [1] (๐) person taking bath +1F6CC ; Emoji_Modifier_Base # E1.0 [1] (๐) person in bed +1F90C ; Emoji_Modifier_Base # E13.0 [1] (๐ค) pinched fingers +1F90F ; Emoji_Modifier_Base # E12.0 [1] (๐ค) pinching hand +1F918 ; Emoji_Modifier_Base # E1.0 [1] (๐ค) sign of the horns +1F919..1F91E ; Emoji_Modifier_Base # E3.0 [6] (๐ค..๐ค) call me hand..crossed fingers +1F91F ; Emoji_Modifier_Base # E5.0 [1] (๐ค) love-you gesture +1F926 ; Emoji_Modifier_Base # E3.0 [1] (๐คฆ) person facepalming +1F930 ; Emoji_Modifier_Base # E3.0 [1] (๐คฐ) pregnant woman +1F931..1F932 ; Emoji_Modifier_Base # E5.0 [2] (๐คฑ..๐คฒ) breast-feeding..palms up together +1F933..1F939 ; Emoji_Modifier_Base # E3.0 [7] (๐คณ..๐คน) selfie..person juggling +1F93C..1F93E ; Emoji_Modifier_Base # E3.0 [3] (๐คผ..๐คพ) people wrestling..person playing handball +1F977 ; Emoji_Modifier_Base # E13.0 [1] (๐ฅท) ninja +1F9B5..1F9B6 ; Emoji_Modifier_Base # E11.0 [2] (๐ฆต..๐ฆถ) leg..foot +1F9B8..1F9B9 ; Emoji_Modifier_Base # E11.0 [2] (๐ฆธ..๐ฆน) superhero..supervillain +1F9BB ; Emoji_Modifier_Base # E12.0 [1] (๐ฆป) ear with hearing aid +1F9CD..1F9CF ; Emoji_Modifier_Base # E12.0 [3] (๐ง..๐ง) person standing..deaf person +1F9D1..1F9DD ; Emoji_Modifier_Base # E5.0 [13] (๐ง..๐ง) person..elf -# Total elements: 120 +# Total elements: 122 # ================================================ # All omitted code points have Emoji_Component=No # @missing: 0000..10FFFF ; Emoji_Component ; No -0023 ; Emoji_Component # 1.1 [1] (#๏ธ) number sign -002A ; Emoji_Component # 1.1 [1] (*๏ธ) asterisk -0030..0039 ; Emoji_Component # 1.1 [10] (0๏ธ..9๏ธ) digit zero..digit nine -200D ; Emoji_Component # 1.1 [1] (โ) zero width joiner -20E3 ; Emoji_Component # 3.0 [1] (โฃ) combining enclosing keycap -FE0F ; Emoji_Component # 3.2 [1] () VARIATION SELECTOR-16 -1F1E6..1F1FF ; Emoji_Component # 6.0 [26] (๐ฆ..๐ฟ) regional indicator symbol letter a..regional indicator symbol letter z -1F3FB..1F3FF ; Emoji_Component # 8.0 [5] (๐ป..๐ฟ) light skin tone..dark skin tone -1F9B0..1F9B3 ; Emoji_Component # 11.0 [4] (๐ฆฐ..๐ฆณ) red hair..white hair -E0020..E007F ; Emoji_Component # 3.1 [96] (๓ ..๓ ฟ) tag space..cancel tag +0023 ; Emoji_Component # E0.0 [1] (#๏ธ) number sign +002A ; Emoji_Component # E0.0 [1] (*๏ธ) asterisk +0030..0039 ; Emoji_Component # E0.0 [10] (0๏ธ..9๏ธ) digit zero..digit nine +200D ; Emoji_Component # E0.0 [1] (โ) zero width joiner +20E3 ; Emoji_Component # E0.0 [1] (โฃ) combining enclosing keycap +FE0F ; Emoji_Component # E0.0 [1] () VARIATION SELECTOR-16 +1F1E6..1F1FF ; Emoji_Component # E0.0 [26] (๐ฆ..๐ฟ) regional indicator symbol letter a..regional indicator symbol letter z +1F3FB..1F3FF ; Emoji_Component # E1.0 [5] (๐ป..๐ฟ) light skin tone..dark skin tone +1F9B0..1F9B3 ; Emoji_Component # E11.0 [4] (๐ฆฐ..๐ฆณ) red hair..white hair +E0020..E007F ; Emoji_Component # E0.0 [96] (๓ ..๓ ฟ) tag space..cancel tag # Total elements: 146 @@ -506,264 +764,498 @@ E0020..E007F ; Emoji_Component # 3.1 [96] (๓ ..๓ ฟ) tag space..ca # All omitted code points have Extended_Pictographic=No # @missing: 0000..10FFFF ; Extended_Pictographic ; No -00A9 ; Extended_Pictographic# 1.1 [1] (ยฉ๏ธ) copyright -00AE ; Extended_Pictographic# 1.1 [1] (ยฎ๏ธ) registered -203C ; Extended_Pictographic# 1.1 [1] (โผ๏ธ) double exclamation mark -2049 ; Extended_Pictographic# 3.0 [1] (โ๏ธ) exclamation question mark -2122 ; Extended_Pictographic# 1.1 [1] (โข๏ธ) trade mark -2139 ; Extended_Pictographic# 3.0 [1] (โน๏ธ) information -2194..2199 ; Extended_Pictographic# 1.1 [6] (โ๏ธ..โ๏ธ) left-right arrow..down-left arrow -21A9..21AA ; Extended_Pictographic# 1.1 [2] (โฉ๏ธ..โช๏ธ) right arrow curving left..left arrow curving right -231A..231B ; Extended_Pictographic# 1.1 [2] (โ..โ) watch..hourglass done -2328 ; Extended_Pictographic# 1.1 [1] (โจ๏ธ) keyboard -2388 ; Extended_Pictographic# 3.0 [1] (โ) HELM SYMBOL -23CF ; Extended_Pictographic# 4.0 [1] (โ๏ธ) eject button -23E9..23F3 ; Extended_Pictographic# 6.0 [11] (โฉ..โณ) fast-forward button..hourglass not done -23F8..23FA ; Extended_Pictographic# 7.0 [3] (โธ๏ธ..โบ๏ธ) pause button..record button -24C2 ; Extended_Pictographic# 1.1 [1] (โ๏ธ) circled M -25AA..25AB ; Extended_Pictographic# 1.1 [2] (โช๏ธ..โซ๏ธ) black small square..white small square -25B6 ; Extended_Pictographic# 1.1 [1] (โถ๏ธ) play button -25C0 ; Extended_Pictographic# 1.1 [1] (โ๏ธ) reverse button -25FB..25FE ; Extended_Pictographic# 3.2 [4] (โป๏ธ..โพ) white medium square..black medium-small square -2600..2605 ; Extended_Pictographic# 1.1 [6] (โ๏ธ..โ
) sun..BLACK STAR -2607..2612 ; Extended_Pictographic# 1.1 [12] (โ..โ) LIGHTNING..BALLOT BOX WITH X -2614..2615 ; Extended_Pictographic# 4.0 [2] (โ..โ) umbrella with rain drops..hot beverage -2616..2617 ; Extended_Pictographic# 3.2 [2] (โ..โ) WHITE SHOGI PIECE..BLACK SHOGI PIECE -2618 ; Extended_Pictographic# 4.1 [1] (โ๏ธ) shamrock -2619 ; Extended_Pictographic# 3.0 [1] (โ) REVERSED ROTATED FLORAL HEART BULLET -261A..266F ; Extended_Pictographic# 1.1 [86] (โ..โฏ) BLACK LEFT POINTING INDEX..MUSIC SHARP SIGN -2670..2671 ; Extended_Pictographic# 3.0 [2] (โฐ..โฑ) WEST SYRIAC CROSS..EAST SYRIAC CROSS -2672..267D ; Extended_Pictographic# 3.2 [12] (โฒ..โฝ) UNIVERSAL RECYCLING SYMBOL..PARTIALLY-RECYCLED PAPER SYMBOL -267E..267F ; Extended_Pictographic# 4.1 [2] (โพ๏ธ..โฟ) infinity..wheelchair symbol -2680..2685 ; Extended_Pictographic# 3.2 [6] (โ..โ
) DIE FACE-1..DIE FACE-6 -2690..2691 ; Extended_Pictographic# 4.0 [2] (โ..โ) WHITE FLAG..BLACK FLAG -2692..269C ; Extended_Pictographic# 4.1 [11] (โ๏ธ..โ๏ธ) hammer and pick..fleur-de-lis -269D ; Extended_Pictographic# 5.1 [1] (โ) OUTLINED WHITE STAR -269E..269F ; Extended_Pictographic# 5.2 [2] (โ..โ) THREE LINES CONVERGING RIGHT..THREE LINES CONVERGING LEFT -26A0..26A1 ; Extended_Pictographic# 4.0 [2] (โ ๏ธ..โก) warning..high voltage -26A2..26B1 ; Extended_Pictographic# 4.1 [16] (โข..โฑ๏ธ) DOUBLED FEMALE SIGN..funeral urn -26B2 ; Extended_Pictographic# 5.0 [1] (โฒ) NEUTER -26B3..26BC ; Extended_Pictographic# 5.1 [10] (โณ..โผ) CERES..SESQUIQUADRATE -26BD..26BF ; Extended_Pictographic# 5.2 [3] (โฝ..โฟ) soccer ball..SQUARED KEY -26C0..26C3 ; Extended_Pictographic# 5.1 [4] (โ..โ) WHITE DRAUGHTS MAN..BLACK DRAUGHTS KING -26C4..26CD ; Extended_Pictographic# 5.2 [10] (โ..โ) snowman without snow..DISABLED CAR -26CE ; Extended_Pictographic# 6.0 [1] (โ) Ophiuchus -26CF..26E1 ; Extended_Pictographic# 5.2 [19] (โ๏ธ..โก) pick..RESTRICTED LEFT ENTRY-2 -26E2 ; Extended_Pictographic# 6.0 [1] (โข) ASTRONOMICAL SYMBOL FOR URANUS -26E3 ; Extended_Pictographic# 5.2 [1] (โฃ) HEAVY CIRCLE WITH STROKE AND TWO DOTS ABOVE -26E4..26E7 ; Extended_Pictographic# 6.0 [4] (โค..โง) PENTAGRAM..INVERTED PENTAGRAM -26E8..26FF ; Extended_Pictographic# 5.2 [24] (โจ..โฟ) BLACK CROSS ON SHIELD..WHITE FLAG WITH HORIZONTAL MIDDLE BLACK STRIPE -2700 ; Extended_Pictographic# 7.0 [1] (โ) BLACK SAFETY SCISSORS -2701..2704 ; Extended_Pictographic# 1.1 [4] (โ..โ) UPPER BLADE SCISSORS..WHITE SCISSORS -2705 ; Extended_Pictographic# 6.0 [1] (โ
) check mark button -2708..2709 ; Extended_Pictographic# 1.1 [2] (โ๏ธ..โ๏ธ) airplane..envelope -270A..270B ; Extended_Pictographic# 6.0 [2] (โ..โ) raised fist..raised hand -270C..2712 ; Extended_Pictographic# 1.1 [7] (โ๏ธ..โ๏ธ) victory hand..black nib -2714 ; Extended_Pictographic# 1.1 [1] (โ๏ธ) check mark -2716 ; Extended_Pictographic# 1.1 [1] (โ๏ธ) multiplication sign -271D ; Extended_Pictographic# 1.1 [1] (โ๏ธ) latin cross -2721 ; Extended_Pictographic# 1.1 [1] (โก๏ธ) star of David -2728 ; Extended_Pictographic# 6.0 [1] (โจ) sparkles -2733..2734 ; Extended_Pictographic# 1.1 [2] (โณ๏ธ..โด๏ธ) eight-spoked asterisk..eight-pointed star -2744 ; Extended_Pictographic# 1.1 [1] (โ๏ธ) snowflake -2747 ; Extended_Pictographic# 1.1 [1] (โ๏ธ) sparkle -274C ; Extended_Pictographic# 6.0 [1] (โ) cross mark -274E ; Extended_Pictographic# 6.0 [1] (โ) cross mark button -2753..2755 ; Extended_Pictographic# 6.0 [3] (โ..โ) question mark..white exclamation mark -2757 ; Extended_Pictographic# 5.2 [1] (โ) exclamation mark -2763..2767 ; Extended_Pictographic# 1.1 [5] (โฃ๏ธ..โง) heart exclamation..ROTATED FLORAL HEART BULLET -2795..2797 ; Extended_Pictographic# 6.0 [3] (โ..โ) plus sign..division sign -27A1 ; Extended_Pictographic# 1.1 [1] (โก๏ธ) right arrow -27B0 ; Extended_Pictographic# 6.0 [1] (โฐ) curly loop -27BF ; Extended_Pictographic# 6.0 [1] (โฟ) double curly loop -2934..2935 ; Extended_Pictographic# 3.2 [2] (โคด๏ธ..โคต๏ธ) right arrow curving up..right arrow curving down -2B05..2B07 ; Extended_Pictographic# 4.0 [3] (โฌ
๏ธ..โฌ๏ธ) left arrow..down arrow -2B1B..2B1C ; Extended_Pictographic# 5.1 [2] (โฌ..โฌ) black large square..white large square -2B50 ; Extended_Pictographic# 5.1 [1] (โญ) star -2B55 ; Extended_Pictographic# 5.2 [1] (โญ) hollow red circle -3030 ; Extended_Pictographic# 1.1 [1] (ใฐ๏ธ) wavy dash -303D ; Extended_Pictographic# 3.2 [1] (ใฝ๏ธ) part alternation mark -3297 ; Extended_Pictographic# 1.1 [1] (ใ๏ธ) Japanese โcongratulationsโ button -3299 ; Extended_Pictographic# 1.1 [1] (ใ๏ธ) Japanese โsecretโ button -1F000..1F02B ; Extended_Pictographic# 5.1 [44] (๐..๐ซ) MAHJONG TILE EAST WIND..MAHJONG TILE BACK -1F02C..1F02F ; Extended_Pictographic# NA [4] (๐ฌ..๐ฏ) <reserved-1F02C>..<reserved-1F02F> -1F030..1F093 ; Extended_Pictographic# 5.1[100] (๐ฐ..๐) DOMINO TILE HORIZONTAL BACK..DOMINO TILE VERTICAL-06-06 -1F094..1F09F ; Extended_Pictographic# NA [12] (๐..๐) <reserved-1F094>..<reserved-1F09F> -1F0A0..1F0AE ; Extended_Pictographic# 6.0 [15] (๐ ..๐ฎ) PLAYING CARD BACK..PLAYING CARD KING OF SPADES -1F0AF..1F0B0 ; Extended_Pictographic# NA [2] (๐ฏ..๐ฐ) <reserved-1F0AF>..<reserved-1F0B0> -1F0B1..1F0BE ; Extended_Pictographic# 6.0 [14] (๐ฑ..๐พ) PLAYING CARD ACE OF HEARTS..PLAYING CARD KING OF HEARTS -1F0BF ; Extended_Pictographic# 7.0 [1] (๐ฟ) PLAYING CARD RED JOKER -1F0C0 ; Extended_Pictographic# NA [1] (๐) <reserved-1F0C0> -1F0C1..1F0CF ; Extended_Pictographic# 6.0 [15] (๐..๐) PLAYING CARD ACE OF DIAMONDS..joker -1F0D0 ; Extended_Pictographic# NA [1] (๐) <reserved-1F0D0> -1F0D1..1F0DF ; Extended_Pictographic# 6.0 [15] (๐..๐) PLAYING CARD ACE OF CLUBS..PLAYING CARD WHITE JOKER -1F0E0..1F0F5 ; Extended_Pictographic# 7.0 [22] (๐ ..๐ต) PLAYING CARD FOOL..PLAYING CARD TRUMP-21 -1F0F6..1F0FF ; Extended_Pictographic# NA [10] (๐ถ..๐ฟ) <reserved-1F0F6>..<reserved-1F0FF> -1F10D..1F10F ; Extended_Pictographic# NA [3] (๐..๐) <reserved-1F10D>..<reserved-1F10F> -1F12F ; Extended_Pictographic# 11.0 [1] (๐ฏ) COPYLEFT SYMBOL -1F16C ; Extended_Pictographic# 12.0 [1] (๐
ฌ) RAISED MR SIGN -1F16D..1F16F ; Extended_Pictographic# NA [3] (๐
ญ..๐
ฏ) <reserved-1F16D>..<reserved-1F16F> -1F170..1F171 ; Extended_Pictographic# 6.0 [2] (๐
ฐ๏ธ..๐
ฑ๏ธ) A button (blood type)..B button (blood type) -1F17E ; Extended_Pictographic# 6.0 [1] (๐
พ๏ธ) O button (blood type) -1F17F ; Extended_Pictographic# 5.2 [1] (๐
ฟ๏ธ) P button -1F18E ; Extended_Pictographic# 6.0 [1] (๐) AB button (blood type) -1F191..1F19A ; Extended_Pictographic# 6.0 [10] (๐..๐) CL button..VS button -1F1AD..1F1E5 ; Extended_Pictographic# NA [57] (๐ญ..๐ฅ) <reserved-1F1AD>..<reserved-1F1E5> -1F201..1F202 ; Extended_Pictographic# 6.0 [2] (๐..๐๏ธ) Japanese โhereโ button..Japanese โservice chargeโ button -1F203..1F20F ; Extended_Pictographic# NA [13] (๐..๐) <reserved-1F203>..<reserved-1F20F> -1F21A ; Extended_Pictographic# 5.2 [1] (๐) Japanese โfree of chargeโ button -1F22F ; Extended_Pictographic# 5.2 [1] (๐ฏ) Japanese โreservedโ button -1F232..1F23A ; Extended_Pictographic# 6.0 [9] (๐ฒ..๐บ) Japanese โprohibitedโ button..Japanese โopen for businessโ button -1F23C..1F23F ; Extended_Pictographic# NA [4] (๐ผ..๐ฟ) <reserved-1F23C>..<reserved-1F23F> -1F249..1F24F ; Extended_Pictographic# NA [7] (๐..๐) <reserved-1F249>..<reserved-1F24F> -1F250..1F251 ; Extended_Pictographic# 6.0 [2] (๐..๐) Japanese โbargainโ button..Japanese โacceptableโ button -1F252..1F25F ; Extended_Pictographic# NA [14] (๐..๐) <reserved-1F252>..<reserved-1F25F> -1F260..1F265 ; Extended_Pictographic# 10.0 [6] (๐ ..๐ฅ) ROUNDED SYMBOL FOR FU..ROUNDED SYMBOL FOR CAI -1F266..1F2FF ; Extended_Pictographic# NA[154] (๐ฆ..๐ฟ) <reserved-1F266>..<reserved-1F2FF> -1F300..1F320 ; Extended_Pictographic# 6.0 [33] (๐..๐ ) cyclone..shooting star -1F321..1F32C ; Extended_Pictographic# 7.0 [12] (๐ก๏ธ..๐ฌ๏ธ) thermometer..wind face -1F32D..1F32F ; Extended_Pictographic# 8.0 [3] (๐ญ..๐ฏ) hot dog..burrito -1F330..1F335 ; Extended_Pictographic# 6.0 [6] (๐ฐ..๐ต) chestnut..cactus -1F336 ; Extended_Pictographic# 7.0 [1] (๐ถ๏ธ) hot pepper -1F337..1F37C ; Extended_Pictographic# 6.0 [70] (๐ท..๐ผ) tulip..baby bottle -1F37D ; Extended_Pictographic# 7.0 [1] (๐ฝ๏ธ) fork and knife with plate -1F37E..1F37F ; Extended_Pictographic# 8.0 [2] (๐พ..๐ฟ) bottle with popping cork..popcorn -1F380..1F393 ; Extended_Pictographic# 6.0 [20] (๐..๐) ribbon..graduation cap -1F394..1F39F ; Extended_Pictographic# 7.0 [12] (๐..๐๏ธ) HEART WITH TIP ON THE LEFT..admission tickets -1F3A0..1F3C4 ; Extended_Pictographic# 6.0 [37] (๐ ..๐) carousel horse..person surfing -1F3C5 ; Extended_Pictographic# 7.0 [1] (๐
) sports medal -1F3C6..1F3CA ; Extended_Pictographic# 6.0 [5] (๐..๐) trophy..person swimming -1F3CB..1F3CE ; Extended_Pictographic# 7.0 [4] (๐๏ธ..๐๏ธ) person lifting weights..racing car -1F3CF..1F3D3 ; Extended_Pictographic# 8.0 [5] (๐..๐) cricket game..ping pong -1F3D4..1F3DF ; Extended_Pictographic# 7.0 [12] (๐๏ธ..๐๏ธ) snow-capped mountain..stadium -1F3E0..1F3F0 ; Extended_Pictographic# 6.0 [17] (๐ ..๐ฐ) house..castle -1F3F1..1F3F7 ; Extended_Pictographic# 7.0 [7] (๐ฑ..๐ท๏ธ) WHITE PENNANT..label -1F3F8..1F3FA ; Extended_Pictographic# 8.0 [3] (๐ธ..๐บ) badminton..amphora -1F400..1F43E ; Extended_Pictographic# 6.0 [63] (๐..๐พ) rat..paw prints -1F43F ; Extended_Pictographic# 7.0 [1] (๐ฟ๏ธ) chipmunk -1F440 ; Extended_Pictographic# 6.0 [1] (๐) eyes -1F441 ; Extended_Pictographic# 7.0 [1] (๐๏ธ) eye -1F442..1F4F7 ; Extended_Pictographic# 6.0[182] (๐..๐ท) ear..camera -1F4F8 ; Extended_Pictographic# 7.0 [1] (๐ธ) camera with flash -1F4F9..1F4FC ; Extended_Pictographic# 6.0 [4] (๐น..๐ผ) video camera..videocassette -1F4FD..1F4FE ; Extended_Pictographic# 7.0 [2] (๐ฝ๏ธ..๐พ) film projector..PORTABLE STEREO -1F4FF ; Extended_Pictographic# 8.0 [1] (๐ฟ) prayer beads -1F500..1F53D ; Extended_Pictographic# 6.0 [62] (๐..๐ฝ) shuffle tracks button..downwards button -1F546..1F54A ; Extended_Pictographic# 7.0 [5] (๐..๐๏ธ) WHITE LATIN CROSS..dove -1F54B..1F54F ; Extended_Pictographic# 8.0 [5] (๐..๐) kaaba..BOWL OF HYGIEIA -1F550..1F567 ; Extended_Pictographic# 6.0 [24] (๐..๐ง) one oโclock..twelve-thirty -1F568..1F579 ; Extended_Pictographic# 7.0 [18] (๐จ..๐น๏ธ) RIGHT SPEAKER..joystick -1F57A ; Extended_Pictographic# 9.0 [1] (๐บ) man dancing -1F57B..1F5A3 ; Extended_Pictographic# 7.0 [41] (๐ป..๐ฃ) LEFT HAND TELEPHONE RECEIVER..BLACK DOWN POINTING BACKHAND INDEX -1F5A4 ; Extended_Pictographic# 9.0 [1] (๐ค) black heart -1F5A5..1F5FA ; Extended_Pictographic# 7.0 [86] (๐ฅ๏ธ..๐บ๏ธ) desktop computer..world map -1F5FB..1F5FF ; Extended_Pictographic# 6.0 [5] (๐ป..๐ฟ) mount fuji..moai -1F600 ; Extended_Pictographic# 6.1 [1] (๐) grinning face -1F601..1F610 ; Extended_Pictographic# 6.0 [16] (๐..๐) beaming face with smiling eyes..neutral face -1F611 ; Extended_Pictographic# 6.1 [1] (๐) expressionless face -1F612..1F614 ; Extended_Pictographic# 6.0 [3] (๐..๐) unamused face..pensive face -1F615 ; Extended_Pictographic# 6.1 [1] (๐) confused face -1F616 ; Extended_Pictographic# 6.0 [1] (๐) confounded face -1F617 ; Extended_Pictographic# 6.1 [1] (๐) kissing face -1F618 ; Extended_Pictographic# 6.0 [1] (๐) face blowing a kiss -1F619 ; Extended_Pictographic# 6.1 [1] (๐) kissing face with smiling eyes -1F61A ; Extended_Pictographic# 6.0 [1] (๐) kissing face with closed eyes -1F61B ; Extended_Pictographic# 6.1 [1] (๐) face with tongue -1F61C..1F61E ; Extended_Pictographic# 6.0 [3] (๐..๐) winking face with tongue..disappointed face -1F61F ; Extended_Pictographic# 6.1 [1] (๐) worried face -1F620..1F625 ; Extended_Pictographic# 6.0 [6] (๐ ..๐ฅ) angry face..sad but relieved face -1F626..1F627 ; Extended_Pictographic# 6.1 [2] (๐ฆ..๐ง) frowning face with open mouth..anguished face -1F628..1F62B ; Extended_Pictographic# 6.0 [4] (๐จ..๐ซ) fearful face..tired face -1F62C ; Extended_Pictographic# 6.1 [1] (๐ฌ) grimacing face -1F62D ; Extended_Pictographic# 6.0 [1] (๐ญ) loudly crying face -1F62E..1F62F ; Extended_Pictographic# 6.1 [2] (๐ฎ..๐ฏ) face with open mouth..hushed face -1F630..1F633 ; Extended_Pictographic# 6.0 [4] (๐ฐ..๐ณ) anxious face with sweat..flushed face -1F634 ; Extended_Pictographic# 6.1 [1] (๐ด) sleeping face -1F635..1F640 ; Extended_Pictographic# 6.0 [12] (๐ต..๐) dizzy face..weary cat -1F641..1F642 ; Extended_Pictographic# 7.0 [2] (๐..๐) slightly frowning face..slightly smiling face -1F643..1F644 ; Extended_Pictographic# 8.0 [2] (๐..๐) upside-down face..face with rolling eyes -1F645..1F64F ; Extended_Pictographic# 6.0 [11] (๐
..๐) person gesturing NO..folded hands -1F680..1F6C5 ; Extended_Pictographic# 6.0 [70] (๐..๐
) rocket..left luggage -1F6C6..1F6CF ; Extended_Pictographic# 7.0 [10] (๐..๐๏ธ) TRIANGLE WITH ROUNDED CORNERS..bed -1F6D0 ; Extended_Pictographic# 8.0 [1] (๐) place of worship -1F6D1..1F6D2 ; Extended_Pictographic# 9.0 [2] (๐..๐) stop sign..shopping cart -1F6D3..1F6D4 ; Extended_Pictographic# 10.0 [2] (๐..๐) STUPA..PAGODA -1F6D5 ; Extended_Pictographic# 12.0 [1] (๐) hindu temple -1F6D6..1F6DF ; Extended_Pictographic# NA [10] (๐..๐) <reserved-1F6D6>..<reserved-1F6DF> -1F6E0..1F6EC ; Extended_Pictographic# 7.0 [13] (๐ ๏ธ..๐ฌ) hammer and wrench..airplane arrival -1F6ED..1F6EF ; Extended_Pictographic# NA [3] (๐ญ..๐ฏ) <reserved-1F6ED>..<reserved-1F6EF> -1F6F0..1F6F3 ; Extended_Pictographic# 7.0 [4] (๐ฐ๏ธ..๐ณ๏ธ) satellite..passenger ship -1F6F4..1F6F6 ; Extended_Pictographic# 9.0 [3] (๐ด..๐ถ) kick scooter..canoe -1F6F7..1F6F8 ; Extended_Pictographic# 10.0 [2] (๐ท..๐ธ) sled..flying saucer -1F6F9 ; Extended_Pictographic# 11.0 [1] (๐น) skateboard -1F6FA ; Extended_Pictographic# 12.0 [1] (๐บ) auto rickshaw -1F6FB..1F6FF ; Extended_Pictographic# NA [5] (๐ป..๐ฟ) <reserved-1F6FB>..<reserved-1F6FF> -1F774..1F77F ; Extended_Pictographic# NA [12] (๐ด..๐ฟ) <reserved-1F774>..<reserved-1F77F> -1F7D5..1F7D8 ; Extended_Pictographic# 11.0 [4] (๐..๐) CIRCLED TRIANGLE..NEGATIVE CIRCLED SQUARE -1F7D9..1F7DF ; Extended_Pictographic# NA [7] (๐..๐) <reserved-1F7D9>..<reserved-1F7DF> -1F7E0..1F7EB ; Extended_Pictographic# 12.0 [12] (๐ ..๐ซ) orange circle..brown square -1F7EC..1F7FF ; Extended_Pictographic# NA [20] (๐ฌ..๐ฟ) <reserved-1F7EC>..<reserved-1F7FF> -1F80C..1F80F ; Extended_Pictographic# NA [4] (๐ ..๐ ) <reserved-1F80C>..<reserved-1F80F> -1F848..1F84F ; Extended_Pictographic# NA [8] (๐ก..๐ก) <reserved-1F848>..<reserved-1F84F> -1F85A..1F85F ; Extended_Pictographic# NA [6] (๐ก..๐ก) <reserved-1F85A>..<reserved-1F85F> -1F888..1F88F ; Extended_Pictographic# NA [8] (๐ข..๐ข) <reserved-1F888>..<reserved-1F88F> -1F8AE..1F8FF ; Extended_Pictographic# NA [82] (๐ขฎ..๐ฃฟ) <reserved-1F8AE>..<reserved-1F8FF> -1F90C ; Extended_Pictographic# NA [1] (๐ค) <reserved-1F90C> -1F90D..1F90F ; Extended_Pictographic# 12.0 [3] (๐ค..๐ค) white heart..pinching hand -1F910..1F918 ; Extended_Pictographic# 8.0 [9] (๐ค..๐ค) zipper-mouth face..sign of the horns -1F919..1F91E ; Extended_Pictographic# 9.0 [6] (๐ค..๐ค) call me hand..crossed fingers -1F91F ; Extended_Pictographic# 10.0 [1] (๐ค) love-you gesture -1F920..1F927 ; Extended_Pictographic# 9.0 [8] (๐ค ..๐คง) cowboy hat face..sneezing face -1F928..1F92F ; Extended_Pictographic# 10.0 [8] (๐คจ..๐คฏ) face with raised eyebrow..exploding head -1F930 ; Extended_Pictographic# 9.0 [1] (๐คฐ) pregnant woman -1F931..1F932 ; Extended_Pictographic# 10.0 [2] (๐คฑ..๐คฒ) breast-feeding..palms up together -1F933..1F93A ; Extended_Pictographic# 9.0 [8] (๐คณ..๐คบ) selfie..person fencing -1F93C..1F93E ; Extended_Pictographic# 9.0 [3] (๐คผ..๐คพ) people wrestling..person playing handball -1F93F ; Extended_Pictographic# 12.0 [1] (๐คฟ) diving mask -1F940..1F945 ; Extended_Pictographic# 9.0 [6] (๐ฅ..๐ฅ
) wilted flower..goal net -1F947..1F94B ; Extended_Pictographic# 9.0 [5] (๐ฅ..๐ฅ) 1st place medal..martial arts uniform -1F94C ; Extended_Pictographic# 10.0 [1] (๐ฅ) curling stone -1F94D..1F94F ; Extended_Pictographic# 11.0 [3] (๐ฅ..๐ฅ) lacrosse..flying disc -1F950..1F95E ; Extended_Pictographic# 9.0 [15] (๐ฅ..๐ฅ) croissant..pancakes -1F95F..1F96B ; Extended_Pictographic# 10.0 [13] (๐ฅ..๐ฅซ) dumpling..canned food -1F96C..1F970 ; Extended_Pictographic# 11.0 [5] (๐ฅฌ..๐ฅฐ) leafy green..smiling face with hearts -1F971 ; Extended_Pictographic# 12.0 [1] (๐ฅฑ) yawning face -1F972 ; Extended_Pictographic# NA [1] (๐ฅฒ) <reserved-1F972> -1F973..1F976 ; Extended_Pictographic# 11.0 [4] (๐ฅณ..๐ฅถ) partying face..cold face -1F977..1F979 ; Extended_Pictographic# NA [3] (๐ฅท..๐ฅน) <reserved-1F977>..<reserved-1F979> -1F97A ; Extended_Pictographic# 11.0 [1] (๐ฅบ) pleading face -1F97B ; Extended_Pictographic# 12.0 [1] (๐ฅป) sari -1F97C..1F97F ; Extended_Pictographic# 11.0 [4] (๐ฅผ..๐ฅฟ) lab coat..flat shoe -1F980..1F984 ; Extended_Pictographic# 8.0 [5] (๐ฆ..๐ฆ) crab..unicorn -1F985..1F991 ; Extended_Pictographic# 9.0 [13] (๐ฆ
..๐ฆ) eagle..squid -1F992..1F997 ; Extended_Pictographic# 10.0 [6] (๐ฆ..๐ฆ) giraffe..cricket -1F998..1F9A2 ; Extended_Pictographic# 11.0 [11] (๐ฆ..๐ฆข) kangaroo..swan -1F9A3..1F9A4 ; Extended_Pictographic# NA [2] (๐ฆฃ..๐ฆค) <reserved-1F9A3>..<reserved-1F9A4> -1F9A5..1F9AA ; Extended_Pictographic# 12.0 [6] (๐ฆฅ..๐ฆช) sloth..oyster -1F9AB..1F9AD ; Extended_Pictographic# NA [3] (๐ฆซ..๐ฆญ) <reserved-1F9AB>..<reserved-1F9AD> -1F9AE..1F9AF ; Extended_Pictographic# 12.0 [2] (๐ฆฎ..๐ฆฏ) guide dog..probing cane -1F9B0..1F9B9 ; Extended_Pictographic# 11.0 [10] (๐ฆฐ..๐ฆน) red hair..supervillain -1F9BA..1F9BF ; Extended_Pictographic# 12.0 [6] (๐ฆบ..๐ฆฟ) safety vest..mechanical leg -1F9C0 ; Extended_Pictographic# 8.0 [1] (๐ง) cheese wedge -1F9C1..1F9C2 ; Extended_Pictographic# 11.0 [2] (๐ง..๐ง) cupcake..salt -1F9C3..1F9CA ; Extended_Pictographic# 12.0 [8] (๐ง..๐ง) beverage box..ice cube -1F9CB..1F9CC ; Extended_Pictographic# NA [2] (๐ง..๐ง) <reserved-1F9CB>..<reserved-1F9CC> -1F9CD..1F9CF ; Extended_Pictographic# 12.0 [3] (๐ง..๐ง) person standing..deaf person -1F9D0..1F9E6 ; Extended_Pictographic# 10.0 [23] (๐ง..๐งฆ) face with monocle..socks -1F9E7..1F9FF ; Extended_Pictographic# 11.0 [25] (๐งง..๐งฟ) red envelope..nazar amulet -1FA00..1FA53 ; Extended_Pictographic# 12.0 [84] (๐จ..๐ฉ) NEUTRAL CHESS KING..BLACK CHESS KNIGHT-BISHOP -1FA54..1FA5F ; Extended_Pictographic# NA [12] (๐ฉ..๐ฉ) <reserved-1FA54>..<reserved-1FA5F> -1FA60..1FA6D ; Extended_Pictographic# 11.0 [14] (๐ฉ ..๐ฉญ) XIANGQI RED GENERAL..XIANGQI BLACK SOLDIER -1FA6E..1FA6F ; Extended_Pictographic# NA [2] (๐ฉฎ..๐ฉฏ) <reserved-1FA6E>..<reserved-1FA6F> -1FA70..1FA73 ; Extended_Pictographic# 12.0 [4] (๐ฉฐ..๐ฉณ) ballet shoes..shorts -1FA74..1FA77 ; Extended_Pictographic# NA [4] (๐ฉด..๐ฉท) <reserved-1FA74>..<reserved-1FA77> -1FA78..1FA7A ; Extended_Pictographic# 12.0 [3] (๐ฉธ..๐ฉบ) drop of blood..stethoscope -1FA7B..1FA7F ; Extended_Pictographic# NA [5] (๐ฉป..๐ฉฟ) <reserved-1FA7B>..<reserved-1FA7F> -1FA80..1FA82 ; Extended_Pictographic# 12.0 [3] (๐ช..๐ช) yo-yo..parachute -1FA83..1FA8F ; Extended_Pictographic# NA [13] (๐ช..๐ช) <reserved-1FA83>..<reserved-1FA8F> -1FA90..1FA95 ; Extended_Pictographic# 12.0 [6] (๐ช..๐ช) ringed planet..banjo -1FA96..1FFFD ; Extended_Pictographic# NA[1384] (๐ช..๐ฟฝ) <reserved-1FA96>..<reserved-1FFFD> +00A9 ; Extended_Pictographic# E0.6 [1] (ยฉ๏ธ) copyright +00AE ; Extended_Pictographic# E0.6 [1] (ยฎ๏ธ) registered +203C ; Extended_Pictographic# E0.6 [1] (โผ๏ธ) double exclamation mark +2049 ; Extended_Pictographic# E0.6 [1] (โ๏ธ) exclamation question mark +2122 ; Extended_Pictographic# E0.6 [1] (โข๏ธ) trade mark +2139 ; Extended_Pictographic# E0.6 [1] (โน๏ธ) information +2194..2199 ; Extended_Pictographic# E0.6 [6] (โ๏ธ..โ๏ธ) left-right arrow..down-left arrow +21A9..21AA ; Extended_Pictographic# E0.6 [2] (โฉ๏ธ..โช๏ธ) right arrow curving left..left arrow curving right +231A..231B ; Extended_Pictographic# E0.6 [2] (โ..โ) watch..hourglass done +2328 ; Extended_Pictographic# E1.0 [1] (โจ๏ธ) keyboard +2388 ; Extended_Pictographic# E0.0 [1] (โ) HELM SYMBOL +23CF ; Extended_Pictographic# E1.0 [1] (โ๏ธ) eject button +23E9..23EC ; Extended_Pictographic# E0.6 [4] (โฉ..โฌ) fast-forward button..fast down button +23ED..23EE ; Extended_Pictographic# E0.7 [2] (โญ๏ธ..โฎ๏ธ) next track button..last track button +23EF ; Extended_Pictographic# E1.0 [1] (โฏ๏ธ) play or pause button +23F0 ; Extended_Pictographic# E0.6 [1] (โฐ) alarm clock +23F1..23F2 ; Extended_Pictographic# E1.0 [2] (โฑ๏ธ..โฒ๏ธ) stopwatch..timer clock +23F3 ; Extended_Pictographic# E0.6 [1] (โณ) hourglass not done +23F8..23FA ; Extended_Pictographic# E0.7 [3] (โธ๏ธ..โบ๏ธ) pause button..record button +24C2 ; Extended_Pictographic# E0.6 [1] (โ๏ธ) circled M +25AA..25AB ; Extended_Pictographic# E0.6 [2] (โช๏ธ..โซ๏ธ) black small square..white small square +25B6 ; Extended_Pictographic# E0.6 [1] (โถ๏ธ) play button +25C0 ; Extended_Pictographic# E0.6 [1] (โ๏ธ) reverse button +25FB..25FE ; Extended_Pictographic# E0.6 [4] (โป๏ธ..โพ) white medium square..black medium-small square +2600..2601 ; Extended_Pictographic# E0.6 [2] (โ๏ธ..โ๏ธ) sun..cloud +2602..2603 ; Extended_Pictographic# E0.7 [2] (โ๏ธ..โ๏ธ) umbrella..snowman +2604 ; Extended_Pictographic# E1.0 [1] (โ๏ธ) comet +2605 ; Extended_Pictographic# E0.0 [1] (โ
) BLACK STAR +2607..260D ; Extended_Pictographic# E0.0 [7] (โ..โ) LIGHTNING..OPPOSITION +260E ; Extended_Pictographic# E0.6 [1] (โ๏ธ) telephone +260F..2610 ; Extended_Pictographic# E0.0 [2] (โ..โ) WHITE TELEPHONE..BALLOT BOX +2611 ; Extended_Pictographic# E0.6 [1] (โ๏ธ) check box with check +2612 ; Extended_Pictographic# E0.0 [1] (โ) BALLOT BOX WITH X +2614..2615 ; Extended_Pictographic# E0.6 [2] (โ..โ) umbrella with rain drops..hot beverage +2616..2617 ; Extended_Pictographic# E0.0 [2] (โ..โ) WHITE SHOGI PIECE..BLACK SHOGI PIECE +2618 ; Extended_Pictographic# E1.0 [1] (โ๏ธ) shamrock +2619..261C ; Extended_Pictographic# E0.0 [4] (โ..โ) REVERSED ROTATED FLORAL HEART BULLET..WHITE LEFT POINTING INDEX +261D ; Extended_Pictographic# E0.6 [1] (โ๏ธ) index pointing up +261E..261F ; Extended_Pictographic# E0.0 [2] (โ..โ) WHITE RIGHT POINTING INDEX..WHITE DOWN POINTING INDEX +2620 ; Extended_Pictographic# E1.0 [1] (โ ๏ธ) skull and crossbones +2621 ; Extended_Pictographic# E0.0 [1] (โก) CAUTION SIGN +2622..2623 ; Extended_Pictographic# E1.0 [2] (โข๏ธ..โฃ๏ธ) radioactive..biohazard +2624..2625 ; Extended_Pictographic# E0.0 [2] (โค..โฅ) CADUCEUS..ANKH +2626 ; Extended_Pictographic# E1.0 [1] (โฆ๏ธ) orthodox cross +2627..2629 ; Extended_Pictographic# E0.0 [3] (โง..โฉ) CHI RHO..CROSS OF JERUSALEM +262A ; Extended_Pictographic# E0.7 [1] (โช๏ธ) star and crescent +262B..262D ; Extended_Pictographic# E0.0 [3] (โซ..โญ) FARSI SYMBOL..HAMMER AND SICKLE +262E ; Extended_Pictographic# E1.0 [1] (โฎ๏ธ) peace symbol +262F ; Extended_Pictographic# E0.7 [1] (โฏ๏ธ) yin yang +2630..2637 ; Extended_Pictographic# E0.0 [8] (โฐ..โท) TRIGRAM FOR HEAVEN..TRIGRAM FOR EARTH +2638..2639 ; Extended_Pictographic# E0.7 [2] (โธ๏ธ..โน๏ธ) wheel of dharma..frowning face +263A ; Extended_Pictographic# E0.6 [1] (โบ๏ธ) smiling face +263B..263F ; Extended_Pictographic# E0.0 [5] (โป..โฟ) BLACK SMILING FACE..MERCURY +2640 ; Extended_Pictographic# E4.0 [1] (โ๏ธ) female sign +2641 ; Extended_Pictographic# E0.0 [1] (โ) EARTH +2642 ; Extended_Pictographic# E4.0 [1] (โ๏ธ) male sign +2643..2647 ; Extended_Pictographic# E0.0 [5] (โ..โ) JUPITER..PLUTO +2648..2653 ; Extended_Pictographic# E0.6 [12] (โ..โ) Aries..Pisces +2654..265E ; Extended_Pictographic# E0.0 [11] (โ..โ) WHITE CHESS KING..BLACK CHESS KNIGHT +265F ; Extended_Pictographic# E11.0 [1] (โ๏ธ) chess pawn +2660 ; Extended_Pictographic# E0.6 [1] (โ ๏ธ) spade suit +2661..2662 ; Extended_Pictographic# E0.0 [2] (โก..โข) WHITE HEART SUIT..WHITE DIAMOND SUIT +2663 ; Extended_Pictographic# E0.6 [1] (โฃ๏ธ) club suit +2664 ; Extended_Pictographic# E0.0 [1] (โค) WHITE SPADE SUIT +2665..2666 ; Extended_Pictographic# E0.6 [2] (โฅ๏ธ..โฆ๏ธ) heart suit..diamond suit +2667 ; Extended_Pictographic# E0.0 [1] (โง) WHITE CLUB SUIT +2668 ; Extended_Pictographic# E0.6 [1] (โจ๏ธ) hot springs +2669..267A ; Extended_Pictographic# E0.0 [18] (โฉ..โบ) QUARTER NOTE..RECYCLING SYMBOL FOR GENERIC MATERIALS +267B ; Extended_Pictographic# E0.6 [1] (โป๏ธ) recycling symbol +267C..267D ; Extended_Pictographic# E0.0 [2] (โผ..โฝ) RECYCLED PAPER SYMBOL..PARTIALLY-RECYCLED PAPER SYMBOL +267E ; Extended_Pictographic# E11.0 [1] (โพ๏ธ) infinity +267F ; Extended_Pictographic# E0.6 [1] (โฟ) wheelchair symbol +2680..2685 ; Extended_Pictographic# E0.0 [6] (โ..โ
) DIE FACE-1..DIE FACE-6 +2690..2691 ; Extended_Pictographic# E0.0 [2] (โ..โ) WHITE FLAG..BLACK FLAG +2692 ; Extended_Pictographic# E1.0 [1] (โ๏ธ) hammer and pick +2693 ; Extended_Pictographic# E0.6 [1] (โ) anchor +2694 ; Extended_Pictographic# E1.0 [1] (โ๏ธ) crossed swords +2695 ; Extended_Pictographic# E4.0 [1] (โ๏ธ) medical symbol +2696..2697 ; Extended_Pictographic# E1.0 [2] (โ๏ธ..โ๏ธ) balance scale..alembic +2698 ; Extended_Pictographic# E0.0 [1] (โ) FLOWER +2699 ; Extended_Pictographic# E1.0 [1] (โ๏ธ) gear +269A ; Extended_Pictographic# E0.0 [1] (โ) STAFF OF HERMES +269B..269C ; Extended_Pictographic# E1.0 [2] (โ๏ธ..โ๏ธ) atom symbol..fleur-de-lis +269D..269F ; Extended_Pictographic# E0.0 [3] (โ..โ) OUTLINED WHITE STAR..THREE LINES CONVERGING LEFT +26A0..26A1 ; Extended_Pictographic# E0.6 [2] (โ ๏ธ..โก) warning..high voltage +26A2..26A6 ; Extended_Pictographic# E0.0 [5] (โข..โฆ) DOUBLED FEMALE SIGN..MALE WITH STROKE SIGN +26A7 ; Extended_Pictographic# E13.0 [1] (โง๏ธ) transgender symbol +26A8..26A9 ; Extended_Pictographic# E0.0 [2] (โจ..โฉ) VERTICAL MALE WITH STROKE SIGN..HORIZONTAL MALE WITH STROKE SIGN +26AA..26AB ; Extended_Pictographic# E0.6 [2] (โช..โซ) white circle..black circle +26AC..26AF ; Extended_Pictographic# E0.0 [4] (โฌ..โฏ) MEDIUM SMALL WHITE CIRCLE..UNMARRIED PARTNERSHIP SYMBOL +26B0..26B1 ; Extended_Pictographic# E1.0 [2] (โฐ๏ธ..โฑ๏ธ) coffin..funeral urn +26B2..26BC ; Extended_Pictographic# E0.0 [11] (โฒ..โผ) NEUTER..SESQUIQUADRATE +26BD..26BE ; Extended_Pictographic# E0.6 [2] (โฝ..โพ) soccer ball..baseball +26BF..26C3 ; Extended_Pictographic# E0.0 [5] (โฟ..โ) SQUARED KEY..BLACK DRAUGHTS KING +26C4..26C5 ; Extended_Pictographic# E0.6 [2] (โ..โ
) snowman without snow..sun behind cloud +26C6..26C7 ; Extended_Pictographic# E0.0 [2] (โ..โ) RAIN..BLACK SNOWMAN +26C8 ; Extended_Pictographic# E0.7 [1] (โ๏ธ) cloud with lightning and rain +26C9..26CD ; Extended_Pictographic# E0.0 [5] (โ..โ) TURNED WHITE SHOGI PIECE..DISABLED CAR +26CE ; Extended_Pictographic# E0.6 [1] (โ) Ophiuchus +26CF ; Extended_Pictographic# E0.7 [1] (โ๏ธ) pick +26D0 ; Extended_Pictographic# E0.0 [1] (โ) CAR SLIDING +26D1 ; Extended_Pictographic# E0.7 [1] (โ๏ธ) rescue workerโs helmet +26D2 ; Extended_Pictographic# E0.0 [1] (โ) CIRCLED CROSSING LANES +26D3 ; Extended_Pictographic# E0.7 [1] (โ๏ธ) chains +26D4 ; Extended_Pictographic# E0.6 [1] (โ) no entry +26D5..26E8 ; Extended_Pictographic# E0.0 [20] (โ..โจ) ALTERNATE ONE-WAY LEFT WAY TRAFFIC..BLACK CROSS ON SHIELD +26E9 ; Extended_Pictographic# E0.7 [1] (โฉ๏ธ) shinto shrine +26EA ; Extended_Pictographic# E0.6 [1] (โช) church +26EB..26EF ; Extended_Pictographic# E0.0 [5] (โซ..โฏ) CASTLE..MAP SYMBOL FOR LIGHTHOUSE +26F0..26F1 ; Extended_Pictographic# E0.7 [2] (โฐ๏ธ..โฑ๏ธ) mountain..umbrella on ground +26F2..26F3 ; Extended_Pictographic# E0.6 [2] (โฒ..โณ) fountain..flag in hole +26F4 ; Extended_Pictographic# E0.7 [1] (โด๏ธ) ferry +26F5 ; Extended_Pictographic# E0.6 [1] (โต) sailboat +26F6 ; Extended_Pictographic# E0.0 [1] (โถ) SQUARE FOUR CORNERS +26F7..26F9 ; Extended_Pictographic# E0.7 [3] (โท๏ธ..โน๏ธ) skier..person bouncing ball +26FA ; Extended_Pictographic# E0.6 [1] (โบ) tent +26FB..26FC ; Extended_Pictographic# E0.0 [2] (โป..โผ) JAPANESE BANK SYMBOL..HEADSTONE GRAVEYARD SYMBOL +26FD ; Extended_Pictographic# E0.6 [1] (โฝ) fuel pump +26FE..2701 ; Extended_Pictographic# E0.0 [4] (โพ..โ) CUP ON BLACK SQUARE..UPPER BLADE SCISSORS +2702 ; Extended_Pictographic# E0.6 [1] (โ๏ธ) scissors +2703..2704 ; Extended_Pictographic# E0.0 [2] (โ..โ) LOWER BLADE SCISSORS..WHITE SCISSORS +2705 ; Extended_Pictographic# E0.6 [1] (โ
) check mark button +2708..270C ; Extended_Pictographic# E0.6 [5] (โ๏ธ..โ๏ธ) airplane..victory hand +270D ; Extended_Pictographic# E0.7 [1] (โ๏ธ) writing hand +270E ; Extended_Pictographic# E0.0 [1] (โ) LOWER RIGHT PENCIL +270F ; Extended_Pictographic# E0.6 [1] (โ๏ธ) pencil +2710..2711 ; Extended_Pictographic# E0.0 [2] (โ..โ) UPPER RIGHT PENCIL..WHITE NIB +2712 ; Extended_Pictographic# E0.6 [1] (โ๏ธ) black nib +2714 ; Extended_Pictographic# E0.6 [1] (โ๏ธ) check mark +2716 ; Extended_Pictographic# E0.6 [1] (โ๏ธ) multiply +271D ; Extended_Pictographic# E0.7 [1] (โ๏ธ) latin cross +2721 ; Extended_Pictographic# E0.7 [1] (โก๏ธ) star of David +2728 ; Extended_Pictographic# E0.6 [1] (โจ) sparkles +2733..2734 ; Extended_Pictographic# E0.6 [2] (โณ๏ธ..โด๏ธ) eight-spoked asterisk..eight-pointed star +2744 ; Extended_Pictographic# E0.6 [1] (โ๏ธ) snowflake +2747 ; Extended_Pictographic# E0.6 [1] (โ๏ธ) sparkle +274C ; Extended_Pictographic# E0.6 [1] (โ) cross mark +274E ; Extended_Pictographic# E0.6 [1] (โ) cross mark button +2753..2755 ; Extended_Pictographic# E0.6 [3] (โ..โ) question mark..white exclamation mark +2757 ; Extended_Pictographic# E0.6 [1] (โ) exclamation mark +2763 ; Extended_Pictographic# E1.0 [1] (โฃ๏ธ) heart exclamation +2764 ; Extended_Pictographic# E0.6 [1] (โค๏ธ) red heart +2765..2767 ; Extended_Pictographic# E0.0 [3] (โฅ..โง) ROTATED HEAVY BLACK HEART BULLET..ROTATED FLORAL HEART BULLET +2795..2797 ; Extended_Pictographic# E0.6 [3] (โ..โ) plus..divide +27A1 ; Extended_Pictographic# E0.6 [1] (โก๏ธ) right arrow +27B0 ; Extended_Pictographic# E0.6 [1] (โฐ) curly loop +27BF ; Extended_Pictographic# E1.0 [1] (โฟ) double curly loop +2934..2935 ; Extended_Pictographic# E0.6 [2] (โคด๏ธ..โคต๏ธ) right arrow curving up..right arrow curving down +2B05..2B07 ; Extended_Pictographic# E0.6 [3] (โฌ
๏ธ..โฌ๏ธ) left arrow..down arrow +2B1B..2B1C ; Extended_Pictographic# E0.6 [2] (โฌ..โฌ) black large square..white large square +2B50 ; Extended_Pictographic# E0.6 [1] (โญ) star +2B55 ; Extended_Pictographic# E0.6 [1] (โญ) hollow red circle +3030 ; Extended_Pictographic# E0.6 [1] (ใฐ๏ธ) wavy dash +303D ; Extended_Pictographic# E0.6 [1] (ใฝ๏ธ) part alternation mark +3297 ; Extended_Pictographic# E0.6 [1] (ใ๏ธ) Japanese โcongratulationsโ button +3299 ; Extended_Pictographic# E0.6 [1] (ใ๏ธ) Japanese โsecretโ button +1F000..1F003 ; Extended_Pictographic# E0.0 [4] (๐..๐) MAHJONG TILE EAST WIND..MAHJONG TILE NORTH WIND +1F004 ; Extended_Pictographic# E0.6 [1] (๐) mahjong red dragon +1F005..1F0CE ; Extended_Pictographic# E0.0 [202] (๐
..๐) MAHJONG TILE GREEN DRAGON..PLAYING CARD KING OF DIAMONDS +1F0CF ; Extended_Pictographic# E0.6 [1] (๐) joker +1F0D0..1F0FF ; Extended_Pictographic# E0.0 [48] (๐..๐ฟ) <reserved-1F0D0>..<reserved-1F0FF> +1F10D..1F10F ; Extended_Pictographic# E0.0 [3] (๐..๐) CIRCLED ZERO WITH SLASH..CIRCLED DOLLAR SIGN WITH OVERLAID BACKSLASH +1F12F ; Extended_Pictographic# E0.0 [1] (๐ฏ) COPYLEFT SYMBOL +1F16C..1F16F ; Extended_Pictographic# E0.0 [4] (๐
ฌ..๐
ฏ) RAISED MR SIGN..CIRCLED HUMAN FIGURE +1F170..1F171 ; Extended_Pictographic# E0.6 [2] (๐
ฐ๏ธ..๐
ฑ๏ธ) A button (blood type)..B button (blood type) +1F17E..1F17F ; Extended_Pictographic# E0.6 [2] (๐
พ๏ธ..๐
ฟ๏ธ) O button (blood type)..P button +1F18E ; Extended_Pictographic# E0.6 [1] (๐) AB button (blood type) +1F191..1F19A ; Extended_Pictographic# E0.6 [10] (๐..๐) CL button..VS button +1F1AD..1F1E5 ; Extended_Pictographic# E0.0 [57] (๐ญ..๐ฅ) MASK WORK SYMBOL..<reserved-1F1E5> +1F201..1F202 ; Extended_Pictographic# E0.6 [2] (๐..๐๏ธ) Japanese โhereโ button..Japanese โservice chargeโ button +1F203..1F20F ; Extended_Pictographic# E0.0 [13] (๐..๐) <reserved-1F203>..<reserved-1F20F> +1F21A ; Extended_Pictographic# E0.6 [1] (๐) Japanese โfree of chargeโ button +1F22F ; Extended_Pictographic# E0.6 [1] (๐ฏ) Japanese โreservedโ button +1F232..1F23A ; Extended_Pictographic# E0.6 [9] (๐ฒ..๐บ) Japanese โprohibitedโ button..Japanese โopen for businessโ button +1F23C..1F23F ; Extended_Pictographic# E0.0 [4] (๐ผ..๐ฟ) <reserved-1F23C>..<reserved-1F23F> +1F249..1F24F ; Extended_Pictographic# E0.0 [7] (๐..๐) <reserved-1F249>..<reserved-1F24F> +1F250..1F251 ; Extended_Pictographic# E0.6 [2] (๐..๐) Japanese โbargainโ button..Japanese โacceptableโ button +1F252..1F2FF ; Extended_Pictographic# E0.0 [174] (๐..๐ฟ) <reserved-1F252>..<reserved-1F2FF> +1F300..1F30C ; Extended_Pictographic# E0.6 [13] (๐..๐) cyclone..milky way +1F30D..1F30E ; Extended_Pictographic# E0.7 [2] (๐..๐) globe showing Europe-Africa..globe showing Americas +1F30F ; Extended_Pictographic# E0.6 [1] (๐) globe showing Asia-Australia +1F310 ; Extended_Pictographic# E1.0 [1] (๐) globe with meridians +1F311 ; Extended_Pictographic# E0.6 [1] (๐) new moon +1F312 ; Extended_Pictographic# E1.0 [1] (๐) waxing crescent moon +1F313..1F315 ; Extended_Pictographic# E0.6 [3] (๐..๐) first quarter moon..full moon +1F316..1F318 ; Extended_Pictographic# E1.0 [3] (๐..๐) waning gibbous moon..waning crescent moon +1F319 ; Extended_Pictographic# E0.6 [1] (๐) crescent moon +1F31A ; Extended_Pictographic# E1.0 [1] (๐) new moon face +1F31B ; Extended_Pictographic# E0.6 [1] (๐) first quarter moon face +1F31C ; Extended_Pictographic# E0.7 [1] (๐) last quarter moon face +1F31D..1F31E ; Extended_Pictographic# E1.0 [2] (๐..๐) full moon face..sun with face +1F31F..1F320 ; Extended_Pictographic# E0.6 [2] (๐..๐ ) glowing star..shooting star +1F321 ; Extended_Pictographic# E0.7 [1] (๐ก๏ธ) thermometer +1F322..1F323 ; Extended_Pictographic# E0.0 [2] (๐ข..๐ฃ) BLACK DROPLET..WHITE SUN +1F324..1F32C ; Extended_Pictographic# E0.7 [9] (๐ค๏ธ..๐ฌ๏ธ) sun behind small cloud..wind face +1F32D..1F32F ; Extended_Pictographic# E1.0 [3] (๐ญ..๐ฏ) hot dog..burrito +1F330..1F331 ; Extended_Pictographic# E0.6 [2] (๐ฐ..๐ฑ) chestnut..seedling +1F332..1F333 ; Extended_Pictographic# E1.0 [2] (๐ฒ..๐ณ) evergreen tree..deciduous tree +1F334..1F335 ; Extended_Pictographic# E0.6 [2] (๐ด..๐ต) palm tree..cactus +1F336 ; Extended_Pictographic# E0.7 [1] (๐ถ๏ธ) hot pepper +1F337..1F34A ; Extended_Pictographic# E0.6 [20] (๐ท..๐) tulip..tangerine +1F34B ; Extended_Pictographic# E1.0 [1] (๐) lemon +1F34C..1F34F ; Extended_Pictographic# E0.6 [4] (๐..๐) banana..green apple +1F350 ; Extended_Pictographic# E1.0 [1] (๐) pear +1F351..1F37B ; Extended_Pictographic# E0.6 [43] (๐..๐ป) peach..clinking beer mugs +1F37C ; Extended_Pictographic# E1.0 [1] (๐ผ) baby bottle +1F37D ; Extended_Pictographic# E0.7 [1] (๐ฝ๏ธ) fork and knife with plate +1F37E..1F37F ; Extended_Pictographic# E1.0 [2] (๐พ..๐ฟ) bottle with popping cork..popcorn +1F380..1F393 ; Extended_Pictographic# E0.6 [20] (๐..๐) ribbon..graduation cap +1F394..1F395 ; Extended_Pictographic# E0.0 [2] (๐..๐) HEART WITH TIP ON THE LEFT..BOUQUET OF FLOWERS +1F396..1F397 ; Extended_Pictographic# E0.7 [2] (๐๏ธ..๐๏ธ) military medal..reminder ribbon +1F398 ; Extended_Pictographic# E0.0 [1] (๐) MUSICAL KEYBOARD WITH JACKS +1F399..1F39B ; Extended_Pictographic# E0.7 [3] (๐๏ธ..๐๏ธ) studio microphone..control knobs +1F39C..1F39D ; Extended_Pictographic# E0.0 [2] (๐..๐) BEAMED ASCENDING MUSICAL NOTES..BEAMED DESCENDING MUSICAL NOTES +1F39E..1F39F ; Extended_Pictographic# E0.7 [2] (๐๏ธ..๐๏ธ) film frames..admission tickets +1F3A0..1F3C4 ; Extended_Pictographic# E0.6 [37] (๐ ..๐) carousel horse..person surfing +1F3C5 ; Extended_Pictographic# E1.0 [1] (๐
) sports medal +1F3C6 ; Extended_Pictographic# E0.6 [1] (๐) trophy +1F3C7 ; Extended_Pictographic# E1.0 [1] (๐) horse racing +1F3C8 ; Extended_Pictographic# E0.6 [1] (๐) american football +1F3C9 ; Extended_Pictographic# E1.0 [1] (๐) rugby football +1F3CA ; Extended_Pictographic# E0.6 [1] (๐) person swimming +1F3CB..1F3CE ; Extended_Pictographic# E0.7 [4] (๐๏ธ..๐๏ธ) person lifting weights..racing car +1F3CF..1F3D3 ; Extended_Pictographic# E1.0 [5] (๐..๐) cricket game..ping pong +1F3D4..1F3DF ; Extended_Pictographic# E0.7 [12] (๐๏ธ..๐๏ธ) snow-capped mountain..stadium +1F3E0..1F3E3 ; Extended_Pictographic# E0.6 [4] (๐ ..๐ฃ) house..Japanese post office +1F3E4 ; Extended_Pictographic# E1.0 [1] (๐ค) post office +1F3E5..1F3F0 ; Extended_Pictographic# E0.6 [12] (๐ฅ..๐ฐ) hospital..castle +1F3F1..1F3F2 ; Extended_Pictographic# E0.0 [2] (๐ฑ..๐ฒ) WHITE PENNANT..BLACK PENNANT +1F3F3 ; Extended_Pictographic# E0.7 [1] (๐ณ๏ธ) white flag +1F3F4 ; Extended_Pictographic# E1.0 [1] (๐ด) black flag +1F3F5 ; Extended_Pictographic# E0.7 [1] (๐ต๏ธ) rosette +1F3F6 ; Extended_Pictographic# E0.0 [1] (๐ถ) BLACK ROSETTE +1F3F7 ; Extended_Pictographic# E0.7 [1] (๐ท๏ธ) label +1F3F8..1F3FA ; Extended_Pictographic# E1.0 [3] (๐ธ..๐บ) badminton..amphora +1F400..1F407 ; Extended_Pictographic# E1.0 [8] (๐..๐) rat..rabbit +1F408 ; Extended_Pictographic# E0.7 [1] (๐) cat +1F409..1F40B ; Extended_Pictographic# E1.0 [3] (๐..๐) dragon..whale +1F40C..1F40E ; Extended_Pictographic# E0.6 [3] (๐..๐) snail..horse +1F40F..1F410 ; Extended_Pictographic# E1.0 [2] (๐..๐) ram..goat +1F411..1F412 ; Extended_Pictographic# E0.6 [2] (๐..๐) ewe..monkey +1F413 ; Extended_Pictographic# E1.0 [1] (๐) rooster +1F414 ; Extended_Pictographic# E0.6 [1] (๐) chicken +1F415 ; Extended_Pictographic# E0.7 [1] (๐) dog +1F416 ; Extended_Pictographic# E1.0 [1] (๐) pig +1F417..1F429 ; Extended_Pictographic# E0.6 [19] (๐..๐ฉ) boar..poodle +1F42A ; Extended_Pictographic# E1.0 [1] (๐ช) camel +1F42B..1F43E ; Extended_Pictographic# E0.6 [20] (๐ซ..๐พ) two-hump camel..paw prints +1F43F ; Extended_Pictographic# E0.7 [1] (๐ฟ๏ธ) chipmunk +1F440 ; Extended_Pictographic# E0.6 [1] (๐) eyes +1F441 ; Extended_Pictographic# E0.7 [1] (๐๏ธ) eye +1F442..1F464 ; Extended_Pictographic# E0.6 [35] (๐..๐ค) ear..bust in silhouette +1F465 ; Extended_Pictographic# E1.0 [1] (๐ฅ) busts in silhouette +1F466..1F46B ; Extended_Pictographic# E0.6 [6] (๐ฆ..๐ซ) boy..woman and man holding hands +1F46C..1F46D ; Extended_Pictographic# E1.0 [2] (๐ฌ..๐ญ) men holding hands..women holding hands +1F46E..1F4AC ; Extended_Pictographic# E0.6 [63] (๐ฎ..๐ฌ) police officer..speech balloon +1F4AD ; Extended_Pictographic# E1.0 [1] (๐ญ) thought balloon +1F4AE..1F4B5 ; Extended_Pictographic# E0.6 [8] (๐ฎ..๐ต) white flower..dollar banknote +1F4B6..1F4B7 ; Extended_Pictographic# E1.0 [2] (๐ถ..๐ท) euro banknote..pound banknote +1F4B8..1F4EB ; Extended_Pictographic# E0.6 [52] (๐ธ..๐ซ) money with wings..closed mailbox with raised flag +1F4EC..1F4ED ; Extended_Pictographic# E0.7 [2] (๐ฌ..๐ญ) open mailbox with raised flag..open mailbox with lowered flag +1F4EE ; Extended_Pictographic# E0.6 [1] (๐ฎ) postbox +1F4EF ; Extended_Pictographic# E1.0 [1] (๐ฏ) postal horn +1F4F0..1F4F4 ; Extended_Pictographic# E0.6 [5] (๐ฐ..๐ด) newspaper..mobile phone off +1F4F5 ; Extended_Pictographic# E1.0 [1] (๐ต) no mobile phones +1F4F6..1F4F7 ; Extended_Pictographic# E0.6 [2] (๐ถ..๐ท) antenna bars..camera +1F4F8 ; Extended_Pictographic# E1.0 [1] (๐ธ) camera with flash +1F4F9..1F4FC ; Extended_Pictographic# E0.6 [4] (๐น..๐ผ) video camera..videocassette +1F4FD ; Extended_Pictographic# E0.7 [1] (๐ฝ๏ธ) film projector +1F4FE ; Extended_Pictographic# E0.0 [1] (๐พ) PORTABLE STEREO +1F4FF..1F502 ; Extended_Pictographic# E1.0 [4] (๐ฟ..๐) prayer beads..repeat single button +1F503 ; Extended_Pictographic# E0.6 [1] (๐) clockwise vertical arrows +1F504..1F507 ; Extended_Pictographic# E1.0 [4] (๐..๐) counterclockwise arrows button..muted speaker +1F508 ; Extended_Pictographic# E0.7 [1] (๐) speaker low volume +1F509 ; Extended_Pictographic# E1.0 [1] (๐) speaker medium volume +1F50A..1F514 ; Extended_Pictographic# E0.6 [11] (๐..๐) speaker high volume..bell +1F515 ; Extended_Pictographic# E1.0 [1] (๐) bell with slash +1F516..1F52B ; Extended_Pictographic# E0.6 [22] (๐..๐ซ) bookmark..pistol +1F52C..1F52D ; Extended_Pictographic# E1.0 [2] (๐ฌ..๐ญ) microscope..telescope +1F52E..1F53D ; Extended_Pictographic# E0.6 [16] (๐ฎ..๐ฝ) crystal ball..downwards button +1F546..1F548 ; Extended_Pictographic# E0.0 [3] (๐..๐) WHITE LATIN CROSS..CELTIC CROSS +1F549..1F54A ; Extended_Pictographic# E0.7 [2] (๐๏ธ..๐๏ธ) om..dove +1F54B..1F54E ; Extended_Pictographic# E1.0 [4] (๐..๐) kaaba..menorah +1F54F ; Extended_Pictographic# E0.0 [1] (๐) BOWL OF HYGIEIA +1F550..1F55B ; Extended_Pictographic# E0.6 [12] (๐..๐) one oโclock..twelve oโclock +1F55C..1F567 ; Extended_Pictographic# E0.7 [12] (๐..๐ง) one-thirty..twelve-thirty +1F568..1F56E ; Extended_Pictographic# E0.0 [7] (๐จ..๐ฎ) RIGHT SPEAKER..BOOK +1F56F..1F570 ; Extended_Pictographic# E0.7 [2] (๐ฏ๏ธ..๐ฐ๏ธ) candle..mantelpiece clock +1F571..1F572 ; Extended_Pictographic# E0.0 [2] (๐ฑ..๐ฒ) BLACK SKULL AND CROSSBONES..NO PIRACY +1F573..1F579 ; Extended_Pictographic# E0.7 [7] (๐ณ๏ธ..๐น๏ธ) hole..joystick +1F57A ; Extended_Pictographic# E3.0 [1] (๐บ) man dancing +1F57B..1F586 ; Extended_Pictographic# E0.0 [12] (๐ป..๐) LEFT HAND TELEPHONE RECEIVER..PEN OVER STAMPED ENVELOPE +1F587 ; Extended_Pictographic# E0.7 [1] (๐๏ธ) linked paperclips +1F588..1F589 ; Extended_Pictographic# E0.0 [2] (๐..๐) BLACK PUSHPIN..LOWER LEFT PENCIL +1F58A..1F58D ; Extended_Pictographic# E0.7 [4] (๐๏ธ..๐๏ธ) pen..crayon +1F58E..1F58F ; Extended_Pictographic# E0.0 [2] (๐..๐) LEFT WRITING HAND..TURNED OK HAND SIGN +1F590 ; Extended_Pictographic# E0.7 [1] (๐๏ธ) hand with fingers splayed +1F591..1F594 ; Extended_Pictographic# E0.0 [4] (๐..๐) REVERSED RAISED HAND WITH FINGERS SPLAYED..REVERSED VICTORY HAND +1F595..1F596 ; Extended_Pictographic# E1.0 [2] (๐..๐) middle finger..vulcan salute +1F597..1F5A3 ; Extended_Pictographic# E0.0 [13] (๐..๐ฃ) WHITE DOWN POINTING LEFT HAND INDEX..BLACK DOWN POINTING BACKHAND INDEX +1F5A4 ; Extended_Pictographic# E3.0 [1] (๐ค) black heart +1F5A5 ; Extended_Pictographic# E0.7 [1] (๐ฅ๏ธ) desktop computer +1F5A6..1F5A7 ; Extended_Pictographic# E0.0 [2] (๐ฆ..๐ง) KEYBOARD AND MOUSE..THREE NETWORKED COMPUTERS +1F5A8 ; Extended_Pictographic# E0.7 [1] (๐จ๏ธ) printer +1F5A9..1F5B0 ; Extended_Pictographic# E0.0 [8] (๐ฉ..๐ฐ) POCKET CALCULATOR..TWO BUTTON MOUSE +1F5B1..1F5B2 ; Extended_Pictographic# E0.7 [2] (๐ฑ๏ธ..๐ฒ๏ธ) computer mouse..trackball +1F5B3..1F5BB ; Extended_Pictographic# E0.0 [9] (๐ณ..๐ป) OLD PERSONAL COMPUTER..DOCUMENT WITH PICTURE +1F5BC ; Extended_Pictographic# E0.7 [1] (๐ผ๏ธ) framed picture +1F5BD..1F5C1 ; Extended_Pictographic# E0.0 [5] (๐ฝ..๐) FRAME WITH TILES..OPEN FOLDER +1F5C2..1F5C4 ; Extended_Pictographic# E0.7 [3] (๐๏ธ..๐๏ธ) card index dividers..file cabinet +1F5C5..1F5D0 ; Extended_Pictographic# E0.0 [12] (๐
..๐) EMPTY NOTE..PAGES +1F5D1..1F5D3 ; Extended_Pictographic# E0.7 [3] (๐๏ธ..๐๏ธ) wastebasket..spiral calendar +1F5D4..1F5DB ; Extended_Pictographic# E0.0 [8] (๐..๐) DESKTOP WINDOW..DECREASE FONT SIZE SYMBOL +1F5DC..1F5DE ; Extended_Pictographic# E0.7 [3] (๐๏ธ..๐๏ธ) clamp..rolled-up newspaper +1F5DF..1F5E0 ; Extended_Pictographic# E0.0 [2] (๐..๐ ) PAGE WITH CIRCLED TEXT..STOCK CHART +1F5E1 ; Extended_Pictographic# E0.7 [1] (๐ก๏ธ) dagger +1F5E2 ; Extended_Pictographic# E0.0 [1] (๐ข) LIPS +1F5E3 ; Extended_Pictographic# E0.7 [1] (๐ฃ๏ธ) speaking head +1F5E4..1F5E7 ; Extended_Pictographic# E0.0 [4] (๐ค..๐ง) THREE RAYS ABOVE..THREE RAYS RIGHT +1F5E8 ; Extended_Pictographic# E2.0 [1] (๐จ๏ธ) left speech bubble +1F5E9..1F5EE ; Extended_Pictographic# E0.0 [6] (๐ฉ..๐ฎ) RIGHT SPEECH BUBBLE..LEFT ANGER BUBBLE +1F5EF ; Extended_Pictographic# E0.7 [1] (๐ฏ๏ธ) right anger bubble +1F5F0..1F5F2 ; Extended_Pictographic# E0.0 [3] (๐ฐ..๐ฒ) MOOD BUBBLE..LIGHTNING MOOD +1F5F3 ; Extended_Pictographic# E0.7 [1] (๐ณ๏ธ) ballot box with ballot +1F5F4..1F5F9 ; Extended_Pictographic# E0.0 [6] (๐ด..๐น) BALLOT SCRIPT X..BALLOT BOX WITH BOLD CHECK +1F5FA ; Extended_Pictographic# E0.7 [1] (๐บ๏ธ) world map +1F5FB..1F5FF ; Extended_Pictographic# E0.6 [5] (๐ป..๐ฟ) mount fuji..moai +1F600 ; Extended_Pictographic# E1.0 [1] (๐) grinning face +1F601..1F606 ; Extended_Pictographic# E0.6 [6] (๐..๐) beaming face with smiling eyes..grinning squinting face +1F607..1F608 ; Extended_Pictographic# E1.0 [2] (๐..๐) smiling face with halo..smiling face with horns +1F609..1F60D ; Extended_Pictographic# E0.6 [5] (๐..๐) winking face..smiling face with heart-eyes +1F60E ; Extended_Pictographic# E1.0 [1] (๐) smiling face with sunglasses +1F60F ; Extended_Pictographic# E0.6 [1] (๐) smirking face +1F610 ; Extended_Pictographic# E0.7 [1] (๐) neutral face +1F611 ; Extended_Pictographic# E1.0 [1] (๐) expressionless face +1F612..1F614 ; Extended_Pictographic# E0.6 [3] (๐..๐) unamused face..pensive face +1F615 ; Extended_Pictographic# E1.0 [1] (๐) confused face +1F616 ; Extended_Pictographic# E0.6 [1] (๐) confounded face +1F617 ; Extended_Pictographic# E1.0 [1] (๐) kissing face +1F618 ; Extended_Pictographic# E0.6 [1] (๐) face blowing a kiss +1F619 ; Extended_Pictographic# E1.0 [1] (๐) kissing face with smiling eyes +1F61A ; Extended_Pictographic# E0.6 [1] (๐) kissing face with closed eyes +1F61B ; Extended_Pictographic# E1.0 [1] (๐) face with tongue +1F61C..1F61E ; Extended_Pictographic# E0.6 [3] (๐..๐) winking face with tongue..disappointed face +1F61F ; Extended_Pictographic# E1.0 [1] (๐) worried face +1F620..1F625 ; Extended_Pictographic# E0.6 [6] (๐ ..๐ฅ) angry face..sad but relieved face +1F626..1F627 ; Extended_Pictographic# E1.0 [2] (๐ฆ..๐ง) frowning face with open mouth..anguished face +1F628..1F62B ; Extended_Pictographic# E0.6 [4] (๐จ..๐ซ) fearful face..tired face +1F62C ; Extended_Pictographic# E1.0 [1] (๐ฌ) grimacing face +1F62D ; Extended_Pictographic# E0.6 [1] (๐ญ) loudly crying face +1F62E..1F62F ; Extended_Pictographic# E1.0 [2] (๐ฎ..๐ฏ) face with open mouth..hushed face +1F630..1F633 ; Extended_Pictographic# E0.6 [4] (๐ฐ..๐ณ) anxious face with sweat..flushed face +1F634 ; Extended_Pictographic# E1.0 [1] (๐ด) sleeping face +1F635 ; Extended_Pictographic# E0.6 [1] (๐ต) dizzy face +1F636 ; Extended_Pictographic# E1.0 [1] (๐ถ) face without mouth +1F637..1F640 ; Extended_Pictographic# E0.6 [10] (๐ท..๐) face with medical mask..weary cat +1F641..1F644 ; Extended_Pictographic# E1.0 [4] (๐..๐) slightly frowning face..face with rolling eyes +1F645..1F64F ; Extended_Pictographic# E0.6 [11] (๐
..๐) person gesturing NO..folded hands +1F680 ; Extended_Pictographic# E0.6 [1] (๐) rocket +1F681..1F682 ; Extended_Pictographic# E1.0 [2] (๐..๐) helicopter..locomotive +1F683..1F685 ; Extended_Pictographic# E0.6 [3] (๐..๐
) railway car..bullet train +1F686 ; Extended_Pictographic# E1.0 [1] (๐) train +1F687 ; Extended_Pictographic# E0.6 [1] (๐) metro +1F688 ; Extended_Pictographic# E1.0 [1] (๐) light rail +1F689 ; Extended_Pictographic# E0.6 [1] (๐) station +1F68A..1F68B ; Extended_Pictographic# E1.0 [2] (๐..๐) tram..tram car +1F68C ; Extended_Pictographic# E0.6 [1] (๐) bus +1F68D ; Extended_Pictographic# E0.7 [1] (๐) oncoming bus +1F68E ; Extended_Pictographic# E1.0 [1] (๐) trolleybus +1F68F ; Extended_Pictographic# E0.6 [1] (๐) bus stop +1F690 ; Extended_Pictographic# E1.0 [1] (๐) minibus +1F691..1F693 ; Extended_Pictographic# E0.6 [3] (๐..๐) ambulance..police car +1F694 ; Extended_Pictographic# E0.7 [1] (๐) oncoming police car +1F695 ; Extended_Pictographic# E0.6 [1] (๐) taxi +1F696 ; Extended_Pictographic# E1.0 [1] (๐) oncoming taxi +1F697 ; Extended_Pictographic# E0.6 [1] (๐) automobile +1F698 ; Extended_Pictographic# E0.7 [1] (๐) oncoming automobile +1F699..1F69A ; Extended_Pictographic# E0.6 [2] (๐..๐) sport utility vehicle..delivery truck +1F69B..1F6A1 ; Extended_Pictographic# E1.0 [7] (๐..๐ก) articulated lorry..aerial tramway +1F6A2 ; Extended_Pictographic# E0.6 [1] (๐ข) ship +1F6A3 ; Extended_Pictographic# E1.0 [1] (๐ฃ) person rowing boat +1F6A4..1F6A5 ; Extended_Pictographic# E0.6 [2] (๐ค..๐ฅ) speedboat..horizontal traffic light +1F6A6 ; Extended_Pictographic# E1.0 [1] (๐ฆ) vertical traffic light +1F6A7..1F6AD ; Extended_Pictographic# E0.6 [7] (๐ง..๐ญ) construction..no smoking +1F6AE..1F6B1 ; Extended_Pictographic# E1.0 [4] (๐ฎ..๐ฑ) litter in bin sign..non-potable water +1F6B2 ; Extended_Pictographic# E0.6 [1] (๐ฒ) bicycle +1F6B3..1F6B5 ; Extended_Pictographic# E1.0 [3] (๐ณ..๐ต) no bicycles..person mountain biking +1F6B6 ; Extended_Pictographic# E0.6 [1] (๐ถ) person walking +1F6B7..1F6B8 ; Extended_Pictographic# E1.0 [2] (๐ท..๐ธ) no pedestrians..children crossing +1F6B9..1F6BE ; Extended_Pictographic# E0.6 [6] (๐น..๐พ) menโs room..water closet +1F6BF ; Extended_Pictographic# E1.0 [1] (๐ฟ) shower +1F6C0 ; Extended_Pictographic# E0.6 [1] (๐) person taking bath +1F6C1..1F6C5 ; Extended_Pictographic# E1.0 [5] (๐..๐
) bathtub..left luggage +1F6C6..1F6CA ; Extended_Pictographic# E0.0 [5] (๐..๐) TRIANGLE WITH ROUNDED CORNERS..GIRLS SYMBOL +1F6CB ; Extended_Pictographic# E0.7 [1] (๐๏ธ) couch and lamp +1F6CC ; Extended_Pictographic# E1.0 [1] (๐) person in bed +1F6CD..1F6CF ; Extended_Pictographic# E0.7 [3] (๐๏ธ..๐๏ธ) shopping bags..bed +1F6D0 ; Extended_Pictographic# E1.0 [1] (๐) place of worship +1F6D1..1F6D2 ; Extended_Pictographic# E3.0 [2] (๐..๐) stop sign..shopping cart +1F6D3..1F6D4 ; Extended_Pictographic# E0.0 [2] (๐..๐) STUPA..PAGODA +1F6D5 ; Extended_Pictographic# E12.0 [1] (๐) hindu temple +1F6D6..1F6D7 ; Extended_Pictographic# E13.0 [2] (๐..๐) hut..elevator +1F6D8..1F6DF ; Extended_Pictographic# E0.0 [8] (๐..๐) <reserved-1F6D8>..<reserved-1F6DF> +1F6E0..1F6E5 ; Extended_Pictographic# E0.7 [6] (๐ ๏ธ..๐ฅ๏ธ) hammer and wrench..motor boat +1F6E6..1F6E8 ; Extended_Pictographic# E0.0 [3] (๐ฆ..๐จ) UP-POINTING MILITARY AIRPLANE..UP-POINTING SMALL AIRPLANE +1F6E9 ; Extended_Pictographic# E0.7 [1] (๐ฉ๏ธ) small airplane +1F6EA ; Extended_Pictographic# E0.0 [1] (๐ช) NORTHEAST-POINTING AIRPLANE +1F6EB..1F6EC ; Extended_Pictographic# E1.0 [2] (๐ซ..๐ฌ) airplane departure..airplane arrival +1F6ED..1F6EF ; Extended_Pictographic# E0.0 [3] (๐ญ..๐ฏ) <reserved-1F6ED>..<reserved-1F6EF> +1F6F0 ; Extended_Pictographic# E0.7 [1] (๐ฐ๏ธ) satellite +1F6F1..1F6F2 ; Extended_Pictographic# E0.0 [2] (๐ฑ..๐ฒ) ONCOMING FIRE ENGINE..DIESEL LOCOMOTIVE +1F6F3 ; Extended_Pictographic# E0.7 [1] (๐ณ๏ธ) passenger ship +1F6F4..1F6F6 ; Extended_Pictographic# E3.0 [3] (๐ด..๐ถ) kick scooter..canoe +1F6F7..1F6F8 ; Extended_Pictographic# E5.0 [2] (๐ท..๐ธ) sled..flying saucer +1F6F9 ; Extended_Pictographic# E11.0 [1] (๐น) skateboard +1F6FA ; Extended_Pictographic# E12.0 [1] (๐บ) auto rickshaw +1F6FB..1F6FC ; Extended_Pictographic# E13.0 [2] (๐ป..๐ผ) pickup truck..roller skate +1F6FD..1F6FF ; Extended_Pictographic# E0.0 [3] (๐ฝ..๐ฟ) <reserved-1F6FD>..<reserved-1F6FF> +1F774..1F77F ; Extended_Pictographic# E0.0 [12] (๐ด..๐ฟ) <reserved-1F774>..<reserved-1F77F> +1F7D5..1F7DF ; Extended_Pictographic# E0.0 [11] (๐..๐) CIRCLED TRIANGLE..<reserved-1F7DF> +1F7E0..1F7EB ; Extended_Pictographic# E12.0 [12] (๐ ..๐ซ) orange circle..brown square +1F7EC..1F7FF ; Extended_Pictographic# E0.0 [20] (๐ฌ..๐ฟ) <reserved-1F7EC>..<reserved-1F7FF> +1F80C..1F80F ; Extended_Pictographic# E0.0 [4] (๐ ..๐ ) <reserved-1F80C>..<reserved-1F80F> +1F848..1F84F ; Extended_Pictographic# E0.0 [8] (๐ก..๐ก) <reserved-1F848>..<reserved-1F84F> +1F85A..1F85F ; Extended_Pictographic# E0.0 [6] (๐ก..๐ก) <reserved-1F85A>..<reserved-1F85F> +1F888..1F88F ; Extended_Pictographic# E0.0 [8] (๐ข..๐ข) <reserved-1F888>..<reserved-1F88F> +1F8AE..1F8FF ; Extended_Pictographic# E0.0 [82] (๐ขฎ..๐ฃฟ) <reserved-1F8AE>..<reserved-1F8FF> +1F90C ; Extended_Pictographic# E13.0 [1] (๐ค) pinched fingers +1F90D..1F90F ; Extended_Pictographic# E12.0 [3] (๐ค..๐ค) white heart..pinching hand +1F910..1F918 ; Extended_Pictographic# E1.0 [9] (๐ค..๐ค) zipper-mouth face..sign of the horns +1F919..1F91E ; Extended_Pictographic# E3.0 [6] (๐ค..๐ค) call me hand..crossed fingers +1F91F ; Extended_Pictographic# E5.0 [1] (๐ค) love-you gesture +1F920..1F927 ; Extended_Pictographic# E3.0 [8] (๐ค ..๐คง) cowboy hat face..sneezing face +1F928..1F92F ; Extended_Pictographic# E5.0 [8] (๐คจ..๐คฏ) face with raised eyebrow..exploding head +1F930 ; Extended_Pictographic# E3.0 [1] (๐คฐ) pregnant woman +1F931..1F932 ; Extended_Pictographic# E5.0 [2] (๐คฑ..๐คฒ) breast-feeding..palms up together +1F933..1F93A ; Extended_Pictographic# E3.0 [8] (๐คณ..๐คบ) selfie..person fencing +1F93C..1F93E ; Extended_Pictographic# E3.0 [3] (๐คผ..๐คพ) people wrestling..person playing handball +1F93F ; Extended_Pictographic# E12.0 [1] (๐คฟ) diving mask +1F940..1F945 ; Extended_Pictographic# E3.0 [6] (๐ฅ..๐ฅ
) wilted flower..goal net +1F947..1F94B ; Extended_Pictographic# E3.0 [5] (๐ฅ..๐ฅ) 1st place medal..martial arts uniform +1F94C ; Extended_Pictographic# E5.0 [1] (๐ฅ) curling stone +1F94D..1F94F ; Extended_Pictographic# E11.0 [3] (๐ฅ..๐ฅ) lacrosse..flying disc +1F950..1F95E ; Extended_Pictographic# E3.0 [15] (๐ฅ..๐ฅ) croissant..pancakes +1F95F..1F96B ; Extended_Pictographic# E5.0 [13] (๐ฅ..๐ฅซ) dumpling..canned food +1F96C..1F970 ; Extended_Pictographic# E11.0 [5] (๐ฅฌ..๐ฅฐ) leafy green..smiling face with hearts +1F971 ; Extended_Pictographic# E12.0 [1] (๐ฅฑ) yawning face +1F972 ; Extended_Pictographic# E13.0 [1] (๐ฅฒ) smiling face with tear +1F973..1F976 ; Extended_Pictographic# E11.0 [4] (๐ฅณ..๐ฅถ) partying face..cold face +1F977..1F978 ; Extended_Pictographic# E13.0 [2] (๐ฅท..๐ฅธ) ninja..disguised face +1F979 ; Extended_Pictographic# E0.0 [1] (๐ฅน) <reserved-1F979> +1F97A ; Extended_Pictographic# E11.0 [1] (๐ฅบ) pleading face +1F97B ; Extended_Pictographic# E12.0 [1] (๐ฅป) sari +1F97C..1F97F ; Extended_Pictographic# E11.0 [4] (๐ฅผ..๐ฅฟ) lab coat..flat shoe +1F980..1F984 ; Extended_Pictographic# E1.0 [5] (๐ฆ..๐ฆ) crab..unicorn +1F985..1F991 ; Extended_Pictographic# E3.0 [13] (๐ฆ
..๐ฆ) eagle..squid +1F992..1F997 ; Extended_Pictographic# E5.0 [6] (๐ฆ..๐ฆ) giraffe..cricket +1F998..1F9A2 ; Extended_Pictographic# E11.0 [11] (๐ฆ..๐ฆข) kangaroo..swan +1F9A3..1F9A4 ; Extended_Pictographic# E13.0 [2] (๐ฆฃ..๐ฆค) mammoth..dodo +1F9A5..1F9AA ; Extended_Pictographic# E12.0 [6] (๐ฆฅ..๐ฆช) sloth..oyster +1F9AB..1F9AD ; Extended_Pictographic# E13.0 [3] (๐ฆซ..๐ฆญ) beaver..seal +1F9AE..1F9AF ; Extended_Pictographic# E12.0 [2] (๐ฆฎ..๐ฆฏ) guide dog..white cane +1F9B0..1F9B9 ; Extended_Pictographic# E11.0 [10] (๐ฆฐ..๐ฆน) red hair..supervillain +1F9BA..1F9BF ; Extended_Pictographic# E12.0 [6] (๐ฆบ..๐ฆฟ) safety vest..mechanical leg +1F9C0 ; Extended_Pictographic# E1.0 [1] (๐ง) cheese wedge +1F9C1..1F9C2 ; Extended_Pictographic# E11.0 [2] (๐ง..๐ง) cupcake..salt +1F9C3..1F9CA ; Extended_Pictographic# E12.0 [8] (๐ง..๐ง) beverage box..ice +1F9CB ; Extended_Pictographic# E13.0 [1] (๐ง) bubble tea +1F9CC ; Extended_Pictographic# E0.0 [1] (๐ง) <reserved-1F9CC> +1F9CD..1F9CF ; Extended_Pictographic# E12.0 [3] (๐ง..๐ง) person standing..deaf person +1F9D0..1F9E6 ; Extended_Pictographic# E5.0 [23] (๐ง..๐งฆ) face with monocle..socks +1F9E7..1F9FF ; Extended_Pictographic# E11.0 [25] (๐งง..๐งฟ) red envelope..nazar amulet +1FA00..1FA6F ; Extended_Pictographic# E0.0 [112] (๐จ..๐ฉฏ) NEUTRAL CHESS KING..<reserved-1FA6F> +1FA70..1FA73 ; Extended_Pictographic# E12.0 [4] (๐ฉฐ..๐ฉณ) ballet shoes..shorts +1FA74 ; Extended_Pictographic# E13.0 [1] (๐ฉด) thong sandal +1FA75..1FA77 ; Extended_Pictographic# E0.0 [3] (๐ฉต..๐ฉท) <reserved-1FA75>..<reserved-1FA77> +1FA78..1FA7A ; Extended_Pictographic# E12.0 [3] (๐ฉธ..๐ฉบ) drop of blood..stethoscope +1FA7B..1FA7F ; Extended_Pictographic# E0.0 [5] (๐ฉป..๐ฉฟ) <reserved-1FA7B>..<reserved-1FA7F> +1FA80..1FA82 ; Extended_Pictographic# E12.0 [3] (๐ช..๐ช) yo-yo..parachute +1FA83..1FA86 ; Extended_Pictographic# E13.0 [4] (๐ช..๐ช) boomerang..nesting dolls +1FA87..1FA8F ; Extended_Pictographic# E0.0 [9] (๐ช..๐ช) <reserved-1FA87>..<reserved-1FA8F> +1FA90..1FA95 ; Extended_Pictographic# E12.0 [6] (๐ช..๐ช) ringed planet..banjo +1FA96..1FAA8 ; Extended_Pictographic# E13.0 [19] (๐ช..๐ชจ) military helmet..rock +1FAA9..1FAAF ; Extended_Pictographic# E0.0 [7] (๐ชฉ..๐ชฏ) <reserved-1FAA9>..<reserved-1FAAF> +1FAB0..1FAB6 ; Extended_Pictographic# E13.0 [7] (๐ชฐ..๐ชถ) fly..feather +1FAB7..1FABF ; Extended_Pictographic# E0.0 [9] (๐ชท..๐ชฟ) <reserved-1FAB7>..<reserved-1FABF> +1FAC0..1FAC2 ; Extended_Pictographic# E13.0 [3] (๐ซ..๐ซ) anatomical heart..people hugging +1FAC3..1FACF ; Extended_Pictographic# E0.0 [13] (๐ซ..๐ซ) <reserved-1FAC3>..<reserved-1FACF> +1FAD0..1FAD6 ; Extended_Pictographic# E13.0 [7] (๐ซ..๐ซ) blueberries..teapot +1FAD7..1FAFF ; Extended_Pictographic# E0.0 [41] (๐ซ..๐ซฟ) <reserved-1FAD7>..<reserved-1FAFF> +1FC00..1FFFD ; Extended_Pictographic# E0.0[1022] (๐ฐ..๐ฟฝ) <reserved-1FC00>..<reserved-1FFFD> -# Total elements: 3793 +# Total elements: 3537 #EOF |